Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel: Retrieve polymorphic attributes efficiently

My actual Question is: How to return attributes to the client which I actually need, throw the rest away and be as efficient as possible?

So, I have a construction where I basically have media objects, which can be of different types, like video, picture, link and so on. This is constructed in a polymorphic manner. In this example, users can have an avatar, which is in fact a media Object, but the only thing that I'm interested in on client side is actually the image url.

Our applicaiton is RESTful and if I want to list all users, I want the users JSON to basically return a property with only the url of the avatar and not the complete polymorphic model, which I then would have to traverse on client side, not to mention the huge overhead to send from the server to the client.

From User Model:

/**
 * A user can have an avatar
 *
 * @return \Illuminate\Database\Eloquent\Relations\MorphOne
 */
public function avatar() {
    return $this->morphOne('Fanlete\Media', 'appendable');
}

From Media Model:

/**
 * A media element can be extended in different ways (kind of inheritance), this function morphs to the specific implementation
 *
 * @return \Illuminate\Database\Eloquent\Relations\MorphTo
 */
public function mediable()
{
    return $this->morphTo();
}

/**
 * A media element can be appended by various models, i.e. a user to save its avatar
 *
 * @return \Illuminate\Database\Eloquent\Relations\MorphTo
 */
public function appendable() {
    return $this->morphTo();
}

From Picture Model, a concrete Media implementation:

public function media()
{
    return $this->morphMany('Fanlete\Media', 'mediable');
}

So, let's say I have a function to list all users in my controller:

public function index()
{
    return User::all();

}

Where I want an output like this:

{
    "id": 1,
    "email": "[email protected]",
    "name": "Example",
    "surname": "User",
    "created_at": "2015-06-17 07:05:26",
    "updated_at": "2015-06-17 07:05:26",
    "avatar": "http://upload.wikimedia.org/wikipedia/en/3/38/Avatarjakeneytiri.jpg"
}

At the moment I solve it like this, which feels actually not that elegant and somehow very wrong and bottlenecked:

User Model again:

protected $appends = ['avatar'];

public function getAvatarAttribute()
{
    return $this->avatar()->get()->first()->mediable()->get()->first()->url;
}

Of course I could also do something like this in my controller:

$users = Users::with("avatar.mediable")->get()->all();
// Now sort out attributes I don't need and return array

and without sortin the output would look like this (very messy and huge overhead):

{

    "id": 1,
    "email": "[email protected]",
    "name": "Example",
    "surname": "User",
    "created_at": "2015-06-17 07:05:26",
    "updated_at": "2015-06-17 07:05:26",
    "avatar": 

    {
        "id": 6,
        "appendable_id": 1,
        "appendable_type": "Fanlete\\User",
        "mediable_id": 6,
        "mediable_type": "Fanlete\\Picture",
        "created_at": "2015-06-17 07:05:26",
        "updated_at": "2015-06-17 07:05:26",
        "mediable": 

                {
                    "id": 6,
                    "url": "http://upload.wikimedia.org/wikipedia/en/3/38/Avatarjakeneytiri.jpg",
                    "title": null,
                    "created_at": "2015-06-17 07:05:26",
                    "updated_at": "2015-06-17 07:05:26"
                }
            }
}

But this would also feel like a workaround and is exactly that overhead I do not need and want.

thx

like image 690
Thinklabs Avatar asked Oct 17 '25 18:10

Thinklabs


1 Answers

You might be almost there.

Your getAvatarAttribute() is way too complicated, indeed. You could try to do something like:

/** we hide the relation from the json */
protected $hidden = ['avatar'];

/** we append the url of the avatar to the json */
protected $appends = ['avatar_url'];

/**
 * To not be confused with the avatar() method, I used avatar_url.
 */
public function getAvatarUrlAttribute()
{
    return $this->avatar->mediable->url;
}

And then, you should be able to do $users = User::with('avatar.mediable')->get(); without the bottleneck, and have a nice JSON response.

like image 122
Thomas Ruiz Avatar answered Oct 20 '25 13:10

Thomas Ruiz