Upgrade to Pro — share decks privately, control downloads, hide ads and more …

What Symfony Components can Do for You

What Symfony Components can Do for You

Andreas Hucks

May 15, 2013
Tweet

More Decks by Andreas Hucks

Other Decks in Programming

Transcript

  1. Symfony2 is a reusable set of standalone, decoupled, and cohesive

    PHP 5.3 components that solve common web development problems.
  2. Stuff built using SF2 Components (to varying degrees) • Symfony2

    (duh) • Drupal 8 • Silex • Laravel • PPI • PHPUnit • Composer • ... I probably forgot something important
  3. if  ($_GET['action']  ==  'close') {        $query  =

     'UPDATE  todo  SET  is_done  =  1  WHERE  id  =  '.  mysql_real_escape_string($_GET['id']);        mysql_query($query,  $conn)  or  die('Unable  to  update  existing  task  :  '.  mysql_error());        header('Location:  /app.php/'); } $result  =  mysql_query('SELECT  COUNT(*)  FROM  todo',  $conn); $count    =  current(mysql_fetch_row($result)); $result  =  mysql_query('SELECT  *  FROM  todo',  $conn) ?> <table>        <?php                while  ($todo  =  mysql_fetch_assoc($result))  {                        echo  '<tr>';                        echo  '    <td  class="center">'.  $todo['id']  .'</td>';                        echo  '    <td><a  href="/app.php/show?id='.  $todo['id']  .'">'.  $todo['title']  .'</a></td>';                        echo  '    <td  class="center">'; 4.0 TM
  4. Legacy Wrapper public  function  execute($file,  Request  $request) {    

       $file  =  $this-­‐>basePath  .  $file;        if  (!is_file($file)  &&  someSanityCheck($file))  {                throw  new  \Exception('Invalid  controller.');        }        extract($this-­‐>context);        ob_start();        require_once  $this-­‐>basePath  .  $file;        return  new  Response(ob_get_clean()); }
  5. web/app.php use  Legacy\Wrapper; use  Symfony\Component\Debug\Debug; use  Symfony\Component\HttpFoundation\Request; $wrapper  =  new

     Wrapper(__DIR__  .  '/../legacy'); $request  =  Request::createFromGlobals(); $response  =  $wrapper-­‐>execute(        $request-­‐>getPathInfo(),        $request ); $response-­‐>send();
  6. Wrap Up • Starting point for refactoring: • Isolated legacy

    code • Can now be integrated into new app • Testable!
  7. Routing • Define routes as patterns • Assign attributes •

    Match an incoming URI to a route • Generate an URI from a route object
  8. Defining a Route $routeHome  =  new  Route('/index.php'); $routeHome-­‐>setDefault(    

       '_controller',        function(Request  $request)  use  ($wrapper)        {                return  $wrapper-­‐>execute('/index.php',  $request);        } );
  9. Matching $routes  =  new  RouteCollection(); $routes-­‐>add('list',  $routeHome); //  ... $context

     =  new  RequestContext(); $context-­‐>fromRequest($request); $matcher  =  new  UrlMatcher($routes,  $context); $parameters  =  $matcher-­‐>match($request-­‐>getPathInfo());
  10. Parameters? $routeHome-­‐>setDefault('_controller',  'MyController'); $routeHome-­‐>setDefault('page',  1); $routes-­‐>add('list',  $routeHome); array  (  

         '_controller'  =>  'MyController',        '_route'            =>  'list',        'page'                =>  1 )
  11. • Simple, extensible templating engine • PHP! • Escaping, Inheritance

    • Generic Interface to allow for easy engine replacement (Twig!)
  12. <?php        include  'config.php';        include

     'header.php'; ?> <table>        [...]  / / here be dragons </table> <?php        include  'footer.php' ?> 4.0 TM
  13. list.php <?php  $view-­‐>extend('layout.php')  ?> <?php  $view['slots']-­‐>start('content')  ?>      

     [...]                <?php  foreach  ($tasks  as  $task):  ?>                        <tr>                                <td><?php  echo  $task['title'];  ?></td>/td>                                [...]                        </tr>                <?php  endforeach;  ?>        [...] <?php  $view['slots']-­‐>stop()  ?>
  14. Rendering $templating  =  new  PhpEngine(        new  TemplateNameParser(),

           new  FilesystemLoader(                array(__DIR__  .  '/../templates/%name%')        ) ); $html  =  $templating-­‐>render(    'list.php',    array(            'tasks'  =>  $tasks    ) );
  15. Inside the Controller public  function  listAction(Request  $request) {    

       //  load  tasks  from  db        return  new  Response(                $this-­‐>templating-­‐>render(                      'list.php',                      array(                          'urlGenerator'  =>  $router-­‐>getGenerator(),                          'tasks'                =>  $tasks                  )          )      ); }
  16. Generating URLs [...] <div  id="content">        <h1>  

                 <a  href=  "<?php  $urlGenerator-­‐>generate('list');  ?>">                        My  Todo  List                </a>        </h1>        [...] </div> [...]
  17. The all-in-one Router $context  =  new  RequestContext(); $context-­‐>fromRequest($request); $locator  =

     new  FileLocator(__DIR__  .  '/../config'); $router  =  new  Router(        new  YamlFileLoader($locator),        'routing.yml',        array(                'cache_dir'  =>  __DIR__  .  '/../cache'        ),        $context );
  18. YAML Configuration list:        pattern:  /    

       methods:  GET        defaults:  {  _controller:  list  } show:        pattern:  /{id}        methods:  GET        defaults:  {  _controller:  show  } create:        pattern:  /        methods:  POST        defaults:  {  _controller:  create  }
  19. Call the (new) Controller $controller  =  //  create  controller  instance

    $parameters  =  $matcher-­‐>match($request-­‐>getPathInfo()); $response  =  call_user_func(   array(     $controller,     $parameters['_controller']  .  'Action'   ),   $request ); $response-­‐>send();
  20. HttpKernel • Provides a predefined workflow to convert a Request

    into a Response • Events to hook into • Error Logging (optionally)
  21. Let the kernel handle it. $dispatcher  =  new  EventDispatcher(); $dispatcher-­‐>addSubscriber(

           new  RouterListener($router-­‐>getMatcher()) ); $resolver  =  new  MyControllerResolver($controller); $kernel  =  new  HttpKernel($dispatcher,  $resolver); $request  =  Request::createFromGlobals(); $response  =  $kernel-­‐>handle($request); $response-­‐>send();
  22. Controller Resolver? interface  ControllerResolverInterface {      public  function  getController(Request

     $request);      public  function  getArguments(Request  $request,  $controller); }
  23. Base Test use  Symfony\Component\HttpKernel\Client; class  FunctionalTestCase  extends  \PHPUnit_Framework_TestCase {  

         protected  static  function  createClient()        {                $kernel  =  //  somehow  create  a  kernel                return  new  Client($kernel);        } }
  24. A Test Case class  IndexPageTest  extends  FunctionalTestCase {    

       public  function  testList()        {                $client  =  static::createClient();                $crawler  =  $client-­‐>request('GET',  '/');                $this-­‐>assertEquals(200,  $client-­‐>getResponse()-­‐>getStatusCode());                $this-­‐>assertCount(1,  $crawler-­‐>filter('h1:contains("My  Todos  List")'));        } }
  25. Testing Forms $form  =  $crawler-­‐>selectButton('send')-­‐>form(); $client-­‐>submit($form,  array('title'  =>  'MyTask')); $this-­‐>assertEquals(302,

     $client-­‐>getResponse()-­‐>getStatusCode()); $crawler  =  $client-­‐>followRedirect(); $this-­‐>assertCount(1,  $crawler-­‐>filter(sprintf('a:contains("%s")',  'MyTask')));
  26. Config • Locating, Loading, Caching of config files • Validation

    of config files • Merging of cascading config sets
  27. Loading Configuration Data $cachePath  =  __DIR__  .  '/../cache/config.php'; $configPath  =

     __DIR__  .  '/../config/config.yml'; $configCache  =  new  ConfigCache($cachePath,  true); if  (!$configCache-­‐>isFresh())  {        $resource  =  new  FileResource($configPath);        $code  =  '<?php  return  '  .  var_export(Yaml::parse($configPath),  true)  .  ';';        $configCache-­‐>write($code,  array($resource)); } $config  =  require  $cachePath;
  28. Commands use  Symfony\Component\Console\Command\Command; class  AddTaskCommand  extends  Command {    

       public  function  configure()        {                $this-­‐>setName('todo:add');                $this-­‐>setDescription('Add  a  new  task  to  your  todo  list');                $this-­‐>addArgument('title',  InputArgument::OPTIONAL,  'The  task  title');        }        //  ... }
  29. The Application use  Symfony\Component\Console\Application; $db  =  //  create  PDO  instance

    $app  =  new  Application('Todo  List  Helpers'); $app-­‐>add(new  AddTaskCommand($db)); $app-­‐>add(new  ExpireTasksCommand($db)); $app-­‐>run();
  30. Execution public  function  execute(InputInterface  $input,  OutputInterface  $output) {    

       $dialog  =  $this-­‐>getHelperSet()-­‐>get('dialog');        $title  =  $dialog-­‐>ask(                $output,                '<question>What  do  you  have  to  do?</question>  '        );        if  ($title)  {                //  do  stuff                $output-­‐>writeln('<info>Task  created.</info>');        }  else  {                $output-­‐>writeln('<error>No  input  given!</error>');        } }
  31. What else is there? • Form • Security • Validator

    • Event Dispatcher • Finder • Process • PropertyAccess • OptionsResolver • ...