Unlimited Model fields - Expandable Behavior

Posted by Felix Geisendörfer, on Jun 01, 2008 - in PHP & CakePHP » DataSources, Models & Behaviors

Hey folks,

lets say you have a model called Upload. Your Upload model has some generic fields like this:

The problem

However, you would like to store different types of meta information for different kinds of uploads. Examples:

Images:

  • Width
  • Height
  • Quality (if jpg)
  • Camera
  • Lens
  • Focus

Videos:

  • FPS
  • Bitrate

PDFs:

  • Author
  • Description

ZIPs:

  • Original Size
  • File Count
  • Compression Rate

The solution

So what are you going to do? Add 13 fields to your uploads table? Probably not. It is time to normalize things:

Ok nothing fancy so far. CakePHP's associations make it easy to deal with it. However, working with this setup can be a little inconvenient at times. Everytime you fetch a set of records from Upload, you will have to manually extract the meta information from the associated UploadField records:

-
    Upload:
        id: 1
        name: funny.mov
        type: video/quicktime
        bytes: 20480
        created: 2008-06-01 14:47:23
    UploadField:
        -
            id: 1
            upload_id: 1
            key: fps
            val: 26
        -
            id: 2
            upload_id: 1
            key: bitrate
            val: 376

So everytime you want to access your videos bitrate you will have to search your UploadField records for the 'bitrate' key. How annoying. But worry not, Expandable comes to rescue. With the Expandable behavior activated on your Upload model, your resultset will look like this:

-
    Upload:
        id: 1
        name: funny.mov
        type: video/quicktime
        bytes: 20480
        created: 2008-06-01 14:47:23
        fps: 26
        bitrate: 376
    UploadField:
        -
            id: 1
            upload_id: 1
            key: fps
            val: 26
        -
            id: 2
            upload_id: 1
            key: bitrate
            val: 376

But it comes even better. Expandable also makes it dead-simple to create / update UploadField records. This is how it works:

php
  1. $this->Upload->save(array(
  2.   'id' => 1,
  3.   'fps' => 30,
  4.   'rating'= > 7/10,
  5. ));

Without you having to do anything, the following happens to your uploads resultset:

-
    Upload:
        id: 1
        name: funny.mov
        type: video/quicktime
        bytes: 20480
        created: 2008-06-01 14:47:23
        fps: 30
        bitrate: 376
        rating: 0.7
    UploadField:
        -
            id: 1
            upload_id: 1
            key: fps
            val: 30
        -
            id: 2
            upload_id: 1
            key: bitrate
            val: 376
        -
            id: 3
            upload_id: 1
            key: rating
            val: 0.7

As you can see the fps UploadField value has been updated and a new record with the key rating has been created. So this means you can use the CakePHP form helper to create different editors for your uploads like this:

php
  1. $form->input('Upload.fps')
  2. $form->input('Upload.bitrate')
  3. $form->input('Upload.rating', array('options' => array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)));

And even so none of those fields really exist on the Upload model, everything will work just as if they would ; ).

How to use it

  1. Download the behavior from: the debuggable Scraps repository at github
  2. Place the expandable.php file into /app/models/behaviors/expandable.php
  3. Optional: Place the expandable.test.php file into /app/tests/cases/behaviors/expandable.test.php
  4. Create a table and model for UploadField with at least the fields shown above. (replace Upload with the name of your base model)
  5. Setup a Upload hasMany UploadField and UploadField belongsTo Upload association
  6. Add this to your Upload model:
php
  1. class Upload extends AppModel{
  2.   var $actsAs = array('Expandable');
  3. }

That is it. You are ready to go. Enjoy the magic ; ).

Pro Contra
  • Easy to use
  • Saves db space
  • Mostly leverages existing CakePHP magic
  • (Small) Performance hit while updating meta data

Please let me know what you think about this approach!

-- Felix Geisendörfer aka the_undefined