I'm passing an entire Movie and sometimes I'm just using the ID
in what context? Excuse me if I don't crawl your entire project
Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.
I'm loving Laravel and MVC. I love the opinions, structure and design paradigm that it enforces. It helps me keep my thoughts straight and "see" the big picture a little easier than if I was just building a SPA or similar website/webapp using your favorite JS framework like Svelte, Vue, React (and their meta framework equivalents).
One thing that I'm not super clear on is how passing Models and route model binding works completely. I get that I can pass a Model to a Controller and it has access to that Model in the View. But when should you not do that, and instead just pass the ID and make the resulting SQL query in whatever you're passing the ID to?
I suppose it varies project to project, but some sort of good indicator of when to not would really help me. I am having trouble with knowing when to do it in my own project. You can see in my repo (dev branch) that sometimes I'm passing an entire Movie and sometimes I'm just using the ID. I've kind of guessed when its best to do one or the other or just leaned one way out of convenience but some tips on how to improve and answers to these questions would go a long way to improving my understanding of designing good Laravel apps.
https://github.com/0niSec/reeltrack-laravel/tree/develop
Thanks!
I'm passing an entire Movie and sometimes I'm just using the ID
in what context? Excuse me if I don't crawl your entire project
@Snapey No worries. I didn't mean it as a required task to help me. Without reviewing the repo, my questions were more along the lines of best practices and when to do certain things. The repo is just there if a curious mind was so inclined to review quickly and see if anything stuck out
For things like controllers and service/action classes, passing the entire model instance is fine and what I typically see, since it feels the most natural to do.
In the case of Livewire though, passing in and storing the entire model as a public property is a bit more debatable. It's important to note that Livewire has to "dehydrate" and "rehydrate" all public properties to and from JSON on each request.
With this process, there are two things to be aware of when storing a model as a public property:
Internal details about the model, such as ID, namespace, and any loaded relationships will be exposed to the browser. Some might consider this a security issue, but others may consider it a possible performance hit since it does increase the payload a bit (especially if you're storing multiple models).
Any additional query constraints on the model aren't preserved. For example, if you're querying for the model with an extra WHERE condition in the controller, then pass that model to a Livewire component on the page. Any subsequent requests to the component will not have that extra WHERE condition applied when re-querying.
Because of these little "gotchas", you might wish to instead pass in the model ID and re-query as needed (which is what I typically do). You could also use a computed property to make this less verbose.
One thing to note with computed properties though is that you may end up making duplicate queries. Such as if you're passing the same model ID to multiple components on a page, each component will make a separate query on the initial render. In this case, you might use caching or simply pass in the entire model instance if you're aware of the nuances.
Any additional query constraints on the model aren't preserved. For example, if you're querying for the model with an extra WHERE condition in the controller, then pass that model to a Livewire component on the page. Any subsequent requests to the component will not have that extra WHERE condition applied when re-querying.
I actually didn't know this. That might actually answer one of the other issues I was still working on solving elsewhere. Thanks!
In the case of Livewire though, passing in and storing the entire model as a public property is a bit more debatable. It's important to note that Livewire has to "dehydrate" and "rehydrate" all public properties to and from JSON on each request.
I didn't know this either and for my Livewire components, I think it makes the most sense to then pass in the ID instead of the entire Movie Model because where the Livewire components are being used where Movie would be passed in, it's quite a large Model. And I have seen the hydration in the DOM when looking at it. It's quite verbose lol.
One thing to note with computed properties though is that you may end up making duplicate queries. Such as if you're passing the same model ID to multiple components on a page, each component will make a separate query on the initial render. In this case, you might use caching or simply pass in the entire model instance if you're aware of the nuances.
And this is the tradeoff that I've been fighting internally throughout this project. I've had a blast learning but deciding whether to go the easy route and send an entire Model (of which, I can't directly measure the performance impact) or send just the ID which will require me to create another SQL query (or more, depending on relationships) and which is going to have the least performance impact has been difficult.
I appreciate the help though! These are good tips that I can go back to my code later on and review and make some changes.
@ctrlaltdelme To clarify this.
In the blade that loads the livewire component, or in a controller that loads single page livewire component, you should AVOID saving that model in a public property as it will be converted to json and sent to the client.
Public in Livewire is public everywhere!
Passing data to blade templates is private because it is only passed within the blade. This includes sending a model to the view in a Livewire render function.
if you are talking about route model binding {{ route'posts.edit', $post) }} is no different to {{ route'posts.edit', $post->id) }}
if you are talking about passing from the controller to an action for instance, you should always pass the model if you have it. You can avoid database queries if you have the model and can pass it around the application.
@Snapey So doing something like this would not be good, right? I'd want to use the movieId here and pass that into mount? The Movie here is passed to the Livewire Blade component?
class WatchInput extends Component
{
#[Validate('required|boolean')]
public bool $isWatched = false;
public Movie $movie;
public function mount(Movie $movie): void
{
$this->movie = $movie;
// If there's a watch record for this user, grab its "is_watched" value; otherwise default to false.
$this->isWatched = (bool) ($movie->watches()
->where('user_id', auth()->id())
->value('is_watched') ?? false
);
}
if you are talking about passing from the controller to an action for instance, you should always pass the model if you have it. You can avoid database queries if you have the model and can pass it around the application.
I think that's what I'm doing here where I'm passing Movie into the Controller action, show (this is the action that's creating 25 SQL queries lol)
public function show(Movie $movie): View
{
$userId = auth()->id();
$cacheKey = 'movie_details_'.$movie->id;
$cachedMovie = Cache::remember($cacheKey, 3600, function () use ($movie) {
return Movie::with([
'cast' => function ($query) {
$query->orderBy('order', 'asc')
->with(['person:id,name,profile_path'])
->take(10);
},
'crew' => function ($query) {
$query->whereIn('department', ['Directing', 'Writing', 'Production'])
->orderBy('order', 'asc')
->with(['person:id,name,profile_path'])
->take(10);
},
'genres',
])
->findOrFail($movie->id);
});
// Retrieve likes, ratings, and watchlists separately
$movieFresh = Movie::where('id', $movie->id)
->with([
'likes' => fn($query) => $query
->where('user_id', $userId)
->select('id'),
'ratings' => fn($query) => $query
->where('user_id', $userId)
->select('rateable_id', 'user_id', 'rating'),
'watchlists' => fn($query) => $query->where('user_id', $userId),
'reviews',
])
->withCount([
'likes' => fn($query) => $query->where('status', true),
'ratings',
])
->withAvg('ratings as avg_rating', 'rating')
->firstOrFail();
$cachedMovie->setRelation('likes', $movieFresh->likes);
$cachedMovie->setRelation('ratings', $movieFresh->ratings);
$cachedMovie->setRelation('watchlists', $movieFresh->watchlists);
$cachedMovie->likes_count = $movieFresh->likes_count;
$cachedMovie->ratings_count = $movieFresh->ratings_count;
$cachedMovie->avg_rating = $movieFresh->avg_rating;
// Return the view
return view('movies.show', ['movie' => $cachedMovie]);
}
}
I think you are referring to route model binding.
Route::get('/articles/{id}, ArticleShowController::class);
// VS
Route::get('/articles/{article}, ArticleShowController::class);
and
public function __invoke($id)
{
$article = Article::findOrFail($id);
}
//VS
public function __invoke(Article $article)
{
}
Which you should use depends a lot on your application, if you have lots of isolated tables, and don't need to eager load any relations, then route model binding is great, but if you are loading several related tables, then it's not optimal. There are of course ways around that, one way would be to create a database view that handles that for you.
@Tray2 Thanks! I'm actually only using the first method. I've never seen the __invoke calls before nor know what they'd be used for :D. But, yes, in my route definitions, I'm binding to the action of a Controller and the route would be something like /movie/{movie}. Is there a difference in /movie/{movie} and /movie/{id}? Don't they both equate to the same thing?
I have quite a few polymorphic relationships going on (so, a lot of eager loading) as I was trying to keep things clean. It's reluctantly led to a little confusion on my end but I've been able to work through it and things work. I just end up with A LOT of queries on some pages (or at least one in particular, /movie/{movie}) and it's led me to try to find some creative ways to deal with it. One of which, @jj15 already mentioned - caching. Though, it's only reduced queries down from 25 to 20.
@ctrlaltdelme They are called single action controllers, I prefer using them over regular restful controllers.
https://github.com/Tray2/mediabase/blob/main/app/Http/Controllers/Books/BooksIndexController.php
, I'm binding to the action of a Controller and the route would be something like /movie/{movie}. Is there a difference in /movie/{movie} and /movie/{id}? Don't they both equate to the same thing?
Yes, but one requires you to write the code to fetch the model data, and the other does it for you, if you inject it in your controllers method. And that is called route model binding.
As for your queries, this post might give you some ideas https://tray2.se/posts/use-a-view-instead-of-a-complex-eloquent-query-in-your-laravel-application
Please or to participate in this conversation.