Slide 1

Slide 1 text

Accelerate with Service Autowiring! Bring Symfony’s new Innovation into your Drupal App with your friend @weaverryan

Slide 2

Slide 2 text

> Lead of the Symfony documentation team
 > Writer for SymfonyCasts.com > Symfony evangelist… Fanboy > Husband of the much more talented @leannapelham symfonycasts.com twitter.com/weaverryan Yo! I’m Ryan! > Father to my much more charming son, Beckett

Slide 3

Slide 3 text

(0) So, Service Configuration @weaverryan

Slide 4

Slide 4 text

// modules/custom/coffee_shop/src/Service/Barista.php namespace Drupal\coffee_shop\Service; class Barista { public function prepareDrink($type) { // ... return 'I just made you a '.$type; } } Barista: prepares drinks for us

Slide 5

Slide 5 text

// modules/custom/coffee_shop/src/Service/Barista.php namespace Drupal\coffee_shop\Service; use Drupal\Core\Config\ConfigFactoryInterface; class Barista { private $configFactory; public function __construct(ConfigFactoryInterface $configFactory) { $this->configFactory = $configFactory; } public function prepareDrink($type) { // ... return 'I just made you a '.$type; } } With a config factory dependency

Slide 6

Slide 6 text

Registered as a service # modules/custom/coffee_shop/coffee_shop.services.yml services: coffee_shop.barista: class: Drupal\coffee_shop\Service\Barista arguments: ['@config.factory']

Slide 7

Slide 7 text

And used in our controller… // modules/custom/coffee_shop/src/Controller/CoffeeController.php namespace Drupal\coffee_shop\Controller; class CoffeeController { public function brewCoffee($type) { $barista = \Drupal::getContainer() ->get('coffee_shop.barista'); $text = $barista->prepareDrink($type); return [ '#type' => 'markup', '#markup' => $text, ]; } }

Slide 8

Slide 8 text

Brilliant! @weaverryan

Slide 9

Slide 9 text

Except… @weaverryan

Slide 10

Slide 10 text

We did WAY too much work! @weaverryan

Slide 11

Slide 11 text

(1) Goodbye machine service ids @weaverryan

Slide 12

Slide 12 text

Service id === Class name # modules/custom/coffee_shop/coffee_shop.services.yml services: - coffee_shop.barista: + Drupal\coffee_shop\Service\Barista: class: Drupal\coffee_shop\Service\Barista arguments: ['@config.factory']

Slide 13

Slide 13 text

And used in our controller… // modules/custom/coffee_shop/src/Controller/CoffeeController.php namespace Drupal\coffee_shop\Controller; class CoffeeController { public function brewCoffee($type) { $barista = \Drupal::getContainer() ->get('coffee_shop.barista'); ->get('Drupal\coffee_shop\Service\Barista'); // ... } }

Slide 14

Slide 14 text

Use the simple ::class Syntax // modules/custom/coffee_shop/src/Controller/CoffeeController.php namespace Drupal\coffee_shop\Controller; use Drupal\coffee_shop\Service\Barista; class CoffeeController { public function brewCoffee($type) { $barista = \Drupal::getContainer() ->get(Barista::class); // ... } }

Slide 15

Slide 15 text

Because… if a class will only be registered once as a service… …why add the extra layer of abstraction? @weaverryan

Slide 16

Slide 16 text

The "class" key is optional # modules/custom/coffee_shop/coffee_shop.services.yml services: Drupal\coffee_shop\Service\Barista: arguments: ['@config.factory']

Slide 17

Slide 17 text

Let's add a second service @weaverryan

Slide 18

Slide 18 text

New CoffeeMachine Service // modules/custom/coffee_shop/src/Service/CoffeeMachine.php namespace Drupal\coffee_shop\Service; class CoffeeMachine { public function brew() { return 'coffee brewed!'; } }

Slide 19

Slide 19 text

Use it in Barista // modules/custom/coffee_shop/src/Service/Barista.php class Barista { private $configFactory; private $coffeeMachine; public function __construct($configFactory, CoffeeMachine $coffeeMachine) { $this->configFactory = $configFactory; $this->coffeeMachine = $coffeeMachine; } // ... }

Slide 20

Slide 20 text

Add service & update arguments # modules/custom/coffee_shop/coffee_shop.services.yml services: Drupal\coffee_shop\Service\Barista: arguments: - '@config.factory', + - '@Drupal\coffee_shop\Service\CoffeeMachine' + Drupal\coffee_shop\Service\CoffeeMachine: + arguments: []

Slide 21

Slide 21 text

(2) Autowiring @weaverryan

Slide 22

Slide 22 text

Autowire: automatically add my arguments # modules/custom/coffee_shop/coffee_shop.services.yml services: Drupal\coffee_shop\Service\Barista: + autowire: true arguments: - - '@config.factory', - - '@Drupal\coffee_shop\Service\CoffeeMachine' Drupal\coffee_shop\Service\CoffeeMachine: arguments: []

Slide 23

Slide 23 text

It works by reading the type-hint // modules/custom/coffee_shop/src/Service/Barista.php class Barista { private $configFactory; private $coffeeMachine; public function __construct( ConfigFactoryInterface $configFactory, CoffeeMachine $coffeeMachine) { $this->configFactory = $configFactory; $this->coffeeMachine = $coffeeMachine; } // ... }

Slide 24

Slide 24 text

Autowire: automatically add my arguments # modules/custom/coffee_shop/coffee_shop.services.yml services: Drupal\coffee_shop\Service\Barista: autowire: true arguments: [] Drupal\coffee_shop\Service\CoffeeMachine: arguments: []

Slide 25

Slide 25 text

(3) Default Service Config @weaverryan

Slide 26

Slide 26 text

Default options for all services? # modules/custom/coffee_shop/coffee_shop.services.yml services: + # defaults for services in this file + _defaults: + autowire: true Drupal\coffee_shop\Service\Barista: arguments: [] Drupal\coffee_shop\Service\CoffeeMachine: arguments: []

Slide 27

Slide 27 text

(4) Service Auto-Registration @weaverryan

Slide 28

Slide 28 text

Auto-register your services # modules/custom/coffee_shop/coffee_shop.services.yml services: _defaults: autowire: true # auto-registers all services in this directory # using the class name as the service id Drupal\coffee_shop\Service\: resource: '../src/Service'

Slide 29

Slide 29 text

Amazing! @weaverryan

Slide 30

Slide 30 text

1) Create & use services and touch zero config 2) Add new constructor args to a service and touch zero config @weaverryan

Slide 31

Slide 31 text

It doesn't work :( @weaverryan

Slide 32

Slide 32 text

_defaults & service auto- registration are not (yet) supported by Drupal :( @weaverryan

Slide 33

Slide 33 text

(5) & (6) Let's build it ourselves! @weaverryan

Slide 34

Slide 34 text

A Service Provider // modules/custom/coffee_shop/src/CoffeeShopServiceProvider.php namespace Drupal\coffee_shop; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ServiceProviderInterface; class CoffeeShopServiceProvider implements ServiceProviderInterface { public function register(ContainerBuilder $container) { // called when the services are being built } }

Slide 35

Slide 35 text

Find all files in the Service directory // modules/custom/custom/coffee_shop/src/CoffeeShopServiceProvider.php public function register(ContainerBuilder $container) { $finder = new Finder(); $finder->in(__DIR__.'/Service') ->files() ->name('*.php'); foreach ($finder as $fileInfo) { $class = 'Drupal\coffee_shop\Service\\' .substr($fileInfo->getFilename(), 0, -4); // ... } }

Slide 36

Slide 36 text

Register them as autowired services! // modules/custom/custom/coffee_shop/src/CoffeeShopServiceProvider.php public function register(ContainerBuilder $container) { // ... foreach ($finder as $fileInfo) { $class = 'Drupal\coffee_shop\Service\\' .substr($fileInfo->getFilename(), 0, -4); // don't override any existing service if ($container->hasDefinition($class)) { continue; } $definition = new Definition($class); $definition->setAutowired(true); $container->setDefinition($class, $definition); } }

Slide 37

Slide 37 text

Smallest File in your App # modules/custom/coffee_shop/coffee_shop.services.yml # just crickets here!

Slide 38

Slide 38 text

(7) Named Arguments @weaverryan

Slide 39

Slide 39 text

Add a scalar argument // modules/custom/custom/coffee_shop/src/Service/CoffeeMachine.php namespace Drupal\coffee_shop\Service; class CoffeeMachine { private $numberOfScoops; public function __construct(int $numberOfScoops) { $this->numberOfScoops = $numberOfScoops; } public function brew() { return sprintf('using %d scoops!', $this->numberOfScoops); } }

Slide 40

Slide 40 text

Autowiring Fails!!

Slide 41

Slide 41 text

Smallest File in your App # modules/custom/custom/coffee_shop/coffee_shop.services.yml services: Drupal\coffee_shop\Service\CoffeeMachine: autowire: true arguments: $numberOfScoops: 3 public function __construct(int $numberOfScoops) { $this->numberOfScoops = $numberOfScoops; } Arguments are bound by name!

Slide 42

Slide 42 text

(8) Only use the "New" Autowiring @weaverryan

Slide 43

Slide 43 text

Smallest File in your App # modules/custom/custom/coffee_shop/coffee_shop.services.yml parameters: container.autowiring.strict_mode: true services: # ...

Slide 44

Slide 44 text

Autowiring Fails!!

Slide 45

Slide 45 text

Create "aliases" for your type-hints # modules/custom/custom/coffee_shop/coffee_shop.services.yml services: # ... Drupal\Core\Config\ConfigFactoryInterface: alias: 'config.factory'

Slide 46

Slide 46 text

Ideally, core would add these aliases and empower autowiring @weaverryan

Slide 47

Slide 47 text

Wait, so, do what? @weaverryan

Slide 48

Slide 48 text

(1) Use class names as your service ids and remove the class key. @weaverryan

Slide 49

Slide 49 text

(2) Use autowire: true and omit arguments that Drupal can guess. @weaverryan

Slide 50

Slide 50 text

(3) & (4) Leverage a service provider to auto-register services & default them to autowire. @weaverryan

Slide 51

Slide 51 text

(5) Specify any non- autowireable arguments with $named arguments @weaverryan

Slide 52

Slide 52 text

(6) Turn off "magic" autowiring and add service aliases for the type- hints you want to autowire @weaverryan

Slide 53

Slide 53 text

Ryan Weaver @weaverryan THANK YOU! Free Symfony Tutorial https://symfonycasts.com/symfony http://bit.ly/drupal-autowiring-code