How to Create a Blog with Laravel 5+ Part 5 (CRUD Post & Page) Final Part

Advertisement

This is the final part of our Part 5 (Post & Page CRUD) tutorial series, on the previous parts, Part 1 (CRUD Post) it focused on PostsController and CRUD Post and Part 2 (CRUD Page) also focused on PagesController and CRUD Page, as you’ve noticed those two previous parts focuses only on back-end functionality, we have left one important functionality which is the front-end display and that’s what we’d like to address in this final part.

Let’s get started

app/Helpers/Helper.php

Helper or commonly referred to as functions, we’ll use this file to include or add our custom codes for later use, though, Helper code is a user defined, it’s a good practice to separate custom codes from Laravel’s files.

Please add the Helper file above and paste the below code.


<?php

namespace App\Helpers;

use App\Post;

class Helper
{
	/**
     * Get lists of pages
     */ 
    public static function get_pages()
    {
        $pages = Post::where('post_type', 'page')->get();
        
        return $pages;
    }

}

config/app.php

Instead of calling App\Helpers\Helper::get_pages(), let’s add Aliases for our Helper Class and we can call Aliases key directly, like so Helper::get_pages(), it’s more cleaner this way.


'aliases' => [
	...

	'Helper' => App\Helpers\Helper::class,
],

partials/_header.blade.php

Now lets update _header.blade.php with Pages and Recent posts links to its specific pages.


<!doctype html>
<html lang="{{ config('app.locale') }}">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        
        <!-- CSRF Token -->
        <meta name="csrf-token" content="{{ csrf_token() }}">
        
        <title>{{ config('app.name') }} @yield('title')</title>
        
        <!-- Bootstrap core CSS -->
        <link rel="stylesheet" href="{{ asset('css/app.css') }}" />
        
        <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
        <link rel="stylesheet" href="{{ asset('css/ie10-viewport-bug-workaround.css') }}" />
        
        <!-- Custom styles for this template -->
        <link rel="stylesheet" href="{{ asset('css/blog.css') }}" />
        
        @yield('stylesheet')
    </head>
    <body>
        
        <div class="blog-masthead">
            <div class="container">
                <nav class="blog-nav">
                    <ul class="nav navbar-nav">
                        <li><a class="blog-nav-item {{ null == Request::query() ? 'active' : '' }}" href="/">Home</a></li>
                        
                        @if( Helper::get_pages() )
                            @foreach( Helper::get_pages() as $page )
                                <li><a class="blog-nav-item {{ $page->id == Request::query('page_id') ? 'active' : '' }}" href="/?page_id={{ $page->id }}">{{ $page->post_title }}</a></li>
                            @endforeach
                        @endif
                    </ul>
                    
                    <!-- Right Side Of Navbar -->
                    <ul class="nav navbar-nav navbar-right">
                        <!-- Authentication Links -->
                        @if (Auth::guest())
                            <li><a class="blog-nav-item {{ Request::is('login') ? 'active' : '' }}" href="{{ route('login') }}">Login</a></li>
                            <li><a class="blog-nav-item {{ Request::is('register') ? 'active' : '' }}" href="{{ route('register') }}">Register</a></li>
                        
                        @else

                            <li class="dropdown">
                                <a href="#" class="blog-nav-item dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
                                    {{ Auth::user()->name }} <span class="caret"></span>
                                </a>

                                <ul class="dropdown-menu" role="menu">
                                    <li><a href="{{ route('posts.index') }}">All Posts</a></li>
                                    <li><a href="{{ route('posts.create') }}">Add New</a></li>
                                    <li><a href="{{ route('categories.index') }}">Categories</a></li>
                                    
                                    <li><hr/></li>
                                    
                                    <li><a href="{{ route('pages.index') }}">All Pages</a></li>
                                    <li><a href="{{ route('pages.create') }}">Add New</a></li>
                
                                    <li><hr/></li>
                                    
                                    <li>
                                        <a class="blog-nav-item" href="{{ route('logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();">Logout</a>
                                        
                                        <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
                                            {{ csrf_field() }}
                                        </form>
                                    </li>
                                </ul>
                            </li>
                        @endif
                    </ul>
                </nav>
            </div>
        </div>

        {{--
            Check if there is a success Session key
            If So display the Session key value
            
            More to this later
        --}}
        <div class="container">
            <div class="row">
                <div class="col-md-12">

                    @if( Session::has('success') )
                        <div class="mt-5 alert alert-success" role="alert">
                            {{ Session::get('success') }}
                        </div>
                    @endif

                </div>
            </div>
        </div>

