+ All Categories
Home > Technology > The symfony platform: Create your very own framework (PHP Quebec 2008)

The symfony platform: Create your very own framework (PHP Quebec 2008)

Date post: 06-May-2015
Category:
Upload: fabien-potencier
View: 5,240 times
Download: 1 times
Share this document with a friend
Popular Tags:
77
PHP Quebec 2008 www.symfony-project.com 1 www.sensiolabs.com Create your very own framework Fabien Potencier / Sensio Labs The symfony platform
Transcript
Page 1: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 1 www.sensiolabs.com

Create your very own framework

Fabien Potencier / Sensio Labs

The symfony platform

Page 2: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 2 www.sensiolabs.com

Sensio / Me • Founder of Sensio

– Web Agency – Founded in 1998 – 45 people – Open-Source Specialists

• Creator of symfony – PHP Web framework – Based on

• 10 years of Sensio experience • Existing Open-Source projects

Page 3: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 3 www.sensiolabs.com

Web Framework « Whatever the application, a framework is build to

ease development by providing tools for recurrent and boring tasks. »

• Generic components – Built-in – Well integrated – To solve web problems

• Professionalize web development

Page 4: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 4 www.sensiolabs.com

The symfony Platform • symfony is made of decoupled classes based on a

small number of core classes – Event Dispatcher – Parameter Holder

• Classes with no dependency

cache, command, database, form, i18n, log, request, response, routing, storage, user, validator, widget

Page 5: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 5 www.sensiolabs.com

The symfony Platform

You can use all of those classes by themselves

… to create your own framework

Page 6: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 6 www.sensiolabs.com

Let’s do it

Page 7: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 7 www.sensiolabs.com

The Goals • We won’t create a full stack framework

• We will create a framework customized for YOUR needs

• The code we will write today can be used as a bootstrap for your own framework

Page 8: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 8 www.sensiolabs.com

The Web Workflow

The User asks a Resource in a Browser

The Browser sends a Request to the Server

The Server sends back a Response

The Browser displays the Resource to the User

Page 9: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 9 www.sensiolabs.com

User > Resource > Request > Response User Resource Request Response

session_start();

if (isset($_GET['name'])) { $name = $_GET['name']; } else if (isset($_SESSION['name'])) { $name = $_SESSION['name']; } else { $name = 'World'; }

$_SESSION['name'] = $name;

echo 'Hello '.$name;

PHP Global arrays Built-in PHP functions

Page 10: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 10 www.sensiolabs.com

Move to OOP • Use objects instead of global arrays and functions

– $_GET, $_POST, getcookie()

– echo, header(), setcookie()

– $_SESSION

• Why ? – To add behaviors to those objects – To have several requests, users, responses in one PHP

process (functional testing) – To be able to mock those objects to ease testing

User

Request Response

Page 11: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 11 www.sensiolabs.com

sfWebRequest PHP Object

$_SERVER[‘REQUEST_METHOD’] $_GET[‘name’]

get_magic_quotes_gpc() ?

stripslashes($_COOKIE[$name]) : $_COOKIE[$name];

( isset($_SERVER['HTTPS']) && ( strtolower($_SERVER ['HTTPS']) == 'on’ || strtolower($_SERVER ['HTTPS']) == 1) ) || ( isset($_SERVER ['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER ['HTTP_X_FORWARDED_PROTO']) == 'https') ) )

>getMethod() >getParameter(‘name’)

>getCookie(‘name’)

>isSecure()

Abstract Parameters and Headers

Page 12: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 12 www.sensiolabs.com

sfWebResponse PHP Object

echo ‘Hello World!’

header(‘HTTP/1.0 404 Not Found’)

setcookie(‘name’, ‘value’)

>setContent(‘Hello World’)

>setStatusCode(404)

>setCookie(‘name’, ‘value’)

Abstract Headers

and Cookies

Page 13: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 13 www.sensiolabs.com

sfUser / sfStorage PHP Object

$_SESSION[‘name’] = ‘value’ >setAttribute(‘name’, ‘value’)

>setCulture(‘fr’)

>setAuthenticated(true)

