This is an old revision of the document!


Yii 2 File Upload
Example CSV File Upload

Model

Create DataUploadForm model:

<?php
namespace app\models;
 
use yii\base\Model;
use yii\web\UploadedFile;
 
class DataUploadForm extends Model
{
    const OVERWRITE_NO        = 0;
    const OVERWRITE_YES       = 1;
 
    const IMPORT_METHOD_UPDATE_INSERT = 0;  // Update existing data (update, or insert if not exist)
    const IMPORT_METHOD_UPDATE        = 1;  // Update existing data (update only, no insert if not exist)
    const IMPORT_METHOD_APPEND_ALL    = 2;  // Append all to existing data (insert only, but all)
    const IMPORT_METHOD_APPEND_NEW    = 3;  // Append only new to existing data (insert only, but only new)
    const IMPORT_METHOD_REPLACE       = 4;  // Replace existing data (delete all, then insert)
 
    public static $importMethods = [
            DataUploadForm::IMPORT_METHOD_UPDATE_INSERT => 'Update existing / Insert new content',
            DataUploadForm::IMPORT_METHOD_UPDATE        => 'Update only existing content',
            DataUploadForm::IMPORT_METHOD_APPEND_ALL    => 'Append all content', 
            DataUploadForm::IMPORT_METHOD_APPEND_NEW    => 'Append only new content', 
            DataUploadForm::IMPORT_METHOD_REPLACE       => 'Replace content', 
    ];
 
    /**
     * @var UploadedFile
     */
    public $dataFile;
    public $overwriteExistingContent = self::OVERWRITE_NO;
    public $importMethod = self::IMPORT_METHOD_UPDATE_INSERT;
 
    public function rules()
    {
        return [
            // see '@vendor/yiisoft/yii2/validators/FileValidator.php' for all options
            [['dataFile'], 'file', 
                'skipOnEmpty'    => false, 
                'maxFiles'       => 1,
 
                // Extensions: For CSV files, use uppercase CSV extension in actual file, otherwise it fails.
                'extensions'     => 'txt, CSV, xls', 
 
                // MimeTypes: must match file type (extension) to be uploaded. Eg: txt, csv, png, csv (in excel).
                //'mimeTypes'      => "text/plain, text/CSV, image/png, application/vnd.ms-excel",  
                //'mimeTypes'      => "*",  // any MIME type
 
                'checkExtensionByMimeType' => false,  // false to stop verifying file extension to MIME types
                'wrongExtension' => '{attribute} = [{file}].  Only these extensions are allowed: {extensions}.'
            ],
        ];
    }
 
    public function upload()
    {
        if ($this->validate()) {
            $this->dataFile->saveAs('uploads/' . 
                strtolower($this->dataFile->baseName) . '.' . 
                strtolower($this->dataFile->extension) 
            );  // save to [app]/web/uploads
            return true;
        } else {
            return false;
        }
    }
 
    //public function attributeLabels()
    //{
    //    return ['file' => Yii:t('app', 'Archivo')];  // es translation
    //}
}
?>

Controller

Add these actions to your controller. Eg: [app]/controllers/ItemController:

namespace app\controllers;
 
use Yii;
use app\models\Item;
use app\models\ItemSearch;
use app\models\DataUploadForm;
use yii\web\Response;
use yii\web\Request;
use yii\web\UploadedFile;
use yii\filters\AccessControl;
use yii\filters\VerbFilter;
//...
 
defined('DS') or define('DS', DIRECTORY_SEPARATOR);  // set const if not defined
 