Now that our _header.blade.php is ready, we need to work on our Pages a bit.

views/pages

Now that our pages and post is dynamically fetched from the database, we can remove previous static blade files for the page and add couple blade files for Page and Blog post display.

Below is how our views/pages look like now.


|-- create.blade.php
|-- edit.blade.php
|-- frontpage.blade.php
|-- index.blade.php
|-- page.blade.php		// This is used for single page display
|-- show.blade.php
|-- single.blade.php		// This is used for single blog post single page display

frontpage.blade.php

Let’s update our frontpage.blade.php to display recently added post and navigation.


@extends('main')

@section('title', '| Blog Tutorial')

@section('content')
    <div class="blog-header">
        <h1 class="blog-title">The Bootstrap Blog</h1>
        <p class="lead blog-description">The official example template of creating a blog with Bootstrap.</p>
    </div>
    
    <div class="row">
        <div class="col-sm-8 blog-main">
            
            @if( $posts->count() )
                @foreach( $posts as $post )
                    
                    <div class="blog-post">
                        <h2 class="blog-post-title">
                            <a href="/?p={{ $post->id }}">{{ $post->post_title }}</a>
                        </h2>
                        <p class="blog-post-meta">{{ date('M j, Y', strtotime( $post->created_at )) }} by <a href="#">{{ $post->author_ID }}</a></p>
                    
                        <div class="blog-content">
                            {!! nl2br( $post->post_content ) !!}
                        </div>
                    </div>

                @endforeach
            @else

                <p>No post added yet!</p>

            @endif

            {{-- Display pagination only if more than the required pagination --}}
            @if( $posts->total() > 6 )
                <nav>
                    <ul class="pager">
                        @if( $posts->firstItem() > 1 )
                            <li><a href="{{ $posts->previousPageUrl() }}">Previous</a></li>
                        @endif
                        
                        @if( $posts->lastItem() < $posts->total() )
                            <li><a href="{{ $posts->nextPageUrl() }}">Next</a></li>
                        @endif
                    </ul>
                </nav>
            @endif

        </div><!-- /.blog-main -->
        
        <!--Sidebar-->
        @include('partials._sidebar')

    </div><!-- /.row -->
@endsection

page.blade.php

This file handles the single Page display


@extends('main')

@section('title')
    | {{ $page->post_title }}
@endsection

@section('content')
    
    <div class="blog-header">
        <h1 class="blog-title">{{ $page->post_title }}</h1>
        <p>{{ date('M j, Y', strtotime( $page->created_at )) }} <a href="{{ route('posts.edit', $page->id) }}">{Edit}</a></p>
    </div>
    
    <div class="row">
        <div class="col-sm-8 blog-main">
            
            <div class="blog-content">
                {!! nl2br( $page->post_content ) !!}
            </div><!-- /.blog-post -->
            
        </div><!-- /.blog-main -->
        
        <!--Sidebar-->
        @include('partials._sidebar')
    </div><!-- /.row -->

@endsection

single.blade.php

This file handles the single post display


@extends('main')

@section('title')
    | {{ $post->post_title }}
@endsection

@section('content')
   
    <div class="blog-header">
        <h1 class="blog-title">{{ $post->post_title }}</h1>
        <p>{{ $post->category_ID }} / {{ date('M j, Y', strtotime( $post->created_at )) }} <a href="{{ route('posts.edit', $post->id) }}">{Edit}</a></p>
    </div>
    
    <div class="row">
        <div class="col-sm-8 blog-main">
            
            <div class="blog-content">
                {!! nl2br( $post->post_content ) !!}
            </div><!-- /.blog-post -->
            
            <section class="mt-5" id="comment">
                <p>Comment goes here!</p>
            </section>

        </div><!-- /.blog-main -->
        
        <!--Sidebar-->
        @include('partials._sidebar')
    </div><!-- /.row -->

@endsection
Advertisement

Finishing…, horray

We’re pretty much there, we just need to add and update few files and we’re done.

views/errors

Now that we handle most of our data dynamically that also means we’re prone to errors including 404 not found, Laravel way of handling an error is pretty much straight forward, you just have to add blade file under views/errors related to each error and in our case 404.


