phinx
phinx copied to clipboard
Dependency injection into migrations
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
- create an interface allowing us to decorate the migration classes
- allow us to set a migration factory (for seeders as well?)
- 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.