How to Create a Blog with Laravel 5+ Part 5 (CRUD Post & Page) Final Part
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
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.