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

Don't reinvent the wheel with Symfony components!

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

Hugo Hamon

September 15, 2012
Tweet

More Decks by Hugo Hamon

Other Decks in Technology

Transcript

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

    View full-size slide

  2. http://www.flickr.com/photos/alshepmcr/4298593016/

    View full-size slide

  3. {
    "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

    View full-size slide

  4. ClassLoader
    http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

    View full-size slide

  5. 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();

    View full-size slide

  6. YAML
    http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

    View full-size slide

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

    View full-size slide

  8. # 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

    View full-size slide

  9. use Symfony\Component\Yaml\Yaml;
    $config = Yaml::parse('parameters.yml');
    echo '';
    print_r($config);
    echo '';
    Loading a YAML File

    View full-size slide

  10. 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
    )
    )
    )

    View full-size slide

  11. $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

    View full-size slide

  12. use Symfony\Component\Yaml\Yaml;
    $yaml = Yaml::dump($config);
    echo $yaml;
    Dumping a YAML File

    View full-size slide

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

    View full-size slide

  14. Client Server
    HTTP Request
    HTTP Response
    … Network …

    View full-size slide

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

    View full-size slide

  16. http://www.flickr.com/photos/amazeelabs/7843168540/
    Symfony Way
    Request
    Response
    Session
    Cookie
    File
    ParameterBag
    HeaderBag
    Streaming
    HTTP Caching
    ...

    View full-size slide

  17. 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();

    View full-size slide

  18. 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();

    View full-size slide

  19. 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

    View full-size slide

  20. $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

    View full-size slide

  21. Routing
    http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

    View full-size slide

  22. Bidirectional Router

    View full-size slide

  23. 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

    View full-size slide

  24. # 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

    View full-size slide

  25. PHP
    XML
    YAML
    Closures
    Annotations

    View full-size slide

  26. 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

    View full-size slide

  27. 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

    View full-size slide

  28. $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

    View full-size slide

  29. HttpKernel
    http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

    View full-size slide

  30. 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

    View full-size slide

  31. 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

    View full-size slide

  32. 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));
    }
    }

    View full-size slide

  33. 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

    View full-size slide

  34. 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

    View full-size slide

  35. $request = Request::createFromGlobals();
    $request->overrideGlobals();
    $router = new Router(...);
    $kernel = new Acme\Kernel($router);
    $response = $kernel->handle($request);
    $response->send();
    index.php

    View full-size slide

  36. Event Dispatcher
    http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

    View full-size slide

  37. class Article
    {
    public function save(Database $con)
    {
    $parser = new Markdown();
    $html = $parser->toHTML($this->getContent());
    $this->setHtmlContent($html);
    $this->save($con);
    $this->updateLuceneIndex();
    }
    }

    View full-size slide

  38. 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

    View full-size slide

  39. Setting the Dispatcher
    use Symfony\Component\EventDispatcher\EventDispatcher;
    class Article
    {
    private $dispatcher;
    public function setDispatcher(EventDispatcher $dp)
    {
    $this->dispatcher = $dp;
    }
    }

    View full-size slide

  40. 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);
    }
    }

    View full-size slide

  41. 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);
    }
    }

    View full-size slide

  42. Triggering the Registered Listeners
    $markdown = 'Some **markdown**';
    $article = new Article();
    $article->setDispatcher($dispatcher);
    $article->setMarkdownContent($markdown);
    $article->save();

    View full-size slide

  43. Filesystem
    http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  46. $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

    View full-size slide

  47. Finder
    http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

    View full-size slide

  48. 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";
    }

    View full-size slide

  49. // 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');

    View full-size slide

  50. $filter = function (\SplFileInfo $file) {
    if (strlen($file) > 10) {
    return false;
    }
    };
    $finder->files()->filter($filter);
    Custom Filter

    View full-size slide

  51. Console
    http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

    View full-size slide

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

    View full-size slide

  53. #!/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

    View full-size slide

  54. 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)
    {
    // ...
    }
    }

    View full-size slide

  55. 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)
    {
    // ...
    }
    }

    View full-size slide

  56. 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(<<This is the manual for the %command.name% command.
    You can run the command like this:
    $ php %command.full_name% hhamon --length=6
    EOF);
    }

    View full-size slide

  57. 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));
    // ...
    }

    View full-size slide

  58. 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!');
    }
    }

    View full-size slide

  59. Translation
    http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

    View full-size slide

  60. $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
    ));

    View full-size slide


  61. xmlns="urn:oasis:names:tc:xliff:document:1.2">
    datatype="plaintext" original="file.ext">


    Hello %name%
    Bonjour %name%




    View full-size slide

  62. Dependency Injection
    http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

    View full-size slide

  63. class Database
    {
    private $dbh;
    function __construct(PDO $dbh)
    {
    $this->dbh = $dbh;
    }
    function execute($query)
    {
    // ...
    }
    }

    View full-size slide




  64. mysql:host=localhost;dbname=demo
    argument>
    root
    secret






    View full-size slide

  65. $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 ...');

    View full-size slide

  66. Validator
    http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

    View full-size slide

  67. 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('[email protected]', new Email());

    View full-size slide

  68. 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
    )));
    }
    }

    View full-size slide

  69. $post = new Post();
    $post->title = 'Lorem ipsum...';
    $post->body = 'Lorem ipsum...';
    $validator->validate($post);

    View full-size slide

  70. Process
    http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

    View full-size slide

  71. 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();

    View full-size slide

  72. 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

    View full-size slide

  73. Serializer
    http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

    View full-size slide

  74. Serialization is the process of
    converting an object state
    into a format that can be
    stored and resurrected later
    Wikipedia  

    View full-size slide

  75. 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);

    View full-size slide

  76. $serializer
    ->serialize($object, 'xml')
    ;

    View full-size slide

  77. Locale
    http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

    View full-size slide

  78. 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();

    View full-size slide

  79. CSS Selector
    http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

    View full-size slide

  80. 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

    View full-size slide

  81. DOM Crawler
    http://www.flickr.com/photos/justhatch/6134580094/sizes/l/in/photostream/

    View full-size slide

  82. use Symfony\Component\DomCrawler\Crawler;
    $html = <<<'HTML'



    Hello World!
    Hello Crawler!


    HTML;
    $crawler = new Crawler($html);
    $crawler = $crawler->filterXPath('descendant-or-self::body/p');
    $crawler = $crawler->filter('body > p');

    View full-size slide

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

    View full-size slide

  84. $filter = function ($node, $i) {
    $content = (string) $node->textContent;
    if (!preg_match('/symfony/i', $content)) {
    return false;
    }
    };
    $tweets = $crawler->reduce($filter);
    Reducing a Crawler Selection

    View full-size slide

  85. 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'));

    View full-size slide

  86. https://joind.in/talk/view/7085

    View full-size slide