Does Filament change Laravel's default Route Model Binding?
My understanding of RMB from all the docs I've read is that the app() facade should be able to resolve the current Model, based on the URL parameters. This works within the scope of say, a Resource, but not within a custom class.
This logs the current route, with the correct ID in the correct path, e.g. , however the $project value is a blank model - it is not resolving the ID in the path.
Is this because the routes Filament gets us to create use {record} (e.g. instead of {project}, thus rendering it unable to resolve? I've tried renaming the {record} to {project}, only to have it immediately throw a raft of errors.
I'm really stuck with this one. By everything I've read, this stuff should just work, but it isn't. Is there some trick I'm missing? A trait my class needs to use? A setting on my Model? I don't know enough about how it works to know for sure, but it is beginning to feel like Filament is doing something tricky in the middle here with DI, and that trickiness is either undocumented / or I'm completely missing something obvious.
18 Replies
I don't understand where you are doing your logging. Filament shouldn't mess with the binding, but as you pointed out, it is using
$record
as a placeholder.I'm calling this function something like this (massively cut down and simplified):
The horrible irony is that in the context of that repositorySection() static method, the closure with Project $project? It resolves perfectly fine just there. Yes, I could pass it in as a parameter, but I'm trying to rely on DI as much as possible, and I can't for the life of me understand why RMB would work perfectly well in this context here, but a couple of method calls away, it doesn't.
Maybe there is magic in the Resource that allows this to work, and it's not using RMB at all?
OK, I just tried this:
The first Debug shows the correct Model instance, but the second one shows a blank instance. Am I going crazy, or am I completely misunderstanding how all this is meant to work?
I've tried cleaning up the code to make it more obvious as to what goes where, and I've also commented all the various attempts that do and do not work:
Is there a limit on the number of times a record can be injected? Or do nested injections not work? I'm losing my mind here, what am I doing wrong?
Makes perfect sense. The first one is injected by Filament. The second one tries to resolve a Project object from the container without any information. It doesn't know which one you want so it just returns a new pone.
You are confusing Filaments injection with Laravel's container dependency injection
OK, so I think I may have stumbled onto an underlying truth that I didn't really understand: Route Bindings and Container Bindings are two completely separate things.
Stack Overflow
Laravel: Using mock instance in Customised Route::model binding
Route model binding in Laravel, helps bind an instance to the service container, to be used in a controller action. And the beautiful thing is that when I want to write tests, I can bind a mock ins...
So when Filament is injecting the correct model in that instance, it's doing so via its own methodology, and has nothing to do with the container, OR route model bindings (which I don't think actually work at all in Filament, because we're not using Controllers?)
I have also clearly misunderstood the Laravel documentation - when it says "The Laravel service container is a powerful tool for managing class dependencies and performing dependency injection", it does not mean that it is THE tool for managing dependency injection.
Laravel - The PHP Framework For Web Artisans
Laravel is a PHP web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing you to create without sweating the small things.
I have foolishly believed that if there is DI happening, that it must be the SC doing it. And that if the SC was able to resolve a specific instance of a Model, then it was Route Model Binding doing it.
This whole thing has been such a handbrake on my project, I'm writing some documentation on how it all actually works - only baby steps so far, as I clearly don't actually understand wtf I am doing, but if I am running into these sorts of misunderstandings, then it's guaranteed other people are as well, and I've spent... WEEKS looking at documentation on this.
https://docs.google.com/document/d/19YhjppE5qSw4v1xqaDQFiuHmEjiPCpJyfFOHHY21YTE/edit?usp=sharing this is the start of my documentation, I'll write it in here for now, and eventually publish on Medium.
Google Docs
Things I wish someone had explained to me: Dependency Injection in ...
As someone who is new to both Laravel and Filament, the one area I have struggled with the most is Dependency Injection. When it works, it is seemingly magical, but when it doesn’t work as you are expecting, it is incredibly frustrating trying to determine why. This is not helped when documentat...
There are countless thousands of examples of how DI works, exactly like the base Laravel docs show you. There's almost nothing on why and when it DOESN'T work.
Route binding works because it uses dependency injection and the provided id of the route. This way it can resolve the correct model. That doesn’t mean that it will always resolve this model afterwards since it won’t be registered in the service container.
Filament takes the model from the route and injects it into all the closures where it’s needed via its own dependency injection mechanism. So it’s just passing on the already resolved model.
There has to be some sort of context around which Filament's DI does and does not work - for an example, there's this:
Even if I call Filament's internal evaluate() method, it doesn't just resolve a model from wherever you like
There is no magic happening. Filament passes the object around that was resolved in the beginning.
The evaluate method always required that object to inject it again. This is done via
defaultEvaluationParameters()
method in all Filament components. If you omit the record, it resolved a new one from the container as Laravel does.
Tl;DR: Filament's DI is pretty much the same as Laravels (other thtat it can resolve based on type, too). And it needs context to inject the right stuff otherwise it get's resolved from the containers.Is there any documentation on that "context"? I would love to be able to add it to my article, and maybe help the next idiot like me who doesn't know when to quit 😛
That’s the defaultEvaluationMethods I mentioned before.
I've done some digging, and so far I've worked out that essentially, the "context" is that you're evaluating the closure on an object that has $record set.
so in my instance, the first one worked because it was on an $infoList, which has its record already defined
now to try and find where the infoList gets that record in the first place...
although for the sake of explaining "where" you can use this sort of injection, I think I probably know enough to explain it
It's injected into the Livewire component by route model binding
Then passed to the infolist. Then injected to all the methods that request it
ok, that actually makes a lot of sense!