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

Accelerate Drupal with Service Autowiring!

Accelerate Drupal with Service Autowiring!

Services? Yea, you've basically got those down: registering them in {modulename}.services.yml, adding arguments, using them. Great! While you've been crushing it, Symfony has been busy too - with new features to accelerate how you work!

In this talk, we'll get an overview of service autowiring, auto-registration and autoconfiguration: a set of features that will make you move faster and *love* the process! Just created a new service class and ready to use it? No need to touch *any* configuration files. Need to add another constructor argument? Type-hint it and keep coding: no YAML involved.

This talk assumes an understanding of services in Drupal. By the end, you'll be ready to try out these new features and work faster. Zoom!

weaverryan

April 10, 2019
Tweet

More Decks by weaverryan

Other Decks in Technology

Transcript

  1. > 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
  2. // 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
  3. 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, ]; } }
  4. 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']
  5. 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'); // ... } }
  6. 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); // ... } }
  7. Because… if a class will only be registered once as

    a service… …why add the extra layer of abstraction? @weaverryan
  8. 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; } // ... }
  9. 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: []
  10. 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: []
  11. 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; } // ... }
  12. 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: []
  13. 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'
  14. 1) Create & use services and touch zero config 2)

    Add new constructor args to a service and touch zero config @weaverryan
  15. 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 } }
  16. 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); // ... } }
  17. 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); } }
  18. 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); } }
  19. 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!
  20. (3) & (4) Leverage a service provider to auto-register services

    & default them to autowire. @weaverryan
  21. (6) Turn off "magic" autowiring and add service aliases for

    the type- hints you want to autowire @weaverryan