How to Upload Image for Blog Post in Laravel 5+ (Featured Image)

Advertisement

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
Advertisement

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 ^_^

References and Credits

File Storage

Advertisement