Slide 1

Slide 1 text

Drupal 8 - The Symfony and Drupal partnership Jakub Zalas DrupalCamp Yorkshire 31 May 2014

Slide 2

Slide 2 text

@jakzal @jakub_zalas @SensioLabsUK £  

Slide 3

Slide 3 text

Drupal in the eyes of a Symfony developer

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

DRUPAL IS AWESOME NO, REALLY!

Slide 7

Slide 7 text

Drupal community

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

Drupal is a perfect tool for building websites

Slide 17

Slide 17 text

BUT…

Slide 18

Slide 18 text

IS IT PERFECT FOR EVERYONE?

Slide 19

Slide 19 text

THE DRUPAL WAY PHP developer vs Drupal developer

Slide 20

Slide 20 text

BARRIER TO ENTRY

Slide 21

Slide 21 text

SO…

Slide 22

Slide 22 text

FOCUS ON THE CORE OF YOUR BUSINESS AND LET OTHERS DO THE REST

Slide 23

Slide 23 text

FOLLOW THE WIDELY ADOPTED INDUSTRY STANDARDS AND YOUR COMMUNITY WILL GROW

Slide 24

Slide 24 text

Storm is coming in the Drupal world

Slide 25

Slide 25 text

After every storm the sun will smile

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

Projects on packagist.org 4373 21% 1206 6% 14838 73% Depends on Symfony Depends on Zend Other December  2013  

Slide 32

Slide 32 text

0 100 200 300 400 500 600 700 Bundles Other Symfony Component based projects December  2013  

Slide 33

Slide 33 text

Symfony community

Slide 34

Slide 34 text

GROWING BETTER DEVELOPERS

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

3178 forks 1025 code contributors 739 doc contributors

Slide 37

Slide 37 text

8421 stars

Slide 38

Slide 38 text

HttpFoundation

Slide 39

Slide 39 text

$_GET $_POST $_SERVER $_COOKIE $_FILES

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

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 :)

Slide 42

Slide 42 text

$id = isset($_GET['id']) ? $_GET['id'] : -1; becomes $id = $request->query->get('id', -1);

Slide 43

Slide 43 text

$request->query->get('name', 'default'); $request->query->set('name', 'default'); $request->query->has('name'); $request->query->all(); $request->query->keys(); $request->query->replace('name', 'other'); $request->query->remove('name'); $request->query->getAlpha('name'); $request->query->getAlnum('name'); $request->query->getInt('name'); $request->query->getDigits('name');

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

$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!';

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

HttpKernel

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

Request Response /hello/drupalcamp

Slide 51

Slide 51 text

use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; class SimpleKernel implements HttpKernelInterface { public function handle(Request $request /*,...*/) { return new Response('Hello Leeds!'); } }

Slide 52

Slide 52 text

use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals(); $kernel = new SimpleKernel(); $response = $kernel->handle($request); $response->send();

Slide 53

Slide 53 text

Request Response callable   callable   callable   callable   / /hello/{name} /contact /events Router /hello/drupalcamp

Slide 54

Slide 54 text

Routing

Slide 55

Slide 55 text

use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $routes = new RouteCollection(); $routes->add( 'hello', new Route( '/hello/{name}', ['controller' => 'HelloController'] ) );

Slide 56

Slide 56 text

hello: path: '/hello/{name}' defaults: _controller: 'HelloController' requirements: name: 'a-zA-Z' methods: ['GET'] OR…

Slide 57

Slide 57 text

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' // ]

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

EventDispatcher

Slide 60

Slide 60 text

Handle Request Execute  a   controller   Geoip  lookup   Mobile   redirect   Authen

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

use Symfony\Component\EventDispatcher\EventDispatcherInterface; class Registration { private $dispatcher; public function __construct( EventDispatcherInterface $dispatcher ) { $this->dispatcher = $dispatcher; } // ... }

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

class MailerListener { private $mailer; private $twig; public function __construct( Mailer $mailer, TwigEngine $twig ) { $this->mailer = $mailer; $this->twig = $twig; } // ... }

Slide 68

Slide 68 text

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(/* */); } }

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

