Mastering Laravel 10: Advanced Topics

Mastering-Laravel-Advanced-Topics

Hey, there guys today we will be looking at mastering Laravel 10 advanced topics such as Eloquent, Queues, Events, Service Containers, and Middleware. You will understand best practices, design patterns, and performance optimization techniques. You will practice what you will have learned by building projects and applying these advanced concepts to real-world scenarios. Let’s get started!

1. Eloquent

Eloquent is Laravel’s ORM (Object Relational Mapper) that allows you to work with database objects and relationships using an object-oriented syntax.

a) Eager Loading

Eager loading is a technique to optimize your queries when retrieving related models. Instead of executing additional queries for each related model, you can load all the related models with one query.

For example, let’s say you want to display a list of posts along with their authors and comments on a page. You can use eager loading in your controller to retrieve the data:

use App\Models\Post;

class PostController extends Controller
{
public function index()
{
$posts = Post::with(['user', 'comments'])->get();

return view('posts.index', compact('posts'));
}
}

Here, the with() method is used to eagerly load the related user and comment models for each post in a single query. This helps to reduce the number of queries executed and improve the performance of your application.

In your view (e.g., resources/views/posts/index.blade.php), you can display the posts along with their authors and comments like this:

@foreach ($posts as $post)
<h2>{{ $post->title }}</h2>
<p>By: {{ $post->user->name }}</p>
<p>{{ $post->body }}</p>
<h3>Comments:</h3>
<ul>
@foreach ($post->comments as $comment)
<li>{{ $comment->body }} - {{ $comment->created_at->diffForHumans() }}</li>
@endforeach
</ul>
@endforeach

By using eager loading, you avoid the “N+1” query problem, where each related model would require an additional query. Instead, with eager loading, Laravel retrieves all related models with optimized queries, improving your application’s performance.

b) Many To Many Relationships

In a many-to-many relationship, a model can be related to multiple instances of another model, and vice versa. In this case, a post can have multiple tags and a tag can be assigned to multiple posts.

To implement a many-to-many relationship between posts and tags, follow these steps:

1. Create a tags table migration:

Command:

php artisan make:migration create_tags_table --create=tags
// database/migrations/your_create_tags_table_migration_file.php

Schema::create('tags', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});

2. Create a pivot table migration for the post_tag relationship:

Command:

php artisan make:migration create_post_tag_table --create=post_tag
// database/migrations/your_create_post_tag_table_migration_file.php

Schema::create('post_tag', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('post_id');
$table->unsignedBigInteger('tag_id');
$table->timestamps();

$table->unique(['post_id', 'tag_id']);

$table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade');
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
});

Once you’ve defined the table structure, run the following command to execute the migrations

and create the tables in your database:

Command:

php artisan migrate

3. Define the many-to-many relationship in both the Post and Tag models:

// app/Models/Post.php

class Post extends Model
{
// ...

public function tags()
{
return $this->belongsToMany(Tag::class);
}
}

// app/Models/Tag.php

class Tag extends Model
{
// ...

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

Now you can associate tags with posts and retrieve related tags for a post:

To add tags to your posts, you’ll first need to create these tags and store them in your database.

Here’s how you can do it:

1. Open up Laravel’s Tinker tool by running the following command in your terminal:

Command:

php artisan tinker

2. Then, you can create your tags one by one:

\App\Models\Tag::create(['name' => 'HTML']);
\App\Models\Tag::create(['name' => 'CSS']);
\App\Models\Tag::create(['name' => 'JavaScript']);
\App\Models\Tag::create(['name' => 'Laravel']);
\App\Models\Tag::create(['name' => 'PHP']);
\App\Models\Tag::create(['name' => 'Node.js']);
\App\Models\Tag::create(['name' => 'Vue.js']);
\App\Models\Tag::create(['name' => 'React']);

Accessing Database in Linux:

You can also access your MySQL databases directly from the terminal:

Open MySQL prompt: Open the terminal and run the following command to open the MySQL prompt:

1.

/opt/lampp/bin/mysql -u root -p

When prompted, enter your MySQL root password (if you’ve set one).

Manage databases: Once you’re at the MySQL prompt, you can run SQL commands to manage your databases.

For example, to show all databases, you can use the

Command:

2.

SHOW DATABASES;

To use a specific database, you use the USE command followed by the name of the database. Here’s an example:

3.

 USE your_database_name;

After you’ve selected a database, you can show all tables in the database with the SHOW TABLES; command:

4.

SHOW TABLES;

This will display all the tables in your selected database.

If you want to see the structure of a specific table, you use the DESCRIBE command followed by the name of the table:

5.

DESCRIBE your_table_name;

To view the records in a table, you can use the SELECT statement. For example, if you want to see all records from a table named your_table_name,
you would use:

6.

SELECT * FROM your_table_name;

7.

Exit

Make sure that your Tag model has the $fillable property for the name field:

class Tag extends Model
{
protected $fillable = ['name'];

// ...
}

Once you’ve created the tags, you can add them to posts. For example, to add the “HTML” tag to a post, you could do something like this:

public function index(Post $post)
{
$posts = Post::with('tags')->get(); // This will load all posts with their related tags

$post = \App\Models\Post::first(); // Get the first post

$tag = \App\Models\Tag::where('name', 'HTML')->first(); // Get the "HTML" tag

$post->tags()->attach($tag->id); // Add the tag to the post


return view('posts.index', compact('posts'));
}

In your index.blade.php file, you can display the tags for each post like this:

@foreach ($posts as $post)
<!-- ... -->
<div class="card-body">
<!-- ... -->
<p>
Tags:
@foreach ($post->tags as $tag)
<span class="badge bg-primary">{{ $tag->name }}</span>
@endforeach
</p>
<!-- ... -->
</div>
<!-- ... -->
@endforeach

You May Get this Error:

'SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '1-1' for key 'post_tag_post_id_tag_id_unique'

This error is due to a unique constraint on the post_id and tag_id combination in the post_tag pivot table. This constraint prevents the same tag from being attached to the same post more than once, which is generally a good thing to prevent data duplication.

The error message suggests that you’re trying to attach a tag to a post when that tag is already attached to the post.

Here’s how you can handle this:

1. Check if the tag is already attached before attaching it: You can use the sync or syncWithoutDetaching methods

provided by Laravel. These methods allow you to manage the list of related IDs for many-to-many relationships.

$post = \App\Models\Post::first(); // Get the first post

$htmlTag = \App\Models\Tag::where('name', 'HTML')->first(); // Get the "HTML" tag

$cssTag = \App\Models\Tag::where('name', 'CSS')->first(); // Get the "CSS" tag

// The syncWithoutDetaching method will attach the tags to the post, but it won’t duplicate them if they’re already attached

$post->tags()->syncWithoutDetaching([$htmlTag->id, $cssTag->id]);

2. Use detach before attach: If you want to replace the current tags with new ones, you could detach all current tags and then attach the new ones.

public function index(Post $post)
{
$posts = Post::with('tags')->get(); // This will load all posts with their related tags

$post = \App\Models\Post::first(); // Get the first post

$reactTag = \App\Models\Tag::where('name', 'React')->first(); // Get the "REACT" tag

$javascriptTag = \App\Models\Tag::where('name', 'Javascript')->first(); // Get the "JAVASCRIPT" tag

$post->tags()->detach(); // Remove all current tags

$post->tags()->attach([$reactTag->id, $javascriptTag->id]); // Attach new tags

return view('posts.index', compact('posts'));
}

Note:

attach, detach, and sync methods are part of Laravel’s way of handling many-to-many relationships and are very powerful for managing these types of relationships.

Display Posts with Tag Links.

For the tag links, you’ll need to create a new route, controller method, and view to display the posts that belong to a specific tag.

Here’s a basic example of how you could do it:

1. Add a new route to your routes/web.php file:

Route::get('/tags/{tag}', [App\Http\Controllers\TagController::class, 'show'])->name('tags.show');

2. Create a TagController with a show method:

Command:

php artisan make:controller TagController
// app/Http/Controllers/TagController.php

namespace App\Http\Controllers;

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

class TagController extends Controller
{
public function show(Tag $tag)
{
$posts = $tag->posts;

return view('tags.show', compact('tag', 'posts'));
}
}

3. Create a new tags/show.blade.php view to display the posts:

<x-front-layout>
<div class="container">
<h1 class="text-center mb-5">Posts with tag: {{ $tag->name }}</h1>

<div class="row justify-content-center">
@foreach($posts as $post)
<!-- your post display code here... -->
@endforeach
</div>
</div>
</x-front-layout>

4. Finally, update the tag display code in your index.blade.php file to make the tags clickable:

 

@foreach ($post->tags as $tag)
<a href="{{ route('tags.show', $tag->id) }}">
<span class="badge bg-primary">{{ $tag->name }}</span>
</a>
@endforeach

All Posts With Atleast A Tag:

You can assign tags to every post. However, this will require you to manually assign tags to each post, or programmatically assign them if you’re seeding the database.

In a real-world scenario, typically when creating or editing a post, you would have a mechanism to select one or more tags to associate with the post.

For demonstration, here’s an example of how you could assign a tag to each post programmatically:

$tags = \App\Models\Tag::all(); // Get all tags

\App\Models\Post::all()->each(function ($post) use ($tags) {

$randomTagIds = $tags->random(rand(1, 3))->pluck('id'); // Select between 1 and 3 random tags

$post->tags()->sync($randomTagIds); // Attach the tags to the post
});

In this example, for each post, we randomly select between one and three tags and attach them to the post. This way, each post will have at least one tag, and possibly up to three.

3. Polymorphic Relationships

Polymorphic relationships are useful when you have multiple types of models that can be associated with another model.

For example, In our blog application, we could implement polymorphic relationships in a few different ways. For example, if you want to allow users to ‘like’ both posts and comments, we can use a polymorphic relationship.

Steps To Achieve This…

1. Create a new migration for the ‘likes’ table:

php artisan make:migration create_likes_table --create=likes

2. In the newly created migration, define the polymorphic relationship:

public function up()
{
   Schema::create('likes', function (Blueprint $table) {
   $table->id();
   $table->unsignedBigInteger('user_id');
   $table->unsignedBigInteger('likeable_id');
   $table->string('likeable_type');
   $table->timestamps();

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

3. In your User model, define the ‘likes’ relationship:

public function likes()
{
   return $this->hasMany(Like::class);
}

4. Create a Like model:

php artisan make:model Like

5. In the Like model, define the likeable relationship:

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

6. In both your Post and Comment models, define the ‘likes’ relationship:

public function likes()
{
    return $this->morphMany(Like::class, 'likeable');
}

Adjust your PostController:

public function addLike(Post $post)
{
  $like = new Like;
  $like->user_id = auth()->id(); // the ID of the currently authenticated user
  $post->likes()->save($like);
  return back();
}

Then, in your routes file (web.php), you could add a route for adding a like:

Route::post('/post/{post}/like', [PostController::class, 'addLike'])->name('post.like');

Note that this is a POST route, which means you’d typically trigger it from a form in your view (index.blade.php).

You might have a “Like” button next to each post, which when clicked, submits a form to this route.

<form method="POST" action="{{ route('post.like', $post->id) }}">
@csrf
<button type="submit">Like</button>
</form>

We implemented the “liking” feature for the Post model. Now, let’s implement the same for the Comment model.

1. Add the relationship to the Comment model: Just like we did with the Post model, we need to define the likes relationship in the Comment model:

// app/Models/Comment.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
// existing code...

public function likes()
{
return $this->morphMany(Like::class, 'likeable');
}
}

2. Allow users to like a comment: We need to create a method in our CommentController to allow users to like a comment:

// app/Http/Controllers/CommentController.php

namespace App\Http\Controllers;

use App\Models\Comment;
use App\Models\Like;
use Illuminate\Http\Request;

class CommentController extends Controller
{
// existing code...

public function addLike(Comment $comment)
{
  $like = new Like;
  $like->user_id = auth()->id();
  $comment->likes()->save($like);

  return back();
}
}

3. Add a route for liking a comment: In the routes file (web.php), add a route for liking a comment:

Route::post('/comment/{comment}/like', [CommentController::class, 'addLike'])->name('comment.like');

4. Add a “Like” button for comments: Finally, in the view where you display the comments, add a form to submit a “like” for each comment:

show.blade.php…

@foreach($post->comments as $comment)
<div class="card mb-3">
<div class="card-body">
<p class="card-text">{{ $comment->body }}</p>
<hr>
<p class="card-text"><small class="text-muted">{{ $comment->created_at->format('M d, Y') }}</small></p>
<!-- Add form here to like a comment -->
<form method="POST" action="{{ route('comment.like', $comment->id) }}">
@csrf
<button type="submit">Like</button>
</form>
</div>
</div>
@endforeach

if you only want to allow authenticated users to like a post, you might want to wrap the “Like” button in an @auth directive to hide it from guests:

@auth
<!-- Form to like a post goes here -->
@endauth

or...

@auth
<form method="POST" action="{{ route('post.like', $post->id) }}">
@csrf
<button type="submit">Like</button>
</form>
@endauth

Check How Many Likes…

You can show the number of likes for a post in your blade view using the count() method on the likes relationship.

Here’s how you can display it in your blade template:

<div class="card-body">
<h5 class="card-title">{{ $post->title }}</h5>
<p class="card-text">{{ $post->body }}</p>
<!-- Display like count here -->
<p>Likes: {{ $post->likes->count() }}</p>
</div>

4. Global Scopes

Global Scopes in Laravel offer a way to ensure that every query for a given model applies certain constraints. This is quite powerful as you can add conditions or manipulate the query globally across your application, meaning it will be applied in every scenario where you are retrieving records of a given model.

For instance, in your blog application, let’s say you wanted to only show posts that have been approved by an admin. In this case, you might add an is_approved column to your posts table.

Example:

Using Laravel tinker

$user = App\Models\User::where('email', 'brian@gmail.com')->first();
$user->is_admin = 1;
$user->save();

This code finds the user with the email ‘brian@gmail.com’, sets their is_admin attribute to 1, and then saves the change to the database.

If you want to change the is_admin attribute back to 0, you can use the same code but change is_admin to 0:

$user = App\Models\User::where('email', 'brian@gmail.com')->first();
$user->is_admin = 0;
$user->save();

You could create a global scope that only includes approved posts. Here’s an example of what this might look like:

1. First, let’s create a scope. This should be placed in a new file. You could create this in a new directory like app/Scopes. For instance, you could create a file app/Scopes/ApprovedScope.php:

<?php

namespace App\Scopes;

use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class ApprovedScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public function apply(Builder $builder, Model $model)
{
    $builder->where('is_approved', '=', 1);
}
}

Now, whenever you query the Post model, it will only include posts where is_approved is 1.

Then in your blade files For instance:

1. Index Page: If you want only approved posts to be shown on the posts index page, you would modify your index.blade.php.
The code would look something like this:

@foreach($posts as $post)
@if($post->is_approved || (auth()->check() && auth()->user()->is_admin))
<!-- Post content here -->
@endif
@endforeach

In this case, the @if($post->is_approved) line checks if the post is approved before displaying it.

2. Show Page: Similarly, if you want the single post show page to be accessible only if the post is approved, you would modify your show.blade.php.

The code would look something like this:

<x-front-layout>
<div class="container">
@if($post->is_approved || (auth()->check() && auth()->user()->is_admin))
<!-- Your post content here -->
@else
<p>This post is awaiting approval.</p>
@endif
</div>
</x-front-layout>

Remember, scopes are applied globally, to every query of the model.

Sign up for free tutorials in your inbox.

We don’t spam! Read our privacy policy for more info.

Leave a Comment

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