logo

文件字段

Nova 提供了几种不同类型的字段: File, Image, Avatar, VaporFile, 和 VaporImage. File 字段不仅在文件上传中最为常见,而且也是 ImageAvatar 字段类的基类。在接下来的说明中,我们会探索这些不同的字段以及他们之间的异同.

概述

为了说明 Nova 文件上传字段的行为,我们假设应用程序的用户可以向其账户上传「个人照片」。因此,我们的 users 数据库表将有一个 profile_photo 列。这一列将包含个人照片在磁盘上的路径,或者,当使用云存储提供商(如 Amazon S3)时,个人照片在其「存储桶」中的路径。

字段定义

接下来,让我们将文件字段附加到 User 资源。在本示例中,我们将创建该字段,并指示它将底层文件存储在 public 磁盘上。该磁盘名称应与应用程序的 filesystems 配置文件中的磁盘名称相对应:

php
use Laravel\Nova\Fields\File;

File::make('Profile Photo')->disk('public'),

禁用文件下载

默认情况下,File 字段允许用户下载相应文件。要禁用此功能,可调用字段定义中的 disableDownload 方法:

php
File::make('Profile Photo')->disableDownload(),

文件是如何存储的

使用此字段上传文件时,Nova 将使用 Laravel 的 文件存储,将文件存储到你选择的磁盘上,并为文件分配一个随机生成的文件名。文件存储完成后,Nova 会在文件字段的基础数据库列中存储文件的相对路径。

为了说明 File 字段的默认行为,让我们来看看以同样方式存储文件的 Laravel 路由:

php
use Illuminate\Http\Request;

Route::post('/photo', function (Request $request) {
    $path = $request->profile_photo->store('/', 'public');

    $request->user()->update([
        'profile_photo' => $path,
    ]);
});

当然,文件存储完成后,你可以使用 Laravel Storage 门面在应用程序中检索它:

php
use Illuminate\Support\Facades\Storage;

Storage::get($user->profile_photo);
Storage::url($user->profile_photo);

自定义

上述文档仅演示了 File 字段的默认行为。要进一步了解如何自定义其行为、请查看 自定义文档

本地磁盘

如果你将 public 磁盘与 local 驱动结合使用,你应该运行 php artisan storage:link Artisan 命令来创建一个从 public/storagestorage/app/public 的符号链接。要了解 Laravel 文件存储的更多信息,请查看 Laravel 文件存储文档

图像

Image 字段的行为与 File 字段完全相同;不过,Image 字段将显示底层文件的缩略图预览,而不是仅在 Nova 面板中显示文件的路径。Image 字段的所有配置和自定义选项都与 File 字段相同:

php
use Laravel\Nova\Fields\Image;

Image::make('Profile Photo')->disk('public'),

要设置 Image 字段显示时的宽度,可以使用 maxWidth 方法:

php
Image::make('Profile Photo')->maxWidth(100),

或者,也可以使用 indexWidthdetailWidth 方法为索引视图和详细视图设置不同的宽度:

php
Image::make('Profile Photo')->indexWidth(60)->detailWidth(150),

最大宽度

你还可以在 AvatarGravatar 字段中使用 maxWidthindexWidthdetailWidth 方法。

头像

Avatar 字段的行为与 File 字段完全相同;不过,Avatar 字段将显示底层文件的缩略图预览,而不是仅在 Nova 面板中显示文件的路径。Avatar 字段的所有配置和自定义选项都与 File 字段相同:

php
use Laravel\Nova\Fields\Avatar;

Avatar::make('Poster')->disk('public'),

除了显示底层文件的缩略图预览外,Avatar 字段还会自动显示在 Nova 搜索结果中。Avatar 字段不限于「用户」资源,你可以将 Avatar 字段附加到 Nova 应用程序中的任何资源:

Avatar Example

存储文件的元数据

除了在存储系统中存储文件路径外,你还可以指示 Nova 存储原始客户端文件名及其大小(以字节为单位)。你可以使用 storeOriginalNamestoreSize 方法来实现这一点。这些方法中的每一个都接受你希望存储文件信息的列的名称:

php
use Illuminate\Http\Request;
use Laravel\Nova\Fields\File;
use Laravel\Nova\Fields\Text;

/**
 * 获取资源显示的字段。
 *
 * @param  \Laravel\Nova\Http\Requests\NovaRequest  $request
 * @return array
 */
public function fields(NovaRequest $request)
{
    return [
        // ...

        File::make('Attachment')
                ->disk('s3')
                ->storeOriginalName('attachment_name')
                ->storeSize('attachment_size'),

        Text::make('Attachment Name')->exceptOnForms(),

        Text::make('Attachment Size')
                ->exceptOnForms()
                ->displayUsing(function ($value) {
                    return number_format($value / 1024, 2).'kb';
                }),
    ];
}

存储原始客户端文件名的一个好处是,可以使用上传文件时使用的原始文件名创建文件下载响应。例如,你可以在应用程序的某个路由中执行以下操作:

php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;

Route::get('/download', function (Request $request) {
    $user = $request->user();

    return Storage::download(
        $user->attachment, $user->attachment_name
    );
});

文件下载

使用 storeOriginalName 方法时,Nova 面板中文件字段的「下载」链接将自动使用文件的原名下载文件。

清理和删除

文件字段默认为可删除,但你可以使用 deletable 方法覆盖此行为:

php
File::make('Photo')->disk('public')->deletable(false),

File 字段以及 ImageAvatar 字段可标记为 prunable。当相关模型从数据库中删除时,prunable 方法将指示 Nova 从存储中删除底层文件:

php
File::make('Profile Photo')->disk('public')->prunable(),

非 Nova 删除

Nova 只会自动清理 Nova 内部关联的模型删除文件。应用程序的其他部分可能需要实现自己的文件删除逻辑。

自定义

自定义文件存储

之前我们了解到,默认情况下,Nova 使用 Illuminate\Http\UploadedFile 类的 store 方法存储文件。不过,你可以根据应用程序的需要完全自定义这一行为。

自定义 名称 / 路径

如果只需自定义磁盘上存储文件的名称或路径,则可使用 File 字段的 pathstoreAs 方法:

php
use Illuminate\Http\Request;

File::make('Attachment')
    ->disk('s3')
    ->path($request->user()->id.'-attachments')
    ->storeAs(function (Request $request) {
        return sha1($request->attachment->getClientOriginalName());
    }),

自定义整个存储过程

但是,如果你想对字段的文件存储逻辑进行完整控制,可以使用 store 方法。store 方法接受一个可调用的对象,该对象接收传入的 HTTP 请求和与请求相关联的模型实例:

php
use Illuminate\Http\Request;

File::make('Attachment')
    ->store(function (Request $request, $model) {
        return [
            'attachment' => $request->attachment->store('/', 's3'),
            'attachment_name' => $request->attachment->getClientOriginalName(),
            'attachment_size' => $request->attachment->getSize(),
        ];
    }),

正如你在上面的示例中看到的,store 回调将返回一个包含 键/值 的数组。这些 键/值 对会在模型实例保存到数据库之前映射到模型实例上,从而允许你在文件保存后更新模型的一个或多个数据库列。

下面是另一个自定义存储过程的例子。在这个例子中,我们使用 store 方法在公共存储中存储原始文件,使用 Laravel 的队列系统创建缩略图,最后在资源的 media 关系中填充值:

php
use Laravel\Nova\Http\Requests\NovaRequest;

File::make('Attachment')
    ->store(function (NovaRequest $request, $model) {
        return function () use ($resource, $request) {
            $media = $resource->media()->updateOrCreate([], [
                'path'=> $request->file('attachment')->store('/path', 'public')
            ]);

            OptimizeMedia::dispatch($media);
        };
    }),

可调用对象

当然,在闭包中执行所有文件存储逻辑会导致资源变得臃肿。因此,Nova 允许你向 store 方法传递一个「可调用」对象:

php
File::make('Attachment')->store(new StoreAttachment),

可调用对象应该是一个简单的 PHP 类,只有一个 __invoke 方法:

php
<?php

namespace App\Nova;

use Laravel\Nova\Http\Requests\NovaRequest;

class StoreAttachment
{
    /**
     * 存储上传的文件。
     *
     * @param  \Laravel\Nova\Http\Requests\NovaRequest  $request
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $attribute
     * @param  string  $requestAttribute
     * @param  string|null  $disk
     * @param  string|null  $storagePath
     * @return array
     */
    public function __invoke(NovaRequest $request, $model, $attribute, $requestAttribute, $disk, $storagePath)
    {
        return [
            'attachment' => $request->attachment->store('/', 's3'),
            'attachment_name' => $request->attachment->getClientOriginalName(),
            'attachment_size' => $request->attachment->getSize(),
        ];
    }
}

自定义文件删除

从 Nova 管理面板删除文件时,Nova 会自动从存储中删除底层文件,并在字段的相关列中插入 NULL

如果想覆盖此行为并提供自己的文件删除实现,可以使用 delete 方法。与上文讨论的 store 方法一样,delete 方法也接受一个可调用函数,该函数接收传入的 HTTP 请求和与请求相关联的模型实例:

php
use Illuminate\Support\Facades\Storage;
use Laravel\Nova\Http\Requests\NovaRequest;

File::make('Attachment')
    ->disk('s3')
    ->delete(function (NovaRequest $request, $model, $disk, $path) {
        if (! $path) {
            return;
        }

        Storage::disk($disk)->delete($path);

        return [
            'attachment' => null,
            'attachment_name' => null,
            'attachment_size' => null,
        ];
    }),

如上例所示,delete 回调将返回一个包含 键/值 的数组。这些 键/值 对会在模型实例保存到数据库之前映射到模型实例上,从而允许你在文件保存后更新模型的一个或多个数据库列。通常,在删除字段时,你会在相关数据库列中插入 NULL

可调用对象

当然,在闭包中执行所有文件删除逻辑会导致资源变得臃肿。因此,Nova 允许你向 delete 方法传递一个「可调用」对象:

php
File::make('Attachment')->delete(new DeleteAttachment);

可调用对象应该是一个简单的 PHP 类,只有一个 __invoke 方法:

php
<?php

namespace App\Nova;

use Illuminate\Support\Facades\Storage;
use Laravel\Nova\Http\Requests\NovaRequest;

class DeleteAttachment
{
    /**
     * Delete the field's underlying file.
     *
     * @param  \Laravel\Nova\Http\Requests\NovaRequest  $request
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string|null  $disk
     * @param  string|null  $path
     * @return array
     */
    public function __invoke(NovaRequest $request, $model, $disk, $path)
    {
        if (! $path) {
            return;
        }

        Storage::disk($disk)->delete($path);

        return [
            'attachment' => null,
            'attachment_name' => null,
            'attachment_size' => null,
        ];
    }
}

自定义预览

默认情况下,Nova 将使用 Storage::url 方法来确定用于在资源详情页和编辑表单上显示图像预览的 URL。不过,你可以使用 preview 方法自定义生成此 URL。

preview 方法接受一个可调用函数,该函数应返回预览 URL。字段的底层列值作为第一个参数传递给可调用程序,字段存储磁盘的名称作为第二个参数传递给可调用程序:

php
use Laravel\Nova\Fields\Image;
use Illuminate\Support\Facades\Storage;

Image::make('Profile Photo')
    ->disk('public')
    ->preview(function ($value, $disk) {
        return $value
                    ? Storage::disk($disk)->url($value)
                    : null;
    }),

预览尺寸

默认情况下,Nova 资源详情页显示预览的宽度为 318 像素(「视网膜显示屏」为 636 像素)。

自定义缩略图

默认情况下,Nova 将使用 Storage::url 方法确定 URL,该 URL 应用于在资源索引页面和搜索结果(使用 Avatar 字段时)中显示缩略图预览。不过,你可以使用 thumbnail 方法自定义生成该 URL。

thumbnail 方法接受一个可调用程序,该程序将返回缩略图 URL。字段的基础列值作为第一个参数传递给可调用程序,字段存储磁盘的名称作为第二个参数传递:

php
use Laravel\Nova\Fields\Image;
use Illuminate\Support\Facades\Storage;

Image::make('Profile Photo')
    ->disk('public')
    ->thumbnail(function ($value, $disk) {
        return $value
                    ? Storage::disk($disk)->url($value)
                    : null;
    }),

缩略图大小

默认情况下,Nova 显示的缩略图宽度为 32 像素(「视网膜显示屏」为 64 像素)。

自定义下载

默认情况下,Nova 将使用 Storage::download 方法来确定文件和文件名,以便下载文件。不过,你可以使用 download 方法自定义生成此 URL。download 方法接受一个可调用的文件,它将返回你自己调用 Storage::download 方法的结果:

php
use Laravel\Nova\Fields\Image;
use Illuminate\Support\Facades\Storage;

Image::make('Profile Photo')
    ->disk('public')
    ->download(function ($request, $model, $disk, $value) {
        return Storage::disk($disk)->download($value, 'avatar');
    }),

使用临时 URL 下载大文件

在诸如 Laravel Vapor 等请求执行时间有限的环境中下载文件时,你可能会发现有必要将下载请求重定向到由 Laravel 的 Storage 门面生成的临时 URL:

php
use Laravel\Nova\Fields\Image;
use Illuminate\Support\Facades\Storage;

Image::make('Photo')
    ->disk('public')
    ->download(function ($request, $model, $disk, $value) {
        return redirect(
            Storage::disk($disk)->temporaryUrl($value, now()->addMinutes(5))
        );
    }),

自定义允许的文件类型

默认情况下,File 字段允许上传任何类型的文件;但你可以使用 acceptedTypes 方法自定义接受的文件类型:

php
File::make('Disk Image')->acceptedTypes('.dmg,.exe')

使用 acceptedTypes 方法时,Nova 会将 accept 属性添加到文件输入元素;因此,可向 acceptedTypes 方法提供以下所有媒体类型:

文件类型验证

由于 acceptedTypes 方法只执行客户端验证,因此还应使用服务器端验证规则来验证文件类型。