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

Drupal 8 - The Symfony and Drupal partnership

Drupal 8 - The Symfony and Drupal partnership

Symfony has changed the PHP world forever, not only with its reusable and decoupled components, but also by introducing development standards which were not widely used or even known in the PHP world before. Thanks to its component architecture, Symfony is being widely adopted by many Open Source projects, custom-built applications, other frameworks, and even legacy applications.

Symfony’s growing community has always pushed for better practices and turned many good developers into awesome aficionados. Due to Drupal’s migration to Symfony, both communities have already started to blend and collaborate to bring benefit to both camps.

During the talk Jakub will explain exactly what benefits this new collaboration can offer. Following this, he will introduce several components to help debunk Symfony myths and increase developer confidence in the framework ahead of Drupal 8 later this year.

Jakub Zalas

May 31, 2014
Tweet

More Decks by Jakub Zalas

Other Decks in Programming

Transcript

  1. What is Symfony? First, Symfony2 is a reusable set of

    standalone, decoupled, and cohesive PHP components that solve common web development problems. http://fabien.potencier.org/article/49/what-is-symfony2
  2. 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
  3. What is Symfony? Then, based on these components, Symfony2 is

    also a full-stack web framework. http://fabien.potencier.org/article/49/what-is-symfony2
  4. Projects on packagist.org 4373 21% 1206 6% 14838 73% Depends

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

    Symfony Component based projects December  2013  
  6. use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals(); $request->query->...(); // $_GET $request->request->...(); //

    $_POST $request->cookies->...(); // $_COOKIE $request->files->...(); // $_FILES $request->server->...(); // $_SERVER $request->headers->...(); // $_SERVER $request->attributes->...(); // up to you :)
  7. function router(Request $request) { $path = $request->getPathInfo(); $id = preg_replace('/node/(\d+)',

    '$1', $path); $request->attributes->set('id', $id); $request->attributes->set( '_controller', 'myController' ); } function myController (Request $request) { $id = $request->attributes->get('id'); // do your stuff } router($request); $controller = $request->attributes->get('_controller'); $controller($request);
  8. $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!';
  9. // 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
  10. namespace Symfony\Component\HttpKernel; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; interface HttpKernelInterface { const

    MASTER_REQUEST = 1; const SUB_REQUEST = 2; /** * @return Response */ public function handle( Request $request, $type = self::MASTER_REQUEST, $catch = true ); }
  11. Request Response callable   callable   callable   callable  

    / /hello/{name} /contact /events Router /hello/drupalcamp
  12. use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\RequestContext; $context = new RequestContext( '/hello/Leeds', 'GET'

    ); $matcher = new UrlMatcher($routes, $context); $parameters = $matcher->match('/hello/Leeds'); var_dump($parameters); // [ // 'controller' => 'HelloController', // 'name' => 'Leeds', // '_route' => 'hello' // ]
  13. Handle Request Execute  a   controller   Geoip  lookup  

    Mobile   redirect   Authen<cate   ?   ?   Add     debug  to   response   ?   Match  a   request  
  14. Request Response Router Controller Resolver Event Dispatcher GeoipListener SessionListener LocaleListener

    Firewall kernel.request TemplateListener ControllerListener ParamConverter Listener TemplateListener
  15. Request Response Router Controller Resolver Event Dispatcher GeoipListener SessionListener LocaleListener

    Firewall kernel.request TemplateListener ControllerListener ParamConverter Listener TemplateListener
  16. Subjects Listeners Event Dispatcher Mailer Listener Registration Dispatcher add listener

    registration.success notify registration.success call register
  17. Subjects Listeners Event Dispatcher Mailer Listener Registration Dispatcher add listener

    registration.success notify registration.success call SMS Listener add listener registration.success call register
  18. use Symfony\Component\EventDispatcher\GenericEvent; class Registration { // ... public function register($user)

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

    Mailer $mailer, TwigEngine $twig ) { $this->mailer = $mailer; $this->twig = $twig; } // ... }
  20. 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(/* */); } }
  21. 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);
  22. namespace Symfony\Component\Serializer; interface SerializerInterface { /** * @return string */

    public function serialize( $data, $format, array $context = [] ); /** * @return object */ public function deserialize( $data, $type, $format, array $context ); }
  23. class Person { private $age; private $name; public function getName()

    { return $this->name; } public function getAge() { return $this->age; } public function setName($name) { $this->name = $name; } public function setAge($age) { $this->age = $age; } }
  24. use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer \GetSetMethodNormalizer; $normalizers = [

    new GetSetMethodNormalizer() ]; $encoders = [ new JsonEncoder(), new XmlEncoder() ]; $serializer = new Serializer($normalizers, $encoders); $serializer->serialize($person, 'json'); // {"name":"foo", "age": 18}
  25. use Symfony\Component\Validator\Validation; $validator = Validation::createValidatorBuilder() ->enableAnnotationMapping() ->getValidator(); $errors = $validator->validate(new

    User()); if (count($errors) > 0) { var_dump($errors); } Cau<on:  the  annota<on  namespace  needs  to   be  registered  in  the  annota<on  registry.  
  26. var_dump($errors); class Symfony\Component\Validator\ConstraintViolationList#18 (1) { private $violations => array(1) {

    [0] => class Symfony\Component\Validator\ConstraintViolation#31 (8) { private $message => string(31) "This value should not be blank." private $propertyPath => string(5) "email" private $invalidValue => NULL /* ... */ } } }
  27. use Symfony\Component\Yaml\Yaml; $data = Yaml::parse('config.yml'); var_dump($data); // [ // 'parameters'

    => [ // 'username' => 'kuba', // 'password' => '123123', // 'groups' => [ 'admin', 'editor' ] // ] // ]
  28. foo: &foo bar: ~ hello: drupal test: <<: *foo bar:

    true obj: !!php/object:O:8:"stdClass":1:{s:5:"title";s: 14:"Foo in the bar";}
  29. // Retrieve the Composer autoloader $loader = include __DIR__ .

    '/../vendor/autoload.php'; $loader->unregister(); // Wrap the Composer autoloader into cached autoloader require_once __DIR__.'/../vendor/.../ApcClassLoader.php'; $apcLoader = new ApcClassLoader('drupal.', $loader); $apcLoader->register();
  30. <ul> {% for user in users %} <li>{{ user.username |

    title }}</li> {% else %} <li>no user found</li> {% endfor %} </ul> http://twig.sensiolabs.org/
  31. { "require": { "symfony/console": "~2.3" }, "autoload": { "psr-4": {

    "DrupalCamp\\": "core/lib/DrupalCamp" } } } composer.json
  32. <?php # core/lib/DrupalCamp/Command/AddArticleCommand.php namespace DrupalCamp\Command; use GuzzleHttp\Client; use Symfony\Component\Console\Command\Command; use

    Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class AddArticleCommand extends Command { protected function configure() { } protected function execute( InputInterface $input, OutputInterface $output ) { } }
  33. protected function configure() { $this->setName('drupal:add-article'); $this->setDescription('Adds an article to Drupal');

    $this->setHelp('Very helpful and moderately long help message.'); $this->addArgument( 'title', InputArgument::REQUIRED, 'The title' ); }
  34. protected function execute( InputInterface $input, OutputInterface $output ) { $title

    = $input->getArgument('title'); $body = $this->getBody($title); $response = $this->post($body); $message = sprintf( '<info>%d %s</info> %s', $response->getStatusCode(), $response->getReasonPhrase(), $response->getHeader('Location') ); $output->writeln($message); }
  35. private function getBody($title) { return [ 'title' => [['value' =>

    $title]], 'body' => [ [ 'format' => 'restricted_html', 'summary' => '', 'value' => '<p>hello there!</p>' ] ], '_links' => [ 'type' => [ 'href' => 'http://localhost:8000/rest/type/node/article' ] ] ]; }
  36. private function post($body) { $client = new Client(); $client->setDefaultOption( 'headers',

    [ 'Accept' => 'application/hal+json', 'Content-Type' => 'application/hal+json' ] ); return $client->post( 'http://localhost:8000/entity/node', ['body' => json_encode($body)] ); }
  37. Credits •  https://www.flickr.com/photos/bfs_man/4939624151 •  https://www.flickr.com/photos/marine_corps/6771070707/in/photostream/ •  https://www.flickr.com/photos/katerha/5746905652/in/photostream/ •  https://www.flickr.com/photos/40855483@N00/13247934334 • 

    https://www.flickr.com/photos/eschipul/683341572 •  https://www.flickr.com/photos/harusday/2971387785 •  https://www.flickr.com/photos/kaptainkobold/3203311346 •  http://www.sxc.hu/photo/338038 •  http://www.sxc.hu/photo/1223174 •  http://www.freeimages.com/photo/308460 •  http://www.sxc.hu/photo/771223 •  https://www.flickr.com/photos/so_wrong_its_kelly/4380499403/in/ photolist-7F6cRM-9gU8zv-8QNo5g-6FA4u-TMjzu-4FxwKz-7jeqDa- HHoYw-5YAqDi-9m5quF-6AZ7DQ-4oqxmn-9TLn7Y-4ovcMN-4ouUou-9THiy2-4or7dt-4oqqje- 4oraCR-4ouCKo-4ovgv3-4oqYKp-4ouZa9-4ouJF1-4oquKK-4ouwgY-4ouFj5-4ouWB7-4ouQSQ- 4or3Nz-4ouMEd-8vcTct-7B9zWL-HSGyD-dxiE3c-2bfNXS-4jH2nf-7V8NfZ-ab7nsC-8ngLev- ab79CA-zwpJh-7V5twX-9TLm6G-9TLjZd-ebUsRF-9THuXc-9TLnKA-9GF6oi-ab4ogP