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

Autowire all the things!

Samuele
October 19, 2018

Autowire all the things!

From Symfony 3.3 autowire was introducted to help handle services with ease. If we have a green field project is easy to use this feature but if we have an old application, migration process can be scary. In this talk I'll introduce autowire concepts and show how we have migrated and how to avoid common pitfall in this process.

Samuele

October 19, 2018
Tweet

More Decks by Samuele

Other Decks in Programming

Transcript

  1. AUTOWIRE AUTOWIRE AUTOWIRE AUTOWIRE AUTOWIRE AUTOWIRE RE AUTOWIRE AUTOWIRE WIRE

    AUTOWIRE AUTOWIRE WIRE E AUTOWIRE AUTOWIRE TOWIRE AUTOWIRE AUTOWIRE UTOWIRE AUTOWIRE AUTOWIRE AUTOWIRE AUTOWIRE AUTOWIRE AUTOWIRE AUTOWIRE AUTOWIRE E AUTOWIRE AUTOWIRE IRE “Autowiring allows you to manage services in the container with minimal configuration. It reads the type-hints on your constructor (or other methods) and automatically passes the correct services to each method. Symfony's autowiring is designed to be predictable: if it is not absolutely clear which dependency should be passed, you'll see an actionable exception.” https://symfony.com/doc/current/service_container/autowiring.html
  2. namespace App\Dashboard; class DashboardBlockBuilder { } class DashboardBuilder { private

    $blockBuilder; public function __construct(DashboardBlockBuilder $blockBuilder) { $this->blockBuilder = $blockBuilder; } } Autowire
  3. namespace App\Dashboard; class DashboardBlockBuilder { } class DashboardBuilder { private

    $blockBuilder; public function __construct(DashboardBlockBuilder $blockBuilder) { $this->blockBuilder = $blockBuilder; } } Autowire
  4. namespace App\Dashboard; class DashboardBlockBuilder { } class DashboardBuilder { private

    $blockBuilder; public function __construct(DashboardBlockBuilder $blockBuilder) { $this->blockBuilder = $blockBuilder; } } Autowire
  5. services: app.dashboard.block_builder: class: App\Dashboard\DashboardBlockBuilder app.dashboard.builder: class: App\Dashboard\DashboardBuilder arguments: ['@app.dashboard.block_builder'] services:

    _defaults: autowire: true autoconfigure: true public: false App\Dashboard\DashboardBlockBuilder: ~ App\Dashboard\DashboardBuilder: ~ WITHOUT AUTOWIRE WITH AUTOWIRE Autowire
  6. services: app.dashboard.block_builder: class: App\Dashboard\DashboardBlockBuilder app.dashboard.builder: class: App\Dashboard\DashboardBuilder arguments: ['@app.dashboard.block_builder'] services:

    _defaults: autowire: true autoconfigure: true public: false App\Dashboard\DashboardBlockBuilder: ~ App\Dashboard\DashboardBuilder: ~ WITHOUT AUTOWIRE WITH AUTOWIRE Autowire
  7. services: app.dashboard.block_builder: class: App\Dashboard\DashboardBlockBuilder app.dashboard.builder: class: App\Dashboard\DashboardBuilder arguments: ['@app.dashboard.block_builder'] services:

    _defaults: autowire: true autoconfigure: true public: false App\Dashboard\DashboardBlockBuilder: ~ App\Dashboard\DashboardBuilder: ~ WITHOUT AUTOWIRE WITH AUTOWIRE Autowire
  8. services: app.dashboard.block_builder: class: App\Dashboard\DashboardBlockBuilder app.dashboard.builder: class: App\Dashboard\DashboardBuilder arguments: ['@app.dashboard.block_builder'] services:

    _defaults: autowire: true autoconfigure: true public: false App\Dashboard\DashboardBlockBuilder: ~ App\Dashboard\DashboardBuilder: ~ WITHOUT AUTOWIRE WITH AUTOWIRE Autowire
  9. services: _defaults: autowire: true autoconfigure: true public: false Enable depencency

    “auto”injection Enable service “auto”tagging Autowire
  10. services: _defaults: autowire: true autoconfigure: true public: false Enable depencency

    “auto”injection Enable service “auto”tagging Disable service fetching from container Autowire
  11. services: _defaults: autowire: true autoconfigure: true public: false Enable depencency

    “auto”injection Enable service “auto”tagging Disable service fetching from container Autowire
  12. services: app.dashboard.block_builder: class: App\Dashboard\DashboardBlockBuilder app.dashboard.builder: class: App\Dashboard\DashboardBuilder arguments: ['@app.dashboard.block_builder'] services:

    _defaults: autowire: true autoconfigure: true public: false App\Dashboard\DashboardBlockBuilder: ~ App\Dashboard\DashboardBuilder: ~ WITHOUT AUTOWIRE WITH AUTOWIRE Autowire
  13. services: app.dashboard.block_builder: class: App\Dashboard\DashboardBlockBuilder app.dashboard.builder: class: App\Dashboard\DashboardBuilder arguments: ['@app.dashboard.block_builder'] services:

    _defaults: autowire: true autoconfigure: true public: false App\Dashboard\DashboardBlockBuilder: ~ App\Dashboard\DashboardBuilder: ~ WITHOUT AUTOWIRE WITH AUTOWIRE Autowire
  14. services: _defaults: autowire: true autoconfigure: true public: false App\: resource:

    '../src/*' exclude: '../src/{Entity,Migrations,Tests}' Autowire
  15. services: _defaults: autowire: true autoconfigure: true public: false App\: resource:

    '../src/*' exclude: '../src/{Entity,Migrations,Tests}' Service Discovery Autowire
  16. services: _defaults: autowire: true autoconfigure: true public: false App\: resource:

    '../src/*' exclude: '../src/{Entity,Migrations,Tests}' Service Discovery Autowire
  17. services: _defaults: autowire: true autoconfigure: true public: false App\: resource:

    '../src/*' exclude: '../src/{Entity,Migrations,Tests}' Service Discovery Autowire
  18. App\Controller\: resource: '../src/Controller' tags: ['controller.service_arguments'] Service Discovery class DashboardController {

    public function __construct(DashboardBuilder $builder) { } public function build(DashboardBuilder $builder) { } }
  19. App\Controller\: resource: '../src/Controller' tags: ['controller.service_arguments'] Service Discovery class DashboardController {

    public function __construct(DashboardBuilder $builder) { } public function build(DashboardBuilder $builder) { } }
  20. App\Controller\: resource: '../src/Controller' tags: ['controller.service_arguments'] Service Discovery class DashboardController {

    public function __construct(DashboardBuilder $builder) { } public function build(DashboardBuilder $builder) { } }
  21. App\Controller\: resource: '../src/Controller' tags: ['controller.service_arguments'] Service Discovery class DashboardController {

    public function __construct(DashboardBuilder $builder) { } public function build(DashboardBuilder $builder) { } }
  22. Interface - Abstract Multiple concrete implementation ? Provide default implementation

    (alias) App\Dashboard\DashboardBlockBuilderInterface: '@App\Dashboard\DashboardBlockBuilder' !
  23. Scalar Args services: _defaults: bind: $dashboardName: 'aName' class DashboardBuilder {

    public function __construct(DashboardBlockBuilderInterface $blockBuilder, string $dashboardName) { } } App\Dashboard\DashboardBuilder: $dashboardName: 'aName' App\Dashboard\DashboardBuilder: arguments: ['@App\Dashboard\DashboardBlockBuilder', 'aName'] 1 2 3
  24. Scalar Args services: _defaults: bind: $dashboardName: 'aName' class DashboardBuilder {

    public function __construct(DashboardBlockBuilderInterface $blockBuilder, string $dashboardName) { } } App\Dashboard\DashboardBuilder: $dashboardName: 'aName' App\Dashboard\DashboardBuilder: arguments: ['@App\Dashboard\DashboardBlockBuilder', 'aName'] 1 2 3
  25. Scalar Args services: _defaults: bind: $dashboardName: 'aName' class DashboardBuilder {

    public function __construct(DashboardBlockBuilderInterface $blockBuilder, string $dashboardName) { } } App\Dashboard\DashboardBuilder: $dashboardName: 'aName' App\Dashboard\DashboardBuilder: arguments: ['@App\Dashboard\DashboardBlockBuilder', 'aName'] 1 2 3
  26. Scalar Args services: _defaults: bind: $dashboardName: 'aName' class DashboardBuilder {

    public function __construct(DashboardBlockBuilderInterface $blockBuilder, string $dashboardName) { } } App\Dashboard\DashboardBuilder: $dashboardName: 'aName' App\Dashboard\DashboardBuilder: arguments: ['@App\Dashboard\DashboardBlockBuilder', 'aName'] 1 2 3
  27. Scalar Args services: _defaults: bind: $dashboardName: 'aName' class DashboardBuilder {

    public function __construct(DashboardBlockBuilderInterface $blockBuilder, string $dashboardName) { } } App\Dashboard\DashboardBuilder: $dashboardName: 'aName' App\Dashboard\DashboardBuilder: arguments: ['@App\Dashboard\DashboardBlockBuilder', 'aName'] 1 2 3
  28. Migration - Step 1 For each single configuration file, enable

    autowire and autoconfigure but don’t disable service fetching (public: true) services: _defaults: autowire: true autoconfigure: true #public: false
  29. Migration - Step 1 For each single configuration file, enable

    autowire and autoconfigure but don’t disable service fetching (public: true) services: _defaults: autowire: true autoconfigure: true #public: false
  30. # legacy_aliases.yaml services: _defaults: public: true # Since SF 3.4

    even alias have “private” visibility by default nuvola.protocollo.mail_processing.mail_reader: '@Nuvola\ProtocolloBundle\MailProcessing\MailReader' nuvola.email_fetcher.nuvola_email_fetcher: '@Nuvola\EmailBundle\NuvolaEmailFetcher' # service.yaml imports: - { resource: legacy_aliases.yml } [...] Migration - Step 2
  31. # legacy_aliases.yaml services: _defaults: public: true # Since SF 3.4

    even alias have “private” visibility by default nuvola.protocollo.mail_processing.mail_reader: '@Nuvola\ProtocolloBundle\MailProcessing\MailReader' nuvola.email_fetcher.nuvola_email_fetcher: '@Nuvola\EmailBundle\NuvolaEmailFetcher' # service.yaml imports: - { resource: legacy_aliases.yml } [...] Migration - Step 2
  32. # legacy_aliases.yaml services: _defaults: public: true # Since SF 3.4

    even alias have “private” visibility by default nuvola.protocollo.mail_processing.mail_reader: '@Nuvola\ProtocolloBundle\MailProcessing\MailReader' nuvola.email_fetcher.nuvola_email_fetcher: '@Nuvola\EmailBundle\NuvolaEmailFetcher' # service.yaml imports: - { resource: legacy_aliases.yml } [...] Migration - Step 2
  33. # legacy_aliases.yaml services: _defaults: public: true # Since SF 3.4

    even alias have “private” visibility by default nuvola.protocollo.mail_processing.mail_reader: '@Nuvola\ProtocolloBundle\MailProcessing\MailReader' nuvola.email_fetcher.nuvola_email_fetcher: '@Nuvola\EmailBundle\NuvolaEmailFetcher' # service.yaml imports: - { resource: legacy_aliases.yml } [...] Migration - Step 2
  34. Migration - Step 3 For each single configuration file, disable

    service fetching services: _defaults: autowire: true autoconfigure: true public: false
  35. For each single configuration file, disable service fetching services: _defaults:

    autowire: true autoconfigure: true public: false Migration - Step 3
  36. Migration - Step 4 For each bundle create a file

    for service discovery # discovery.yaml services: _defaults: autowire: true autoconfigure: true public: false Nuvola\ProtocolloBundle\: resource: '../../*' exclude: '../../{Entity/*.php}' Nuvola\ProtocolloBundle\Controller\: resource: '../../Controller' tags: ['controller.service_arguments']
  37. 1) COMMENT EVERY SERVICE IN FILE CONFIGURATION 2) DEBUG CONTAINER

    FROM COMMAND LINE 3) IF ERRORS a) FIX ONE SERVICE AT A TIME AS CONTAINER MESSAGE ARE SUPER COMMUNICATIVE (interface/abstract injection, scalar args, misconfiguration)
  38. 1) COMMENT EVERY SERVICE IN FILE CONFIGURATION 2) DEBUG CONTAINER

    FROM COMMAND LINE 3) IF ERRORS a) FIX ONE SERVICE AT A TIME AS CONTAINER MESSAGE ARE SUPER COMMUNICATIVE (interface/abstract injection, scalar args, misconfiguration) b) RESTART FROM 2
  39. 1) COMMENT EVERY SERVICE IN FILE CONFIGURATION 2) DEBUG CONTAINER

    FROM COMMAND LINE 3) IF ERRORS a) FIX ONE SERVICE AT A TIME AS CONTAINER MESSAGE ARE SUPER COMMUNICATIVE (interface/abstract injection, scalar args, misconfiguration) b) RESTART FROM 2 4) IF NO ERRORS
  40. 1) COMMENT EVERY SERVICE IN FILE CONFIGURATION 2) DEBUG CONTAINER

    FROM COMMAND LINE 3) IF ERRORS a) FIX ONE SERVICE AT A TIME AS CONTAINER MESSAGE ARE SUPER COMMUNICATIVE (interface/abstract injection, scalar args, misconfiguration) b) RESTART FROM 2 4) IF NO ERRORS
  41. 1) COMMENT EVERY SERVICE IN FILE CONFIGURATION 2) DEBUG CONTAINER

    FROM COMMAND LINE 3) IF ERRORS a) FIX ONE SERVICE AT A TIME AS CONTAINER MESSAGE ARE SUPER COMMUNICATIVE (interface/abstract injection, scalar args, misconfiguration) b) RESTART FROM 2 4) IF NO ERRORS
  42. 1) COMMENT EVERY SERVICE IN FILE CONFIGURATION 2) DEBUG CONTAINER

    FROM COMMAND LINE 3) IF ERRORS a) FIX ONE SERVICE AT A TIME AS CONTAINER MESSAGE ARE SUPER COMMUNICATIVE (interface/abstract injection, scalar args, misconfiguration) b) RESTART FROM 2 4) IF NO ERRORS
  43. ENTITY MANAGER As EntityManager isn’t registered as a service but

    his interface has an alias to the default one, when injection EntityManager, verify that’s the right one (multiple connections)
  44. CUSTOM TAG If those tagged service implement a common interface,

    let symfony tag them for you $container ->registerForAutoconfiguration(MyInterface::class) ->addTag(‘aTag’);
  45. SETTER INJECTION Setter injection can’t be done when compiling the

    container so you need to be explicit about it either declaring the service with the “old” fashion or ...
  46. DOCTRINE LISTENER # In Compiler Pass $services = $container->findTaggedServiceIds('doctrine.entity_listener'); foreach

    ($services as $service => $attributes) { $container->getDefinition($service)->setPublic(true); }
  47. RECAP • Autowire let you configure service with almost zero

    configuration • It is predictable so no way to mess up the things (theoricatelly :D ) • Autowire ain’t service discovery (even if them are used in tandem) • You can migrate your application a step at a time (if multi “isolated” dependency bundle even a bundle at a time) • You can let autowire work with all previous configuration (so you’ll be able to skip “old service definition” cleanup • Let the container help you in migration process • Don’t trust the container and check the pointer in the previous slides (and tell me if you find more …)