t0ne's avatar
Level 4

pattern for media library

I need to refactor my media controller:

function upload(){
    $this->store($uploaded_file);
}
function store($file){
    switch($request->get('media_type')){
                case "image":
            //create image collection for database
        case "file":
            //create file collection for database
        case "video":
            //create video collection for database
    }

    $media->{media_type}->create($collection);
}
0 likes
12 replies
jekinney's avatar

Check out polymorphic relationships in the docs. Might give you some ideas.

1 like
t0ne's avatar
Level 4

@jekinney Thanks for your advice, i tried this before but it didn't make much sense at the time. I try it again. Media can morph into Image object, File Object, Video Object.

This is what i have so far: (thanks to @martinbean and @pmall)

class Media extends Model
{
    protected $fillable = [
        'title',
        'description',
        'path',
        'thumbnail_path',
        'etc...'
    ];
    
    public function mediable(){
        return $this->morphTo('mediable');
    }
}

class Image extends Model
{
    protected $fillable = [
        'height',
        'width',
        'orientation',
        'alt',
        'ratio'
    ];

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

class File extends Model
{
    protected $fillable = [
        'orientation',
        'application',
        'etc..'
    ];

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

Now i can retrieve a collection with Media Objects which have a 'mediable' relationship which contain a 'Image' or 'Video' or 'File' Object.

So far so good (?)

First question:

$media->mediable morphs into Image,File or Video,
how to determine which object $media->mediable is?
Now i use switch statement, is this the right way to use this pattern?
    //get a mediaList with all Images, Videos and Files 
    $mediaList = Media::with('mediable')->get();
    foreach ($mediaList as $media){
            dc($media->mediable);
            
            //get media thumbnail
            //all mediable object share 'thumbnail' property (Media->thumbnail_path)
            dc($media->thumbnail_path);
            
            //display specific mediable object properties
            //example: 
            //  - 'Video' has 'length' property
            //  - 'Image' has 'ratio' property
            switch (class_basename($medium->mediable_type)) {
                case 'Image':
                    dc($media->mediable->ratio);
                    break;
                case 'File':
                    
                    break;
                case 'Video':
                    dc($media->mediable->length);
                    break;
            } 
            
            //view blade
            include('media.'.class_basename($medium->mediable_type).'',[$media=>$media->mediable])
    }
pmall's avatar

Yes of course you have to check the mediable type. I use instanceof operator.

1 like
t0ne's avatar
Level 4

@pmall thanks for your reply, now i know i'm on the right track.

Another question: Instead of creating list of all mediable types as above, i need a list of a specific type: how should i do this? This is what i have so far:

get all Videos: option 1

//controller
    //polymorph relation
    $videoList = Video::with(['media'])->get();

    foreach ($videoList as $video) {
        //don't like this:
        dc($video->media);
    }

or

option 2

//model
class Media extends Model{

    public function video()
    {
        return $this->belongsTo('App\Video', 'mediable_id');
    }

}

//controller:
    //belongsTo relation
    $mediaVideoList = Media::with(['video'])->get();
    
    foreach ($mediaVideoList as $media) {
        //makes more sense to me:
        dc($media->video);
    }

or ???

t0ne's avatar
Level 4

When trying to update mediable types, i found one of the benefits of polymorphic relations i guess, am i right?

class MediaController extends Controller{
    
    //one method which can update all the mediable types,
    public function update(Request $request, $id){
        $media = Media::find($id);
        $media->update($request->all());
        
        //updates mediable types (Image, Video etc)
        $media->mediable()->update($request->all());    //!!!!  
    }

}
pmall's avatar

The first option is the way to go ($video->media). If you dont like it, its just a matter of table/model name.

For your second question, what are you trying to do with this ?! $media->mediable()->update($request->all()); ?

Why are you even trying to put a media table in the first place? Why all this?

1 like
t0ne's avatar
Level 4

thanks again! why all this? good question, i try all this to learn about polymorphic... maybe not the best use case.

In this case i have my MediaController with an update() method which contains: "$media->mediable()->update($request->all());"

When update my Video or Image or File object, i can sent this request in all of these cases to MediaController->update().

This won't work inserting a Video,Image or File object, in this case need something like Video->media()->store().

And so i ask myself why am i doing all this. I already have it up and running with regular relations.

That's why this post is called "best pattern for media library", is polymorphic the right solution for this case?

Or am i totally missing the point?

pmall's avatar

It would be a big mess to try to put all the mediable in one controller (for example doesnt the validation rules change between video and image ?) you'd better stick with one controller per mediable type.

The only point of polymorphic here is you can get only one big list of media objects attached to one object instead of many relationship for each type.

1 like
t0ne's avatar
Level 4

ah oke, it's mess indeed.

Just to be sure: every mediable type has it's own controller for updating and storing etc.

And if i really wanted to: in the MediaController i can make method which returns "one big list of media objects" only for this reason i have make use of polymorphic relation?

So it's oke to do it without polymorphic relations?

pmall's avatar
pmall
Best Answer
Level 56

Polymorphic :

$some_model->media // get all the media

Non polymorphic :

$some_model->videos;
$some_model->images;
$some_model->files;

Thats the big difference.

You can share basic crud controllers for many models but it involve injecting interfaces for models / form request and bind them conditionally according to the url. I made post about it here maybe you can try to find it.

1 like

Please or to participate in this conversation.