How to Create a Blog with Laravel 5+ Part 5 (CRUD Post & Page) Part 1
The moment we’ve been waiting for, the ability to Create, Read, Update and Delete (CRUD) records directly from the database, in the previous part, Part 4 (Authentication) it focused on adding site authentication and making our site secure so it makes more sense that the only user with such privileges are able to implement CRUD, it is also included in our Database Schema Design in Part 3 (Database Schema Design) to include post author ID for each post.
Let’s get started
The first thing we need to do is update our Blog header section to include Menu items for Posts and Pages that link to a form where we can manage content.
_header.blade.php
<!doctype html>
<html lang="{{ config('app.locale') }}">
<head>
...
<!--nothing changed here-->
...
</head>
<body>
<div class="blog-masthead">
<div class="container">
<nav class="blog-nav">
<ul class="nav navbar-nav">
<li><a class="blog-nav-item {{ Request::is('/') ? 'active' : '' }}" href="/">Home</a></li>
<li><a class="blog-nav-item {{ Request::is('show-case') ? 'active' : '' }}" href="/show-case">Show Case</a></li>
<li><a class="blog-nav-item {{ Request::is('services') ? 'active' : '' }}" href="/services">Services</a></li>
<li><a class="blog-nav-item {{ Request::is('contact') ? 'active' : '' }}" href="/contact">Contact</a></li>
</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="#">All Posts</a></li>
<li><a href="#">Add New</a></li>
<li><a href="#">Categories</a></li>
<li><hr/></li>
<li><a href="#">All Pages</a></li>
<li><a href="#">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>
Now that our Menu items are there and ready it’s time to create forms and blade pages, but before that, it would be ideal to create Post and Page Controller first so we’re a step-by-step process and can save us a lot of time.
Instead of creating Controller and then add methods manually, Laravel does offer Resource Controllers
, what it does is it assigns the typical “CRUD” routes to a controller with a single line of code, Woah, handy.
Run the command below to start adding Controllers.
Post Controller
php artisan make:controller PostsController --resource
Page Controller
Before running the command below make sure to backup your current PagesController.php
, you can remove it right after so it won’t cause any error when running the command.
php artisan make:controller PagesController --resource
To view your newly added Controllers
navigate to this directory app/Http/Controllers
.
Great we’re doing good, but at this stage, when you click any of your Blog pages you’ll surely get 404 Error that is because we replace the previous PagesController with a fresh new one, so to add a temporary fix, go and grab your code from the previous file and put it back just right after the new one.
Here’s how it looks on my end.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class PagesController extends Controller
{
/**
* Front page
*/
public function getIndex() {
return view('pages.index');
}
/**
* Show case page
*/
public function getShowCase() {
return view('pages.showcase');
}
/**
* Services page
*/
public function getServices() {
return view('pages.services');
}
/**
* Contact page
*/
public function getContact() {
return view('pages.contact');
}
// ... new code goes here index(), create() and so on.
}
Now that Controllers
have been added successfully, next is we need to update our routes
to read the new resource we’ve just created.
Our routes
will look like this now.
<?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::get('show-case', 'PagesController@getShowCase');
Route::get('services', 'PagesController@getServices');
Route::get('contact', 'PagesController@getContact');
Route::resource('pages', 'PagesController');
Route::resource('posts', 'PostsController');
Auth::routes();
Route List
Artisan command to display list of ruotes, this is gonna be useful soon.
php artisan route:list
views/posts
So here I’ve created a new directory for posts and lists of blade files, I’ve added a comment for each file and it’s self-explanatory.
resources
...
- posts
- - create.blade.php // This file is used to create a new post item
- - edit.blade.php // This file is used to edit selected post item
- - index.blade.php // This file is used to display lists of added posts
- - show.blade.php // This file is used to display newly added post (optional)
...
PostsController.php
Complete PostsController
code, I’ve added comment so I wouldn’t go through each line of code later 😀
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Requests;
use App\Post;
use Session;
class PostsController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
// Fetch data in pagination so only 10 posts per page
// To get all data you may use get() method
$posts = Post::where('post_type', 'post')->paginate( 10 );
return view('posts.index', ['posts' => $posts]);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
// Directly display `posts.create` view blade file
return view('posts.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
$post = new Post;
$post->author_ID = $request->author_ID;
$post->post_type = $request->post_type;
$post->post_title = $request->post_title;
$post->post_slug = $request->post_slug;
$post->post_content = $request->post_content;
$post->category_ID = $request->category_ID;
$post->save();
// Store data for only a single request and destory
Session::flash( 'sucess', 'Post published.' );
// Redirect to `posts.show` route
// Use route:list to view the `Action` or where this routes going to
return redirect()->route('posts.show', $post->id);
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$post = Post::findOrFail( $id );
return view('posts.show', [ 'post' => $post ]);
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
$post = Post::findOrFail( $id );
return view('posts.edit', [ 'post' => $post ]);
}
/**
* 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
$post = Post::findOrFail($id);
$post->author_ID = $request->input('author_ID');
$post->post_type = $request->input('post_type');
$post->post_title = $request->input('post_title');
$post->post_slug = $request->input('post_slug');
$post->post_content = $request->input('post_content');
$post->category_ID = $request->input('category_ID');
$post->save();
Session::flash('success', 'Post updated.');
return redirect()->route('posts.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
$post = Post::findOrFail( $id );
// https://laravel.com/docs/5.4/queries#deletes
$post->delete();
Session::flash('success', 'Post deleted.');
return redirect()->route('posts.index');
}
}
Now that we already have PostsController
added, it’s time to work on our Views
.
_header.blade.php
<!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 {{ Request::is('/') ? 'active' : '' }}" href="/">Home</a></li>
<li><a class="blog-nav-item {{ Request::is('show-case') ? 'active' : '' }}" href="/show-case">Show Case</a></li>
<li><a class="blog-nav-item {{ Request::is('services') ? 'active' : '' }}" href="/services">Services</a></li>
<li><a class="blog-nav-item {{ Request::is('contact') ? 'active' : '' }}" href="/contact">Contact</a></li>
</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>
index.blade.php
@extends('main')
@section('title', '| Posts')
@section('content')
<div class="container">
{{-- Check if current user is logged-in or a guest --}}
@if (Auth::guest())
<p class="mt-5">Cheatn?, please <a href="/login/">login</a> to continue.</p>
@else
<div class="blog-header">
<h1 class="blog-title">Posts <a class="btn btn-sm btn-primary" href="{{ route('posts.create') }}">Add New</a></h1>
</div>
<div class="row">
<div class="col-md-12">
<table class="table">
<tr>
<th>Title</th>
<th>Author</th>
<th>Content</th>
<th>Category</th>
<th>Date</th>
<th> </th>
</tr>
<tr>
{{-- Blade if and else --}}
@if( $posts->count() )
{{-- Blade foreach --}}
@foreach( $posts as $post )
<tr>
<td>
<strong>
<a href="{{ route('posts.edit', $post->id) }}">
{{ $post->post_title }}
</a>
</strong>
</td>
<td>{{ $post->author_ID }}</td>
<td>
@if ( strlen( $post->post_content ) > 60 )
{{ substr( $post->post_content, 0, 60 ) }} ...
@else
{{ $post->post_content }}
@endif
</td>
<td>
<a href="#">
{{ $post->category_ID }}
</a>
</td>
<td>Published {{ date( 'j/m/Y', strtotime( $post->created_at ) ) }}</td>
<td>
<form class="d-inline" action="{{ route('posts.destroy', $post->id) }}" method="POST">
{{ csrf_field() }}
{{ method_field('DELETE') }}
<input type="submit" value="Delete" class="btn btn-sm btn-danger" />
</form>
<a class="btn btn-sm btn-info" href="{{ route('posts.edit', $post->id) }}">Edit</a>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="5">No post has been added yet!</td>
</tr>
@endif
</tr>
</table>
{{ $posts->links() }}
</div>
</div>
@endif
</div>
@endsection
create.blade.php
@extends('main')
@section('title', '| Add New Post')
@section('content')
<div class="container">
{{-- Check if current user is logged-in or a guest --}}
@if (Auth::guest())
<p class="mt-5">Please <a href="/login/">login</a> to add a new post.</p>
@else
<div class="blog-header">
<h1 class="blog-title">Add New Post</h1>
</div>
<div class="row">
<div class="push-md-2 col-md-8">
<form action="{{ route('posts.store') }}" method="POST">
{{ csrf_field() }}
<input type="hidden" name="author_ID" value="{{ Auth::id() }}" />
<input type="hidden" name="post_type" value="post" />
<div class="form-group{{ $errors->has('post_title') ? ' has-error' : '' }}">
<label for="post_title">Title</label> <br/>
<input type="text" name="post_title" id="post_title" value="{{ old('post_title') }}" />
@if ($errors->has('post_title'))
<span class="help-block">
<strong>{{ $errors->first('post_title') }}</strong>
</span>
@endif
</div>
<div class="form-group{{ $errors->has('post_slug') ? ' has-error' : '' }}">
<label for="post_slug">Slug</label> <br/>
<input type="text" name="post_slug" id="post_slug" value="{{ old('post_slug') }}" />
@if ($errors->has('post_slug'))
<span class="help-block">
<strong>{{ $errors->first('post_slug') }}</strong>
</span>
@endif
</div>
<div class="form-group{{ $errors->has('post_content') ? ' has-error' : '' }}">
<label for="post_content">Content</label> <br/>
<textarea name="post_content" id="post_content" cols="80" rows="6">{{ old('post_content') }}</textarea>
@if ($errors->has('post_content'))
<span class="help-block">
<strong>{{ $errors->first('post_content') }}</strong>
</span>
@endif
</div>
<div class="form-group">
<label for="title">Category</label> <br/>
<select name="category_ID" id="category_ID">
<option value="1">1</option>
<option value="2">2</option>
</select>
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="Publish" />
<a class="btn btn-primary" href="{{ route('posts.index') }}">Cancel</a>
</div>
</form>
</div>
</div>
@endif
</div>
@endsection
edit.blade.php
@extends('main')
@section('title', '| Edit Post')
@section('content')
<div class="container">
{{-- Check if current user is logged-in or a guest --}}
@if (Auth::guest())
<p class="mt-5">Cheatn?, please <a href="/login/">login</a> to continue.</p>
@else
<div class="blog-header">
<h1 class="blog-title">Edit Post <a class="btn btn-sm btn-primary" href="{{ route('posts.show', $post->id) }}">Preview Changes</a></h1>
</div>
<div class="row">
<div class="push-md-2 col-md-8">
{{--
Check route:list for `posts.update` for more info
URL is posts/{post}, `{post}` meaning that we have to supply ID
--}}
<form action="{{ route('posts.update', $post->id) }}" method="POST">
{{ csrf_field() }}
{{--
HTML forms do not support PUT, PATCH or DELETE actions.
So, when defining PUT, PATCH or DELETE routes that are called from an HTML form,
you will need to add a hidden _method field to the form.
--}}
{{ method_field('PUT') }}
<input type="hidden" name="author_ID" value="{{ Auth::id() }}" />
<input type="hidden" name="post_type" value="post" />
<div class="form-group{{ $errors->has('post_title') ? ' has-error' : '' }}">
<label for="post_title">Title</label> <br/>
<input type="text" name="post_title" id="post_title" value="{{ $post->post_title }}" />
@if ($errors->has('post_title'))
<span class="help-block">
<strong>{{ $errors->first('post_title') }}</strong>
</span>
@endif
</div>
<div class="form-group{{ $errors->has('post_slug') ? ' has-error' : '' }}">
<label for="post_slug">Slug</label> <br/>
<input type="text" name="post_slug" id="post_slug" value="{{ $post->post_slug }}" />
@if ($errors->has('post_slug'))
<span class="help-block">
<strong>{{ $errors->first('post_slug') }}</strong>
</span>
@endif
</div>
<div class="form-group{{ $errors->has('post_content') ? ' has-error' : '' }}">
<label for="post_content">Content</label> <br/>
<textarea name="post_content" id="post_content" cols="80" rows="6">{{ $post->post_content }}</textarea>
@if ($errors->has('post_content'))
<span class="help-block">
<strong>{{ $errors->first('post_content') }}</strong>
</span>
@endif
</div>
<div class="form-group">
<label for="title">Category</label> <br/>
<select name="category_ID" id="category_ID">
<option value="1" {{ 1 == $post->category_ID ? 'selected="selected"' : '' }}>1</option>
<option value="2" {{ 2 == $post->category_ID ? 'selected="selected"' : '' }}>2</option>
</select>
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="Update" />
<a class="btn btn-primary" href="{{ route('posts.index') }}">Cancel</a>
</div>
</form>
</div>
</div>
@endif
</div>
@endsection
show.blade.php
@extends('main')
@section('title', '| Post')
@section('content')
<div class="container">
{{-- Check if current user is logged-in or a guest --}}
@if (Auth::guest())
<p class="mt-5">Cheatn?, please <a href="/login/">login</a> to continue.</p>
@else
<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-12 blog-main">
<div class="blog-content">
{{-- Inserts HTML line breaks before all newlines in a string --}}
{!! nl2br( $post->post_content ) !!}
</div>
</div>
</div>
@endif
</div>
@endsection
{{ vs {!!
By default, Blade
{{ }}
statements are automatically sent through PHP’s htmlentities function to prevent XSS attacks. If you do not want your data to be escaped, you may use the following{!! !!}
Wow what a long article, I think let’s break it into two pieces :-), see ya in Part 2, which focuses on Pages Controllers and Views.
That’s it happy coding ^_^