[Laravel Admin] 檔案 / 圖片上傳功能之擴充套件 -- 上傳新圖且保留原圖

Elijah_Wang發表於2019-05-16

首先,這是一個略感奇葩的需求,不要問我為什麼,如果有遇到過相同需求,握爪先。
本文的重點是,如何實現擴充套件 laravel-admin 原有 form 元件的部分功能。
在使用 laravel-admin 後臺擴充套件包時,根據客戶需求,希望其原有 form 元件中的 檔案/圖片上傳 功能可以實現 上傳新圖但保留原圖。(<<== 吐槽:你這是要弄啥嘞!!)

追根溯源

透過審查程式碼,可知專案中用到的 $form->image('photo', '照片') 是基於 Encore\Admin\Form 類中的魔術方法 __call() 實現的,其本質是將 Encore\Admin\Form\Field\Image 類例項化,使之渲染並繫結當前 Form 表單中 typefileinput 框,如下:

<input type="file" name="photo" ...>

之所以原有 image 元件會在上傳新圖的同時刪除原圖,原因在於 Encore\Admin\Form\Field\Image 中的 prepare() 方法:

public function prepare($image)
{
    if (request()->has(static::FILE_DELETE_FLAG)) {
        return $this->destroy();
    }
    $this->name = $this->getStoreName($image);
    $this->callInterventionMethods($image->getRealPath());
    return $this->uploadAndDeleteOriginal($image); // <<== 看這裡!!
}

此處,呼叫了 Encore\Admin\Form\Field\File 中的 uploadAndDeleteOriginal() 方法:

protected function uploadAndDeleteOriginal(UploadedFile $file)
{
    $this->renameIfExists($file);
    $path = $this->storage->putFileAs($this->getDirectory(), $file, $this->name);
    $this->destroy(); // <<== 看到了吧,重點在這裡 。。。
    return $path;
}

對,這裡關鍵的一句 $this->destroy();,正是刪除原圖的癥結所在。

解決方案

找到這一步,解決思路應該比較清晰了,有的童鞋講:“把那一行程式碼註釋掉不就ok了麼”。
NO,NO,NO,圖樣圖森破!修改擴充套件包原始碼,乃是下下策!
正確的解題姿勢應該是醬紫滴:
根據官方文件推薦的 Form 元件擴充套件方法(參見:Form 元件管理),我們在後臺部分的程式碼目錄下建立一個目錄用於元件擴充套件,比如:app/Admin/Extensions/Form,對應的名稱空間隨意,比如:App\Admin\Extensions\Form
藍後,建立一個自己喜歡的 Image 元件類,命名隨意,比如:ExtraImage,具體程式碼:

<?php

namespace App\Admin\Extensions\Form;

use Encore\Admin\Form\Field\Image;
use Symfony\Component\HttpFoundation\File\UploadedFile;

class ExtraImage extends Image
{
    /**
     * Upload file and delete original file.
     *
     * @param UploadedFile $file
     *
     * @return mixed
     */
    protected function uploadAndDeleteOriginal(UploadedFile $file)
    {
        $this->renameIfExists($file);
        $path = $this->storage->putFileAs($this->getDirectory(), $file, $this->name);
        // $this->destroy(); // <<== 重點!!
        return $path;
    }
}

再藍後,找到 app/Admin/bootstrap.php,重新註冊 Image 元件:

use App\Admin\Extensions\Form\ExtraImage;
...
Encore\Admin\Form::forget([..., 'image']); // 刪除原有註冊的 Image 元件
Encore\Admin\Form::extend('image', ExtraImage::class); // 重新註冊新的 Image 元件

至此,上傳新圖但保留原圖 功能實現。

$form->image('photo', '照片')
    ->uniqueName()
    ->removable()
    ->move('original/' . date('Ym', now()->timestamp))
    ->help('Photo尺寸:420 * 380')
    ->rules('image');

功能最佳化

然鵝 ~~ 這樣做,有一個問題:我們更改了原有 Image 元件的功能性狀。怎樣可以保留框架原有的 Image 元件功能性狀呢?求相容,求相容!!
其實,相容性的實現是很簡單滴~~
App\Admin\Extensions\Form\ExtraImage 中程式碼稍作修改,如下:

<?php

namespace App\Admin\Extensions\Form;

use Encore\Admin\Form\Field\Image;
use Symfony\Component\HttpFoundation\File\UploadedFile;

class ExtraImage extends Image
{
    protected $isDeletable = false; // <<== 立了一個Flag,標識是否支援上傳新圖同時可刪除原圖

    /**
     * Upload file and delete original file.
     *
     * @param UploadedFile $file
     *
     * @return mixed
     */
    protected function uploadAndDeleteOriginal(UploadedFile $file)
    {
        $this->renameIfExists($file);
        $path = $this->storage->putFileAs($this->getDirectory(), $file, $this->name);
        if (!$this->isDeletable){ // <<== 論 Flag 的正確用法 。。。
            $this->destroy();
        }
        return $path;
    }

    public function deletable($bool = false)
    {
        $this->isDeletable = $bool;
        return $this;
    }
}

改造後的 Image 元件的用法如下:

// 上傳新圖不保留原圖
$form->image('photo', '照片')
    ->uniqueName()
    ->removable()
    ->move('original/' . date('Ym', now()->timestamp))
    ->help('Photo尺寸:420 * 380')
    ->rules('image');
// 上傳新圖但保留原圖
$form->image('photo', '照片')
    ->deletable(true) // <<== 重點!!
    ->uniqueName()
    ->removable()
    ->move('original/' . date('Ym', now()->timestamp))
    ->help('Photo尺寸:420 * 380')
    ->rules('image');

至此,新的 Image 元件實現 上傳新圖但保留原圖,且相容原有 Image 元件,功能改造完美收官。。。

泉涸,魚相與處於陸,
相呴以溼,相濡以沫,不如相忘於江湖。
與其譽堯而非桀也,不如兩忘而化其道。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
夏蟲不語冰

相關文章