34

I am wondering if it is possible to return a relationship with laravels Route model binding ?

Say is a have a user model with a relationship 'friends' to other users, and I want to return both the user info and the relationship from a route or controller.

eg for the route domain.tld/user/123

Route::model('user', 'User');

Route::get('/user/{user}', function(User $user) {

    return Response::json($user);

});

this will return me the user info fine but I also want the relationships, is there any easy/proper way to do this ?

I know I can do this

Route::get('/user/{user}', function((User $user) {

    return Response::json(User::find($user['id'])->with('friends')->get());

});

or

Route::get('/user/{id}', function(($id) {

   return Response::json(User::find($id)->with('friends')->get());

});

but I suspect there may be a better way.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Keith
  • 1,136
  • 2
  • 13
  • 30

3 Answers3

62

You don’t want to eager-load relationships on every query like Matt Burrow suggests, just to have it available in one context. This is inefficient.

Instead, in your controller action, you can load relationships “on demand” when you need them. So if you use route–model binding to provide a User instance to your controller action, but you also want the friends relationship, you can do this:

class UserController extends Controller
{
    public function show(User $user)
    {
        $user->load('friends');

        return view('user.show', compact('user'));
    }
}

Edit: you can also conditionally load relations using loadMissing. This will only load the relation if it isn’t already loaded on the model:

class UserController extends Controller
{
    public function show(User $user)
    {
        // If friends relation has already been loaded, will be a no-op
        $user->loadMissing('friends');

        return view('user.show', compact('user'));
    }
}
Martin Bean
  • 38,379
  • 25
  • 128
  • 201
  • Thanks for this. You can also use `load` a little like `with`, for instance as part of a more complex query like: `return $module->load(['questions', 'questions.results' => function($q) use($user){ return $q->where('user_id', $user->id); }]);` – Djave Apr 02 '17 at 21:43
  • 8
    @Djave `load()` is for loading relationships _after_ a query’s been performed, `with()` is for including relationships as part of a query. – Martin Bean Apr 04 '17 at 10:17
  • According to question, surely protected $with is worth the answer, but in overall according to app performance, this is what we should follow. Just load explicitly when you need it. – iamawesome May 25 '21 at 05:52
38

You can populate the $with property in the User model. Like so;

protected $with = ['friends'];

This will autoload the relationship data for you automatically.

Please Note: This will do it for every user model query.

If you dont want friends to be loaded all the time, then you can bind it to the parameter within your route, like so;

Route::bind('user_id', function($id) {
    return User::with('friends')->findOrFail($id);
});

Route::get('/user/{user_id}', 'BlogController@viewPost');
Matt Burrow
  • 10,477
  • 2
  • 34
  • 38
7

Martin Bean's response is probably not the way you want to tackle this, only because it introduces an n+1 to your Controller:

1) It must load the User via Route Model Binding, then....

2) It now loads the relationship for friends

He is correct, however, that you probably don't want to load the relationship every time.

This is why Matt Burrow's solution is probably better (he's binding a different value: instead of {user}, you could use something like {user_with_friends} and bind that separately from {user}...

Personally, I think if you need to load friends for only that route, I'd simply just pass $userId (without binding), and just begin the controller method with:

$user = User::with('friends')->findOrFail($userId);

You can either let Laravel handle the ModelNotFoundException automatically (like it does with Route Model Binding), or wrap it in a try/catch

jsfrank
  • 79
  • 1
  • 1
  • 1
    _Martin Bean's response is probably not the way you want to tackle this, only because it introduces an n+1 to your Controller_. No, it doesn’t. – Martin Bean May 25 '21 at 09:03
  • How exactly would an N+1 problem be introduced when we're talking about route model binding - ie: a single model is being returned. Surely `$user->load('friends')` is just going to load the related friends once because there's only one `$user`. – Joseph Mar 01 '23 at 08:24