Serializer

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

No content

Slide 73

Slide 73 text

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; } }

Slide 74

Slide 74 text

$person = new Person(); $person->setAge(18); $person->setName('Kuba');

Slide 75

Slide 75 text

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}

Slide 76

Slide 76 text

Validator

Slide 77

Slide 77 text

use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\Email; class User { /** * @Email * @NotBlank */ public $email; }

Slide 78

Slide 78 text

User: properties: name: - Email: ~ - NotBlank: ~ OR…

Slide 79

Slide 79 text

use Symfony\Component\Validator\Validation; $validator = Validation::createValidatorBuilder() ->enableAnnotationMapping() ->getValidator(); $errors = $validator->validate(new User()); if (count($errors) > 0) { var_dump($errors); } Cau

Slide 80

Slide 80 text

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 /* ... */ } } }

Slide 81

Slide 81 text

Yaml

Slide 82

Slide 82 text

parameters: username: kuba password: 123123 groups: [ 'admin', 'editor' ]

Slide 83

Slide 83 text

use Symfony\Component\Yaml\Yaml; $data = Yaml::parse('config.yml'); var_dump($data); // [ // 'parameters' => [ // 'username' => 'kuba', // 'password' => '123123', // 'groups' => [ 'admin', 'editor' ] // ] // ]

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

ClassLoader

Slide 86

Slide 86 text

use Symfony\Component\ClassLoader\ClassLoader; $loader = new ClassLoader(); $loader->addPrefix('Acme\\', __DIR__.'/src'); $loader->register(); // Acme\Search\Query => src/Acme/Search/Query.php

Slide 87

Slide 87 text

// 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();

Slide 88

Slide 88 text

DependencyInjection

Slide 89

Slide 89 text

class appDevDebugProjectContainer extends Container { protected function getSymfonyConApiService() { $this->services['drupal.api'] = $instance = new SymfonyConApi(); return $instance; } }

Slide 90

Slide 90 text

Slide 91

Slide 91 text

Few ideas for the next steps

Slide 92

Slide 92 text

{ "require": { "monolog/monolog": "1.2.*" } } https://getcomposer.org/

Slide 93

Slide 93 text

    {% for user in users %}
  • {{ user.username | title }}
  • {% else %}
  • no user found
  • {% endfor %}
http://twig.sensiolabs.org/

Slide 94

Slide 94 text

more ?

Slide 95

Slide 95 text

{ "require": { "symfony/console": "~2.3" }, "autoload": { "psr-4": { "DrupalCamp\\": "core/lib/DrupalCamp" } } } composer.json

Slide 96

Slide 96 text

curl -sS https://getcomposer.org/installer | php composer update symfony/console

Slide 97

Slide 97 text

Slide 98

Slide 98 text

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

add(new \DrupalCamp\Command\AddArticleCommand()); $app->run();

Slide 101

Slide 101 text

No content

Slide 102

Slide 102 text

No content

Slide 103

Slide 103 text

protected function execute( InputInterface $input, OutputInterface $output ) { $title = $input->getArgument('title'); $body = $this->getBody($title); $response = $this->post($body); $message = sprintf( '%d %s %s', $response->getStatusCode(), $response->getReasonPhrase(), $response->getHeader('Location') ); $output->writeln($message); }

Slide 104

Slide 104 text

private function getBody($title) { return [ 'title' => [['value' => $title]], 'body' => [ [ 'format' => 'restricted_html', 'summary' => '', 'value' => '

hello there!

' ] ], '_links' => [ 'type' => [ 'href' => 'http://localhost:8000/rest/type/node/article' ] ] ]; }

Slide 105

Slide 105 text

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)] ); }

Slide 106

Slide 106 text

No content

Slide 107

Slide 107 text

No content

Slide 108

Slide 108 text

No content

Slide 109

Slide 109 text

Thank you http://bit.ly/sflive2014

Slide 110

Slide 110 text

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