Native session storage + MySQL, PostgreSQL, PDO, …

Abstract $_SESSION Add features

Page 14: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 14 www.sensiolabs.com

sfEventDispatcher • Allow decoupled objects to communicate

// sfUser $event = new sfEvent($this, 'user.change_culture', array('culture' => $culture)); $dispatcher->notify($event);

// sfI18N $callback = array($this, 'listenToChangeCultureEvent');

$dispatcher->connect('user.change_culture', $callback);

• sfI18N and sfUser are decoupled • « Anybody » can listen to any event • You can notify existing events or create new ones

Based on Cocoa Notification Center

Page 15: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 15 www.sensiolabs.com

User > Resource > Request > Response User Resource Request Response

session_start();

if (isset($_GET['name'])) { $name = $_GET['name']; } else if (isset($_SESSION['name'])) { $name = $_SESSION['name']; } else { $name = 'World'; }

$_SESSION['name'] = $name;

echo 'Hello '.$name;

sfWebRequest

sfWebResponse

sfUser

Page 16: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 16 www.sensiolabs.com

Install symfony • Install symfony 1.1 (via PEAR or Subversion)

• Core classes are autoloaded

require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register();

$ svn co http://svn.symfony-project.com/branches/1.1 symfony

Page 17: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 17 www.sensiolabs.com

User > Resource > Request > Response User Resource Request Response

if (isset($_GET['name'])) { $name = $_GET['name']; }

require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register();

$dispatcher = new sfEventDispatcher(); $request = new sfWebRequest($dispatcher);

session_start();

if ($request->hasParameter('name')) { $name = $request->getParameter('name'); } else if (isset($_SESSION['name'])) { $name = $_SESSION['name']; } else { $name = 'World'; }

$_SESSION['name'] = $name;

echo 'Hello '.$name;

Page 18: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 18 www.sensiolabs.com

User > Resource > Request > Response User Resource Request Response

require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register();

$dispatcher = new sfEventDispatcher(); $request = new sfWebRequest($dispatcher);

session_start();

if (!$name = $request->getParameter('name')) { if (isset($_SESSION['name'])) { $name = $_SESSION['name']; } else { $name = 'World'; } }

$_SESSION['name'] = $name;

echo 'Hello '.$name;

>getParameter() returns null if the parameter is not defined

Page 19: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 19 www.sensiolabs.com

require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register();

$dispatcher = new sfEventDispatcher(); $request = new sfWebRequest($dispatcher); session_start();

if (!$name = $request->getParameter('name')) { if (isset($_SESSION['name'])) { $name = $_SESSION['name']; } else { $name = 'World'; } }

$_SESSION['name'] = $name;

$response = new sfWebResponse($dispatcher); $response->setContent('Hello '.$name); $response->send();

User > Resource > Request > Response User Resource Request Response

echo 'Hello '.$name;

Page 20: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 20 www.sensiolabs.com

User > Resource > Request > Response User Resource Request Response

require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register();

$dispatcher = new sfEventDispatcher(); $storage = new sfSessionStorage(); $user = new sfUser($dispatcher, $storage); $request = new sfWebRequest($dispatcher);

if (!$name = $request->getParameter('name')) { if (!$name = $user->getAttribute('name')) { $name = 'World'; } }

$user->setAttribute('name', $name);

$response = new sfWebResponse($dispatcher); $response->setContent('Hello '.$name); $response->send();

session_start();

else if (isset($_SESSION['name'])) { $name = $_SESSION['name']; }

$_SESSION['name'] = $name;

Page 21: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 21 www.sensiolabs.com

User > Resource > Request > Response User Resource Request Response

require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register();

$dispatcher = new sfEventDispatcher(); $storage = new sfSessionStorage(); $user = new sfUser($dispatcher, $storage); $request = new sfWebRequest($dispatcher);

$name = $request->getParameter('name', $user->getAttribute('name', 'World'));

$user->setAttribute('name', $name);

$response = new sfWebResponse($dispatcher); $response->setContent('Hello '.$name); $response->send();

Page 22: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 22 www.sensiolabs.com

sfRouting • Clean URLs <> Resources • Parses PATH_INFO to inject parameters in the

