Laravel morphing - Multi Relational Links

 Its hard to describe in a handful of words what morphing in Laravel is, especially when its also know by the definition of polymorphic relationships.

What is a polymorphic relationship?

A polymorphic relationship is one model with a linking field that can link to multiple other models.

Do I need one?

Simple answer is no. What it does do, is allow lazy relationship linking instead of creating specific relationships for every relation. This is especially handy where you might be expanding a database but are currently unaware of what will be added.

Visualised example

Lets visualise a particular database setup now and from this, should be able to see why polymorphic relationships are handy.

Initial models setup:

  • Contact
  • Company
  • Project
  • Sale
  • User


Lets now introduce a Documents model.

The requirement is simple, documents need to be added to companies, projects and sales.


So the optimal route here is to add two fields to documents:
  • one to determine the relationship (type)
  • one to link to the relationship record (id)

Setting up a polymorphic relationship

Database

Lets start from the start. All relationships essentially boil down to the database and a relational database at that (such as MySQL). I am not going to go into too specifics on database setup and language as this is beyond the scope of this post.

So we need the following:

  • A migration
  • A way to access the morph data
Lets setup a model and a migration for our new Document model:

php artisan make:model Document -m

The "-m" simply tells artisan to make a migration as well as the model. You can add "c" and "r" as well to include a controller and a resource if you wish (-mcr) 

Now we have our migration file ready to fill in.

Lets jump in and add a couple of fields to this as well.

class CreateDocumentsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('documents', function (Blueprint $table) {
            $table->id();
            $table->string('title', 160);
            $table->text('description');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('documents');
    }
}

Now lets add our morph field

    public function up()
    {
        Schema::create('documents', function (Blueprint $table) {
            $table->id();
            $table->string('title', 160);
            $table->text('description');
            $table->morphs('documentable');
            $table->timestamps();
        });
    }

Note we have only provided one additional line of code. What this line does is this:

  • Create field 'documentable_type' varchar(255)
  • Create field 'documentable_id' bigint unsigned

Models

So now we have the database all setup, we can setup the models relationships.

On the document model itself, its really simple:

class Document extends Model
{
    /**
     * Get the owning documentable model.
     */
    public function documentable()
    {
        return $this->morphTo();
    }
}

This simply declares that it has links to other models via the documentable fields.

On the other models that we want to link now, the relationships are as follows:

class Company extends Model
{
    /**
     * Get one document if expecting only one.
     */
    public function document()
    {
        return $this->morphOne(Document::class, 'documentable');
    }

    /**
     * Get multiple documents.
     */
    public function documents()
    {
        return $this->morphMany(Document::class, 'documentable');
    }
}

The exact same methods can be setup as required on any future model that may require documents and no additional database work is required!

In my opinion, morphing relationships is very powerful and very useful to save time and future effort. However, if the system you are developing has any chance of massive numbers or large database queries because of this, you may want to continue linking fields completely seperately and modifying the database to fit the need.

Comments