透過表單上傳檔案(Forms uploading files)

上傳檔案的方式在教學裡面已經解釋了,不過還是需要再詳細說明一下,因為當我們使用 Active Record 作為表單的物件時,如果有上傳檔案,時常會將上傳檔案與檔案路徑這兩者混淆在一起。

1. 目標

我們希望有個表單可以處理貼文。在表單裡面,我們可以上傳圖片,標題與文字。這邊的圖片不是必須的。並且當圖片已經存在所以沒有上傳時,不應該將圖片路徑設成 null。

2. 準備

我們建立資料庫表格如下:

CREATE TABLE post
(
    id INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
    title VARCHAR(255) NOT NULL,
    text TEXT NOT NULL,
    image VARCHAR(255)
);

接著使用 Gii 工具建立Post模型以及在PostController裡面建立對應的CRUD。

現在,準備開始撰寫留言的程式了。

3. 留言模型修正

Post 模型的image儲存上傳圖片的路徑。注意這邊不應該將檔案路徑與檔案本身搞混。所以我們用另一個變數進行區分。因為檔案本身不需要儲存在資料庫內,我們直接加入一個變數$upload

class Post extends \yii\db\ActiveRecord
{
    public $upload;

然後,修改驗證規則:

/**
 * @inheritdoc
 */
public function rules()
{
    return [
        [['title', 'text'], 'required'],
        [['text'], 'string'],
        [['title'], 'string', 'max' => 255],
        [['upload'], 'file', 'extensions' => 'png, jpg'],
    ];
}

驗證規則裡面,因為與使用者輸入無關,我們將$image相關的驗證移除,並加入針對$upload的驗證。

4. 表單

views/post/_form.php的表單需要改變兩個部份。首先,我們移除image欄位。第二,我們加入上傳檔案upload的欄位:

<?php $form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data']]); ?>

    <?= $form->field($model, 'title')->textInput(['maxlength' => true]) ?>

    <?= $form->field($model, 'text')->textarea(['rows' => 6]) ?>

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

    <div class="form-group">
        <?= Html::submitButton($model->isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
    </div>

<?php ActiveForm::end(); ?>

5. 處理上傳

處理上傳的部份,我們簡單的放進PostController內。兩個 action:actionCreate()actionUpdate()。兩邊都同樣需要處理驗證跟儲存,所以我們將這段分離成handlePostSave()函式:

public function actionCreate()
{
    $model = new Post();
    $this->handlePostSave($model);

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

public function actionUpdate($id)
{
    $model = $this->findModel($id);

    $this->handlePostSave($model);

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

然後我們實做handlePostSave()

protected function handlePostSave(Post $model)
{
    if ($model->load(Yii::$app->request->post())) {
        $model->upload = UploadedFile::getInstance($model, 'upload');
        //驗證
        if ($model->validate()) {
            //更新檔案
            if ($model->upload) {
                $filePath = 'uploads/' . $model->upload->baseName . '.' . $model->upload->extension;
                //上傳檔案
                if ($model->upload->saveAs($filePath)) {
                    $model->image = $filePath;
                }
            }
            //更新失敗
            if ($model->save(false)) {
                return $this->redirect(['view', 'id' => $model->id]);
            }
        }
    }
}

上面的程式碼內,一拿到從POST過來的資料,我們就將檔案實體放進$upload裡面。這邊的重點是,這件事情必須在驗證之前做。

驗證過後,如果檔案已經成功上傳了,我們就儲存檔案,並將檔案路徑寫進$image。 最後,我們呼叫save()的時候,使用false參數,代表「不驗證」。因為我們在前面已經驗證過了,不需要重複。

好了,我們的目標達成了。

6. 網頁表單與 Active Record 的一點心得

為求簡化,Active Record 常常直接用在表單裡面。在許多狀況下,這種作法沒有問題。不過,有時候網頁表單的資料,與存進資料庫資料的結構並不相同。這種狀況下,會建議不要直接使用 Active Record,而是建立另外的表單模型。

資料儲存應該在模型裡面處理,而不是在控制器裡面。

results matching ""

    No results matching ""