johannchopin
johannchopin

Reputation: 14844

Laravel: Using belongsTo() add unexpected attributes to my Model

I'm writing a REST API using Lumen. I have for my example 2 models User and Post. Post model use the method belongsTo to get the User model which created this post. My goal was to define an accessor so I can get the author's username of the post just like that Post::find($id)->author. So according to the doc I do this:

Post.php :

<?php
namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
  protected $table = 'posts';
  protected $appends = ['author'];

  protected $fillable = [
    'title',
    'description'
  ];

  protected $hidden = [
    'user_id',
    'created_at',
    'updated_at'
  ];

  public function user()
  {
    return $this->belongsTo('App\User', 'user_id');
  }

  public function getAuthorAttribute()
  {
    return $this->user->username;
  }
}

Now the getter works well and I can easily get the author of the given Post.

But if I tried to return the Post in a JSON response, it's also return me weird attributes like user that seems to come from my user() method that call a belongsTo():

return response()->json(Post::find(2), 200);
{
    "id": 2,
    "title": "Amazing Post",
    "description": "Nice post",
    "author": "FooBar",
    "user": {
        "id": 4,
        "username": "FooBar"
    }
}

If I use the attributesToArray() it's work as expected:

return response()->json(Post::find(2)->attributesToArray(), 200);
{
    "id": 2,
    "title": "Amazing Post",
    "description": "Nice post",
    "author": "FooBar"
}

Moreover if I remove the getter getAuthorAttribute() and the $appends declaration, I don't get the unexpected user attribute.

But I don't want to use this method each time and it doesn't make it work if I want to return all my Post using:

return response()->json(Post::all(), 200);

Have someone an idea why I get this additional attribute using belongsTo?

Upvotes: 0

Views: 592

Answers (2)

Nico Jaimico
Nico Jaimico

Reputation: 46

Your first approach was good, you just need to add 'user' into the $hidden array

<?php
namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
  protected $table = 'posts';
  protected $appends = ['author'];

  protected $fillable = [
    'title',
    'description'
  ];

  protected $hidden = [
    'user_id',
    'created_at',
    'updated_at',
    'user', // <-- add 'user' here
  ];

  public function user()
  {
    return $this->belongsTo('App\User', 'user_id');
  }

  public function getAuthorAttribute()
  {
    return $this->user->username;
  }
}

And your resulting model would be:

{
  "id": 2,
  "title": "Amazing Post",
  "description": "Nice post",
  "author": "FooBar"
}

Upvotes: 0

Amin Shojaei
Amin Shojaei

Reputation: 6518

  • This behavior is because of performance. When you call $post->user for first time, The Laravel read it from the database and keep it in $post->relation[] for next usage. So next time Laravel can read it from the array and prevent from executing a query again(it will be useful if you use it in multiple places).

  • Plus, the user is also an attribute and the Laravel merges $attributes and $relations array together when you call $model->toJson() or $model->toArray()

The Laravel's Model source code:

public function toArray()
{
    return array_merge($this->attributesToArray(), $this->relationsToArray());
}

public function jsonSerialize()
{
    return $this->toArray();
}

Upvotes: 1

Related Questions