|-- 404.blade.php

PagesController.php

We can now safely remove static page methods that are added to this file and add a few lines of code to handle dynamic display for our Page and Single blog post.


<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Controllers\Controller;
use App\Http\Requests;
use App\Post;
use Session;

class PagesController extends Controller
{

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        // Fetch records in pagination so only 10 pages per page
        // To get all records you may use get() method
        $pages = Post::where('post_type', 'page')->paginate( 10 );

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

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        // Directly display `pages.create` view blade file
        return view('pages.create');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        // Validate and filter user inputted data
        // Refer to `References` for more info
        $this->validate($request, [
            'post_title'        => 'required',
            'post_slug'         => 'required|alpha_dash|max:200|unique:posts,post_slug',
            'post_content'      => 'required',
        ]);

        // Create a new Post Model initialization
        // There are many ways to coke an Egg and same in storing data to database in Laravel
        // You might use or prefer this one https://laravel.com/docs/5.4/queries#inserts
        // I just love using Eloquent
        $page = new Post;

        $page->author_ID        = $request->author_ID;
        $page->post_type        = $request->post_type;
        $page->post_title       = $request->post_title;
        $page->post_slug        = $request->post_slug;
        $page->post_content     = $request->post_content;

        $page->save();
        
        // Store data for only a single request and destory
        Session::flash( 'sucess', 'Page published.' );

        // Redirect to `pages.show` route
        // Use route:list to view the `Action` or where this routes going to
        return redirect()->route('pages.show', $page->id);
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        $page = Post::findOrFail( $id );

        return view('pages.show', [ 'page' => $page ]);
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        $page = Post::findOrFail( $id );

        return view('pages.edit', [ 'page' => $page ]);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        $this->validate($request, [
            'post_title'        => 'required',
            'post_slug'         => 'required|alpha_dash|max:200|unique:posts,post_slug,'.$id,
            'post_content'      => 'required',
        ]);

        // You might prefer to use this one instead https://laravel.com/docs/5.4/queries#updates
        // You choose
        $page = Post::findOrFail($id);

        $page->author_ID        = $request->input('author_ID');
        $page->post_type        = $request->input('post_type');
        $page->post_title       = $request->input('post_title');
        $page->post_slug        = $request->input('post_slug');
        $page->post_content     = $request->input('post_content');

        $page->save();

        Session::flash('success', 'Page updated.');

        return redirect()->route('pages.edit', $id);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        // Retrieve records and throw an exception if a model is not found
        $page = Post::findOrFail( $id );

        // https://laravel.com/docs/5.4/queries#deletes
        $page->delete();
        
        Session::flash('success', 'Page deleted.');
        
        return redirect()->route('pages.index');
    }


    /**
     * Front page
     */
    public function getIndex( Request $request ) {

        // Get query string
        $page_id    = $request->query('page_id');
        $post_id    = $request->query('p');

        // If page_id is viewed use `pages.page.php` file to handle the display
        // otherwise use and display 404.blade.php file
        if( $page_id ) :

            $posts = Post::where('id', $page_id)
                        ->where('post_type', 'page')
                        ->first();
            
            // Check if page exist
            if( $posts )     
                return view('pages.page', [ 'page' => $posts ]);
            else
                return view('errors.404');

        // If post_id is viewed use `pages.single.php` file to handle the display
        // otherwise use and display 404.blade.php file
        elseif ( $post_id ) :

            $posts = Post::where('id', $post_id)
                        ->where('post_type', 'post')
                        ->first();

            // Check if page exist
            if( $posts )     
                return view('pages.single', [ 'post' => $posts ]);
            else
                return view('errors.404');

        // If none of the above is true, then display most recently added Blot posts
        else :

            $posts = Post::where('post_type', 'post')
                    ->paginate( 6 );
            
            // It replaces the previous `index.blade.php` blade file that is now used in displaying lists of added pages
            return view('pages.frontpage', [ 'posts' => $posts ]);

        endif;
    }

}

And one final step, update our routes.

routes/web.php

Much more cleaner now, now more static routes


<?php

/*
|--------------------------------------------------------------------------
| 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('/', 'PagesController@getIndex')->name('home');
Route::resource('pages', 'PagesController');
Route::resource('posts', 'PostsController');
Route::resource('categories', 'CategoriesController');

Auth::routes();

That’s it, happy coding, see ya in the next part.

Advertisement