It's time to introduce a second DB table and a relationship. After working with Categories, it's time to handle Posts.
We will create an Eloquent Model for posts and define a relationship. Every post should belong to one of the categories. And lastly, on the page, we will build a link to show posts by the selected category.
Creating Model and Migration
First, let's create a Model and Migration. We can do it with two Artisan commands or one command, make:model
, and provide a -m
flag, which means migration.
php artisan make:model Post -m
Now, we can fill the migration. In addition to regular string and text columns, we need one for the category relation.
Laravel has a syntax to create foreign keys. There are a few options, but I prefer the foreignId()
method with constrained()
. It will create both the DB column and the foreign key.
The name of the relation column should have a format of "xxxxx_id", where "xxxxx" is a singular form of the relations table. And then, you define a constrained()
, which is a shorter Laravel method for ->references('id')->on('categories')
.
Finally, onDelete('cascade')
defines that if the user wants to delete the category that contains posts, all posts will be automatically deleted, too. If you don't specify this onDelete('cascade')
value, the default behavior is to restrict the deletion and throw an error when the user wants to delete the category (which may be exactly what you want, to protect data).
database/migrations/xxx_create_posts_table.php:
public function up(): void{ Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->text('text'); $table->foreignId('category_id')->constrained()->onDelete('cascade'); $table->timestamps(); });}
We can run the migrations.
php artisan migrate
I have added posts manually and assigned each post to a different category. For now, I did it manually because we are talking about getting the data. Later, we will discuss how to manage the posts/categories.
Getting Posts in Controller
We need to get posts in the same HomeController
alongside categories. We can use the all()
method on a Post Model or get the latest posts first.
app/Http/Controllers/HomeController.php:
use App\Models\Post; class HomeController extends Controller{ public function index() { $categories = Category::all(); $posts = Post::orderBy('id', 'desc')->get(); return view('home', ['categories' => $categories, 'posts' => $posts]); }}
Alternative syntax for the orderBy()
: we can use an Eloquent method called latest()
.
app/Http/Controllers/HomeController.php:
use App\Models\Post; class HomeController extends Controller{ public function index() { $categories = Category::all(); $posts = Post::orderBy('id', 'desc')->get(); $posts = Post::latest()->get(); return view('home', ['categories' => $categories, 'posts' => $posts]); }}
We need to pass those posts to a View. We could add posts to the same array as in the example above.
However, there is a shorter way to use the PHP function compact()
, just listing the variables one by one. This function can be used if the names of the variables are the same as array keys.
app/Http/Controllers/HomeController.php:
class HomeController extends Controller{ public function index() { $categories = Category::all(); $posts = Post::latest()->get(); return view('home', ['categories' => $categories, 'posts' => $posts]); return view('home', compact('categories', 'posts')); }}
Show Posts in Blade
Now, let's show the posts in Blade.
resources/views/home.blade.php:
<!-- Blog Posts Section --><section class="w-3/4 bg-white p-6 shadow-md rounded-lg"> <h2 class="text-xl font-semibold mb-4">Latest Posts</h2> <div class="space-y-6"> <article class="flex gap-4 border-b pb-4"> <img src="{{ asset('images/placeholder-150x150.png') }}" alt="Post Image" class="w-32 h-32 object-cover rounded"> <div> <h3 class="text-lg font-semibold"><a href="#" class="hover:underline">Blog Post Title</a></h3> <p class="text-gray-600">A short description of the blog post goes here...</p> </div> </article> <article class="flex gap-4 border-b pb-4"> <img src="{{ asset('images/placeholder-150x150.png') }}" alt="Post Image" class="w-32 h-32 object-cover rounded"> <div> <h3 class="text-lg font-semibold"><a href="#" class="hover:underline">Another Blog Post</a></h3> <p class="text-gray-600">Another short description of a blog post...</p> </div> </article> @foreach($posts as $post) <article class="flex gap-4 border-b pb-4"> <img src="{{ asset('images/placeholder-150x150.png') }}" alt="Post Image" class="w-32 h-32 object-cover rounded"> <div> <h3 class="text-lg font-semibold"><a href="#" class="hover:underline">{{ $post->title }}</a></h3> <p class="text-gray-600">{{ substr($post->text, 0, 50) }}...</p> </div> </article> @endforeach </div></section>
On the home page, we can see the posts from the database.
Showing Posts By Category
Now, we will add a link to view the category with its posts. The link will have a Route to the home page with a parameter. There are a few ways to add a parameter, but we will use the most straightforward way: with the URL like /?category_id=XXXXX
In fact, I'm not sure if you've noticed, but we already have those links in the sidebar.
resources/views/home.blade.php:
@foreach($categories as $category) <li> <a href="/?category_id={{ $category->id }}" class="text-gray-600 hover:text-gray-800"> {{ $category->name }} </a> </li>@endforeach
Then, in the Controller, we can filter posts with a where()
method and get a category from a request using the request()
helper.
app/Http/Controllers/HomeController.php:
class HomeController extends Controller{ public function index() { $categories = Category::all(); $posts = Post::latest()->get(); $posts = Post::where('category_id', request('category_id')) ->latest() ->get(); return view('home', compact('categories', 'posts')); }}
After refreshing the home page, we see an empty posts list because we have no category parameter.
But if you click on a category, the posts that belong to that category are shown.
Fix Showing All Posts
Now, let's fix the page to show all categories when the category isn't selected. This will be an example of how powerful Eloquent is.
Eloquent has a when()
method, which accepts the condition as a first parameter. If a condition is true
, Eloquent will execute a closure function, a second parameter of the when()
method.
app/Http/Controllers/HomeController.php:
class HomeController extends Controller{ public function index() { $categories = Category::all(); $posts = Post::where('category_id', request('category_id')) ->latest() ->get(); $posts = Post::when(request('category_id'), function ($query) { $query->where('category_id', request('category_id')); })->latest()->get(); return view('home', compact('categories', 'posts')); }}
This syntax means when a category_id
from requests exists, the where
condition will be added to the query.
After refreshing the home page, all posts are shown, and when a category is selected, only posts that belong to that category are shown. All done!
Here's the GitHub commit for this lesson change.
Also, there's extra commit made later that adds onDelete('cascade')
to the migration.
In the next lesson, we will show the page of the Blog Article with a different URL structure and parameters, like /post/1
instead of /post?post_id=1
.
No comments or questions yet...