decoupled libraries for php 5.4: the aura project
DESCRIPTION
TRANSCRIPT
Decoupled Libraries for PHP 5.4:The Aura Project
php|tek24 May 2012
joind.in/6495
paul-m-jones.com@pmjones
Read These
About Me• 8 years USAF Intelligence
• PHP since 1999
• Developer, Senior Developer,Team Lead, Architect, VP Engineering
• Aura project, benchmarking series, Solar framework, Savant template system, Zend_DB, Zend_View
• PEAR Group member,ZCE Education Advisory Board
Overview
• Background of libraries vs. frameworks
• Principles of decoupled libraries
• Individual Aura libraries
Some Background
Frameworks: Bad!
• PHP 4 and early PHP 5: “framework” a bad word (“CMS” was ok)
• Libraries and collections: Horde, PEAR, phpLib, phpClasses
• Not unified in operation: different constructor signatures, different method verbiage, different usage idioms, tough to combine
• Started Solar as a PHP 5 E_STRICT library collection
Frameworks: Good!
• Rails: “framework” suddenly acceptable
• Cake, CodeIgniter, Symfony, Zend
• Tapped into user needs
• All delivered as a monolithic whole
• Re-branded Solar libraries as a framework
Frameworks: Good?
• Seeing a shift away from framework devotion
• Want to use just part of a framework? Difficult.
• Download entire framework and try to use one part ...
• ... except it has dependencies.
• Have to set up parts you don’t care about.
Frameworks: Re-Evaluating
• PHP 5.3: Lithium, Symfony 2, Zend Framework 2
• Micro-frameworks: Glue, Limonade, Silex, Slim
• Context, router/dispatcher, HTTP request/response, session manager
• Component systems: Symfony (kind of)
Aura
Rewrite Solar
• Solar Framework: 5+ years old
• Monolithic, tough to use just parts of it
• Extract components and rewrite using PHP 5.4
• Independent, de-coupled library packages
Driving Principles
• Libraries primary, framework secondary
• No package depends on any other (self-contained)
• Tests and assets encapsulated within package
• Some repetition
• Carry data across package boundaries using transfer objects
Use Dependency Injection
• Solar used service locator and universal constructor
• In Aura, that would mean a package dependency
• So, all packages are set up for dependency injection
• You can use any DI container you like
Service Locator Examplesclass Foo{ protected $db; public function __construct() { $this->db = Locator::get('db'); }}
class Foo{ protected $db; public function __construct(Locator $locator) { $this->db = $locator->get('db'); }}
Dependency Injection Examplesclass Foo{ protected $db; public function __construct(Database $db) { $this->db = $db; }}
class Foo{ protected $db; public function setDb(Database $db) { $this->db = $db; }}
Package Organization
Aura.Router
Description
Aura Router is a PHP package that implements web routing. Given a URI path and a copy of
$_SERVER, it will extract controller, action, and parameter values for a specific application route.
Instantiation
// create the map object directly ...$map = require '/path/to/Aura.Router/scripts/instance.php';
// ... or add to autoloader, then:use Aura\Router\Map;use Aura\Router\RouteFactory;$map = new Map(new RouteFactory);
Creating Routes// add a simple named route without params$map->add('home', '/');
// add a simple unnamed route with params$map->add(null, '/{:controller}/{:action}/{:id:(\d+)}');
// add a complex named route$map->add('read', '/blog/read/{:id}{:format}', [ 'params' => [ 'id' => '(\d+)', 'format' => '(\..+)?', ], 'values' => [ 'controller' => 'blog', 'action' => 'read', 'format' => '.html', ],));
Matching Routes// get the incoming request URI path$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
// get the route based on the path and server$route = $map->match($path, $_SERVER);
The `match()` method does not parse the URI or use `$_SERVER` internally. This is because different
systems may have different ways of representing that information; e.g., through a URI object or a
context object. As long as you can pass the string path and a server array, you can use Aura Router
in your application foundation or framework.
Route Values
$route = $map->match('/blog/read/42.json', $_SERVER);var_export($route->values);
// shows these values:[ 'controller' => 'blog', 'action' => 'read', 'id' => '42', 'format' => '.json',]
Dispatching Routes
$params = $route->values;
$class = ucfirst($params['controller']) . 'Page';unset($params['controller']);
$method = $params['action'] . 'Action';unset($params['action']);
$object = new $class();echo $object->$method($params);
Micro-Framework Route
$map->add('read', '/blog/read/{:id}{:format}', [ 'params' => [ 'id' => '(\d+)', 'format' => '(\..+)?', ], 'values' => [ 'controller' => function ($args) { $id = (int) $args['id']; return "Reading blog ID {$id}"; }, 'format' => '.html', ],));
Micro-Framework Dispatcher
$params = $route->values;$controller = $params['controller'];unset($params['controller']);echo $controller($params);
Generating Paths
$map->add('read', '/blog/read/{:id}{:format}' // ...
// ...
$path = $map->generate('read', [ 'id' => 42, 'format' => '.atom',]);
// $path => "/blog/read/42.atom"$href = htmlspecialchars($path, ENT_QUOTES, 'UTF-8');echo '<a href="$href">Atom feed for this blog entry</a>';
Aura.Web
Description
The Aura Web package provides tools to build web page controllers, including an
`AbstractPage` for action methods, a `Context` class for disovering the request environment, and a `Response` transfer
object that describes the eventual HTTP response. (Note that the `Response` transfer object is not itself an HTTP
response.)
Setup
// include all package files,// or add to your autoloaderinclude '/path/to/Aura.Web/src.php';
// create a page controllernamespace Vendor\Package\Web;use Aura\Web\AbstractPage;class Page extends AbstractPage{ // controller body}
Instantiation and Callinguse Vendor\Package\Web\Page;use Aura\Web\Context;use Aura\Web\Response as ResponseTransfer;
$params = [ 'action' => 'hello', 'noun' => 'world', 'format' => '.html',];
$page = new Page( new Context($GLOBALS), new ResponseTransfer, $params);
$response_transfer = $page->exec();
Important Parts
• $params[‘action’] and $params[‘format’]
• $this->context for get/post/files/accept/etc
• $this->response for headers, cookies, content
• $this->data for data to be rendered
• $this->render() for rendering
• (pre|post)(Exec/Action/Render) hooks
Action Method
namespace Vendor\Package\Web;use Aura\Web\AbstractPage;class Page extends AbstractPage{ protected function actionHello($noun = null) { $noun = htmlspecialchars($noun, ENT_QUOTES, 'UTF-8'); $content = "Hello, {$noun}!"; $this->response->setContent($content); }}
Data and Renderingprotected function actionHello($noun = null){ $this->data->noun = $noun;}
protected function render(){ // escape all data $data = []; foreach ((array) $this->data as $key => $val) { $data[$key] = htmlspecialchars($val, ENT_QUOTES, 'UTF-8'); } // what format? if ($this->getFormat() == '.json') { // json $this->response->setContentType('application/json'); $content = json_encode([ 'success' => true, 'content' => $data, ]); } else { // html by default $content = "Hello, {$data['noun']}!"; } $this->response->setContent($content);}
Response Delivery
• ResponseTransfer object is *not* an HTTP response
• Must convert it to a real HTTP response
• Allows any HTTP library, or none
Delivery Code$headers = $response_transfer->getHeaders();foreach ($headers as $label => $value) { header($label, $value);}
$cookies = $response_transfer->getCookies();foreach ($cookies as $cookie) { setcookie( $cookie['value'], $cookie['expire'], $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly'] );}
echo $response_transfer->getContent();
Full-Stack Framework
Packages
• Aura.Autoload
• Aura.Cli
• Aura.Di
• Aura.Http
• Aura.Marshal
• Aura.Router
• Aura.Signal
• Aura.Sql
• Aura.View
• Aura.Web
Aura.Framework and System
• Aura.Framework package to connect all the others together into a cohesive full-stack framework
• “System” package to provide a project skeleton
Conclusion
• Frameworks vs libraries
• Principles and packages
• Still much to extract from Solar: filters, session manager, i18n/l10n
• Google Beta
• Join the project: auraphp.github.com
Thanks!
joind.in/6495
auraphp.github.com
paul-m-jones.com@pmjones