Laravel for Beginners: Model Relationships

Arlind Musliu Portrait
Arlind Musliu

January 4, 2024 · 5 min read

Laravel Blogpost Image

Embarking on a Laravel adventure means you're going to deal with data, and not just any data, but data that are connected in all sorts of complex ways. That's where model relationships come in, linking everything together as the glue of your application's data.

Defining Relationships in Laravel

Laravel supports a variety of relationship types, allowing you to express the connections between your data models clearly. Here's a brief overview of each type:

  • One To One: This is a straightforward relationship where one record from a table corresponds to one record in another table. For instance, each user might have one unique profile.

  • One To Many: A common relationship where one record can be associated with multiple records in another table. For example, a user can have many posts.

  • Many To One: The inverse of one-to-many, where many records in one table relate to a single record in another table.

  • Many To Many: This relationship allows multiple records in one table to be associated with multiple records in another table. A user might subscribe to multiple magazines, and each magazine might have multiple subscribers.

  • Polymorphic Relationships: These allow a model to belong to more than one other model on a single association. An image might be associated with either a post or a user profile.

  • Many To Many Polymorphic Relationships: An extension of polymorphic relationships where a model can have multiple relations to multiple models.

  • Has One Through and Has Many Through: These relationships allow you to access distant relations via an intermediate relation. For example, a country might have many posts through a user model.

Let's Build an App with Laravel

Creating a blog with Laravel is a good example of beginning your journey into the world of web development. As your blog grows, so does the complexity of the relationships between your data. Understanding how to define and use these relationships in Laravel is key to building a feature-rich and efficient blogging platform.

Note: Do not worry about coding for now, we will start coding in the next lesson. Focus on understanding these concepts as they will help you in your Laravel journey.

The Blueprint of Our App

Let's define the main features of our blog:

  • Users: The authors who write blog posts.

  • Profiles: The profiles of the users.

  • Posts: The articles or blog entries published by users.

  • Tags: Keywords or topics that categorize posts.

  • Images: Photos or graphics associated with posts or users.

Now, let's explore how these entities relate to each other in a typical blog.

Creating the Relationships

One To One: User and Profile

Each user on our blog might have a detailed profile. This is a one-to-one relationship.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Authenticatable
{
    public function profile()
    {
        return $this->hasOne(Profile::class);
    }
}

For the one-to-one relationship between a User and a Profile, the inverse would allow us to find the user from the profile.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Profile extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

One To Many: User and Posts

A user can write many blog posts, but each post is written by one user. This is a one-to-many relationship.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Authenticatable
{
	public function profile()
    {
        return $this->hasOne(Profile::class);
    }
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

The inverse of the one-to-many relationship between User and Posts would allow us to find the author of a specific post.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

Many To Many: Posts and Tags

A post can be tagged with multiple keywords, and each tag can be applied to multiple posts. This is a many-to-many relationship.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

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

For the many-to-many relationship between Posts and Tags, the inverse relationship would allow us to find all posts associated with a particular tag.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

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

Polymorphic: Images

An image might be associated with a user profile or a blog post. This is a polymorphic relationship.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Image extends Model
{
	protected $fillable = ['path'];

    public function imageable()
    {
        return $this->morphTo();
    }
}

The inverse relationship would have to be implemented on the Post and Profile model as well.

Let's start with the Post:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
	public function tags()
    {
        return $this->belongsToMany(Tag::class);
    }
    public function images()
    {
        return $this->morphMany(Image::class, 'imageable');
    }
}

We do the same for the Profile:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Profile extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
    public function images()
    {
        return $this->morphMany(Image::class, 'imageable');
    }
}

Using Relationships in Your App

To cover the different relationships in our blog application, we'll create several controllers, each with a clear purpose. This approach follows the Single Responsibility Principle and makes our application easier to maintain and understand.

UserController

The UserController handles operations related to users, such as displaying a user's profile and posts.

<?php

namespace App\Http\Controllers;

use App\Models\User;

class UserController extends Controller
{
	// Display all users
 	public function index()
    {
        $users = User::all();
        return view('users.index', ['users' => $users]);
    }

    // Display a user and their profile
    public function show($id)
    {
        $user = User::with('profile')->findOrFail($id);
        return view('users.show', ['user' => $user]);
    }
}

PostController

The PostController is concerned with blog post operations, including listing, creating, editing, and deleting posts.

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use App\Models\Tag;
use Illuminate\Http\Request;

