decoupled library packages for php 5.4

57
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

Upload: pmjones88

Post on 17-Dec-2014

6.033 views

Category:

Technology


1 download

DESCRIPTION

 

TRANSCRIPT

Page 1: Decoupled Library Packages for PHP 5.4

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

Page 2: Decoupled Library Packages for PHP 5.4

Read These

Saturday, December 1, 12

Page 3: Decoupled Library Packages for PHP 5.4

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

Page 4: Decoupled Library Packages for PHP 5.4

Overview

• Background of libraries vs. frameworks

• Principles of decoupled libraries

• Examples: individual Aura libraries

Saturday, December 1, 12

Page 5: Decoupled Library Packages for PHP 5.4

Background:Libraries vs Frameworks

Saturday, December 1, 12

Page 6: Decoupled Library Packages for PHP 5.4

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

Page 7: Decoupled Library Packages for PHP 5.4

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

Page 8: Decoupled Library Packages for PHP 5.4

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

Page 9: Decoupled Library Packages for PHP 5.4

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

Page 10: Decoupled Library Packages for PHP 5.4

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

Page 11: Decoupled Library Packages for PHP 5.4

The Aura Projectfor PHP 5.4+

Saturday, December 1, 12

Page 12: Decoupled Library Packages for PHP 5.4

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

Page 13: Decoupled Library Packages for PHP 5.4

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

Page 14: Decoupled Library Packages for PHP 5.4

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

Page 15: Decoupled Library Packages for PHP 5.4

Service Locator Examplesclass Foo{ protected $db; public function __construct() { $this->db = Locator::get('db'); }}

Saturday, December 1, 12

Page 16: Decoupled Library Packages for PHP 5.4

class Foo{ protected $db; public function __construct(Locator $locator) { $this->db = $locator->get('db'); }}

Saturday, December 1, 12

Page 17: Decoupled Library Packages for PHP 5.4

Dependency Injection Examplesclass Foo{ protected $db; public function __construct(Database $db) { $this->db = $db; }}

Saturday, December 1, 12

Page 18: Decoupled Library Packages for PHP 5.4

class Foo{ protected $db; public function setDb(Database $db) { $this->db = $db; }}

Saturday, December 1, 12

Page 19: Decoupled Library Packages for PHP 5.4

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

Page 20: Decoupled Library Packages for PHP 5.4

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

Page 21: Decoupled Library Packages for PHP 5.4

Aura.Router

Saturday, December 1, 12

Page 22: Decoupled Library Packages for PHP 5.4

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

Page 23: Decoupled Library Packages for PHP 5.4

Package Organization

Saturday, December 1, 12

Page 24: Decoupled Library Packages for PHP 5.4

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

Page 25: Decoupled Library Packages for PHP 5.4

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

Page 26: Decoupled Library Packages for PHP 5.4

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

Page 27: Decoupled Library Packages for PHP 5.4

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

Page 28: Decoupled Library Packages for PHP 5.4

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

Page 29: Decoupled Library Packages for PHP 5.4

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

Page 30: Decoupled Library Packages for PHP 5.4

Micro-Framework Dispatcher

$params = $route->values;$controller = $params['controller'];unset($params['controller']);echo $controller($params);

Saturday, December 1, 12

Page 31: Decoupled Library Packages for PHP 5.4

Aura.Web

Saturday, December 1, 12

Page 32: Decoupled Library Packages for PHP 5.4

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

Page 33: Decoupled Library Packages for PHP 5.4

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

Page 34: Decoupled Library Packages for PHP 5.4

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

Page 35: Decoupled Library Packages for PHP 5.4

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

Page 36: Decoupled Library Packages for PHP 5.4

$params = [ 'action' => 'hello', 'format' => '.html', 'noun' => 'world',];

Saturday, December 1, 12

Page 37: Decoupled Library Packages for PHP 5.4

$page = new Page( new Context($GLOBALS), new Accept($_SERVER), new Response, new Signal, new Renderer, $params);

$response_transfer = $page->exec();

Saturday, December 1, 12

Page 38: Decoupled Library Packages for PHP 5.4

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

Page 39: Decoupled Library Packages for PHP 5.4

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

Page 40: Decoupled Library Packages for PHP 5.4

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

Page 41: Decoupled Library Packages for PHP 5.4

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

Page 42: Decoupled Library Packages for PHP 5.4

Data Capture

protected function actionHello($noun = null){ $this->data->noun = $noun;}

Saturday, December 1, 12

Page 43: Decoupled Library Packages for PHP 5.4

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

Page 44: Decoupled Library Packages for PHP 5.4

// 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

Page 45: Decoupled Library Packages for PHP 5.4

// 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

Page 46: Decoupled Library Packages for PHP 5.4

// set content on response, and done! $response = $this->controller->getResponse(); $response->setContent($content);

Saturday, December 1, 12

Page 47: Decoupled Library Packages for PHP 5.4

// template file 'hello.php'$noun = htmlspecialchars($noun, ENT_QUOTES, 'UTF-8');echo "Hello {$noun}!";

Saturday, December 1, 12

Page 48: Decoupled Library Packages for PHP 5.4

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

Page 49: Decoupled Library Packages for PHP 5.4

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

Page 50: Decoupled Library Packages for PHP 5.4

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

Page 51: Decoupled Library Packages for PHP 5.4

Full-Stack Framework

Saturday, December 1, 12

Page 52: Decoupled Library Packages for PHP 5.4

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

Page 53: Decoupled Library Packages for PHP 5.4

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

Page 54: Decoupled Library Packages for PHP 5.4

Saturday, December 1, 12

Page 55: Decoupled Library Packages for PHP 5.4

Conclusion

Saturday, December 1, 12

Page 56: Decoupled Library Packages for PHP 5.4

• Background of libraries vs. frameworks

• Principles of decoupled libraries

• Individual Aura packages

Saturday, December 1, 12

Page 57: Decoupled Library Packages for PHP 5.4

Obrigado!

auraphp.github.com

paul-m-jones.com@pmjones

Saturday, December 1, 12