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

Don't reinvent the wheel with Symfony components!

E2ed7c278c8c49bb3e7fe0b7de039997?s=47 Hugo Hamon
September 15, 2012

Don't reinvent the wheel with Symfony components!

Paradoxically, Symfony2 is not only a full-stack framework. Its underlaying architecture relies on standalone, decoupled and cohesive components. This session will focus on the use of the most interesting components fo Symfony like the Dependency Injection Container, Event Dispatcher, Console, HttpFoundation, HttpKernel, Routing, Browser Kit, Css Selector& Dom Crawler, and much more...

E2ed7c278c8c49bb3e7fe0b7de039997?s=128

Hugo Hamon

September 15, 2012
Tweet

Transcript

  1. Don’t Reinvent the Wheel w/ Symfony Components h"p://www.flickr.com/photos/vemsteroo/7595601626/   PFCongres

     2012  –  Utrecht  –  Hugo  Hamon  
  2. None
  3. http://www.flickr.com/photos/alshepmcr/4298593016/

  4. None
  5. { "require": { "symfony/http-kernel": "2.1.*", "symfony/config": "2.1.*", "symfony/routing": "2.1.*", "symfony/yaml":

    "2.1.*", "symfony/filesystem": "2.1.*", "symfony/finder": "2.1.*", "symfony/process": "2.1.*", ... } } Composer Installation
  6. ClassLoader http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

  7. require '/path/to/UniversalClassLoader.php'; use Symfony\Component\ClassLoader\UniversalClassLoader; $loader = new UniversalClassLoader(); $loader->registerNamespaces(array( 'Symfony'

    => __DIR__.'/vendor/symfony/src', 'Zend' => __DIR__.'/vendor/zend/src', 'Demo' => __DIR__.'/src', )); $loader->registerPrefixes(array( 'Acme' => __DIR__.'/src', )); $loader->register();
  8. YAML http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

  9. http://www.flickr.com/photos/drymek/4310204250/

  10. # parameters.yml parameters: database_host: localhost database_port: 3306 database_name: demo database_user:

    root database_pwd: ~ credentials: { user: john, password: secret } recaptcha: public_key: 6LdZPNYSAAAAAICZ43AfeMfEsJVQXr_CXh4f3mWy private_key: 6LdZPNYSAAAAAB8ALTjfVr72iGYODjKAzic1KuoC
  11. use Symfony\Component\Yaml\Yaml; $config = Yaml::parse('parameters.yml'); echo '<pre>'; print_r($config); echo '</pre>';

    Loading a YAML File
  12. Array ( [parameters] => Array ( [database_host] => localhost [database_port]

    => 3306 [database_name] => demo [database_user] => root [database_pwd] => [credentials] => Array ( [user] => john [password] => secret ) [recaptcha] => Array ( [public_key] => 6LdZPNYSAAAAAICZ43AfeMfEsJVQXr_CXh4f3mWy [private_key] => 6LdZPNYSAAAAAB8ALTjfVr72iGYODjKAzic1KuoC ) ) )
  13. $config = array('parameters' => array( 'database_host' => 'localhost', 'database_port' =>

    3306, 'database_name' => 'root', 'database_user' => 'root', 'database_pwd' => null, 'credentials' => array( 'user' => 'john', 'password' => 'secret', ), 'recaptcha' => array( 'public_key' => '6LdZPNYSAAAAAICZ43AfeMfEsJVQXr_CXh4f3m', 'private_key' => '6LdZPNYSAAAAAB8ALTjfVr72iGYODjKAzic1Ku', ), )); Dumping a YAML File
  14. use Symfony\Component\Yaml\Yaml; $yaml = Yaml::dump($config); echo $yaml; Dumping a YAML

    File
  15. HttpFoundation http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

  16. Client Server HTTP Request HTTP Response … Network …

  17. http://www.flickr.com/photos/sebastian_bergmann/173880207/ PHP Way $_GET $_POST $_COOKIE $_SERVER $_SESSION setcookie() headers()

  18. http://www.flickr.com/photos/amazeelabs/7843168540/ Symfony Way Request Response Session Cookie File ParameterBag HeaderBag

    Streaming HTTP Caching ...
  19. use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals(); $request->overrideGlobals(); $page = $request->query->get('page', 1);

    $body = $request->request->get('body'); $name = $request->cookies->get('username'); $user = $request->server->get('HTTP_AUTH_USER'); $ip = $request->getClientIp(); $lang = $request->getPreferredLanguage(); $user = $request->getUser(); $pwd = $request->getPassword(); $isPost = $request->isMethod('POST'); $isHttps = $request->isSecure(); $isAjax = $request->isXmlHttpRequest();
  20. use Symfony\Component\HttpFoundation\Response; $response = new Response(); $response->setCharset('UTF-8'); $response->setEtag('abcdef'); $response->headers->set('Content-Type', 'application/json');

    if ($response->isNotModified($request)) { $response->send(); } $response->setContent('{ "name": "John Doe"}'); $response->setStatusCode(200); $response->setPublic(); $response->setSharedMaxAge(3600); $response->prepare($request); $response->send();
  21. use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\RedirectResponse; $cookie = new Cookie('user', 'hhamon', '+30

    days'); $resp = new RedirectResponse('http://www.acme.com'); $resp->headers->setCookie($cookie); $resp->send(); Cookies and Redirects
  22. $storage = new NativeSessionStorage(); $session = new Session($storage); $session->start(); //

    Saving data into the session $session->set('cart', array(1, 2, 3)); $cart = $session->get('cart'); $session->save(); Session Management
  23. Routing http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

  24. Bidirectional Router

  25. use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $routes = new RouteCollection(); $routes->add('post', new

    Route( '/blog/{id}/{slug}', array('controller' => 'BlogController::post'), array('id' => '\d+') )); PHP Con guration
  26. # config/routing.yml blog: pattern: /blog defaults: { controller: 'BlogController::index' }

    post: pattern: /blog/{id}/{slug} defaults: { controller: 'BlogController::post' } requirements: { id: \d+ } YAML Con guration
  27. PHP XML YAML Closures Annotations

  28. use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\RequestContext; $context = new RequestContext($_SERVER['REQUEST_URI']); $matcher =

    new UrlMatcher($routes, $context); $parameters = $matcher->match('/blog/42/symfony-2'); Request Matching
  29. use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\Generator\UrlGenerator; // ... $context = new RequestContext($_SERVER['REQUEST_URI']);

    $generator = new UrlGenerator($routes, $context); echo $generator->generate('post', array( 'id' => '42', 'slug' => 'symfony-components', 'page' => '2', )); // Generates /blog/42/symfony-components?page=2 URLs Generator
  30. $locator = new FileLocator(array(__DIR__.'/config')); $router = new Router( new YamlFileLoader($locator),

    'routing.yml', array('cache_dir' => __DIR__.'/cache'), new RequestContext($_SERVER['REQUEST_URI']), ); $params = $router->match('/blog/42/symfony'); $url = $router->generate('blog'); The All-in-One Router
  31. HttpKernel http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

  32. namespace Symfony\Component\HttpKernel; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; interface HttpKernelInterface { const

    MASTER_REQUEST = 1; const SUB_REQUEST = 2; public function handle( Request $request, $type = self::MASTER_REQUEST, $catch = true); } The HttpKernel Interface
  33. namespace Acme; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Routing\RouterInterface; class Kernel

    implements HttpKernelInterface { private $router; public function __construct(RouterInterface $router) { $this->router = $router; } public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) { // ... } } Designing a Kernel for a Legacy Application
  34. class Kernel implements HttpKernelInterface { public function handle(Request $request, ...)

    { try { $uri = $request->server->get('PATH_INFO'); $params = $this->router->match($uri); } catch (ResourceNotFoundException $e) { $params['controller'] = 'Acme\Controller\ErrorController'; $params['action'] = 'error404'; } $method = sprintf('%sAction', $params['action']); $callable = array(new $params['controller'](), $method); return call_user_func_array($callable, array($request)); } }
  35. namespace Acme\Controller; use Symfony\Component\HttpFoundation\Response; abstract class Controller { public function

    render($name, array $context = array()) { $template = sprintf(__DIR__.'/../../../views/%s.php', $name); if (!is_readable($template)) { throw new \InvalidArgumentException('No "'. $template.'" template.'); } extract($context); ob_start(); include $template; $content = ob_get_clean(); return new Response($content, 200); } } The Base Controller Class
  36. namespace Acme\Controller; use Symfony\Component\HttpFoundation\Request; class BlogController extends Controller { public

    function indexAction(Request $request) { // Get a list of recent posts $posts = ...; return $this->render('blog/index', array('posts' => $posts)); } } The BlogController Class
  37. $request = Request::createFromGlobals(); $request->overrideGlobals(); $router = new Router(...); $kernel =

    new Acme\Kernel($router); $response = $kernel->handle($request); $response->send(); index.php
  38. Event Dispatcher http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

  39. None
  40. class Article { public function save(Database $con) { $parser =

    new Markdown(); $html = $parser->toHTML($this->getContent()); $this->setHtmlContent($html); $this->save($con); $this->updateLuceneIndex(); } }
  41. use Symfony\Component\EventDispatcher\EventDispatcher; // Declaring listeners $l1 = array(new ArticleListener(), 'onPreSave');

    $l2 = array(new LuceneListener(), 'onPostSave'); // Registering listeners $dispatcher = new EventDispatcher(); $dispatcher->addListener('article.pre_save', $l1); $dispatcher->addListener('article.post_save', $l2); Registering Listeners in the Dispatcher
  42. Setting the Dispatcher use Symfony\Component\EventDispatcher\EventDispatcher; class Article { private $dispatcher;

    public function setDispatcher(EventDispatcher $dp) { $this->dispatcher = $dp; } }
  43. Dispatching Events class Article { // ... public function save(Database

    $con) { $e = new ArticleEvent($this); $this->dispatcher->dispatch('article.pre_save', $e); $this->save($con); $this->dispatcher->dispatch('article.post_save', $e); } }
  44. The Listener class ArticleListener { public function onPreSave(ArticleEvent $event) {

    $article = $event->article; $markdown = $article->getContent(); $parser = new Markdown(); $html = $parser->toHTML($markdown); $article->setHtmlContent($html); } }
  45. Triggering the Registered Listeners $markdown = 'Some **markdown**'; $article =

    new Article(); $article->setDispatcher($dispatcher); $article->setMarkdownContent($markdown); $article->save();
  46. Filesystem http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

  47. http://www.flickr.com/photos/sebastian_bergmann/173880207/ PHP Way mkdir() copy() delete() unlink() chmod() chown() ...

  48. http://www.flickr.com/photos/amazeelabs/7843168540/ Symfony Way Filesystem

  49. $cache1 = __DIR__.'/tmp/cache1'; $cache2 = __DIR__.'/tmp/cache2'; $fs = new Symfony\Component\Filesystem\Filesystem();

    $fs->mkdir(array($cache1, $cache2)); $fs->touch($cache1.'/routing.php'); $fs->copy( $cache1.'/routing.php', $cache1.'/routing.old' ); $fs->remove($cache1); The Filesystem Object
  50. Finder http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

  51. use Symfony\Component\Finder\Finder; $finder = new Finder(); $finder ->files() ->in(__DIR__.'/src/Symfony/Bundle/FrameworkBundle') ->exclude('Tests')

    ->ignoreVCS(true) ->name('/Controller/') ; foreach ($finder->sortByName() as $file) { echo $file->getFilename()."\n"; }
  52. // Filter by name $finder->directories()->name('Tests'); $finder->files()->name('*.php'); $finder->files()->name('/\.php$/'); $finder->files()->notName('*.rb'); // Filter

    by size $finder->files()->size('< 1.5K'); $finder->files()->size('>= 1K')->size('<= 2K'); // Filter by date $finder->date('since yesterday'); // Limiting the depth $finder->depth('== 0'); $finder->depth('< 3');
  53. None
  54. $filter = function (\SplFileInfo $file) { if (strlen($file) > 10)

    { return false; } }; $finder->files()->filter($filter); Custom Filter
  55. Console http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

  56. http://www.flickr.com/photos/ideonexus/7012997141/lightbox/

  57. None
  58. None
  59. #!/usr/bin/env php $loader = require __DIR__.'/vendor/autoload.php'; $loader->add('Hangman', __DIR__.'/src'); use Symfony\Component\Console\Application;

    use Hangman\Command\HangmanPlayCommand; $application = new Application(); $application->add(new HangmanPlayCommand()); $application->run(); The Console Script
  60. namespace HangmanPlay\Command; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class HangmanPlayCommand

    extends Command { protected function configure() { // ... } protected function execute(InputInterface $input, OutputInterface $output) { // ... } }
  61. namespace HangmanPlay\Command; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class HangmanPlayCommand

    extends Command { protected function configure() { // ... } protected function execute(InputInterface $input, OutputInterface $output) { // ... } }
  62. protected function configure() { $this ->setName('hangman:play') ->setDescription('Play the famous Hangman

    game') ->addArgument('name', InputArgument::REQUIRED, 'Your name') ->addOption('length', 'l', InputOption::VALUE_REQUIRED, 'The word length', 8) ->setHelp(<<<EOF This is the manual for the <info>%command.name%</info> command. You can run the command like this: $ php %command.full_name% hhamon --length=6 EOF); }
  63. protected function execute(InputInterface $input, OutputInterface $output) { $words = new

    WordList(); $words->load(__DIR__.'/../../../data/words.txt'); $length = $input->getOption('length'); $name = $input->getArgument('name'); $game = new Game($words->getRandomWord($length)); // ... }
  64. protected function execute(InputInterface $input, OutputInterface $output) { // ... $dialog

    = $this->getHelperSet()->get('dialog'); $output->writeln('Welcome '.$name.' to the Hangman Game!'); while (!$game->isOver()) { $letter = $dialog->ask($output, 'Type a letter: '); $game->tryLetter($letter); $output->writeLn(implode(' ', $game->getSolution())); } if ($game->isWon()) { $output->writeln('Congratulations!'); } else { $output->writeln('Game Over!'); } }
  65. None
  66. Translation http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

  67. $selector = new MessageSelector(); $trans = new Translator('fr', $selector); $trans->setFallbackLocale('en');

    $loader = new XliffFileLoader(); $trans->addLoader('xliff', $loader); $trans->addResource('yaml', '/fr.yml', 'fr'); $trans->trans('Hello %name%', array( '%name%' => $name ));
  68. <?xml version="1.0"?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="file.ext"> <body>

    <trans-unit id="1"> <source>Hello %name%</source> <target>Bonjour %name%</target> </trans-unit> </body> </file> </xliff>
  69. Dependency Injection http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

  70. class Database { private $dbh; function __construct(PDO $dbh) { $this->dbh

    = $dbh; } function execute($query) { // ... } }
  71. <container ...> <services> <service id="db.pdo_handler" class="PDO"> <argument>mysql:host=localhost;dbname=demo</ argument> <argument>root</argument> <argument>secret</argument>

    </service> <service id="dbal" class="DatabaseConnection"> <argument type="service" id="db.pdo_handler"/> </service> </services> </container>
  72. $locator = new FileLocator(__DIR__); $container = new ContainerBuilder(); $loader =

    new XmlFileLoader( $container, $locator ); $loader->load('services.xml'); // Get the DBAL service $db = $container->get('dbal'); $result = $db->execute('SELECT ...');
  73. Validator http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

  74. use Symfony\Component\Validator\Validator; use Symfony\Component\Validator\Mapping\ClassMetadataFactory; use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader; use Symfony\Component\Validator\ConstraintValidatorFactory; use Symfony\Component\Validator\Constraints\Email;

    $validator = new Validator( new ClassMetadataFactory(new StaticMethodLoader()), new ConstraintValidatorFactory() ); $validator->validateValue('foo@bar.com', new Email());
  75. use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints; class Post { public $title; public

    $body; static public function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('title', new Constraints\NotNull()); $metadata->addPropertyConstraint('title', new Constraints\NotBlank()); $metadata->addPropertyConstraint('body', new Constraints\Length(array( 'max' => 10 ))); } }
  76. $post = new Post(); $post->title = 'Lorem ipsum...'; $post->body =

    'Lorem ipsum...'; $validator->validate($post);
  77. Process http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

  78. use Symfony\Component\Process\Process; $proc = new Process('ls -lah'); $proc->setWorkingDirectory(__DIR__.'/..'); $proc->setTimeout(3600); $proc->run();

    if (!$proc->isSuccessful()) { throw new RuntimeException( $proc->getErrorOutput() ); } print $proc->getOutput();
  79. total 0 drwxr-xr-x 11 Hugo staff 374B Sep 8 13:38

    . drwxr-xr-x 8 Hugo staff 272B Sep 8 13:39 .. drwxr-xr-x 4 Hugo staff 136B Aug 30 11:31 01-ClassLoader drwxr-xr-x 2 Hugo staff 68B Aug 30 11:30 02-HttpFoundation drwxr-xr-x 4 Hugo staff 136B Sep 4 22:06 03-Routing drwxr-xr-x 6 Hugo staff 204B Sep 2 11:49 04-HttpKernel drwxr-xr-x 4 Hugo staff 136B Sep 2 14:07 05-Filesystem drwxr-xr-x 3 Hugo staff 102B Sep 4 22:40 06-Finder drwxr-xr-x 5 Hugo staff 170B Sep 8 10:24 07-Console drwxr-xr-x 5 Hugo staff 170B Sep 8 12:55 08-Yaml drwxr-xr-x 3 Hugo staff 102B Sep 8 13:41 09-Process
  80. Serializer http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

  81. Serialization is the process of converting an object state into

    a format that can be stored and resurrected later Wikipedia  
  82. use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Encoder\XmlEncoder; // Register

    the normalizers $normalizers[] = new GetSetMethodNormalizer(); // Register the encoders $encoders[] = new JsonEncoder(); $encoders[] = new XmlEncoder(); // Create and initialize the serializer $serializer = new Serializer($normalizers, $encoders);
  83. $serializer ->serialize($object, 'xml') ;

  84. None
  85. Locale http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

  86. use Symfony\Component\Locale\Locale; // Get the country names for a locale

    or get all country codes $countries = Locale::getDisplayCountries('pl'); $countryCodes = Locale::getCountries(); // Get the language names for a locale or get all language codes $languages = Locale::getDisplayLanguages('fr'); $languageCodes = Locale::getLanguages(); // Get the locale names for a given code or get all locale codes $locales = Locale::getDisplayLocales('en'); $localeCodes = Locale::getLocales(); // Get ICU versions $icuVersion = Locale::getIcuVersion(); $icuDataVersion = Locale::getIcuDataVersion();
  87. CSS Selector http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

  88. use Symfony\Component\CssSelector\CssSelector; print CssSelector::toXPath('div.item > h4 > a'); Converting a

    CSS Selector to an XPath descendant-or-self::div[contains(concat(' ',normalize-space(@class), ' '), ' item ')]/ h4/a
  89. DOM Crawler http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

  90. use Symfony\Component\DomCrawler\Crawler; $html = <<<'HTML' <!DOCTYPE html> <html> <body> <p

    class="message">Hello World!</p> <p>Hello Crawler!</p> </body> </html> HTML; $crawler = new Crawler($html); $crawler = $crawler->filterXPath('descendant-or-self::body/p'); $crawler = $crawler->filter('body > p');
  91. // Filtering with CSS or XPath selectors $tweets = $crawler->filter('#sidebar

    .tweet'); $tweets = $crawler->filterXPath('//p[class="tweet"]'); // Traversing $first = $tweets->first(); $third = $tweets->eq(2); $last = $tweets->last(); // Extracting $text = $first->text(); $class = $first->attr('class'); $infos = $first->extract(array('_text', 'class')); Using the Crawler
  92. $filter = function ($node, $i) { $content = (string) $node->textContent;

    if (!preg_match('/symfony/i', $content)) { return false; } }; $tweets = $crawler->reduce($filter); Reducing a Crawler Selection
  93. Finding Links Finding Forms $link = $crawler->selectLink('Click me')->link(); $crawler =

    $client->click($link); $form = $crawler->selectButton('send')->form(); $client->submit($form, array('name' => 'Foo'));
  94. https://joind.in/talk/view/7085