檔案存放空間

簡介

多虧了 Flysystem,Laravel 提供了強大的檔案系統抽象介面。Flysystem 是 Frank de Jonge 提供的一個 PHP 套件。Laravel 整合 Flysystem 來提供多個簡單的 Driver,可處理本機檔案系統、SFTP、Amazon S3 等。甚至,在本機開發環境與正式伺服器間交換使用各個不同的儲存空間非常地簡單,且每個儲存系統都有相同的 API。

設定

Laravel 的檔案系統設定檔位在 config/filesystems.php。在這個檔案中,我們可以設定所有的檔案系統「Disk(磁碟)」。各個 Disk 都代表了一個特定的儲存空間 Driver 與儲存位置。該設定檔內已包含了各個支援 Driver 的範例設定,讓你能修改這些設定來反映出儲存空間偏好與認證方式。

local Driver 負責處理保存在執行該 Laravel 專案之本機伺服器上的檔案。而 s3 Driver 則用來將檔案寫入 Amazon 的 S3 雲端儲存服務。

lightbulb

可以隨意設定多個 Disk,甚至也可以設定多個使用相同 Driver 的 Disk。

「Local」Driver

使用 local Driver 時,所有的檔案操作都相對於 filesystems 設定檔中定義的 root 根目錄。預設情況下,這個值設為 storage/app 目錄。因此,下列方法會寫入 storage/app/example.txt

1use Illuminate\Support\Facades\Storage;
2 
3Storage::disk('local')->put('example.txt', 'Contents');
1use Illuminate\Support\Facades\Storage;
2 
3Storage::disk('local')->put('example.txt', 'Contents');

「Public」Disk

專案中的 filesystems 設定檔內有個 public Disk,public Disk 是用來處理要提供公開存取的檔案。預設情況下,public Disk 使用 local Driver,並將檔案保存在 storage/app/public

為了這些檔案可在網頁上存取,請建立一個 public/storagestorage/app/public符號連結(Symbolic Link)。使用這個資料夾慣例來把所有可公開存取的檔案放到同一個資料夾內,就你在使用如 Envoyer 這類不停機部署系統時也能輕鬆的在多個部署間共用這個資料夾。

若要建立符號連結,可使用 storage:link Artisan 指令:

1php artisan storage:link
1php artisan storage:link

保存檔案並建立好符號連結後,就可以使用 asset 輔助函式來建立該檔案的 URL:

1echo asset('storage/file.txt');
1echo asset('storage/file.txt');

也可以在 filesystems 設定檔中設定其他符號連結。在執行 storage:link 指令時,會建立設定中的各個符號連結:

1'links' => [
2 public_path('storage') => storage_path('app/public'),
3 public_path('images') => storage_path('app/images'),
4],
1'links' => [
2 public_path('storage') => storage_path('app/public'),
3 public_path('images') => storage_path('app/images'),
4],

Driver 的前置需求

S3 Driver 設定

在使用 S3 Driver 前,需要先使用 Composer 套件管理員安裝 Flysystem S3 套件:

1composer require league/flysystem-aws-s3-v3 "^3.0"
1composer require league/flysystem-aws-s3-v3 "^3.0"

S3 Driver 的設定資訊保存在 config/filesystems.php 設定檔內。這個檔案中包含了用於 S3 Driver 的範例設定。可以自行將陣列改為你的 S3 設定與認證資訊。為了方便起見,這些環境變數的名稱都符合 AWS CLI 使用的命名慣例。

FTP Driver 設定

在使用 FTP Driver 前,需要先使用 Composer 套件管理員安裝 Flysystem FTP 套件:

1composer require league/flysystem-ftp "^3.0"
1composer require league/flysystem-ftp "^3.0"

Laravel 的 Flysystem 整合可以完美配合 FTP。不過,Laravel 的預設 filesystems.php 設定檔中並未包含 FTP 的範例設定。若有需要設定 FTP 檔案系統,可使用下列範例設定:

1'ftp' => [
2 'driver' => 'ftp',
3 'host' => env('FTP_HOST'),
4 'username' => env('FTP_USERNAME'),
5 'password' => env('FTP_PASSWORD'),
6 
7 // 可選的 FTP 設定...
8 // 'port' => env('FTP_PORT', 21),
9 // 'root' => env('FTP_ROOT'),
10 // 'passive' => true,
11 // 'ssl' => true,
12 // 'timeout' => 30,
13],
1'ftp' => [
2 'driver' => 'ftp',
3 'host' => env('FTP_HOST'),
4 'username' => env('FTP_USERNAME'),
5 'password' => env('FTP_PASSWORD'),
6 
7 // 可選的 FTP 設定...
8 // 'port' => env('FTP_PORT', 21),
9 // 'root' => env('FTP_ROOT'),
10 // 'passive' => true,
11 // 'ssl' => true,
12 // 'timeout' => 30,
13],

