Exploring magical Model injection in FilamentPHP

Hi everyone, Everyhing is fine, but I wondered if there are any experts on Filament's DI I could quiz, because ever since learning about the magical DI available to us, I became completely consumed with the idea of never passing anything to a method ever again πŸ˜› To help me understand a bit more about how the magic actually works though, here's an issue I'm running into: trying to get a Service class to DI the current instance of the Model. Take the following code as an example: In my EnvironmentResource, inside an Infolist...
Action::make('connect-repo')
->label('Connect your Repository')
->action(function (Environment $record, EnvironmentServiceInterface $environment_service) {
CreateGithubRepository::dispatch(new Collection([$record->fetchRepository()]));
});
Action::make('connect-repo')
->label('Connect your Repository')
->action(function (Environment $record, EnvironmentServiceInterface $environment_service) {
CreateGithubRepository::dispatch(new Collection([$record->fetchRepository()]));
});
and the Service...
class EnvironmentService implements EnvironmentServiceInterface
{
public function __construct(protected Environment $environment)
{

}
}
class EnvironmentService implements EnvironmentServiceInterface
{
public function __construct(protected Environment $environment)
{

}
}
and the ServiceProvider...
$this->app->bind(Environment\EnvironmentServiceInterface::class, Environment\EnvironmentService::class);
$this->app->bind(Environment\EnvironmentServiceInterface::class, Environment\EnvironmentService::class);
Now, as I said, everything is working fine, in the Action, DI is injecting the current Environment model just fine, and it's injecting the Service as well, BUT... the Environment model that the Service class attempts to inject, it's a blank model. Clearly the magic that is enabling the current instance of the Environment model to be injected (I presume it's Route Model Binding?) doesn't apply when a thing is calling a thing, rather than the thing being called directly. Obviously, I can work around this, and just pass in the Model as an argument, but... well, I was hoping someone could explain why it's not working in a little more detail, so I can both understand Laravel's DI better, but also so I can try and magic up a solution!
Solution:
In your case it should be ```php $service = app(EnvironmentServiceInterface::class, ['environment' => $record]);...
Jump to solution
8 Replies
Dennis Koch
Dennis Kochβ€’14mo ago
I am confused, because you aren't using your $environment_service. Filament's DI is basically calling Laravel DI container with some context aware defaults like $record, $livewire, $action, ...
Malcolm Turntbull
Malcolm TurntbullOPβ€’14mo ago
I removed the code to use it just to clean things up, but if you dd() on it, it's a perfectly instanced version of the EnvironmentServices class, and it HAS an Environment model attached, it's just an empty one - not the current model I'm viewing at the time. I even tried dumping out $request over in the Service class, and it's showing Livewire paths, basically. I considered using the mount() method somewhere (not available in a Resource class though?), because according to Livewire's docs, that's the only place where you're going to get a decent shot at the $request path being a real one.
Dennis Koch
Dennis Kochβ€’14mo ago
I think the issue is, that Laravel DI will create a new model, since it isn't aware of the currently injected $record. You need to resolve this yourself. Something like this:
$service = app(EnvironmentServiceInterface::class, ['record' => $record]);
$service = app(EnvironmentServiceInterface::class, ['record' => $record]);
Malcolm Turntbull
Malcolm TurntbullOPβ€’14mo ago
Hrm, tried that just now, and it didn't work. Tried '''PHP ['id' => $record->id]''' as well, no go. I guess, however, that passing the current record to the app facade isn't really any different to passing it to a method - I guess if there's no way to trick Laravel's DI into Route Model Binding off the original request URL, then I'm pretty much stuck with having to pass something to something. I know I am trying to push the magic too far, but the idea of never passing arguments to methods ever again is just too tempting!
Dennis Koch
Dennis Kochβ€’14mo ago
I guess, however, that passing the current record to the app facade isn't really any different to passing it to a method
I think it is. The latter explicitly passes a $record instance for that Environment Service
Solution
Dennis Koch
Dennis Kochβ€’14mo ago
In your case it should be
$service = app(EnvironmentServiceInterface::class, ['environment' =>
$record]);
$service = app(EnvironmentServiceInterface::class, ['environment' =>
$record]);
Malcolm Turntbull
Malcolm TurntbullOPβ€’14mo ago
oh hey, using 'environment' worked! look at that! That has opened up a whole swathe of possibilities... cheers Dennis! Gotta say, absolutely loving Laravel + Filament, I'd never touched Laravel up until a few weeks ago, the learning curve has been pretty brutal, but it's been the most fun writing code I've had in I don't even know how long!
Dennis Koch
Dennis Kochβ€’14mo ago
Learning Laravel, Livewire and Filament at the same time seems like a lot, but as long as you have fun. Even if you already start diving into topics like Dependency Injection. πŸ˜…
Want results from more Discord servers?
Add your server