An practical introduction into Dependency Injection with Symfony Dependency Injection for TYPO3.
Go to file
2023-03-10 20:32:55 +01:00
.gitignore Initial version for webcamp venlo 2023-03-10 20:32:55 +01:00
LICENSE Initial commit 2023-03-06 15:34:56 +01:00
Readme.rst Initial version for webcamp venlo 2023-03-10 20:32:55 +01:00

Talk TYPO3 Dependency Injection

A practical introduction into Dependency Injection with Symfony Dependency Injection for TYPO3.

What is Dependency Injection

  • Our code has dependencies (E.g. Controller → View, Controller → Repository)
  • Inversion of control (ask others to inject dependencies)
  • Allow configuration of dependencies
  • Do yourself a favour and use interfaces
  • Used to connect pieces of code (E.g. event listener, commands, …)

Flow

TYPO3 will:

  1. Check all Services.yaml and Services.php files once
  2. Use Symfony Dependency Injection once
  3. To build a psr/container (PSR-11) once
  4. Then load the psr/container on consecutive calls
  5. Use psr/container to fetch concrete implementations

The generated code (Container) is stored at: var/cache/code/di/DependencyInjectionContainer_<hash>.php

Usage within TYPO3

Injection

A consumer needs dependencies.

  • Injection via __construct()
  • Injection via inject*() method
  • Injection via @required annotated method

See: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/DependencyInjection/Index.html#constructor-injection

Manually create instances

Always use GeneralUtility::makeInstance().

  • 0 arguments will ask the container
  • Requested service needs to be public

Public services

Prevent them!

Mark as public when TYPO3 forces you to do so.

See: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/DependencyInjection/Index.html#what-to-make-public

The Services.yaml

Basic example if you prefer YAML:

services:
 _defaults:
   autowire: true
   autoconfigure: true
   public: false

 DanielSiepmann\Tracking\:
   resource: '../Classes/*'

The Services.php

Basic example if you prefer PHP:

<?php

declare(strict_types=1);

namespace WerkaumMedia\ABTest\Configuration;

use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (
   ContainerConfigurator $containerConfigurator
) {
    $services = $containerConfigurator
        ->services()
        ->defaults()
        ->autowire()
        ->autoconfigure()
    ;

    $services->load('WerkraumMedia\\ABTest\\', '../Classes/');
};

Concrete Examples

Injecting Query Builder

services:
  _defaults:
    autowire: true
    autoconfigure: true
    public: false

  DanielSiepmann\Tracking\:
    resource: '../Classes/*'

  querybuilder.tx_tracking_pageview:
    class: 'TYPO3\CMS\Core\Database\Query\QueryBuilder'
    factory:
      - '@TYPO3\CMS\Core\Database\ConnectionPool'
      - 'getQueryBuilderForTable'
    arguments:
      - 'tx_tracking_pageview'

  DanielSiepmann\Tracking\Domain\Repository\Pageview:
    public: true
    arguments:
      - '@querybuilder.tx_tracking_pageview'

See: https://git.daniel-siepmann.de/danielsiepmann/tracking/src/branch/main/Configuration/Services.yaml

Event Listener

<?php

declare(strict_types=1);

namespace WerkraumMedia\ABTest\Configuration;

use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use WerkraumMedia\ABTest\Events\SwitchedToVariant;
use WerkraumMedia\ABTest\MatomoTracker;

return static function (ContainerConfigurator $containerConfigurator) {
    $services = $containerConfigurator
        ->services()
        ->defaults()
        ->autowire()
        ->autoconfigure()
    ;

    $services->load('WerkraumMedia\\ABTest\\', '../Classes/');

    $services->set(MatomoTracker::class)->tag('event.listener', [
        'method' => 'handleVariant',
        'event' => SwitchedToVariant::class,
    ]);
};

See: https://git.daniel-siepmann.de/Customers/abtest/src/branch/main/Configuration/Services.php

Command

services:
  _defaults:
    autowire: true
    autoconfigure: true
    public: false

  DanielSiepmann\Tracking\:
    resource: '../Classes/*'

  DanielSiepmann\Tracking\Command\UpdateDataCommand:
    tags:
      - name: 'console.command'
        command: 'tracking:updatedata'

See: https://git.daniel-siepmann.de/danielsiepmann/tracking/src/branch/main/Configuration/Services.yaml

CompilerPass

  • Alter existing service definitions
  • E.g. add tagged classes to a registry
  • E.g. add event listeners
  • E.g. mark classes as public
<?php

declare(strict_types=1);

namespace WerkraumMedia\ThueCat;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use WerkraumMedia\ThueCat\Domain\Import\Entity\MapsToType;
use WerkraumMedia\ThueCat\Domain\Import\Typo3Converter\Converter;
use WerkraumMedia\ThueCat\Domain\Import\UrlProvider\UrlProvider;

return function (ContainerConfigurator $container, ContainerBuilder $containerBuilder) {
    $containerBuilder->registerForAutoconfiguration(UrlProvider::class)
      ->addTag(DependencyInjection\UrlProvidersPass::TAG);
    $containerBuilder->addCompilerPass(new DependencyInjection\UrlProvidersPass());

    $containerBuilder->registerForAutoconfiguration(Converter::class)
      ->addTag(DependencyInjection\ConverterPass::TAG);
    $containerBuilder->addCompilerPass(new DependencyInjection\ConverterPass());

    $containerBuilder->registerForAutoconfiguration(MapsToType::class)
      ->addTag(DependencyInjection\EntityPass::TAG);
    $containerBuilder->addCompilerPass(new DependencyInjection\EntityPass());
};

.. code-block:: php
   :linenos:

<?php

declare(strict_types=1);

namespace WerkraumMedia\ThueCat\DependencyInjection;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use WerkraumMedia\ThueCat\Domain\Import\Typo3Converter\Registry;

class ConverterPass implements CompilerPassInterface
{
    public const TAG = 'thuecat.typo3.converter';

    public function process(ContainerBuilder $container): void
    {
        $registry = $container->findDefinition(Registry::class);

        foreach ($container->findTaggedServiceIds(self::TAG) as $id => $tags) {
            $definition = $container->findDefinition($id);
            if (!$definition->isAutoconfigured() || $definition->isAbstract()) {
                continue;
            }

            $registry->addMethodCall('registerConverter', [$definition]);
        }
    }
}

See:

Sources