วันครอบครัว


การเขียนโปรแกรม Upload ไฟล์ด้วย Yii Framework 2

คู่มือการเรียนรู้ Yii Framework 2 (Yii2)

บทเรียนดีๆ สำหรับผู้ที่ต้องการแบ่งปัน บทเรียนของคุณอาจมีค่าสำหรับอีกหลายๆ คน

beta version

การเขียนโปรแกรม Upload ไฟล์ด้วย Yii Framework 2

มานพ กองอุ่น yii upload 3,025

สำหรับการ Upload ไฟล์นั้นแน่นอนว่าเกือบทุก Web Application จะต้องมีการ Upload ไฟล์ ขึ้นไปที่ Server ทั้ง Upload แบบไฟล์เดียวหรือ Upload ทีละหลายๆ ไฟล์พร้อมกัน

ในบทเรียนรู้นีเรามาทำความเข้าใจการ Upload ไฟล์ ทั้งทีอยู่ใน actionCreate actionUpdate และ actionView พร้อมทั้งระบบการลบรูปภาพออกจากระบบด้วย เพื่อเป็นแนวทางในการพัฒนา Web Application ด้วย Yii Framework 2 ต่อไป

สามารถดูรายละเอียดเพิ่มเติมได้ที่ http://www.yiiframework.com/doc-2.0/guide-input-file-upload.html

โครงสร้างไฟล์ประกอบ

  • frontend/controllers/UploadController.php
  • frontend/models/Upload.php
  • frontend/views/upload/index.php
  • frontend/views/upload/create.php
  • frontend/views/upload/update.php
  • frontend/views/upload/view.php
  • frontend/views/upload/_form.php
  • frontend/web/uploads/images
  • frontend/web/uploads/files

โครงสร้างตารางฐานข้อมูล

ในที่นี้จะใช้ yii migrate ในการสร้างตารางฐานข้อมูลโดยพิมพ์ 

yii migrate/create create_upload

จะได้ไฟล์ console/migrations/xxxxx_xxxxx_create_upload.php จากนั้นแก้ไข method up() ดังนี้

<?php

use yii\db\Migration;

/**
 * Handles the creation for table `upload`.
 */
class m160707_103419_create_upload extends Migration
{
    /**
     * @inheritdoc
     */
    public function up()
    {
        $this->createTable('upload', [
            'id' => $this->primaryKey(),
            'title' => $this->string()->notNull(),
            'content' => $this->text(),
            'image' => $this->string(),
            'files' => $this->string(),
        ]);
    }

    /**
     * @inheritdoc
     */
    public function down()
    {
        $this->dropTable('upload');
    }
}

จากนั้นทำการเรียก migration เพื่อ apply ลงฐานข้อมูลด้วยคำสั่ง

yii migrate

แล้วกด yes ก็จะได้ตาราง upload เรียบร้อยแล้ว 

ขั้นตอนต่อไปให้ทำการสร้าง Model โดยจะใช้ Gii Generator ก็ได้

Upload.php

สำหรับ Model Upload เป็น Model ในการทดสอบ หากท่านไหนใช้การ Upload กับ Model อื่นๆ ก็สามารถทำได้ โดยกำหนด Attribute และ Method ต่างๆ เพิ่มเติมคือ

  • $uploadImageFolder เป็นที่เก็บของรูปภาพ
  • $uploadFilesFolder เป็นที่เก็บของไฟล์
<?php

namespace frontend\models;

use Yii;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;

/**
 * This is the model class for table "upload".
 *
 * @property integer $id
 * @property string $title
 * @property string $content
 * @property string $image
 * @property string $files
 */
class Upload extends \yii\db\ActiveRecord
{
    public $uploadImageFolder = 'uploads/images'; //ที่เก็บรูปภาพ
    public $uploadFilesFolder = 'uploads/files'; //ที่เก็บไฟล์

    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return 'upload';
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['title'], 'required'],
            [['content'], 'string'],
            [['title'], 'string', 'max' => 255],
            [['image'], 'file', 'extensions' => 'jpg, png, gif'],//กำหนดให้เป็นแบบ file
            [['files'], 'file', 'maxFiles' => 10], //กำหนดให้เป็นแบบ file ตรงนี้สามารถกำหนดนามสกุลได้นะครับ เหมือนกันกับแบบของรูปภาพ
        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id' => 'ID',
            'title' => 'หัวเรื่อง',
            'content' => 'เนื้อหา',
            'image' => 'รูปภาพ',
            'files' => 'ไฟล์',
        ];
    }

    /*
    * UploadImage เป็น Method ในการ upload รูปภาพในที่นี้จะ upload ได้เพียงไฟล์เดียว โดยจะ return ชื่อไฟล์
    */
    public function uploadImage(){

        if($this->validate()){
            if($this->image){
                if($this->isNewRecord){//ถ้าเป็นการเพิ่มใหม่ ให้ตั้งชื่อไฟล์ใหม่
                    $fileName = substr(md5(rand(1,1000).time()),0,15).'.'.$this->image->extension;//เลือกมา 15 อักษร .นามสกุล
                }else{//ถ้าเป็นการ update ให้ใช้ชื่อเดิม
                    $fileName = $this->getOldAttribute('image');
                }
                $this->image->saveAs(Yii::getAlias('@webroot').'/'.$this->uploadImageFolder.'/'.$fileName);

                return $fileName;
            }//end image upload
        }//end validate
        return $this->isNewRecord ? false : $this->getOldAttribute('image'); //ถ้าไม่มีการ upload ให้ใช้ข้อมูลเดิม
    }

    /*
    * UploadFiles เป็น Method ในการ upload หลายไฟล์ สูงสุด 10 ไฟล์ตามที่ได้กำหนดจาก rules() และจะ return ชื่อไฟล์ aaaa.aaa, bbbb.bbb, ....
    */
    public function uploadFiles(){
        $filesName = []; //กำหนดชื่อไฟล์ที่จะ return
        if($this->validate()){
            if($this->files){
                foreach($this->files as $file){
                    $fileName = substr(md5(rand(1,1000).time()),0,15).'.'.$file->extension;//เลือกมา 15 อักษร .นามสกุล
//$fileName = iconv('UTF-8','WINDOWS-874',$file->baseName).'.'.$file->extension; //ใช้ไฟล์ภาษาไทย
                    $file->saveAs(Yii::getAlias('@webroot').'/'.$this->uploadFilesFolder.'/'.$fileName);
                    $filesName[] = $fileName;
                }

                if($this->isNewRecord){ //ถ้าเป็นการเพิ่ม Record ใหม่ให้บันทึกไฟล์ aaa.aaa,bbb.bbb ....
                    return implode(',', $filesName);
                }else{//ถ้าเป็นการปรับปรุงให้เพิ่มจากของเดิม
                    return implode(',', ArrayHelper::merge($fileName, $this->getOldAttribute('files') ? explode(',', $this->getOldAttribute('files')) : []));
                }
            }//end files upload

        }//end validate
        return $this->isNewRecord ? false : $this->getOldAttribute('files'); //ถ้าไม่มีการ upload ให้ใช้ข้อมูลเดิม
    }

    /*
    * getImage เป็น method สำหรับเรียกที่เก็บไฟล์ เพื่อนำไปแสดงผล
    */
    public function getImage()
    {
        return Yii::getAlias('@web').'/'.$this->uploadImageFolder.'/'.$this->image;
    }
    /*
    * getFiles เป็น method สำหรับเรียกชื่อไฟล์ให้อยู่ในรูปของ array
    */
    public function getFiles()
    {
        return explode(',', $this->files);
    }

}

UploadController.php

<?php

namespace frontend\controllers;

use frontend\models\Upload;
use yii\web\UploadedFile;
use yii\data\ActiveDataProvider;
use Yii;