sfRequest object • Several strategies: PathInfo, Pattern • Decouples Request and Controller

Page 23: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 23 www.sensiolabs.com

User > Resource > Request > Response User Resource Request Response

require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register();

$dispatcher = new sfEventDispatcher(); $storage = new sfSessionStorage(); $user = new sfUser($dispatcher, $storage); $routing = new sfPatternRouting($dispatcher); $routing->connect('hello', '/hello/:name'); $request = new sfWebRequest($dispatcher);

$name = $request->getParameter('name', $user->getAttribute('name', 'World’));

$user->setAttribute('name', $name);

$response = new sfWebResponse($dispatcher); $response->setContent('Hello '.$name); $response->send();

/step.php?name=Fabien

/step.php/hello/Fabien

Page 24: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 24 www.sensiolabs.com

The Web Workflow

The User asks a Resource in a Browser

The Browser sends a Request to the Server

The Server sends back a Response

The Browser displays the Resource to the User

Page 25: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 25 www.sensiolabs.com

Let’s create a new framework

Code name: fp

Page 26: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 26 www.sensiolabs.com

require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register();

$dispatcher = new sfEventDispatcher(); $storage = new sfSessionStorage(); $user = new sfUser($dispatcher, $storage); $routing = new sfPatternRouting($dispatcher); $routing->connect('hello', '/hello/:name'); $request = new sfWebRequest($dispatcher);

$name = $request->getParameter('name', $user->getAttribute('name', 'World'));

$user->setAttribute('name', $name);

$response = new sfWebResponse($dispatcher); $response->setContent('Hello '.$name); $response->send();

User > Resource > Request > Response User Resource Request Response

Application

Resource

Generic

Page 27: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 27 www.sensiolabs.com

Resource specific code

Request Response ?

fp gives you a Request

fp wants A Response

You handle the resource specific code

application

resource

generic

Page 28: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 28 www.sensiolabs.com

• The dispatching logic is the same for every resource

• The business logic depends on the resource and is managed by the controller

• The controller responsability is to « convert » the request to a response

Page 29: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 29 www.sensiolabs.com

require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register();

$dispatcher = new sfEventDispatcher(); $storage = new sfSessionStorage(); $user = new sfUser($dispatcher, $storage); $routing = new sfPatternRouting($dispatcher); $routing->connect('hello', '/hello/:name'); $request = new sfWebRequest($dispatcher);

$controller = new helloController();

$response = $controller->indexAction($dispatcher, $request, $user);

$response->send();

Page 30: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 30 www.sensiolabs.com

class helloController { function indexAction($dispatcher, $request, $user) { $name = $request->getParameter('name', $user->getAttribute('name', 'World’));

$user->setAttribute('name', $name);

$response = new sfWebResponse($dispatcher); $response->setContent('Hello '.$name);

return $response; } }

Page 31: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 31 www.sensiolabs.com

The framework creation process

• We write code that just works

• We abstract the code to make it generic

Page 32: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 32 www.sensiolabs.com

require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register();

$dispatcher = new sfEventDispatcher(); $storage = new sfSessionStorage(); $user = new sfUser($dispatcher, $storage); $routing = new sfPatternRouting($dispatcher); $routing->connect('hello', '/hello/:name', array('controller' => 'hello', 'action' => 'index')); $request = new sfWebRequest($dispatcher);

$class = $request->getParameter('controller').'Controller'; $method = $request->getParameter('action').'Action'; $controller = new $class();

$response = $controller->$method($dispatcher, $request, $user);

$response->send(); sfPatternRouting accepts default parameter values

Page 33: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 33 www.sensiolabs.com

require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register();

$dispatcher = new sfEventDispatcher(); $storage = new sfSessionStorage(); $user = new sfUser($dispatcher, $storage); $routing = new sfPatternRouting($dispatcher); $routing->connect('hello', '/hello/:name', array('controller' => 'hello', 'action' => 'index')); $request = new sfWebRequest($dispatcher);

$class = $request->getParameter('controller').'Controller'; $method = $request->getParameter('action').'Action'; $controller = new $class();

$response = $controller->$method($dispatcher, $request, $user);

$response->send();

Application

Resource

Generic

Page 34: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 34 www.sensiolabs.com

The Request Handler • Handles the dispatching of the request

• Calls the Controller

• Has the responsability to return a sfResponse

Page 35: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 35 www.sensiolabs.com

$class = $request->getParameter('controller').'Controller'; $method = $request->getParameter('action').'Action'; $controller = new $class();

$response = $controller->$method($dispatcher, $request, $user);

$dispatcher = new sfEventDispatcher(); $storage = new sfSessionStorage(); $user = new sfUser($dispatcher, $storage); $routing = new sfPatternRouting($dispatcher); $routing->connect('hello', '/hello/:name', array('controller' => 'hello', 'action' => 'index')); $request = new sfWebRequest($dispatcher);

$response = fpRequestHandler::handle($dispatcher, $request, $user);

$response->send();

Page 36: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 36 www.sensiolabs.com

class fpRequestHandler { static function handle($dispatcher, $request, $user) { $class = $request->getParameter('controller').'Controller'; $method = $request->getParameter('action').'Action';

$controller = new $class(); $response = $controller->$method($dispatcher, $request, $user);

return $response; } }

Page 37: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 37 www.sensiolabs.com

require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register();

$dispatcher = new sfEventDispatcher(); $storage = new sfSessionStorage(); $user = new sfUser($dispatcher, $storage); $routing = new sfPatternRouting($dispatcher); $routing->connect('hello', '/hello/:name', array('controller' => 'hello', 'action' => 'index')); $request = new sfWebRequest($dispatcher);

$response = fpRequestHandler::handle($dispatcher, $request, $user);

$response->send();

Application

Resource

Generic

Page 38: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 38 www.sensiolabs.com

•  I need a container for my application objects – The dispatcher – The user – The routing – The i18n – The database – …

• These objects are specific to my Application and do not depend on the Request

Abstract object management

Page 39: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 39 www.sensiolabs.com

$dispatcher = new sfEventDispatcher(); $application = new helloApplication($dispatcher);

$request = new sfWebRequest($dispatcher);

$response = $application->handle($request);

$response->send();

Page 40: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 40 www.sensiolabs.com

class helloApplication { public $dispatcher, $user, $routing;

function __construct($dispatcher) { $this->dispatcher = $dispatcher;

$storage = new sfSessionStorage(); $this->user = new sfUser($this->dispatcher, $storage);

$this->routing = new sfPatternRouting($this->dispatcher); $this->routing->connect('hello', '/hello/:name', array('controller' => 'hello', 'action' => 'index')); }

function handle($request) { return fpRequestHandler::handle($this->dispatcher, $request, $this-

>user); } }

Page 41: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 41 www.sensiolabs.com

Instead of passing a dispatcher around, pass the application object

Page 42: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 42 www.sensiolabs.com

class helloApplication { // ...

function handle($request) { return fpRequestHandler::handle($this, $request); } }

class fpRequestHandler { static function handle($application, $request) { $class = $request->getParameter('controller').'Controller'; $method = $request->getParameter('action').'Action';

$controller = new $class(); $response = $controller->$method($application, $request);

return $response; } }

class helloController { function indexAction($application, $request) { $name = $request->getParameter('name', $application->user->getAttribute('name', 'World’));

$application->user->setAttribute('name', $name);

$response = new sfWebResponse($application->dispatcher); $response->setContent('Hello '.$name);

return $response; } }

fpRequestHandler is now generic

Page 43: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 43 www.sensiolabs.com

class helloApplication { public $dispatcher, $user, $routing;

function __construct($dispatcher) { $this->dispatcher = $dispatcher;

$storage = new sfSessionStorage(); $this->user = new sfUser($this->dispatcher, $storage);

$this->routing = new sfPatternRouting($this->dispatcher); $this->routing->connect('hello', '/hello/:name', array('controller' => 'hello', 'action' => 'index')); }

function handle($request) { return fpRequestHandler::handle($this->dispatcher, $request, $this-

>user); } }

Application

Generic

Page 44: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 44 www.sensiolabs.com

Create a fpApplication class

Page 45: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 45 www.sensiolabs.com

abstract class fpApplication { public $dispatcher, $user, $routing;

function __construct($dispatcher) { $this->dispatcher = $dispatcher; $this->user = new sfUser($this->dispatcher, new sfSessionStorage()); $this->routing = new sfPatternRouting($this->dispatcher);

$this->configure(); }

abstract function configure();

function handle($request) { return fpRequestHandler::handle($this, $request); } }

class helloApplication extends fpApplication { function configure() { $this->routing->connect('hello', '/hello/:name', array('controller' => 'hello', 'action' => 'index')); } }

Page 46: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 46 www.sensiolabs.com

Move the public properties to accessor methods

Page 47: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 47 www.sensiolabs.com

abstract class fpApplication { protected $dispatcher, $storage, $user, $routing;

function __construct($dispatcher) { $this->dispatcher = $dispatcher; $this->configure(); }

function getDispatcher() { return $this->dispatcher; }

function getStorage() { if (is_null($this->storage)) { $this->storage = new new sfSessionStorage(); }

return $this->storage; } function getUser() { if (is_null($this->user)) { $this->user = new sfUser($this->dispatcher, $this->getStorage()); }

return $this->user; }

function getRouting() { if (is_null($this->routing)) { $this->routing = new sfPatternRouting($this->dispatcher); }

return $this->routing; }

// ... }

Page 48: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 48 www.sensiolabs.com

Sensible defaults

• Most of the time – The dispatcher is a sfEventDispatcher – The request is a sfWebRequest object

• Let’s change the Application to take defaults

Page 49: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 49 www.sensiolabs.com

abstract class fpApplication { function __construct($dispatcher = null) { $this->dispatcher = is_null($dispatcher) ? new sfEventDispatcher() : $dispatcher;

// ... }

function handle($request = null) { $request = is_null($request) ? new sfWebRequest($this->dispatcher) : $request;

return fpRequestHandler::handle($this, $request); } }

$application = new helloApplication();

$response = $application->handle();

$response->send();

Page 50: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 50 www.sensiolabs.com

More sensible defaults

• Most of the time – The controller creates a sfWebResponse object – … with some content

• Let’s introduce a new Controller abstract class

Page 51: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 51 www.sensiolabs.com

class fpController { function __construct($application) { $this->application = $application; }

function render($content) { $response = new sfWebResponse($this->application->dispatcher); $response->setContent($content);

return $response; } }

class helloController extends fpController { function indexAction($request) { $name = $request->getParameter('name', $this->application->getUser()->getAttribute('name', 'World'));

$this->application->getUser()->setAttribute('name', $name);

return $this->render('Hello '.$name); } }

Page 52: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 52 www.sensiolabs.com

Move the framework • Move the framework code to its own directory

structure require dirname(__FILE__).'/../lib/symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register();

require dirname(__FILE__).'/../lib/framework/fpApplication.class.php'; require dirname(__FILE__).'/../lib/framework/fpController.class.php'; require dirname(__FILE__).'/../lib/framework/fpRequestHandler.class.php';

class helloApplication extends fpApplication { // ... }

class helloController extends fpController { // ... }

$application = new helloApplication(); $application->handle()->send();

Page 53: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 53 www.sensiolabs.com

Autoload our framework require dirname(__FILE__).'/../lib/symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register();

$autoload = sfSimpleAutoload::getInstance(); $autoload->addDirectory(dirname(__FILE__).'/../lib/framework'); $autoload->register();

class helloApplication extends fpApplication { // ... }

class helloController extends fpController { // ... }

$application = new helloApplication(); $application->handle()->send();

Page 54: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 54 www.sensiolabs.com

Create a bootstrap file require dirname(__FILE__).'/../lib/framework/bootstrap.php';

class helloApplication extends fpApplication { // ... }

class helloController extends fpController { // ... }

$application = new helloApplication(); $application->handle()->send();

Page 55: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 55 www.sensiolabs.com

Move classes • Move the application and controller classes to

their own directory structure

require dirname(__FILE__).'/../lib/framework/bootstrap.php';

require dirname(__FILE__).'/../hello/application.class.php'; require dirname(__FILE__).'/../hello/controller/helloController.class.php';

$application = new helloApplication(); $application->handle()->send();

Page 56: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 56 www.sensiolabs.com

Summary • 3 generic classes

– fpApplication – fpController – fpRequestHandler

• 2 specific classes – helloApplication – helloController

• A small boostrap code $application = new helloApplication(); $application->handle()->send();

Page 57: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 57 www.sensiolabs.com

Conventions • We already have some conventions

– Controller class names – Action method names

• Let’s add some directory name conventions – Controllers are in the controller directory in the same

directory as applications.class.php – The controller file is the controller class name suffixed

by .class.php

Page 58: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 58 www.sensiolabs.com

abstract class fpApplication { function __construct($dispatcher = null) { $this->dispatcher = is_null($dispatcher) ? new sfEventDispatcher() : $dispatcher;

$r = new ReflectionObject($this); $this->root = realpath(dirname($r->getFileName()));

// ... }

// ... }

class fpRequestHandler { static function handle($application, $request) { $class = $request->getParameter('controller').'Controller'; $method = $request->getParameter('action').'Action';

require_once $application->root.'/controller/'.$class.'.class.php';

$controller = new $class(); $response = $controller->$method($application, $request);

return $response; } }

Page 59: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 59 www.sensiolabs.com

Let’s add a simple templating system based on PHP

Page 60: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 60 www.sensiolabs.com

abstract class fpApplication { protected $dispatcher, $storage, $user, $routing, $template;

function getTemplate() { if (is_null($this->template)) { $this->template = new fpTemplate(); }

return $this->template; }

// ... }

class fpTemplate { function render($template, $parameters = array()) { extract($parameters);

ob_start(); require $template;

return ob_get_clean(); } }

Page 61: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 61 www.sensiolabs.com

The directory structure • Add a template directory

Page 62: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 62 www.sensiolabs.com

class fpController { // ...

function render($template, $parameters = array()) { $template = $this->application->root.'/template/'.$template; $content = $this->application->getTemplate()->render($template, $parameters);

$response = new sfWebResponse($this->application->getDispatcher()); $response->setContent($content);

return $response; } }

class helloController extends fpController { function indexAction($request) { $name = $request->getParameter('name', $this->application->getUser()->getAttribute('name',

'World'));

$this->application->getUser()->setAttribute('name', $name);

return $this->render('hello.php', array('name' => $name)); } }

Page 63: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 63 www.sensiolabs.com

Write some tests • Create a test/ directory to host test classes • Use PHPUnit to test our controllers • Change the session storage object

Page 64: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 64 www.sensiolabs.com

<?php

require_once 'PHPUnit/Framework.php'; require dirname(__FILE__).'/../../lib/framework/bootstrap.php'; require dirname(__FILE__).'/../application.class.php';

class helloApplicationTest extends helloApplication { function __construct($dispatcher = null) { parent::__construct($dispatcher); $this->root = dirname(__FILE__).'/..'; }

function getStorage() { return new sfSessionTestStorage( array('session_path' => '/tmp/quebec_demo', 'session_id' => '123') ); } }

Page 65: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 65 www.sensiolabs.com

class helloControllerTest extends PHPUnit_Framework_TestCase { protected function setUp() { $this->application = new helloApplicationTest(); }

public function testWithRequestParameter() { $_SERVER['PATH_INFO'] = '/hello/Fabien'; $request = new sfWebRequest($this->application->getDispatcher()); $response = $this->application->handle($request);

$this->assertEquals('Hello Fabien', $response->getContent()); }

public function testWithSession() { $_SERVER['PATH_INFO'] = '/hello'; $request = new sfWebRequest($this->application->getDispatcher()); $response = $this->application->handle($request);

$this->assertEquals('Hello Fabien', $response->getContent()); } }

Page 66: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 66 www.sensiolabs.com

Refactor the code to create a fpControllerTest class

Page 67: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 67 www.sensiolabs.com

abstract class fpControllerTest extends PHPUnit_Framework_TestCase { protected function setUp() { $r = new ReflectionObject($this); $root = realpath(dirname($r->getFileName()).'/..');

require_once $root.'/application.class.php';

$this->application = $this->getMock( basename($root).'Application', array('getStorage') ); $this->application->root = $root;

$storage = new sfSessionTestStorage( array('session_path' => '/tmp/quebec_demo', 'session_id' => '123') );

$this->application-> expects($this->any())-> method('getStorage')-> will($this->returnValue($storage)) ; }

// ... }

Page 68: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 68 www.sensiolabs.com

Add XSS protection • Add XSS protection by escaping all template

parameters • Use sfOutputEscaper symfony classes to do the

job • Update the tests

Page 69: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 69 www.sensiolabs.com

abstract class fpApplication { // ...

function getTemplate() {

if (is_null($this->template)) {

$this->template = new fpTemplate($this->dispatcher); }

return $this->template; }

// ... }

class fpTemplate { function __construct($dispatcher) { $this->dispatcher = $dispatcher; }

function render($template, $parameters = array()) { $event = $this->dispatcher->filter(new sfEvent($this, 'template.filter_parameters'), $parameters); $parameters = $event->getReturnValue();

// ... } }

Page 70: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 70 www.sensiolabs.com

class helloApplication extends fpApplication { function configure() { $this->getRouting()->connect('hello', '/hello/:name', array('controller' => 'hello', 'action' => 'index'));

$this->getDispatcher()->connect('template.filter_parameters’, array($this, 'escapeTemplateParameters')); }

function escapeTemplateParameters(sfEvent $event, $parameters) { $parameters['sf_data'] = sfOutputEscaper::escape(array($this, 'htmlspecialchars'), $parameters); foreach ($parameters['sf_data'] as $key => $value) { $parameters[$key] = $value; }

return $parameters; }

function htmlspecialchars($value) { return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); } }

Page 71: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 71 www.sensiolabs.com

Move the generic code to fpApplication

Page 72: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 72 www.sensiolabs.com

class helloApplication extends fpApplication { function configure() { $this->getRouting()->connect('hello', '/hello/:name', array('controller' => 'hello', 'action' => 'index'));

$this->enableOutputEscaping(); } }

class fpApplication { // ...

function enableOutputEscaping() { $this->dispatcher->connect('template.filter_parameters’, array($this, 'escapeTemplateParameters')); }

function escapeTemplateParameters(sfEvent $event, $parameters) { $parameters['sf_data'] = sfOutputEscaper::escape(array($this, 'htmlspecialchars'), $parameters); foreach ($parameters['sf_data'] as $key => $value) { $parameters[$key] = $value; }

return $parameters; }

function htmlspecialchars($value) { return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); } }

Page 73: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 73 www.sensiolabs.com

Add custom 404, 500 • Allow custom 404 and 500 pages

– 404 pages > sfError404Exception – 500 pages > Exception

• Change fpRequestHandler

Page 74: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 74 www.sensiolabs.com

Customization • Add some events in the RequestHandler

– application.request – application.controller – application.response – application.exception

• They can return a sfResponse and stop the RequestHandler flow

Page 75: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 75 www.sensiolabs.com

What for? • Page caching

– application.request: If I have the page in cache, unserialize the response from the cache and returns it

– application.response : Serialize the response to the cache

• Security – application.controller: Check security and change the

controller if needed

Page 76: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 76 www.sensiolabs.com

If we have time… • Add CSS selector support to the test class • Implement a page cache system • Implement a security mecanism for controllers • Improve performance with caching (routing,

framework « compilation », …) • Add a CLI • Implement a layout system • Use symfony Forms • Use a database/model

Page 77: The symfony platform: Create your very own framework (PHP Quebec 2008)

PHP Quebec 2008 www.symfony-project.com 77 www.sensiolabs.com

SENSIO S.A. 26, rue Salomon de Rothschild

92 286 Suresnes Cedex FRANCE

Tél. : +33 1 40 99 80 80 Fax : +33 1 40 99 83 34

Contact Fabien Potencier

[email protected]

http://www.sensiolabs.com/ http://www.symfony-project.com/


Recommended