-
Notifications
You must be signed in to change notification settings - Fork 892
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dependency injection into migrations #2183
Comments
You could extend the base template and add your own DI management and then refer to the new template in your phinx.php or equivalent. Setting Not everyone needs DI. |
In terms of accessing input and output, the following methods are available:
So you can use them in your migrations already via protected function writeln($message, bool $newline = true, bool $addPadding = true)
{
if (!is_array($message)) {
$message = [$message];
}
$message = array_map(
function ($message) use ($addPadding) {
return ($addPadding ? $this->commentPadding : '').$message;
},
$message
);
$this->getOutput()->write($message, $newline);
} So, all the output from a migration is nicely indented and so when you run a migration you get output like:
This does make things nice and easy to read for us. |
@rquadling I'm sorry, but you misunderstood the issue completely. I don't care about the output or the console commands. What I want to be able to do is to call service code inside my migrations' final class GroupsType extends Migration
{
public function up(): void
{
$rates = $this->container()->get(ConversionService::class)->getRates();
// or just $this->conversionService()->getRates()
// Now use the rates for the migration code here...
}
} And I do want that service to come from my service container, not conjured using static stuff, as that's what I'm currently doing to overcome the issue (using a static facade). Let me elaborate to prevent further confusion. The following is my use My\Database\Migration;
return (function () {
$container = (new Bootstrap)->container();
// This patches the issue of not having access to the container in the migrations.
PhinxCrutch::bind(new PhinxCrutch($container));
$settings = $container->get(Settings::class);
$root = $settings->get('paths.root');
return [
'container' => $container, // This only works for fetching the seeders at the time of writing (v13.4).
'paths' => [
'migrations' => $root . 'db/migrations',
'seeds' => $root . 'db/seeds',
],
'migration_base_class' => Migration::class,
// ...
];
})(); And the following is my migration base class abstract class Migration extends AbstractMigration
{
public function schema(): SchemaBuilder
{
return $this->container()->get(SchemaBuilder::class);
}
public function eq(): EloquentConnection
{
return $this->container()->get(EloquentConnection::class);
}
public function db(): DibiConnection
{
return $this->container()->get(DibiConnection::class);
}
public function capsule(): Capsule
{
return $this->container()->get(Capsule::class);
}
protected function container(): Container
{
try {
// This is a facade-like approach to patching the issue of not having a service container injected.
return PhinxCrutch::instance()->container();
} catch (LogicFault $e) {
throw new LogicFault(
message: 'Unable to fetch DI container for Phinx migrations.',
previous: $e ?? null,
);
}
}
} This allows our developers to use whatever DB tech they are accustomed to, |
The example I gave was a bit ultra simplistic. We use PHP-DI setup in our AbstractMigration class to do all of this. We then use This is similar to using What gets injected is defined by the di-config, so if there's rules about what to use, then great. My example was that you can create your own AbstractMigration and set this to be the default AbstractMigration for all subsequent migrations created by If the you then add your DI handler in the pre-flight, then, effectively, you've setup whatever processing you need as part of the migration's Does this help? |
The question of is there anything extra that Phinx needs to support is probably more along the lines of what you are asking. I'd say that whilst the need for both of us to have Phinx support DI for the migrations is business critical, it isn't for everyone. Implementing DI is relatively trivial with the feature set already present in Phinx. |
Okay, I get the point where one does not need to use dependency injection for all the migrations upon creation. This can also be solved using the same patterns I described in the first post. But if anyone was to do what you suggest, one would have to at least
So yes, it is doable, but at what cost? It is then much more sane to just use a static facade. And the need for these measures can be mitigated at the cost of adding a couple of interfaces or hooks and a couple of |
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. Interface
A simple interface like
would do the job.
Inside
Manager
class, right after instantiating a migration, add this codeAdd
Manager::getContainer
public method to enable access to the container from within the migrations.Or do something similar directly using the container
2. Migration factory
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...3. Decoration hook
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.
The text was updated successfully, but these errors were encountered: