Basic Image Management Part 2

Access all tutorials in sprocket icon.

July 29, 2015 from author Bill Keck.

Basic Image Management

Part 1.

Part 2.

part 3.

So now we get into the fun stuff.

In normal workflow, you may find yourself writing the store method first, and simply leaving validation out of it until after you get the store process working. But this is a little more complicated of a method for us, since we are dealing with separate files that need to be saved, in this case images, we are going to do the validation first.

The Request Class

We’re going to start by making a request class that will handle validation for the create form. From the command line type:



php artisan make:request CreateImageRequest

This will create and place the file within your app/Http/Requests folder.

Next we are going to modify it to the following:



 <?php

namespace App\Http\Requests;

use App\Http\Requests\Request;
use App\Marketingimage;

class CreateImageRequest extends Request
{
   /**
    * Determine if the user is authorized to make this request.
    *
    * @return bool
    */
   public function authorize()
   {
       return true;
   }

   /**
    * Get the validation rules that apply to the request.
    *
    * @return array
    */
   public function rules()
   {
       return [
           'image_name' => 'alpha_num | required | unique:marketing_images',
           'mobile_image_name' => 'alpha_num | required | unique:marketing_images',
           'is_active' => 'boolean',
           'is_featured' => 'boolean',
           'image' => 'required | mimes:jpeg,jpg,bmp,png | max:1000',
           'mobile_image' => 'required | mimes:jpeg,jpg,bmp,png | max:1000'
       ];
   }
}

One of things I truly love about the Laravel framework is how they handle validation. If you are not already familiar with this, you will see how simple it is.

You can see we set the authorize method to:



       return true;

This allows the request to pass. If we set a condition on it, we can restrict it to, for example, logged in users only. But we will save examples of that for a separate tutorial on middleware.

Next we have the rules method, which will impose the validation on the form fields. I won’t go too deep here, you can check validation in the laravel docs.

If you need to learn how it works. Just as refresher, you can see the pipe character separates the rules, and when we use unique as a rule, we specify the tablename the record is found in. Pretty simple stuff.

Obviously, we have also set size limits on the files. You can find a complete list of validation rules in the docs.

The Store Method

Anyway, we are now ready to work with our store method on our MarketingImageController:



public function store(CreateImageRequest $request)
{
   //create new instance of model to save from form

   $marketingImage = new Marketingimage([
       'image_name'        => $request->get('image_name'),
       'image_extension'   => $request->file('image')->getClientOriginalExtension(),
       'mobile_image_name' => $request->get('mobile_image_name'),
       'mobile_extension'  => $request->file('mobile_image')->getClientOriginalExtension(),
       'is_active'         => $request->get('is_active'),
       'is_featured'       => $request->get('is_featured'),

   ]);

   //define the image paths

   $destinationFolder = '/imgs/marketing/';
   $destinationThumbnail = '/imgs/marketing/thumbnails/';
   $destinationMobile = '/imgs/marketing/mobile/';

   //assign the image paths to new model, so we can save them to DB

   $marketingImage->image_path = $destinationFolder;
   $marketingImage->mobile_image_path = $destinationMobile;

   // format checkbox values and save model

   $this->formatCheckboxValue($marketingImage);
   $marketingImage->save();

   //parts of the image we will need

   $file = Input::file('image');

   $imageName = $marketingImage->image_name;
   $extension = $request->file('image')->getClientOriginalExtension();

   //create instance of image from temp upload

   $image = Image::make($file->getRealPath());

   //save image with thumbnail

   $image->save(public_path() . $destinationFolder . $imageName . '.' . $extension)
       ->resize(60, 60)
       // ->greyscale()
       ->save(public_path() . $destinationThumbnail . 'thumb-' . $imageName . '.' . $extension);

   // now for mobile

   $mobileFile = Input::file('mobile_image');

   $mobileImageName = $marketingImage->mobile_image_name;
   $mobileExtension = $request->file('mobile_image')->getClientOriginalExtension();

   //create instance of image from temp upload
   $mobileImage = Image::make($mobileFile->getRealPath());
   $mobileImage->save(public_path() . $destinationMobile . $mobileImageName . '.' . $mobileExtension);


   // Process the uploaded image, add $model->attribute and folder name

   flash()->success('Marketing Image Created!');

   return redirect()->route('marketingimage.show', [$marketingImage]);
}

Ok, let’s tackle this beast. We’ll start with the function signature:



public function store(CreateImageRequest $request)

This is obviously pulling in an instance of the request object as $request, so we have that available to us to help us create the new instance of the model:



//create new instance of model to save from form

