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. { "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
  2. 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();
  3. # 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
  4. 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 ) ) )
  5. $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
  6. 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();
  7. 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();
  8. 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
  9. $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
  10. 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
  11. # 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
  12. 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
  13. $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
  14. 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
  15. 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
  16. 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)); } }
  17. 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
  18. 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
  19. $request = Request::createFromGlobals(); $request->overrideGlobals(); $router = new Router(...); $kernel =

    new Acme\Kernel($router); $response = $kernel->handle($request); $response->send(); index.php
  20. class Article { public function save(Database $con) { $parser =

    new Markdown(); $html = $parser->toHTML($this->getContent()); $this->setHtmlContent($html); $this->save($con); $this->updateLuceneIndex(); } }
  21. 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
  22. Setting the Dispatcher use Symfony\Component\EventDispatcher\EventDispatcher; class Article { private $dispatcher;

    public function setDispatcher(EventDispatcher $dp) { $this->dispatcher = $dp; } }
  23. 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); } }
  24. 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); } }
  25. Triggering the Registered Listeners $markdown = 'Some **markdown**'; $article =

    new Article(); $article->setDispatcher($dispatcher); $article->setMarkdownContent($markdown); $article->save();
  26. $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
  27. 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"; }
  28. // 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');
  29. $filter = function (\SplFileInfo $file) { if (strlen($file) > 10)

    { return false; } }; $finder->files()->filter($filter); Custom Filter
  30. #!/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
  31. 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) { // ... } }
  32. 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) { // ... } }
  33. 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); }
  34. 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)); // ... }
  35. 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!'); } }
  36. $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 ));
  37. <?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>
  38. class Database { private $dbh; function __construct(PDO $dbh) { $this->dbh

    = $dbh; } function execute($query) { // ... } }
  39. <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>
  40. $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 ...');
  41. 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 ))); } }
  42. $post = new Post(); $post->title = 'Lorem ipsum...'; $post->body =

    'Lorem ipsum...'; $validator->validate($post);
  43. 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();
  44. 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
  45. Serialization is the process of converting an object state into

    a format that can be stored and resurrected later Wikipedia  
  46. 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);
  47. 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();
  48. 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
  49. 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');
  50. // 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
  51. $filter = function ($node, $i) { $content = (string) $node->textContent;

    if (!preg_match('/symfony/i', $content)) { return false; } }; $tweets = $crawler->reduce($filter); Reducing a Crawler Selection
  52. 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'));