파일 저장소
업데이트됨번역일: 2026년 9월 18일
이 페이지는 원문이 업데이트되어 번역이 갱신되었습니다.
- 원문 수정
- 2026년 9월 18일
- 번역 갱신
- 2026년 9월 18일
파일 저장소
파일 저장소
소개
Laravel은 Frank de Jonge가 만든 훌륭한 Flysystem PHP 패키지를 기반으로 강력한 파일시스템 추상화 기능을 제공합니다. Laravel의 Flysystem 통합은 로컬 파일시스템, SFTP, Amazon S3를 다루기 위한 간단한 드라이버를 제공합니다. 더욱 좋은 점은 각 저장소 시스템에 대해 API가 동일하게 유지되기 때문에, 로컬 개발 환경과 운영 서버 사이에서 저장소 옵션을 손쉽게 전환할 수 있다는 것입니다.
NOTE
예를 들어 로컬 개발 중에는 파일을 디스크에 저장하다가, 운영 환경에 배포할 때는 설정 값 하나만 바꿔서 Amazon S3나 NCP(Naver Cloud Platform) Object Storage 같은 S3 호환 스토리지를 사용하도록 전환할 수 있습니다. 코드 변경 없이 설정만으로 저장소를 바꿀 수 있는 것이 이 추상화 계층의 핵심 장점입니다.
파일 저장소
설정
Laravel의 파일시스템 설정 파일은 config/filesystems.php에 위치합니다. 이 파일에서는 애플리케이션이 사용할 모든 파일시스템 "디스크"를 설정할 수 있습니다. 여기서 각 디스크는 특정 스토리지 드라이버와 저장 위치를 조합한 것을 의미합니다. 설정 파일에는 지원되는 각 드라이버별로 예시 설정이 포함되어 있으므로, 이를 참고해 실제 스토리지 환경과 인증 정보에 맞게 수정하면 됩니다.
local 드라이버는 Laravel 애플리케이션이 실행 중인 서버에 로컬로 저장된 파일을 다루며, sftp 스토리지 드라이버는 SSH 키 기반 FTP에 사용됩니다. s3 드라이버는 Amazon S3 클라우드 스토리지 서비스에 파일을 쓸 때 사용합니다.
NOTE
디스크는 원하는 만큼 자유롭게 설정할 수 있으며, 같은 드라이버를 사용하는 디스크를 여러 개 만들어도 상관없습니다.
로컬 드라이버
local 드라이버를 사용할 때 모든 파일 작업은 filesystems 설정 파일에 정의된 root 디렉터리를 기준으로 상대 경로로 처리됩니다. 기본값은 storage/app/private 디렉터리입니다. 따라서 아래 코드는 storage/app/private/example.txt 파일에 내용을 씁니다.
use Illuminate\Support\Facades\Storage;
Storage::disk('local')->put('example.txt', 'Contents');public 디스크
애플리케이션의 filesystems 설정 파일에 포함된 public 디스크는 외부에 공개적으로 접근 가능해야 하는 파일을 저장하는 용도로 사용됩니다. 기본적으로 public 디스크는 local 드라이버를 사용하며 파일을 storage/app/public에 저장합니다.
public 디스크가 local 드라이버를 사용하고 있고 이 파일들을 웹에서 접근할 수 있도록 만들고 싶다면, storage/app/public 디렉터리(소스)에서 public/storage 디렉터리(대상)로 향하는 심볼릭 링크를 생성해야 합니다.
심볼릭 링크는 storage:link Artisan 명령어로 생성할 수 있습니다.
php artisan storage:linkNOTE
흔히 하는 실수 중 하나가 storage/app/public에 파일을 저장해두고 심볼릭 링크 생성을 잊어버리는 것입니다. 이 경우 파일은 서버에 존재하지만 웹 브라우저에서는 404 에러가 발생하니, 배포 스크립트에 storage:link 실행을 포함해두는 것이 좋습니다.
파일을 저장하고 심볼릭 링크를 생성했다면, asset 헬퍼를 사용해 파일의 URL을 만들 수 있습니다.
echo asset('storage/file.txt');추가적인 심볼릭 링크는 filesystems 설정 파일에서 설정할 수 있습니다. storage:link 명령어를 실행하면 설정된 모든 링크가 생성됩니다.
'links' => [
public_path('storage') => storage_path('app/public'),
public_path('images') => storage_path('app/images'),
],설정된 심볼릭 링크를 삭제하려면 storage:unlink 명령어를 사용하면 됩니다.
php artisan storage:unlink드라이버 사전 준비 사항
S3 드라이버 설정
S3 드라이버를 사용하기 전에 Composer 패키지 관리자를 통해 Flysystem S3 패키지를 설치해야 합니다.
composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependenciesS3 디스크 설정 배열은 config/filesystems.php 설정 파일에 있습니다. 일반적으로는 config/filesystems.php 파일에서 참조하는 아래 환경 변수를 통해 S3 관련 정보와 인증 정보를 설정합니다.
AWS_ACCESS_KEY_ID=<your-key-id>
AWS_SECRET_ACCESS_KEY=<your-secret-access-key>
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=<your-bucket-name>
AWS_USE_PATH_STYLE_ENDPOINT=false편의를 위해 이 환경 변수들은 AWS CLI에서 사용하는 명명 규칙과 동일하게 맞춰져 있습니다.
FTP 드라이버 설정
FTP 드라이버를 사용하기 전에 Composer 패키지 관리자를 통해 Flysystem FTP 패키지를 설치해야 합니다.
composer require league/flysystem-ftp "^3.0"Laravel의 Flysystem 통합은 FTP와도 잘 작동합니다. 다만 프레임워크의 기본 config/filesystems.php 설정 파일에는 FTP 예시 설정이 포함되어 있지 않습니다. FTP 파일시스템을 설정해야 한다면 아래 예시를 참고하세요.
'ftp' => [
'driver' => 'ftp',
'host' => env('FTP_HOST'),
'username' => env('FTP_USERNAME'),
'password' => env('FTP_PASSWORD'),
// 선택적인 FTP 설정...
// 'port' => env('FTP_PORT', 21),
// 'root' => env('FTP_ROOT'),
// 'passive' => true,
// 'ssl' => true,
// 'timeout' => 30,
],SFTP 드라이버 설정
SFTP 드라이버를 사용하기 전에 Composer 패키지 관리자를 통해 Flysystem SFTP 패키지를 설치해야 합니다.
composer require league/flysystem-sftp-v3 "^3.0"Laravel의 Flysystem 통합은 SFTP와도 잘 작동합니다. 다만 프레임워크의 기본 config/filesystems.php 설정 파일에는 SFTP 예시 설정이 포함되어 있지 않습니다. SFTP 파일시스템을 설정해야 한다면 아래 예시를 참고하세요.
'sftp' => [
'driver' => 'sftp',
'host' => env('SFTP_HOST'),
// 기본 인증 설정...
'username' => env('SFTP_USERNAME'),
'password' => env('SFTP_PASSWORD'),
// 암호가 걸린 SSH 키 기반 인증 설정...
'privateKey' => env('SFTP_PRIVATE_KEY'),
'passphrase' => env('SFTP_PASSPHRASE'),
// 파일 / 디렉터리 권한 설정...
'visibility' => 'private', // `private` = 0600, `public` = 0644
'directory_visibility' => 'private', // `private` = 0700, `public` = 0755
// 선택적인 SFTP 설정...
// 'hostFingerprint' => env('SFTP_HOST_FINGERPRINT'),
// 'maxTries' => 4,
// 'passphrase' => env('SFTP_PASSPHRASE'),
// 'port' => env('SFTP_PORT', 22),
// 'root' => env('SFTP_ROOT', ''),
// 'timeout' => 30,
// 'useAgent' => true,
],스코프 지정, 읽기 전용, 리드스루 파일시스템
스코프(scoped) 디스크를 사용하면 모든 경로에 특정 접두사(prefix)가 자동으로 붙는 파일시스템을 정의할 수 있습니다. 스코프 파일시스템 디스크를 만들기 전에 Composer 패키지 관리자를 통해 추가 Flysystem 패키지를 설치해야 합니다.
composer require league/flysystem-path-prefixing "^3.0"scoped 드라이버를 사용하는 디스크를 정의하면 기존의 어떤 파일시스템 디스크든 특정 경로로 스코프를 지정한 인스턴스로 만들 수 있습니다. 예를 들어 기존 s3 디스크를 특정 경로 접두사로 스코프를 지정한 디스크를 만들면, 이 스코프 디스크를 통한 모든 파일 작업은 지정된 접두사 하위에서만 이루어집니다.
's3-videos' => [
'driver' => 'scoped',
'disk' => 's3',
'prefix' => 'path/to/videos',
],"읽기 전용(read-only)" 디스크를 사용하면 쓰기 작업이 불가능한 파일시스템 디스크를 만들 수 있습니다. read-only 설정 옵션을 사용하기 전에 Composer 패키지 관리자를 통해 추가 Flysystem 패키지를 설치해야 합니다.
composer require league/flysystem-read-only "^3.0"그런 다음, 하나 이상의 디스크 설정 배열에 read-only 설정 옵션을 추가하면 됩니다.
's3-videos' => [
'driver' => 's3',
// ...
'read-only' => true,
],리드스루(read-through) 디스크를 사용하면 다운타임 없이 디스크 간 파일을 마이그레이션할 수 있습니다. 파일을 읽을 때 Laravel은 우선 기본(primary) 디스크를 확인합니다. 만약 해당 파일이 폴백(fallback) 디스크에만 존재한다면, Laravel은 폴백 디스크에서 파일을 읽어온 뒤 이후 요청을 위해 기본 디스크에 복사해 둡니다.
'assets' => [
'driver' => 'read-through',
'primary' => 's3',
'fallback' => 'legacy-s3',
],쓰기 작업과 디렉터리 목록 조회는 항상 기본 디스크를 대상으로 이루어집니다. 파일 존재 여부 확인이나 메타데이터 조회는 어느 쪽 디스크든 사용할 수 있으며, 이 과정에서는 기본 디스크로 파일이 복사되지 않습니다. 폴백 디스크의 파일을 기본 디스크로 복사(promotion)하는 과정이 실패하더라도, 기본적으로 읽기 작업 자체는 성공한 것으로 처리됩니다. 복사 실패 시 예외를 발생시키고 싶다면 throw_on_promotion_failure 설정 옵션을 true로 지정하세요.
Amazon S3 호환 파일시스템
애플리케이션의 filesystems 설정 파일에는 기본적으로 s3 디스크에 대한 설정이 포함되어 있습니다. 이 디스크는 Amazon S3와 연동하는 용도 외에도, RustFS, DigitalOcean Spaces, Vultr Object Storage, Cloudflare R2, Hetzner Cloud Storage와 같은 S3 호환 스토리지 서비스와 연동하는 데도 사용할 수 있습니다.
일반적으로는 사용하려는 서비스의 인증 정보로 디스크 설정을 업데이트한 뒤, endpoint 설정 옵션의 값만 변경해주면 됩니다. 이 옵션의 값은 보통 AWS_ENDPOINT 환경 변수를 통해 정의합니다.
'endpoint' => env('AWS_ENDPOINT', 'https://rustfs:9000'),디스크 인스턴스 가져오기
Storage 파사드를 사용하면 설정해 둔 모든 디스크를 자유롭게 다룰 수 있습니다. 예를 들어 put 메서드를 호출하면 기본 디스크에 아바타 파일을 저장할 수 있습니다. Storage 파사드에서 disk 메서드를 먼저 호출하지 않고 다른 메서드를 바로 사용하면, 해당 메서드는 자동으로 기본 디스크를 대상으로 실행됩니다:
use Illuminate\Support\Facades\Storage;
Storage::put('avatars/1', $content);애플리케이션에서 여러 개의 디스크를 함께 사용하는 경우에는, Storage 파사드의 disk 메서드를 통해 원하는 디스크를 지정해서 작업할 수 있습니다:
Storage::disk('s3')->put('avatars/1', $content);온디맨드(On-Demand) 디스크
경우에 따라 filesystems 설정 파일에 미리 등록해 두지 않은 설정으로, 실행 중에 즉석에서 디스크를 생성해야 할 때가 있습니다. 이런 상황에서는 Storage 파사드의 build 메서드에 설정 배열을 직접 전달해서 디스크를 만들 수 있습니다:
use Illuminate\Support\Facades\Storage;
$disk = Storage::build([
'driver' => 'local',
'root' => '/path/to/root',
]);
$disk->put('image.jpg', $content);NOTE
온디맨드 디스크는 예를 들어 사용자별로 서로 다른 저장 경로나 자격 증명(credential)을 동적으로 지정해야 하는 멀티 테넌트(multi-tenant) 애플리케이션에서 특히 유용합니다.
파일 조회하기
get 메서드를 사용하면 파일의 내용을 조회할 수 있습니다. 이 메서드는 파일의 원시 문자열 내용을 반환합니다. 모든 파일 경로는 디스크의 "루트" 위치를 기준으로 상대 경로로 지정해야 한다는 점을 잊지 마세요:
$contents = Storage::get('file.jpg');조회하려는 파일이 JSON 형식이라면 json 메서드를 사용해 파일 내용을 가져오면서 동시에 디코딩할 수 있습니다:
$orders = Storage::json('orders.json');exists 메서드를 사용하면 디스크에 특정 파일이 존재하는지 확인할 수 있습니다:
if (Storage::disk('s3')->exists('file.jpg')) {
// ...
}반대로 missing 메서드는 디스크에 파일이 존재하지 않는지 확인합니다:
if (Storage::disk('s3')->missing('file.jpg')) {
// ...
}파일 다운로드하기
download 메서드는 주어진 경로에 있는 파일을 사용자의 브라우저가 강제로 다운로드하도록 만드는 응답을 생성합니다. 두 번째 인수로 파일명을 전달하면 사용자가 다운로드할 때 보게 되는 파일 이름을 지정할 수 있고, 세 번째 인수로는 HTTP 헤더 배열을 전달할 수 있습니다:
return Storage::download('file.jpg');
return Storage::download('file.jpg', $name, $headers);파일 URL
url 메서드를 사용하면 주어진 파일의 URL을 얻을 수 있습니다. local 드라이버를 사용하는 경우, 보통 주어진 경로 앞에 /storage를 붙인 상대 URL을 반환합니다. s3 드라이버를 사용하는 경우에는 완전한 형태의 원격 URL을 반환합니다:
use Illuminate\Support\Facades\Storage;
$url = Storage::url('file.jpg');local 드라이버를 사용할 때, 외부에 공개적으로 접근 가능해야 하는 파일은 모두 storage/app/public 디렉터리에 저장해야 합니다. 그리고 storage/app/public 디렉터리를 가리키는 심볼릭 링크를 public/storage 위치에 생성해야 합니다.
WARNING
local 드라이버를 사용할 때 url의 반환값은 URL 인코딩되지 않습니다. 이 때문에 파일을 저장할 때는 항상 유효한 URL을 만들 수 있는 이름을 사용하는 것이 좋습니다. (예: 공백이나 한글, 특수문자가 포함된 파일명은 피하는 것이 안전합니다.)
URL 호스트 커스터마이징
Storage 파사드로 생성되는 URL의 호스트를 변경하고 싶다면, 디스크 설정 배열에 url 옵션을 추가하거나 수정하면 됩니다:
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => false,
],임시 URL
temporaryUrl 메서드를 사용하면 local과 s3 드라이버로 저장된 파일에 대한 임시 URL을 생성할 수 있습니다. 이 메서드는 경로와 함께 URL이 만료될 시점을 나타내는 DateTime 인스턴스를 인수로 받습니다:
use Illuminate\Support\Facades\Storage;
$url = Storage::temporaryUrl(
'file.jpg', now()->plus(minutes: 5)
);NOTE
임시 URL은 예를 들어 사용자에게 파일 다운로드 링크를 잠깐만 유효하게 제공하고 싶을 때(예: 첨부파일 공유, 일회성 다운로드 링크) 유용합니다.
로컬 임시 URL 활성화하기
local 드라이버에 임시 URL 기능이 추가되기 이전부터 애플리케이션을 개발해왔다면, 로컬 임시 URL 기능을 별도로 활성화해야 할 수 있습니다. 이를 위해서는 config/filesystems.php 설정 파일에서 local 디스크 설정 배열에 serve 옵션을 추가하면 됩니다:
'local' => [ 'driver' => 'local', 'root' => storage_path('app/private'), 'serve' => true, // 'throw' => false,],S3 요청 파라미터
추가적인 S3 요청 파라미터를 지정해야 한다면, temporaryUrl 메서드의 세 번째 인수로 요청 파라미터 배열을 전달하면 됩니다:
$url = Storage::temporaryUrl(
'file.jpg',
now()->plus(minutes: 5),
[
'ResponseContentType' => 'application/octet-stream',
'ResponseContentDisposition' => 'attachment; filename=file2.jpg',
]
);임시 URL 커스터마이징
특정 스토리지 디스크에서 임시 URL이 생성되는 방식을 커스터마이징해야 한다면 buildTemporaryUrlsUsing 메서드를 사용할 수 있습니다. 예를 들어, 보통은 임시 URL을 지원하지 않는 디스크에 저장된 파일을 다운로드할 수 있게 해주는 컨트롤러가 있을 때 유용합니다. 일반적으로 이 메서드는 서비스 프로바이더의 boot 메서드 안에서 호출합니다:
<?php
namespace App\Providers;
use DateTime;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* 애플리케이션의 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Storage::disk('local')->buildTemporaryUrlsUsing(
function (string $path, DateTime $expiration, array $options) {
return URL::temporarySignedRoute(
'files.download',
$expiration,
array_merge($options, ['path' => $path])
);
}
);
}
}임시 업로드 URL
WARNING
임시 업로드 URL을 생성하는 기능은 s3와 local 드라이버에서만 지원됩니다.
클라이언트 애플리케이션에서 파일을 직접 업로드할 수 있는 임시 URL을 생성해야 한다면 temporaryUploadUrl 메서드를 사용할 수 있습니다. 이 메서드는 경로와 URL이 만료될 시점을 나타내는 DateTime 인스턴스를 인수로 받습니다. temporaryUploadUrl 메서드는 업로드 URL과 업로드 요청 시 함께 포함해야 하는 헤더로 구조 분해할 수 있는 연관 배열을 반환합니다:
use Illuminate\Support\Facades\Storage;
['url' => $url, 'headers' => $headers] = Storage::temporaryUploadUrl(
'file.jpg', now()->plus(minutes: 5)
);이 메서드는 클라이언트 애플리케이션이 Amazon S3와 같은 클라우드 스토리지 시스템에 파일을 직접 업로드해야 하는 서버리스 환경에서 특히 유용합니다.
파일 메타데이터
파일을 읽고 쓰는 기능 외에도, Laravel은 파일 자체에 대한 정보를 제공할 수 있습니다. 예를 들어 size 메서드는 파일의 크기를 바이트 단위로 반환합니다:
use Illuminate\Support\Facades\Storage;
$size = Storage::size('file.jpg');lastModified 메서드는 파일이 마지막으로 수정된 시각을 UNIX 타임스탬프로 반환합니다:
$time = Storage::lastModified('file.jpg');파일의 MIME 타입은 mimeType 메서드로 확인할 수 있습니다:
$mime = Storage::mimeType('file.jpg');파일 경로
path 메서드를 사용하면 주어진 파일의 경로를 확인할 수 있습니다. local 드라이버를 사용 중이라면 파일의 절대 경로를 반환하며, s3 드라이버를 사용 중이라면 S3 버킷 내 상대 경로를 반환합니다:
use Illuminate\Support\Facades\Storage;
$path = Storage::path('file.jpg');파일 저장소
파일 저장하기
put 메서드를 사용하면 디스크에 파일 내용을 저장할 수 있습니다. put 메서드에는 PHP resource를 전달할 수도 있는데, 이 경우 Flysystem이 내부적으로 지원하는 스트림 기능을 사용하게 됩니다. 모든 파일 경로는 해당 디스크에 설정된 "루트" 위치를 기준으로 상대 경로로 지정해야 한다는 점을 기억하세요:
use Illuminate\Support\Facades\Storage;
Storage::put('file.jpg', $contents);
Storage::put('file.jpg', $resource);쓰기 실패 처리
put 메서드(또는 다른 "쓰기" 관련 작업)가 파일을 디스크에 쓰지 못하면 false가 반환됩니다:
if (! Storage::put('file.jpg', $contents)) {
// 파일을 디스크에 쓸 수 없습니다...
}필요하다면 파일시스템 디스크 설정 배열에 throw 옵션을 정의할 수 있습니다. 이 옵션을 true로 설정하면, put과 같은 "쓰기" 메서드가 쓰기 작업에 실패했을 때 League\Flysystem\UnableToWriteFile 인스턴스를 예외로 던집니다:
'public' => [
'driver' => 'local',
// ...
'throw' => true,
],또는 report 옵션을 정의할 수도 있습니다. 이 옵션을 true로 설정하면, "쓰기" 작업이 실패했을 때 Laravel이 애플리케이션의 예외 핸들러를 통해 내부 예외를 로그로 기록합니다. 이때는 예외를 던지거나 쓰기 작업의 반환값을 방해하지 않습니다:
'public' => [
'driver' => 'local',
// ...
'report' => true,
],throw와 report 옵션을 모두 정의하지 않으면, 디스크는 실패 시 조용히 false를 반환하며 내부 예외는 던져지지도, 로그로 남지도 않습니다.
파일 앞/뒤에 내용 추가하기
prepend와 append 메서드를 사용하면 파일의 맨 앞이나 맨 뒤에 내용을 추가할 수 있습니다:
Storage::prepend('file.log', 'Prepended Text');
Storage::append('file.log', 'Appended Text');파일 복사 및 이동
copy 메서드는 디스크 내에서 기존 파일을 새 위치로 복사할 때, move 메서드는 기존 파일의 이름을 변경하거나 새 위치로 옮길 때 사용합니다:
Storage::copy('old/file.jpg', 'new/file.jpg');
Storage::move('old/file.jpg', 'new/file.jpg');파일을 다른 디스크로 복사하거나 이동하려면 copyToDisk와 moveToDisk 메서드를 사용하면 됩니다. 세 번째 인수를 지정하지 않으면 원본 파일의 경로가 대상 디스크에서도 그대로 사용됩니다:
Storage::disk('local')->copyToDisk('s3', 'reports/report.csv');
Storage::disk('local')->moveToDisk(
's3', 'reports/report.csv', 'archive/report.csv'
);자동 스트리밍
파일을 스토리지에 스트리밍 방식으로 저장하면 메모리 사용량을 크게 줄일 수 있습니다. Laravel이 파일을 자동으로 스트리밍해서 저장하도록 하려면 putFile 또는 putFileAs 메서드를 사용하세요. 이 메서드들은 Illuminate\Http\File 또는 Illuminate\Http\UploadedFile 인스턴스를 인수로 받아, 지정한 위치로 파일을 자동으로 스트리밍합니다:
use Illuminate\Http\File;
use Illuminate\Support\Facades\Storage;
// 파일명을 위한 고유 ID를 자동으로 생성...
$path = Storage::putFile('photos', new File('/path/to/photo'));
// 파일명을 직접 지정...
$path = Storage::putFileAs('photos', new File('/path/to/photo'), 'photo.jpg');putFile 메서드와 관련해 몇 가지 눈여겨볼 점이 있습니다. 위 예시에서는 파일명이 아니라 디렉터리 이름만 지정했습니다. putFile 메서드는 기본적으로 고유 ID를 생성해 파일명으로 사용합니다. 파일 확장자는 파일의 MIME 타입을 확인해 자동으로 결정됩니다. putFile 메서드는 저장된 파일의 경로(자동 생성된 파일명 포함)를 반환하므로, 이 값을 데이터베이스에 저장해두면 됩니다.
putFile과 putFileAs 메서드는 저장할 파일의 "가시성(visibility)"을 지정하는 인수도 받을 수 있습니다. 이는 Amazon S3와 같은 클라우드 디스크에 파일을 저장하고, 생성된 URL을 통해 외부에 공개적으로 접근할 수 있도록 하고 싶을 때 특히 유용합니다:
Storage::putFile('photos', new File('/path/to/photo'), 'public');파일 업로드
웹 애플리케이션에서 파일 저장이 가장 많이 사용되는 상황 중 하나는 사진이나 문서 같은 사용자 업로드 파일을 저장하는 경우입니다. Laravel에서는 업로드된 파일 인스턴스의 store 메서드를 사용하면 매우 간단하게 파일을 저장할 수 있습니다. 파일을 저장하려는 경로를 인수로 store 메서드를 호출하면 됩니다:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserAvatarController extends Controller
{
/**
* 사용자의 아바타를 업데이트합니다.
*/
public function update(Request $request): string
{
$path = $request->file('avatar')->store('avatars');
return $path;
}
}이 예시에서 몇 가지 짚어볼 부분이 있습니다. 파일명이 아니라 디렉터리 이름만 지정했다는 점에 주목하세요. 기본적으로 store 메서드는 고유 ID를 생성해 파일명으로 사용합니다. 파일 확장자는 파일의 MIME 타입을 통해 자동으로 결정됩니다. store 메서드는 저장된 파일의 경로(자동 생성된 파일명 포함)를 반환하므로, 이 값을 데이터베이스에 저장해두면 됩니다.
동일한 작업을 Storage 파사드의 putFile 메서드로도 수행할 수 있습니다:
$path = Storage::putFile('avatars', $request->file('avatar'));파일명 직접 지정하기
저장되는 파일에 자동으로 파일명이 부여되는 것을 원하지 않는다면, 경로와 파일명, (선택적으로) 디스크를 인수로 받는 storeAs 메서드를 사용할 수 있습니다:
$path = $request->file('avatar')->storeAs(
'avatars', $request->user()->id
);Storage 파사드의 putFileAs 메서드를 사용해도 위와 동일한 작업을 수행할 수 있습니다:
$path = Storage::putFileAs(
'avatars', $request->file('avatar'), $request->user()->id
);WARNING
파일 경로에서 출력 불가능한 문자나 유효하지 않은 유니코드 문자는 자동으로 제거됩니다. 따라서 Laravel의 파일 저장 메서드에 경로를 전달하기 전에 미리 경로를 정제(sanitize)해두는 것이 좋습니다. 파일 경로는 League\Flysystem\WhitespacePathNormalizer::normalizePath 메서드를 통해 정규화됩니다.
디스크 지정하기
업로드된 파일의 store 메서드는 기본적으로 애플리케이션의 기본 디스크를 사용합니다. 다른 디스크를 지정하고 싶다면, store 메서드의 두 번째 인수로 디스크 이름을 전달하세요:
$path = $request->file('avatar')->store(
'avatars/'.$request->user()->id, 's3'
);storeAs 메서드를 사용하는 경우에는 세 번째 인수로 디스크 이름을 전달하면 됩니다:
$path = $request->file('avatar')->storeAs(
'avatars',
$request->user()->id,
's3'
);업로드된 파일의 기타 정보
업로드된 파일의 원본 이름과 확장자를 알고 싶다면 getClientOriginalName, getClientOriginalExtension 메서드를 사용하면 됩니다:
$file = $request->file('avatar');
$name = $file->getClientOriginalName();
$extension = $file->getClientOriginalExtension();다만 getClientOriginalName과 getClientOriginalExtension 메서드는 안전하지 않은 것으로 간주됩니다. 악의적인 사용자가 파일명과 확장자를 임의로 조작할 수 있기 때문입니다. 이런 이유로, 일반적으로는 파일명과 확장자를 얻을 때 hashName과 extension 메서드를 사용하는 것이 좋습니다:
$file = $request->file('avatar');
$name = $file->hashName(); // 고유한 무작위 이름을 생성합니다...
$extension = $file->extension(); // 파일의 MIME 타입을 기준으로 확장자를 결정합니다...파일 가시성(Visibility)
Laravel의 Flysystem 통합에서 "가시성(visibility)"은 여러 플랫폼에 걸쳐 파일 권한을 추상화한 개념입니다. 파일은 public 또는 private로 선언할 수 있습니다. 파일을 public으로 선언한다는 것은 일반적으로 그 파일에 다른 사람들도 접근할 수 있어야 한다는 의미입니다. 예를 들어 S3 드라이버를 사용할 때, public 파일에 대해서는 URL을 조회할 수 있습니다.
put 메서드로 파일을 저장할 때 가시성을 함께 지정할 수 있습니다:
use Illuminate\Support\Facades\Storage;
Storage::put('file.jpg', $contents, 'public');이미 저장된 파일이라면 getVisibility와 setVisibility 메서드로 가시성을 조회하거나 변경할 수 있습니다:
$visibility = Storage::getVisibility('file.jpg');
Storage::setVisibility('file.jpg', 'public');업로드된 파일을 다룰 때는 storePublicly와 storePubliclyAs 메서드를 사용해 public 가시성으로 저장할 수 있습니다:
$path = $request->file('avatar')->storePublicly('avatars', 's3');
$path = $request->file('avatar')->storePubliclyAs(
'avatars',
$request->user()->id,
's3'
);이미지 조작
업로드된 이미지를 저장하기 전에 크기 조절, 자르기, 포맷 변환 등의 처리가 필요하다면 Laravel의 이미지 조작 기능을 사용할 수 있습니다:
$path = $request->image('avatar')
->cover(400, 400)
->toWebp()
->storePublicly('avatars', 'public');파일시스템 디스크에 이미 저장되어 있는 파일로부터 이미지 인스턴스를 생성할 수도 있습니다:
$image = Storage::disk('public')->image('avatars/photo.jpg');로컬 파일과 가시성
local 드라이버를 사용할 때, public 가시성은 디렉터리에 0755 권한, 파일에 0644 권한으로 변환됩니다. 이 권한 매핑은 애플리케이션의 filesystems 설정 파일에서 수정할 수 있습니다:
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
'permissions' => [
'file' => [
'public' => 0644,
'private' => 0600,
],
'dir' => [
'public' => 0755,
'private' => 0700,
],
],
'throw' => false,
],디렉터리
디렉터리 내 모든 파일 조회하기
files 메서드는 주어진 디렉터리 안에 있는 모든 파일 목록을 배열로 반환합니다. 하위 디렉터리까지 포함해서 모든 파일을 재귀적으로 조회하고 싶다면 allFiles 메서드를 사용하면 됩니다:
use Illuminate\Support\Facades\Storage;
$files = Storage::files($directory);
$files = Storage::allFiles($directory);디렉터리 내 모든 하위 디렉터리 조회하기
directories 메서드는 주어진 디렉터리 안에 있는 모든 하위 디렉터리 목록을 배열로 반환합니다. 마찬가지로 모든 하위 디렉터리를 재귀적으로 조회하려면 allDirectories 메서드를 사용하면 됩니다:
$directories = Storage::directories($directory);
$directories = Storage::allDirectories($directory);디렉터리 생성하기
makeDirectory 메서드는 지정한 디렉터리를 필요한 하위 디렉터리까지 포함해 새로 생성합니다:
Storage::makeDirectory($directory);디렉터리 삭제하기
마지막으로, deleteDirectory 메서드는 디렉터리와 그 안에 있는 모든 파일을 함께 삭제할 때 사용합니다:
Storage::deleteDirectory($directory);NOTE
로컬 드라이버를 사용하는 경우 deleteDirectory는 디렉터리 안의 파일까지 재귀적으로 모두 삭제하므로, 실행 전에 대상 경로가 올바른지 반드시 확인하세요.
디렉터리
디렉터리 내 모든 파일 조회하기
files 메서드는 지정한 디렉터리 안에 있는 모든 파일의 배열을 반환합니다. 하위 디렉터리까지 포함해서 전체 파일 목록을 가져오고 싶다면 allFiles 메서드를 사용하면 됩니다:
use Illuminate\Support\Facades\Storage;
$files = Storage::files($directory);
$files = Storage::allFiles($directory);디렉터리 내 모든 하위 디렉터리 조회하기
directories 메서드는 지정한 디렉터리 안에 있는 모든 디렉터리의 배열을 반환합니다. 하위 디렉터리 안의 디렉터리까지 재귀적으로 모두 가져오려면 allDirectories 메서드를 사용하세요:
$directories = Storage::directories($directory);
$directories = Storage::allDirectories($directory);디렉터리 생성하기
makeDirectory 메서드는 지정한 디렉터리를 생성합니다. 이 과정에서 필요한 하위 디렉터리도 함께 생성됩니다:
Storage::makeDirectory($directory);디렉터리 삭제하기
deleteDirectory 메서드를 사용하면 디렉터리와 그 안에 있는 모든 파일을 한 번에 삭제할 수 있습니다:
Storage::deleteDirectory($directory);NOTE
deleteDirectory는 디렉터리 자체와 그 하위의 모든 내용을 재귀적으로 삭제하므로, 삭제 전에 대상 경로가 올바른지 반드시 확인하는 것이 좋습니다.
파일 저장소
테스트
Storage 파사드의 fake 메서드를 사용하면 가짜 디스크를 손쉽게 생성할 수 있습니다. 이 메서드를 Illuminate\Http\UploadedFile 클래스가 제공하는 파일 생성 유틸리티와 함께 사용하면 파일 업로드 테스트를 매우 간단하게 작성할 수 있습니다. 예제를 살펴보겠습니다.
Pest
<?php
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
test('albums can be uploaded', function () {
Storage::fake('photos');
$response = $this->json('POST', '/photos', [
UploadedFile::fake()->image('photo1.jpg'),
UploadedFile::fake()->image('photo2.jpg')
]);
// 파일이 하나 이상 저장되었는지 확인...
Storage::disk('photos')->assertExists('photo1.jpg');
Storage::disk('photos')->assertExists(['photo1.jpg', 'photo2.jpg']);
// 파일이 저장되지 않았는지 확인...
Storage::disk('photos')->assertMissing('missing.jpg');
Storage::disk('photos')->assertMissing(['missing.jpg', 'non-existing.jpg']);
// 특정 디렉터리 내 파일 개수가 예상한 값과 일치하는지 확인...
Storage::disk('photos')->assertCount('/wallpapers', 2);
// 특정 디렉터리가 비어 있는지 확인...
Storage::disk('photos')->assertDirectoryEmpty('/wallpapers');
// 디스크에 파일이 하나도 없는지 확인...
Storage::disk('photos')->assertEmpty();
});PHPUnit
<?php
namespace Tests\Feature;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_albums_can_be_uploaded(): void
{
Storage::fake('photos');
$response = $this->json('POST', '/photos', [
UploadedFile::fake()->image('photo1.jpg'),
UploadedFile::fake()->image('photo2.jpg')
]);
// 파일이 하나 이상 저장되었는지 확인...
Storage::disk('photos')->assertExists('photo1.jpg');
Storage::disk('photos')->assertExists(['photo1.jpg', 'photo2.jpg']);
// 파일이 저장되지 않았는지 확인...
Storage::disk('photos')->assertMissing('missing.jpg');
Storage::disk('photos')->assertMissing(['missing.jpg', 'non-existing.jpg']);
// 특정 디렉터리 내 파일 개수가 예상한 값과 일치하는지 확인...
Storage::disk('photos')->assertCount('/wallpapers', 2);
// 특정 디렉터리가 비어 있는지 확인...
Storage::disk('photos')->assertDirectoryEmpty('/wallpapers');
// 디스크에 파일이 하나도 없는지 확인...
Storage::disk('photos')->assertEmpty();
}
}fake 메서드는 기본적으로 임시 디렉터리에 있는 모든 파일을 삭제합니다. 테스트가 끝난 뒤에도 파일을 남겨두고 싶다면 persistentFake 메서드를 대신 사용하면 됩니다. 파일 업로드 테스트에 대한 더 자세한 내용은 HTTP 테스트 문서의 파일 업로드 테스트 관련 항목을 참고하세요.
WARNING
image 메서드를 사용하려면 GD 확장 모듈이 설치되어 있어야 합니다.
커스텀 파일시스템
Laravel의 Flysystem 통합은 기본적으로 여러 "드라이버"를 제공하지만, Flysystem은 이것들에만 국한되지 않고 훨씬 더 다양한 저장소 시스템을 위한 어댑터를 지원합니다. 이런 추가 어댑터 중 하나를 Laravel 애플리케이션에서 사용하고 싶다면, 커스텀 드라이버를 직접 만들 수 있습니다.
커스텀 파일시스템을 정의하려면 먼저 Flysystem 어댑터가 필요합니다. 커뮤니티에서 관리하는 Dropbox 어댑터를 프로젝트에 추가해 보겠습니다.
composer require spatie/flysystem-dropbox그다음, 애플리케이션의 서비스 프로바이더 중 하나의 boot 메서드 안에서 드라이버를 등록하면 됩니다. 이를 위해 Storage 파사드의 extend 메서드를 사용합니다.
<?php
namespace App\Providers;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider;
use League\Flysystem\Filesystem;
use Spatie\Dropbox\Client as DropboxClient;
use Spatie\FlysystemDropbox\DropboxAdapter;
class AppServiceProvider extends ServiceProvider
{
/**
* 애플리케이션 서비스를 등록합니다.
*/
public function register(): void
{
// ...
}
/**
* 애플리케이션 서비스를 부트스트랩합니다.
*/
public function boot(): void
{
Storage::extend('dropbox', function (Application $app, array $config) {
$adapter = new DropboxAdapter(new DropboxClient(
$config['authorization_token']
));
return new FilesystemAdapter(
new Filesystem($adapter, $config),
$adapter,
$config
);
});
}
}extend 메서드의 첫 번째 인자는 드라이버의 이름이고, 두 번째 인자는 $app과 $config 변수를 전달받는 클로저입니다. 이 클로저는 반드시 Illuminate\Filesystem\FilesystemAdapter 인스턴스를 반환해야 합니다. $config 변수에는 config/filesystems.php에서 해당 디스크에 대해 정의한 설정값들이 담겨 있습니다.
NOTE
위 예시처럼 커스텀 드라이버를 확장하는 코드는 반드시 AppServiceProvider가 아니어도 됩니다. 별도의 서비스 프로바이더(예: DropboxServiceProvider)를 만들어 관리하면, 나중에 해당 확장 기능을 제거하거나 다른 프로젝트로 옮길 때 훨씬 편리합니다.
확장 기능을 담은 서비스 프로바이더를 만들고 등록했다면, 이제 config/filesystems.php 설정 파일에서 dropbox 드라이버를 사용할 수 있습니다.
'dropbox' => [
'driver' => 'dropbox',
'authorization_token' => env('DROPBOX_AUTHORIZATION_TOKEN'),
],이렇게 등록된 dropbox 디스크는 앞서 살펴본 로컬이나 S3 디스크와 완전히 동일한 방식으로 사용할 수 있습니다. 즉, Storage::disk('dropbox')->put(...)처럼 호출하면 되며, 애플리케이션 코드 입장에서는 저장소가 어디인지 신경 쓸 필요가 없습니다.
이처럼 Laravel의 파일시스템 계층은 실제 저장소가 로컬 디스크든, S3든, Dropbox와 같은 외부 서비스든 상관없이 동일한 API로 다룰 수 있도록 추상화되어 있습니다. 필요한 어댑터가 Flysystem 생태계에 이미 존재한다면, 이렇게 몇 줄의 코드만으로 프로젝트에 새로운 저장소를 손쉽게 통합할 수 있습니다.