$marketingImage = new Marketingimage([
   'image_name'        => $request->get('image_name'),
   'image_extension'   => $request->file('image')->getClientOriginalExtension(),
   'mobile_image_name' => $request->get('mobile_image_name'),
   'mobile_extension'  => $request->file('mobile_image')->getClientOriginalExtension(),
   'is_active'         => $request->get('is_active'),
   'is_featured'       => $request->get('is_featured'),

]);

I do a lot of commenting on this method, it’s easy to get confused. Anyway, we create a new instance of Marketingimage and we set the model properties by using our instance of $request.

This is simply pulling in the data from the form, once it’s passed validation.

Next we need to define the image paths:



//define the image paths

$destinationFolder = '/imgs/marketing/';
$destinationThumbnail = '/imgs/marketing/thumbnails/';
$destinationMobile = '/imgs/marketing/mobile/';

I tried to make these names as intuitive as possible. Next we are going to assign these to the model, so we can save them in the DB.



//assign the image paths to new model, so we can save them to DB

$marketingImage->image_path = $destinationFolder;
$marketingImage->mobile_image_path = $destinationMobile;

Now we didn’t have to do it this way. We could have provided the path information in the form or we could have skipped it entirely. But I find that keeping the path in the DB makes it easier to work with the image when I want to use it in my views, so that is why I’m doing it this way.

Next we format the checkbox values and save:



// format checkbox values and save model

$this->formatCheckboxValue($marketingImage);
$marketingImage->save();

You can see I’m handing in the model instance, $marketingImage into the formatCheckboxValue method, which is a method I created to make sure the checkbox is handled properly:



public function formatCheckboxValue($marketingImage)
{

   $marketingImage->is_active = ($marketingImage->is_active == null) ? 0 : 1;
   $marketingImage->is_featured = ($marketingImage->is_featured == null) ? 0 : 1;
}

We can only save 0 or 1 because of data type in our DB, so we need to make sure that we are converting that properly from the checkbox on the form. We hand in the model instance and then set the properties accordingly.

Then, back in our store method, we simply save:



$marketingImage->save();

And that takes care of the DB record, however, we still have to deal with the file. First we work on some parts of the image we will need:



//parts of the image we will need

$file = Input::file('image');

$imageName = $marketingImage->image_name;
$extension = $request->file('image')->getClientOriginalExtension();

Alternatively, you could inline some of this, but then you get very long lines of code and also, this just makes it easier to follow.

Now we are going to use the Image library to help us out from here. We will create an instance of the image from the upload:



//create instance of image from temp upload

$image = Image::make($file->getRealPath());

Next we’re going to save it, make a thumbnail and save the thumbnail by chaining the methods:



//save image with thumbnail
$image->save(public_path() . $destinationFolder . $imageName . '.' . $extension)
   ->resize(60, 60)
   // ->greyscale()
   ->save(public_path() . $destinationThumbnail . 'thumb-' . $imageName . '.' . $extension);

So we can break this apart to understand it. The first part saves the primary image:



$image->save(public_path() . $destinationFolder . $imageName . '.' . $extension)

I’m using public_path() to get me up to the point where I need to define the destination folder, which, if you remember, we set to /imgs/marekting/.

We concatenate the bits of the image we need and this gives us everything we need. It’s concise easy to follow code.

Since we also want to create a thumbnail, we have chained that method:



->resize(60, 60)

This will create a 60 by 60 version of the image. Obviously, if you want to set this to a different number, you can. You could also create a field in your form for users to specify the height and width of the thumbnails, but that’s beyond our scope here.

I included the commented out chained method for making the thumbnail greyscale, but you can see that there. If you want to have black and white thumbnails, just uncomment that line.

Next we save the thumbnail:



->save(public_path() . $destinationThumbnail . 'thumb-' . $imageName . '.' . $extension);

So now we need to do the same for our mobile image:



//create instance of image from temp upload
$mobileImage = Image::make($mobileFile->getRealPath());
$mobileImage->save(public_path() . $destinationMobile . $mobileImageName . '.' . $mobileExtension);

Since this is like the primary image minus the thumbnail, we’ll leave it at that.

Ok, so next we have a flash message:



flash()->success('Marketing Image Created!');

Now that only works if you have Jefferey Wey’s flash package installed and call it in the master page view.

If you don’t have the package, then don’t include that line.

Finally, after saving both the model and the image files, we redirect to the show page:



return redirect()->route('marketingimage.show', [$marketingImage]);

With that, you should be able to test uploading and saving an image, a related thumbnail, and a mobile image by visiting the form at:



yourdomain.com/marketingimage/create

You can verify that the images were created by looking inside the folders where the image is supposed to reside.

So obviously the store method is doing a lot. I don’t have a lot of error checking, and you could possibly make it more robust by working on that. I’m not sure it’s necessary though.

Since laravel does such a good job of separating the form validation from the store method, it never hits the method if validation fails.

