Golfy PHP framework

Code golf is a type of recreational computer programming competition in which participants strive to achieve the shortest possible source code that implements a certain algorithm. [source]

Picco is a tiny PHP web framework that only takes ~2,3 kB of space and has no dependencies on other libraries, while still providing quite a lot of features, being extensible and reasonably easy to use.




Just install Composer and run:

composer create-project avris/picco-project my_new_project

Then copy-paste parameters.php.dist to parameters.php and fill it with your database access data.

To create database schema and fill it with some dummy data, run:

bin/picco fixtures

Also, configure the directory for the runtime files:

chmod -R 777 run

You can run it using the PHP built-in server:

php -S localhost:8000 -t web/

You should be able to access the project at http://localhost:8000.


Since an example is worth a thousand words, Picco comes with a starter project, which serves as a demo of what you can do with Picco and how to extend it (with RedBeanPHP ORM, cache, logs, translations...). Check out its code!


Class App\Routing should have a public static get method that returns and array of routes. Its keys are route names (same as controller name and view name) and values are regular expressions that correspond to them.

'home' => '/',
'itemList' => '/item/list',
'itemShow' => '/item/(\d+)/show',

Assuming this set of routes: URL / will run the home controller, /item/list – the itemList controller, and anything like /item/1/show, /item/7/show, /item/666/show, etc. – the itemShow controller. Anything other than that will throw a 404 exception.

To generate a route, fetch the router from the DI container and use get method, for instance:

$c->router->get('itemShow', [$item->id])


Controllers are public methods of the App\Controllers class. Their first parameter is allways the DI container, while all the rest are consecutive matches from the route. They should return an array of variables that will be passed on to the view. For instance:

public function itemShow(Container $c, $id)
    $item = $c->db->load('item', $id);
    if (!$item->id) { throw new \Exception("Item $id not found", 404); }

    return ['item' => $item];


Views are .phtml files in the /views directory with a name corresponding to the route/controller name. They have access to the router ($r) and the values returned by the controller (in case of views/itemShow.phtml it's just $item). You can also render partials using $this->render($name, $vars):

<?= $this->render('partial/head', get_defined_vars()); ?>

<h2><?= $item->name ?></h2>
<a href="<?= $r->get('itemDelete', [$item->id])?>" class="btn btn-danger"><?=$t['delete']?></a>

<?= $this->render('partial/foot', get_defined_vars()); ?>


There are 5 constants defined by Picco:

Dependency Injection Container

Atfer Picco sets the system services (router, controllers, view, dispatcher), it runs App\Services::get(Container $c) method. In there you can define your services and parameters:

$c->foo = 'bar';

$c->sizeChecker = new SizeChecker();

$c->parameters = require D.'parameters.php';

$c->db = function($c) {
    $p = $c->parameters['db'];
    $db = new \R;
    $db->setup($p['dsn'], $p['user'], $p['pass']);
    if (!E) { $db->freeze(); }
    return $db;

If what you're setting is a callable, it will be resolved (eagerly, on retrieval) with the container as a parameter.

To retrieve a service/parameter, simply get it, like: $c->db->findAll(...).

Event Dispatcher

To define an event listener:

$c->dispatcher->event_name = function(Container $c, $moreParameters) {
    // do something...

To trigger it:

$c->dispatcher->event_name($c, 'and', 'other', 'params');

Listeners are triggered in the order they were defined. The default listeners for the system events (request, response and error) go off after user defined ones. If any listener in the chain returns anything, the chain isn't executed anymore.

Error handling

Picco handles errors and exceptions in the following way: if E=true (debug mode), the exception is re-thrown, otherwise, the error controller (with the container and that exception as parameters) will we executed, so that you can display a 404/500/whatever error page.

You can overwrite that default behaviour by listening to the error event.

CLI tasks

Similarly to controllers, there is an App\Tasks class, public methods of which are CLI tasks.

If you run, for instance, vendor/bin/picco test foo bar, it will execute App\Tasks::test('foo', 'bar').


Picco's source code is available at Gitlab, feel free to create a pull request, if you can make it shorter or better in any way.

Note: no testing frameworks were used, to run the testsuite go to the library directory and run ./test;