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

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 ALL THE THINGS!
    Verona, 19 Ottobre 2018
    SymfonyDayIT

    View Slide

  2. Samuele Lilli
    “Don Callisto”

    View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  26. SERVICE
    DISCOVERYAIN’T
    AUTOWIRE

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  31. AUTOWIRE INTERFACES
    SCALAR ARGS
    ABSTRACT CLASSES

    View Slide

  32. Interface - Abstract
    Multiple concrete implementation
    ?
    Provide default implementation (alias)
    App\Dashboard\DashboardBlockBuilderInterface:
    '@App\Dashboard\DashboardBlockBuilder'
    !

    View Slide

  33. Interface - Abstract
    Single concrete implementation
    ?
    Nothing to do: SF provide alias for you
    !

    View Slide

  34. Different implementation than default
    ?
    Declare explicitly service
    !
    Interface - Abstract

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  40. SERVICE
    MIGRATION

    View Slide

  41. Migration
    Multiple bundles

    View Slide

  42. Multiple bundles
    Multiple config. files
    Migration

    View Slide

  43. STEP 1ENABLE
    AUTOWIRE

    View Slide

  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

    View Slide

  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

    View Slide

  46. STEP 2 FQCN SERVICES
    &
    ALIAS

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  55. STEP 3PRIVATE
    SERVICES

    View Slide

  56. Migration - Step 3
    For each single
    configuration file, disable
    service fetching
    services:
    _defaults:
    autowire: true
    autoconfigure: true
    public: false

    View Slide

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

    View Slide

  58. STEP 4ENABLE
    SERVICE
    DISCOVERY

    View Slide

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

    View Slide

  60. GET RID OF
    EVERY* SERVICE
    DEFINITION

    View Slide

  61. HOWWITHOUT
    break
    LEGACY CODE?

    View Slide

  62. 1) COMMENT EVERY SERVICE IN FILE CONFIGURATION

    View Slide

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

    View Slide

  64. bin/console debug:container

    View Slide

  65. 1) COMMENT EVERY SERVICE IN FILE CONFIGURATION
    2) DEBUG CONTAINER FROM COMMAND LINE
    3) IF ERRORS

    View Slide

  66. View Slide

  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)

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  73. AVOID
    PITFALLSAKA WHATCONTAINER
    WON’T TELL YOU

    View Slide

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

    View Slide

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

    View Slide

  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)

    View Slide

  77. CUSTOM TAG
    If those tagged service implement a common
    interface, let symfony tag them for you
    $container
    ->registerForAutoconfiguration(MyInterface::class)
    ->addTag(‘aTag’);

    View Slide

  78. CUSTOM TAG
    Otherwise
    declare and tag explicitly

    View Slide

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

    View Slide

  80. SETTER INJECTION
    /**
    * @required
    */
    public function setLogger(LoggerInterface $logger)
    {
    $this->logger = $logger;
    }

    View Slide

  81. SERVICE PRIORITY
    Don’t comment out services
    where priority is declared

    View Slide

  82. DOCTRINE LISTENER
    # In Compiler Pass
    $services = $container->findTaggedServiceIds('doctrine.entity_listener');
    foreach ($services as $service => $attributes) {
    $container->getDefinition($service)->setPublic(true);
    }

    View Slide

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

    View Slide

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

    View Slide

  85. NON SHARED SERVICES
    Don’t comment out services
    where priority is declared

    View Slide

  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 …)

    View Slide

  87. questions?

    View Slide

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

    View Slide

  89. WE’RE
    HIRING!

    View Slide