Your SlideShare is downloading. ×
0
The symfony platform

                  Create your very own framework

                                    Fabien Potenci...
Sensio / Me
  • Founder of Sensio
         – Web Agency
         – Founded in 1998
         – 45 people
         – Open-So...
Web Framework
       « Whatever the application, a framework is build to
       ease development by providing tools for re...
The symfony Platform
  • symfony is made of decoupled classes based on a
    small number of core classes
         – Event...
The symfony Platform


          You can use all of those classes by themselves

                  … to create your own fr...
Let’s do it


PHP Quebec 2008   www.symfony-project.com   6   www.sensiolabs.com
The Goals
  • We won’t create a full stack framework

  • We will create a framework customized for YOUR
    needs

  • Th...
The Web Workflow

                  The User asks a Resource in a Browser

           The Browser sends a Request to the S...
session_start();

  if (isset($_GET['name']))
  {
    $name = $_GET['name'];
  }
  else if (isset($_SESSION['name']))
  {
...
Move to OOP
  • Use objects instead of global arrays and functions
         – $_GET, $_POST, getcookie()                  ...
sfWebRequest
                           PHP                                                    Object
  $_SERVER[‘REQUEST_...
sfWebResponse
                  PHP                                          Object
  echo ‘Hello World!’                 ...
sfUser / sfStorage
                  PHP                                         Object
  $_SESSION[‘name’] = ‘value’     ...
sfEventDispatcher
  • Allow decoupled objects to communicate
  // sfUser
  $event = new sfEvent($this, 'user.change_cultur...
session_start();

  if (isset($_GET['name']))
  {

  }
    $name = $_GET['name'];
                                        ...
Install symfony
  • Install symfony 1.1 (via PEAR or Subversion)
  $ svn co http://svn.symfony-project.com/branches/1.1 sy...
require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php';
  sfCoreAutoload::register();

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

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

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

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


  $dispatch...
sfRouting
  • Clean URLs <> Resources
  • Parses PATH_INFO to inject parameters in the
    sfRequest object
  • Several st...
require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php';
  sfCoreAutoload::register();


  $dispatch...
The Web Workflow

                  The User asks a Resource in a Browser

           The Browser sends a Request to the S...
Let’s create
                  a new framework

                      Code name: fp
PHP Quebec 2008   www.symfony-project....
require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php';
  sfCoreAutoload::register();


  $dispatch...
Resource specific code
                                              generic

                                            ...
• The dispatching logic is the same for every
    resource

  • The business logic depends on the resource and is
    mana...
require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php';
  sfCoreAutoload::register();


  $dispatch...
class helloController
  {
    function indexAction($dispatcher, $request, $user)
    {
          $name = $request->getPara...
The framework creation process

  • We write code that just works

  • We abstract the code to make it generic




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


  $dispatch...
require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php';
  sfCoreAutoload::register();
             ...
The Request Handler
  • Handles the dispatching of the request

  • Calls the Controller

  • Has the responsability to re...
$dispatcher = new sfEventDispatcher();
  $storage = new sfSessionStorage();
  $user = new sfUser($dispatcher, $storage);
 ...
class fpRequestHandler
  {
    static function handle($dispatcher, $request, $user)
    {
      $class = $request->getPara...
require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php';
  sfCoreAutoload::register();
             ...
Abstract object management
  •  I need a container for my application objects
         –  The dispatcher
         –  The u...
$dispatcher = new sfEventDispatcher();
  $application = new helloApplication($dispatcher);

  $request = new sfWebRequest(...
class helloApplication
  {
    public $dispatcher, $user, $routing;

      function __construct($dispatcher)
      {
     ...
Instead of passing a dispatcher around,
                        pass the application object




PHP Quebec 2008       www....
class helloApplication
  {
    // ...

      function handle($request)
      {
        return fpRequestHandler::handle($th...
class helloApplication
  {
                                                                          Application
    publi...
Create a fpApplication class




PHP Quebec 2008   www.symfony-project.com   44   www.sensiolabs.com
abstract class fpApplication
  {
    public $dispatcher, $user, $routing;

      function __construct($dispatcher)
      {...
Move the public properties
                         to accessor methods




PHP Quebec 2008   www.symfony-project.com   46...
abstract class fpApplication
  {
    protected $dispatcher, $storage, $user, $routing;
      function __construct($dispatc...
Sensible defaults
  • Most of the time
         – The dispatcher is a sfEventDispatcher
         – The request is a sfWebR...
abstract class fpApplication
  {
    function __construct($dispatcher = null)
    {
          $this->dispatcher = is_null(...
More sensible defaults
  • Most of the time
         – The controller creates a sfWebResponse object
         – … with som...
class fpController
  {
    function __construct($application)
    {
      $this->application = $application;
    }

      ...
Move the framework
  • Move the framework code to its own directory
    structure
  require dirname(__FILE__).'/../lib/sym...
Autoload our framework
  require dirname(__FILE__).'/../lib/symfony/lib/autoload/sfCoreAutoload.class.php';
  sfCoreAutolo...
Create a bootstrap file
  require dirname(__FILE__).'/../lib/framework/bootstrap.php';

  class helloApplication extends f...
Move classes
  • Move the application and controller classes to
    their own directory structure

  require dirname(__FIL...
Summary
  • 3 generic classes
         – fpApplication
         – fpController
         – fpRequestHandler
  • 2 specific ...
Conventions
  • We already have some conventions
         – Controller class names
         – Action method names


  • Le...
abstract class fpApplication
  {
    function __construct($dispatcher = null)
    {
          $this->dispatcher = is_null(...
Let’s add a simple templating
                       system based on PHP




PHP Quebec 2008   www.symfony-project.com   5...
abstract class fpApplication
  {
    protected $dispatcher, $storage, $user, $routing, $template;

      function getTempl...
The directory structure
  • Add a template directory




PHP Quebec 2008   www.symfony-project.com   61   www.sensiolabs.c...
class fpController
  {
    // ...

      function render($template, $parameters = array())
      {
        $template = $th...
Write some tests
  • Create a test/ directory to host test classes
  • Use PHPUnit to test our controllers
  • Change the ...
<?php

  require_once 'PHPUnit/Framework.php';
  require dirname(__FILE__).'/../../lib/framework/bootstrap.php';
  require...
class helloControllerTest extends PHPUnit_Framework_TestCase
  {
    protected function setUp()
    {
      $this->applica...
Refactor the code to create
                       a fpControllerTest class




PHP Quebec 2008   www.symfony-project.com ...
abstract class fpControllerTest extends PHPUnit_Framework_TestCase
  {
    protected function setUp()
    {
      $r = new...
Add XSS protection
  • Add XSS protection by escaping all template
    parameters
  • Use sfOutputEscaper symfony classes ...
abstract class fpApplication
  {
    // ...

      function getTemplate()
      {
        if (is_null($this->template))
  ...
class helloApplication extends fpApplication
  {
    function configure()
    {
      $this->getRouting()->connect('hello'...
Move the generic code to fpApplication




PHP Quebec 2008       www.symfony-project.com   71   www.sensiolabs.com
class helloApplication extends fpApplication
  {
    function configure()
    {
      $this->getRouting()->connect('hello'...
Add custom 404, 500
  • Allow custom 404 and 500 pages
         – 404 pages > sfError404Exception
         – 500 pages > E...
Customization
  • Add some events in the RequestHandler
         – application.request
         – application.controller
 ...
What for?
  • Page caching
         – application.request: If I have the page in cache,
           unserialize the respons...
If we have time…
  • Add CSS selector support to the test class
  • Implement a page cache system
  • Implement a security...
SENSIO S.A.
                                    26, rue Salomon de Rothschild
                                        92 2...
Upcoming SlideShare
Loading in...5
×

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

5,375

Published on

Published in: Technology
1 Comment
4 Likes
Statistics
Notes
No Downloads
Views
Total Views
5,375
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
95
Comments
1
Likes
4
Embeds 0
No embeds

No notes for slide

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

  1. 1. The symfony platform Create your very own framework Fabien Potencier / Sensio Labs PHP Quebec 2008 www.symfony-project.com 1 www.sensiolabs.com
  2. 2. 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 PHP Quebec 2008 www.symfony-project.com 2 www.sensiolabs.com
  3. 3. 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 PHP Quebec 2008 www.symfony-project.com 3 www.sensiolabs.com
  4. 4. 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 PHP Quebec 2008 www.symfony-project.com 4 www.sensiolabs.com
  5. 5. The symfony Platform You can use all of those classes by themselves … to create your own framework PHP Quebec 2008 www.symfony-project.com 5 www.sensiolabs.com
  6. 6. Let’s do it PHP Quebec 2008 www.symfony-project.com 6 www.sensiolabs.com
  7. 7. 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 PHP Quebec 2008 www.symfony-project.com 7 www.sensiolabs.com
  8. 8. 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 PHP Quebec 2008 www.symfony-project.com 8 www.sensiolabs.com
  9. 9. session_start(); if (isset($_GET['name'])) { $name = $_GET['name']; } else if (isset($_SESSION['name'])) { $name = $_SESSION['name']; PHP Global arrays } Built-in PHP functions else { $name = 'World'; } $_SESSION['name'] = $name; echo 'Hello '.$name; User > Resource > Request > Response Request Response PHP Quebec 2008 www.symfony-project.com 9 www.sensiolabs.com
  10. 10. Move to OOP • Use objects instead of global arrays and functions – $_GET, $_POST, getcookie() Request – echo, header(), setcookie() Response – $_SESSION User • 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 PHP Quebec 2008 www.symfony-project.com 10 www.sensiolabs.com
  11. 11. sfWebRequest PHP Object $_SERVER[‘REQUEST_METHOD’] >getMethod() $_GET[‘name’] >getParameter(‘name’) get_magic_quotes_gpc() ? >getCookie(‘name’) stripslashes($_COOKIE[$name]) : $_COOKIE[$name]; ( isset($_SERVER['HTTPS']) && ( strtolower($_SERVER ['HTTPS']) == 'on’ >isSecure() || strtolower($_SERVER ['HTTPS']) == 1) ) || ( isset($_SERVER ['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER ['HTTP_X_FORWARDED_PROTO']) == 'https') ) ) Abstract Parameters and Headers PHP Quebec 2008 www.symfony-project.com 11 www.sensiolabs.com
  12. 12. sfWebResponse PHP Object echo ‘Hello World!’ >setContent(‘Hello World’) header(‘HTTP/1.0 404 Not Found’) >setStatusCode(404) setcookie(‘name’, ‘value’) >setCookie(‘name’, ‘value’) Abstract Headers and Cookies PHP Quebec 2008 www.symfony-project.com 12 www.sensiolabs.com
  13. 13. sfUser / sfStorage PHP Object $_SESSION[‘name’] = ‘value’ >setAttribute(‘name’, ‘value’) >setCulture(‘fr’) >setAuthenticated(true) Native session storage + MySQL, PostgreSQL, PDO, … Abstract $_SESSION Add features PHP Quebec 2008 www.symfony-project.com 13 www.sensiolabs.com
  14. 14. sfEventDispatcher • Allow decoupled objects to communicate // sfUser $event = new sfEvent($this, 'user.change_culture', array('culture' => $culture)); $dispatcher->notify($event); // sfI18N Based on $callback = array($this, 'listenToChangeCultureEvent'); Cocoa Notification Center $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 PHP Quebec 2008 www.symfony-project.com 14 www.sensiolabs.com
  15. 15. session_start(); if (isset($_GET['name'])) { } $name = $_GET['name']; sfWebRequest else if (isset($_SESSION['name'])) { $name = $_SESSION['name']; } else { sfUser $name = 'World'; } $_SESSION['name'] = $name; echo 'Hello '.$name; sfWebResponse User > Resource > Request > Response Request Response PHP Quebec 2008 www.symfony-project.com 15 www.sensiolabs.com
  16. 16. Install symfony • Install symfony 1.1 (via PEAR or Subversion) $ svn co http://svn.symfony-project.com/branches/1.1 symfony • Core classes are autoloaded require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register(); PHP Quebec 2008 www.symfony-project.com 16 www.sensiolabs.com
  17. 17. require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register(); $dispatcher = new sfEventDispatcher(); $request = new sfWebRequest($dispatcher); session_start(); if ($request->hasParameter('name')) if (isset($_GET['name'])) { { $name = $request->getParameter('name'); $name = $_GET['name']; } } else if (isset($_SESSION['name'])) { $name = $_SESSION['name']; } else { $name = 'World'; } $_SESSION['name'] = $name; echo 'Hello '.$name; User > Resource > Request > Response Request Response PHP Quebec 2008 www.symfony-project.com 17 www.sensiolabs.com
  18. 18. 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'; } } >getParameter() returns null $_SESSION['name'] = $name; if the parameter is not defined echo 'Hello '.$name; User > Resource > Request > Response Request Response PHP Quebec 2008 www.symfony-project.com 18 www.sensiolabs.com
  19. 19. 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); echo 'Hello '.$name; $response->send(); User > Resource > Request > Response Request Response PHP Quebec 2008 www.symfony-project.com 19 www.sensiolabs.com
  20. 20. require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register(); $dispatcher = new sfEventDispatcher(); $storage = new sfSessionStorage(); session_start(); $user = new sfUser($dispatcher, $storage); $request = new sfWebRequest($dispatcher); if (!$name = $request->getParameter('name')) else if (isset($_SESSION['name'])) { { if (!$name = $user->getAttribute('name')) $name = $_SESSION['name']; { } $name = 'World'; } } $user->setAttribute('name', $name); $_SESSION['name'] = $name; $response = new sfWebResponse($dispatcher); $response->setContent('Hello '.$name); $response->send(); User > Resource > Request > Response Request Response PHP Quebec 2008 www.symfony-project.com 20 www.sensiolabs.com
  21. 21. 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(); User > Resource > Request > Response Request Response PHP Quebec 2008 www.symfony-project.com 21 www.sensiolabs.com
  22. 22. sfRouting • Clean URLs <> Resources • Parses PATH_INFO to inject parameters in the sfRequest object • Several strategies: PathInfo, Pattern • Decouples Request and Controller PHP Quebec 2008 www.symfony-project.com 22 www.sensiolabs.com
  23. 23. require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register(); $dispatcher = new sfEventDispatcher(); $storage = new sfSessionStorage(); $user = new sfUser($dispatcher, $storage); /step.php?name=Fabien $routing = new sfPatternRouting($dispatcher); $routing->connect('hello', '/hello/:name'); $request = new sfWebRequest($dispatcher); /step.php/hello/Fabien $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 Request Response PHP Quebec 2008 www.symfony-project.com 23 www.sensiolabs.com
  24. 24. 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 PHP Quebec 2008 www.symfony-project.com 24 www.sensiolabs.com
  25. 25. Let’s create a new framework Code name: fp PHP Quebec 2008 www.symfony-project.com 25 www.sensiolabs.com
  26. 26. require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register(); $dispatcher = new sfEventDispatcher(); Application $storage = new sfSessionStorage(); $user = new sfUser($dispatcher, $storage); Resource $routing = new sfPatternRouting($dispatcher); $routing->connect('hello', '/hello/:name'); Generic $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 Request Response PHP Quebec 2008 www.symfony-project.com 26 www.sensiolabs.com
  27. 27. Resource specific code generic application resource Request ? Response fp gives You handle the fp wants resource you a Request specific code A Response PHP Quebec 2008 www.symfony-project.com 27 www.sensiolabs.com
  28. 28. • 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 PHP Quebec 2008 www.symfony-project.com 28 www.sensiolabs.com
  29. 29. 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(); PHP Quebec 2008 www.symfony-project.com 29 www.sensiolabs.com
  30. 30. 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; } } PHP Quebec 2008 www.symfony-project.com 30 www.sensiolabs.com
  31. 31. The framework creation process • We write code that just works • We abstract the code to make it generic PHP Quebec 2008 www.symfony-project.com 31 www.sensiolabs.com
  32. 32. 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 PHP Quebec 2008 www.symfony-project.com 32 www.sensiolabs.com
  33. 33. require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register(); Application $dispatcher = new sfEventDispatcher(); Resource $storage = new sfSessionStorage(); $user = new sfUser($dispatcher, $storage); Generic $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(); PHP Quebec 2008 www.symfony-project.com 33 www.sensiolabs.com
  34. 34. The Request Handler • Handles the dispatching of the request • Calls the Controller • Has the responsability to return a sfResponse PHP Quebec 2008 www.symfony-project.com 34 www.sensiolabs.com
  35. 35. $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(); $class = $request->getParameter('controller').'Controller'; $method = $request->getParameter('action').'Action'; $controller = new $class(); $response = $controller->$method($dispatcher, $request, $user); PHP Quebec 2008 www.symfony-project.com 35 www.sensiolabs.com
  36. 36. 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; } } PHP Quebec 2008 www.symfony-project.com 36 www.sensiolabs.com
  37. 37. require dirname(__FILE__).'/../symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register(); Application $dispatcher = new sfEventDispatcher(); Resource $storage = new sfSessionStorage(); $user = new sfUser($dispatcher, $storage); Generic $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(); PHP Quebec 2008 www.symfony-project.com 37 www.sensiolabs.com
  38. 38. Abstract object management •  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 PHP Quebec 2008 www.symfony-project.com 38 www.sensiolabs.com
  39. 39. $dispatcher = new sfEventDispatcher(); $application = new helloApplication($dispatcher); $request = new sfWebRequest($dispatcher); $response = $application->handle($request); $response->send(); PHP Quebec 2008 www.symfony-project.com 39 www.sensiolabs.com
  40. 40. 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); } } PHP Quebec 2008 www.symfony-project.com 40 www.sensiolabs.com
  41. 41. Instead of passing a dispatcher around, pass the application object PHP Quebec 2008 www.symfony-project.com 41 www.sensiolabs.com
  42. 42. 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'; fpRequestHandler $controller = new $class(); $response = $controller->$method($application, $request); is now generic 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; } } PHP Quebec 2008 www.symfony-project.com 42 www.sensiolabs.com
  43. 43. class helloApplication { Application public $dispatcher, $user, $routing; function __construct($dispatcher) Generic { $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); } } PHP Quebec 2008 www.symfony-project.com 43 www.sensiolabs.com
  44. 44. Create a fpApplication class PHP Quebec 2008 www.symfony-project.com 44 www.sensiolabs.com
  45. 45. 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')); } } PHP Quebec 2008 www.symfony-project.com 45 www.sensiolabs.com
  46. 46. Move the public properties to accessor methods PHP Quebec 2008 www.symfony-project.com 46 www.sensiolabs.com
  47. 47. 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; } // ... } PHP Quebec 2008 www.symfony-project.com 47 www.sensiolabs.com
  48. 48. 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 PHP Quebec 2008 www.symfony-project.com 48 www.sensiolabs.com
  49. 49. 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(); PHP Quebec 2008 www.symfony-project.com 49 www.sensiolabs.com
  50. 50. More sensible defaults • Most of the time – The controller creates a sfWebResponse object – … with some content • Let’s introduce a new Controller abstract class PHP Quebec 2008 www.symfony-project.com 50 www.sensiolabs.com
  51. 51. 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); } } PHP Quebec 2008 www.symfony-project.com 51 www.sensiolabs.com
  52. 52. 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(); PHP Quebec 2008 www.symfony-project.com 52 www.sensiolabs.com
  53. 53. 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(); PHP Quebec 2008 www.symfony-project.com 53 www.sensiolabs.com
  54. 54. 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(); PHP Quebec 2008 www.symfony-project.com 54 www.sensiolabs.com
  55. 55. 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(); PHP Quebec 2008 www.symfony-project.com 55 www.sensiolabs.com
  56. 56. Summary • 3 generic classes – fpApplication – fpController – fpRequestHandler • 2 specific classes – helloApplication – helloController • A small boostrap code $application = new helloApplication(); $application->handle()->send(); PHP Quebec 2008 www.symfony-project.com 56 www.sensiolabs.com
  57. 57. 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 PHP Quebec 2008 www.symfony-project.com 57 www.sensiolabs.com
  58. 58. 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; } } PHP Quebec 2008 www.symfony-project.com 58 www.sensiolabs.com
  59. 59. Let’s add a simple templating system based on PHP PHP Quebec 2008 www.symfony-project.com 59 www.sensiolabs.com
  60. 60. 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(); } } PHP Quebec 2008 www.symfony-project.com 60 www.sensiolabs.com
  61. 61. The directory structure • Add a template directory PHP Quebec 2008 www.symfony-project.com 61 www.sensiolabs.com
  62. 62. 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)); } } PHP Quebec 2008 www.symfony-project.com 62 www.sensiolabs.com
  63. 63. Write some tests • Create a test/ directory to host test classes • Use PHPUnit to test our controllers • Change the session storage object PHP Quebec 2008 www.symfony-project.com 63 www.sensiolabs.com
  64. 64. <?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') ); } } PHP Quebec 2008 www.symfony-project.com 64 www.sensiolabs.com
  65. 65. 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()); } } PHP Quebec 2008 www.symfony-project.com 65 www.sensiolabs.com
  66. 66. Refactor the code to create a fpControllerTest class PHP Quebec 2008 www.symfony-project.com 66 www.sensiolabs.com
  67. 67. 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)) ; } // ... } PHP Quebec 2008 www.symfony-project.com 67 www.sensiolabs.com
  68. 68. Add XSS protection • Add XSS protection by escaping all template parameters • Use sfOutputEscaper symfony classes to do the job • Update the tests PHP Quebec 2008 www.symfony-project.com 68 www.sensiolabs.com
  69. 69. 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(); // ... } } PHP Quebec 2008 www.symfony-project.com 69 www.sensiolabs.com
  70. 70. 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'); } } PHP Quebec 2008 www.symfony-project.com 70 www.sensiolabs.com
  71. 71. Move the generic code to fpApplication PHP Quebec 2008 www.symfony-project.com 71 www.sensiolabs.com
  72. 72. 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'); } } PHP Quebec 2008 www.symfony-project.com 72 www.sensiolabs.com
  73. 73. Add custom 404, 500 • Allow custom 404 and 500 pages – 404 pages > sfError404Exception – 500 pages > Exception • Change fpRequestHandler PHP Quebec 2008 www.symfony-project.com 73 www.sensiolabs.com
  74. 74. 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 PHP Quebec 2008 www.symfony-project.com 74 www.sensiolabs.com
  75. 75. 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 PHP Quebec 2008 www.symfony-project.com 75 www.sensiolabs.com
  76. 76. 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 PHP Quebec 2008 www.symfony-project.com 76 www.sensiolabs.com
  77. 77. 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 fabien.potencier@sensio.com http://www.sensiolabs.com/ http://www.symfony-project.com/ PHP Quebec 2008 www.symfony-project.com 77 www.sensiolabs.com
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×