class ItemController extends Controller
{
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'rules' => [
                    [
                        'actions' => [
                            'index', 'create', 'view', 'update', 'delete', 'report', 
                            'download-sample', 'batch-create',
                        ],
                        'allow' => true,
                        'roles' => ['@'],  // @ = Authenticated users
                    ],
                    //[
                    //   'allow' => true,
                    //   'actions' => ['index', 'view'],
                    //   'roles' => ['?'],  // ? = Guest user
                    //],
                ],
            ],
            //...
        ];
    }
 
    //...
 
    /**
     * Creates a downloadable sample CSV file to be used in batch-create.
     * It return processing messages.
     * @return mixed
     */
    public function actionDownloadSample($file_name = 'file.csv') 
    {
        $data = [
            ["code", "description", "quantity"],
            ["AHI-POUCH", "Audina Navy Blue Pouch", "800"],
            ["NOLOGO-SDELIVERYCASE", "Audina S-Delivery White Case No Logo", "96"],
        ];
        $this->actionOutputCSV($data, $file_name);
    }
 
    // Usage: 
    //     actionOutputCSV(array(
    //         array("Volvo", "BMW", "Toyota"),
    //         array("Volvo 1", "BMW 1", "Toyota 1"),
    //         array("Volvo 2", "BMW 2", "Toyota 2"),
    //         array("Volvo 3", "BMW 3", "Toyota 3"),
    //     ),'download.csv');
    // Source: See more at: https://arjunphp.com/create-download-csv-files-php/#sthash.f6JCTy4J.dpuf
    function actionOutputCSV($data, $fileName = 'file.csv', $is_csv_excel=false) 
    {
        # output headers so that the file is downloaded rather than displayed
        header("Content-Type: text/csv");
        header("Content-Disposition: attachment; filename=$fileName");
        # Disable caching - HTTP 1.1
        header("Cache-Control: no-cache, no-store, must-revalidate");
        # Disable caching - HTTP 1.0
        header("Pragma: no-cache");
        # Disable caching - Proxies
        header("Expires: 0");
 
        # Start the ouput
        $output = fopen("php://output", "w");
        if ($is_csv_excel) {
            fwrite($output, "sep=\t"); // set default separator to TAB, so Excel can import CSV as tab-separated
        }
 
 
         # Then loop through the rows
        foreach ($data as $row) {
            # Add the rows to the body
            //fputcsv($output, $row); // here you can change delimiter/enclosure
            fputcsv($output, $row, "\t"); // here you can change delimiter/enclosure
        }
        # Close the stream off
        fclose($output);
    }
 
    /**
     * Creates Item models in batch.
     * If create is successful, the browser will be redirected to the 'index' page.
     * @return mixed
     */
    public function actionBatchCreate()
    {
        $model = new DataUploadForm();
 
        if (Yii::$app->request->isPost) {
            $model->dataFile = UploadedFile::getInstance($model, 'dataFile');
            $model->overwriteExistingContent = (
                isset($_POST['DataUploadForm']['overwriteExistingContent']) ? 
                $_POST['DataUploadForm']['overwriteExistingContent'] : 
                DataUploadForm::OVERWRITE_NO
            );
            $model->importMethod = (
                isset($_POST['DataUploadForm']['importMethod']) ? 
                $_POST['DataUploadForm']['importMethod'] : 
                DataUploadForm::IMPORT_METHOD_APPEND
            );
 
            if ($model->upload()) {
                // file is uploaded successfully
                Yii::$app->session->setFlash('success', "File {$model->dataFile} was uploaded successfully.");
 
                // clear table
                if ($model->overwriteExistingContent) {
                    Item::deleteAll();
                }
 
                // import data
                $resultsImport = $this->importDataFromCSV($model->dataFile, $model->importMethod);
                if ($resultsImport) {
                    $panelCollapsed  = ' <button class="btn btn-primary" type="button" ';
                    $panelCollapsed .= '   data-toggle="collapse" ';
                    $panelCollapsed .= '   data-target="#collapseExample" ';
                    $panelCollapsed .= '   aria-expanded="false" ';
                    $panelCollapsed .= '   aria-controls="collapseExample"> View Import Results </button>';
                    $panelCollapsed .= '<div class="collapse" id="collapseExample">';
                    $panelCollapsed .= '  <div class="well">' . $resultsImport . '</div>';
                    $panelCollapsed .= '</div>';
                    Yii::$app->session->setFlash('success', 
                        "Item data was imported successfully. " . $panelCollapsed
                    );
                } else {
                    Yii::$app->session->setFlash('error', "Item data failed to be imported. ");
                }
 
                return $this->redirect(['index']);
            } else {
                Yii::$app->session->setFlash('error', "File {$model->dataFile} failed to be uploaded.");
            }
        }
 
        return $this->render('batch-create', ['model' => $model]);
    }
 
    /**
     * Updates an existing Item model.
     * If update is successful, the browser will be redirected to the 'index' page.
     * @return mixed
     */
    public function actionBatchUpdate()
    {
        // // Retrieve items to be updated in a batch mode,
        // // assuming each item is of model class 'Item'
        // $items=$this->getItemItemsToUpdate();
        // if (Model::loadMultiple($items, Yii::$app->request->post()) && Model::validateMultiple($items)) {
        //     $count = 0;
        //     foreach ($items as $item) {
        //        // populate and save records for each model
        //         if ($item->save()) {
        //             // do something here after saving
        //             $count++;
        //         }
        //     }
        //     Yii::$app->session->setFlash('success', "Processed {$count} records successfully.");
        //     return $this->redirect(['index']); // redirect to your next desired page
        // } else {
        //     return $this->render('update', [
        //         'order-items' => $items,   
        //     ]);
        // }
    }
 
    /**
     * Imports a CSV file to be used in batch-create.
     * It return processing messages.
     * @return mixed
     */
    private function importDataFromCSV($fileName, $importMethod=DataUploadForm::IMPORT_METHOD_UPDATE_INSERT)
    {
        $output = "<pre>\n";
        $output .= "Processing data\n";
 
        // Preprocess based on Import Method
        switch($importMethod) {
            case DataUploadForm::IMPORT_METHOD_APPEND_ALL:    // Append content (insert all)
            case DataUploadForm::IMPORT_METHOD_APPEND_NEW:    // Append only new to existing data (insert only, but only new)
            case DataUploadForm::IMPORT_METHOD_UPDATE_INSERT: // Update content (update, or insert if new)
            case DataUploadForm::IMPORT_METHOD_UPDATE:        // Update content (update only, no insert if new)
            default:
                break;
 
            case DataUploadForm::IMPORT_METHOD_REPLACE:       // Replace existing data (delete all, then insert)
                Item::deleteAll();
                break;
        }
 
        // open the source data file
        //$dataFilename = Yii::$app->basePath . DS . 'commands' . DS . 'data' . DS . 'Item-data.csv';
        $dataFilename = Yii::$app->basePath . DS . 'web' . DS . 'uploads' . DS . strtolower($fileName);
        if (!file_exists($dataFilename)) {
            //echo "Missing data file: [" . $dataFilename . "]\n";
            Yii::$app->session->setFlash('error', "Missing data file: [{$dataFilename}]");
            return false;
            //return "Missing data file: [" . $dataFilename . "]\n";
        }
 
        $rowCnt   = 0;
 
        $dataFile = fopen($dataFilename, "r") or die("Unable to open file!");
        while(!feof($dataFile)) {
            // grab one line from file
            $data = fgets($dataFile);
 
            // parse data
            $row_fields = explode("\t", $data);
 
            // Skip header
            if ((trim($row_fields[0]) == '')     ||
                (trim($row_fields[0]) == 'sep=') ||
                (trim($row_fields[0]) == 'code') ||
                (trim($row_fields[0]) == '------') )
            {
                $output .= "### Skipped: {$data}. \n";
                continue; // skip this header row
            }
 
            // Get row data
            if (count($row_fields)>0) {
                $code            = trim(trim($row_fields[0]),  '"');
                $description     = trim(trim($row_fields[1]),  '"');
                $quantity        = trim(trim($row_fields[2]),  '"');
            } else {
                break; // stop processing rows
            }
 
            switch($importMethod) {
                case DataUploadForm::IMPORT_METHOD_APPEND_ALL:    // Append content (insert all)
                case DataUploadForm::IMPORT_METHOD_UPDATE_INSERT: // Update content (update, or insert if new)
                default:
                    if (($model = Item::findOne(['code'=>$code])) === null) {
                        $model = new Item();  // if existing row not found, we add as new record.
                    }
                    break;
 
                case DataUploadForm::IMPORT_METHOD_APPEND_NEW:    // Append only new to existing data (insert only, but only new)
                    if (($model = Item::findOne(['code'=>$code])) !== null) {
                        $processThisRow = false; // Skip this existing row. We only want to insert new records.
                    } else {
                        $model = new Item();
                    }
                    break;
 
                case DataUploadForm::IMPORT_METHOD_UPDATE:        // Update content (update only, no insert if new)
                    if (($model = Item::findOne(['code'=>$code])) === null) {
                        $processThisRow = false; // Skip this new row. We only want to update existing records.
                    }
                    break;
 
                case DataUploadForm::IMPORT_METHOD_REPLACE:       // Replace existing data (delete all, then insert)
                    $model = new Item();  // we only add as new record (any previous records were deleted at beginning of this function)
                    break;
            }
 
            // insert data
            if ($processThisRow) { 
                $model->code        = $code;
                $model->description = $description;
                $model->quantity    = $quantity;
            }
 
            $rowCnt++;  // for printing, it is always one ahead
 
            if ($model->save()) {
                $entry_id = $model->id;
                $output .= "{$rowCnt}. ### Inserted Item [{$entry_id}] ###\n";
                $row_fieldsData = "[".implode(',', [
                    $code, $description, $quantity
                ])."]";
                $output .= "{$rowCnt}. Insert Item {$row_fieldsData}\n";
            } else {
                $row_fieldsData = "[".implode(',', [
                    $code, $description, $quantity
                ])."]";
                $output .= "{$rowCnt}. Failed to insert Item {$row_fieldsData}. " . 
                  print_r($model->getErrors(), true) . "\n";
            }
        }
        $output .= "</pre>\n";
        fclose($dataFile);
 
        return $output;
    }
 
}    

View

Add a button to do a Batch Upload. Update view index under views. Eg: [app]/views/items/index

<?php
//...
<?= Html::a(Yii::t('app', 'Batch Upload'), ['batch-create'], ['class' => 'btn btn-success']) ?>
//...
?>

Create view batch-create under views. Eg: [app]/views/items/batch-create:

<?php
use yii\helpers\Html;
use yii\helpers\Url;
use yii\widgets\ActiveForm;
use yii\helpers\ArrayHelper;
 
$this->title = Yii::t('app', 'Batch Upload');
$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Item'), 'url' => ['index']];
//$this->params['breadcrumbs'][] = $this->title;
$this->params['breadcrumbs'][] = Yii::t('app', 'Batch Upload');
 
?>
 
<?php $form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data']]) ?>
 
    <h2>Item (Batch Upload)</h2>
 
    <div class="col-lg-12">
        <?php if(Yii::$app->session->hasFlash('success')): ?>
            <div class="alert alert-success" role="alert">
                <?= Yii::$app->session->getFlash('success'); ?>
            </div>
        <?php endif; ?>
        <?php if(Yii::$app->session->hasFlash('error')): ?>
            <div class="alert alert-danger" role="alert">
                <?= Yii::$app->session->getFlash('error'); ?>
            </div>
        <?php endif; ?>
    </div>
 
    <div class="alert alert-info" role="alert">
 
        <h4>Data File</h4>
        Create a CSV (<kbd>tab</kbd>-delimited) file with the following format. Eg:
 
        <table class="table table-bordered table-striped table-hover table-condensed" border="1">
            <tr>
                <td>code</td><td>description</td><td>quantity</td>
            </tr>
            <tr class="warning">
                <td>ACME-POUCH</td><td>Branded Pouch</td><td>800</td>
            </tr>
            <tr>
                <td>NOLOGO-POUCH</td><td>Unbranded Pouch</td><td>96</td>
            </tr>
        </table>
 
        <?= Html::a('<i class="glyphicon glyphicon-download d-icon"></i>' . ' Download Sample ', 
                 ['item/download-sample', 'file_name'=>'sample.csv'], ['class'=>'btn btn-default']) 
        ?>
    </div>
 
    <?= $form->field($model, 'dataFile')->fileInput(['class'=>'form-control']) ?>
    <?= $form->field($model, 'overwriteExistingContent')->dropDownList(
                    ['0'=>'No', '1'=>'Yes']
    ) ?>
 
    <button class='btn btn-success'>Submit</button>
 
<?php ActiveForm::end() ?>

Web

Create an uploads directory in web folders. Eg: [app]/web/uploads

Example Image Upload and Save Filename in DB

Controller

Add action actionCreate() to model (here it is model organization), and override afterSave() to store image file to server, and its path in the database:

public function actionCreate()
{
  $model = new Organization();
  if ($model->load(Yii::$app->request->post()) && $model->save()) {
     return $this->redirect(['view', 'id' => $model->id]);
  } 
  else {
     return $this->render('create', [
         'model' => $model,
     ]);
  }
}
 
public function afterSave($insert, $changedAttributes)
{
    if(isset($this->logo)) {
        $this->logo = UploadedFile::getInstance($this, 'logo');
        if(is_object($this->logo)) {
            $path = Yii::$app->basePath . '/images/';               // set directory path to save image
            $this->logo->saveAs($path.$this->id."_".$this->logo);   // saving image in folder
            $this->logo = $this->id."_".$this->logo;                // appending id to image name            
            \Yii::$app->db->createCommand()
                  ->update('organization', ['logo' => $this->logo], 'id = "'.$this->id.'"')
                  ->execute();    // manually update image name to db
        }
    }
}

Source: StackOverflow: Yii 2 Upload and Save Image Files Locally and Image URL is Saved in DB

Example Image File Upload

Model

Controller

View