Autowire all the things!

6ec359ca87eda89de3251951372a2e8d?s=47 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.

6ec359ca87eda89de3251951372a2e8d?s=128

Samuele

October 19, 2018
Tweet

Transcript

  1. AUTOWIRE ALL THE THINGS! Verona, 19 Ottobre 2018 SymfonyDayIT

  2. Samuele Lilli “Don Callisto”

  3. None
  4. None
  5. None
  6. 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
  7. namespace App\Dashboard; class DashboardBlockBuilder { } class DashboardBuilder { private

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

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

    $blockBuilder; public function __construct(DashboardBlockBuilder $blockBuilder) { $this->blockBuilder = $blockBuilder; } } Autowire
  10. 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
  11. 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
  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 Autowire

  15. services: _defaults: autowire: true autoconfigure: true public: false Autowire

  16. services: _defaults: autowire: true autoconfigure: true public: false Enable depencency

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

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

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

    “auto”injection Enable service “auto”tagging Disable service fetching from container Autowire
  20. 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
  21. 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
  22. services: _defaults: autowire: true autoconfigure: true public: false App\: resource:

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

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

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

    '../src/*' exclude: '../src/{Entity,Migrations,Tests}' Service Discovery Autowire
  26. SERVICE DISCOVERYAIN’T AUTOWIRE

  27. App\Controller\: resource: '../src/Controller' tags: ['controller.service_arguments'] Service Discovery class DashboardController {

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

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

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

    public function __construct(DashboardBuilder $builder) { } public function build(DashboardBuilder $builder) { } }
  31. AUTOWIRE INTERFACES SCALAR ARGS ABSTRACT CLASSES

  32. Interface - Abstract Multiple concrete implementation ? Provide default implementation

    (alias) App\Dashboard\DashboardBlockBuilderInterface: '@App\Dashboard\DashboardBlockBuilder' !
  33. Interface - Abstract Single concrete implementation ? Nothing to do:

    SF provide alias for you !
  34. Different implementation than default ? Declare explicitly service ! Interface

    - Abstract
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. SERVICE MIGRATION

  41. Migration Multiple bundles

  42. Multiple bundles Multiple config. files Migration

  43. STEP 1ENABLE AUTOWIRE

  44. 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
  45. 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
  46. STEP 2 FQCN SERVICES & ALIAS

  47. # 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
  48. # 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
  49. # 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
  50. # 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
  51. services: nuvola.protocollo.mail_processing.mail_reader: class: Nuvola\ProtocolloBundle\MailProcessing\MailReader arguments: ['@nuvola.email_fetcher.nuvola_email_fetcher'] nuvola.email_fetcher.nuvola_email_fetcher: class: Nuvola\EmailBundle\NuvolaEmailFetcher services:

    Nuvola\ProtocolloBundle\MailProcessing\MailReader: arguments: ['@nuvola.email_fetcher.nuvola_email_fetcher'] Nuvola\EmailBundle\NuvolaEmailFetcher: ~ Migration - Step 2
  52. services: nuvola.protocollo.mail_processing.mail_reader: class: Nuvola\ProtocolloBundle\MailProcessing\MailReader arguments: ['@nuvola.email_fetcher.nuvola_email_fetcher'] nuvola.email_fetcher.nuvola_email_fetcher: class: Nuvola\EmailBundle\NuvolaEmailFetcher services:

    Nuvola\ProtocolloBundle\MailProcessing\MailReader: arguments: ['@nuvola.email_fetcher.nuvola_email_fetcher'] Nuvola\EmailBundle\NuvolaEmailFetcher: ~ Migration - Step 2
  53. services: nuvola.protocollo.mail_processing.mail_reader: class: Nuvola\ProtocolloBundle\MailProcessing\MailReader arguments: ['@nuvola.email_fetcher.nuvola_email_fetcher'] nuvola.email_fetcher.nuvola_email_fetcher: class: Nuvola\EmailBundle\NuvolaEmailFetcher services:

    Nuvola\ProtocolloBundle\MailProcessing\MailReader: arguments: ['@nuvola.email_fetcher.nuvola_email_fetcher'] Nuvola\EmailBundle\NuvolaEmailFetcher: ~ Migration - Step 2
  54. services: nuvola.protocollo.mail_processing.mail_reader: class: Nuvola\ProtocolloBundle\MailProcessing\MailReader arguments: ['@nuvola.email_fetcher.nuvola_email_fetcher'] nuvola.email_fetcher.nuvola_email_fetcher: class: Nuvola\EmailBundle\NuvolaEmailFetcher services:

    Nuvola\ProtocolloBundle\MailProcessing\MailReader: arguments: ['@nuvola.email_fetcher.nuvola_email_fetcher'] Nuvola\EmailBundle\NuvolaEmailFetcher: ~ Migration - Step 2
  55. STEP 3PRIVATE SERVICES

  56. Migration - Step 3 For each single configuration file, disable

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

    autowire: true autoconfigure: true public: false Migration - Step 3
  58. STEP 4ENABLE SERVICE DISCOVERY

  59. 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']
  60. GET RID OF EVERY* SERVICE DEFINITION

  61. HOWWITHOUT break LEGACY CODE?

  62. 1) COMMENT EVERY SERVICE IN FILE CONFIGURATION

  63. 1) COMMENT EVERY SERVICE IN FILE CONFIGURATION 2) DEBUG CONTAINER

    FROM COMMAND LINE
  64. bin/console debug:container

  65. 1) COMMENT EVERY SERVICE IN FILE CONFIGURATION 2) DEBUG CONTAINER

    FROM COMMAND LINE 3) IF ERRORS
  66. None
  67. 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)
  68. 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
  69. 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
  70. 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
  71. 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
  72. 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
  73. AVOID PITFALLSAKA WHATCONTAINER WON’T TELL YOU

  74. SAME CLASS SERVICES app.dashboard.dashboard_horizontal_builder: class: App\Dashboard\DashboardBlockBuilder arguments: [...] app.dashboard.dashboard_vertical_builder: class:

    App\Dashboard\DashboardBlockBuilder arguments: [...]
  75. SAME CLASS SERVICES app.dashboard.dashboard_horizontal_builder: class: App\Dashboard\DashboardBlockBuilder arguments: [...] app.dashboard.dashboard_vertical_builder: class:

    App\Dashboard\DashboardBlockBuilder arguments: [...]
  76. 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)
  77. CUSTOM TAG If those tagged service implement a common interface,

    let symfony tag them for you $container ->registerForAutoconfiguration(MyInterface::class) ->addTag(‘aTag’);
  78. CUSTOM TAG Otherwise declare and tag explicitly

  79. 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 ...
  80. SETTER INJECTION /** * @required */ public function setLogger(LoggerInterface $logger)

    { $this->logger = $logger; }
  81. SERVICE PRIORITY Don’t comment out services where priority is declared

  82. DOCTRINE LISTENER # In Compiler Pass $services = $container->findTaggedServiceIds('doctrine.entity_listener'); foreach

    ($services as $service => $attributes) { $container->getDefinition($service)->setPublic(true); }
  83. VALIDATOR ALIAS <service id="Nuvola\ProtocolloBundle\Validator\Constraints\Documento\DocumentoCreabileValidator" autowire="true" autoconfigure="false" public="false" parent="nuvola.protocollo.validator.documento.documento_base_validator"> <tag name="validator.constraint_validator"

    alias="nuvola.protocollo.validator.documento.documento_creabile_validator"/> </service>
  84. VALIDATOR ALIAS <service id="Nuvola\ProtocolloBundle\Validator\Constraints\Documento\DocumentoCreabileValidator" autowire="true" autoconfigure="false" public="false" parent="nuvola.protocollo.validator.documento.documento_base_validator"> <tag name="validator.constraint_validator"

    alias="nuvola.protocollo.validator.documento.documento_creabile_validator"/> </service>
  85. NON SHARED SERVICES Don’t comment out services where priority is

    declared
  86. 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 …)
  87. questions?

  88. https://joind.in/talk/1244f

  89. WE’RE HIRING!