I can't get browser caching to work properly with the laravel integration bundle.
The major problem is that the cached image (ImageCache) is not accessible as a file.
Is there already a proper solution for this situation?
The imagecache was intended just to bypass GD library operations and should actually not care about the HTTP response (browser caching).
Why not add Cache-Control header manually?
Could you give an example of a "proper" HTTP response header?
Because you need to know what file you're dealing with. Because of the Cache abstraction you can't "touch" the actual image.
A header could look something like this. (+ expires, etc.)
$headers = array(
'Content-Disposition' => 'inline; filename="' . $name . '"',
'Cache-Control' => 'must-revalidate',
'Pragma' => 'public',
'Etag' => $etag,
'Content-Type' => $mime,
'Content-Length' => $length,
);
I'm afraid there is no quick solution. Since there is no image file by default, we only have image-data in cache-storage, which might be backed by Memcached, Redis or whatever is configured.
I think what's most important is the age of the data in cache. But I can't find methods of Illuminate/Cache to get information like this.
Thats what I thought. It would be a nice feature, thou.
This is my version for browser cache
<?php
use Intervention\Image\ImageManagerStatic as Image;
Image::configure([
'driver' => class_exists('Imagick') ? 'imagick' : 'gd'
]);
$browserCache = 60*60*24*7;
$quality = 75;
function sendImage($output, $info, $browserCache) {
$lastModified = gmdate('D, d M Y H:i:s', $info['modified']).' GMT';
$eTag = '"' . $info['checksum'] . '"';
$isModified = false;
header('Cache-Control: private, max-age='. $browserCache);
header('Expires: '.gmdate('D, d M Y H:i:s', time()+$browserCache).' GMT');
header('Content-Length: ' . strlen($output));
header('Last-Modified: ' . $lastModified);
header('ETag: ' . $eTag);
$ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE'])
: false;
$ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH'])
? stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])
: false;
if (!$ifModifiedSince && !$ifNoneMatch) {
$isModified = true;
} elseif ($ifNoneMatch && $ifNoneMatch !== $eTag) {
$isModified = true;
} else if ($ifModifiedSince && $ifModifiedSince != $lastModified) {
$isModified = true;
}
if ($isModified) {
echo $output;
} else {
header('HTTP/1.1 304 Not Modified');
}
}
try {
$info = [];
$img = Image::cache(function($image) use ( $source, &$info) {
$image->make($source);
$info = [
'checksum' => $image->checksum(),
'modified' => array_key_exists('modified', $image->properties) ? $image->properties['modified'] : time()
];
}, $browserCache, true);
sendImage($img->response($img->mime, $quality), $info, $browserCache);
} catch (Exception $e) {
header("HTTP/1.0 404 Not Found");
}
Thanks @vitalyrotari, I will look into this.
I would not use image->checksum() because the method is performance intensive. I would rather create a hash directly from the image data.
Thanks for tip @olivervogel.
Updated version
<?php
use Intervention\Image\ImageManagerStatic as Image;
Image::configure([
'driver' => class_exists('Imagick') ? 'imagick' : 'gd'
]);
$browserCache = 60*60*24*7;
$quality = 75;
function sendImage($output, $modified, $browserCache) {
$lastModified = gmdate('D, d M Y H:i:s', $modified).' GMT';
$eTag = '"' . md5($output) . '"';
$isModified = false;
header('Cache-Control: private, max-age='. $browserCache);
header('Expires: '.gmdate('D, d M Y H:i:s', time()+$browserCache).' GMT');
header('Content-Length: ' . strlen($output));
header('Last-Modified: ' . $lastModified);
header('ETag: ' . $eTag);
$ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE'])
: false;
$ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH'])
? stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])
: false;
if (!$ifModifiedSince && !$ifNoneMatch) {
$isModified = true;
} elseif ($ifNoneMatch && $ifNoneMatch !== $eTag) {
$isModified = true;
} else if ($ifModifiedSince && $ifModifiedSince != $lastModified) {
$isModified = true;
}
if ($isModified) {
echo $output;
} else {
header('HTTP/1.1 304 Not Modified');
}
}
try {
$modified = 0;
$img = Image::cache(function($image) use ($source, &$modified) {
$image->make($source);
$modified = array_key_exists('modified', $image->properties) ? $image->properties['modified'] : time();
}, $browserCache, true);
sendImage($img->response($img->mime, $quality), $modified, $browserCache);
} catch (Exception $e) {
header("HTTP/1.0 404 Not Found");
}
Don't know if i'll help someone, but here is my implementation (Laravel 5.3).
It modifies the Reponse properly, instead of sending direct headers.
function sendImage($response, $modified, $browserCache=60*60*24*7)
{
$lastModified = gmdate('D, d M Y H:i:s', $modified).' GMT';
$eTag = '"' . md5($response->getContent()) . '"';
$isModified = false;
$ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE'])
: false;
$ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH'])
? stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])
: false;
if (!$ifModifiedSince && !$ifNoneMatch) {
$isModified = true;
} elseif ($ifNoneMatch && $ifNoneMatch !== $eTag) {
$isModified = true;
} else if ($ifModifiedSince && $ifModifiedSince != $lastModified) {
$isModified = true;
}
if ($isModified) {
return $response->withHeaders([
'Cache-Control' => 'private, max-age='.$browserCache,
'Expires' => gmdate('D, d M Y H:i:s', time()+$browserCache).' GMT',
'Content-Length' => strlen($response->getContent()),
'Last-Modified' => $lastModified,
'ETag' => $eTag,
]);
} else {
return $response->setNotModified();
}
}
// In your controller...
$modified = 0;
$imgResponse = Image::cache(function($image) use ($source, &$modified) {
$image->make($source);
$modified = array_key_exists('modified', $image->properties) ? $image->properties['modified'] : time();
}, $browserCache, true)->response('jpg', $quality);
return sendImage($imgResponse, $modified, $browserCache);
Most helpful comment
Don't know if i'll help someone, but here is my implementation (Laravel 5.3).
It modifies the Reponse properly, instead of sending direct headers.