sfMogileFSPlugin - 0.6.0

Distributed filesystem client

You are currently browsing
the website for symfony 1

Visit the Symfony2 website


« Back to the Plugins Home

Signin


Forgot your password?
Create an account

Tools

Stats

advanced search
Information Readme Releases Changelog Contribute
Show source | Show as Markdown

sfMogileFSPlugin

sfMogileFS enables symfony applications to interact with [MogileFS]. MogileFS is an anagram for "OMG Files" and was created by [http://www.livejournal.com LiveJournal] to handle the storage, replication and retrieval of the large amount of file uploads they were, and continue to, experience. Many of the web's most popular sites use MogileFS as their file store. This includes [http://www.digg.com Digg], [http://www.last.fm Last.fm], http://www.guba.com Guba, and others.

Prerequisites

The following must be installed and setup before using this plugin.

How It Works

Perlbal is a load balancer that runs on port 80 with a pool of apache servers running behind it. In addition to dispatching requests to the internal servers, Perlbal also sends the critical X-REPROXY-URL header to MogileFS. MogileFS sends the file back based on the key given.

MogileFS Terminology

There are four main components to a MogileFS setup:

  • tracker(s): talk to the MogileFS database. By default run on port 6001.
  • database (a MySQL Cluster is recommended but simply one db server will work): maintains a list of storage nodes, files, etc.
  • storage node(s). Physical disks (hard drives) where files are stored. Mogstored defaults to port 7500.
  • client library (aka sfMogileFSPlugin) - talks to trackers (mogilefsd).

Storing files:

  • domain: Top level separation of files. File keys are unique within domains. The Socialhouse.com domain (socialhouse) is defined within the socialhouse application in app.yml. A domain consists of a set of classes that define the files within the domain. class
  • key: A unique textual string that identifies a file. Keys are unique within domains. Examples of keys: userpicture:34:39, phonepost:93:3834, userbackup:15.
  • class: Part of exactly one domain. A class, in effect, only specifies the minimum replica count of a file. Examples of Socialhouse classes: image, thumbnail, video.
  • minimum replica count: Property of a class. This defines how many times the files in that class need to be replicated onto different devices in order to ensure redundancy among the data and prevent loss.
  • file: Every file is part of exactly one class. A file is a defined collection of bits uploaded to MogileFS to store. Files are replicated according to their minimum replica count. Each file has a key, is a part of one class, and is located in one domain.

Plugin Installation

$ php symfony plugin-install http://plugins.symfony-project.com/sfMogileFSPlugin

In your app.yml file add:

dev:
  sfMogileFSPlugin:
    domain:     mogilefs_domain
    timeout:    5
    trackers:   [222.222.222.2:6001](111.111.111.1:6001,)

You will also need to define settings for your production environment.

Modify your routing.yml (feel free to change "i", module, and action to whatever you'd like).

@mfs
  url:   /i/:mkey
  param: { module: file, action: showMogileFSFile }

This plugin was designed to primarily add and retrieve files from MogileFS. You will need to create your own media handling model(s), module(s) and action(s), though an example setup is given below.

Installation complete. Clear your cache.

Plugin Files

The plugin contains 4 classes and one helper. They are phpdocumented and unit tests are included in the tests/ folder.

  • sfMogileFSBaseFile.class.php - contains methods shared by both sfMogileFSFile and sfMogileFSRemote file.
  • sfMogileFSFile.class.php - represents a file before it's saved to MogileFS.
  • sfMogileFSRemoteFile.class.php - represents a file retrieved from MogileFS.
  • sfMogileFS.class.php - contains static methods for interacting with the MogileFS trackers.
  • MogileFSAssetsHelper.php - wrapper for existing symfony helper's suchc as url_for and image_tag.

To output an image file from your template:



To add a file to MogileFS:

  $mfile = new sfMogileFSFile();
  $mfile->setKey($mfs_key);
  $mfile->setClass($mfs_class);
  $mfile->setFile($local_file);
  $mfile->save(); // saves to MogileFS

Alternatively:

$mfile = new sfMogileFSFile($key, $class, $file);
$mfile->save();

To retrieve a file directly from MogileFS as a string:

  $mfile = sfMogileFS::loadFile($mfs_key);

To get a valid tracker path:

  $mfile = sfMogileFS::loadFile($mfs_key);
  $mfile->getPath();

Example Setup

This is a complete example. However, You should only use this as a guide and should make changes based on your own application and needs.

The example assumes you already have a file in MogileFS with the key "cab9f3c712d04de874dafb0af0a0bf03e303e6e0".

schema.yml:

  files:
    _attributes:       { phpName: File }
    id:
    user_id:           { type: integer, foreignTable: users, foreignReference: id, onDelete: cascade }
    file_type_id:      { type: integer, foreignTable: file_types, foreignReference: id, onDelete: cascade }
    mime_type:                 varchar(128)
    extension:                 varchar(8)
    title:                     varchar(128)
    description:               varchar(255)
    created_at:
    updated_at:

  photos:
    _attributes:            { phpName: Photo }
    id:
    file_id:                { type: integer, foreignTable: files, foreignReference: id, onDelete: cascade }
    photo_type_id:          { type: integer, foreignTable: photo_types, foreignReference: id, onDelete: cascade }
    photo_status_type_id:   { type: integer, foreignTable: photo_status_types, foreignReference: id, onDelete: cascade }
    mogilefs_key:           { type: varchar(40), phpName: MogileFSKey, required: true }
    width:                          integer
    height:                         integer
    filesize:                       integer
    created_at:

routing.yml:

mfs:
  url:   /i/:mkey
  param: { module: file, action: showMogileFSFile }

/apps//modules/file/actions/uploadAction.class.php:

class uploadAction extends sfAction
{
  public function execute()
  {
    if ($this->getRequest()->getMethod() == sfRequest::POST && $this->getRequest()->hasFiles())
    {
      if ($this->getRequest()->hasFileErrors())
      {
        $this->setFlash('notice', 'An error occurred while uploading ('.$this->getRequest->getFileError('file').')');
        $this->redirect('@upload');
      }

      // save() method adds file to File and Photo tables as well as MogileFS
      $file = new File();
      $file->setUserId($this->getUser()->getId());
      $file->setFileTypeId(1); // image
      $file->setMimeType($this->getRequest()->getFileType('file'));
      $file->setExtension($this->getRequest()->getFileExtension('file'));
      $file->setTitle($this->getRequestParameter('title'));
      $file->setDescription($this->getRequestParameter('description'));
      $file->setTmpFile($this->getRequest()->getFilePath('file')); // not actually part of model but used by it
      $file->setFilesize($this->getRequest()->getFileSize('file')); // not actually part of the model but used by it
      $file->save();

      // delete upload
      unlink($file->getTmpFile());

      // redirect to upload form
      $this->setFlash('notice', 'Your file has been uploaded');
      $this->redirect('@upload');
    }
    else
    {
      // display upload form
      return sfView::SUCCESS;
    }
  }

//lib/model/Photo.php:

class Photo extends BasePhoto
{
  public function save($con = null)
  {
    if ($this->isNew())
    {
      $this->setMogileFSKey(hash('sha1', $this->getFileId().':'.$this->getId()));
    }
    parent::save($con);
  }
}

//lib/model/File.php:

class File extends BaseFile
{
  protected $filesize;
  protected $tmp_file;

  public function setFilesize($fileSize)
  {
    $this->filesize = $fileSize;
  }

  public function getFilesize()
  {
    return $this->filesize;
  }

  public function setTmpFile($filePath)
  {
    $this->tmp_file = $filePath;
  }

  public function getTmpFile()
  {
    return $this->tmp_file;
  }

  /**
   * save
   * adds original file to file table, sub-file (photo, etc) table and MogileFS
   * All steps are wrapped in a transaction.
   *
   * @param mixed $con
   * @access public
   * @return void
   */
  public function save($con = null)
  {
    if ($this->isNew())
    {
      list($width, $height) = getimagesize($this->getTmpFile());

      // store it
      $con = Propel::getConnection();
      try
      {
        $con->begin();

        // save to File table
        parent::save($con);

        // add to photo table
        if ($this->getFileTypeId() == 1)
        {
          $photo = new Photo();
          $photo->setFileId($this->getId()); // parent file
          $photo->setPhotoTypeId(1); // original photo
          $photo->setPhotoStatusTypeId(1); // uploaded but not converted
          $photo->setWidth($width);
          $photo->setHeight($height);
          $photo->setFilesize($this->getFilesize());
          $photo->save();
        }

        // save to MogileFS
        $sfMogileFSFile = new sfMogileFSFile();
        $sfMogileFSFile->setKey($photo->getMogileFSKey());
        $sfMogileFSFile->setClass('orig_photo');
        $sfMogileFSFile->setFile($this->getTmpFile());
        $sfMogileFSFile->setFilesize($photo->getFilesize());
        $sfMogileFSFile->save();

        $con->commit();
      }
      catch (Exception $e)
      {
        $con->rollback();
        throw $e;
      }
    }
  }
}

/apps//modules/file/actions/showMogileFSFileAction.class.php:

class showMogileFSFileAction extends sfAction
{
  public function execute()
  {
    // remove file's extension
    $mogileFSKey = preg_replace('@\.[\w\d]+$@', '', $this->getRequestParameter('mkey'));

    // get mogilefs key from photo table
    $c = new Criteria();
    $c->add(PhotoPeer::MOGILEFS_KEY, $mogileFSKey);
    $photo = PhotoPeer::doSelectOne($c);

    // load path from mogilefs
    $sfMogileFSRemoteFile = sfMogileFS::loadFile($mogileFSKey);

    // call perlbal's x-reproxy-url and set mime type
    $this->getResponse()->setHttpHeader('Content-type', $photo->getFile()->getMimeType(), true);
    $this->getResponse()->setHttpHeader('X-REPROXY-URL', $sfMogileFSRemoteFile->getPath(), true);

    return sfView::HEADER_ONLY;
  }
}

layout.php (or any template):



This results in the following HTML rendering:

<img src="/index.php/i/cab9f3c712d04de874dafb0af0a0bf03e303e6e0.jpg" />

Todo

  • More detailed How It Works explanation
  • Example of calling MogileFS using a separate php script and mod_rewrite (instead of having to launch symfony for every image call)
  • Backend module with MogileFS stats, etc.

Changelog

trunk

2007-07-19 | 0.6.0 Alpha

  • bmeynell: Initial release