phinx icon indicating copy to clipboard operation
phinx copied to clipboard

Dependency injection into migrations

Open dakujem opened this issue 1 year ago • 6 comments

Phinx has a serious design issue IMHO.

As far as I can tell, there is no way to inject anything to migrations. At least there is a way to do so with the seeders (using the undocumented 'container' option), which would not be the best solution for the migrations, but would do the job.
Obviously, defining dependencies for every migration would be tedious, unless something with magic (like PHP-DI) is used to construct them.
And there would be the problem of inlining the dynamic arguments ($migration = new $class($environment, $version, $this->getInput(), $this->getOutput());).

Currently, I am able to set my base migration class, which might have the option setManager which would allow to call something like $this->getManager()->getContainer() inside the migrations and to fetch my dependencies from there. But I am not able to cofigure it in any way, so I can't call the fictional setManager method.

Suggestions

I suggest the following solutions

  1. create an interface allowing us to decorate the migration classes
  2. allow us to set a migration factory (for seeders as well?)
  3. introduce hooks to call after a migration has been instantiated enabling decoration (for seeders as well?)

1. Interface

A simple interface like

interface ManagerAwareMigrationInterface {
  public function setManager(Manager $manager);
}

would do the job.

Inside Manager class, right after instantiating a migration, add this code

if ($migration instanceof ManagerAwareMigrationInterface ){
  $migration->setManager($this);
}

Add Manager::getContainer public method to enable access to the container from within the migrations.

Or do something similar directly using the container

if (if $this->container && $migration instanceof ContainerAwareMigrationInterface ){
  $migration->setContainer($this->container);
}

2. Migration factory

$phinxOptions = [
  'migration_factory' => $container->get(MyMigrationFactory::class),
];

as the possible values, use a generic callable with signature same as the AbstractMigration's constructor (plus the class name) or an interface with a method with the same signature...

// a generic callable
$factory = fn(string $className, string $environment, int $version, ?InputInterface $input = null, ?OutputInterface $output = null): AbstractMigration => WhateverHere();

// an interface
interface MigrationFactory{
  public function createMigration(string $className, string $environment, int $version, ?InputInterface $input = null, ?OutputInterface $output = null): AbstractMigration;
}

3. Decoration hook

$phinxOptions = [
  'migration_decorator' => $container->get(MyMigrationFactory::class)->makeDecorator(),
];
// Manager.php
$migration = new $class($environment, $version, $this->getInput(), $this->getOutput());
if ($this->migrationFactoryHook){
  $migration = ($this->migrationFactoryHook)($migration) ?? $migration;
}

Conclusion

All the above options are simple to implement and will do the job. I could provide a PR if you wanted me to, but let me know which approach to take.

dakujem avatar Mar 13 '23 15:03 dakujem