Laravel bundle for easy thumbnail handling in Eloquent models, inspired by sfDoctrineThumbnailablePlugin
PHP
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
config version bump Apr 22, 2013
CHANGELOG.md
README.md
example_newfile_methods.md
start.php
thumbnailable.php
thumbnailer.php

README.md

Thumbnailable

Easy thumbnailing for your Eloquent models!

Installing the Bundle

Install the bundle using Artisan:

php artisan bundle:install thumbnailable

Update your application/bundles.php file with:

'thumbnailable' => array( 'auto' => true ),

Updating your Models

For PHP 5.4 users, you simple need to include the Thumbnailable trait in your model, and define the configuration for your model in a public static property $thumbnailable.

class User extends Eloquent
{

    use Thumbnailable;

    public static $thumbnailable = array(
        'default_field' => 'headshot',
        'fields' => array(
            'headshot' => array(
                'default_size' => 'small',
                'sizes' => array(
                    'small'  => '50x50',
                    'medium' => '100x100',
                    'large'  => '200x200',
                )
            ),
            'bodyshot' => array(
                'sizes' => array(
                    'full' => '240x360'
                )
            )
        )
    );

}

That's it ... your model is now "thumbnailable"!

** Please see the note if you are using a version of PHP prior to 5.4. **

Using the Class

Saving a model is easy:

$user = new User(array(
    'name'     => Input::get('name'),
    'headshot' => Input::file('headshot'),
));

$user->save();

Retrieving the thumbnails:

// return the path to the default thumbnail for the "headshot" file
$user->thumbnail('headshot');

// return the path to the "small" thumbnail
$user->thumbnail('headshot', 'small');

// return the path to a custom-sized thumbnail
$user->thumbnail('headshot', '75x75');


// in your views
{{ HTML::image( $user->thumbnail_url('headshot') ) }}

// or use the easy helper
{{ $user->thumbnail_image('headshot', 'small', 'Colin', array('class'=>'headshot') ) }}
{{ $user->thumbnail_image('headshot', '75x75' ) }}
{{ $user->thumbnail_image() }}

And when you're all done, deleting is a piece of cake too:

$user->delete();

The bundle will automatically delete the original uploaded file and all thumbnails associated with it.

Global Configuration

Configuration was designed to be as flexible as possible. You can set up defaults for all of your Eloquent models, defaults for all the image fields within a model, or even override those settings for individual images.

By default, global configuration can be set in the application/config/thumbnailable.php file. If a configuration isn't set, then the bundle defaults from bundles/thumbnailable/config/thumbnailable.php are used. Here is an example configuration, with all the settings shown:

return array(

    'storage_dir'       => path('storage'). 'uploads' . DS . 'thumbnails',
    'base_url'          => '/img/thumbnails'
    'keep_original'     => true,
    'strict_sizes'      => true,
    'on_save'           => true,
    'resize_method'     => 'crop',
    'thumbnail_format'  => 'png',
    'thumbnail_quality' => 80,
    'newfile_method'    => false,

);

storage_dir is the directory where the images and generated thumbnails are stored. The directory needs to be writable by the webserver (but the bundle will do its best to create any missing directories for you).

base_url defines how you can publically access your generated thumbnails. Generally, this would be a symlink you create in your application's public folder that links to the storage_dir, although you could set it to anything depending on your particular setup.

keep_original is a boolean that determines whether to keep the original (un- thumbnailed) image file after processing. Maybe you really only need thumbnails, so save storage space by discarding the original.

strict_sizes is a boolean. If it's set to true, then you can only generate and access thumbnails of the pre-determined sizes (defined later on in the configuration). If it's set to false, then you can generate thumbnails of any size at any time.

on_save is a boolean. If true, then saving your Eloquent model will trigger thumbnail generation of all the pre-determined sizes. This way, you will have instant access to the resized images. If false, then thumbnails are only generated when they are requested (you must set keep_original to true in this case).

resize_method is one of the strings "exact", "portrait", "landscape", "auto", "fit" or "crop". These are passed to the Resizer class.

  • "crop" will resize your image so that it completely fills the thumbnail, but maintains the original image's aspect ratio.

  • "fit" will resize the image so that it fits inside the thumbnail (padding it, if necessary)

  • "exact" simply resizes the original image to the dimensions of the thumbnail with no regard for aspect ratios.

The remaining options of "portrait", "landscape" and "auto" can produce images larger than the dimensions you give, so their use is discourage.

thumbnail_format is one of "png", "jpg" or "gif" and determines what the final image format of the thumbnails should be. Set it to "auto" and your thumbnails will be same image type as the original images.

thumbnail_quality is an integer from 0 to 100 and determines (for PNG and JPG images) the quality or compression of the thumbnail (0 is lowest quality/highest compression, 100 is best quality/no compression).

newfile_method lets you override Thumbnailable's internal logic that renames file uploads. By default, any uploaded files are give an random 24-character filename and stored in the directory defined by storage_dir. This might be okay for most users, but for applications with lots of thumbnailed images, you might run up against the 32k-files-per-directory limit. You also might have other reasons to want to use a different naming scheme.

In order to use a custom scheme, you should create a static class in your model that generates a name, and save the name of that method to the newfile_method configuration option. (Alternatively, just name your static method newfile to override the one in the trait.)

The class takes four arguments: the original filename, the base storage directory (i.e. storage_dir, which may be vary per-model or per-field), the uploaded file extension, and the field being thumbnailed.

As an example, you could put this in your configuration:

'newfile_method' => 'thumbnailer_newfile',

And this in your model (or base model, if you like):

/**
 * Custom file namer for Thumbnailable
 *
 * In this particular case, the files are going to be stored in:
 *
 * base_directory
 *   /slug_of_model_class/
 *     /random-6-character-directory
 *       /original_filename
 *
 * @param  {string} $original  The original name of the uploaded file
 * @param  {string} $directory The base storage directory
 * @param  {string} $ext       The file extension
 * @param  {string} $field     The field being thumbnailed
 * @return {string}            Path and new filename (relative to $directory, above) for
 *                             the uploaded images.
 */
public static function thumbnailer_newfile( $original, $directory, $ext, $field ) {
    do {
        $newdir = Str::slug( get_called_class() ) . DS . Str::random(6);
    } while ( File::exists( $directory . DS . $newdir ) );
    return $newdir . DS . $original;
}

The end result is that when I upload "Colin.jpg" to my Person model, it will be stored somewhere like /storage/uploads/person/jmdY7a/Colin.jpg.

The only caveat is that your static method should check that the new filename doesn't already exist, or it could be over-write existing files.

See the example_newfile_methods.md file for more ideas.

Model Configuration

The configuration for each model that uses the bundle is done in the model itself. Let's look at our User class again:

class User extends Eloquent
{

    use Thumbnailable;

    public static $thumbnailable = array(
        'default_field' => 'headshot',
        'fields' => array(
            'headshot' => array(
                'default_size' => 'small',
                'sizes' => array(
                    'small'  => '50x50',
                    'medium' => '100x100',
                    'large'  => '200x200',
                ),
                'save_filename' => 'headshot_file'
            ),
            'bodyshot' => array(
                'strict_sizes' => false,
                'sizes' => array(
                    'full' => '240x360'
                )
            )
        )
    );

}

fields is an array of which attributes in the model (or fields in the database) represent the images to be thumbnailed. Looking at our User model, we have thumbnail images for "headshot" and "bodyshot". When generating your database, these should be string fields (since they will store the path to the images), e.g.:

Schema::create('users', function($table)
{
    $table->increments('id');
    $table->string('name');
    $table->string('headshot');
    $table->string('headshot_file');
    $table->string('bodyshot');
    $table->timestamps();
});

Within each element of fields array is where we define what thumbnail sizes to generate, and what friendly names to give them. For our User->headshot field, we are generating 50x50, 100x100 and 200x200 thumbnails. We can access the small one this way:

$user->thumbnail('headshot','50x50');

But what happens if you decide that 50x50 is too big and you really need to change those dimensions? It would be a pain to search your entire application for instances of that, so you can (and should!) refer to the image size by name (i.e. the key in that array):

$user->thumbnail('headshot','small');

We make it easier as well: you can define a default_size for a particular field, so the following code will also return the small thumbnail image:

$user->thumbnail('headshot');

Still too much typing? There is a default_field configuration for the model. So this code will also return the small headshot file:

$user->thumbail();

The headshot field also has a configuration setting for save_filename. If this is set, then the original name of the uploaded file is stored in this attribute of the model (i.e. field in the database). So in this case, the name of our uploaded headshot image (e.g. "colin.jpg") is stored in $user->headshot_file. This is useful if, for example, you want to allow users to redownload the file but want to rename it back to the original filename using, perhaps using Response::download().

Our model has a "bodyshot" field as well (maybe a secondary photo the users upload). We have set strict_sizes to false and not given any predetermined sizes. This means you can request any size thumbnail for that field, although you need to do it explicitly:

$user->thumbnail('bodyshot', '200x100');

Also note that save_filename isn't set for the "bodyshot" field, so we aren't keeping the name of the uploaded file in this case.

And all of the global configuration values can be redefined on a model-by- model -- or even field-by-field -- case.

Let's take a quick look at an other example model:

class Dog extends Eloquent
{

    use Thumbnailable;

    public static $thumbnailable = array(
        'keep_original' => false,
        'fields' => array(
            'image' => array(
                'sizes' => array(
                    'small'  => '50x50',
                ),
            ),
        ),
    );

}

Here, we aren't keeping the original image, so any file that is uploaded is thumbnailed to 50x50 (since the global on_save is true). We also only have one field with one size, so we don't need to define a default_field or default_size. All of the following lines of code will return the same thing:

