Puppy Application
HTTP package for Puppy framework, (*1)
, (*2)
Puppy Application is like an HTTP controller. It parses the current request and calls a matched controller., (*3)
Application basic logic:, (*4)
- specify controllers for all specific requests
- add services
- manage services and controllers from modules
- manage middlewares
- pre/post-process
- manage application error (todo)
Installation
$ composer require raphhh/puppy-application
Basic usage
Puppy needs a config and a request to run. Then you can add a controller., (*5)
use Puppy\Application;
use Puppy\Config\Config;
use Symfony\Component\HttpFoundation\Request;
$puppy = new Application(new Config(), Request::createFromGlobals());
$puppy->get('hello', function(){
return 'Hello world!';
});
$puppy->run(); //good dog! :)
For the config, you can use any class implementing \ArrayAccess, instead of the Puppy Config., (*6)
Routes
A route simply matches a request to a controller. When you call this uri, that controller will be called., (*7)
How to add controller?
Puppy\Application has some simple methods to help you to declare your controllers., (*8)
$puppy->get($uri, $controller); //filter on GET http method
$puppy->post($uri, $controller); //filter on POST http method
$puppy->json($uri, $controller); //filter on JSON format
$puppy->any($uri, $controller); //filter only on the requested uri
$puppy->filter($filter, $controller); //specific filter as callable
How to define the route pattern?
A pattern of a route is a regex which will match with a specific request uri., (*9)
Only one of your controllers will be called when its pattern will match with the request uri. So, depending of the uri, the code of your controller will be executed., (*10)
$puppy->get('my/page/(\d)', $controller);
To simplify your live and have more readable uri, you can define some binding. For example:, (*11)
$puppy->get('my/specific/:uri', $controller)->bind('uri');
By default, the binding will accept a pattern with string, numeric, '_' and '-'. But you can add a specific regex:, (*12)
$puppy->get('my/page/:index', $controller)->bind('index', '\d');
To simplify your life a little bit more, you can use predefined bindings. For example:, (*13)
$puppy->get(':all', $controller); //every uri
$puppy->get(':home', $controller); //home uri (empty or '/')
$puppy->get(':slug', $controller); //string uri, with numeric, '_' and '-'
$puppy->get(':id', $controller); //any unsigned int, except 0
$puppy->get(':index', $controller); //any unsigned int
$puppy->get(':lang', $controller); //two letters lower case, eventually followed by hyphen and two letters upper case (e.i. fr-FR)
$puppy->get(':datetime', $controller); //datetime with format yyyy-mm-ddThh:mm:ss or yyyy-mm-ddThh:mm:ss+hh:ss
$puppy->get(':date', $controller); //date with format yyyy-mm-dd
$puppy->get(':time', $controller); //time with format hh:mm:ss
How to specify other request constraints?
When you set controllers with the Puppy methods, you can continue to specify some other rules., (*14)
$puppy->get($uri, $controller)->content('xml/application');
$puppy->json($uri, $controller)->method('post');
All the constraints can be linked together for a same route., (*15)
$puppy->any('my/page/:index', $controller)
->bind('index', '\d')
->method('post')
->content('json/application');
You can also restrict your route to a specific path namespace., (*16)
$puppy->get('users', $controller)->restrict('admin'); // this is accessible only with the request uri 'admin/users'
How to group routes?
You can also group several of your routes to process to some common actions., (*17)
$puppy->group([
$puppy->get($uri1, $controller1),
$puppy->get($uri2, $controller2),
])
->bind('index', '\d')
->method('post')
->restrict('admin');
Controllers
What is a controller?
A controller is any callable., (*18)
For example, a controller can be a closure:, (*19)
$puppy->get('hello', function(){
...
});
or it can be a class method:, (*20)
$puppy->get('hello', array($controller, 'method'));
or what you want that is callable..., (*21)
What will a controller return?
String
Your controller will return the response to send to the client. This can be a simple string., (*22)
$puppy->get('hello', function(){
return '<h1>Hello world!</h1>';
});
Response
But more powerful, this can be also a Response, which will manage also the http header., (*23)
$puppy->get('hello', function(){
return new Response('<h1>Hello world!</h1>');
});
To help you to manage some common actions, AppController has some cool methods for you. See AppController section., (*24)
Which arguments will receive the controller?
The controller receive two kinds of arguments, depending on what you want., (*25)
The pattern matches
If you want to receive the list of matches between pattern and uri, you must specify the param "array $args"., (*26)
$puppy->get('hello/:all', function(array $args){
return $args['all']; //will return the value "world" for the uri "/hello/world"
});
If you use binding, the key of your matched arg is the alias without ":". For example, binding ":id" can be retrieved with the key "id"., (*27)
The Services
If you want to have the services container, you must specify the param "ArrayAccess $services"., (*28)
$puppy->get('hello', function(\ArrayAccess $services){
...
});
Of course, you can have the services with the matched args., (*29)
$puppy->get('hello', function(array $args, Container $services){
...
});
The order of params has no importance!, (*30)
You can also specify which service you want. You just have to name it in the params. (The name of the param must be exactly the name of your service.), (*31)
$puppy->get('hello', function(Request $request){
return 'You ask for the uri "'.htmlentities($request->getRequestUri());
});
See services section to know which services are available by default., (*32)
What a controller can do?
A controller manages the HTTP response. So, to help you in common actions, you can use Puppy\Controller\AppController. This is a simple class that contains some utilities methods., (*33)
Which are the AppController methods?
Methods are for example:, (*34)
$appController->render($templateFile);
$appController->redirect($url);
$appController->call($uri);
$appController->abort();
$appController->flash()->get($myMessage);
$appController->retrieve($key);
$appController->getService($serviceName);
How to implement AppController?
There are three ways to use it., (*35)
As binded class
First, if you simply use a closure as controller, all the methods of AppController will be bound to your closure., (*36)
$puppy->get('hello', function(){
return $this->abort();
});
As parent class
Second, you can create your Controller class which extends AppController., (*37)
use Puppy\Controller\AppController;
class MyController extends AppController
{
public function myAction()
{
return $this->abort();
}
}
As service class
Third, you can ask for AppController as a service in the params., (*38)
$puppy->get('hello', function(AppController $appController){
return $appController->abort();
});
See services section for more information., (*39)
Is there no dependencies?
Be careful, if you use AppController::flash(), you will need a service 'session'. And if your use AppController::rend(), you will need a service 'template'., (*40)
To simplify your life, you have two solutions., (*41)
Work with Puppy
Directly work with raphhh/puppy., (*42)
Include everything you need., (*43)
Work with Puppy/Service
You can work directly with Puppy\Service\Session and Puppy\Service\Template. These two services fit perfectly with the AppController., (*44)
First, you need to include their package to your project. Then, you just need to add these two services with Puppy\Application::addService(). See services section for more information., (*45)
Middlewares
What is a middleware?
A middleware is just a code executed before the controller. The middleware will trigger the call of its associated controller., (*46)
For example, imagine you want to call a controller only for users with admin rights. Then, your middleware can control this for you by filtering only accessible controllers., (*47)
How to implement a middleware?
Just by linking a callable to a controller., (*48)
$puppy->get($uri, $controller)->filter(function(){
return true;
});
A middleware works like a controller: it can be any callable. The only difference is that a middleware must return a boolean indicating if we can call the controller., (*49)
You can also add any middleware you want. They will be executed in the same order. But, the chain will stop when a middleware returns false., (*50)
$puppy->get($uri, $controller)
->filter($middleware1)
->filter($middleware2)
->filter($middleware3);
Like a controller, a middleware works with the same system of dynamic params. You can retrieve any service you want. You just have to specify it in the params., (*51)
$puppy->get($uri, $controller)->filter(function(Request $request){
...
});
You can also apply middleware on a group of route. See route section for more information about group method., (*52)
$puppy->group($routes)
->filter($middleware1)
->filter($middleware2);
Pre and post processing
You can easily process on the HTTP request and response before and after the routing., (*53)
The method 'before' is called before the routing and receive the HTTP request., (*54)
$puppy->before(function(Request $request){
...
});
The method 'after' is called after the routing and receive the HTTP response., (*55)
$puppy->after(function(Response $response){
...
});
You can add as many processing as you want., (*56)
$puppy->before($callback1)
->before($callback2)
->after($callback3)
->after($callback4);
Note that if you give a Closure as process, the context will be bound with Application., (*57)
Mirrors
You can require that some uri be analysed like there were another ones. These uri will be like a mirror that points to a specific predefined route., (*58)
For example, you want your request uri "mail" points to "contact". "contact" is a real route, and "mail" must do exactly the same. So, if we the request uri is "mail", the route "contact" will be called., (*59)
$puppy->mirror('mail', 'contact'); //request uri "mail" will point to "contact"
Mirrors accept also dynamic params., (*60)
$puppy->mirror('mail/:id', 'contact/{id}');
Services
What is a service?
A service is a class which will be present in all your controllers., (*61)
By default, Puppy adds some services:
* config (an object with the config according to your env)
* request (an object with all the context of the current request. Just be aware that current request could be not the master request.)
* requestStack (an object with all the requests used during the process. you can retrieve the current and the master request.)
* router (an object which can analyse all the defined routes and controllers of your app)
* frontController (instance of the class Puppy\Controller\AppController)
* appController (instance of the class Puppy\Controller\AppController)
* retriever (instance of the class Puppy\Helper\Retriever), (*62)
You can add any services you want, like for example a templating library, an ORM, ..., (*63)
How to add a service?
Because Puppy uses Pimple as services container, a service must be added from a callable., (*64)
$puppy->addService('serviceName', function(Container $services){
return new MyService();
});
How to retrieve any services?
From Application
If you work with the Application object., (*65)
$puppy->getService('myService');
From AppController
If you work with the AppController object., (*66)
$appController->getService('myService');
See AppController section for more information about this class., (*67)
From any controller
The more powerful way is to retrieve dynamically your service in the params of the controller. You just have to specify a param with the same name as your service., (*68)
//you want the request?
$puppy->get('hello', function(Request $request){
...
});
//you want the request and the config?
$puppy->get('hello', function(Request $request, \ArrayAccess $config){
...
});
//you want the router and the appController?
$puppy->get('hello', function(Router $router, AppController $appController){
...
});
The order of the params does not matter., (*69)
Modules
What is a module?
A module is a class that wraps a specific list of services an controllers. The module receives the Application in argument. So, your module class can add any services or controllers that are in your package., (*70)
//your module class
class MyModule implements \Puppy\Module\IModule{
function init(\Puppy\Application $puppy){
$puppy->get('my-module/:all', function(){
return 'This is my module';
});
}
}
//add the module to the Application
$puppy->addModule(new MyModule());
How to load dynamically modules?
You can load dynamically all the modules of your project. You just have to create classes with two specifications:
- The name of the class has to end with 'Module'.
- The class must extend Puppy\Module\IModule., (*71)
Application::initModules(new ModulesLoader()) will load for you the modules of your project (by default modules in "src" and "vendor" dir). You can use a cache loader with ModulesLoaderProxy(). The search in the project will be done only on the first call and be cached into the filesystem., (*72)
Application error (todo)
You can add an error/exception handler which will be called for every error (event fatal error) and not caught exception., (*73)
$puppy->error(function(\Exception $exception){
...
});
If the script is interrupted because of a fatal error, you can specify a controller to send a correct HTTP header., (*74)
$puppy->die($controller);
It is recommended to display the error in the dev env only (display_error), but to intercept always the error (error_reporting). See the PHP doc for more information about the error., (*75)
Config options
- 'module.directories' => define the directories where to find dynamically modules. (used by ModuleFactory)
- 'module.cache.enable' => active the file cache of modules loader. (used by ModuleFactory)
- 'module.cache.path' => set the path to save the cached files of the modules loader. (used by ModuleFactory)