The problem we’re solving

I’m building a small SaaS project with Laravel. My project is a website up-time monitoring platform that pings users’ websites every 5 minutes to check if they’re live. Suppose a website is unreachable for any reason: In that case, a notification will then be sent to the account owner so they can get their website back online as soon as possible.

The above is relevant for context towards the example I’ll provide today. Here are the names of 3 of the tables used for this project:

  • users – Stores user account information.
  • websites – Stores the URLs of the websites that are to be monitored.
  • website_relationships – Connects user account records to website records.

In this post, I will be explaining how to correctly link the data contained in these 3 tables in Laravel by use of foreign keys.

What are Foreign keys?

‘A FOREIGN KEY is a field (or collection of fields) in one table, that refers to the PRIMARY KEY in another table.’ – W3 Schools

All 3 tables that I’ve mentioned have a field called id set as their primary key. A primary key is a unique identifier field that is different for each record on the same table. The primary keys used in my example are all auto-incrementing integers. This means the first record added will automatically be assigned the id of 1, then the second will automatically get assigned the id of 2, and so on.

If I want to link records from my users table to records from my website_relationships table, I would create a field on the website_relationships table called user_id. I would then use this field to store the id of the user with which this record should be associated.

By using foreign keys within our SQL database, we can ensure that all records that have related data in other tables are married up correctly. Additionally, we can utilise cascading so that if, for example, a user removes their account, all of their website_relationships are automatically removed too. Finally, we’ll also be able to utilise Eloquent ORM’s built-in functionality to handle fetching and handling related records in a way that’s fast and easy.

Step 1: Create the database table migrations

To create a new database migration, you can use Laravels Artisan command:

php artisan make:migration website_relationships

(Where website_relationships is the name of the table you’d like to create)

This will create a new file which is used to define the structure of the table you want. Here is the website_relationships migration file from my project which is used to connect user accounts to websites on the database:

<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('website_relations', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->foreignId('website_id')->constrained()->onDelete('cascade');
        });
    }

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

As you can see, in the up function, a new table is being created with 3 fields:

  • id (Primary key)
  • user_id (Foreign key)
  • website_id (Foreign key)

To link the ID from the users and websites tables, I’m using the foreignId method. This is a shorthand way to link IDs between tables. To make it clearer how this works, here’s a more verbose example that does the same thing:

$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');

As you can see, this creates a new field called user_id which references the id field on the users table. It then sets it so that if the record it links to is deleted, any records relating to it are then deleted in cascade.

By following this example, you should now be able to link your tables together by utilising foreign keys.

Step 2: Create helper methods in the Models

Now that we have our tables set up correctly with the foreign keys connected, it’s time to show our Models these same connections so they can be utilised as simply and efficiently as possible via Eloquent.

If you haven’t done so already, first create your model class using Laravels Artisan command:

php artisan make:model WebsiteRelation

(Where WebsiteRelation is the desired model name)

After generating the model, here’s how mine looked after I’d made the required changes to accommodate the table relationships.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Model;

class WebsiteRelation extends Model
{
    //... Rest of the Models code

    /**
     * Get the User associated with the WebsiteRelation.
     * 
     * @return BelongsTo
     */
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    /**
     * Get the Website associated with the WebsiteRelation.
     *
     * @return BelongsTo
     */
    public function website(): BelongsTo
    {
        return $this->belongsTo(Website::class);
    }
}

In the above example you can see that I’ve added two methods: user and website. Both of these methods call the belongsTo method, passing the class of appropriate models for each. As each website relationship will link only to a single user record and a single website record each, the BelongsTo method is appropriate for both.

You also need to set up the opposite. Here we’re specifying on the Website model that it can have many associated WebsiteRelations.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Model;

class Website extends Model
{
    //... Rest of the Models code

    /**
     * Get all the WebsiteRelations associated with the Website.
     *
     * @return HasMany
     */
    public function websiteRelations(): HasMany
    {
        return $this->hasMany(WebsiteRelation::class);
    }
}

HasMany is similar to BelongsTo in that it helps Eloquent understand the relationships between the different tables. However, HasMany allows for multiple records from the ‘other’ table to link to a single record on this one. BelongsTo is the reverse and allows for the current record to link to only a single record on the ‘other’ table.

At this stage, you should now have your database tables set up, and your model classes for both sides of the relationship should be set up with either the hasMany or belongsTo methods so that the models are prepared to handle the relationships too.

Now that everything’s set up, it’s time to take it for a spin. Here’s a new method I’ve added to my WebsiteRelations model:

/**
 * Gets all Websites belonging to the given user ID
 * 
 * @param int $userID
 * @return Collection
 */
public static function getWebsitesForUser(int $userId): Collection
{
    return self::with('website')
        ->where('user_id', $userId)
        ->get()
        ->map(function ($relation) {
            return $relation->website;
        });
}

This new method uses very minimal logic and is really easy to read and understand. Here’s a quick breakdown:

  • Line 9: Fetch entries from this model’s table (website_relationships), with their related entries from the websites table.
  • Line 10: Where the user_id matches the given id from the method’s arguments.
  • Line 11: Get the Collection instance for this query – Laravel Collections are like really useful arrays used for storing data.
  • Lines 12-14: Using the collections map method, we iterate over each record, replacing the result with only the Website instance belonging to it.

If you’re not comfortable with Eloquent, this might seem a little confusing. However, with no complex logic or if...else faff, we’ve been able to fetch related records spanning multiple tables (user_id to fetch website_relationships to return only the websites records). All of this is achieved thanks to setting up the database table structures in line with best practices and following Laravels documentation.

Now I can get all websites belonging to a user at any time with only the following code:

$myWebsites = WebsiteRelation::getWebsitesForUser(1);

(Where 1 is the id of the user I want the websites for)

Summary

Linking data via foreign keys for use in Laravel with Eloquent ORM is quite simple:

  1. Set up foreign keys in the Migration file,
  2. Add helper methods to the appropriate Models to provide context to Eloquent.
  3. Fetch the data as required using Eloquent’s built-in functions, as it will automatically know how to handle the relationships.

If anything isn’t quite clear on this post, let me know and I’ll be happy to revise it where needed. This was written in line with Laravel 10’s documentation with PHP 8.2. While previous/future versions of either of these may work without edits, please consult the appropriate documentation if you’re unsure.

Leave a Reply

Your email address will not be published. Required fields are marked *