Share
Embed
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
Transcript
89 slides
-
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 -
namespace App\Dashboard; class DashboardBlockBuilder { } class DashboardBuilder { private
$blockBuilder; public function __construct(DashboardBlockBuilder $blockBuilder) { $this->blockBuilder = $blockBuilder; } } Autowire -
namespace App\Dashboard; class DashboardBlockBuilder { } class DashboardBuilder { private
$blockBuilder; public function __construct(DashboardBlockBuilder $blockBuilder) { $this->blockBuilder = $blockBuilder; } } Autowire -
namespace App\Dashboard; class DashboardBlockBuilder { } class DashboardBuilder { private
$blockBuilder; public function __construct(DashboardBlockBuilder $blockBuilder) { $this->blockBuilder = $blockBuilder; } } Autowire -
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 -
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 -
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 -
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 -
services: _defaults: autowire: true autoconfigure: true public: false Enable depencency
“auto”injection Autowire -
services: _defaults: autowire: true autoconfigure: true public: false Enable depencency
“auto”injection Enable service “auto”tagging Autowire -
services: _defaults: autowire: true autoconfigure: true public: false Enable depencency
“auto”injection Enable service “auto”tagging Disable service fetching from container Autowire -
services: _defaults: autowire: true autoconfigure: true public: false Enable depencency
“auto”injection Enable service “auto”tagging Disable service fetching from container Autowire -
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 -
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 -
services: _defaults: autowire: true autoconfigure: true public: false App\: resource:
'../src/*' exclude: '../src/{Entity,Migrations,Tests}' Autowire -
services: _defaults: autowire: true autoconfigure: true public: false App\: resource:
'../src/*' exclude: '../src/{Entity,Migrations,Tests}' Service Discovery Autowire -
services: _defaults: autowire: true autoconfigure: true public: false App\: resource:
'../src/*' exclude: '../src/{Entity,Migrations,Tests}' Service Discovery Autowire -
services: _defaults: autowire: true autoconfigure: true public: false App\: resource:
'../src/*' exclude: '../src/{Entity,Migrations,Tests}' Service Discovery Autowire -
App\Controller\: resource: '../src/Controller' tags: ['controller.service_arguments'] Service Discovery class DashboardController {
public function __construct(DashboardBuilder $builder) { } public function build(DashboardBuilder $builder) { } } -
App\Controller\: resource: '../src/Controller' tags: ['controller.service_arguments'] Service Discovery class DashboardController {
public function __construct(DashboardBuilder $builder) { } public function build(DashboardBuilder $builder) { } } -
App\Controller\: resource: '../src/Controller' tags: ['controller.service_arguments'] Service Discovery class DashboardController {
public function __construct(DashboardBuilder $builder) { } public function build(DashboardBuilder $builder) { } } -
App\Controller\: resource: '../src/Controller' tags: ['controller.service_arguments'] Service Discovery class DashboardController {
public function __construct(DashboardBuilder $builder) { } public function build(DashboardBuilder $builder) { } } -
Interface - Abstract Multiple concrete implementation ? Provide default implementation
(alias) App\Dashboard\DashboardBlockBuilderInterface: '@App\Dashboard\DashboardBlockBuilder' ! -
Interface - Abstract Single concrete implementation ? Nothing to do:
SF provide alias for you ! -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
# 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 -
# 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 -
# 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 -
# 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 -
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 -
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 -
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 -
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 -
Migration - Step 3 For each single configuration file, disable
service fetching services: _defaults: autowire: true autoconfigure: true public: false -
For each single configuration file, disable service fetching services: _defaults:
autowire: true autoconfigure: true public: false Migration - Step 3 -
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'] -
1) COMMENT EVERY SERVICE IN FILE CONFIGURATION 2) DEBUG CONTAINER
FROM COMMAND LINE -
1) COMMENT EVERY SERVICE IN FILE CONFIGURATION 2) DEBUG CONTAINER
FROM COMMAND LINE 3) IF ERRORS -
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) -
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 -
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 -
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 -
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 -
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 -
SAME CLASS SERVICES app.dashboard.dashboard_horizontal_builder: class: App\Dashboard\DashboardBlockBuilder arguments: [...] app.dashboard.dashboard_vertical_builder: class:
App\Dashboard\DashboardBlockBuilder arguments: [...] -
SAME CLASS SERVICES app.dashboard.dashboard_horizontal_builder: class: App\Dashboard\DashboardBlockBuilder arguments: [...] app.dashboard.dashboard_vertical_builder: class:
App\Dashboard\DashboardBlockBuilder arguments: [...] -
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) -
CUSTOM TAG If those tagged service implement a common interface,
let symfony tag them for you $container ->registerForAutoconfiguration(MyInterface::class) ->addTag(‘aTag’); -
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 ... -
SETTER INJECTION /** * @required */ public function setLogger(LoggerInterface $logger)
{ $this->logger = $logger; } -
DOCTRINE LISTENER # In Compiler Pass $services = $container->findTaggedServiceIds('doctrine.entity_listener'); foreach
($services as $service => $attributes) { $container->getDefinition($service)->setPublic(true); } -
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> -
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> -
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 …)