class UploadController extends \yii\web\Controller
{
    public function actionCreate()
    {
        $model = new Upload();

        if($model->load(Yii::$app->request->post())){

            try{
                $model->image = UploadedFile::getInstance($model, 'image');
                $model->files = UploadedFile::getInstances($model, 'files'); //upload หลายไฟล์ getInstances (เติม s)
                $model->image = $model->uploadImage(); //method return ชื่อไฟล์
                $model->files = $model->uploadFiles(); //method return ชื่อไฟล์ aaaa.aaa, bbbb.bbb, ...


                $model->save();//บันทึกข้อมูล
                /*var_dump($model);
                die();*/
                Yii::$app->session->setFlash('success', 'บันทึกข้อมูลเรียบร้อย');
                return $this->redirect(['index']);
            }catch(Exception $e){
                Yii::$app->session->setFlash('danger', 'มีข้อผิดพลาด');
                return $this->redirect(['index']);
            }
        }

        return $this->render('create', [
            'model' => $model,
        ]);
    }

    public function actionUpdate($id)
    {
        $model = $this->loadModel($id);
        if($model->load(Yii::$app->request->post())){

            try{
                $model->image = UploadedFile::getInstance($model, 'image');
                $model->files = UploadedFile::getInstances($model, 'files');//upload หลายไฟล์ getInstances (เติม s)
                $model->image = $model->uploadImage();//method return ชื่อไฟล์
                $model->files = $model->uploadFiles();//method return ชื่อไฟล์ aaaa.aaa, bbbb.bbb, 


                $model->save();//บันทึกข้อมูล

                Yii::$app->session->setFlash('success', 'บันทึกข้อมูลเรียบร้อย');
                return $this->redirect(['index']);
            }catch(Exception $e){
                Yii::$app->session->setFlash('danger', 'มีข้อผิดพลาด');
                return $this->redirect(['index']);
            }
        }
        return $this->render('update', [
            'model' => $model
        ]);
    }

    public function actionView($id)
    {
        $model = $this->loadModel($id);
        return $this->render('view', [
            'model' => $model
        ]);
    }

    public function actionIndex()
    {
        $dataProvider = new ActiveDataProvider([
            'query' => Upload::find()
        ]);
        return $this->render('index', [
            'dataProvider' => $dataProvider
        ]);
    }

    public function actionDeleteFile($id, $file)
    {
        $model = $this->loadModel($id); //โหลด record ที่ต้องการมา
        try{
            $files = explode(',', $model->files); //เอาชื่อไฟล์มาแปลงเป็น array
            $files = array_diff($files, array($file)); //เอาชื่อไฟล์ที่ส่งมามาเอาออกจาก record
            unlink(Yii::getAlias('@webroot').'/'.$model->uploadFilesFolder.'/'.$file); //ลบไฟล์ออก
            $files = implode(',', $files); 
            $model->files = $files;//นำไฟล์ใหม่กลับเข้า attribute
            $model->save();//บันทึกข้อมูลใหม่
            Yii::$app->session->setFlash('success', 'ลบข้อมูลเรียบร้อยแล้ว');
            return $this->redirect(['view', 'id' => $model->id]);
        }catch(Exception $e){
            Yii::$app->session->setFlash('success', 'มีข้อผิดพลาด');
            return $this->redirect(['view', 'id' => $model->id]);
        }


    }

    protected function loadModel($id)
    {
        $model = Upload::findOne($id);
        if(!$model){
            throw new \Exception("Error Processing Request", 1);
        }
        return $model;
    }

}

frontend/views/upload/create.php

<?php
$this->title = 'Upload Single and Multiple files';
?>
<h1><?php $this->title?></h1>

<?=$this->render('_form', [
    'model' => $model
])?>

frontend/views/upload/index.php

<?php
use yii\grid\GridView;
$this->title = 'Yii2 Upload Tutorial';
?>
<?=GridView::widget([
    'dataProvider' => $dataProvider,
    'columns' => [
        ['class' => 'yii\grid\SerialColumn'],
        'title',
        'content',
        'image',
        'files',
        ['class' => 'yii\grid\ActionColumn']
    ]
])?>

frontend/views/upload/update.php

<?php
$this->title = 'Upload Single and Multiple files';
?>
<h1><?php $this->title?></h1>

<?=$this->render('_form', [
    'model' => $model
])?>

frontend/views/upload/view.php

<?php
use yii\widgets\DetailView;
use yii\helpers\Html;
use yii\helpers\Url;

$this->title = $model->title;
?>
<h1><?=$this->title?></h1>

<?=DetailView::widget([
    'model' => $model,
    'attributes' => [
        'content:html',
        [
            'attribute' => 'image',
            'format' => 'raw',
            'value' => $model->image ? Html::img($model->getImage(), ['class' => 'img-responsive']) : null
        ],
        [
            'attribute' => 'files',
            'format' => 'raw',
            'value' => call_user_func(function($data){
                $files = null;
                if($data->files){
                    foreach ($data->getFiles() as $key => $value) {
                        $files .= Html::a('<i class="glyphicon glyphicon-trash"></i>', ['delete-file', 'id' => $data->id, 'file' => $value], ['class' => 'btn btn-xs btn-danger', 'data' => ['confirm' => 'แน่ใจนะว่าต้องการลบ?', 'method' => 'post']]).' '.
                        Html::a($value, Url::to(Yii::getAlias('@web').'/'.$data->uploadFilesFolder.'/'.$value), ['target' => '_blank']).'<br />';
                    }
                    return $files;
                }else{
                    return null;
                }
            },$model),
        ]
    ]
])?>

frontend/views/upload/_form.php

<?php
//หากมี fileInput form จะสร้าง enctype="multipart/form-data ให้อัตโนมัตินะ (v2.0.8)
use yii\helpers\Html;
use yii\widgets\ActiveForm;
?>
<?php $form = ActiveForm::begin()?>

<?=$form->field($model, 'title')?>

<?=$form->field($model, 'content')->textarea()?>

<?=$form->field($model, 'image')->fileInput()?>

<?=$form->field($model, 'files[]')->fileInput(['multiple' => true])//ต้องมี [] ด้วยนะเพราะหลายไฟล์เป็น array และมี multiple ด้วย?>

<?=Html::submitButton('บันทึกข้อมูล', ['class' => 'btn btn-success'])?>

<?php ActiveForm::end()?>

 

ตัวอย่างหน้า view

ทดลองเพิ่มการ Upload รูปแบบ Base64

สร้าง migration เพื่อเพิ่ม field โดยพิมพ์คำสั่ง

yii migrate/create add_img64_to_upload --fields="img64:text"

จะได้ไฟล์ console/migration/xxx_xxx_add_img64_to_upload.php เมื่อเปิดดูจะได้ดังนี้

<?php

use yii\db\Migration;

/**
 * Handles adding img64 to table `upload`.
 */
class m160708_055025_add_img64_to_upload extends Migration
{
    /**
     * @inheritdoc
     */
    public function up()
    {
        $this->addColumn('upload', 'img64', $this->text());
    }

    /**
     * @inheritdoc
     */
    public function down()
    {
        $this->dropColumn('upload', 'img64');
    }
}

จากนั้นพิมพ์คำสั่ง yii migrate เพื่อ apply ลงฐานข้อมูล กด yes ก็จะได้ field img64 ในตาราง upload เรียบร้อยแล้ว

ติดตั้ง JavaScript Bower package ชื่อ croppie

โดยพิมพ์คำสั่ง 

bower install croppie

จากนั้นจะได้ folder vendor/bower/Croppie

สามารถดูรายละเอียด การตั้งค่า Package นี้ได้ที่ 

http://foliotek.github.io/Croppie/

สร้างไฟล์ UploadAsset เพื่อลงทะเบียน JS และ CSS

โดยสร้างไว้ที่ frontend/assets/UploadAsset.php

<?php

namespace frontend\assets;

use yii\web\AssetBundle;

class UploadAsset extends AssetBundle
{
    public $sourcePath = '@bower';

    public $css = [
        'Croppie/croppie.css'
    ];
    public $js = [
		'Croppie/croppie.js'
    ];
    public $depends = [
        'yii\web\YiiAsset',
        'yii\bootstrap\BootstrapAsset',
    ];
}

 

แก้ไข frontend/models/Upload.php

เพิ่มใน method rules() ดังนี้

[['img64'], 'string']

เพิ่มใน method attributeLabels() ดังนี้

'img64' => 'รูป'

แก้ไข frontend/views/upload/_form.php

โดยเพิ่ม ส่วนการลงทะเบียน Asset

use frontend\assets\UploadAsset;

UploadAsset::register($this);

และเพิ่มส่วนเลือกไฟล์ดังนี้

<div class="upload-demo">

    <?=Html::fileInput('upload', null, ['id' => 'upload'])?>
    <?=$form->field($model, 'img64')->hiddenInput(['id' => 'img64'])?>
    <div class="upload-msg">
        Upload a file to start cropping
    </div>
    <div id="upload-demo"></div>


</div>

และเพิ่ม CSS และ JS

<?php $this->registerCss("
.upload-demo #upload-demo,
.upload-demo .upload-result,
.upload-demo.ready .upload-msg {
    display: none;
}
.upload-demo.ready #upload-demo {
    display: block;
}
.upload-demo.ready .upload-result {
    display: inline-block;
}

.upload-msg {
    text-align: center;
    padding: 50px;
    font-size: 22px;
    color: #aaa;
    width: 260px;
    margin: 50px auto;
    border: 1px solid #aaa;
}
")?>
<?php $this->registerJs("
        var \$uploadCrop;

		function readFile(input) {
 			if (input.files && input.files[0]) {
	            var reader = new FileReader();

	            reader.onload = function (e) {
	            	\$uploadCrop.croppie('bind', {
	            		url: e.target.result
	            	});
	            	$('.upload-demo').addClass('ready');
	            }

	            reader.readAsDataURL(input.files[0]);
	        }
	        else {
		        swal(\"Sorry - you're browser doesn't support the FileReader API\");
		    }
		}

        \$uploadCrop = $('#upload-demo').croppie({
            enableExif: true,
            viewport: {
                width: 200,
                height: 200,
                type: 'square' //square, circle
            },
            boundary: {
                width: 300,
                height: 300
            }
        });
        $('#upload').on('change', function () { readFile(this); });


        $('#form-submit').on('click', function() {
            //alert( 'Handler for .submit() called.' );
            \$uploadCrop.croppie('result', {
                type: 'canvas',
                size: 'viewport'
            }).then(function (resp) {
                //alert( resp );
                $('#img64').val(resp);
            });
        });
")?>

แก้ไขไฟล์ frontend/views/upload/view.php

เพิ่มการแสดงผลรูปภาพที่ Upload

[
            'attribute' => 'img64',
            'format' => 'raw',
            'value' => $model->img64 ? Html::img($model->img64, ['class' => 'img-responsive']) : null,
        ],

 

ทดสอบการทำงานจะเมื่อเลือกไฟล์จะเห็น Web Application ดังนี้

 

 

เมื่อ upload รูปจะปรากฏ base64 image ในฐานข้อมูลดังนี้

เมื่อเปิดดู view จะเห็นลักษณะนี้


หากบทเรียนรู้มีความผิดพลาดประการใด หรือมีข้อเสนอแนะกรุณาแจ้ง contact@programmerthailand.com
มานพ กองอุ่น

มานพ กองอุ่น
admin

  • 2 เพื่อน
  • 8 ผู้ติดตาม
  • 754 โพสต์
กลับขึ้นบน