Basic Image Management Part 3

Access all tutorials in sprocket icon.

July 29, 2015 from author Bill Keck.

Basic Image Management

Part 1.

Part 2.

Part 3.

The Edit Method

Obviously edit and delete are not going to do anything yet. So let’s work on edit first. We’ll start with the controller method:



public function edit($id)
{

   $marketingImage = Marketingimage::findOrFail($id);

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

Again we’re using the findOrFail method and sending the object along to the view. We need it because we want to pre-populate our form with the appropriate values from the data record.

The Edit View

Now let’s change views/marektingimage/edit.blade.php to the following:

Gist For Edit view

Ok, some things to note about this. We don’t allow the user to edit the image names or paths. The reason for this is that we want them to delete the record and start over. Otherwise, it gets complicated where we have to rename the files that are saved and that’s beyond the scope of this tutorial. So I did this for simplicity.

So what’s different here about the form, besides the fact that we’re using less fields, we’re using form model binding in the form helper:



{!! Form::model($marketingImage, ['route' => ['marketingimage.update', $marketingImage->id],
'method' => 'PATCH',
'class' => 'form',
'files' => true]
) !!}

Our model instance, $marketingImage is bound to the form so the fields will pre-polulate. You can also see that we specify $marketingImage->id, so we can send that to the controller method and edit the correct record.

The method is set to PATCH, and this is automatically converted because PATCH is not supported by HTML. We also set ‘files’ to true so we can send the files along.

Everything else we pretty much covered and is nothing new. We do have a delete button at the bottom, in case the user wants to delete instead of edit.

Everything else, we pretty much covered before.

In workflow, you would probably move onto the update method in the MarketingImagesController. But I’m going to create a request class first to handle the update scenario.

EditImageRequest

The difference between validation on create and update is that when you create an image, the image file is required, whereas when you update, it is not. I decided for simplicity and clarity to create a separate request class named EditImageRequest, rather than put it in one request class.

The problem I ran into was I didn’t know how to make it conditional on which method the class was being injected into, so I went this route. Alternatively, I could have passed along a hidden field which identified the form, and put controlling logic on that, if from the create form, make the image required etc., but I just decided it’s so simple to create the separate class, I would just do that.

So from your command line run:



php artisan make:request EditImageRequest

Then set the authorize method on that class to return true and change the rules method to the following:



public function rules()
{
   return [
       'is_active' => 'boolean',
       'is_featured' => 'boolean',
       'image' => 'mimes:jpeg,jpg,bmp,png | max:1000',
       'mobile_image' => 'mimes:jpeg,jpg,bmp,png | max:1000'
   ];
}

We already covered the rules in the earlier section.

The Update Method

So now let’s move on to our update method on the MarketingImageController:



public function update($id, EditImageRequest $request)
{
   $marketingImage = Marketingimage::findOrFail($id);

   $marketingImage->is_active = $request->get('is_active');
   $marketingImage->is_featured = $request->get('is_featured');

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

   if ( ! empty(Input::file('image'))){

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

       $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);

   }

   if ( ! empty(Input::file('mobile_image'))) {

       $destinationMobile = '/imgs/marketing/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);
   }

   flash()->success('image edited!');
   return view('marketingimage.edit', compact('marketingImage'));
}

So let’s start with the method signature:



public function update($id, EditImageRequest $request)
{

You can see we’re pulling in an instance of the correct request object.

Then we findOrFail on the model record, so we can set the values from the request instance. We format the checkbox value and save:



   $marketingImage = Marketingimage::findOrFail($id);

   $marketingImage->is_active = $request->get('is_active');
   $marketingImage->is_featured = $request->get('is_featured');

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

If the primary image is not empty, we update it:



if ( ! empty(Input::file('image'))){

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

   $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);

}

Then we do the same check on the mobile image:



if ( ! empty(Input::file('mobile_image'))) {

   $destinationMobile = '/imgs/marketing/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);
}

Then we flash success and return, in this case to the edit page, but that’s completely up to you:



flash()->success('image edited!');
return view('marketingimage.edit', compact('marketingImage'));

Please note that if you are doing this for enterprise development, you might want a more robust check to see if the file is actually created, and handle it differently if for some reason it doesn’t happen. But that is beyond the scope of this tutorial.

Ok, we’re almost ready to wrap this up.

The Destroy Method

We just need to write out destroy method to handle deleting the photos:



public function destroy($id)
{
   $marketingImage = Marketingimage::findOrFail($id);
   $thumbPath = $marketingImage->image_path.'thumbnails/';

   File::delete(public_path($marketingImage->image_path).
                            $marketingImage->image_name . '.' .
                            $marketingImage->image_extension);

   File::delete(public_path($marketingImage->mobile_image_path).
                            $marketingImage->mobile_image_name . '.' .
                            $marketingImage->mobile_extension);
   File::delete(public_path($thumbPath). 'thumb-' .
                            $marketingImage->image_name . '.' .
                            $marketingImage->image_extension);

    Marketingimage::destroy($id);

   flash()->success('image deleted!');

   return redirect()->route('marketingimage.index');

}

You can see we findOrFail on the id that is handed in through the signature, then we use laravel’s File helper to delete, so once again we get beautiful syntax to show us what we are doing.

We use the public_path() method and put the components of the image inside of that signature. Then we just repeat for each type of image associated with the record.

Then we use the destroy method to remove the record from the DB. We flash a success method and redirect to the index page. And we’re done!

I hope you have enjoyed this tutorial and found it useful. Click on the sprocket icon at the top of the page to see all tutorials. Please comment, share, and like if you can, thanks.

I don’t have a donate button, but If you would like to support my work and learn more about Laravel, you can do so by buying one of my books, Laraboot: laravel 5* For Beginners, I really appreciate it.

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.

Basic Image Management Part 1

Access all tutorials in sprocket icon.

July 29, 2015 from author Bill Keck.

Basic Image Management

Part 1.

Introduction

One thing I’ve always found a little tricky is image management, lot’s of little gotchas that make it a bit of pain. I’m so used to dealing with models and database tables, that dealing with physical files isn’t as intuitive for me. So if you are just starting out with Laravel and tripping over this, I feel your pain.

Luckily, basic image management in Laravel is not that tough, once you get a few of the basics down. If you are really new to Laravel, this is not a good first tutorial. I recommend working through some of my other tutorials first before proceeding.

Just a couple of notes on this tutorial. It’s a long one. I’m going to break it up into several parts.

Also note that I’m not much of a frontend designer, so the views we make are not especially pretty, it will be up to you to decorate them how you wish. The main point here is to learn about image management.

So anyway, what are the basics? I thought about the typical requirements you might see on an application and came up with this list:

  • create images
  • store images
  • edit images
  • update images
  • create thumbnails
  • edit thumbnails
  • create and edit separate mobile images

For those of you who are familiar with restful controller actions, you know that create and edit actions show forms, and that store and update actually creates and updates the records and files.

With images, we are dealing with two things, the model, which operates on the data, things like the name of the image and its path, and then the other thing is the image file itself, which is stored in the folder that we are going to assign it to.

install Intervention

We are going to start by installing the Intervention/image package. If you have not already done so, add the following to the require section of your composer.json file:



"intervention/image": "~2.2"

Next we are going to add the following to the providers array in app\config\app.php:



Intervention\Image\ImageServiceProvider::class,

And then in the aliases array:



Intervention\Image\ImageServiceProvider::class,

I recommend that you check out the intervention page to make sure these instructions are the latest version.

Note that last time I checked, the Intervention instructions were using the old style way of referencing the provider:



'Intervention\Image\ImageServiceProvider'

You can see above, in both cases we have used ::class, which is a neat tip I picked up from Laracasts.com. It’s a better way to do it because if you are using PHP Storm for example, you click right through to the underlying class.

The intervention package gives us a simpler syntax and an easy way to make thumbnails, which is primarily what we’re going to use in this tutorial. They have a bunch of other cool methods, so check out the documentation for that.

Also note, we will be using the laravelcollective/html package and the patricktalmadge/bootstrapper package. If you followed my Top Ten Must-Have Laravel 5.1 tutorial, you have these installed already. If not, please install them before proceeding.

Ok, moving on.

Creating the Model

To give ourselves something to work with, we’re going to create a Marketingimage model. We can do that by using artisan from the commandline as follows:



php artisan make:model Marketingimage -m

You’ll notice the -m flag which tells Laravel that you also want to create a migration at the same time, so this is a handy feature.

Ok run that and you should have a model file, Marketingimage.php directly under your app directory and a migration file in the database/migrations folder. Let’s modify the up on the migration file method to the following:



public function up()
{
    Schema::create('marketing_images', function(Blueprint $table)
    {
        $table->increments('id');
        $table->boolean('is_active')->default(false);
        $table->boolean('is_featured')->default(false);
        $table->string('image_name')->unique();
        $table->string('image_path');
        $table->string('image_extension', 10);
        $table->string('mobile_image_name')->unique();
        $table->string('mobile_image_path');
        $table->string('mobile_extension', 10);
        $table->timestamps();
    });
}

First thing you can see is that I changed the table name. I prefer separation of words in my table names by underscore. You are free to follow your own convention, however note that we must use plural to follow Laravel’s conventions correctly. In Laravel, the model is singular and the table name is plural.

After the id column, we have two boolean columns that let us know if the image is active or featured, useful constraints to help us work with the images later on.

Then we follow that with name, path, extension columns for both image and mobile image. This will give us enough flexibility so that if we want to save a different image as the mobile image, we can. This is often necessary because simply scaling the photo may not produce the desired results.

Since we are going to create the thumbnail from the primary image, we don’t need to save any data for that.

By saving the path and extension of the image, we have an easy reference that we can use to show the image in our application and also for managing the images in a list of images.

We will of course also need to modify the down method:




public function down()
{
   Schema::drop('marketing_images');
}

Once that’s done, go ahead and run php artisan migrate from the command line, then check to make sure the table has been created.

Next we are going to make a modification to our Marketingimage model. Change it to the following:



 <?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Marketingimage extends Model
{
    protected $table = 'marketing_images';

    protected $fillable = ['is_active',
                                     'is_featured',
                                    'image_name',
                                    'image_path',
                                    'image_extension',
                                    'mobile_image_name',
                                    'mobile_image_path',
                                    'mobile_extension'
    ];
}

You can see that are telling the model which table to reference, as well as providing the fillable columns, so we don’t run into a mass-assignment problem.

The Controller

Ok, cool, we are ready to move on, things are zipping along that this point. So next we’ll create the controller by using artisan:



php artisan make:controller MarketingImageController

This will get you a restful controller in app/Http/Controllers with stubs for the following:

  • index
  • create
  • store
  • show
  • edit
  • update
  • destroy

We will be using each of these methods.

Now a handy tip for beginners is to put in the index method, something like the following:



return 'Here is the index method.';

This will give you a chance to test this route. More advanced users can skip that if they want.

Next we’ll setup our routes. Let’s modify app/Http/routes.php and add the following:



Route::resource('marketingimage', 'MarketingImageController');

You can see we are adding a resource, which gives us routes to all of our actions, all in one go, a very convenient way to do it.

So now, with that in place, you should be able to go to yourproject.com/marketingimage and get the following result:



Here is the index method.

So the next logical step is to setup our views. Let create a marketingimage folder under resources/views. Then go ahead and create the following empty files inside of the marketingimages folder:

  • create.blade.php
  • edit.blade.php
  • index.blade.php
  • show.blade.php

Setting up the Folders

