JaggyGauran

Freelance developer, and designer

CakePHP Polymorphic Associations

So I recently just found out about polymorphic relationships and let me tell you, it's amazing. It saves a lot of fields and just feels right.

Anyways, implementing it in CakePHP 2 is quite easy.

Let's say we have an Image which belongs to Employee and Client, here's how the structure would look like.

Employee

class Employee extends AppModel
{
    public $hasMany = [
        'Image' => [
            'className'  => 'Image',
            'foreignKey' => 'foreign_id',
            'conditions' => ['Image.class' => 'Employee'],
        ]
    ];
}

Client

class Client extends AppModel
{
    public $hasMany = [
        'Image' => [
            'className'  => 'Image',
            'foreignKey' => 'foreign_id',
            'conditions' => ['Image.class' => 'Client'],
        ]
    ];
}

Image

Now, the client should have a foreign_id and class within their schema.

create table `images`(
    `id` int unsigned not null auto_increment primary key,
    `path` varchar(255),
    `class` varchar(255),
    `foreign_id` int unsigned
);
class Image extends AppModel
{
    public $belongsTo = [
        'Employee' => [
            'className'  => 'Employee',
            'foreignKey' => 'foreign_id',
            'conditions' => ['Image.class' => 'Employee'],
        ],
        'Client' => [
            'className'  => 'Client',
            'foreignKey' => 'foreign_id',
            'conditions' => ['Image.class' => 'Client'],
        ]
    ];
}

Whenever you call $this->Image->find('all'), you'll get the related model.

Cleaning up

With what's done, there's an excess set,

array(
    'Image' => array(
        'path'       => '/path/to/image/file.jpg',
        'class'      => 'Client',
        'foreign_id' => 1
    ),

    'Employee' => array(
        'name' => null
    ),

    'Client' => array(
        'name' => 'Finn the Human'
    )
)

To clean this up, in your Image model, just clean it in your afterFInd

public function afterFind($result, $primary = false){
    return Set::filter($result, true); 
}

Update

I've been playing around with it, and it seems it returns an empty array if you try to do it with a hasOne relationship.