thomasjsn's avatar

Where to put logic for getting image path and thumbnails?

I have a Image model, which contains uploaded images. Each image entity has an original image and a couple of thumbnails. Where is the logical place to put the paths and file names to these? In the model?

Now I have a helper class that provides these fields, but it's getting kind of big and all methods needs the Image class as an input parameter. So I am starting to wonder if maybe this information belongs in the model, but that doesn't feel quite right...

Examples of helper methods:

    private static function _image(Image $image, $base)
    {
        return sprintf('%s/%s/%s',
            Config::get('static.images.' . $base), //url or path
            Config::get('static.images.original'),
            $image->category->slug
        );
    }

    private static function _thumb(Image $image, $base, $template)
    {
        $image_path = sprintf('%s/%s', $image->id % 100, ($image->id / 100) % 100);

        return sprintf('%s/%s/%s/%s',
            Config::get('static.images.' . $base), //url or path
            Config::get('static.images.thumbnail'),
            $template,
            $image_path
        );
    }

As you can see some; some logic is required to get the paths. Any suggestions to how and where I might put these methods?

0 likes
13 replies
perfilev's avatar

I would suggest to put it in Config. It's not model's logic to store variables inside.

thomasjsn's avatar

The constants are stored in the config, but some logic is required to get the full path and name. Such the image category and thumbnail template. I see that my initial question was not quite clear, I have updated it.

RemiC's avatar
RemiC
Best Answer
Level 8

I would probably put these method inside a dedicated service, something like this :


interface ImagePathService {

    public function getOriginalPath(Image $image);

    public function getThumbnailPath(Image $image);

    public function getOriginalUrl(Image $image);

    public function getThumbnailUrl(Image $image);
}

This way you have the ability for example to switch between a local image folder to a cloud based, without touching your model class.

2 likes
thomasjsn's avatar

Thanks for the tip @RemiC, I created ImageHelper and ThumbHelper to do this. And removed any trace of this type of logic from the models.

The "service" name is not something I typically use, is this kind of logic more of a "service" than a "helper" or is that just down to personal preference?

RemiC's avatar

Well, for me Helpers refer to more generic / global functionnality shared across the application. Services are more specific and consist in one class usually resolving one particular problem.

But most importantly they allow you to extract any piece of code that is framework specific (in your case calling the Config facade), outside your domain models. In short anytime you find yourself calling framework or 3rd party classes from your model, is a good hint for a service class.

unitedworx's avatar

The most suitable way for doing this is utilising a presenter! so that you can retrieve the full path of an image file name you have in you model

I am using Jeffrey's presenter package for this https://github.com/laracasts/Presenter

So I can easily do the following and get the full path of the thumb instead of just the file name and have my logic centrally in the image presenter!

$Image->present()->thumb

Or

$Image->present()->full

Of course in the presenter I have the logic of where each image is stored depending on the type of model this image is associated with.

RemiC's avatar

@unitedworx : in that particular case where the Image path is calculated from the Image's id, you'll most certainly want to reuse that piece of logic to save the uploaded file into the correct path, so I think it make sense to have it in a service class, to avoid duplicate code.

Also what if you want to serve your image not in a view but in a json response, does it works also (that's a question I never used this package) ?

martinbean's avatar

@hebron When working with images, I’ll store the unique filename in a column in my database. By not storing the path, means I can move data stores easily.

In terms of building paths, I’ll then have a helper function where I pass the filename, and it returns the full path. So if I’m storing images in an S3 bucket:

function s3_object_url($key, $expires = null)
{
    return app('aws')->get('s3')->getObjectUrl($key, $expires);
}

Obviously this is tied to S3, so you could have a generic get_image_url method that uses your config file to determine where your image is stored, and build a full URL as appropriate.

I personally don’t use the Image instance as a parameter for my URL-building functions, as it’s very rare I’ll need anything other than the image’s filename to build a URL.

unitedworx's avatar

That logic is in the presenter class so its in a central place! Also you don't have to do anything special to get the path than adding present() before you image column! This does not only work on views but anywhere in you code. Also if you create a new model and do $Image->present()->thumb you basically get the path where you thumbs should be stored :)

This has been a simple and elegant solution for me which makes my life a lot easier and keeps my code damn clean! Feel free to use whatever suits you best. Just pointing out presenters can be used here since their goal as their name implied is to present data slightly different from what stored in the database.

I don't use presenters only for getting images paths but lots more stuff! e.g grabbing a users gravatar, grabbing the html code for a video based only on its youtube/vimoe url, showing formatted product prices etc etc.

If you want to include extra stuff in your json response you can easily specify them in the protected $appends array in your model so you can include al lot more stuff in there than only your models columns!

unitedworx's avatar

Here is an actual example of doing this utilizing a presenter!

<?php
use Laracasts\Presenter\PresentableTrait;
class Image extends Eloquent  {
    public $timestamps = true;
    use PresentableTrait;
    protected $presenter = 'ImagePresenter' ;
}
<?php

use Laracasts\Presenter\Presenter;

class ImagePresenter extends Presenter {

    public function thumb()
    {
        return $this->ImageFullPath( 'thumb' );
    }

    public function full()
    {
        return $this->ImageFullPath( 'full' );
    }

    // i keep paths stored in config files for different types of content.
    // i actually store a lot more stuff for my images but showing only path to keep it simple
    // e.g media.article.path = 'media/article-images/';
    // e.g media.product.path = 'media/product-images/';
    public function ImageFullPath( $size )
    {
            return Config::get('media.'.$this->type.'.path').$this->entity->{$size};
     }

}

then i can simply do the following to grab the full path of the image or when i am processing images I can do the same to get where the image is going to be stored!

$Image->present()->full

so if i have "an_article_image_full.jpg" stored in the database images full column and i call $Image->present()->full i will get "media/aticle-images/an_article_image_full.jpg"

RemiC's avatar

Yes this works, still conceptually I would'nt use the presenter in other place than the View layer. But that's architectural pointilism :)

thomasjsn's avatar

Thank you all for you inputs, much appreciated. @unitedworx, I am using a presenter :) But the presenter is only one "subscriber" of the image path, it is also needed in CreateThumbnailsCommand.

This is how my presenter looks:

    public function sizedUrl($template)
    {
        return ThumbHelper::url($this->entity, $template);
    }

    public function originalUrl()
    {
        return ImageHelper::url($this->entity);
    }

Please or to participate in this conversation.