class PostController extends Controller
{
    // Display all blog posts
    public function index()
    {
        $posts = Post::with('user', 'tags')->latest()->paginate();

        return view('posts.index', ['posts' => $posts]);
    }

    // Show a form for creating a new blog post
    public function create()
    {
        return view('posts.create');
    }

    // Store a newly created blog post
    public function store(Request $request)
    {
        $validatedData = $request->validate([
            'title' => 'required|max:255',
            'content' => 'required',
            // other validation rules...
        ]);

		$tags = $request->validate([
            'tags' => 'required',
        ]);

		// the new post belongs to the authenticated user
        $post = auth()->user()->posts()->create($validatedData);

		// we add tags to the post by using the sync() method
        $post->tags()->sync($tags);

        return redirect()->route('posts.show', $post);
    }

    // Display a single blog post
    public function show($id)
    {
        $post = Post::with('user', 'tags', 'images')
							->findOrFail($id);

        return view('posts.show', ['post' => $post]);
    }

    // Show a form for editing the specified blog post
    public function edit($id)
    {
        $post = Post::findOrFail($id);
        $tags = Tag::all();
        return view('posts.edit', ['post' => $post, 'tags' => $tags]);
    }

    // Update the specified blog post
    public function update(Request $request, Post $post)
    {
        $validatedData = $request->validate([
            'title' => 'required|max:255',
            'content' => 'required',
            // other validation rules...
        ]);

		$tags = $request->validate([
            'tags' => 'required',
        ]);

        $post->update($validatedData);

		// we modify the tags of the post by using the sync() method
        $post->tags()->sync($tags);

        return redirect()->route('posts.show', $post);
    }

    // Remove the specified blog post
    public function destroy(Post $post)
    {
        $post->delete();

        return redirect()->route('posts.index');
    }
}

TagController

The TagController handles tag-related actions, such as viewing all posts associated with a tag.

<?php

namespace App\Http\Controllers;

use App\Models\Tag;

class TagController extends Controller
{
    // Display all posts for a specific tag
    public function show($id)
    {
        $tag = Tag::with('posts')->findOrFail($id);

        return view('tags.show', ['tag' => $tag, 'posts' => $tag->posts]);
    }
}

Uploading Images (Polymorphic relationship)

Here's an example of how you might handle image uploads in a PostController. This is a simplified example, and in a real-world scenario, you would likely have more complex validation, storage, and error handling.

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use App\Models\Image;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

class PostController extends Controller
{
    // ... (other methods)

    // Store a newly created blog post with an image
    public function store(Request $request)
    {
        $validatedData = $request->validate([
            'title' => 'required|max:255',
            'content' => 'required',
            'image' => 'nullable|image|max:2048', // 2MB Max
        ]);

		// we already showed how you can create a user post
        $post = auth()->user()->posts()->create($validatedData);

		// now let's add our image to the post we wrote
        if ($request->hasFile('image')) {
            $path = $request->file('image')->store('images', 'public');
            $post->images()->create(['path' => $path]);
        }

        return redirect()->route('posts.show', $post);
    }

    // ... (other methods)
}

In this store method, we're validating the incoming request, including an image file. If an image is included, we use Laravel's store method, which saves the file in the images directory within the public disk (public storage). We then create a new Image record associated with the post using the create method on the images relationship.

The 'public' parameter in the store method specifies the disk defined in config/filesystems.php, where you can set up different filesystems. The 'images' is a directory within that disk.

Remember to include enctype="multipart/form-data" in your <form> tag to allow file uploads.

Conclusion

Model relationships in Laravel provide a powerful way to represent and manage the interconnected data of a blog. By defining clear relationships between users, posts, tags, and images, you can write code that's both efficient and expressive. Laravel's Eloquent ORM makes it easy to handle these relationships, allowing you to focus on crafting the perfect blogging experience.

Upcoming Articles in the Series

  1. Laravel for Beginners: Laravel Migrations

  2. Laravel for Beginners: Laravel Seeders and Factories

  3. Laravel for Beginners: Routing Your Application


Bring Your Ideas to Life 🚀

If you need help with a Laravel project let's get in touch.

Lucky Media is proud to be recognized as a Top Laravel Development Agency

Arlind Musliu Portrait
Arlind Musliu

Cofounder and CFO of Lucky Media

Technologies:

Laravel
Heading Pattern

Related Posts

Stay up to date

Be updated with all news, products and tips we share!