Skip to content

Instantly share code, notes, and snippets.

@nervetattoo
Created November 1, 2010 20:26
Show Gist options
  • Save nervetattoo/658806 to your computer and use it in GitHub Desktop.
Save nervetattoo/658806 to your computer and use it in GitHub Desktop.
Initial image handler route
<?php
class Photo extends \lithium\data\Model {
/**
* Generate a cached version under webroot
* @param string $id The image id as in mongodb
* @param array $options Possible values are
* width
* height
* @return mixed
*/
public static function version($id, $options = array()) {
if (!$id)
return false;
// This is the same as Photo::first($id) when called from inside itself
$self = static::first($id);
return ($self)
? $self->generateVersion($options)
: false;
}
/**
* Generate a cached version under webroot
* @param Document $self The document from the db
* @param array $options Possible values are
* @return mixed
*/
public function generateVersion($self, $options = array()) {
// This is quite naive, it would fail at .jpeg for example. Be more elaborate on production code!
$type = substr($self->file->file['filename'], -3);
$path = LITHIUM_APP_PATH . "/webroot/image/{$self->_id}";
$originalPath = $path . "." . $type;
// Always create the original variant if it doesnt exist yet. It is needed for resize
if (!file_exists($originalPath))
file_put_contents($originalPath, $self->file->getBytes());
if (isset($options['width']) && isset($options['height'])) {
$width = (int) $options['width'];
$height = (int) $options['height'];
$path .= "_{$width}x{$height}.{$type}";
// This requires imagemagick and access to system calls.
// It is possible to use gd but it is a much worse alternative so please dont.
$convertCommand = "convert $originalPath -resize {$width}x{$height}\> $path";
shell_exec($convertCommand);
// Return data of the resized version
return file_get_contents($path);
}
// If no width/height were set, just return data of the original
return $self->file->getBytes();
}
}
<?php
Router::connect('/photos/view/{:id:[0-9a-f]{24}}.jpg', array(), function($request) {
return new Response(array(
'headers' => array('Content-type' => 'image/jpeg'),
'body' => Photo::first($request->id)->file->getBytes()
));
});
<?php
/**
Define an anonymous function that we will pass to the router instead of linking to a controller action
The logic is quite simple:
Call the version() method on the Photo model class with $request->id (MongoId for image) and a set of options. This is passed as an array to allow adding more options later.
Finally just return a Response object with the image data as body (this is what version() returns) and the appropriate content type for the file ending.
This method is limited, supports few formats etc but its a good start
*/
$imageSizer = function($request) {
$contentTypeMappings = array(
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif'
);
// Generate file based image of this
$imageBody = Photo::version($request->id, array(
'width' => $request->width,
'height' => $request->height
));
return new Response(array(
'headers' => array('Content-type' => $contentTypeMappings[$request->type]),
'body' => $imageBody
));
};
/**
This is a little bit more complicated.
We state that the handler for when this route is matched is the anonymous function we've declared and
we set up a pattern to match our two cases of image urls — both with and without size information.
The regex is quite simple even if it looks complex:
^/image/ <- begin with /image/
(?P<foo>) is for setting up a named capture group. This equals doing {:foo:{pattern}} in Lithium.
So we have 1 capture group named {id} that have to match by 24 signs (mongoid), and an optional part "_{width}x{height}" and finally the filtype.
Im unsure if the keys array can be handled some other way, but it failed for me without it.
*/
$imageHandlingOptions = array(
'handler' => $imageSizer,
'pattern' => '@^/image/(?P<id>[0-9a-f]{24})(_(?P<width>[0-9]*)x(?P<height>[0-9]*)?)\.(?<type>[a-z]{2,4})@',
'keys' => array('id'=>'id', 'width'=>'width', 'height'=>'height', 'type'=>'type')
);
/**
Finally we connect this as a route. The regex sent as the first param here is overriden by the more complex one we have defined in the options array.
*/
Router::connect('/image/{:id:[0-9a-f]{24}}.jpg', array(), $imageHandlingOptions);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment