Skip to content
This repository has been archived by the owner on Dec 9, 2023. It is now read-only.

Cache in filesystem problems #53

Open
garygreen opened this issue Sep 3, 2015 · 3 comments
Open

Cache in filesystem problems #53

garygreen opened this issue Sep 3, 2015 · 3 comments

Comments

@garygreen
Copy link

This cache package seems very geared towards storing and returning the actual data stored for the image.

The problem is when it comes to filesystem, you really don't want to be reading image files into memory in PHP and then output the data somehow. Your whole framework shouldn't need to be booted up to serve an image. Ideally you want nginx to serve a cached version of the image stored on disk straight up.

With this package, it's extremely difficult. This might be related to issue #38.

It's good that it's general using the Flysystem, but I think there needs to be more consideration for those that don't want PHP to read the images but just have the cache return the stored image path.

@garygreen
Copy link
Author

Bump. Any thoughts @olivervogel ?

@simplenotezy
Copy link

Interested as well; this package is so great, but it would be great with optimization on the cache.

@eddiclin
Copy link

eddiclin commented May 10, 2016

This package is so awesome that I want to use it to create a dynamic image service. I also met this problem today and I have done some test on it. As below:

Solution

  • Store the image file on the disk and make its path equals the route path.
  • Config try_files in nginx.conf.

Version

laravel/framework: 5.2.*
intervention/imagecache: ^2.3

Step

  • Read the guideline first.
  • Define a route in app/Http/routes.php. Don't config the route in config/imagecache.php.
Route::get('imagecache/{template}/{filename}', [
    'uses' => 'ImageCacheController@getResponse',
    'as'   => 'imagecache'
])->where('filename', '[ \w\\.\\/\\-\\@]+');
  • Make a new controller named App\Http\Controllers\ImageCacheController which extends Intervention\Image\ImageCacheController. Override getImage method, and then copy getTemplate, getImagePath and buildResponse, because they are private counld not be inherited.
namespace App\Http\Controllers;

use Illuminate\Http\Response;
use Intervention\Image\Facades\Image;
use Intervention\Image\ImageCacheController as BaseController;

class ImageCacheController extends BaseController
{
    /**
     * Get HTTP response of template applied image file
     *
     * @param  string $template
     * @param  string $filename
     * @return Illuminate\Http\Response
     */
    public function getImage($template, $filename)
    {
        $filter = $this->getTemplate($template);
        $path = $this->getImagePath($filename);

        // It is different from the source code, just save it in the disk directly.
        $cachePath = safe_mkdir(public_path("imagecache/$template/$filename"));
        $image = Image::make($path)->filter($filter)->save($cachePath);

        return $this->buildResponse($image);
    }

    /**
     * Returns corresponding template object from given template name
     *
     * @param  string $template
     * @return mixed
     */
    private function getTemplate($template)
    {
        $template = config("imagecache.templates.{$template}");

        switch (true) {
            // closure template found
            case is_callable($template):
                return $template;

            // filter template found
            case class_exists($template):
                return new $template;

            default:
                // template not found
                abort(404);
                break;
        }
    }

    /**
     * Returns full image path from given filename
     *
     * @param  string $filename
     * @return string
     */
    private function getImagePath($filename)
    {
        // find file
        foreach (config('imagecache.paths') as $path) {
            // don't allow '..' in filenames
            $image_path = $path . '/' . str_replace('..', '', $filename);
            if (file_exists($image_path) && is_file($image_path)) {
                // file found
                return $image_path;
            }
        }

        // file not found
        abort(404);
    }

    /**
     * Builds HTTP response from given image data
     *
     * @param  string $content 
     * @return Illuminate\Http\Response
     */
    private function buildResponse($content)
    {
        // define mime type
        $mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $content);

        // return http response
        return new Response($content, 200, [
            'Content-Type'  => $mime,
            'Cache-Control' => 'max-age=' . (config('imagecache.lifetime') * 60) . ', public',
            'Etag'          => md5($content)
        ]);
    }
}
  • Add a helper function safe_mkdir
use Symfony\Component\HttpFoundation\File\Exception\FileException;

if (! function_exists('safe_mkdir')) {
    function safe_mkdir($directory, $mode = 0777)
    {
        $dir = pathinfo($directory, PATHINFO_EXTENSION) ? dirname($directory) : $directory;

        if (! is_dir($dir)) {
            if (false === mkdir($dir, $mode, true) && ! is_dir($dir)) {
                throw new FileException(sprintf('Unable to create the "%s" directory', $dir));
            }
        } elseif (! is_writable($dir)) {
            throw new FileException(sprintf('Unable to write in the "%s" directory', $dir));
        }

        return $directory;
    }
}
  • Config Nginx, modify root, server_name as yours.
server {
    listen   80;
    root   path/to/laravel/public;
    server_name local-laravel.com;
    index  index.php;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include        fastcgi_params;
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    }
}

Test

  1. Reload or restart nginx
  2. Put an image test.jpg in public/upload/201605/10 file
  3. Access this link in browser http://local-laravel.com/imagecache/small/201605/10/test.jpg
  4. Comment or remove the code in App\Http\Controllers\ImageCacheController::getImage
  5. Force refresh the browser, you could still see the small image although you have removed the code of getImage. Because the small image has been already stored in public/imagecache/small/201605/10
  6. Delete the small image file, and force refresh the browser again. And now you will lose it.
public function getImage($template, $filename) { }

Suggestion

  • Does it support save dynamic images on disk in the package? I think we can make the methods getTemplate, getImagePath and buildResponse as public or protected at least.
  • Shall we add a mkdir feature in Intervention\Image\Image::save, like safe_mkdir ?

Finally, thanks for sharing @olivervogel . Maybe I'm not good at English, and could not explain something clearly. If anyone is confused, please let me know. ^_^

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants