Eloquent Relationships
Real applications are never just one isolated table — articles belong to authors, orders have many line items, users have one profile, and so on. Eloquent lets you describe these connections directly on your model classes, as ordinary PHP methods, and then work with related data almost as naturally as accessing a property.
The Main Relationship Types
| Relationship | Meaning | Example |
|---|---|---|
hasOne | This model owns exactly one of another model. | A User has one Profile. |
hasMany | This model owns many of another model. | An Author has many Article records. |
belongsTo | The inverse side — this model is owned by one of another model. | An Article belongs to one Author. |
belongsToMany | A many-to-many relationship, using a pivot table in between. | An Article has many Tag records, and a Tag is used on many articles. |
A Full Worked Example: Author and Article
An author writes many articles, and each article belongs to exactly one author — the classic one-to-many relationship. Both sides are defined as methods, one on each model:
<?php
// app/Models/Author.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Author extends Model
{
public function articles()
{
return $this->hasMany(Article::class);
}
}
<?php
// app/Models/Article.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
public function author()
{
return $this->belongsTo(Author::class);
}
}
Eloquent figures out the foreign key by convention too — it expects an author_id
column on the articles table, matching the singular, snake_case name of the
Author model plus _id. Like every other convention covered so far, this
can be overridden by passing extra arguments to hasMany()/belongsTo(), but
the default almost always fits naturally.
Accessing a Relationship Like a Property
Even though articles() and author() are defined as methods, once they
exist you almost never call them with parentheses directly — instead you access them as if they
were plain properties:
<?php
$author = Author::find(1);
foreach ($author->articles as $article) {
echo $article->title;
}
This isn't a coincidence or special syntax — it's ordinary PHP "magic" at work. Eloquent's base
Model class implements the __get() magic method, and when you access
$author->articles, PHP can't find a real articles property, so it falls
through to __get(), which notices there's an articles() method, calls it
for you, executes the query, and caches the result on the model. The second time you access
$author->articles in the same request, no extra query runs — it reuses the cached
result.
Eager Loading: Avoiding the N+1 Query Problem
That caching only helps within a single model instance. The real danger shows up when you loop over a collection of models and access a relationship inside the loop — this is one of the most common performance mistakes in any ORM, well known enough to have its own name: the N+1 query problem.
<?php
$authors = Author::all(); // 1 query, fetches all authors
foreach ($authors as $author) {
echo $author->articles->count(); // +1 query, EVERY iteration
}
With 1 author, this runs 2 queries — not a problem. With 200 authors, this runs 201 queries: one to
fetch the authors, then one more per author to fetch their articles, all firing separately
against the database. That's the "N+1": one query, plus N more, one for every row in the first
result.
The fix is eager loading with with(), which tells Eloquent up front which relationships you'll need, so it can fetch them all in one extra query instead of one-per-row:
<?php
$authors = Author::with('articles')->get(); // 2 queries total, no matter how many authors
foreach ($authors as $author) {
echo $author->articles->count(); // already loaded, no extra query
}
With with('articles'), Eloquent runs one query to get every author, then a second
query that fetches every article belonging to any of those authors in a single
WHERE author_id IN (...) call, and stitches the results back together in PHP. The
total stays at 2 queries whether you have 5 authors or 5,000 — a massive win as your data grows.
belongsToMany and Pivot Tables
A many-to-many relationship — articles having many tags, and tags being reused across many articles — can't be represented with a single foreign key on either table, since both sides can have many matches. It needs a third table in between, called a pivot table, whose job is just to record which article goes with which tag.
<?php
// app/Models/Article.php
public function tags()
{
return $this->belongsToMany(Tag::class);
}
By convention, Eloquent expects the pivot table to be named after both model names, singular,
alphabetically ordered, joined with an underscore — so Article and Tag
gives you a pivot table named article_tag, with just two foreign key columns:
article_id and tag_id.
If the pivot table itself carries extra data (a timestamp of when the tag was attached, for
example), Eloquent exposes it through a special pivot property available on each
related model:
<?php
$article = Article::find(1);
echo $article->tags->first()->pivot->created_at;