$dog->thumbnail('image','50x50');
$dog->thumbnail('image','small');
$dog->thumbnail('image');
$dog->thumbnail();

Per-Size Configuration

Let's look at another example:

class Dog extends Eloquent
{

    use Thumbnailable;

    public static $thumbnailable = array(
        'keep_original'    => false,
        'resize_method'    => 'crop',
        'thumbnail_format' => 'png',
        'storage_dir'      => '/path/to/thumbnails',

        'fields' => array(
            'image' => array(
                'sizes' => array(
                    'small'  => '50x50',
                    'medium' => '50x50',
                    'large'  => array(
                        'size'             => '200x200',
                        'resize_method'    => 'auto',
                        'thumbnail_format' => 'jpg',
                        'storage_dir'      => '/path/to/large/thumbnails',
                    ),
                ),
            ),
        ),
    );

}

Here we've overrode some of the configuration options for the large thumbnails. The "small" and "medium" sizes will be cropped to their respective dimensions, converted to PNG images, and stored in /path/to/thumbnails. However, the "large" size is resized using a the "auto" method, converted to a JPG, and stored in another directory.

This gives you considerable flexibility in terms of generating thumbnails. For each size, you can pass a straight width x height string, or an array with any or all of the following fields:

  • size (mandatory)
  • storage_dir
  • thumbnail_format
  • resize_method
  • thumbnail_quality

If you pass one or more of those values, they will be used when generating the thumbnails. If not, the bundle will use the default configuration value for that setting.

Out-of-Model Configuration

Another option for configuring your models is to handle it all in the application/config/thumbnailable.php file:

return array(

    'storage_dir'       => path('storage'). 'uploads' . DS . 'thumbnails',
    'keep_original'     => true,
    'strict_sizes'      => true,
    'on_save'           => true,
    'resize_method'     => 'crop',
    'thumbnail_format'  => 'png',
    'thumbnail_quality' => 80,

    'user' => array(
        'default_field' => 'headshot',
        'fields' => array(
            'headshot' => array(
                'default_size' => 'small',
                'sizes' => array(
                    'small'  => '50x50',
                    'medium' => '100x100',
                    'large'  => '300x300',
                ),
            ),
            'alternate' => array(
                'strict_size' => false,
            ),
        ),
    ),

    'dog' => array(
        'keep_original' => false,
        'fields' => array(
            'image' => array(
                'sizes' => array(
                    'small'  => '50x50',
                    'medium' => '50x50',
                    'large'  => array(
                        'size'             => '200x200',
                        'resize_method'    => 'auto',
                        'thumbnail_format' => 'jpg',
                        'storage_dir'      => path('storage'). 'uploads' . DS . 'large-thumbnails',
                    ),
                ),
            ),
        ),
    ),

);

This puts all the configuration for your application in one place, which might be your preference. In this case, you still need to define that a model is thumbnailable with the static property like so:

class User extends Eloquent {

    use Thumbnailable;

    public static $thumbnailable = array();

}

Per-model Storage Directories

Because PHP only allows static properties to be assigned simple values, you unfortunately can't do this:

class Dog extends Eloquent
{

    use Thumbnailable;

    public static $thumbnailable = array(
        'storage_dir' => path('storage'). 'uploads' . DS . 'dog_pics',
            ...
    );

}

The two ways to define a different directory for each model are:

  1. Use the [Out-of-Model Configuration], or
  2. Define it in the constructor of your model, i.e.
class Dog extends Eloquent
{

    use Thumbnailable;

    public static $thumbnailable = array(
        /* all your normal settings */
    );


    public function __construct($attributes = array(), $exists = false)
    {

        self::$thumbnailable['storage_dir'] = path('storage'). 'uploads' . DS . 'dog_pics';

        parent::__construct($attributes, $exists);

    }

}

Credits

The idea for this bundle came from using Symfony's sfDoctrineThumbnailablePlugin ... back when I built sites with Symfony. It was a quick and "automagic" way to get image thumbnailing built into my Symfony Doctrine models. I tried to keep the configuration for the Thumbnailable bundle fairly similar to the SF plugin, but the bundle's code is from scratch.

The bundle depends on having the Resizer bundle installed. Artisan should handle the dependency for you.

And thanks to all the Github users who've give suggestions or (especially) merge requests.

Pull Requests

If you find a bug, or have an improvement, please make a pull request against the develop branch.

NOTE

Because this bundles uses PHP traits, it will only work if you are running PHP 5.4.

If you are running PHP 5.3, you can still use the bundle, but you will need to define your models a bit differently: basically, copy the two trait methods from thumbnailable.php into your model, and skip the use Thumbnailable setting. e.g.:

class User extends Eloquent
{

    public function thumbnail( $field=null, $size=null )
    {
        return Thumbnailer::get( $this, $field, $size );
    }

    public function thumbnail_path( $field=null, $size=null )
    {
        return Thumbnailer::get_path( $this, $field, $size );
    }

    public static $thumbnailable = array(
        // your regular config settings
    );

}