SFTP Driver 設定

在使用 SFTP Driver 前,需要先使用 Composer 套件管理員安裝 Flysystem SFTP 套件:

1composer require league/flysystem-sftp-v3 "^3.0"
1composer require league/flysystem-sftp-v3 "^3.0"

Laravel 的 Flysystem 整合可以完美配合 SFTP。不過,Laravel 的預設 filesystems.php 設定檔中並未包含 SFTP 的範例設定。若有需要設定 SFTP 檔案系統,可使用下列範例設定:

1'sftp' => [
2 'driver' => 'sftp',
3 'host' => env('SFTP_HOST'),
4 
5 // 設定 Basic 身份認證...
6 'username' => env('SFTP_USERNAME'),
7 'password' => env('SFTP_PASSWORD'),
8 
9 // 設定有加密密碼之基於 SSH 金鑰的身份認證...
10 'privateKey' => env('SFTP_PRIVATE_KEY'),
11 'passphrase' => env('SFTP_PASSPHRASE'),
12 
13 // 可選的 SFTP 設定...
14 // 'hostFingerprint' => env('SFTP_HOST_FINGERPRINT'),
15 // 'maxTries' => 4,
16 // 'passphrase' => env('SFTP_PASSPHRASE'),
17 // 'port' => env('SFTP_PORT', 22),
18 // 'root' => env('SFTP_ROOT', ''),
19 // 'timeout' => 30,
20 // 'useAgent' => true,
21],
1'sftp' => [
2 'driver' => 'sftp',
3 'host' => env('SFTP_HOST'),
4 
5 // 設定 Basic 身份認證...
6 'username' => env('SFTP_USERNAME'),
7 'password' => env('SFTP_PASSWORD'),
8 
9 // 設定有加密密碼之基於 SSH 金鑰的身份認證...
10 'privateKey' => env('SFTP_PRIVATE_KEY'),
11 'passphrase' => env('SFTP_PASSPHRASE'),
12 
13 // 可選的 SFTP 設定...
14 // 'hostFingerprint' => env('SFTP_HOST_FINGERPRINT'),
15 // 'maxTries' => 4,
16 // 'passphrase' => env('SFTP_PASSPHRASE'),
17 // 'port' => env('SFTP_PORT', 22),
18 // 'root' => env('SFTP_ROOT', ''),
19 // 'timeout' => 30,
20 // 'useAgent' => true,
21],

限定範圍與唯讀的檔案系統

使用限定範圍的 Disk,我們可以定義一個檔案系統,在該檔案系統中,所有的路徑都會自動被加上給定的路徑前置詞。在建立限定範圍的檔案系統 Disk 前,我們需要先使用 Composer 套件管理員安裝一個額外的 Flysystem 套件:

1composer require league/flysystem-path-prefixing "^3.0"
1composer require league/flysystem-path-prefixing "^3.0"

只要使用 scoped Driver,我們就可以使用任何現有的檔案系統 Disk 來定義限定路徑範圍的 Disk。舉例來說,我們可以建立一個 Disk,該 Disk 使用現有的 s3 Disk,並將路徑限定在特定的路徑前置詞內。接著,使用這個限定範圍 Disk 的所有檔案操作都會在這個指定的前置詞下:

1's3-videos' => [
2 'driver' => 'scoped',
3 'disk' => 's3',
4 'prefix' => 'path/to/videos',
5],
1's3-videos' => [
2 'driver' => 'scoped',
3 'disk' => 's3',
4 'prefix' => 'path/to/videos',
5],

使用「唯讀」Disk,我們就能建立不允許任何寫入操作的檔案系統 Disk。在使用 read-only 組態設定選項前,我們還需要使用 Composer 套件管理員安裝一個額外的 Flysystem 套件:

1composer require league/flysystem-read-only "^3.0"
1composer require league/flysystem-read-only "^3.0"

接著,我們可以在任何一個或多個 Disk 設定內加上 read-only 設定選項:

1's3-videos' => [
2 'driver' => 's3',
3 // ...
4 'read-only' => true,
5],
1's3-videos' => [
2 'driver' => 's3',
3 // ...
4 'read-only' => true,
5],

相容於 Amazon S3 的檔案系統

預設情況下,專案的 filesystems 設定檔中已包含了一個 s3 Disk 設定。除了以該 Disk 來使用 Amazon S3 外,還可以通過這個 Disk 來使用相容於 S3 的檔案存放服務,如 MinIODigitalOcean Spaces

一般來說,為 Disk 設定要使用服務的認證資訊後,就只需要更改 endpoint 設定選項即可。這個選項值通常是以 AWS_ENDPOINT 環境變數定義的:

1'endpoint' => env('AWS_ENDPOINT', 'https://minio:9000'),
1'endpoint' => env('AWS_ENDPOINT', 'https://minio:9000'),

MinIO

為了讓 Laravel 的 Flysystem 整合在使用 MinIO 時整合正確的 URL,請定義 AWS_URL 環境變數,並設定適用於專案本機 URL 的值,且該值應在 URL 路徑內包含 Bucket 名稱:

1AWS_URL=http://localhost:9000/local
1AWS_URL=http://localhost:9000/local
exclamation

使用 MinIO 時,不支援通過 temporaryUrl 方法來產生臨時儲存空間 URL。

取得 Disk 實體

可通過 Storage Facade 來與設定好的任一 Disk 互動。舉例來說,可以使用 Facade 上的 put 方法來將使用者圖片保存在預設 Disk 內。若在呼叫方法時沒有在 Storage Facade 上先呼叫 disk 方法,則這個方法呼叫會自動被傳到預設的 Disk 上:

1use Illuminate\Support\Facades\Storage;
2 
3Storage::put('avatars/1', $content);
1use Illuminate\Support\Facades\Storage;
2 
3Storage::put('avatars/1', $content);

若你的專案使用多個 Disk,可使用 Storage Facade 上的 disk 方法來在特定 Disk 上處理檔案:

1Storage::disk('s3')->put('avatars/1', $content);
1Storage::disk('s3')->put('avatars/1', $content);

隨需提供的 Disk

有時候,我們會想在不實際將設定寫入 filesystems 設定檔的情況下,在執行階段直接通過給定的一組設定來建立 Disk。若要在執行階段建立 Disk,請將一組設定陣列傳給 Storage Facade 的 build 方法:

1use Illuminate\Support\Facades\Storage;
2 
3$disk = Storage::build([
4 'driver' => 'local',
5 'root' => '/path/to/root',
6]);
7 
8$disk->put('image.jpg', $content);
1use Illuminate\Support\Facades\Storage;
2 
3$disk = Storage::build([
4 'driver' => 'local',
5 'root' => '/path/to/root',
6]);
7 
8$disk->put('image.jpg', $content);

取得檔案

get 方法可用來取得檔案內容。該方法會回傳檔案的原始字串內容。請記得,所有檔案路徑都是相對於該 Disk 所指定的「root」根目錄:

1$contents = Storage::get('file.jpg');
1$contents = Storage::get('file.jpg');

exists 方法可用來判斷某個檔案是否存在於 Disk 上:

1if (Storage::disk('s3')->exists('file.jpg')) {
2 // ...
3}
1if (Storage::disk('s3')->exists('file.jpg')) {
2 // ...
3}

可使用 missing 方法來判斷 Disk 上是否不存在這個檔案:

1if (Storage::disk('s3')->missing('file.jpg')) {
2 // ...
3}
1if (Storage::disk('s3')->missing('file.jpg')) {
2 // ...
3}

下載檔案

可使用 download 方法來產生一個強制使用者在給定路徑上下載檔案的 Response。download 方法接受檔案名稱作為其第二個引數,該引數用來判斷使用者看到的檔案名稱。最後,我們可以傳入一組包含 HTTP 標頭的陣列作為該方法的第三個引數:

1return Storage::download('file.jpg');
2 
3return Storage::download('file.jpg', $name, $headers);
1return Storage::download('file.jpg');
2 
3return Storage::download('file.jpg', $name, $headers);

檔案 URL

可以使用 url 來取得給定檔案的 URL。若使用 local Driver,通常這個網址就只是在給定路徑前方加上 /storage 然後回傳該檔案的相對 URL 而已。若使用 s3 Driver,則會回傳完整的遠端 URL:

1use Illuminate\Support\Facades\Storage;
2 
3$url = Storage::url('file.jpg');
1use Illuminate\Support\Facades\Storage;
2 
3$url = Storage::url('file.jpg');

使用 local Driver 時,所有要供公開存取的檔案都應放在 storage/app/public 目錄內。此外,也應建立一個符號連結來將 public/storage 指向 storage/app/public 目錄。

exclamation

使用 local Driver 時,url 的回傳值未經過 URL 編碼。因此,我們建議你只使用能產生有效 URL 的檔名來保存檔案。

時效性 URL

使用 temporaryUrl 方法,就可以為儲存在 s3 Driver 上的檔案建立時效性 URL。這個方法接受一個路徑、以及一個用來指定 URL 何時過期的 DateTime 實體:

1use Illuminate\Support\Facades\Storage;
2 
3$url = Storage::temporaryUrl(
4 'file.jpg', now()->addMinutes(5)
5);
1use Illuminate\Support\Facades\Storage;
2 
3$url = Storage::temporaryUrl(
4 'file.jpg', now()->addMinutes(5)
5);

若想指定額外的 S3 Request 參數,只需要將 Request 參數陣列作為第三個引數傳給 temporaryUrl 方法即可:

1$url = Storage::temporaryUrl(
2 'file.jpg',
3 now()->addMinutes(5),
4 [
5 'ResponseContentType' => 'application/octet-stream',
6 'ResponseContentDisposition' => 'attachment; filename=file2.jpg',
7 ]
8);
1$url = Storage::temporaryUrl(
2 'file.jpg',
3 now()->addMinutes(5),
4 [
5 'ResponseContentType' => 'application/octet-stream',
6 'ResponseContentDisposition' => 'attachment; filename=file2.jpg',
7 ]
8);

若有需要自訂某個存放 Disk 要如何產生臨時 URL,可以使用 buildTemporaryUrlsUsing 方法。舉例來說,若有檔案儲存在不支援時效性 URL 的 Driver 上,而在某個 Controller 上我們又想讓使用者能下載這些檔案,就很適合使用這個方法。一般來說,應在某個 Service Provider 的 boot 方法內呼叫這個方法:

1<?php
2 
3namespace App\Providers;
4 
5use DateTime;
6use Illuminate\Support\Facades\Storage;
7use Illuminate\Support\Facades\URL;
8use Illuminate\Support\ServiceProvider;
9 
10class AppServiceProvider extends ServiceProvider
11{
12 /**
13 * Bootstrap any application services.
14 */
15 public function boot(): void
16 {
17 Storage::disk('local')->buildTemporaryUrlsUsing(function (string $path, DateTime $expiration, array $options) {
18 return URL::temporarySignedRoute(
19 'files.download',
20 $expiration,
21 array_merge($options, ['path' => $path])
22 );
23 });
24 }
25}
1<?php
2 
3namespace App\Providers;
4 
5use DateTime;
6use Illuminate\Support\Facades\Storage;
7use Illuminate\Support\Facades\URL;
8use Illuminate\Support\ServiceProvider;
9 
10class AppServiceProvider extends ServiceProvider
11{
12 /**
13 * Bootstrap any application services.
14 */
15 public function boot(): void
16 {
17 Storage::disk('local')->buildTemporaryUrlsUsing(function (string $path, DateTime $expiration, array $options) {
18 return URL::temporarySignedRoute(
19 'files.download',
20 $expiration,
21 array_merge($options, ['path' => $path])
22 );
23 });
24 }
25}

自訂 URL 主機

若想為 Storage Facade 產生的 URL 預先定義主機,可在 Disk 設定陣列內加上一個 url 選項:

1'public' => [
2 'driver' => 'local',
3 'root' => storage_path('app/public'),
4 'url' => env('APP_URL').'/storage',
5 'visibility' => 'public',
6],
1'public' => [
2 'driver' => 'local',
3 'root' => storage_path('app/public'),
4 'url' => env('APP_URL').'/storage',
5 'visibility' => 'public',
6],

檔案詮釋資料

除了讀寫檔案外,Laravel 還提供了一些有關檔案本身的資訊。舉例來說,size 方法可用來取得單位為位元組(Bytes)的檔案大小:

1use Illuminate\Support\Facades\Storage;
2 
3$size = Storage::size('file.jpg');
1use Illuminate\Support\Facades\Storage;
2 
3$size = Storage::size('file.jpg');

lastModified 方法回傳以 UNIX 時戳(Timestamp)表示的檔案最後修改時間:

1$time = Storage::lastModified('file.jpg');
1$time = Storage::lastModified('file.jpg');

使用 mimeType 方法,就可取得給定檔案的 MIME 型別:

1$mime = Storage::mimeType('file.jpg')
1$mime = Storage::mimeType('file.jpg')

檔案路徑

可以使用 path 方法來取得給定檔案的路徑。若使用 local Driver,該方法會回傳檔案的絕對路徑。若使用 s3 Driver,該方法會回傳在 S3 Bucket 中的相對路徑:

1use Illuminate\Support\Facades\Storage;
2 
3$path = Storage::path('file.jpg');
1use Illuminate\Support\Facades\Storage;
2 
3$path = Storage::path('file.jpg');

保存檔案

可使用 put 方法來將檔案內容保存到 Disk 上。也可以傳入一個 PHP resourceput 方法,Laravel 會使用 Flysystem 的底層串流支援來保存檔案。請記得,所有的檔案路徑都是相對於 Disk 設定中「root」根目錄的路徑:

1use Illuminate\Support\Facades\Storage;
2 
3Storage::put('file.jpg', $contents);
4 
5Storage::put('file.jpg', $resource);
1use Illuminate\Support\Facades\Storage;
2 
3Storage::put('file.jpg', $contents);
4 
5Storage::put('file.jpg', $resource);

寫入失敗

put 方法 (或其他「寫入」動作) 無法將檔案寫入到磁碟上,則該方法會回傳 false

1if (! Storage::put('file.jpg', $contents)) {
2 // 無法將該檔案寫入磁碟...
3}
1if (! Storage::put('file.jpg', $contents)) {
2 // 無法將該檔案寫入磁碟...
3}

若有需要的話,也可以在檔案系統 Disk 的設定陣列中定義 throw 選項。當該選項定義為 true 時,如 put 等的「寫入」方法會在寫入動作失敗時擲回一個 League\Flysystem\UnableToWriteFile 實體:

1'public' => [
2 'driver' => 'local',
3 // ...
4 'throw' => true,
5],
1'public' => [
2 'driver' => 'local',
3 // ...
4 'throw' => true,
5],

將內容加到檔案的最前面或最後面

使用 prependappend 方法,就可以讓我們將內容寫入到檔案的最前端或最後端:

1Storage::prepend('file.log', 'Prepended Text');
2 
3Storage::append('file.log', 'Appended Text');
1Storage::prepend('file.log', 'Prepended Text');
2 
3Storage::append('file.log', 'Appended Text');

複製與移動檔案

可使用 copy 方法來將現有的檔案複製到 Disk 中的新路徑。而 move 方法則可用來重新命名現有檔案或將現有檔案移至新路徑:

1Storage::copy('old/file.jpg', 'new/file.jpg');
2 
3Storage::move('old/file.jpg', 'new/file.jpg');
1Storage::copy('old/file.jpg', 'new/file.jpg');
2 
3Storage::move('old/file.jpg', 'new/file.jpg');

自動串流

使用串流將檔案到寫入存放空間可顯著降低記憶體使用。若想讓 Laravel 自動管理存放路徑中給定檔案的串流,可使用 putFileputFileAs 方法。這兩個方法接受 Illuminate\Http\FileIlluminate\Http\UploadedFile 實體,會自動將該檔案串流到指定的路徑上:

1use Illuminate\Http\File;
2use Illuminate\Support\Facades\Storage;
3 
4// 自訂為檔案名稱產生一個不重複的 ID...
5$path = Storage::putFile('photos', new File('/path/to/photo'));
6 
7// 手動指定檔案名稱...
8$path = Storage::putFileAs('photos', new File('/path/to/photo'), 'photo.jpg');
1use Illuminate\Http\File;
2use Illuminate\Support\Facades\Storage;
3 
4// 自訂為檔案名稱產生一個不重複的 ID...
5$path = Storage::putFile('photos', new File('/path/to/photo'));
6 
7// 手動指定檔案名稱...
8$path = Storage::putFileAs('photos', new File('/path/to/photo'), 'photo.jpg');

有關 putFile 方法,還有幾點重要事項要注意。請注意,我們只有指定資料夾名稱,而未指定檔案名稱。預設情況下,putFile 會自動產生一個不重複 ID 來作為檔案名稱。檔案的副檔名會依照該檔案的 MIME 來判斷。putFile 方法會回傳該檔案包含檔名的路徑,好讓我們能保存該路徑到資料庫中。

putFileputFileAs 方法也接受一個用來指定保存檔案「可見度(Visibility)」的引數。若你使用 Amazon S3 等雲端 Disk 來儲存檔案且想產生能公開存取的 URL,這個功能就特別實用:

1Storage::putFile('photos', new File('/path/to/photo'), 'public');
1Storage::putFile('photos', new File('/path/to/photo'), 'public');

檔案上傳

在 Web App 中,儲存檔案最常見的例子就是保存使用者上傳的檔案了 (如:照片、文件)。在 Laravel 中,要保存上傳的檔案非常簡單,只要在上傳的檔案實體上使用 store 方法即可。呼叫 store 方法並傳入要上傳檔案要保存的位置即可:

1<?php
2 
3namespace App\Http\Controllers;
4 
5use App\Http\Controllers\Controller;
6use Illuminate\Http\Request;
7 
8class UserAvatarController extends Controller
9{
10 /**
11 * Update the avatar for the user.
12 */
13 public function update(Request $request): string
14 {
15 $path = $request->file('avatar')->store('avatars');
16 
17 return $path;
18 }
19}
1<?php
2 
3namespace App\Http\Controllers;
4 
5use App\Http\Controllers\Controller;
6use Illuminate\Http\Request;
7 
8class UserAvatarController extends Controller
9{
10 /**
11 * Update the avatar for the user.
12 */
13 public function update(Request $request): string
14 {
15 $path = $request->file('avatar')->store('avatars');
16 
17 return $path;
18 }
19}

在這個範例中還有幾點重要事項要注意。請注意,我們只有指定資料夾名稱,而未指定檔案名稱。預設情況下,store 會自動產生一個不重複 ID 來作為檔案名稱。檔案的副檔名會依照該檔案的 MIME 來判斷。store 方法會回傳該檔案包含檔名的路徑,好讓我們能保存該路徑到資料庫中。

也可以呼叫 Storage Facade 上的 putFile 方法進行與上方範例相同的檔案存放操作:

1$path = Storage::putFile('avatars', $request->file('avatar'));
1$path = Storage::putFile('avatars', $request->file('avatar'));

指定檔案名稱

若不想使用自動指派給保存檔案的檔名,可使用 storeAs 方法。該方法的引數是路徑、檔名、以及 (可選的) Disk:

1$path = $request->file('avatar')->storeAs(
2 'avatars', $request->user()->id
3);
1$path = $request->file('avatar')->storeAs(
2 'avatars', $request->user()->id
3);

也可以呼叫 Storage Facade 上的 putFileAs 方法進行與上方範例相同的檔案存放操作:

1$path = Storage::putFileAs(
2 'avatars', $request->file('avatar'), $request->user()->id
3);
1$path = Storage::putFileAs(
2 'avatars', $request->file('avatar'), $request->user()->id
3);
exclamation

路徑中若有不可列印 (Unprintable) 或無效的 Unicode 字元,則會被自動移除。因此,在將檔案路徑傳給 Laravel 的檔案存放方法前,我們可能會想先消毒 (Sanitize) 檔案路徑。可使用 League\Flysystem\WhitespacePathNormalizer::normalizePath 來正常化 (Normalize) 檔案路徑。

指定 Disk

預設情況下,上傳檔案的 store 方法會使用預設的 Disk。若想指定另一個 Disk,請將 Disk 名稱作為第三個引數傳給 store 方法:

1$path = $request->file('avatar')->store(
2 'avatars/'.$request->user()->id, 's3'
3);
1$path = $request->file('avatar')->store(
2 'avatars/'.$request->user()->id, 's3'
3);

若使用 storeAs 方法,則可將 Disk 名稱作為第三引數傳給該方法:

1$path = $request->file('avatar')->storeAs(
2 'avatars',
3 $request->user()->id,
4 's3'
5);
1$path = $request->file('avatar')->storeAs(
2 'avatars',
3 $request->user()->id,
4 's3'
5);

其他上傳檔案的資訊

若想取得上傳檔案的原始名稱與副檔名,可使用 getClientOriginalNamegetClientOriginalExtension 方法:

1$file = $request->file('avatar');
2 
3$name = $file->getClientOriginalName();
4$extension = $file->getClientOriginalExtension();
1$file = $request->file('avatar');
2 
3$name = $file->getClientOriginalName();
4$extension = $file->getClientOriginalExtension();

不過,請注意,應將 getClientOriginalNamegetClientOriginalExtension 方法視為不安全的,因為惡意使用者可以偽造檔案名稱與副檔名。因此,建議一般還是使用 hashNameextension 方法來取得給定上傳檔案的檔名與副檔名:

1$file = $request->file('avatar');
2 
3$name = $file->hashName(); // 產生一個不重複、隨機的名稱...
4$extension = $file->extension(); // 依據檔案的 MIME 型別判斷檔案的副檔名...
1$file = $request->file('avatar');
2 
3$name = $file->hashName(); // 產生一個不重複、隨機的名稱...
4$extension = $file->extension(); // 依據檔案的 MIME 型別判斷檔案的副檔名...

檔案可見度

在 Laravel 的 Flysystem 整合中,「可見度(Visibility)」是在多個平台間抽象化的檔案權限。檔案可以被定義為 public,或是被定義為 private。若將檔案定義為 public,即代表該檔案是可以被其他人正常存取的。舉例來說,若使用 S3 Driver,可以取得 public 檔案的 URL。

在使用 put 方法寫入檔案時,可以設定可見度:

1use Illuminate\Support\Facades\Storage;
2 
3Storage::put('file.jpg', $contents, 'public');
1use Illuminate\Support\Facades\Storage;
2 
3Storage::put('file.jpg', $contents, 'public');

若檔案已被保存,則可使用 getVisibility 來取得可見度,並使用 setVisibility 來設定可見度:

1$visibility = Storage::getVisibility('file.jpg');
2 
3Storage::setVisibility('file.jpg', 'public');
1$visibility = Storage::getVisibility('file.jpg');
2 
3Storage::setVisibility('file.jpg', 'public');

在處理上傳的檔案時,應使用 storePubliclystorePubliclyAs 方法來以 public 可見度保存上傳的檔案:

1$path = $request->file('avatar')->storePublicly('avatars', 's3');
2 
3$path = $request->file('avatar')->storePubliclyAs(
4 'avatars',
5 $request->user()->id,
6 's3'
7);
1$path = $request->file('avatar')->storePublicly('avatars', 's3');
2 
3$path = $request->file('avatar')->storePubliclyAs(
4 'avatars',
5 $request->user()->id,
6 's3'
7);

