create a photo gallery with laravel

In this tutorial we’ll code a simple photo gallery system with Laravel. We’ll also cover Laravel’s built-in file validation, file upload, and the hasMany database relation mechanism. We will use thevalidation class to validate the data and files. Also, we’ll cover the file class for processing files. The following topics are covered in this chapter:

  • Creating an Album model
  • Creating an Image model
  • Creating an album
  • Creating a photo upload form
  • Moving photos between albums

Lets Get started…

here is the source code of this tutorial adapted to laravel 5.4
 

Creating a table and migrating albums

We assume that you’ve already defined the database credentials in the database.php file located atapp/config/. To build a photo gallery system, we need a database that has two tables: albums andimages. To create a new database, simply run the following SQL command:

CREATE DATABASE laravel_photogallery

After successfully creating the database for the application, we will first need to create the albumstable and install it in the database. To do this, open up your terminal, navigate through your project folder, and run the following command:

The preceding command will generate a migration file under app/database/migrations to generate a new MySQL table, named posts, in our laravel_photogallery database.

To define our table columns, we need to edit the migration file. After editing, the file should have the following code:

<?php

use IlluminateDatabaseSchemaBlueprint;
use IlluminateDatabaseMigrationsMigration;

class CreateAlbumsTable extends Migration {

  /**
  * Run the migrations.
  *
  * @return void
  */
  public function up()
    {
      Schema::create('albums', function(Blueprint $table)
      {
        $table->increments('id')->unsigned();
        $table->string('name');
        $table->text('description');
        $table->string('cover_image');
        $table->timestamps();
      });
    }

  /**
  * Reverse the migrations.
  *
  * @return void
  */
  public function down()
  {
    Schema::drop('albums');
  }
}

After saving the file, we need to use a simple artisan command again to execute migrations:

php artisan migrate

If no error has occurred, please check the laravel_photogallery database for the albums table and its columns.

Let’s examine the columns in the following list:

  • id: This column is used for storing the ID of the album
  • name: This column is used for storing the name of the album
  • description: This column is used for storing the description of the album
  • cover_image: This column is for storing the cover image of the album

We’ve successfully created our albums table, so we need to code our Album model.

Creating an Album model

As you know, for anything related to database operations on Laravel, using models is the best practice. We will benefit from using the Eloquent ORM.

Save the following code as Album.php in the app/models/ directory:

<?php
class Album extends Eloquent {

  protected $table = 'albums';

  protected $fillable = array('name','description','cover_image');

  public function Photos(){

    return $this->has_many('images');
  }
}

We have set the database table name using the protected $table variable; we’ve also set the editable columns using the protected $fillable variable, which we’ve already seen and used in previous chapters. The variables that are defined in the model are enough for using Laravel’s Eloquent ORM. We’ll cover the public Photos () function in the Assigning a photo to an album section of this chapter.

Our Album model is ready; now we need an Image model and a database to assign photos to albums. Let’s create them.

 

Creating the images database with the migrating class

To create our migration file for images, open up your terminal, navigate through your project folder, and run the following command:

As you know, the command will generate a migration file in app/database/migrations. Let’s edit the migration file; the final code should be as follows:

<?php

use IlluminateDatabaseSchemaBlueprint;
use IlluminateDatabaseMigrationsMigration;

class CreateImagesTable extends Migration {

  /**
  * Run the migrations.
  *
  * @return void
  */
  public function up()
  {
    Schema::create('images', function(Blueprint $table)
    {
      $table->increments('id')->unsigned();
      $table->integer('album_id')->unsigned();
      $table->string('image');
      $table->string('description');
      $table->foreign('album_id')->references('id')->on('albums')->onDelete('CASCADE')->onUpdate('CASCADE');
      $table->timestamps();
    });
  }

  /**
  * Reverse the migrations.
  *
  * @return void
  */
  public function down()
  {
    Schema::drop('images');
  }
}

After editing the migration file, run the following migrate command:

php artisan migrate

As you know, the command creates the images table and its columns. If no error has occurred, check the laravel_photogallery database for the users table and the columns.

Let’s examine the columns in the following list:

  • id: This column is used for storing the id of the image
  • album_id: This column is used for storing the id of the image’s album
  • description: This column is used for storing the description of the image
  • image: This column is used for storing the path of the image

We need to explain one more thing for this migration file. As you can see in the migration code, there is a foreign key. We use the foreign key when we need to link two tables. We have an albums table and each album will have images. If the album is deleted from the database, you want all its images to be deleted as well.

 

Creating an Image model

We’ve already created the images table. So, as you know, we need a model to operate database tables on Laravel. To create that, save the following code as Image.php in the app/models/directory:

note – by default models directory is loacted in app directory but we have created a models directory for ease  with composer autoload class

class Images extends Eloquent {
  
  protected $table = 'images';
  
  protected $fillable = array('album_id','description','image');
  
}

Our Image model is ready; now we need a controller to create the albums on our database. Let’s create that.

 

Creating an album

As you know from the previous chapters in this book, Laravel has a great RESTful controller mechanism. We’ll continue to use that to keep the code simple and short during development. In the next chapters, we’ll cover another great controller/routing method named Resource Controllers.

To list, create, and delete an album, we need some functions in our controller. To create them, save the following code as AlbumsController.php in the app/http/controllers/ directory:

<?php

class AlbumsController extends BaseController{

  public function getList()
  {
    $albums = Album::with('Photos')->get();
    return View::make('index')
    ->with('albums',$albums);
  }
  public function getAlbum($id)
  {
    $album = Album::with('Photos')->find($id);
    return View::make('album')
    ->with('album',$album);
  }
  public function getForm()
  {
    return View::make('createalbum');
  }
  public function postCreate()
  {
    $rules = array(

      'name' => 'required',
      'cover_image'=>'required|image'

    );
    
    $validator = Validator::make(Input::all(), $rules);
    if($validator->fails()){

      return Redirect::route('create_album_form')
      ->withErrors($validator)
      ->withInput();
    }

    $file = Input::file('cover_image');
    $random_name = str_random(8);
    $destinationPath = 'albums/';
    $extension = $file->getClientOriginalExtension();
    $filename=$random_name.'_cover.'.$extension;
    $uploadSuccess = Input::file('cover_image')
    ->move($destinationPath, $filename);
    $album = Album::create(array(
      'name' => Input::get('name'),
      'description' => Input::get('description'),
      'cover_image' => $filename,
    ));

    return Redirect::route('show_album',array('id'=>$album->id));
  }

  public function getDelete($id)
  {
    $album = Album::find($id);

    $album->delete();

    return Redirect::route('index');
  }
}

The postCreate() function first validates the posted data of the form. We’ll cover validation next. If the data is validated successfully, we will rename the cover image and upload it with a new filename, because the code overwrites files which have the same name.

The getDelete() function is deleting the album along with assigned images (which are stored in animages table) from the database. Please remember the following migration file code:

$table->foreign('album_id')->references('id')->on('albums')->onDelete('CASCADE')->onUpdate('CASCADE');

Before creating our templates, we need to define the routes. So, open up the routes.php file in theapp folder and replace the code with the following one:

<?php
Route::get('/', array('as' => 'index','uses' => '[email protected]'));
Route::get('/createalbum', array('as' => 'create_album_form','uses' => '[email protected]'));
Route::post('/createalbum', array('as' => 'create_album','uses' => '[email protected]'));
Route::get('/deletealbum/{id}', array('as' => 'delete_album','uses' => '[email protected]'));
Route::get('/album/{id}', array('as' => 'show_album','uses' => '[email protected]'));

