Symfony components in the wild [SymfonyCon 2013]

1a4e1f98f3aeef310273366c8c785207?s=47 Jakub Zalas
December 13, 2013

Symfony components in the wild [SymfonyCon 2013]

Symfony is a full-stack framework built on top of reusable components. Since those components are decoupled, they’re also successfully used with many open source projects, custom-built applications, other frameworks or even legacy applications needing an injection of up-to-date technology. The talk is an introduction to several Symfony components. It shows how easy it is to use them alone, integrated into a custom project and how components are used with existing open source applications.

Feedback on joind.in: https://joind.in/talk/view/10366

1a4e1f98f3aeef310273366c8c785207?s=128

Jakub Zalas

December 13, 2013
Tweet

Transcript

  1. Symfony components in the wild Jakub Zalas

  2. What is Symfony?

  3. http://symfony.com/projects

  4. Projects on packagist.org 4373 21% 1206 6% 14838 73% Depends

    on Symfony Depends on Zend Other
  5. 0 100 200 300 400 500 600 700 Bundles Other

    Symfony Component based projects
  6. Fixing PHP Debug HttpFoundation Yaml Tools Filesystem Finder Process Stopwatch

    Web Framework Foundation HttpKernel Routing Templating Data Helpers Config OptionsResolver PropertyAccess Serializer Patterns DependencyInjection   EventDispatcher   I18n Intl + Icu Locale Translation Crawlers BrowserKit CssSelector DomCrawler Framework+ ClassLoader Console ExpressionLanguage Form Security Validator
  7. Fixing PHP

  8. HttpFoundation

  9. $_GET $_POST $_SERVER $_COOKIE $_FILES

  10. None
  11. use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals(); $request->query $request->request $request->server $request->cookies $request->files

    $request->headers $request->attributes
  12. $id = isset($_GET['id']) ? $_GET['id'] : -1; becomes $id =

    $request->query->get('id', -1);
  13. use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals(); $request->attributes->set('id', 15); function myController (Request

    $request) { $id = $request->attributes->get('id'); // do your stuff }
  14. $expiresAt = new \DateTime('+1hour'); $expiresAt->setTimezone( new \DateTimeZone('UTC') ); $expires =

    $expresAt->format('D, d M Y H:i:s'); $expires.= ' GMT’; header('X-Event: SymfonyCon'); header('Cache-Control: public'); header('Expires: '.$expires); echo 'Hello!';
  15. use Symfony\Component\HttpFoundation\Response; $response = new Response('Hello!', 200); $response->headers->set('X-Event', 'SymfonyCon'); $response->setPublic();

    $response->setExpires(new \DateTime('+1hour')); $response->send();
  16. // Somewhere in a controller $id = $request->query->get('id'); $date =

    $this->getPageUpdatedAtById($id); $response = new Response(); $response->setPublic(); $response->setLastModified($date); if ($response->isNotModified($request)) { return $response; } // else do heavy processing to render the page
  17. in the wild…

  18. use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals(); $configuration = new \Proxy\Configuration(); $configuration->setBackend('nousefreak.be');

    $proxy = new \Proxy\Proxy(); $proxy->setConfiguration($configuration); $response = $proxy->proxy($request); $response->send(); nousefreak/proxy  
  19. protected function createResponse( GuzzleResponse $gResponse, Request $request ) { $response

    = new Response( $gResponse->getBody(), $gResponse->getStatusCode() ); foreach ($gResponse->getHeaderLines() as $header) { list($name, $value) = explode(':', $header, 2); $response->headers->set($name, $value); } return $this->prepareResponse($response, $request); } nousefreak/proxy  
  20. Yaml

  21. use Symfony\Component\Yaml\Yaml; $yaml = <<<YAML parameters: username: kuba password: 123123

    YAML; $data = Yaml::parse($yaml); var_dump($data); // array( // 'parameters' => array( // 'username' => 'kuba', // 'password' => '123123' // ) // )
  22. foo: &foo bar: ~ hello: cześć test: <<: *foo bar:

    true obj: !!php/object:O:8:"stdClass":1:{s: 5:"title";s:14:"Foo in the bar";}
  23. in the wild…

  24. namespace Nelmio\Alice\Loader; use Symfony\Component\Yaml\Yaml as YamlParser; class Yaml extends Base

    { public function load($file) { ob_start(); $loader = $this; $includeWrapper = function () use ($file, $loader) { return include $file; }; $data = $includeWrapper(); if (true !== $data) { $yaml = ob_get_clean(); $data = YamlParser::parse($yaml); } // ... } } nelmio/alice  
  25. Debug

  26. None
  27. None
  28. \Symfony\Component\Debug\Debug::enable();

  29. None
  30. None
  31. in the wild…

  32. #!/usr/bin/env php <?php if (!@include __DIR__.'/../autoload.php') { die('You must set

    up the project dependencies'); } use Symfony\Component\Debug\Debug; Debug::enable(); $application = new Carew\Carew(); $application->run(); carew/carew  
  33. Hiding dirty details

  34. Process

  35. use Symfony\Component\Process\Process; $process = new Process('ls –l'); $exitCode = $process->run();

    $output = $process->getOutput();
  36. $process = new PhpProcess( '<?php echo md5("SymfonyCon");' ); $process->run(); $output

    = $process->getOutput();
  37. $printCallback = function ($type, $data) { printf('[%s] %s', $type, $data);

    }; $process = new Process( 'echo "Symfony"; sleep 2; echo "Con";' ); $process->start($printCallback); // [...] do other stuff while command is running $pid = $process->getPid(); // wait untill it finishes $process->wait();
  38. in the wild…

  39. $server = new HttpServer\HttpServer( '/var/www', 'localhost', 8080 ); $server->start(); $contents

    = file_get_contents( 'http://localhost:8080/example.php' ); $server->stop(); robloach/h6pserver  
  40. class HttpServer extends PhpProcess { // ... public function start($callback

    = null) { if (false === $php = $this->executableFinder->find()) { throw new \RuntimeException('Unable to find PHP'); } $options = ' -S ' . $this->addr . ':' . $this->port; if (isset($this->documentRoot)) { $options .= ' -t ' . $this->documentRoot; } $this->setCommandLine($php . $options); parent::start($callback); } } robloach/h6pserver  
  41. Making better developers

  42. EventDispatcher

  43. Subjects   Listeners   Event  Dispatcher   Mailer  Listener  

    RegistraAon   Dispatcher   add  listener   registraAon.success   noAfy   registraAon.success   call   register  
  44. Subjects   Listeners   Event  Dispatcher   Mailer   Listener

      RegistraAon   Dispatcher   add  listener   registraAon.success   noAfy   registraAon.success   call   SMS   Listener   add  listener   registraAon.success   call   register  
  45. use Symfony\Component\EventDispatcher\EventDispatcherInterface; class Registration { private $dispatcher; public function __construct(

    EventDispatcherInterface $dispatcher ) { $this->dispatcher = $dispatcher; } // ... }
  46. use Symfony\Component\EventDispatcher\GenericEvent; class Registration { // ... public function register($user)

    { // register the user ... // dispatch an event if registration was // successful $this->dispatcher->dispatch( 'registration.success', new GenericEvent($user) ); } }
  47. class MailerListener { private $mailer; private $twig; public function __construct(

    Mailer $mailer, TwigEngine $twig ) { $this->mailer = $mailer; $this->twig = $twig; } // ... }
  48. use Symfony\Component\EventDispatcher\GenericEvent; class MailerListener { // ... public function sendMail(GenericEvent

    $event) { $user = $event->getSubject(); $message = $this->createMessage($user) $this->mailer->send($message); } private function createMessage($user) { return $this->twig->render(/* */); } }
  49. use Symfony\Component\EventDispatcher\EventDispatcher; $mailerListener = new MailerListener($mailer, $twig); $smsListener = new

    SmsListener($smsGateway); $dispatcher = new EventDispatcher(); $dispatcher->addListener( 'registration.success', array($mailerListener, 'sendMail') ); $dispatcher->addListener( 'registration.success', array($smsListener, 'sendSms’) ); $registration = new Registration($dispatcher); $registration->register($user);
  50. in the wild…

  51. use Phoebe\Connection; use Phoebe\Event\Event; $freenode = new Connection(); $freenode->setServerHostname('irc.freenode.net'); $freenode->setNickname('Phoebe2');

    $dispatcher = $freenode->getEventDispatcher(); $dispatcher->addListener( 'irc.received.001', function (Event $event) { $event->getWriteStream()->ircJoin('#symfony'); } ); $events->addSubscriber(new BeerPlugin()); sAl/phoebe  
  52. use Phoebe\Event\Event; use Phoebe\Plugin\PluginInterface; class BeerPlugin implements PluginInterface { public

    static function getSubscribedEvents() { return [ 'irc.received.PRIVMSG' => array('onMessage', 0) ]; } public function onMessage(Event $event) { $message = $event->getMessage(); if (!$message->matchText('/SymfonyCon/') { return; } $es = $event->getWriteStream(); $es->ircPrivmsg($message->getSource(), 'Hello!'); } } sAl/phoebe  
  53. https://github.com/jakzal/SymfonyConBot sAl/phoebe  

  54. DependencyInjection

  55. class appDevDebugProjectContainer extends Container { protected function getSymfonyConApiService() { $this->services['symfonycon.api']

    = $instance = new SymfonyConApi(); return $instance; } }
  56. <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/..." ...> <services> <service id="symfonycon.api" class="SymfonyConApi"

    /> </services> </container>
  57. <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/..." ...> <services> <service id="http_client" class="HttpClient">

    <argument>%symfonycon.api.username%</argument> <argument>%symfonycon.api.key%</argument> </service> <service id="symfonycon.api" class="SymfonyCon"> <argument type="service" id="http_client" /> <call method="chooseEndpoint"> <argument>%symfonycon.api.endpoint%</argument> </call> <service> </services> </container>
  58. in the wild…

  59. // BehatApplication protected function createContainer( InputInterface $input ) { $container

    = new ContainerBuilder(); $this->loadCoreExtension( $container, $this->loadConfiguration( $container, $input ) ); $container->compile(); return $container; } behat/behat  
  60. default: extensions: Behat\Symfony2Extension\Extension: mink_driver: true Behat\MinkExtension\Extension: default_session: symfony2 PSS\Behat\Symfony2MockerExtension\Extension: ~

    behat/behat  
  61. namespace PSS\Behat\Symfony2MockerExtension; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Behat\Behat\Extension\ExtensionInterface;

    class Extension implements ExtensionInterface { public function load( array $config, ContainerBuilder $container ) { $loader = new XmlFileLoader( $container, new FileLocator(__DIR__.'/services') ); $loader->load('core.xml'); } // ... } behat/behat  
  62. Having healthy relationships

  63. Routing

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

    new Route( '/hello/{name}', array('controller' => 'HelloController') ) );
  65. use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\RequestContext; $context = new RequestContext( '/hello/Poland', 'GET’

    ); $matcher = new UrlMatcher($routes, $context); $parameters = $matcher->match('/hello/Poland'); var_dump($parameters); // array( // 'controller' => 'HelloController', // 'name' => 'Poland', // '_route' => 'hello' // )
  66. use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\RequestContext; $request = Request::createFromGlobals(); $context

    = new RequestContext(); $context->fromRequest($request); $matcher = new UrlMatcher($routes, $context);
  67. in the wild…

  68. use Ratchet\App; use Ratchet\Server\EchoServer; $app = new App('localhost', 8080); $app->route('/chat',

    new MyChat()); $app->route('/echo', new EchoServer(), ['*']); $app->run(); cboden/ratchet  
  69. Leading innovation

  70. ExpressionLanguage

  71. use Symfony\Component\ExpressionLanguage\ExpressionLanguage; $language = new ExpressionLanguage(); echo $language->evaluate('1 + 1');

    // 2 echo $language->compile('1 + 2'); // (1 + 2)
  72. echo $language->evaluate( 'life + universe + everything', array( 'life' =>

    10, 'universe' => 10, 'everything' => 22, ) ); // 42
  73. class Conference { public function getName() { return 'SymfonyCon'; }

    } echo $language->evaluate( 'c.getName()', array('c' => new Conference('SymfonyCon')) ); // SymfonyCon
  74. $language->register( 'reverse', function ($string) { if (!is_string($string)) { return $string;

    } return sprintf('strrev(%s)', $string); }, function ($arguments, $string) { if (!is_string($string)) { return $string; } return strrev($string); } ); echo $language->evaluate('reverse("SymfonyCon")');
  75. services: symfonycon.api: class: SymfonyCon\Api arguments: - "@=service('factory').getHttpClient()" new \SymfonyCon\Api( $container->get('factory')->getHttpClient()

    );
  76. "@=container.hasParameter('a') ? parameter('a') : 'default'"

  77. /** * @Route("/post/{id}") * @Cache(lastModified="post.getUpdatedAt()") */ public function showAction(Post $post)

    { }
  78. /** * @Route("/post/{id}") * @Method("GET") * @Cache(lastModified="post.getUpdatedAt()") * @ParamConverter("post", class="Sensio:Post")

    * @Security("has_role('ROLE_USER')") * @Template() * @MyResponseConverter("xml") */ public function showAction(Post $post) { return $post; } Annotation Driven Development
  79. in the wild…

  80. /** * @Hateoas\Relation( * "self", * href = "expr('/api/users/' ~

    object.getId())" * ) */ class Post {} willdurand/hateoas  
  81. use BCC\EnumerableUtility\Collection; $values = new Collection(array(1, 2, 3)); $values->where(function($item) {

    return $item % 2; }); $values->select(array( 'i' => 'i * m', 'm' => 2, )); bcc/enumerable-­‐uAlity  
  82. It’s Plug & Play!

  83. Console DomCrawler CssSelector

  84. None
  85. None
  86. None
  87. None
  88. None
  89. None
  90. None
  91. None
  92. None
  93. None
  94. Symfony Components •  Make PHP better •  Make your code

    better •  Make you better
  95. Thank you! o  I work @SensioLabsUK o  I tweet @jakub_zalas

    o  I code @jakzal
  96. Credits •  http://www.sxc.hu/photo/1223174 •  http://www.sxc.hu/photo/338038 •  http://www.sxc.hu/photo/13223 •  http://www.sxc.hu/photo/1092493 • 

    http://www.sxc.hu/photo/308460 •  http://www.sxc.hu/photo/1412072 •  http://www.sxc.hu/photo/771223 •  http://www.sxc.hu/photo/1178514 •  http://www.sxc.hu/photo/1379212 •  http://www.sxc.hu/photo/1432060 •  http://www.sxc.hu/photo/1115716