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.
Step 3: Use Eloquent for fetching related data
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 thewebsites
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 theWebsite
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:
- Set up foreign keys in the Migration file,
- Add helper methods to the appropriate Models to provide context to Eloquent.
- 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