avris / picco
Golfy PHP framework
Requires
- php: ^7.0
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
anderror
- 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 (ifAPP_DEBUG
env var is set to0
).
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
- Author: Andrzej Prusinowski (Avris.it)
- Licence: MIT