Using NCache to store image thumbnails

There are many ways to handle image thumbnails.  Sites with heavy traffic generate resized copies right after the original image is uploaded. Light traffic sites on the other hand, prefer to wait until the thumbnail is actually requested. Nette offers a few creative ways to handle this:

  1. You can use .htaccess to catch requests for non-existent thumbnails and redirect them to your script.
  2. Following the same theme, you can also use your ErrorPresenter to generate the thumbnail and override the response code (so that it’s not 404).
  3. An unusual solution is to use a helper and generate the thumbnail when rendering your template.
  4. Or simply use a presenter action.

In any of these cases, you need to decide where you’re going to store the thumbnail and how you’re going to delete/update it when the main image changes. This is where you can use the excellent built-in caching aparatus.

To keep the demonstration simple, I’m going to use the simplest of the pack, a presenter action. This goes into your BasePresenter.

  1.  
  2. public function sendResampled($model, $id, $attribute, $width, $height) {
  3.   $entity = $model->getById($id);
  4.   // a simple database query that returns a single row with the given id
  5.  
  6.   if (!$entity[‘id’]) {
  7.     throw new NBadRequestException("Entity no longer exists.", 404);
  8.     // the whole database record is gone
  9.   }
  10.  
  11.   $fullFilePath = $model->getUploadedFile($id, $attribute, $entity[$attribute]);
  12.   // a custom model method that returns full path to the main image file
  13.  
  14.   if ($fullFilePath === false) {
  15.     throw new NBadRequestException("This entity has no image.", 404);
  16.   }
  17.  
  18.   /*
  19.    * Now the interesting part: storing a thumbnail in NCache
  20.    */
  21.   $configurator = NEnvironment::getConfigurator();
  22.   $cache = new NCache($configurator->container->cacheStorage, ‘ResizedImages’);
  23.   // ResizedImages is my own identificator – change to whatever you please
  24.  
  25.   $cache->clean();
  26.   // deletes all timed-out/orphaned thumbnails; this call can go into cron instead
  27.  
  28.   $key = get_class($model)."|$id|$attribute|${width}x${height}";
  29.   $cachedImage = $cache->load($key);
  30.  
  31.   if ($cachedImage !== NULL) {
  32.     $image = NImage::fromString($cachedImage);
  33.  
  34.   } else {
  35.     $wwwDir = NEnvironment::getVariable(‘wwwDir’);
  36.     $image = NImage::fromFile($wwwDir . $fullFilePath)->resize($width, $height, NImage::FILL)->crop(0, 0, $width, $height);
  37.  
  38.     $cache->save($key, $image->toString(), array(
  39.       NCache::EXPIRE => ‘+ 2 months’,
  40.       NCache::SLIDING => TRUE,
  41.       NCache::FILES => $wwwDir . $fullFilePath
  42.       // if the source file changes, clear its cached thumbnail
  43.     ));
  44.   }
  45.   print $image->send();
  46.  
  47.   $this->terminate();
  48. }
  49.  

This is how you then call it from your regular presenter:

  1.  
  2. class GalleryPhotoPresenter extends BasePresenter {
  3.   public function renderPhoto($id, $width = null, $height = null) {
  4.     $this->sendResampled(new GalleryPhotoModel, $id, ‘image_file_name’, (isset($width) ? $width : 150), (isset($height) ? $height : 200));
  5.   }
  6. }
  7.  

Tags:

Leave a Reply