So what else can go wrong? Well the file could fail to be saved. This could be because the disk on the filesystem is full, or you have a permissions error, or a typo in your path or folder names.

These problems are difficult to troubleshoot because you might not get the corresponding error message. Anyway, those are common sources of problems, if you need to troubleshoot.

You can see that while nothing in image management is especially difficult, it is fairly involved.

The next logical step for us is to build the show method on the controller and the corresponding view.

The Show Method

Ok, here is the show method:



public function show($id)
{
   $marketingImage = Marketingimage::findOrFail($id);

   return view('marketingimage.show', compact('marketingImage'));
}

This is straightforward Laravel retrieval of the model instance and returning it to the view. Notice the use of findOrFail, which will return a ModelNotFoundException, which you can handle in your Handler.php file in app/Exceptions. We will cover this in a separate lesson.

The Show View

Ok, moving on to the view:



@extends('layouts.master')


@section('content')

   {!! Breadcrumb::withLinks(['Home'   => '/',
   'marketing images' => '/marketingimage',
   "show $marketingImage->image_name.$marketingImage->image_extension"
   ]) !!}

    <div>

{{ $marketingImage->image_name }} :  <br>

        <img src="/imgs/marketing/{{ $marketingImage->image_name . '.' .
         $marketingImage->image_extension . '?'. 'time='. time() }}">

    </div>

    <div>

       {{ $marketingImage->image_name }} - thumbnail :  <br>

        <img src="/imgs/marketing/thumbnails/{{ 'thumb-' . $marketingImage->image_name . '.' .
    $marketingImage->image_extension . '?'. 'time='. time() }}">

    </div>

    <div>

       {{ $marketingImage->mobile_image_name }} - mobile :  <br>

        <img src="/imgs/marketing/mobile/{{ $marketingImage->mobile_image_name . '.' .
         $marketingImage->mobile_extension . '?'. 'time='. time() }}">

    </div>

@endsection

You can delete the Breadcrumb helper if you are not using that package.

So there is no fancy front-end stuff here, just basic output to show what we have. Since we have sent an instance of $marketingImage to the controller, we use blade syntax call the name of the image:

{{ $marketingImage->image_name }}

Next we call the primary image:



 <img src="/imgs/marketing/{{ $marketingImage->image_name . '.' .
                                                $marketingImage->image_extension .
                                               '?'. 'time='. time() }}">

You can see we just use our blade {{ to get us to echo the image name and extension, which we have recorded in our database. By keeping the extension in the DB, we can use a number of different extensions and be covered in each case, instead of hardcoding .jpg for example.

You can also see we added a get variable, ‘?’. ‘time=’. time(). I’m doing this to prevent image caching, since it will append the time on the end of the link. For image management, caching can be a pain when you are updating images.

This is of course completely optional and you can do it however you wish.

The rest of the view code is more of the same, just for the thumbnail and for the mobile image. It’s not a fancy page, but you can see it gets the job done.

So now if you have an image saved in the DB and uploaded, then you can see it by going to:



yourproject.com/marketingimage/1

Or use whichever number on the end that is represented by the data record.

Ok, so we’re moving through this. Before we work on edit and update, let’s build our index method on the controller and then the index view.

The Index Method

On the MarketingImageController, change the index method to the following:



public function index()
{

   $images = Marketingimage::all();

   return view('marketingimage.index', compact('images'));
}

You can see this is super simple, we are just retrieving all records to the $images object, which gets passed the view via the compact method.

The Index View

Next, let’s modify our views/marketingimages/index.blade.php to the following:



@extends('layouts.master')

@section('content')
   {!! Breadcrumb::withLinks(['Home' => '/', 'marketing images' => '/marketingimage']) !!}
 <br>

    <div>
        <div class="panel panel-default">
            <!-- Default panel contents -->
            <div class="panel-heading">List of Marketing Images </div>
            <div class="panel-body">
            <a href="/marketingimage/create"> <button type="button" class="btn btn-lg btn-success">
                     Create New </button> </a>
            </div>

            <!-- Table -->
            <table class="table">
                <tr>
                    <th>Id </th>
                    <th>Name </th>
                    <th>Thumbnail </th>
                    <th>Edit </th>
                    <th>Delete </th>
                </tr>
    @foreach($images as $image )

                    <tr>
                        <td>{{ $image->id }}  </td>
                        <td>{{ $image->image_name }} </td>
                        <td> <a href="/marketingimage/{{ $image->id  }}">
                                <img src="/imgs/marketing/thumbnails/{{ 'thumb-'. $image->image_name . '.' .
                               $image->image_extension . '?'. 'time='. time() }}"> </a> </td>
                        <td>  <a href="/marketingimage/{{ $image->id }}/edit">
                                <span class="glyphicon glyphicon-edit"          
                                 aria-hidden="true"> </span> </a> </td>
                        <td>{!! Form::model($image, ['route' => ['marketingimage.destroy', $image->id],
                                    'method' => 'DELETE'
                      ]) !!}
                        <div class="form-group">

                        {!! Form::submit('Delete', array('class'=>'btn btn-danger', 'Onclick' => 'return ConfirmDelete();')) !!}

                         </div>
                         {!! Form::close() !!} </td>
                    </tr>
        @endforeach
            </table>
        </div>
    </div>

   @endsection
