Skip to content
Advertisement

Why does laravel map function returns the whole object from relationship defined?

Say I have relationship of One User to Many Post

Where Post has a relationship of Many Post to One Tag

I am trying to get the title of Post, in the returned collection through API

Code below:

        $user = User::find($id);
        $data = $user->posts()->get()->map(function ($query) {
            $query['tag'] = $query->tag->title;

            return $query;
        })->values();

returns me just the object instead of the name:

[{
    // Post attribute
    "name": "Post Title 1",
    "tag":  {
            "id": 1,
            "title": "Tag 1",
            "created_at": "2020-12-22T23:17:21.000000Z",
            "updated_at": "2020-12-22T23:17:21.000000Z"
        },
}]

if I use different name in anonymous function, I will get the name and the object together:

        $user = User::find($id);
        $data = $user->posts()->get()->map(function ($query) {
            $query['tag_title'] = $query->tag->title;

            return $query;
        })->values();

Then I will get:

[{
    // Post attribute
    "name": "Post Title 1",
    "tag":  {
            "id": 1,
            "title": "Tag 1",
            "created_at": "2020-12-22T23:17:21.000000Z",
            "updated_at": "2020-12-22T23:17:21.000000Z"
        },
    "tag_title": "Tag 1"
}]

I have to change the code inside anonymous function to: $query->tag()->first()->title;

For me to get what I want (just the name, not the object)

What is the explanation to this?

Advertisement

Answer

When you are accessing the dynamic property of a relationship Eloquent will try to load the relationship if it is not already loaded:

$post->tag->title

It needs to load tag to access it here. When relationships are loaded they will be included in the serialized output for the Model. You will see this tag relationship loaded in that output.

When you are calling $post->tag()->first() you are directly querying the relationship for these records. This does not “load” any relationship, it just lets you directly query it. Since you have not loaded the relationship here it will not be in the serialized output.

Laravel 8.x Docs – Eloquent – Relationships – Relationship Methods Vs. Dynamic Properties

There are ways to ‘hide’ things in the serialized output, but it might not be a bad idea to think about something like a Transformer or a ApiResource (which Laravel provides) to serialize the data how you would like it to look.

Laravel 8.x Docs – Eloquent – Serialization – Hiding attributes from JSON

Laravel 8.x Docs – Eloquent – Api Resources

map is not doing anything special here. You have to remember that the object that is passed into your callback there is the same object you are returning. So you are only adding a field to the object and returning it (which in the process of doing that you are loading the relationship on the Model).

A suggestion to decrease the queries and hide the relationship:

class Post ...
{
    ...
    public function getTagTitleAttribute()
    {
        return $this->tag->title;
    }
}

$user = User::with('posts.tag')->findOrFail($id);

return $user->posts->append('tag_title')->makeHidden('tag');
User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement