Nova 提供了几种不同类型的字段: File, Image, Avatar, VaporFile, 和 VaporImage. File 字段不仅在文件上传中最为常见,而且也是 Image 和 Avatar 字段类的基类。在接下来的说明中,我们会探索这些不同的字段以及他们之间的异同.
为了说明 Nova 文件上传字段的行为,我们假设应用程序的用户可以向其账户上传「个人照片」。因此,我们的 users 数据库表将有一个 profile_photo 列。这一列将包含个人照片在磁盘上的路径,或者,当使用云存储提供商(如 Amazon S3)时,个人照片在其「存储桶」中的路径。
接下来,让我们将文件字段附加到 User 资源。在本示例中,我们将创建该字段,并指示它将底层文件存储在 public 磁盘上。该磁盘名称应与应用程序的 filesystems 配置文件中的磁盘名称相对应:
use Laravel\Nova\Fields\File;
File::make('Profile Photo')->disk('public'),默认情况下,File 字段允许用户下载相应文件。要禁用此功能,可调用字段定义中的 disableDownload 方法:
File::make('Profile Photo')->disableDownload(),使用此字段上传文件时,Nova 将使用 Laravel 的 文件存储,将文件存储到你选择的磁盘上,并为文件分配一个随机生成的文件名。文件存储完成后,Nova 会在文件字段的基础数据库列中存储文件的相对路径。
为了说明 File 字段的默认行为,让我们来看看以同样方式存储文件的 Laravel 路由:
use Illuminate\Http\Request;
Route::post('/photo', function (Request $request) {
$path = $request->profile_photo->store('/', 'public');
$request->user()->update([
'profile_photo' => $path,
]);
});当然,文件存储完成后,你可以使用 Laravel Storage 门面在应用程序中检索它:
use Illuminate\Support\Facades\Storage;
Storage::get($user->profile_photo);
Storage::url($user->profile_photo);自定义
上述文档仅演示了 File 字段的默认行为。要进一步了解如何自定义其行为、请查看 自定义文档。
如果你将 public 磁盘与 local 驱动结合使用,你应该运行 php artisan storage:link Artisan 命令来创建一个从 public/storage 到 storage/app/public 的符号链接。要了解 Laravel 文件存储的更多信息,请查看 Laravel 文件存储文档。
Image 字段的行为与 File 字段完全相同;不过,Image 字段将显示底层文件的缩略图预览,而不是仅在 Nova 面板中显示文件的路径。Image 字段的所有配置和自定义选项都与 File 字段相同:
use Laravel\Nova\Fields\Image;
Image::make('Profile Photo')->disk('public'),要设置 Image 字段显示时的宽度,可以使用 maxWidth 方法:
Image::make('Profile Photo')->maxWidth(100),或者,也可以使用 indexWidth 和 detailWidth 方法为索引视图和详细视图设置不同的宽度:
Image::make('Profile Photo')->indexWidth(60)->detailWidth(150),Avatar 字段的行为与 File 字段完全相同;不过,Avatar 字段将显示底层文件的缩略图预览,而不是仅在 Nova 面板中显示文件的路径。Avatar 字段的所有配置和自定义选项都与 File 字段相同:
use Laravel\Nova\Fields\Avatar;
Avatar::make('Poster')->disk('public'),除了显示底层文件的缩略图预览外,Avatar 字段还会自动显示在 Nova 搜索结果中。Avatar 字段不限于「用户」资源,你可以将 Avatar 字段附加到 Nova 应用程序中的任何资源:
![]()
除了在存储系统中存储文件路径外,你还可以指示 Nova 存储原始客户端文件名及其大小(以字节为单位)。你可以使用 storeOriginalName 和 storeSize 方法来实现这一点。这些方法中的每一个都接受你希望存储文件信息的列的名称:
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';
}),
];
}存储原始客户端文件名的一个好处是,可以使用上传文件时使用的原始文件名创建文件下载响应。例如,你可以在应用程序的某个路由中执行以下操作:
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 方法覆盖此行为:
File::make('Photo')->disk('public')->deletable(false),File 字段以及 Image 和 Avatar 字段可标记为 prunable。当相关模型从数据库中删除时,prunable 方法将指示 Nova 从存储中删除底层文件:
File::make('Profile Photo')->disk('public')->prunable(),非 Nova 删除
Nova 只会自动清理 Nova 内部关联的模型删除文件。应用程序的其他部分可能需要实现自己的文件删除逻辑。
之前我们了解到,默认情况下,Nova 使用 Illuminate\Http\UploadedFile 类的 store 方法存储文件。不过,你可以根据应用程序的需要完全自定义这一行为。
如果只需自定义磁盘上存储文件的名称或路径,则可使用 File 字段的 path 和 storeAs 方法:
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 请求和与请求相关联的模型实例:
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 关系中填充值:
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 方法传递一个「可调用」对象:
File::make('Attachment')->store(new StoreAttachment),可调用对象应该是一个简单的 PHP 类,只有一个 __invoke 方法:
<?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 请求和与请求相关联的模型实例:
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 方法传递一个「可调用」对象:
File::make('Attachment')->delete(new DeleteAttachment);可调用对象应该是一个简单的 PHP 类,只有一个 __invoke 方法:
<?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。字段的底层列值作为第一个参数传递给可调用程序,字段存储磁盘的名称作为第二个参数传递给可调用程序:
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。字段的基础列值作为第一个参数传递给可调用程序,字段存储磁盘的名称作为第二个参数传递:
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 方法的结果:
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');
}),在诸如 Laravel Vapor 等请求执行时间有限的环境中下载文件时,你可能会发现有必要将下载请求重定向到由 Laravel 的 Storage 门面生成的临时 URL:
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 方法自定义接受的文件类型:
File::make('Disk Image')->acceptedTypes('.dmg,.exe')使用 acceptedTypes 方法时,Nova 会将 accept 属性添加到文件输入元素;因此,可向 acceptedTypes 方法提供以下所有媒体类型:
.dmg.dmg,.exe,.debimage/*audio/*video/*文件类型验证
由于 acceptedTypes 方法只执行客户端验证,因此还应使用服务器端验证规则来验证文件类型。