@section('scripts')
    <script>

       function ConfirmDelete()
       {
           var x = confirm("Are you sure you want to delete?");
           if (x)
               return true;
           else
               return false;
       }

    </script>

   @endsection

Ok, so we start by extending layouts.master and then open up our content with:

 

@section(‘content’)

Then we get the Breadcrumb, which again, if you are not using, just disregard.

Now I’m using basic Bootstrap here, which I got directly from the Bootstrap site.

We’re going to end up with a table inside of bootstrap panel. We’ll have a button to create a new image, as well as an edit and delete button for each image. We will show the thumbnail of each image in the list.

You can see in the panel heading how we handle the create button:


 <a href="/marketingimage/create"> <button type="button" class="btn btn-lg btn-success">
         Create New </button> </a>

Just really straight forward. The table is not complex, but does require a foreach loop to print all the rows. Here’s how we do it:



 <!-- Table -->
       <table class="table">
           <tr>
               <th>Id </th>
               <th>Name </th>
               <th>Thumbnail </th>
               <th>Edit </th>
               <th>Delete </th>
           </tr>

So that gets our column headings.  Next we add the foreach:
@foreach($images as $image )

               <tr>
                   <td>{{ $image->id }}  </td>
                   <td>{{ $image->image_name }} </td>
                   <td> <a href="/marketingimage/{{ $image->id  }}">
                           <img src="/imgs/marketing/thumbnails/{{ 'thumb-'. $image->image_name . '.' .
                          $image->image_extension . '?'. 'time='. time() }}"> </a> </td>
                   <td>  <a href="/marketingimage/{{ $image->id }}/edit">
                           <span class="glyphicon glyphicon-edit" aria-hidden="true"> </span> </a> </td>
                   <td>{!! Form::model($image, ['route' => ['marketingimage.destroy', $image->id],
                               'method' => 'DELETE'
                 ]) !!}
                   <div class="form-group">

                   {!! Form::submit('Delete', array('class'=>'btn btn-danger', 'Onclick' => 'return ConfirmDelete();')) !!}

                    </div>
                    {!! Form::close() !!} </td>
               </tr>
   @endforeach
       </table>

Ok, nothing too tricky. You can see I added a form helper around the delete link. This is because for security reasons, we have to POST a delete instead of allowing it to be wide open through a GET.

You’ll also notice that we used:



'Onclick' => 'return ConfirmDelete();'

Which we use in combination with javascript to open a confirm box, so that users don’t accidentally delete something.

Then in the @section(‘scripts’) we add the following javascript. Just a quick note. This assumes that you have a @yield(‘scripts’) on your master page. If you are not doing it that way, you can simply include the javascript before the @endsection of the @section(‘content’).

Anyway, here is the javascript:



 <script>

   function ConfirmDelete()
   {
       var x = confirm("Are you sure you want to delete?");
       if (x)
           return true;
       else
           return false;
   }

 </script>

Pretty simple stuff.

And with that, you should have a working index page, if you point your browser to:



yourproject.com/marketingimage

This tutorial is continued in Basic Image Management Part 3.

11 thoughts on “Basic Image Management Part 2

    • sounds like you are not getting the file. dd($file) after this line: $file = Input::file(‘image’); If you don’t get file, look at your form and make sure files is set to true in the form.

      Like

  1. bonhomie says:

    Hello! Thanks for the great tutorial. I have been able to implement most of this except for one thing: I am not able to save the image to my public/images folder.

    The requested resource /images/2 was not found on this server.

    If I use the Illuminate/Support/Facades/Input in the (config/app.php), then I get a totally different error and I am not able to access even the home page – which has no relation to the Images functionality:

    FatalErrorException in ProviderRepository.php line 120:
    call to undefined method llluminate\Support\Facades\Input::isDeferred()….

    Any help on this would be greatly appreciated.

    Like

  2. Thank you Bill Keck for this great tutorial !

    The code given led to a “Route [image.store] not defined” error when calling the create.blade.php view.

    I had to change this
    {!! Form::open(array(‘route’ => ‘image.store’, ‘class’ => ‘form’, ‘files’ => true)) !!}
    to this
    {!! Form::open(array(‘url’ => ‘image.store’, ‘class’ => ‘form’, ‘files’ => true)) !!}
    to make it work.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s