Home > Software design >  How Laravel loads relations?
How Laravel loads relations?

Time:02-15

Let's say we do this $users = User::with('comments')->get(); it will execute

select * from `users`
select * from `comments` where `comments`.`user_id` in (1, 2, 3, 4, 5)

I don't understand how Laravel knows which comments are linked to which user? Does Laravel do some extra code after it executes queries to link each user to his comments so when we do:

foreach ($users as $user) {
    $user->comments //Laravel knows that which comments to load for this user
}

CodePudding user response:

I don't understand how Laravel knows which comments are linked to which user

Well, in your example every comment has a user_id field, it is not hard to loop over the comments and assign every one to the correct user.

Does Laravel do some extra code after it executes queries to link each user to his comments

Definitely. You should take a deep dive on laravel internals (https://github.com/laravel/framework/) to find the exact parsing code though.

I guess that logically it does something along the lines of:

  • Perform the base query (select * from users), creates a collection of User for each row returned

  • Get the unique user IDS in the collection

  • Perform the relationship query (select * from comments where comments.user_id in (1, 2, 3, 4, 5)), creates a collection of Comment, loop over the collection and assign every comment to the proper User

  • Return the collection of Users

CodePudding user response:

lets examine a simpler example first.

$user = User::with('comments')->find(1); // user with id 1

now laravel knows which comments belong to user just because of the id :

SELECT * FROM `users` WHERE `id` = 1    // gets user
SELECT * FROM `comments` WHERE `user_id` = 1 // gets user's comments

now when you need comments for more then one user, this happens:

SELECT * FROM `comments` WHERE `user_id` in (/* user ids */)

and then appends each comment to their relative user's collection

CodePudding user response:

To build database queries using Eloquent under the hood, Laravel uses Illuminate\Database\Eloquent\Builder instance.

When you call with method, you add a relation to eagetLoad array property of the builder:

public function with($relations, $callback = null)
{
    if ($callback instanceof Closure) {
        $eagerLoad = $this->parseWithRelations([$relations => $callback]);
    } else {
        $eagerLoad = $this->parseWithRelations(is_string($relations) ? func_get_args() : $relations);
    }

    $this->eagerLoad = array_merge($this->eagerLoad, $eagerLoad);

    return $this;
}

Then when you try to receive a result using get, find, findOrFail, etc., builder calls eagerLoadRelation method:

protected function eagerLoadRelation(array $models, $name, Closure $constraints)
{
    // First we will "back up" the existing where conditions on the query so we can
    // add our eager constraints. Then we will merge the wheres that were on the
    // query back to it in order that any where conditions might be specified.
    $relation = $this->getRelation($name);

    $relation->addEagerConstraints($models);

    $constraints($relation);

    // Once we have the results, we just match those back up to their parent models
    // using the relationship instance. Then we just return the finished arrays
    // of models which have been eagerly hydrated and are readied for return.
    return $relation->match(
        $relation->initRelation($models, $name),
        $relation->getEager(), $name
    );
}

The matching with parent models is happening within match method, which implemented differently for every relation type (you may see Illuminate\Database\Eloquent\Relations namespace). For most relations the matchOneOrMany method is used (dictionary is a simple collection, that filtered by foreign key for relation):

    protected function matchOneOrMany(array $models, Collection $results, $relation, $type)
    {
        $dictionary = $this->buildDictionary($results);

        // Once we have the dictionary we can simply spin through the parent models to
        // link them up with their children using the keyed dictionary to make the
        // matching very convenient and easy work. Then we'll just return them.
        foreach ($models as $model) {
            if (isset($dictionary[$key = $this->getDictionaryKey($model->getAttribute($this->localKey))])) {
                $model->setRelation(
                    $relation, $this->getRelationValue($dictionary, $key, $type)
                );
            }
        }

        return $models;
    }

CodePudding user response:

If you try using the die and dump function for "debugging" and understanding the structure of your queries whenever you're confused about the structures, then things will start making more sense.

dd($users)

You'll find that Laravel will neatly fetch the comments of each user if everything is set up correctly in your models.

  • Related