Created
November 1, 2010 20:26
-
-
Save nervetattoo/658806 to your computer and use it in GitHub Desktop.
Initial image handler route
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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() | |
)); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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