Laravel – polymorphic relations and model observers

Laravel has a very nice feature which is polymorphic relations. This is very useful, when you have a model in your application that is related to many other models.

Let’s say we have an application using the following classes:

    class Document extends Eloquent {
        .... some stuff...
    }

    class User extends Eloquent {
        .... some user’s stuff...
    }

    class Case extends Eloquent {
        .... some stuff...
    }

Let’s say User and Case can both have multiple documents uploaded against them. Now, if we want to maintain relations between Document and User/Case or more models – each additional model requires change to the database (to add foreign key). Now that’s good, but becomes difficult to maintain we want to scale up the application and add more models that will have multiple documents.

With polymorphic relations all you need is to add two fields into the table representing documents:

–   documentable_id – used for storing the id of Case or User

–   documentable_type – determines which table documentable_id relates to.

No we have to define polymorphic relation in the code:

    class Case extends Eloquent {
        public function documents() {
            return $this->morphMany('Document', 'documentable');
        }
    }

    class User extends Eloquent {
        public function documents() {
            return $this->morphMany('Document', 'documentable');
        }
    }

That’s it. Now when you introducing a new model that will have documents attached all you need to do is add this documents() function.

A small downside is that there is no relation defined at the database level. So when deleting Case the Document object will stay in the database. That’s where I’m using model observers. Let’s modify the Case and User classes a bit and create a Generic class that Case and User will inherit from.

    class Case extends Generic {
        ... some stuff...
    }

    class User extends Generic {
        ... some stuff...
    }

    abstract class Generic extends Eloquent {
        public function documents() {
            return $this->morphMany('Document', 'documentable');
        }
        public static function boot() {
            parent::boot();
            self::observe(new GenericObserver);
        }
    }

Now, let’s define the GenericObserver

    class GenericObserver {
        public function deleting ($model) {
            if ($model->documents) {
                foreach ($model->documents as $document) {
                    $document->delete();
                }
            }
        }
    }

Now, whenever a parent object is deleted all its documents will be deleted too. Going even further we could take care of the files uploaded using observers too. Let’s modify Document class:

    class Document extends Eloquent {
        public static function boot() {
            parent::boot();
            self::observe(new DocumentObserver);
        }
    }

    class DocumentObserver {
        public function deleting ($model) {
            if (file_exists(DOCUMENTS_PATH . $model->filename) {
                unlink(DOCUMENTS_PATH . $model->filename);    
            }
        }
    }

Now, whenever a User or Case object is deleted, the related Document object will be deleted too also the uploaded file will be removed.