We will return to those files in a bit, but for now let’s create a place to store our actual images. I’m keeping this very simple for this tutorial. Let’s create a folder named imgs directly under your public folder. Inside of the imgs folder, create a folder named marketing, and inside that marketing folder, create a folder named mobile and a folder named thumbnails.

So this gives us the folders for the image files.

The Create View

Ok, next let’s tackle the create view. Place the following inside of create.blade.php:



@extends('layouts.master')

@section('content')

    {!! Breadcrumb::withLinks(['Home' => '/', 'marketing images' => '/marketingimage', 'create']) !!}

     <h1>Upload a Photo </h1>


     <hr/>

     @if (count($errors) > 0)
	 <div class="alert alert-danger">
	 <strong>Whoops! </strong> There were some problems with your input. <br> <br>
	 <ul>
	     @foreach ($errors->all() as $error)
		 <li>{{ $error }} </li>
	     @endforeach

         </ul>
         </div>

    @endif


    {!! Form::open(array('route' => 'marketingimage.store', 'class' => 'form', 'files' => true)) !!}

     <!-- image name Form Input -->
     <div class="form-group">
        {!! Form::label('image name', 'Image name:') !!}
        {!! Form::text('image_name', null, ['class' => 'form-control']) !!}
     </div>


     <!-- mobile_image_name Form Input -->
     <div class="form-group">
        {!! Form::label('mobile_image_name', 'Mobile Image Name:') !!}
        {!! Form::text('mobile_image_name', null, ['class' => 'form-control']) !!}
     </div>


     <!-- is_something Form Input -->
     <div class="form-group">
        {!! Form::label('is_active', 'Is Active:') !!}
        {!! Form::checkbox('is_active') !!}
     </div>

     <!-- is_featured Form Input -->
     <div class="form-group">
        {!! Form::label('is_featured', 'Is Featured:') !!}
        {!! Form::checkbox('is_featured') !!}
     </div>

    <!-- form field for file -->
    <div class="form-group">
       {!! Form::label('image', 'Primary Image') !!}
       {!! Form::file('image', null, array('required', 'class'=>'form-control')) !!}
    </div>

     <!-- form field for file -->
     <div class="form-group">
        {!! Form::label('mobile_image', 'Mobile Image') !!}
        {!! Form::file('mobile_image', null, array('required', 'class'=>'form-control')) !!}
     </div>

     <div class="form-group">

        {!! Form::submit('Upload Photo', array('class'=>'btn btn-primary')) !!}

     </div>

    {!! Form::close() !!}

@endsection

Ok, nothing too crazy here.

Note that we are extending the master page, which is the layouts folder in the views folder. We call it like so:



@extends('layouts.master')

If you have a different master page or it’s in a different location, adjust that as needed. If you are unfamiliar with the concept of master page, find a tutorial on that and learn it before proceeding.

If you are not using the Bootstrapper package, eliminate the following:



{!! Breadcrumb::withLinks(['Home' => '/', 'marketing images' => '/marketingimage', 'create']) !!}

Also note, for this tutorial, I am including the if statement to print out form input errors, but in almost all cases, this would actually be extracted out to a view partial and then be referenced by something like:



@include('errors.errors')

You can also see that we are using the Form helper from the laravelcollective/html package quite a bit. I find the form helper very useful and it’s great for things like opening the form:



{!! Form::open(array('route' => 'marketingimage.store', 'class' => 'form', 'files' => true)) !!}

You can see we included ‘files’ => true, which allows us to upload multiple files.

If you are new to using this form helper, you can obviously see that we don’t need to specify POST and we don’t need to call the CSFR token because it does it for you automatically.

Then we have the various form inputs, nothing too crazy there. We also have the Form::submit which we use for the button and finally Form::close().

You can see from our form helper that the route is set to marketingimage.store, so we know from our route resource in routes.php that this will take us to the store method on MarketingImageController.php, which is in our Controllers directory.

This tutorial is continued in Part 2.