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');