Yii 2.0: Upload files in Yii2 with MongoDb and GridFs

5 followers

The scenario

Uploading files in a webapp can be extremely tricky and sometimes the quickest way to do it is to store the file directly in the webserver or into a DNS (like Amazon S3) and then to save the link and the metadata inside a table in the DB. The thing is that you'll have to deal with file permissions, server storage, file management and so on (which is perfectly fine, by the way).

Another approach is to save the file as a blob inside a database: this is extremely easy and convenient with MongoDb because you can then leverage the built-in sharding feature in case you scale-up your app. With yii2 it's also surprisingly easy. Let's begin.

The structure

We will work with the GridFs framework tutorial found here: Metadata and asset management.

Step 1: the model

Depending on your type of application (advanced or basic), create a new model Asset in app\models or, backend\models / frontend\models common\models.

namespace common\models;
 
use yii\mongodb\file\ActiveRecord;
 
/**
 * Class Asset
 * @package common\models
 * @property string $_id MongoId
 * @property array $filename
 * @property string $uploadDate
 * @property string $length
 * @property string $chunkSize
 * @property string $md5
 * @property array $file
 * @property string $newFileContent
 * Must be application/pdf, image/png, image/gif etc...
 * @property string $contentType
 * @property string $description
 */
class Asset extends ActiveRecord
{
 
    public static function collectionName()
    {
        return 'asset';
    }
 
    public function rules()
    {
        return[
            [['description', 'contentType'], 'required'],
        ];
    }
 
    public function attributes()
    {
          return array_merge(
              parent::attributes(),
              ['contentType', 'description']
          );
    }
}

Now, from the documentation, you must set all the fields but 2: "contentType" and "description" that we will add for 2 reasons. The type of content is necessary to render the blob and the description is a nice addition for the "alt" tag in case of images. Create the AssetSearch model class accordingly to your needs.

Step 2: the view

In _form.php (format as you like, in this case there's no html because I put this form in a modal with custom html and tags that can be confusing for the sake of the tutorial)

<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
 
 $form = ActiveForm::begin([
                'options'=>['enctype'=>'multipart/form-data']]);?>
 
                <?= $form->field($model, 'description')->textInput(['class'=>'form-control inline-input', 'placeholder'=>Yii::t('app', 'Description')])->label('')?>
 
                <?= $form->field($model, 'file')->fileInput()->label('')?>
 
                <?= Html::submitButton($model->isNewRecord ? 'Upload' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
 
<?php ActiveForm::end(); ?>

Step 3: the controller

Action Create

Now, the interesting part:

use yii\web\UploadedFile;
 
public function actionCreate()
    {
        $model = new Asset;
        if ($model->load($_POST)) {
            $file = UploadedFile::getInstance($model,'file');
            $model->filename=$file->name;
            $model->contentType=$file->type;
            $model->file=$file;
            if($model->save()){
                return $this->redirect(['view', 'id' => (string)$model->_id]);
            }
        }else
            return $this->render('create', [
                'model' => $model,
            ]);
    }

Under the hood GridFs will create 2 collections: asset.files and asset.chunks ; the first one stores the metadata and the second one the file itself, but you don't need to worry about any of that because our friends at 10gen and yii made all this quite "invisible" and easy.

Ok, now you've uploaded a file with GriFs in MongoDb. Now you have to retrieve it.

Action get

Name this action as you like. Basically this is the action that will render the file; now you'll see the reason of that "contentType" attribute.

/**
     * Displays the asset as a blob
     * @param integer $_id
     * @return mixed
     */
    public function actionGet($id)
    {
        $model=$this->findModel($id);
        header('Content-type: '.$model->contentType);
        echo $model->file->getBytes();
    }

Rendering the file

In order to render the file - let's say, an image - , apply this code in any view:

use yii\helpers\Html;
use yii\helpers\Url;
 
<?= Html::img(Url::to(['asset/get', 'id'=>(string)$model->_id]), ['alt'=>$model->description]);?>

If this was an asset gallery à la Wordpress where you put pdf, images and so on, then you might want something like this:

use yii\helpers\Html;
use yii\helpers\Url;
 
<?php if(strpos($model->contentType,'image')===false): //Not an image?>
        <iframe src="<?=Url::to(['asset/get', 'id'=>(string)$model->_id]);?>" width="100%" height="600px"></iframe>
    <?php else: ?>
        <?= Html::img(Url::to(['asset/get', 'id'=>(string)$model->_id]), ['alt'=>$model->description]);?>
    <?php endif; ?>

Quick note: this method is recommended for all of you guys who have to store sensible user data files (like card IDs, passports and so on) because you can leverage the standard AccessControl rules to display a single file so that you can put granular permissions to CRUD operations regarding files too (especially to read operations). If you are thinking of storing your user files in the server and you are not sure of how an .htaccess or apache2.conf file works, then I recommend this method (search engines crawlers can be very nasty when you publish /your/folder/image.png and you forget to remove the "List" permissions).

Hope this will help someone: thanks for your feedback.

Total 3 comments

#18450 report it
skworden at 2014/10/30 07:16pm
Updated Wiki

I updated the wiki to show the proper namespaces and Url::to...works great thanks raz

#18412 report it
razvanphp at 2014/10/25 06:03am
New Url Helper

replace with:

Url::to(['asset/get', 'id'=>(string)$model->_id]);

GitHub yii2: Adding Url helper class #2630

#18397 report it
skworden at 2014/10/23 10:50pm
Error
Call to undefined method yii\helpers\Html::url()
Html::url()

isn't a function? Do you have the proper one to use in the view?

I'm including

use yii\helpers\Html;

Other than that seems to be working but I can't show the image.

Leave a comment

Please to leave your comment.

Write new article