avris/picco

Golfy PHP framework

v2.0.0 2018-02-17 21:24 UTC

This package is auto-updated.

Last update: 2024-04-11 04:00:03 UTC


README

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.

Features

  • Dependency Injection
  • Event dispatcher: system events request, response and error
  • REST-ful two-way routing with parameter recognition
  • Basic error handling
  • Views in .phtml files
  • CLI tasks

Requirements

  • Code should be as short as possible, but:
  • It shouldn't look golfed from the outside, i.e. "nice" namespaces, classnames and public method names are required
  • Compromise between readability and brevity: new lines should be used to separate significant blocks of code, but indentation is unwanted
  • Picco is a Composer package with no dependencies
  • No warnings allowed either

Installation

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.

Usage

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!

Routing

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

Controllers are public methods of the App\Controllers class. Their first parameter is always 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

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()); ?>

Constants

There are 5 constants defined by Picco:

  • D -- root directory of the project, useful for instance for loading configuration files, setting up filesystem cache, etc.
  • F -- base of the URL (like picco/web/index.php), useful for generating links to other pages, if you don't want to use the router.
  • B -- base of the URL, but without front controller (like picco/web/), useful for including assets, eg. <img src=<?=B?>logo.png.
  • R -- current route
  • E -- current environment: true for dev/debug mode (default), false for prod mode (if APP_DEBUG env var is set to 0).

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) {
    define('REDBEAN_MODEL_PREFIX','\\App\\Model\\');
    $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').

Contributing

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;

Copyright