Laravel for Beginners #4

In this article, I’m going to show you, step by step, how to create a blog application using Laravel. Here is what we are going to do. First, we need to go through the database structure and relation, which is what we are going to do in this article. Next, we need to create the view structure, the routes and the controllers. After that, we can build some useful features such as pagination and search. Finally, we can deploy our project to the cloud.

Let’s start with the database structure. I assume by now you already know how to create a Laravel project, and what initial configurations you need to do, so I will not repeat it here. But, as a reminder, here is a checklist of what you need to do.

  • Create a new project using laravel new app.
  • Setup the database and edit the .env file.
  • Setup storage symbolic link.
  • Make sure APP_URL in the .env file is correct.
  • Generate application key if it is not generated (in .env file).

Designing database structure

For a simple blogging system, we need at least 4 database tables: UsersCategoriesTags, and Posts. If you want other functions for your blog, comments, for example, you can add other tables yourself. In order to keep this tutorial short and easy to understand, these four are all we need.

Users Table

key type info
id integer auto increment
name string cannot be empty
email string unique, cannot be empty
password string

The migration file for the users table is already included in Laravel and we don’t need to do anything about it.

Categories Table

key type info
name string cannot be empty
slug string unique, cannot be empty
description text can be empty

Tags Table

key type info
name string cannot be empty
slug string unique, cannot be empty
description text can be empty

Posts Table

key type info
title string cannot be empty
slug string unique, cannot be empty
featured image string or text can be empty
content text cannot be empty
is_published boolean

The difference between string and text is that string corresponds to a VARCHAR column type, which has a maximum length of 255 characters, and text corresponds to a TEXT column type, and it does not have that limit.

Design Relations

For our blog website project. There are six relationships we need to take care of.

  • Each user has multiple posts
  • Each category has many posts
  • Each tag has many posts
  • Each post belongs to one user
  • Each post belongs to one category
  • Each post has many tags

Models and migrations

Now, it’s time for us to implement this design. The first thing we need to do is to generate the necessary models and migration files with the following commands. And remember that the -m option will generate the corresponding migration files as well.

1
2
3
4
5
php artisan make:model Category -m

php artisan make:model Tag -m

php artisan make:model Post -m

Category

create_category_table.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 public function up()
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('slug')->unique();
            $table->text('description')->nullable();
            $table->timestamps();
        });
    }

The unique() modifier makes sure that each item in the slug column is unique.

The nullable() modifier means that the items in the description column could be empty (has value NULL). By default, all columns must be filled. You can find other column modifiers here.

Category.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Category extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'description',
        'slug',
    ];
}

Laravel is smart enough to know that the plural form of “category” is “categories” and not “categorys”, so you don’t have to specify the database table name here.

Tag

create_tag_table.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 public function up()
    {
        Schema::create('tags', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('slug')->unique();
            $table->text('description')->nullable();
            $table->timestamps();
        });
    }

Tag.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Tag extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'description',
        'slug',
    ];
}

Post

create_post_table.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13

 public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            $table->string('title');
            $table->string('slug')->unique();
            $table->text('content');
            $table->string('featured_image')->nullable();
            $table->boolean('is_published')->default(false);
        });
    }

Post.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Post extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        "title",
        'content',
        'slug',
        'featured_image',
        'is_published',
    ];
}

Relations

Please refer to this article for details on database relations.

Between User and Post (One to Many)

Since each user can have many posts, this relation is obviously a one-to-many relation, and that means we need to have a user_id column inside the posts table, which would tell us which user owns this post.

1
$table->bigInteger('user_id');

Next, we need to define this relation in the models, as well as its inversed relation.

User.php

1
2
3
4
5
6
7
 /**
     * Get the posts for the user.
     */
    public function posts()
    {
        return $this->hasMany(Post::class);
    }

Post.php

1
2
3
4
5
6
7
8

 /**
     * Get the user that owns the post.
     */
    public function user()
    {
        return $this->belongsTo(User::class);
    }

Between Category and Post (One to Many)

The relation between category and post is very similar, Again, we need to have a category_id column in the posts table, which stores the id of the category that this post belongs to.

1
$table->bigInteger('category_id');

Define the relationships in the models.

Category.php

1
2
3
4
5
6
7
8

 /**
     * Get the posts for the user.
     */
    public function posts()
    {
        return $this->hasMany(Post::class);
    }

Post.php

1
2
3
4
5
6
7
 /**
     * Get the category that owns the post.
     */
    public function category()
    {
        return $this->belongsTo(Category::class);
    }

Between Tag and Post (Many to Many)

This one is a bit more complicated, each tag can have many posts, and each post can have many tags. It requires a Many To Many Relationship and an extra database table post_tag. This table is called a pivot table.

First, create a new migration file:

1
php artisan make:migration create_post_tag_table
1
2
3
4
Schema::create('post_tag', function (Blueprint $table) {
    $table->bigInteger('tag_id');
    $table->bigInteger('post_id');
});

Now we can define the relationships between tags and posts.

Tag.php

1
2
3
4
5
6
7
8
9

 /**

   * Get the posts that belong to the post.
   */
   public function posts()
   {
       return $this->belongsToMany(Post::class);
   }

Post.php

1
2
3
4
5
6
7
 /**
     * Get the tags that belong to the post.
     */
    public function tags()
    {
        return $this->belongsToMany(Tag::class);
    }

Here, Laravel assumes there is a post_tag table and there are two columns post_id and tag_id in it. The name of the table needs to be in alphabetical order. If you named them differently, tag_post, for example, you need to specify them like this.

1
2
3
4
public function tags()
{
    return $this->belongsToMany(Tag::class, 'tag_post');
}

Remember to apply the migration files using php artisan migrate.

Setup the admin panel

Every web application requires an admin panel that only admins of the website can access. For our blog application, it would be a place where you can edit the posts and manage the categories and tags. We can create our own admin panel like we’ve seen in our previous article, where we created an admin for the sample homepage. But that might be a little too challenging for beginners. So, in this course, we are going to use a third-party admin panel called Voyager.

To install voyager, we can use the following command:

1
2
3
composer require tcg/voyager

php artisan voyager:install

Create a new admin user:

1
php artisan voyager:admin your@email.com --create

After that, you can access the admin by going to http://127.0.0.1:8000/admin.

Notice that in Voyager, there is something called BREAD, which stands for Browse, Read, Edit, Add and Delete. This sounds very familiar, right? It is essentially the CRUD operation we’ve talked about before.

Setup BREAD

To make Voyager work, we need to add BREAD (or CRUD, however you wish to call it) to all the necessary databases. Let’s start with categories.

Go to http://127.0.0.1:8000/admin/bread, find the categories table and click on “Add BREAD to this table”.

Voyager BREAD

In the BREAD Info section, we need to change the path that points to Model Name. By default, it is App\Category, but starting from Laravel 8, we have an independent folder for models, we need to change it to App\Models\Category. It is OK to just leave everything else the way it is, but you can play with the settings if you want.

What we really care about is the Edit the rows for the categories table below section.

Voyager edit

For the first column (Field), each item corresponds to a column in the categories section. In the Visibility column, we can configure if we wish to Browse, Read, Edit, Add or Delete items in that column. Input Type allows us to choose different form fields. Display Name determines how you want the field to be named, and Optional Details allows us to add some extra configurations, which we’ll see later.

This is what I did:

Voyager edit result