Now, we need some template files to show, create, and list the albums. First, we should create the index template. To create that, save the following code as index.blade.php in the recources/views directory:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Awesome Albums</title>
    <!-- Latest compiled and minified CSS -->
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/css/bootstrap.min.css" rel="stylesheet">
    
    <!-- Latest compiled and minified JavaScript -->
    <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/js/bootstrap.min.js"></script>
    <style>
      body {
        padding-top: 50px;
      }
      .starter-template {
        padding: 40px 15px;
      text-align: center;
      }
    </style>
  </head>
  <body>
    <div class="navbar navbar-inverse navbar-fixed-top">
      <div class="container">
      <button type="button" class="navbar-toggle"data-toggle="collapse" data-target=".nav-collapse">
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="/">Awesome Albums</a>
      <div class="nav-collapse collapse">
        <ul class="nav navbar-nav">
          <li><a href="{{URL::route('create_album_form')}}">Create New Album</a></li>
        </ul>
      </div><!--/.nav-collapse -->
    </div>
    </div>
    
      <div class="container">
    
        <div class="starter-template">
      
        <div class="row">
          @foreach($albums as $album)
            <div class="col-lg-3">
              <div class="thumbnail" style="min-height: 514px;">
                <img alt="{{$album->name}}" src="/albums/{{$album->cover_image}}">
                <div class="caption">
                  <h3>{{$album->name}}</h3>
                  <p>{{$album->description}}</p>
                  <p>{{count($album->Photos)}} image(s).</p>
                  <p>Created date:  {{ date("d F Y",strtotime($album->created_at)) }} at {{date("g:ha",strtotime($album->created_at)) }}</p>
                  <p><a href="{{URL::route('show_album',%20array('id'=>$album->id))}}" class="btn btn-big btn-default">Show Gallery</a></p>
                </div>
              </div>
            </div>
          @endforeach
        </div>
    
      </div><!-- /.container -->
    </div>
    
  </body>
</html>

Adding a template for creating albums

As you can see in the following code, we prefer to use Twitter’s bootstrap CSS framework. Thisframework allows you to rapidly create useful, responsive, and multi-browser supported interfaces. Next, we need to create a template for creating albums. To create that, save the following code ascreatealbum.blade.php in the resources/views directory:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>Create an Album</title>
    <!-- Latest compiled and minified CSS -->
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/css/bootstrap.min.css" rel="stylesheet">
    
    <!-- Latest compiled and minified JavaScript -->
    <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/js/bootstrap.min.js"></script>
  </head>
  <body>
    <div class="navbar navbar-inverse navbar-fixed-top">
      <div class="container">
        <button type="button" class="navbar-toggle"data-toggle="collapse" data-target=".nav-collapse">
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span lclass="icon-bar"></span>
        </button>
        <a class="navbar-brand" href="/">Awesome Albums</a>
        <div class="nav-collapse collapse">
          <ul class="nav navbar-nav">
            <li class="active"><ahref="{{URL::route('create_album_form')}}">CreateNew Album</a></li>
          </ul>
        </div><!--/.nav-collapse -->
      </div>
    </div>
    <div class="container" style="text-align: center;">
      <div class="span4" style="display: inline-block;margin-top:100px;">

        @if($errors->has())
          <div class="alert alert-block alert-error fade in"id="error-block">
             <?php
             $messages = $errors->all('<li>:message</li>');
            ?>
            <button type="button" class="close"data-dismiss="alert">×</button>
  
            <h4>Warning!</h4>
            <ul>
              @foreach($messages as $message)
                {{$message}}
              @endforeach

            </ul>
          </div>
        @endif

        <form name="createnewalbum" method="POST"action="{{URL::route('create_album')}}"enctype="multipart/form-data">
          <fieldset>
            <legend>Create an Album</legend>
            <div class="form-group">
              <label for="name">Album Name</label>
              <input name="name" type="text" class="form-control"placeholder="Album Name"value="{{Input::old('name')}}">
            </div>
            <div class="form-group">
              <label for="description">Album Description</label>
              <textarea name="description" type="text"class="form-control" placeholder="Albumdescription">{{Input::old('descrption')}}</textarea>
            </div>
            <div class="form-group">
              <label for="cover_image">Select a Cover Image</label>
              {{Form::file('cover_image')}}
            </div>
            <button type="submit" class="btnbtn-default">Create!</button>
          </fieldset>
        </form>
      </div>
    </div> <!-- /container -->
  </body>
</html>

