aura / dispatcher
Creates objects from a factory and invokes methods using named parameters; also provides a trait for invoking closures and object methods with named parameters.
Installs: 80 349
Dependents: 3
Suggesters: 0
Security: 0
Stars: 38
Watchers: 15
Forks: 8
Open Issues: 0
Requires
- php: >=5.4.0
README
Provides tools to map arbitrary names to dispatchable objects, then to dispatch to those objects using named parameters. This is useful for invoking controller and command objects based on path-info parameters or command line arguments, for dispatching to closure-based controllers, and for building dispatchable objects from factories.
Foreword
Installation
This library requires PHP 5.4 or later; we recommend using the latest available version of PHP as a matter of principle. It has no userland dependencies.
It is installable and autoloadable via Composer as aura/dispatcher.
Alternatively, download a release or clone this repository, then require or include its autoload.php file.
Quality
To run the unit tests at the command line, issue phpunit
at the package root. (This requires PHPUnit to be available as phpunit
.)
This library attempts to comply with PSR-1, PSR-2, and PSR-4. If you notice compliance oversights, please send a patch via pull request.
Community
To ask questions, provide feedback, or otherwise communicate with the Aura community, please join our Google Group, follow @auraphp on Twitter, or chat with us on #auraphp on Freenode.
Getting Started
Overview
First, an external routing mechanism such as Aura.Router or a micro-framework router creates an array of parameters. (Alternatively, the parameters may be an object that implements ArrayAccess).
The parameters are then passed to the Dispatcher. It examines them and picks an object to invoke with those parameters, optionally with a method determined by the parameters.
The Dispatcher then examines the returned result from that first invocation. If the result is itself a dispatchable object, the Dispatcher will recursively dispatch the result until something other than a dispatchable object is returned.
When a non-dispatchable result is returned, the Dispatcher stops recursion and returns the non-dispatchable result.
Closures and Invokable Objects
First, we tell the Dispatcher to examine the controller
parameter to find
the name of the object to dispatch to:
<?php use Aura\Dispatcher\Dispatcher; $dispatcher = new Dispatcher; $dispatcher->setObjectParam('controller'); ?>
Next, we set a closure object into the Dispatcher using setObject()
:
<?php $dispatcher->setObject('blog', function ($id) { return "Read blog entry $id"; }); ?>
We can now dispatch to that closure by using the name as the value for
the controller
parameter:
<?php $params = [ 'controller' => 'blog', 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
The same goes for invokable objects. First, define a class with an
__invoke()
method:
<?php class InvokableBlog { public function __invoke($id) { return "Read blog entry $id"; } } ?>
Next, set an instance of the object into the Dispatcher:
<?php $dispatcher->setObject('blog', new InvokableBlog); ?>
Finally, dispatch to the invokable object (the parameters and logic are the same as above):
<?php $params = [ 'controller' => 'blog', 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
Object Method
We can tell the Dispatcher to examine the params for a method to call on the
object. This method will take precedence over the __invoke()
method on an
object, if such a method exists.
First, tell the Dispatcher to examine the value of the action
param to
find the name of the method it should invoke.
<?php $dispatcher->setMethodParam('action'); ?>
Next, define the object we will dispatch to; note that the method is read()
instead of __invoke()
.
<?php class Blog { public function read($id) { return "Read blog entry $id"; } } ?>
Then, we set the object into the Dispatcher ...
<?php $dispatcher->setObject('blog', new Blog); ?>
... and finally, we invoke the Dispatcher; we have added an action
parameter with the name of the method to invoke:
<?php $params = [ 'controller' => 'blog', 'action' => 'read', 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
Embedding Objects in Parameters
If you like, you can place dispatchable objects directly in the parameters.
(This is often how micro-framework routers work.) For example, let's put a
closure into the controller
parameter; when we invoke the Dispatcher, it
will invoke that closure.
<?php $params = [ 'controller' => function ($id) { return "Read blog entry $id"; }, 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
The same is true for invokable objects ...
<?php $params = [ 'controller' => new InvokableBlog, 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
... and for object-methods:
<?php $params = [ 'controller' => new Blog, 'action' => 'read', 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
Recursion and Lazy Loading
The Dispatcher is recursive. After dispatching to the first object, if that object returns a dispatchable object, the Dispatcher will re-dispatch to that object. It will continue doing this until the returned result is not a dispatchable object.
Let's turn the above example of an invokable object in the Dispatcher into a lazy-loaded instantiation. All we have to do is wrap the instantiation in another dispatchable object (in this example, a closure). The benefit of this is that we can fill the Dispatcher with as many objects as we like, and they won't get instantiated until the Dispatcher calls on them.
<?php $dispatcher->setObject('blog', function () { return new Blog; }); ?>
Then we invoke the dispatcher with the same params as before.
<?php $params = [ 'controller' => 'blog', 'action' => 'read', 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
What happens is this:
-
The Dispatcher finds the 'blog' dispatchable object, sees that it is a closure, and invokes it with the params.
-
The Dispatcher examines the result, sees the result is a dispatchable object, and invokes it with the params.
-
The Dispatcher examines that result, sees that it is not a callable object, and returns the result.
Sending The Array Of Params Directly
Sometimes you will want to send the entire array of parameters directly to the object method or closure, as opposed to matching parameter keys with function argument names. To do so, name a key in the parameters array for the argument name that will receive them, and then set the parameters array into itself using that name. If may be easier to do this by reference, or by copy, depending on your needs.
<?php // a dispatchable closure that takes an array of params directly, // not the individual params by keys matching argument names $dispatcher->setObject('blog', function ($params) { return "Read blog entry {$params['id']}" }); // the initial params $params = [ 'controller' => 'blog', 'action' => 'read', 'id' => 88, ]; // set a params reference into itself; this corresponds with the // 'params' closure argument $params['params'] =& $params; // dispatch $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
Refactoring To Architecture Changes
The Dispatcher is built with the idea that some developers may begin with a micro-framework architecture, and evolve over time toward a full-stack architecture.
At first, the developer uses closures embedded in the params:
<?php $dispatcher->setObjectParam('controller'); $params = [ 'controller' => function ($id) { return "Read blog entry $id"; }, 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
After adding several controllers, the developer is likely to want to keep the routing configurations separate from the controller actions. At this point the developer may start putting the controller actions in the Dispatcher:
<?php $dispatcher->setObject('blog', function ($id) { return "Read blog entry $id!"; }); $params = [ 'controller' => 'blog', 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
As the number and complexity of controllers continues to grow, the developer may wish to put the controllers into their own classes, lazy-loading along the way:
<?php class Blog { public function __invoke($id) { return "Read blog entry $id"; } } $dispatcher->setObject('blog', function () { return new Blog; }); $params = [ 'controller' => 'blog', 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
Finally, the developer may collect several actions into a single controller,
keeping related functionality in the same class. At this point the developer
should call setMethodParam()
to tell the Dispatcher where to find the
method to invoke on the dispatchable object.
<?php class Blog { public function browse() { // ... } public function read($id) { return "Read blog entry $id"; } public function edit($id) { // ... } public function add() { // ... } public function delete($id) { // ... } } $dispatcher->setMethodParam('action'); $dispatcher->setObject('blog', function () { return new Blog; }); $params = [ 'controller' => 'blog', 'action' => 'read', 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>
Construction-Based Configuration
You can set all dispatchable objects, along with the object parameter name and the method parameter name, at construction time. This makes it easier to configure the Dispatcher object in a single call.
<?php $object_param = 'controller'; $method_param = 'action'; $objects = [ 'blog' => function () { return new BlogController; }, 'wiki' => function () { return new WikiController; }, 'forum' => function () { return new ForumController; }, ]; $dispatcher = new Dispatcher($objects, $object_param, $method_param); ?>
Intercessory Dispatch Methods
Sometimes your classes will have an intercessory method that picks an action to run, either on itself or on another object. This package provides an InvokeMethodTrait to invoke a method on an object using named parameters. (The InvokeMethodTrait honors protected and private scopes.)
<?php use Aura\Dispatcher\InvokeMethodTrait; class Blog { use InvokeMethodTrait; public function __invoke(array $params) { $action = isset($params['action']) ? $params['action'] : 'index'; $method = 'action' . ucfirst($action); return $this->invokeMethod($this, $method, $params); } protected function actionRead($id = null) { return "Read blog entry $id"; } } ?>
You can then dispatch to the object as normal, and it will determine its own logical flow.
<?php $dispatcher->setObject('blog', function () { return new Blog; }); $params = [ 'controller' => 'blog', 'action' => 'read', 'id' => 88, ]; $result = $dispatcher($params); // or call __invoke() directly echo $result; // "Read blog entry 88" ?>