jimbojsb / adore
A lightweight Action-Domain-Responder framework based on Aura components
Requires
- aura/router: 2.*
- aura/web: 2.*
This package is auto-updated.
Last update: 2024-10-18 04:55:52 UTC
README
Adore is an extremely lightweight implementation of the Action-Domain-Responder pattern. Currently, this code "works", but should be be considered a pre-alpha draft. I dont' envision it growing in feature complexity, but the API could change considerably based on discoveries made when actually building applications with Adore.
Background
Action-Domain-Responder is an "improvement" on the traditional idea of MVC as it relates to web applications. Paul Jones defines it in detail here: Action-Domain-Responder. The paper defines patterns and separation of concerns, but does not provide any concrete implementation details, and specifically addresses a lack of "glue" for the various concepts. Adore attempts to be that glue, providing routing, dispatching, and response handling. Additionally, Adore provides a way to wire together the action and response components. Adore is provided in it's entirety as one PHP file containing one class and two traits.
Dependencies
In an attempt to not reinvent the wheel, Adore relies on Aura\Web
and Aura\Router
rather than providing a
bespoke implementation of these functions. This allows Adore to be extremely concise and focus on solving only the unsolved
portion of the problem.
Installation & Runtime Configuration
Adore, while provided as a single PHP file, does have external dependencies. As such, the only recommended installation
method is with Composer. Add the following to your composer.json
{ "require": { "jimbojsb/adore": "dev-master" } }
Bootstrapping
The first step to using Adore is to create a new instance of Adore\Application
. This would normally happen in your index.php
.
require_once __DIR__ . '/../vendor/autoload.php'; $app = new Adore\Application;
Wiring Actions & Responders
Adore assumes you'll generally provide your own methods for loading Action and Responder classes, presumably PSR autoloading. Adore still needs to know how to resolve the names of these classes. This is done with factory closures. The following examples assumes but does not enforce a basic application namespace organization.
$app->setActionFactory(function($actionName) { $properActionName = ucfirst($actionName) . "Action"; $className = "\\MyApp\\Action\\$properActionName"; return new $className; }); $app->setResponderFactory(function($responderName) { $properResponderName = ucfirst($responderName) . "Responder"; $className = "\\MyApp\\Responder\\$properResponderName"; return new $className; });
Any initial dependency injection needed for your Actions & Responders should be handled within these closures.
NOTE: The responder factory your specify will be wrapped in another closure to aid in injecting a Response
object.
Error Handling
Adore attempts to provide some rudimentary error handling capabilities. This is done by providing an action name to dispatch if no route is matched, and additional one to dispatch in the case an exception is thrown during execution. The actual names of these actions can be any name you like, and they are resolved through the same action factory provided by you.
$app->setErrorAction("Error"); $app->setNotFoundAction("Notfound");
Routing
Adore proxies Aura\Router
. For a full routing syntax, refer to the Aura\Router
documentation.
Routes take at a minumum, a path to match and an action name. Optionally, you may specificy which HTTP verbs routes match
to, as well as additional parameters to be injection into the action.
// Route with plain path matching $app->addRoute("/", "Homepage"); // Route with url parameters $app->addRoute("/blog/post/{post_slug}", "BlogPost"); // Route that will only match on a POST request $app->addRoute("/login", "Login", ["POST"]); // Route with additional hard-coded parameters $app->addRoute("/about", "StaticContent", ["file" => "about.md"]);
Dispatching
Once you've configured your Adore\Application
instance, actually dispatching it is as simple as calling:
$app->run();
The order of operations for dispatching a request is roughly as follows:
- Create a new
Aura\Web\Request
object - Create a new
Aura\Router
object and load it with the route definitions - Route the request
- Create the appropriate action object using the supplied action factory
- Inject params, request, responder factory onto the action
- Call
_init()
on the action - Dispatch the action, which should return a responder
- Invoke the responder
- Get the response object from the responder and send it to the client
Creating Actions & Responders
Adore attempts to have a very small footprint on your code. It provides traits instead of interfaces or abstract classes so your application inheritance tree can be completely up to you. The traits that Adore provides are mainly for dependency injection and convenience. They are not strictly required as PHP cannot type check traits, but should you choose not to use them, you would need to implement their methods and properties manually.
Actions and Responders in Adore are designed to be invokeable objects. The main entry point for execution of your code will be the __invoke
method.
All methods and properties on the Adore traits are prefixed with _ to avoid any name conflicts with your code.
Actions
An action should be a simple PHP class that uses Adore\ActionTrait
and contains an _invoke()
method. Additionally,
you may provide an _init()
method if you need to do additional setup before dispatch. _init()
is called after
the action has been fully wired. Your action is expected to return an invokeable responder.
class MyAction { use \Adore\ActionTrait; public function __invoke() { // business logic here return new Responder($data); } }
If your _invoke
method has arguments, Adore will attempt to match those named properties with keys from the request
parameters such that the params are passed to your function, as a convenience.
The Adore\ActionTrait
provides the following protected properties:
_params
- An array of all parameters derived from the routing process_request
- An instance ofAura\Web\Request
that represents the current HTTP request context. For a complete listing ofAura\Web\Request
features, please see theAura\Web
documentation
The Adore\ActionTrait
has a reference to the Responder factory, and provides a helper method ->getResponder($responderName)
that can generate wired and initialized responders from inside your action. This is the preferred method to instantiate a Responder.
Responders
A responder should be a simple PHP class that uses Adore\ResponderTrait
and contains an _invoke()
method. Additionally,
you may provide an _init()
method if you need to do additional setup before dispatch. _init()
is called after
the responder has been fully wired. Your responder will be pre-injected with a Aura\Web\Response
object. It should
act on that object appropriately. After invoking your responder, Adore will use that response object to send a properly
formed HTTP response to the client.
class MyResponder { use \Adore\ResponderTrait; public function __invoke() { // presentation logic here $this->_response->content->set("Hello World"); } }
The Adore\ResponderTrait
provides the following protected properties:
_response
- An instance ofAura\Web\Request
. For a full listing of the functionality of theAura\Web\Response
object, please see theAura\Web
documentation.
Known Issues
- Error action handling and exception catching is barely working if at all
- Relies heavily on
Aura
components. Perhaps this could be improved in light of PSR-7?