$30 off During Our Annual Pro Sale. View Details »

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. Accelerate with Service Autowiring!
    Bring Symfony’s new Innovation
    into your Drupal App
    with your friend @weaverryan

    View Slide

  2. > 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

    View Slide

  3. (0)
    So, Service
    Configuration
    @weaverryan

    View Slide

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

    View Slide

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

    View Slide

  6. 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']

    View Slide

  7. 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,
    ];
    }
    }

    View Slide

  8. Brilliant!
    @weaverryan

    View Slide

  9. Except…
    @weaverryan

    View Slide

  10. We did WAY too
    much work!
    @weaverryan

    View Slide

  11. (1)
    Goodbye machine
    service ids
    @weaverryan

    View Slide

  12. 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']

    View Slide

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

    View Slide

  14. 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);
    // ...
    }
    }

    View Slide

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

    View Slide

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

    View Slide

  17. Let's add a second
    service
    @weaverryan

    View Slide

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

    View Slide

  19. 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;
    }
    // ...
    }

    View Slide

  20. 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: []

    View Slide

  21. (2)
    Autowiring
    @weaverryan

    View Slide

  22. 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: []

    View Slide

  23. 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;
    }
    // ...
    }

    View Slide

  24. 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: []

    View Slide

  25. (3)
    Default Service Config
    @weaverryan

    View Slide

  26. 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: []

    View Slide

  27. (4)
    Service Auto-Registration
    @weaverryan

    View Slide

  28. 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'

    View Slide

  29. Amazing!
    @weaverryan

    View Slide

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

    View Slide

  31. It doesn't work :(
    @weaverryan

    View Slide

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

    View Slide

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

    View Slide

  34. 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
    }
    }

    View Slide

  35. 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);
    // ...
    }
    }

    View Slide

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

    View Slide

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

    View Slide

  38. (7)
    Named Arguments
    @weaverryan

    View Slide

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

    View Slide

  40. Autowiring Fails!!

    View Slide

  41. 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!

    View Slide

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

    View Slide

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

    View Slide

  44. Autowiring Fails!!

    View Slide

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

    View Slide

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

    View Slide

  47. Wait, so, do what?
    @weaverryan

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide