Yii 2 元件機制支援單例、延遲載入、依賴注入。將上傳邏輯封裝為 Upload 元件,在 components 註冊,控制器/模型/視圖任意處呼叫 Yii::$app->imgload。
backend/components/Upload.php)namespace backend\components;
use Yii;
use yii\base\Component;
use yii\web\UploadedFile;
class Upload extends Component {
/**
* 上傳圖片並可選生成縮圖
* @param Model $model 已載入資料的模型實例
* @param string $path 相對儲存路徑 (如 'uploads/article/')
* @param string $attribute 模型屬性名 (對應 fileInput name)
* @param bool $thumb 是否生成縮圖
* @return string|false 成功返回相對路徑 (含縮圖用 # 分隔),失敗 false
*/
public function uploadPhoto($model, $path, $attribute, $thumb = false) {
$root = Yii::getAlias('@webroot') . '/' . $path;
$file = UploadedFile::getInstance($model, $attribute);
if (!$file) return false;
// 驗證圖片類型
$allowedTypes = ['image/jpeg', 'image/pjpeg', 'image/png', 'image/x-png', 'image/gif'];
if (!in_array($file->type, $allowedTypes)) {
Yii::$app->session->setFlash('error', '僅支援 JPG/PNG/GIF 格式');
return false;
}
// 驗證大小 (2MB)
if ($file->size > 2 * 1024 * 1024) {
Yii::$app->session->setFlash('error', '圖片不可超過 2MB');
return false;
}
// 建立日期目錄
$dir = date('Ymd') . '/';
if (!is_dir($root . $dir) && !mkdir($root . $dir, 0777, true)) {
Yii::$app->session->setFlash('error', '建立目錄失敗');
return false;
}
// 唯一檔名
$filename = mt_rand(1000, 9999) . time() . '.' . $file->extension;
$filepath = $root . $dir . $filename;
if (!$file->saveAs($filepath)) {
Yii::$app->session->setFlash('error', '儲存失敗');
return false;
}
$relativePath = $path . $dir . $filename;
// 生成縮圖 (需安裝 imagine 或使用 GD)
if ($thumb) {
$thumbPath = $this->generateThumb($filepath, $root . $dir . 'thumb_' . $filename);
if ($thumbPath) $relativePath .= '#' . $path . $dir . 'thumb_' . $filename;
}
return $relativePath;
}
private function generateThumb($src, $dst, $width = 200, $height = 200) {
// 這裡簡略,實際建議使用 Yii2-Imagine 擴展
// return $this->imagineResize($src, $dst, $width, $height);
return true; // 佔位
}
}
backend/config/main.php)return [
'components' => [
// ... 其他元件
'imgload' => [
'class' => 'backend\components\Upload',
],
],
];
namespace backend\controllers;
use backend\models\Article;
use Yii;
class ArticleController extends \yii\web\Controller {
public function actionCreate() {
$model = new Article();
if ($model->load(Yii::$app->request->post())) {
// 上傳封面圖 (cover 為資料表欄位)
$imgPath = Yii::$app->imgload->uploadPhoto($model, 'uploads/article/', 'cover', true);
if ($imgPath) {
$model->cover = $imgPath; // 格式: uploads/article/20240115/abc.jpg#uploads/article/20240115/thumb_abc.jpg
if ($model->save()) {
Yii::$app->session->setFlash('success', '新增成功');
return $this->redirect(['index']);
}
// 存失敗刪除已上傳檔案
@unlink(Yii::getAlias('@webroot') . '/' . explode('#', $imgPath)[0]);
}
Yii::$app->session->setFlash('error', '新增失敗');
}
return $this->render('create', ['model' => $model]);
}
}
use yii\widgets\ActiveForm;
use yii\helpers\Html;
/* @var $model backend\models\Article */
$form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data']]); ?>
= $form->field($model, 'title')->textInput(['maxlength' => true]) ?>
= $form->field($model, 'cover')->fileInput()->label('封面圖') ?>
<div class="mt-2">
<img src="= Yii::$app->urlManager->baseUrl . '/' . $mainImg ?>" alt="預覽" style="max-height:100px;">
</div>
<div class="form-group">
= Html::submitButton('儲存', ['class' => 'btn btn-primary']) ?>
</div>
// 控制器動作回傳 JSON
public function actionUpload() {
$model = new \yii\base\DynamicModel(['file']);
$model->addRule('file', 'file', ['extensions' => 'jpg, png, gif', 'maxSize' => 2*1024*1024]);
$model->file = UploadedFile::getInstance($model, 'file');
if ($model->validate()) {
$path = Yii::$app->imgload->uploadPhoto($model, 'uploads/temp/', 'file');
return ['success' => true, 'url' => Yii::$app->urlManager->baseUrl . '/' . $path];
}
return ['success' => false, 'errors' => $model->errors];
}
| 傳統寫法 | 元件化寫法 |
|---|---|
| 各控制器複製上傳邏輯 | 單一定義,全域 Yii::$app->imgload 呼叫 |
| 修改需逐個檔案改 | 只改元件類,自動生效 |
| 難以單元測試 | 元件可獨立實例化測試 |
| 參數硬編碼 | 元件屬性可配置化 (如最大尺寸、允許類型、儲存路徑前綴) |
main.php)'imgload' => [
'class' => 'backend\components\Upload',
'maxSize' => 5 * 1024 * 1024, // 5MB
'allowedTypes' => ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
'savePathPrefix' => 'uploads/',
'thumbWidth' => 300,
'thumbHeight' => 300,
],
元件類中透過 $this->maxSize 等屬性讀取配置。
掌握 Yii 2 元件機制,將通用業務邏輯 (上傳、匯出、支付、通知等) 封裝為可配置、可測試、全域可用的服務,是構建可維護大型應用的關鍵技能。