# Using a polymorphic attachment model for file storage

In some cases you will want to store multiple file uploads for multiple models, but will not want to use multiple tables because your database is normalized. For example, we might have a `Post` model that can have many images for a gallery, and a `Message` model that has many videos. In this case, we would use an `Attachment` model:

Post hasMany Attachment

We could use the following database schema for the `Attachment` model:

```sql
CREATE table attachments (
    `id` int(10) unsigned NOT NULL auto_increment,
    `model` varchar(20) NOT NULL,
    `foreign_key` int(11) NOT NULL,
    `name` varchar(32) NOT NULL,
    `attachment` varchar(255) NOT NULL,
    `dir` varchar(255) DEFAULT NULL,
    `type` varchar(255) DEFAULT NULL,
    `size` int(11) DEFAULT 0,
    `active` tinyint(1) DEFAULT 1,
    PRIMARY KEY (`id`)
);
```

Our attachment records would thus be able to have a name and be activated or deactivated on the fly. The schema is simply an example, and such functionality would need to be implemented within your application.

Once the `attachments` table has been created, we would create the following model:

```php
class Attachment extends AppModel
{
    public $actsAs = [
        'Upload.Upload' => [
            'attachment' => [
                'thumbnailSizes' => [
                    'xvga' => '1024x768',
                    'vga' => '640x480',
                    'thumb' => '80x80',
                ],
            ],
        ],
    ];

    public $belongsTo = [
        'Post' => [
            'className' => 'Post',
            'foreignKey' => 'foreign_key',
        ],
        'Message' => [
            'className' => 'Message',
            'foreignKey' => 'foreign_key',
        ],
    ];
}
```

We would also need to create a valid inverse relationship in the `Post` model:

```php
class Post extends AppModel
{
    public $hasMany = [
        'Image' => [
            'className' => 'Attachment',
            'foreignKey' => 'foreign_key',
            'conditions' => [
                'Image.model' => 'Post',
            ],
        ],
    ];
}
```

The key thing to note here is the `Post` model has some conditions on the relationship to the `Attachment` model, where the `Image.model` has to be `Post`. Remember to set the `model` field to `Post`, or whatever model it is you'd like to attach it to, otherwise you may get incorrect relationship results when performing find queries.

We would also need a similar relationship in our `Message` model:

```php
class Message extends AppModel
{
    public $hasMany = [
        'Video' => [
            'className' => 'Attachment',
            'foreignKey' => 'foreign_key',
            'conditions' => [
                'Video.model' => 'Message',
            ],
        ],
    ];
}
```

Now that we have our models setup, we should create the proper actions in our controllers. To keep this short, we shall only document the Post model:

```php
class PostsController extends AppController
{
    public $components = ['Flash'];

    /* the rest of your controller here */
    public function add()
    {
        if ($this->request->is('post')) {
            try {
                $this->Post->createWithAttachments($this->request->data);
                $this->Flash->success(__('The post has been saved'));
            } catch (Exception $e) {
                $this->Flash->error($e->getMessage());
            }
        }
    }
}
```

In the above example, we are calling our custom `createWithAttachments` method on the `Post` model. This will allow us to unify the Post creation logic together in one place. That method is outlined below:

```php
class Post extends AppModel
{
    /* the rest of your model here */

    public function createWithAttachments($data)
    {
        // Sanitize your images before adding them
        $images = [];
        if (!empty($data['Image'][0])) {
            foreach ($data['Image'] as $i => $image) {
                if (is_array($data['Image'][$i])) {
                    // Force setting the `model` field to this model
                    $image['model'] = 'Post';

                    // Unset the foreign_key if the user tries to specify it
                    if (isset($image['foreign_key'])) {
                        unset($image['foreign_key']);
                    }

                    $images[] = $image;
                }
            }
        }
        $data['Image'] = $images;

        // Try to save the data using Model::saveAll()
        $this->create();
        if ($this->saveAll($data)) {
            return true;
        }

        // Throw an exception for the controller
        throw new Exception(__('This post could not be saved. Please try again'));
    }
}
```

The above model method will:

- Ensure we only try to save valid images
- Force the foreign_key to be unspecified. This will allow saveAll to properly associate it
- Force the model field to `Post`

Now that this is set, we just need a view for our controller. A sample view for `templates/Posts/add.ctp` (legacy layout: `app/View/Posts/add.ctp`) is as follows (fields not necessary for the example are omitted):

```php
<?php
echo $this->Form->create('Post', ['type' => 'file']);
echo $this->Form->input('Image.0.attachment', ['type' => 'file', 'label' => 'Image']);
echo $this->Form->input('Image.0.model', ['type' => 'hidden', 'value' => 'Post']);
echo $this->Form->end(__('Add'));
```

The one important thing you'll notice is that I am not referring to the `Attachment` model as `Attachment`, but rather as `Image`; when I initially specified the `$hasMany` relationship between an `Attachment` and a `Post`, I aliased `Attachment` to `Image`. This is necessary for cases where many of your Polymorphic models may be related to each other, as a type of *hint* to the CakePHP ORM to properly reference model data.

I'm also using `Model.{n}.field` notation, which would allow you to add multiple attachment records to the Post. This is necessary for `$hasMany` relationships, which we are using for this example.

Once you have all the above in place, you'll have a working Polymorphic upload!

Please note that this is not the only way to represent file uploads, but it is documented here for reference.
