Upload
pmjones88
View
6.035
Download
1
Embed Size (px)
DESCRIPTION
Citation preview
Decoupled Libraries for PHP 5.4:The Aura Project
PHP Conference Brazil 2012
auraphp.github.com
paul-m-jones.com@pmjones
Saturday, December 1, 12
Read These
Saturday, December 1, 12
About Me• PHP since 1999
• Developer, Senior Developer,Team Lead, Architect, VP Engineering
• Aura project, benchmarking series, Solar framework, Savant template system
• Zend_DB, Zend_View
• ZCE Education Advisory Board,PHP-FIG Voting Member (PSR-0/1/2)
Saturday, December 1, 12
Overview
• Background of libraries vs. frameworks
• Principles of decoupled libraries
• Examples: individual Aura libraries
Saturday, December 1, 12
Background:Libraries vs Frameworks
Saturday, December 1, 12
Frameworks: Bad!
• PHP 3, 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 (solarphp.com) in 2005 as a library collection(PHP 5, E_STRICT)
Saturday, December 1, 12
Frameworks: Good!
• Rails: “framework” suddenly acceptable
• Cake, CodeIgniter, Symfony, Zend
• Tapped into developer needs
• All delivered as a monolithic whole
• Re-branded Solar libraries as a framework
Saturday, December 1, 12
Frameworks: Good?
• 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.
Saturday, December 1, 12
Frameworks: Re-Evaluating
• PHP 5.3 “full stack”: Lithium, Symfony 2, Zend Framework 2, others
• Micro-frameworks: Glue, Limonade, Silex, Slim
• Context, router/dispatcher, HTTP request/response, session manager
• Ed Finkler, “The Micro-PHP Manifesto” (microphp.org)
• Componentized: Symfony 2 (kind of), Zend Framework 2 (kind of)
Saturday, December 1, 12
install: zend-config zend-http
depends: zend-escaper zend-filter zend-i18n zend-loader zend-servicemanager zend-stdlib zend-uri zend-validator
suggest: pecl-weakref zendframework/zend-di zendframework/zend-crypt zendframework/zend-db zendframework/zend-math
Saturday, December 1, 12
The Aura Projectfor PHP 5.4+
Saturday, December 1, 12
Rewrite Solar
• Solar Framework: 5+ years old at the time (Oct 2010)
• Monolithic; tough to use just parts of it
• Extract components and rewrite using PHP 5.4
• Independent, de-coupled library packages
Saturday, December 1, 12
Driving Principles
• Libraries first, framework later
• No dependencies on any other package (self-contained)
• Tests and assets encapsulated within package
• No use of globals within packages (e.g., $_SERVER)
• Carry data across package boundaries using data transfer objects
• Use a separated interface for each shared tool (signal, log, cache, etc)
Saturday, December 1, 12
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 (Aura.Di is nice ;-)
• Factories and builders instead of new keyword
Saturday, December 1, 12
Service Locator Examplesclass Foo{ protected $db; public function __construct() { $this->db = Locator::get('db'); }}
Saturday, December 1, 12
class Foo{ protected $db; public function __construct(Locator $locator) { $this->db = $locator->get('db'); }}
Saturday, December 1, 12
Dependency Injection Examplesclass Foo{ protected $db; public function __construct(Database $db) { $this->db = $db; }}
Saturday, December 1, 12
class Foo{ protected $db; public function setDb(Database $db) { $this->db = $db; }}
Saturday, December 1, 12
class Foo{ protected $db_factory;
public function __construct(DatabaseFactory $db_factory) { $this->db_factory = $db_factory; }
public function doSomething() { $db = $this->db_factory->newInstance(); }}
Saturday, December 1, 12
Dependency Injection > Service Locator
• Locator hides dependencies
• Locator is itself a dependency (all libraries need it)
• Harder to write tests
• DI reveals dependencies
• Is not itself a dependency
• Easier to write tests, mocks, etc.
Saturday, December 1, 12
Aura.Router
Saturday, December 1, 12
Description
Aura Router is a PHP package that implements web routing. Given a URL path
and a copy of $_SERVER, it will extract controller, action, and parameter values
for a specific application route.
Saturday, December 1, 12
Package Organization
Saturday, December 1, 12
Instantiation
// (standalone, no autoloader)$map = require '/path/to/Aura.Router/scripts/instance.php';
// (with autoloader)use Aura\Router\Map;use Aura\Router\DefinitionFactory;use Aura\Router\RouteFactory;
$map = new Map(new DefinitionFactory, new RouteFactory);
Saturday, December 1, 12
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', ],));
Saturday, December 1, 12
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.Saturday, December 1, 12
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',]
Saturday, December 1, 12
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);
Saturday, December 1, 12
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', ],));
Saturday, December 1, 12
Micro-Framework Dispatcher
$params = $route->values;$controller = $params['controller'];unset($params['controller']);echo $controller($params);
Saturday, December 1, 12
Aura.Web
Saturday, December 1, 12
DescriptionThe Aura Web package provides tools to build web page controllers, including an
`AbstractPage` for action methods, a `Context` class for discovering 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.)
Saturday, December 1, 12
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}
Saturday, December 1, 12
Instantiation and Callinguse Vendor\Package\Web\Page;use Aura\Web\Context;use Aura\Web\Accept;use Aura\Web\Response;use Aura\Web\Signal;use Aura\Web\Renderer\None as Renderer;
$params = [ 'action' => 'hello', 'format' => '.html', 'noun' => 'world',];
$page = new Page( new Context($GLOBALS), new Accept($_SERVER), new Response, new Signal, new Renderer, $params);
$response_transfer = $page->exec();
Saturday, December 1, 12
use Vendor\Package\Web\Page;use Aura\Web\Context;use Aura\Web\Accept;use Aura\Web\Response;use Aura\Web\Signal;use Aura\Web\Renderer\None as Renderer;
Saturday, December 1, 12
$params = [ 'action' => 'hello', 'format' => '.html', 'noun' => 'world',];
Saturday, December 1, 12
$page = new Page( new Context($GLOBALS), new Accept($_SERVER), new Response, new Signal, new Renderer, $params);
$response_transfer = $page->exec();
Saturday, December 1, 12
Naive Factoryclass NaivePageFactory{ protected $map = [ 'page-name' => 'Vendor\Package\Web\Page', ]; public function newInstance($name, array $params) { $class = $this->map[$name]; return new $class( new Context($GLOBALS), new Accept($_SERVER), new Response, new Signal, new Renderer, $params ); }}
Saturday, December 1, 12
Instantiation With Naive Factoryuse NaivePageFactory;
$params = [ 'action' => 'hello', 'format' => '.html', 'noun' => 'world',];
$factory = new NaivePageFactory;$page = $factory->newInstance('page-name', $params);$response_transfer = $page->exec();
Saturday, December 1, 12
Important Parts• $this->params for incoming parameters
• $this->context for get, post, files, etc.
• $this->accept for content-type, language, encoding, etc
• $this->response for headers, cookies, content (data transfer object)
• $this->signal for signals/events/notifiers (separated interface)
• $this->renderer for rendering strategy (default “none”)
• $this->data for data to be rendered
• (pre|post)_(exec|action|render) hooks, and new catch_exception hook
Saturday, December 1, 12
Action Methodnamespace 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); }}
Saturday, December 1, 12
Data Capture
protected function actionHello($noun = null){ $this->data->noun = $noun;}
Saturday, December 1, 12
Rendering Strategyclass NaiveRenderer extends AbstractRenderer{ public function exec() { // get data from controller $data = (array) $this->controller->getData();
// pick a template file based on controller action $action = $this->controller->getAction(); $__file__ = "/path/to/templates/{$action}.php"; // closure to execute template file $template = function () use (array $data, $__file__) { ob_start(); extract($data); require $__file__; return ob_get_clean(); }; // invoke closure $content = $template(); // set content on response, and done! $response = $this->controller->getResponse(); $response->setContent($content); }}
Saturday, December 1, 12
// get data from controller $data = (array) $this->controller->getData();
// pick a template file based on controller action $action = $this->controller->getAction(); $__file__ = "/path/to/templates/{$action}.php";
Saturday, December 1, 12
// closure to execute template file $template = function () use (array $data, $__file__) { ob_start(); extract($data); require $__file__; return ob_get_clean(); }; // invoke closure $content = $template();
Saturday, December 1, 12
// set content on response, and done! $response = $this->controller->getResponse(); $response->setContent($content);
Saturday, December 1, 12
// template file 'hello.php'$noun = htmlspecialchars($noun, ENT_QUOTES, 'UTF-8');echo "Hello {$noun}!";
Saturday, December 1, 12
Naive Factory and Naive Rendererclass NaivePageFactory{ protected $map = [ 'page-name' => 'Vendor\Package\Web\Page', ]; public function newInstance($name, array $params) { $class = $this->map[$name]; return new $class( new Context($GLOBALS), new Accept($_SERVER), new Response, new Signal, new NaiveRenderer, // <-- strategy $params ); }}
Saturday, December 1, 12
Response Delivery
• ResponseTransfer object is *not* an HTTP response
• It is a Data Transfer Object
• Must convert it to a real HTTP response
• Allows any HTTP library, or none
Saturday, December 1, 12
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();Saturday, December 1, 12
Full-Stack Framework
Saturday, December 1, 12
Packages (Stable and Beta)•Aura.Autoload
•Aura.Cli
•Aura.Di
• Aura.Filter
•Aura.Http
• Aura.Intl
•Aura.Marshal
•Aura.Router
• Aura.Session
•Aura.Signal
•Aura.Sql
•Aura.Uri
•Aura.View
•Aura.WebSaturday, December 1, 12
Aura.Framework and System
• “Aura.Framework” package to connect all the others into a cohesive whole
• “Aura.Demo” package to provide “hello world”
• “System” package to provide a project skeleton
• Still beta
Saturday, December 1, 12
Saturday, December 1, 12
Conclusion
Saturday, December 1, 12
• Background of libraries vs. frameworks
• Principles of decoupled libraries
• Individual Aura packages
Saturday, December 1, 12
Obrigado!
auraphp.github.com
paul-m-jones.com@pmjones
Saturday, December 1, 12