How to use container (and services) in Symfony 5 migrations?

Viewed 5145

I'm trying to get the container - and a service through it - in a migration (using Symfony 5.1.2 and doctrine/migrations 3.0.1). But when I try to create a class based on this example like this:

class Version20200717072537 extends AbstractMigration implements ContainerAwareInterface
{
    use ContainerAwareTrait;

    public function postUp(Schema $schema) : void
    {
        $fooService = $this->container->get('app.foo');
        //...
    }
}

... and I try to run migrations, I get a Call to a member function get() on null error.
So for some reason container is null.

I also tried replacing the use ContainerAwareTrait; with the actual setContainer function that I found in it, and it looks like the setContainer function is never called.

I'm pretty sure some time ago I was able to get the container from the migrations (maybe in Symfony 3 or 4?) based on the example I linked above, but now I don't know if this is the expected behavior in Symfony 5, or a bug, or I am doing something wrong.

1 Answers

As mentioned in the comments documentation related to the old version. But now you can do it with MigrationFactory.

You need to create your own MigrationFactory class, which will inject some service into your migrations. Starts from 3.4 Symfony all services a private, so you can't get them from the container.

doctrine_migrations.yaml

services:
    'Doctrine\Migrations\Version\MigrationFactory': 'App\Doctrine\Migrations\MigrationFactory'

MigrationFactory

<?php

namespace App\Doctrine\Migrations;

use Doctrine\DBAL\Connection;
use Doctrine\Migrations\AbstractMigration;
use Psr\Log\LoggerInterface;

class MigrationFactory implements \Doctrine\Migrations\Version\MigrationFactory
{
    /** @var Connection */
    private $connection;

    /** @var LoggerInterface */
    private $logger;

    /**
     * @var SomeService
     */
    private $service;

    public function __construct(Connection $connection, LoggerInterface $logger, SomeService $service)
    {
        $this->connection = $connection;
        $this->logger     = $logger;
        $this->service = $service;
    }

    public function createVersion(string $migrationClassName) : AbstractMigration
    {
        $migration = new $migrationClassName(
            $this->connection,
            $this->logger
        );

        // or you can ommit this check
        if ($migration instanceof SomeInterface) {
            $migration->setService($this->service);
        }

        return $migration;
    }
}

Migration

<?php

declare(strict_types=1);

namespace App\Doctrine\Migrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
 * Auto-generated Migration: Please modify to your needs!
 */
final class Version20200717112426 extends AbstractMigration implements SomeInterface
{
    private $service;

    public function setService(SomeService $service)
    {
        $this->service = $service;
    }

    public function getDescription() : string
    {
        return '';
    }

    public function up(Schema $schema) : void
    {
        // this up() migration is auto-generated, please modify it to your needs
    }

    public function postUp(Schema $schema): void
    {
        $this->service->someMethod();
    }

    public function down(Schema $schema) : void
    {
        // this down() migration is auto-generated, please modify it to your needs
    }
}
Related