本機檔案與可見度

使用 local Driver 時,public 可見度 對於目錄來說可翻譯為 0755 權限,而檔案則可翻譯為 0644 權限。可以在 filesystems 設定當中修改這個權限映射:

1'local' => [
2 'driver' => 'local',
3 'root' => storage_path('app'),
4 'permissions' => [
5 'file' => [
6 'public' => 0644,
7 'private' => 0600,
8 ],
9 'dir' => [
10 'public' => 0755,
11 'private' => 0700,
12 ],
13 ],
14],
1'local' => [
2 'driver' => 'local',
3 'root' => storage_path('app'),
4 'permissions' => [
5 'file' => [
6 'public' => 0644,
7 'private' => 0600,
8 ],
9 'dir' => [
10 'public' => 0755,
11 'private' => 0700,
12 ],
13 ],
14],

刪除檔案

delete 方法接受要刪除的單一檔案名稱,或是一組檔案名稱陣列:

1use Illuminate\Support\Facades\Storage;
2 
3Storage::delete('file.jpg');
4 
5Storage::delete(['file.jpg', 'file2.jpg']);
1use Illuminate\Support\Facades\Storage;
2 
3Storage::delete('file.jpg');
4 
5Storage::delete(['file.jpg', 'file2.jpg']);

若有需要,也可指定要在哪個 Disk 上刪除檔案:

1use Illuminate\Support\Facades\Storage;
2 
3Storage::disk('s3')->delete('path/file.jpg');
1use Illuminate\Support\Facades\Storage;
2 
3Storage::disk('s3')->delete('path/file.jpg');

目錄

取得目錄中的所有檔案

files 方法回傳一組包含給定目錄中所有檔案的陣列。若想取得包含子目錄在內的給定目錄內所有檔案的清單,可使用 allFiles 方法:

1use Illuminate\Support\Facades\Storage;
2 
3$files = Storage::files($directory);
4 
5$files = Storage::allFiles($directory);
1use Illuminate\Support\Facades\Storage;
2 
3$files = Storage::files($directory);
4 
5$files = Storage::allFiles($directory);

取得目錄內的所有目錄

directories 方法回傳一組包含給定目錄內所有目錄的陣列。此外,也可以使用 allDirectories 方法來取得給定目錄內包含子目錄的所有目錄清單:

1$directories = Storage::directories($directory);
2 
3$directories = Storage::allDirectories($directory);
1$directories = Storage::directories($directory);
2 
3$directories = Storage::allDirectories($directory);

建立目錄

makeDirectory 方法會建立給定的目錄,包含所有需要的子目錄:

1Storage::makeDirectory($directory);
1Storage::makeDirectory($directory);

刪除目錄

最後,可使用 deleteDirectory 方法來移除某個目錄與其中所有檔案:

1Storage::deleteDirectory($directory);
1Storage::deleteDirectory($directory);

測試

使用 Storage Facade 的 fake 方法就可輕鬆地產生 Fake Disk。Fake Disk 可以與 Illuminate\Http\UploadedFile 類別的檔案產生工具來搭配使用,讓我們能非常輕鬆地測試檔案上傳。舉例來說:

1<?php
2 
3namespace Tests\Feature;
4 
5use Illuminate\Http\UploadedFile;
6use Illuminate\Support\Facades\Storage;
7use Tests\TestCase;
8 
9class ExampleTest extends TestCase
10{
11 public function test_albums_can_be_uploaded(): void
12 {
13 Storage::fake('photos');
14 
15 $response = $this->json('POST', '/photos', [
16 UploadedFile::fake()->image('photo1.jpg'),
17 UploadedFile::fake()->image('photo2.jpg')
18 ]);
19 
20 // 判斷保存了一個或多個檔案...
21 Storage::disk('photos')->assertExists('photo1.jpg');
22 Storage::disk('photos')->assertExists(['photo1.jpg', 'photo2.jpg']);
23 
24 // 判斷未保存一個或多個檔案...
25 Storage::disk('photos')->assertMissing('missing.jpg');
26 Storage::disk('photos')->assertMissing(['missing.jpg', 'non-existing.jpg']);
27 
28 // 判斷給定目錄是否為空...
29 Storage::disk('photos')->assertDirectoryEmpty('/wallpapers');
30 }
31}
1<?php
2 
3namespace Tests\Feature;
4 
5use Illuminate\Http\UploadedFile;
6use Illuminate\Support\Facades\Storage;
7use Tests\TestCase;
8 
9class ExampleTest extends TestCase
10{
11 public function test_albums_can_be_uploaded(): void
12 {
13 Storage::fake('photos');
14 
15 $response = $this->json('POST', '/photos', [
16 UploadedFile::fake()->image('photo1.jpg'),
17 UploadedFile::fake()->image('photo2.jpg')
18 ]);
19 
20 // 判斷保存了一個或多個檔案...
21 Storage::disk('photos')->assertExists('photo1.jpg');
22 Storage::disk('photos')->assertExists(['photo1.jpg', 'photo2.jpg']);
23 
24 // 判斷未保存一個或多個檔案...
25 Storage::disk('photos')->assertMissing('missing.jpg');
26 Storage::disk('photos')->assertMissing(['missing.jpg', 'non-existing.jpg']);
27 
28 // 判斷給定目錄是否為空...
29 Storage::disk('photos')->assertDirectoryEmpty('/wallpapers');
30 }
31}

