How to Upload Image for Blog Post in Laravel 5+ (Featured Image)
In this tutorial I’ll discuss on how we can easily add and upload an image into our Blog, our Blog Schema Design does not support or don’t have a field for featured image yet, so we’ll have to update it first.
To achieve this functionality we do have 3 options, use disk()->put()
, file()->store()
and the last one is external script, which is personally preffered Intervention Image
and that is what we’re going to use here, so what is Intervention Image?
Intervention Image is an open source PHP image handling and manipulation library.
It provides an easier and expressive way to create, edit, and compose images and supports currently the two most common image processing libraries GD Library and Imagick.
Let’s get started
Intervention Image Installation
Run the below command line to start installing Intervention Image package.
composer require intervention/image
Laravel Integration
Intervention Image is an external package so we have to integrate it with Laravel in order for it to work.
Open config/app.php
file and scroll down below in 'providers' => [...]
array add the below line of code.
Intervention\Image\ImageServiceProvider::class
Next is to add the facade of this package to the 'aliases' => [...]
array, just below the providers
array list.
'Image' => Intervention\Image\Facades\Image::class
And that should be it, now the Image Class will be auto-loaded by Laravel.
Our previous Database Schema Design didn’t support featured image for posts, so let’s add a new column to our table first.
Run the below command line and open the newly created migration under database/migrations/[DATE]_add_post_thumbnail_to_posts_table.php
php artisan make:migration add_post_thumbnail_to_posts_table --table=posts
[DATE]_add_post_thumbnail_to_posts_table.php
Update the file code to this.
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddPostThumbnailToPostsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('posts', function (Blueprint $table) {
$table->string('post_thumbnail')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('posts', function (Blueprint $table) {
$table->dropColumn('post_thumbnail');
});
}
}
Once copied don’t forget to run the migration.
php artisan migrate
Image Intervention is set, the new column for post featured image is also added, let’s work on Controller and Views now.
posts/create.blade.php
With an updated category lists, now fetch category dynamically and form enctype now also support file upload.
@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 enctype="multipart/form-data" 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/>
<?php $categories = Helper::get_categories(); ?>
<select name="category_ID" id="category_ID">
<?php
if( $categories ) {
foreach( $categories as $category ) {
?>
<option value="{{ $category->id }}">{{ $category->category_name }}</option>
<?php
}
}
?>
</select>
</div>
<div class="form-group">
<label for="post_thumbnail">Thumbnail</label> <br/>
<input type="file" name="post_thumbnail" id="post_thumbnail" />
</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
posts/edit.blade.php
Also with an updated category lists, now fetch category dynamically and form enctype now also support file upload.
@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">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 enctype="multipart/form-data" 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/>
<?php $categories = Helper::get_categories(); ?>
<select name="category_ID" id="category_ID">
<?php
if( $categories ) {
foreach( $categories as $category ) {
?>
<option value="{{ $category->id }}" {{ $category->id == $post->category_ID ? 'selected="selected"' : '' }}>{{ $category->category_name }}</option>
<?php
}
}
?>
</select>
</div>
<div class="form-group">
<label for="post_thumbnail">Thumbnail</label> <br/>
<input type="file" name="post_thumbnail" id="post_thumbnail" />
</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
posts/show.blade.php
Added post thumbnail on this file and also updated Category ID
and display Category Name
instead.
@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">Cheatn?, please <a href="/login/">login</a> to continue.</p>
@else
<div class="blog-header">
<h1 class="blog-title">{{ $post->post_title }}</h1>
<p>{{ Helper::get_category( $post->category_ID )->category_name }} / {{ 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-thumbnail">
<img src="/uploads/{{ $post->post_thumbnail }}" alt="{{ $post->post_title }}" />
</div>
<div class="blog-content">
{{-- Inserts HTML line breaks before all newlines in a string --}}
{!! nl2br( $post->post_content ) !!}
</div>
</div>
</div>
@endif
</div>
@endsection
articles/single.blade.php
The most important of all, diplay featured image in the blog front page section.
@extends('main')
@section('title')
| {{ $post->post_title }}
@endsection
@section('content')
<div class="blog-header">
<h1 class="blog-title">{{ $post->post_title }}</h1>
<p>{{ Helper::get_category( $post->category_ID )->category_name }} / {{ 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">
@if( $post->post_thumbnail )
<div class="blog-thumbnail">
<img src="/uploads/{{ $post->post_thumbnail }}" alt="{{ $post->post_title }}" />
</div>
@endif
<div class="blog-content">
{!! nl2br( $post->post_content ) !!}
</div><!-- /.blog-post -->
<section class="mt-5" id="respond">
<h2>Comments</h2>
{{--display approved comments--}}
<?php
echo Helper::get_comments( $post->id );
?>
</section>
<section class="mt-5" id="comment">
{{-- display comment form --}}
@includeIf('comments.form', ['post_id' => $post->id])
</section>
</div><!-- /.blog-main -->
<!--Sidebar-->
@include('partials._sidebar')
</div><!-- /.row -->
@endsection
Controllers/PostsControllers.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Requests;
use App\Post;
use Image;
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;
// Check if file is present
if( $request->hasFile('post_thumbnail') ) {
$post_thumbnail = $request->file('post_thumbnail');
$filename = time() . '.' . $post_thumbnail->getClientOriginalExtension();
Image::make($post_thumbnail)->resize(600, 600)->save( public_path('uploads/' . $filename ) );
// Set post-thumbnail url
$post->post_thumbnail = $filename;
}
$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');
// Check if file is present
if( $request->hasFile('post_thumbnail') ) {
$post_thumbnail = $request->file('post_thumbnail');
$filename = time() . '.' . $post_thumbnail->getClientOriginalExtension();
Image::make($post_thumbnail)->resize(600, 600)->save( public_path('/uploads/' . $filename ) );
// Set post-thumbnail url
$post->post_thumbnail = $filename;
}
$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');
}
}
One more thing…
Don’t forget to add new directory inside public/
called uploads
.
Bonus, remove post thumbnail
If however, you’d like to remove the image that already exists or uploaded already, use the below code.
$file = public_path('uploads/' . $post->post_thumbnail );
// Do some checking here
if (File::exists($file)) {
unlink($file);
}
Upload for User
If you’re looking to add user avatar, it’s the same process for blog featured image.
That’s it and we’re done, happy coding ^_^