Laravel for Beginners #5

Now that we’ve covered everything we need to do to create a basic blog application with Laravel, it’s time for us to create some other features for our blog. In this article, we are going to talk about pagination, related posts, search and user authentication.

Pagination

As we add more and more posts to our blog, creating a paginator might be a good idea, since we don’t want to have too many posts on one page.

First, we need to make some changes to the controllers. Let’s take the HomeController as an example. Recall that on the homepage, we have a list of all posts sorted in decreasing ID.

1
$posts = Post::where('is_published',true)->orderBy('id','desc')->paginate(5);

Notice the last method, instead of get(), we have paginate(5). It means we will have 5 posts for every page. Of course, you can change that number to whatever you like.

And now we need to tell Laravel where to add that paginator. Go to our post-list.blade.php file:

1
2
3
4
<!-- Pagination -->
<ul class="pagination justify-content-center mb-4">
    {{$posts->links()}}
</ul>

However, right now, if you refresh the page, you will notice that the paginator is not using the Bootstrap framework. To fix that, we need to go to app/Providers/AppServiceProvider.php, and add Paginator::useBootstrap() in the boot() method. Remember to import the Paginator class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Pagination\Paginator;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Paginator::useBootstrap();
    }
} 

Refresh the page again, and you should see the paginator at the bottom.

pagination

You need to do the same thing for all the pages that need a paginator.

The idea is to get the posts with the same tags.

1
2
3
4
//related posts
$related_posts = Post::where('is_published', true)->whereHas('tags', function ($q) use ($post) {
    return $q->whereIn('name', $post->tags->pluck('name'));
})->where('id', '!=', $post->id)->take(3)->get();

This chain of methods looks kind of scary, but don’t worry, let’s analyze them one by one.

The first one is easy, where('is_published', true) returns all the posts that are published.

The whereHas() method is where things get complicated. To understand whereHas() we need to first talk about has(). has() is a Laravel Eloquent method that allows us to check the existence of a relationship. For example:

1
$posts = Post::has('comments', '>', 3)->get();

This code will retrieve all the posts that have more than 3 comments. Notice that you cannot use where() to do this because comments is not a column in the posts table, it is another table that has a relation with posts.

whereHas() works just like has(), only it offers a little more power. Its second parameter is a function that allows us to inspect the content that “another table”, which in our case, is the tags table. We can access the tags table through the variable $q.

Line 2, the whereIn() method takes two parameters, the first one is the specified column, the second is an array of acceptable values. The method will return the records with only the acceptable values and exclude the rest.

The rest should be very easy to understand. where('id', '!=', $post->id) exclude the current post, and take(3) takes the first three records.

Now add the “related posts” section in the view:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- Related Posts -->

<h3>Related Posts</h3>
<div class="row">
    @foreach ($related_posts as $post)
        <div class="col-md-4">
            <div class="card mb-4 box-shadow">
                <img class="card-img-top" src="{{ Storage::url($post->featured_image) }}" alt="Card image cap">
                <div class="card-body">
                    <p class="card-text">
                        {{ Str::limit(strip_tags($post->content), 100, '...') }}</p>
                    <div class="d-flex justify-content-between align-items-center">
                        <div class="btn-group">
                            <a href="{{ route('post.show', ['slug' => $post->slug]) }}"
                                class="btn btn-sm btn-outline-secondary">Read More
                                →</a>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    @endforeach
</div>

related posts

The search function is a fundamental feature for almost all websites, and now, we’ll talk about how to implement a basic search function in Laravel.

A search function consists of three parts, a search form that allows users to pass queries to the back end, a piece of code that does the search, usually in the controller, and a view that displays the results of the search.

Search form

Let’s first look at the search form. For our template, the search form is located at sidebar.blade.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 <!-- Search widget-->
    <div class="card mb-4">
        <div class="card-header">Search</div>
        <form class="card-body" action="/search" method="GET" role="search">
            {{ csrf_field() }}
            <div class="input-group">
                <input class="form-control" type="text" placeholder="Enter search term..."
                    aria-label="Enter search term..." aria-describedby="button-search" name="query"/>
                <button class="btn btn-primary" id="button-search" type="submit">Go!</button>
            </div>
        </form>
    </div>

Line 4, since this is a form, it works exactly like the forms we talked about before. Here I set the method to GET because in this case, the search query would show up in the URL. You can change it to POST if you want.

Line 5, adds the CSRF protection to the field.

Line 8, adds name attribute to the input field, its value would be the variable that is sent to the backend. The value would be the user input

When the button is clicked, it’ll go to /search, so we need to register the corresponding router.

1
Route::get('/search', [PostController::class, 'search']);

This route points to the search() method in the PostController.

Search method

Now, it’s time for us to add the search() method in the PostController.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
    public function search(Request $request)
    {
        // get the search term from the request
        $key = $request->input('query');

        $posts = Post::where('title', 'like', "%{$key}%")
            ->orderBy('id', 'desc')
            ->paginate(1);

        //get all the categories
        $categories = Category::all();

        //get all the tags
        $tags = Tag::all();

        return view('search', [
            'key' => $key,
            'posts' => $posts,
            'categories' => $categories,
            'tags' => $tags,
        ]);
    }

Line 6, the like here is an operator. Laravel will find the title that is similar to the query that was sent from the user.

Search view

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@extends('layout')

@section('meta')
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    <meta name="description" content="{{ setting('site.description') }}" />
    <meta name="author" content="Eric Hu" />
    <title>Search: {{ $key }} - {{ setting('site.title') }}</title>
@endsection

@section('header')
    <!-- Page header with logo and tagline -->
    <header class="py-5 bg-light border-bottom mb-4">
        <div class="container">
            <div class="text-center my-5">
                <h1 class="fw-bolder">Search: {{ $key }}</h1>
            </div>
        </div>
    </header>
@endsection

@section('content')
    @include('vendor.post-list')
    @include('vendor.sidebar')
@endsection

User authentication

If you have some experience in web or app development, you would know that setting up user authentication is actually a very challenging task. It usually takes at least a week to implement from scratch, because you have to consider a lot of different scenarios. For example, how to verify the user’s email, what if the user forgets the user name, email, or password, and so on.

Luckily, Laravel offers us a very easy-to-use starter package called Laravel Breeze that can help us with the task.

Before you install the package, make a copy of your route file (web.php), because the installation would overwrite that file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php

use App\Http\Controllers\CategoryController;
use App\Http\Controllers\HomeController;
use App\Http\Controllers\PostController;
use App\Http\Controllers\TagController;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', [HomeController::class, 'show']);
Route::get('/category/{slug}', [CategoryController::class, 'index'])->name('category.index');
Route::get('/tag/{slug}', [TagController::class, 'index'])->name('tag.index');
Route::get('/post/{slug}', [PostController::class, 'show'])->name('post.show');

Route::get('/search', [PostController::class, 'search']);

Route::group(['prefix' => 'admin'], function () {
    Voyager::routes();
});

Run the following commands to install the package:

1
2
3
4
5
6
7
composer require laravel/breeze --dev

php artisan breeze:install

npm install
npm run dev
php artisan migrate

Next, go to the route file and recover all your old routes.

Now, go to /register or /login in your browser, and see if it works.

Laravel Breeze Register Page

Laravel Breeze Login Page

Finally, it’s time to deploy our Laravel project. Instead of doing everything manually, we’ll use a server management panel, which is much more beginner-friendly.

Deploy our project

First, let’s set up a new VPS. I assume you already bought one from a cloud infrastructure provider such as DigitalOcean, Vultr or Linode. To make things easier, we are going to use a control panel for our project. Here I’ll use aaPanel as an example. (aaPanel Demo)

Connect to your server using SSH. Run the shellcode on the official site of aaPanel. Remember, if you choose to use aaPanel, make sure CentOS is installed on your VPS. aaPanel is developed on CentOS, using other systems may cause errors.

After the installation process is finished. Follow the instructions on the screen and log into the panel.

aaPanel

Setup a new website

From now on, everything should be straightforward. Just install the latest version of Apache or Ngnix, MySQL and PHP. You can install other tools if you want, but for now, these are all we need.

After you’ve installed all the necessary components, go to “Website”, and click on “Add Site”.

add site

Type in the domain you mapped to this server, and create a database. Remember the login information for the database, you’ll need it later on.

You can manually upload the entire project to the site’s root folder, but the problem is if you want to make modifications to it, you are gonna have to upload the files over and over again. I recommend using GitHub. Download and install the GitHub Desktop on your PC and make a new repository for your project. Push it to the origin.

And now you can clone the repository to the root folder of your site. Every time you update your project, just push it to the origin, and then pull it to your server using the command git pull. Here is a cheat sheet for git commands: Git Cheat Sheet

Pre-deploy configurations

After you pulled the Laravel project to the root folder of your site, notice that the .env file is not included. Copy the .env.example and rename it .env. Remember to put in the database information.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=change to your site's URL

LOG_CHANNEL=stack

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=your database name
DB_USERNAME=database user name
DB_PASSWORD=password

Run composer install to install PHP dependencies, and then use npm install to install JavaScript dependencies.

Generate APP_KEY:

1
php artisan key:generate

Link storage:

1
php artisan storage:link

Run migrations:

1
php artisan migrate

Finally, open the .env file again, change APP_ENV to production and APP_DEBUG to false. This is important, or your site will have security issues.

Go to /admin and add some new posts, and now you should be able to access your blog.


comments powered by Disqus