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

Don't Reinvent the Wheel with Symfony2 Components!

Hugo Hamon
November 02, 2012

Don't Reinvent the Wheel with Symfony2 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 of Symfony like the Dependency Injection Container, Event Dispatcher, Console, HttpFoundation, HttpKernel, Routing, Browser Kit, Css Selector and Dom Crawler, and much more...

Hugo Hamon

November 02, 2012
Tweet

More Decks by Hugo Hamon

Other Decks in Technology

Transcript

  1. Don’t Reinvent the Wheel
    with Symfony Components
    h"p://www.flickr.com/photos/25947610@N04/2546779304  

    View Slide

  2. View Slide

  3. 0.
    What is Symfony2?

    View Slide

  4. Symfony2 is a set of
    standalone, decoupled and
    cohesive components.
    On top of these
    components, Symfony2 is
    also a full-stack
    framework.

    View Slide

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

    View Slide

  6. View Slide

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

  8. HttpKernel
    HttpFoundation
    Routing
    EventDispatcher
    Dependency
    Injection
    CssSelector
    DomCrawler
    BrowserKit
    Config
    Yaml
    Security
    OptionsResolver
    Console
    Filesystem
    Finder
    Locale
    Process
    Serializer
    Templating
    Form
    Validator
    Translation

    View Slide

  9. They already use them!

    View Slide

  10. 1.
    HttpFoundation

    View Slide

  11. GET /hello/hugo?p=2 HTTP/1.1
    Host: say.hello.com
    Accept: text/html
    Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
    Cookie: username=hhamon
    User-Agent: Mozilla/5.0 Firefox/16.0
    $path = $_SERVER['REQUEST_URI'];
    $page = isset($_GET['p']) ? $_GET['p'] : 1;
    $cookie = $_COOKIE['username'];
    $lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
    $agent = $_SERVER['HTTP_USER_AGENT'];
    Request

    View Slide

  12. GET /hello/hugo?p=2 HTTP/1.1
    Host: say.hello.com
    Accept: text/html
    Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
    Cookie: username=hhamon
    User-Agent: Mozilla/5.0 Firefox/16.0
    use Symfony\Component\HttpFoundation\Request;
    $request = Request::createFromGlobals();
    $path = $request->getPathInfo();
    $page = $request->query->get('p', 1);
    $cookie = $request->cookies->get('username');
    $lang = $request->getPreferredLanguage();
    $agent = $request->server->get('USER_AGENT');
    Request

    View Slide

  13. HTTP/1.1 200 OK
    Date: Fri, 02 Nov 2012 02:14:17 GMT
    Cache-Control: private, max-age=90
    Set-Cookie: username=anonymous;
    Content-Type: text/html; charset=UTF-8
    Hello Hugo!
    Response
    header('HTTP/1.1 200 OK');
    header('Cache-Control: private, max-age=90');
    setcookie('username', 'anonymous');
    echo 'Hello Hugo!';

    View Slide

  14. HTTP/1.1 200 OK
    Date: Fri, 02 Nov 2012 02:14:17 GMT
    Cache-Control: private, max-age=90
    Set-Cookie: username=anonymous;
    Content-Type: text/html; charset=UTF-8
    Hello Hugo!
    Response
    use Symfony\Component\HttpFoundation\Cookie;
    use Symfony\Component\HttpFoundation\Response;
    $username = new Cookie('username', $name);
    $response = new Response('Hello Hugo!');
    $response->setMaxAge(90);
    $response->headers->setCookie($username);
    $response->send();

    View Slide

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

    View Slide

  16. Session Management
    use Symfony\Component\HttpFoundation\Session;
    $storage = new Storage\Handler\NativeSessionStorage();
    $session = new Session\Session($storage);
    $session->start();
    // Saving data into the session
    $session->set('cart', array(1, 2, 3));
    $cart = $session->get('cart');
    $session->save();

    View Slide

  17. 2.
    Routing

    View Slide

  18. Bidirectional Router

    View Slide

  19. Router in action
    Router
    R1  
    R2  
    R3  
    R3  
    R   Route
    /hello/hugo
    /home
    /about
    /hello/{name}

    View Slide

  20. Configuring Routes with PHP
    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+')
    ));

    View Slide

  21. Configuring Routes inYAML
    # config/routing.yml
    blog:
    pattern: /blog
    defaults: { controller: 'BlogController::index' }
    post:
    pattern: /blog/{id}/{slug}
    defaults: { controller: 'BlogController::post' }
    requirements: { id: \d+ }

    View Slide

  22. Matching a Route
    use Symfony\Component\Routing\Matcher\UrlMatcher;
    use Symfony\Component\Routing\RequestContext;
    $context = new RequestContext($_SERVER['REQUEST_URI']);
    $matcher = new UrlMatcher($routes, $context);
    $params = $matcher->match('/blog/42/symfony-2');

    View Slide

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

    View Slide

  24. The all-in-one Router
    $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');

    View Slide

  25. 3.
    Http Kernel

    View Slide

  26. Kernel in action
    Kernel
    Response
    Request
    Where the
    magic
    happens!

    View Slide

  27. Kernel in action
    // index.php
    use Symfony\Component\HttpFoundation\Request;
    $request = Request::createFromGlobals();
    $request->overrideGlobals();
    $kernel = new DrupalKernel(...);
    $response = $kernel->handle($request);
    $response->prepare($request);
    $response->send();

    View Slide

  28. The HttpKernelInterface
    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);
    }

    View Slide

  29. Wrapping a legacy application
    // index.php
    require_once 'config.php';
    $module = !empty($_GET['module']) ? $_GET['module'] : '';
    $action = !empty($_GET['action']) ? $_GET['action'] : '';
    if (!controller_exists($module, $action)) {
    header('HTTP/1.0 404 Page Not Found');
    echo render_view('error', '404');
    exit;
    }
    echo include_controller($module, $action);

    View Slide

  30. use Symfony\Component\HttpKernel\HttpKernelInterface;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    class LegacyKernel implements HttpKernelInterface
    {
    public function handle(Request $request, ...)
    {
    $module = $request->query->get('module');
    $action = $request->query->get('action');
    require_once 'config.php';
    if (!controller_exists($module, $action)) {
    $html = render_view('error', '404');
    return new Response($html, 404);
    }
    $html = include_controller($module, $action);
    return new Response($html, 200);
    }
    }

    View Slide

  31. Wrapping a legacy application
    // index.php
    use Symfony\Component\HttpFoundation\Request;
    $request = Request::createFromGlobals();
    $request->overrideGlobals();
    $kernel = new LegacyKernel();
    $response = $kernel->handle($request);
    $response->prepare($request);
    $response->send();

    View Slide

  32. 4.
    Event Dispatcher

    View Slide

  33. View Slide

  34. Real World Example
    $order = new Order();
    $order->add('Cheeseburger', 3);
    $order->add('Caesar Salad', 1);
    $order->add('Coffee', 4);
    $cashier = new Cashier();
    $cashier->dispatch($order);

    View Slide

  35. public function dispatch(Order $order)
    {
    if ($order->has('Cheeseburger')) {
    $this->cookCheesburger($order->getQty('Cheeseburger'));
    }
    if ($order->has('Caesar Salad')) {
    $this->cookCaesarSalad($order->getQty('Caesar Salad'));
    }
    if ($order->has('Coffee')) {
    $this->cookCoffee($order->getQty('Coffee'));
    }
    // ...
    }

    View Slide

  36. View Slide

  37. public function dispatch(Order $order)
    {
    // ...
    if ($order->has('Pizza')) {
    $qty = $order->getQty('Pizza');
    $this->cookPizza($qty);
    }
    // ...
    }

    View Slide

  38. use Symfony\Component\EventDispatcher\EventDispatcher;
    $cashier = new EventDispatcher();
    $listeners[] = array(new CheesburgerCooker(), 'onMealCook');
    $listeners[] = array(new CaesarSaladCooker(), 'onMealCook');
    $listeners[] = array(new CoffeeCooker(), 'onMealCook');
    $listeners[] = array(new PizzaCooker(), 'onMealCook');
    foreach ($listeners as $listener) {
    $cashier->addListener('meal.cook', $listener);
    }
    Using the Event Dispatcher

    View Slide

  39. $cashier = new EventDispatcher();
    // ...
    $order = new Order();
    $order->add('Cheeseburger', 3);
    $order->add('Caesar Salad', 1);
    $order->add('Coffee', 4);
    $order->add('Pizza', 1);
    $event = new MealEvent($order);
    $cashier->dispatch('meal.cook', $event);

    View Slide

  40. use Symfony\Component\EventDispatcher\Event;
    class MealEvent extends Event
    {
    public $order;
    public function __construct(Order $order)
    {
    $this->order = $order;
    }
    }
    Designing an event

    View Slide

  41. class PizzaCooker
    {
    public function onMealCook(MealEvent $event)
    {
    if (!$event->order->has('Pizza')) {
    return;
    }
    $quantity = $event->order->getQuantity('Pizza');
    // ... cook the pizza!!!
    }
    }
    Designing a listener (cooker)

    View Slide

  42. 5.
    YAML

    View Slide

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

    View Slide

  44. # 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
    Designing a listener (cooker)

    View Slide

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

    View Slide

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

  47. $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 an array to YAML

    View Slide

  48. use Symfony\Component\Yaml\Yaml;
    $yaml = Yaml::dump($config);
    file_put_contents(
    __DIR__.'/config.yml',
    $yaml
    );
    Dumping an array to YAML

    View Slide

  49. 6.
    Filesystem

    View Slide

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

    View Slide

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

    View Slide

  52. $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 Slide

  53. abstract class AbstractCommand extends Command
    {
    // ...
    protected function createDirectory($directory)
    {
    $filesystem = $this->getFilesystem();
    try {
    $filesystem->mkdir($directory);
    } catch (IOException $e) {
    throw new \RuntimeException('...', 0, $e);
    }
    }
    }
    Example taken from Propel2

    View Slide

  54. 7.
    Finder

    View Slide

  55. View Slide

  56. 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";
    }
    Using the Finder Component

    View Slide

  57. // 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 Slide

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

    View Slide

  59. class MagentoFinder extends Symfony\Component\Finder\Finder
    {
    public function __construct()
    {
    parent::__construct();
    $this
    ->name('*.php')
    ->name('*.phtml')
    ->name('*.xml')
    ->exclude(array(
    'lib',
    'shell',
    'app/Mage.php',
    'app/code/core',
    'app/code/community',
    'app/design/frontend/default',
    'app/design/frontend/enterprise/default',
    'app/design/frontend/base',
    'app/design/adminhtml/default')
    )
    ;
    }
    }

    View Slide

  60. 8.
    Console

    View Slide

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

    View Slide

  62. View Slide

  63. View Slide

  64. #!/usr/bin/env php
    $loader = require __DIR__.'/vendor/autoload.php';
    $loader->add('Hangman', __DIR__.'/src');
    use Symfony\Component\Console\Application;
    use Acme\Command\HangmanPlayCommand;
    $application = new Application();
    $application->add(new HangmanPlayCommand());
    $application->run();
    The Console Script

    View Slide

  65. namespace Acme\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
    ) { ... }
    }
    Creating a new Command Class

    View Slide

  66. 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)
    ->setHelp('The complete manual ...');
    }
    Documenting the Command

    View Slide

  67. protected function execute($input, $output)
    {
    $words = new WordList();
    $words->load(__DIR__.'/../../../data/words.txt');
    $length = $input->getOption('length', 8);
    $name = $input->getArgument('name');
    $game = new Game($words->getRandomWord($length));
    // ...
    }
    Adding the business logic

    View Slide

  68. protected function execute($input, $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 Slide

  69. View Slide

  70. Some purposes
    Generating les
    Deploying les
    Dumping con guration
    Clearing caches
    Generating documentation
    Running a web server
    Sending batch emails…

    View Slide

  71. 9.
    Translation

    View Slide

  72. View Slide

  73. Basic Usage
    $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 Slide

  74. XLIFF Support

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


    Hello %name%
    Bonjour %name%




    View Slide

  75. Usage in Behat
    class TranslatedLoader implements LoaderInterface
    {
    private $translator;
    // ...
    public function load(ContextInterface $context)
    {
    foreach ($context->getTranslationResources() as $path) {
    $extension = pathinfo($path, PATHINFO_EXTENSION);
    if ('yml' === $extension) {
    $this->translator->addResource(
    'yaml', $path, basename($path, '.yml'),
    'behat.definitions'
    );
    }
    // ...
    }
    }
    }

    View Slide

  76. 10.
    Dependency Injection

    View Slide

  77. What’s dependency injection?
    « Dependency Injection is where
    components are given their
    dependencies through their
    constructors, methods, or directly
    into elds. »
    http://picocontainer.org/injection.html

    View Slide

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

    View Slide

  79. $container->setParameter('foo', 'bar');
    $container->getParameter('dbal.dsn');
    $dbal = $container->get('dbal');
    The Service Container

    View Slide

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

    View Slide

  81. Initializing the Container
    $container = new ContainerBuilder();
    $params = array(
    'dbal.dsn' => 'mysql:host=localhost;dbname=demo',
    'dbal.user' => 'root',
    'dbal.password' => 'secret',
    );
    foreach ($params as $name => $value) {
    $container->setParameter($name, $value);
    }

    View Slide

  82. Registering a service
    $container = new ContainerBuilder();
    // ...
    $container
    ->register('dbal.pdo_handler', 'PDO')
    ->addArgument('%dbal.dsn%')
    ->addArgument('%dbal.user%')
    ->addArgument('%dbal.password%')
    ;

    View Slide

  83. Injecting service references
    $container = new ContainerBuilder();
    // ...
    $reference = new Reference('dbal.pdo_handler');
    $container
    ->register('dbal', 'Database')
    ->addArgument($reference)
    ;

    View Slide

  84. Describing services with XML


    mysql:...
    root
    secret



    %dbal.dsn%
    %dbal.user%
    %dbal.password%



    View Slide

  85. Describing services with XML

    View Slide

  86. 12.
    Validator

    View Slide

  87. The Validator Component
    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 Slide

  88. Validating PHP Objects
    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 Slide

  89. Validating PHP Objects
    $post = new Post();
    $post->title = 'Lorem ipsum...';
    $post->body = 'Lorem ipsum...';
    $violations = $validator
    ->validate($post)
    ;

    View Slide

  90. 12.
    Process

    View Slide

  91. Executing command lines
    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 Slide

  92. Executing command lines
    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 Slide

  93. 13.
    CSS Selector

    View Slide

  94. CSS Selectors to XPath
    use Symfony\Component\CssSelector\CssSelector;
    print CssSelector::toXPath('div.item > h4 > a');
    descendant-or-self::div[contains(concat('
    ',normalize-space(@class), ' '), ' item ')]/
    h4/a

    View Slide

  95. 14.
    DOM Crawler

    View Slide

  96. Using the DOM Crawler
    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 Slide

  97. The DOM Crawler API
    // 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'));

    View Slide

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

    View Slide

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

  100. DOM Crawler in Goutte
    use Goutte\Client;
    $client = new Client();
    $crawler = $client->request('GET', 'http://www.symfony.com');
    $download = $crawler
    ->filter('#header .download_button')
    ->link()
    ;
    $crawler = $client->click($download);

    View Slide

  101. https://joind.in/talk/view/7413

    View Slide