預設情況下,fake 方法會刪除其臨時目錄下的所有檔案。若想保留這些檔案,可使用「persistentFake」方法。更多有關測試檔案上傳的資訊,可參考 HTTP 測試說明文件中有關檔案上傳的部分

exclamation

需要有 GD 擴充程式 才可使用 image 方法。

自訂 Filesystem

Laravel 的 Flysystem 整合預設提供了多種可用的「Driver」。不過,Flysystem 也不是只能使用這些 Driver,還有許多其他的存放系統 Adapter 可使用。若想在 Laravel 專案中使用這些額外的 Adapter 的話,則可建立一個自訂的 Driver。

若要定義自訂檔案系統,我們首先需要一個 Flysystem Adapter。我們先來在專案中新增一個由社群維護的 Dropbox Adapter:

1composer require spatie/flysystem-dropbox
1composer require spatie/flysystem-dropbox

接著,我們可以在專案的其中一個 Service Providerboot 方法內註冊這個 Driver。若要註冊 Driver,請使用 Storage Facade 的 extend 方法:

1<?php
2 
3namespace App\Providers;
4 
5use Illuminate\Contracts\Foundation\Application;
6use Illuminate\Filesystem\FilesystemAdapter;
7use Illuminate\Support\Facades\Storage;
8use Illuminate\Support\ServiceProvider;
9use League\Flysystem\Filesystem;
10use Spatie\Dropbox\Client as DropboxClient;
11use Spatie\FlysystemDropbox\DropboxAdapter;
12 
13class AppServiceProvider extends ServiceProvider
14{
15 /**
16 * Register any application services.
17 */
18 public function register(): void
19 {
20 // ...
21 }
22 
23 /**
24 * Bootstrap any application services.
25 */
26 public function boot(): void
27 {
28 Storage::extend('dropbox', function (Application $app, array $config) {
29 $adapter = new DropboxAdapter(new DropboxClient(
30 $config['authorization_token']
31 ));
32 
33 return new FilesystemAdapter(
34 new Filesystem($adapter, $config),
35 $adapter,
36 $config
37 );
38 });
39 }
40}
1<?php
2 
3namespace App\Providers;
4 
5use Illuminate\Contracts\Foundation\Application;
6use Illuminate\Filesystem\FilesystemAdapter;
7use Illuminate\Support\Facades\Storage;
8use Illuminate\Support\ServiceProvider;
9use League\Flysystem\Filesystem;
10use Spatie\Dropbox\Client as DropboxClient;
11use Spatie\FlysystemDropbox\DropboxAdapter;
12 
13class AppServiceProvider extends ServiceProvider
14{
15 /**
16 * Register any application services.
17 */
18 public function register(): void
19 {
20 // ...
21 }
22 
23 /**
24 * Bootstrap any application services.
25 */
26 public function boot(): void
27 {
28 Storage::extend('dropbox', function (Application $app, array $config) {
29 $adapter = new DropboxAdapter(new DropboxClient(
30 $config['authorization_token']
31 ));
32 
33 return new FilesystemAdapter(
34 new Filesystem($adapter, $config),
35 $adapter,
36 $config
37 );
38 });
39 }
40}

傳入 extend 方法的第一個引數是 Driver 的名稱,而第二個引數則是一本接收了 $app$config 變數的閉包。該閉包應回傳 Illuminate\Filesystem\FilesystemAdapter 的實體。$config 變數則包含了定義在 config/filesystems.php 中指定 Disk 的設定值。

建立並註冊好擴充的 Service Provider 後,就可以在 config/filesystems.php 設定當中使用 dropbox Driver。

翻譯進度
100% 已翻譯
更新時間:
2024年6月30日 上午8:26:00 [世界標準時間]
翻譯人員:
  • cornch
幫我們翻譯此頁

留言

尚無留言

“Laravel” is a Trademark of Taylor Otwell.
The source documentation is released under MIT license. See laravel/docs on GitHub for details.
The translated documentations are released under MIT license. See cornch/laravel-docs-l10n on GitHub for details.