[Laravel Admin] 文件 / 图片上传功能之扩展 -- 上传新图且保留原图

首先,这是一个略感奇葩的需求,不要问我为什么,如果有遇到过相同需求,握爪先。
本文的重点是,如何实现扩展 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 协议》,转载必须注明作者和本文链接
夏蟲不語冰
Elijah_Wang
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 3

类似的思路,我用oss上传图片功能,想拼接域名,保存完整的url,怎么弄呢?

4年前 评论
Jea 4年前

大佬好强!!! Encore\Admin\Form::forget([..., 'image']);这里面。。。应该写什么

4年前 评论
Elijah_Wang (楼主) 4年前
Alieven (作者) 4年前

请教一下大佬怎样限制图片上传size

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!