The template creates a basic upload form and shows the validation errors which are passed from thecontroller side. We need just one more template file to list the album images. So, to create it, save the following code as album.blade.php in the  resources/views  directory:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>{{$album->name}}</title>
    <!-- Latest compiled and minified CSS -->
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/css/bootstrap.min.css" rel="stylesheet">

    <!-- Latest compiled and minified JavaScript -->
    <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/js/bootstrap.min.js"></script>
    <style>
      body {
        padding-top: 50px;
      }
      .starter-template {
        padding: 40px 15px;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <div class="navbar navbar-inverse navbar-fixed-top">
      <div class="container">
        <button type="button" class="navbar-toggle"data-toggle="collapse" data-target=".nav-collapse">
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
        <a class="navbar-brand" href="/">Awesome Albums</a>
        <div class="nav-collapse collapse">
          <ul class="nav navbar-nav">
            <li><a href="{{URL::route('create_album_form')}}">Create New Album</a></li>
          </ul>
        </div><!--/.nav-collapse -->
     </div>
    </div>
    <div class="container">
    
      <div class="starter-template">
        <div class="media">
          <img class="media-object pull-left"alt="{{$album->name}}" src="/albums/{{$album->cover_image}}" width="350px">
          <div class="media-body">
            <h2 class="media-heading" style="font-size: 26px;">Album Name:</h2>
            <p>{{$album->name}}</p>
          <div class="media">
          <h2 class="media-heading" style="font-size: 26px;">AlbumDescription :</h2>
          <p>{{$album->description}}<p>
          <a href="{{URL::route('add_image',array('id'=>$album->id))}}"><button type="button"class="btn btn-primary btn-large">Add New Image to Album</button></a>
          <a href="{{URL::route('delete_album',array('id'=>$album->id))}}" onclick="return confirm('Are yousure?')"><button type="button"class="btn btn-danger btn-large">Delete Album</button></a>
        </div>
      </div>
    </div>
    </div>
      <div class="row">
        @foreach($album->Photos as $photo)
          <div class="col-lg-3">
            <div class="thumbnail" style="max-height: 350px;min-height: 350px;">
              <img alt="{{$album->name}}" src="/albums/{{$photo->image}}">
              <div class="caption">
                <p>{{$photo->description}}</p>
                <p><p>Created date:  {{ date("d F Y",strtotime($photo->created_at)) }} at {{ date("g:ha",strtotime($photo->created_at)) }}</p></p>
                <a href="{{URL::route('delete_image',array('id'=>$photo->id))}}" onclick="return confirm('Are you sure?')"><button type="button" class="btnbtn-danger btn-small">Delete Image </button></a>
              </div>
            </div>
          </div>
        @endforeach
      </div>
    </div>

  </body>
</html>

As you may remember, we have used the hasMany() Eloquent method on our model side. On the controller side, we use the function as follows:

$albums = Album::with('Photos')->get();

The code fetches the whole image data in an array that belongs to the album. Because of that, we use the foreach loop in the following template:

@foreach($album->Photos as $photo)
  <div class="col-lg-3">
    <div class="thumbnail" style="max-height: 350px;min-height: 350px;">
    <img alt="{{$album->name}}" src="/albums/{{$photo->image}}">
      <div class="caption">
        <p>{{$photo->description}}</p>
        <p><p>Created date:  {{ date("d F Y",strtotime($photo->created_at)) }} at {{ date("g:ha",strtotime($photo->created_at)) }}</p></p>
        <a href="{{URL::route('delete_image',array('id'=>$photo->id))}}" onclick="return confirm('Are yousure?')"><button type="button" class="btnbtn-danger btn-small">Delete Image</button></a>
      </div>
    </div>
  </div>
@endforeach

 

 

About the author

Deven Rathore

I'm Deven Rathore, a multidisciplinary & self-taught designer with 3 years of experience. I'm passionate about technology, music, coffee, traveling and everything visually stimulating. Constantly learning and experiencing new things.

  • Christopher

    the syntax highlighting is not showing correctly

    • Deven Rathore

      sorry for late reply ! can you please tell me where is not showing correctly ?

      • lino

        may be this ” & gt; ” it showing instead of ” > “

        • Deven Rathore

          ok thanks i will fix it

        • Deven Rathore

          thanks i have fixed that ! i am now just using pre tag simple solution :)

  • unbiased observer

    Hello
    I have gone with the creation of this project one line at a time, I chose to name the model (Image) rather than (Photos) to be consistent, and I get this error

    BadMethodCallException in Builder.php line 1999:
    Call to undefined method IlluminateDatabaseQueryBuilder::Image()
    when I try to track it, it ends in this line:

    $albums = Album::with(‘Photos’)->get();
    return View::make(‘index’)
    ->with(‘albums’,$albums);
    I tried to name it with(appImage) but no luck, can you help me out please?

    by the way, you’re doing an excellent job there, thanks so much.

  • Thomas Pfuhl

    Hello, thanks for this fine tutorial. However, it seems not to fit to the Laravel 5.1 directory structure. Is there an update ?

    • Deven Rathore

      i will upadate it as soon as possible ! now busy in projects :(

  • Ashok raj

    Command will generate a migration file underapp/database/migrations CMD:php artisan make:migration create_albums_table –create=albums –table

    • Deven Rathore

      sorry but what’s your point here ?

      • Ashok raj

        Over all its the good tutorial for beginners… I think you have to double check your commands before posting…Thats the point …

        • ok thanks ! will be doing from next time !

        • Fatih Akgun

          agree it does not say how to create a migration, beginners won’t understand how to create a migration

  • thanks for the album gallery laravel project. I think there is Albums Object missing in :
    Albumscontroller::getAlbum($id)
    and view: album.blade.php where you foreach through the object albums as others.
    you are not providing the obj. albums in Albumscontroller::getAlbum($id).

    • let me see that btw ! thanks 4 pointing it out

    • medraouf moussa

      How did you solve that ?

      • Sergio Peluzzi

        public function getAlbum($id)

        {

        $data[‘album’] = $this->album->with(‘Photos’)->find($id);

        $data[‘albums’] = $this->album->all();

        return view(‘album’)->with($data);

        }

        Obs. I’m using depency injection on __construct(). if u dont replace $this->album to Album::

  • Fatih Akgun

    open up your terminal, navigate through your project folder, and run the following command: “where is the command ?”

    you are missing this command “php artisan make:migration create_albums_table”

  • application fire

    Please, can you send me the source code? [email protected]

    • Nathan Scherneck

      The code is included in the tutorial.

  • application fire

    I have this error when I want to create an album
    TokenMismatchException in VerifyCsrfToken.php line 67:

    • Nathan Scherneck

      Add this to your form..

  • Beaneh

    Great tutorial. Have you uploaded the code anywhere?

    • Nathan Scherneck

      So many derelict Laravel tutorials out there.

  • Tomasz Czerwinski

    It would be faster just to make a pivot table from albums and photos.
    Second thing, if you are talking about albums and photos in plural, than update this tutorial to show people how to upload multiple images, not just one.
    And third thing… we have Laravel 5.3 now ;)

  • Костя Белоус

    i have an error
    ErrorException in d17e4d0327979a8a446079b4be560aa37d15c74a.php line 66:
    Undefined variable: albums (View: album.blade.php)

  • Albert Mungochi II

    Please Help. Class ‘BaseController’ not found

  • Carlos Hernández

    Thank you very much!

  • sentissi aziz

    Hello everyone,

    I am looking to create a Photo Gallery in the employer I already installed and that works well.

    For this gallery I followed the following tutorial:

    http://www.dunebook.com/tutorial-creating-a-photo-gallery-system-with-laravel/

    But I find myself with a mistake and also can not open the link of the function.

    “Whoops, looks like something went wrong. 1/1 FatalErrorException in Handler.php line 25: Uncaught TypeError: Argument 1 passed to AppExceptionsHandler::report() must be an instance of Exception, instance of Error given, called in /home/www/test.com/laravel/vendor/compiled.php on line 1720 and defined in /home/www/test.com/laravel/app/Exceptions/Handler.php:25
Stack trace:
#0 /home/www/test.com/laravel/vendor/compiled.php(1720): AppExceptionsHandler->report(Object(Error))
#1 [internal function]: IlluminateFoundationBootstrapHandleExceptions->handleException(Object(Error))
#2 {main}
thrown 1. in Handler.php line 25”

    And I do not understand how to correct that.

    Thanks to your helpers,

  • Konrad Zając

    You say to put the first controller in:
    app/http/controllers/
    and the second one in:
    app/controllers/
    shouldn’t the controllers go in one place?

  • Konrad Zając

    On the second page you write to out the ImageController (singular) to app/controler, then oon the third page you write to “Open the ImagesController.php file which is located in app/http/controllers/” but firstly there’s no controller there, and secondly is that supposed to be ImageControler (singular) or ImagesControler (plural)?

Pin It on Pinterest

Shares