ImageBehavior – uploaduj pliki prosto do bazy

Jakiś czas temu napisałem o pomyśle cake’owego Behavior (http://webbricks.blogspot.com/2009/02/pliki-w-formie-binarnej-w-bazie.html). Poniżej prezentuję pierwsze podejście do problemu:

<?php
/**
 * ImageBehavior – take best from database blobs adn file image storage

 * requires ‘content’ field that is a blob (mediumblob or longblob), and
 * ‘ext’ varchar(10) field  and

 * ‘modified’ datetime field
 * @author Grzegorz Pawlik
 * @version 1.0
 */
class ImageBehavior extends ModelBehavior {

  
   /**
    * directory in which cached files will be stored
    *
    * @var string
    */
   
var $cacheSubdir ‘filecache’;

   /**
    * if set to false – never check if cached file is present (nor actual)
    *

    * @var bool
    */
   var $usecache true;
  
   function 
setup(&$Model) {
      
// no setup at this time

   }
  
   
/**
    * Insert proper blob when standard data after upload is present
    *
    * @param object $Model

    * @return bool true
    */
   function beforeSave(&$Model) {

      if(isset($Model->data[$Model->name][‘file’][‘tmp_name’]) && is_uploaded_file($Model->data[$Model->name][‘file’][‘tmp_name’])) {

      // podnieś wyżej parametry
      
$Model->data[$Model->name] = array_merge($Model->data[$Model->name],  $Model->data[$Model->name][‘file’]);

      // przygotuj blob
      
$this->_prepareBlob($Model);
     
      
$this->_getExt($Model);
      }

     
      return true;
   }
  
   
/**
    * prepares blob contents
    *
    * @param object $Model

    */
   function _prepareBlob(&$Model) {
      
App::import(‘Core’‘File’);
      
$file = new File($Model->data[‘Medium’][‘tmp_name’], false);

      $content $this->addSlashes$file->read() );
      
$Model->data[$Model->name][‘content’] = $content;

   }
  
   /**
    * Get uploaded file extension
    *
    * @param object $Model
    */
   
function _getExt(&$Model) {

      $file explode(‘.’$Model->data[‘Medium’][‘name’]);
      
$ext array_pop($file);

      $Model->data[$Model->name][‘ext’] = $ext;
   }
  
   
/**
    * replace blob contents with file path

    * After reading database checks if cached file is present. If not creates it (from blob contents) and

    * returns a ‘file’ field with path relative to /app/webroot/img
    *
    *
    * @param object $model

    * @param array $results
    * @param unknown_type $primary
    * @return unknown
    */
   function afterFind(&$model$results$primary) {

      foreach($results as $key => $val) {
        
        
        
         
$relpath $this->cacheSubdir DS .

                 $val[$model->name][‘id’] . ‘_’ $model->name ‘_’ .

                 $val[$model->name][‘modified’] . ‘.’ $val[$model->name][‘ext’];

         $relpath str_replace( array(‘ ‘‘:’) , ‘_’$relpath);
        
         
$fullpath IMAGES $relpath;

        
         if(!file_exists($fullpath) || !$this->usecache ) {
            
file_put_contents($fullpath$this->stripSlashes($results[$key][$model->name][‘content’]));

         }
        
         $results[$key][$model->name][‘file’] = $relpath;
         
// remove blob from results (its messy when You want to output results in debug)

         unset($results[$key][$model->name][‘content’]);
      }
      return 
$results;
   }
  

   /**
    * add slashes (just wrapper)
    *
    * @param string $string
    * @return string with slashes
    */

   function addSlashes($string) {
      return 
addslashes($string);
   }
  
   
/**
    * strip slashes (just wrapper)

    *
    * @param string $string
    * @return string without slashes
    */
   function stripSlashes($string) {

      return stripslashes($string);
   }
}
?>

Zasada działania jest dość prosta. Wyjaśnię ją na przykładzie.
Tabela media:

CREATE TABLE IF NOT EXISTS `media` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(50) NOT NULL,

  `ext` varchar(10) NOT NULL,
  `content` longblob NOT NULL,
  `size` int(11) NOT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,

  `type` varchar(20) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM;

Model:
<?php
class Medium extends AppModel {

    var $name ‘Medium’;
   var 
$actsAs = array(‘Image’);

}

?>

Kontroler:
<?php

class MediaController extends AppController {

    var $name ‘Media’;
    var 
$helpers = array(‘Html’‘Form’);

   
    function index() {
     
      
$this->set(‘media’$this->Medium->findAll());

     
    }
   
    function add() {
       if(!empty(
$this->data)) {
          
$this->Medium->save($this->data);

       }
    }
   
}

?>

Przy uploadzie ImageBehavior oczekuje, że plik będzie przekazany w poly ModelName.file (tutaj Media.file).

add.ctp:
span style=”color: rgb(0, 0, 187);”><?php
   echo $form->create(
      array(
‘url’ => array(
                           
‘controller’ => ‘media’,

                           ‘action’    => ‘add’
                     
),
            
‘enctype’ => ‘multipart/form-data’
            
)
   );
?>

<?php echo $form->file(‘Medium.file’); ?>


<?php echo $form->end(‘submit’); ?>

Przy odczycie dzieje się to co mnie najbardziej interesowało. Zamiast dostać zawartość (BLOB) pliku, dostajemy w polu file ścieżkę (relatywną do app/webroot/img). Domyślne ustawienia wymagają, żeby był tam katalog filecache (z możliwością zapisu). Przy operacji read behavior sprawdzi, czy istnieje aktualy plik w filecache, i jesli nie – utworzy go.

index.ctp:
<?php foreach($media as $medium): ?>
   <?php echo $html->image($medium[‘Medium’][‘file’]); ?>

<?php endforeach; ?>

To rozwiązanie ma przynajmniej dwa zauważalne braki:

  1. Gdy dodamy taki plik do treści np. postu, po jego updacie – nie będą widoczne zmiany
  2. Dobrze byłoby, gdyby przy operacji read nie zwracał zawartości BLOB (ważne, gdy baza jest gdzieś dalej), ale odpytywał tylko wtedy, gdy jest potrzebne zaktualizowanie zawartości pliku w filecache.

To rzeczy do zrobienia w kolejnym podejściu ;)

Share Button

Leave a Reply

Your email address will not be published. Required fields are marked *