Both created_at and updated_at are automatically generated, so we don’t need to edit them. We also want the slug to automatically generate based on the value of name, that is what I put in the Optional Details. You can find other options here (https://voyager-docs.devdojo.com/bread/introduction-1#generating-slugs).

Click Submit and a Categories menu will appear in the menu bar. We can add a new category. Notice that the slug will be automatically generated based on the name.

Voyager slug

Now, let’s configure the tags and posts tables the same way.

For tags

For posts

The posts require some special attention, notice that I changed the default URL from /posts to /my-posts. That is because Voyager has a default Post built-in, we need to change the URL to avoid conflicts.

After that, we need to tell Voyager that the URL has been changed by changing the corresponding menu item. Go to Tools->Menu Builder, and click on Builder.

Menu builder

And edit the menu item called Posts.

edit posts

Relations

Finally, let’s deal with relations. Voyager is not capable of recognizing the relations directly from the models, we need to specify them in BREAD.

For example, let’s go back to the BREAD Edit page for categories, and scroll to the bottom, you can see a Create A Relationship button. Let’s create a relation between Category and Post.

relations

You’ll need to do the same for all the relationships we’ve created in our models.

Routes and controllers

For a basic blogging system, we need at least a HomeController to display the homepage, a CategoryController to display a list of posts that belongs to this category, a TagController to display a list of posts that has this tag, and finally a PostController that shows the content of the post.

1
2
3
4
php artisan make:controller HomeController
php artisan make:controller CategoryController
php artisan make:controller TagController
php artisan make:controller PostController

HomeController

Remember that the Laravel controller is in charge of retrieving data from the database, and send it to the views. So, let’s analyze what data we need for the homepage.

homepage

We need the title and description for the website, a list of recent posts, and a list of all categories and tags. The title and description can be set up inside Voyager (http://127.0.0.1:8000/admin/settings). We’ll see how to use it when we get to views.

So now, let’s get that information in our HomeController. Make sure that you import the correct models here.

 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
30
<?php

namespace App\Http\Controllers;

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

class HomeController extends Controller
{
    // Display the homepage
    public function show()
    {
        //get the posts that are published, sort by decreasing order of "id".
        $posts = Post::where('is_published', true)->orderBy('id', 'desc')->get();

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

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

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

We also need a router that points to this controller.

1
Route::get('/', [HomeController::class, 'show']);

Next, we’ll do the same thing for the category, tag and post.

CategoryController

 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
class CategoryController extends Controller
{
    public function index($slug)
    {
        //get the requested category
        $category = Category::where('slug', $slug)->firstOrFail();

        //get the posts in that category
        $posts = $category->posts()
            ->where('is_published',true)
            ->orderBy('id','desc')
            ->get();

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

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

        //return the data to the corresponding view
        return view('category', [
            'category' => $category,
            'posts' => $posts,
            'categories' => $categories,
            'tags' => $tags,
        ]);
    }
}
1
Route::get('/category/{slug}', [CategoryController::class, 'index'])->name('category.index');

Here we passed a variable $slug from the route to the controller, and inside that controller, we used the variable to locate the correct category. Also notice that we named this route, this is very important, you’ll see why that is when we get to the views later.

TagController

 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
class TagController extends Controller
{
    public function index($slug)
    {
        //get the requested tag
        $tag = Tag::where('slug', $slug)->firstOrFail();

        //get the posts with that tag
        $posts = $tag->posts()
            ->where('is_published',true)
            ->orderBy('id','desc')
            ->get();
    
        //get all the categories
        $categories = Category::all();
    
        //get all the tags
        $tags = Tag::all();
    
        //return the data to the corresponding view
        return view('tag', [
            'tag' => $tag,
            'posts' => $posts,
            'categories' => $categories,
            'tags' => $tags,
        ]);
    }
}
1
Route::get('/tag/{slug}', [TagController::class, 'index'])->name('tag.index');

PostController

 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
class PostController extends Controller
{
    public function show($slug)
    {
        //get the requested post, if it is published
        $post = Post::where('is_published', true)->where('slug', $slug)->firstOrFail();
        
        //get all the tags for this post
        $post_tags = $post->tags;

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

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

        //return the data to the corresponding view
        return view('post', [
            'post' => $post,
            'post_tags' => $post_tags,
            'categories' => $categories,
            'tags' => $tags,
        ]);
    }
}
1
Route::get('/post/{slug}', [PostController::class, 'show'])->name('post.show');

Views

It is time for us to work on the view system, the routes as well as the controllers. For the views, instead of writing our own HTML and CSS code, we are going to use a Bootstrap template instead, since HTML and CSS are not really our focus here. You can download the template directly:

Blog Home

Blog Post

view structure

Here is the template structure that I’m using.

The layout contains the header and the footer, and it is where we import the CSS and JS files. The home, category, tag and post are the views that our controllers point to, and they all extends to the layout. And finally, inside the vendor directory are the components that will appear multiple times in different views, and we can import them using the @include directive.

Layout

 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<!DOCTYPE html>
<html lang="en">

<head>

    @yield('meta')
    
    <!-- Core Bootstrap -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
    <!-- Custom Styles -->
    <link href="{{ asset('css/styles.css') }}" rel="stylesheet">

</head>

<body>
    <!-- Responsive navbar -->
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="/">{{ setting('site.title') }}</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse"
                data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
                aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
            <div class="collapse navbar-collapse" id="navbarSupportedContent">
                <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
                    <li class="nav-item"><a class="nav-link" href="/">Home</a></li>
                    <li class="nav-item"><a class="nav-link" href="#">About</a></li>
                    <li class="nav-item"><a class="nav-link" href="/admin">Admin</a></li>
                </ul>
            </div>
        </div>
    </nav>

    @yield('header')
    
    <!-- Page content -->
    <div class="container mt-5">
        <div class="row">
    
            @yield('content')
    
        </div>
    </div>
    <!-- Footer-->
    <footer class="py-5 bg-dark">
        <div class="container">
            <p class="m-0 text-center text-white">Copyright &copy; {{ setting('site.title') }} 2021</p>
        </div>
    </footer>
    <!-- Bootstrap core JS-->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous">
    </script>

</body>

</html>

Line 12, {{ asset('css/styles.css') }} is how we can import custom CSS files into the Blade template. We are not writing any CSS code in this tutorial, but you need to know how it works. This code will point to the path /public/css/styles.css.

Line 20, {{ setting('site.title') }} is how we can access the value that we put in inside Voyager (http://127.0.0.1:8000/admin/settings).

Line 6, 34 and 40, defines three different sections.

Home

 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
@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>{{ 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">Welcome to {{ setting('site.title') }}!</h1>
                <p class="lead mb-0">{{ setting('site.description') }}</p>
            </div>
        </div>
    </header>
@endsection

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

Line 24 and 25, imports two different components, post-list and sidebar, which we’ll create next.

Post List

 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
30
31
32
33
34
35
36
37
<!-- Blog entries -->
<div class="col-lg-8">
    <!-- Nested row for non-featured blog posts -->
    <div class="row">
        @foreach ($posts as $post)
            <!-- Blog post-->
            <div class="col-md-6">
                <div class="card mb-4 px-md-0">
                    <a href="{{ route('post.show', ['slug' => $post->slug]) }}"><img class="card-img-top" src="{{ Storage::url($post->featured_image) }}"
                            alt="..." /></a>
                    <div class="card-body">
                        <div class="small text-muted">{{ \Carbon\Carbon::parse($post->created_at)->format('M d, Y') }}
                        </div>
                        <h2 class="card-title h4">{{ $post->title }}</h2>
                        <p class="card-text">{{ Str::limit(strip_tags($post->content), 150, '...') }}</p>
                        <a class="btn btn-primary" href="{{ route('post.show', ['slug' => $post->slug]) }}">Read more
                            →</a>
                    </div>
                </div>
            </div>
        @endforeach
    </div>
    <!-- Pagination-->
    <nav aria-label="Pagination">
        <hr class="my-0" />
        <ul class="pagination justify-content-center my-4">
            <li class="page-item disabled"><a class="page-link" href="#" tabindex="-1"
                    aria-disabled="true">Newer</a></li>
            <li class="page-item active" aria-current="page"><a class="page-link" href="#!">1</a></li>
            <li class="page-item"><a class="page-link" href="#!">2</a></li>
            <li class="page-item"><a class="page-link" href="#!">3</a></li>
            <li class="page-item disabled"><a class="page-link" href="#!">...</a></li>
            <li class="page-item"><a class="page-link" href="#!">15</a></li>
            <li class="page-item"><a class="page-link" href="#!">Older</a></li>
        </ul>
    </nav>
</div>

Line 9, {{ route('post.show', ['slug' => $post->slug]) }} is how we can generate URLs inside the blade template, and it is also why we need to name the routes we just created. The route() function takes two parameters, the first one is the name of the route, and the second one is an array of parameters that are required by that route, which in our case, is only $slug.

And {{ Storage::url($post->featured_image) }} is how we can import images from storage, we’ve already talked about his before.

Line 12, Carbon is a PHP package that we can use to format data and time, because the date that is stored inside the database is not exactly user friendly. The package in already included in Laravel. You can read more about Carbon here: https://carbon.nesbot.com/docs/.

Line 15, {{ Str::limit(strip_tags($post->content), 150, '...') }}. Here we used two different functions. strip_tags() will first remove the HTML tags, and Str::limit() will limit the length of the remaining string, which is 150 characters in this example, and the third parameter specifies how you want the string to end.

We also included a paginator in this example, we’ll talk about how to make it work in the next article.

 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!-- Side widgets-->

<div class="col-lg-4">
    <!-- Search widget-->
    <div class="card mb-4">
        <div class="card-header">Search</div>
        <div class="card-body">
            <div class="input-group">
                <input class="form-control" type="text" placeholder="Enter search term..."
                    aria-label="Enter search term..." aria-describedby="button-search" />
                <button class="btn btn-primary" id="button-search" type="button">Go!</button>
            </div>
        </div>
    </div>
    <!-- Categories widget-->
    <div class="card mb-4">
        <div class="card-header">Categories</div>
        <div class="card-body">

            <ul class="list-unstyled mb-0 row">
                @foreach ($categories as $category)
                    <li class="col-sm-6"><a
                            href="{{ route('category.index', ['slug' => $category->slug]) }}">{{ $category->name }}</a>
                    </li>
                @endforeach
            </ul>
            
        </div>
    </div>
    <!-- Tags widget-->
    <div class="card mb-4">
        <div class="card-header">Tags</div>
        <div class="card-body">
    
            <ul class="list-unstyled mb-0 row">
                @foreach ($tags as $tag)
                    <li class="col-sm-6"><a
                            href="{{ route('tag.index', ['slug' => $tag->slug]) }}">{{ $tag->name }}</a>
                    </li>
                @endforeach
            </ul>
    
        </div>
    </div>
    <!-- Side widget-->
    <div class="card mb-4">
        <div class="card-header">Side Widget</div>
        <div class="card-body">You can put anything you want inside of these side widgets. They are easy to
            use, and feature the Bootstrap 5 card component!</div>
    </div>
</div>

Category

 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
@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>{{ $category->name }} - {{ 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">Category: {{ $category->name }}</h1>
                <p class="lead mb-0">{{ $category->description }}</p>
            </div>
        </div>
    </header>
@endsection

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

Tag

 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
@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>{{ $tag->name }} - {{ 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">Tag: {{ $tag->name }}!</h1>
                <p class="lead mb-0">{{ $tag->description }}</p>
            </div>
        </div>
    </header>
@endsection

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

Post

 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
@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>{{ $post->title }} - {{ setting('site.title') }}</title>
@endsection

@section('header')

@endsection

@section('content')
    <div class="col-lg-8">
        <!-- Post content-->
        <article>
            <!-- Post header-->
            <header class="mb-4">
                <!-- Post title-->
                <h1 class="fw-bolder mb-1">{{ $post->title }}</h1>
                <!-- Post meta content-->
                <div class="text-muted fst-italic mb-2">Posted on
                    {{ \Carbon\Carbon::parse($post->created_at)->format('F d, Y') }} by {{ $post->user->name }}</div>
                <!-- Post Tags-->
                @foreach ($post_tags as $tag)
                    <a class="badge bg-secondary text-decoration-none link-light"
                        href="{{ route('tag.index', ['slug' => $tag->slug]) }}">{{ $tag->name }}</a>
                @endforeach
            </header>
            <!-- Preview image figure-->
            <figure class="mb-4"><img class="img-fluid rounded" src="{{ Storage::url($post->featured_image) }}"
                    alt="..." /></figure>
            <!-- Post content-->
            <section class="mb-5">
                {!! $post->content !!}
            </section>
        </article>
        <!-- Comments section-->
        <section class="mb-5">
            <div class="card bg-light">
                <div class="card-body">
                    <!-- Comment form-->
                    <form class="mb-4"><textarea class="form-control" rows="3"
                            placeholder="Join the discussion and leave a comment!"></textarea></form>
                    <!-- Comment with nested comments-->
                    <div class="d-flex mb-4">
                        <!-- Parent comment-->
                        <div class="flex-shrink-0"><img class="rounded-circle"
                                src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                        <div class="ms-3">
                            <div class="fw-bold">Commenter Name</div>
                            If you're going to lead a space frontier, it has to be government; it'll never be
                            private enterprise. Because the space frontier is dangerous, and it's expensive, and
                            it has unquantified risks.
                            <!-- Child comment 1-->
                            <div class="d-flex mt-4">
                                <div class="flex-shrink-0"><img class="rounded-circle"
                                        src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                                <div class="ms-3">
                                    <div class="fw-bold">Commenter Name</div>
                                    And under those conditions, you cannot establish a capital-market evaluation
                                    of that enterprise. You can't get investors.
                                </div>
                            </div>
                            <!-- Child comment 2-->
                            <div class="d-flex mt-4">
                                <div class="flex-shrink-0"><img class="rounded-circle"
                                        src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                                <div class="ms-3">
                                    <div class="fw-bold">Commenter Name</div>
                                    When you put money directly to a problem, it makes a good headline.
                                </div>
                            </div>
                        </div>
                    </div>
                    <!-- Single comment-->
                    <div class="d-flex">
                        <div class="flex-shrink-0"><img class="rounded-circle"
                                src="https://dummyimage.com/50x50/ced4da/6c757d.jpg" alt="..." /></div>
                        <div class="ms-3">
                            <div class="fw-bold">Commenter Name</div>
                            When I look at the universe and all the ways the universe wants to kill us, I find
                            it hard to reconcile that with statements of beneficence.
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </div>
    @include('vendor.sidebar')
@endsection 

Line 37, {!! $post->content !!}. By default, Laravel will print HTML tags as plain text. In order to render them as HTML tags, we need to enable the safe mode by using the syntax {!! !!}.


comments powered by Disqus