Relationships — Laravel 10 Discovery Training

In this new chapter where we will come back a little on the models and we will talk about the relationships and how to represent them with the ORM Eloquent. To take the example of a blog on which we have articles, we will imagine setting up a category system (1-n relationship) and tags (nn relationship).

1-n relationship, belongsTo

In this type of relationship we want to be able to attach a category to an article and this is done through a foreign key category_id on our table posts. We will start by creating the model and then the migration to manage this new relationship.

php artisan make:model Category -m
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->timestamps();
        });
        Schema::table('posts', function (Blueprint $table) {
            $table->foreignIdFor(\App\Models\Category::class)->nullable()->constrained()->cascadeOnDelete();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('categories');
        Schema::table('posts', function (Blueprint $table) {
            $table->dropForeignIdFor(\App\Models\Category::class);
        });
    }
};

Note especially the use of the method foreignIdFor() which automatically names the foreign key and simplifies the overly verbose column declaration.

$table->foreignIdFor(\App\Models\Category::class);
// Equivalent à
$table->unsignedBigInteger('category_id');
$table->foreign('category_id')->references('id')->on('categories');

Then, at the level of our models, we can add the definition of the relation thanks to a method bearing the name of the relation.

use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Post extends Model
{
    public function category(): BelongsTo {
        return $this->belongsTo(Category::class);
    }
}

We can also define the inverse relation on the side of the categories.

use Illuminate\Database\Eloquent\Relations\HasMany;

class Category extends Model
{

    public function posts(): HasMany {
        return $this->hasMany(Post::class);
    }
}

These methods will give us the ability to interact with Linked Data more easily.

  • Accessing the property gives us the results of the relationship $post->category->name Or $category->posts[0]->title
  • Access to the method returns the relation that we can use to build a request or perform certain actions $category->posts()->where('online', 1)->get() Or $post->category()->create(['name' => 'Catégorie de cet article'])

Eager loading and n+1 problem

Also by default Laravel only retrieves a relationship when requested. For example if you collect 10 items (Post::limit(10)->get()) it will not retrieve related categories. If you then loop and display the associated category you will have 11 SQL queries. To avoid this problem you can do somer-loading by preloading the relations upstream with the method with().

$posts = Post::with('category')->limit(10)->get()

In this case, Laravel will only make 2 requests, a first to retrieve the list of articles and a second to retrieve the list of associated categories based on the ids of the articles.

Relationship nn, belongsToMany

This type of relation is more complex because requires the creation of an intermediate table and one must work on this table of connection to define the relation. As for the previous approach, we will create the models then the corresponding migration.

php artisan make:model Tag -m

For the migration we will reuse foreignIdFor. The link table will take the name of the 2 elements to be linked in the singular and organized alphabetically.


use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('tags', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->timestamps();
        });
        Schema::create('post_tag', function (Blueprint $table) {
            $table->foreignIdFor(\App\Models\Post::class)->constrained()->cascadeOnDelete();
            $table->foreignIdFor(\App\Models\Tag::class)->constrained()->cascadeOnDelete();
            $table->primary(['post_id', 'tag_id']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('post_tag');
        Schema::dropIfExists('tags');
    }
};

Then at the level of our models we can define the relationship.

<?php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Post extends Model
{
    public function tags (): BelongsToMany {
        return $this->belongsToMany(Tag::class);
    }
}

As before, we can then retrieve the related information through the property of the same name. On the other hand, we will have methods on the relationship to be able to quickly attach or detach elements at the level of our relationship.

$post->tags()->attach($tagId);
$post->tags()->detach($tagId);
$post->tags()->detach(); // Retire la liaison pour tous les tags de l'article
$user->roles()->sync([1, 2, 3]); // Synchronise la relation avec les ids, en supprimant et ajoutant les relation quand nécessaire

It is also possible to record information on the pivot table but I refer you in this case to the documentation for more information.