시작하며
Laravel 9는 2022년 2월 8일에 릴리스되었습니다. 이 가이드에서는 Laravel 8.x에서 9.x로의 업그레이드 절차와 영향이 큰 변경점을 정리합니다.업그레이드에 필요한 예상 시간은 약 30분입니다. 다만 메일 송신 · 파일 스토리지 · 독자 캐스트 · 프레임워크의 코어 클래스의 오버라이드 상황에 따라 작업량은 늘어날 수 있습니다.
Laravel Shift를 사용한 자동 업그레이드
Laravel Shift를 사용하여 업그레이드를 자동화할 수도 있습니다. Shift는composer.json이나 설정 파일의 갱신을 보조해 주므로, 차분 확인의 출발점으로서 편리합니다.
영향도별 변경점
영향도: 높음
- 의존관계 갱신
- Flysystem 3.x로의 마이그레이션
- Symfony Mailer로의 마이그레이션
영향도: 중간
BelongsToMany의firstOrNew/firstOrCreate/updateOrCreate메서드- Custom Casts와
null의 동작 - HTTP 클라이언트의 기본 타임아웃
- PHP Return Types 추가
- Postgres의
schema설정명 변경 assertDeleted메서드의 폐지lang디렉터리 이동- 패스워드 룰 변경
when/unless메서드의 변경- 유효성 검증에서의 미검증 배열 키의 취급
업그레이드 절차
의존관계 갱신
영향도: 높음 Laravel 9에서는 PHP 8.0.2 이상이 필요합니다. 우선composer.json의 의존관계를 재검토해 주세요.
facade/ignition을 삭제하고,spatie/laravel-ignition:^1.0으로 대체pusher/pusher-php-server를 사용하고 있는 경우는^5.0으로 갱신- 이용 중인 서드파티 패키지가 Laravel 9 대응판인지 확인
- Vonage 통지 채널을 사용하고 있는 경우는 개별 업그레이드 가이드도 확인
PHP 버전 요구사항
영향도: 높음 Laravel 9에서는 PHP 8.0.2 이상이 필수입니다. CI, 로컬 개발 환경, 프로덕션 환경 모두에서 PHP 버전을 맞추고 나서 업그레이드를 진행해 주세요.Symfony Mailer로의 마이그레이션
영향도: 높음 Laravel 9의 큰 변경 중 하나는 2021년 12월로 유지보수가 종료된 SwiftMailer에서 Symfony Mailer로의 마이그레이션입니다. 일반적인Mail::to()->send()만 사용하고 있는 애플리케이션은 영향이 적은 한편, SwiftMailer의 저레벨 API를 직접 만지고 있는 경우에는 확인이 필요합니다.
드라이버 의존관계
withSwiftMessage에서 withSymfonyMessage로
Illuminate\Mail\Mailer의 send, html, raw, plain은 void가 아니라 Illuminate\Mail\SentMessage를 반환하도록 되었습니다. 또한 MessageSent 이벤트의 message 프로퍼티에는 Swift_Message가 아니라 Symfony\Component\Mime\Email이 들어갑니다.
SMTP 설정의 재검토
Symfony Mailer에서는 SMTP의stream 옵션이 폐지되고, 지원되는 설정은 톱 레벨로 이동합니다.
auth_mode의 명시적 설정도 불필요해졌습니다. 무효한 메일 주소를 송신 후에 회수하는 것이 아니라, 송신 전에 유효성 검증하는 운용으로 옮기는 것이 안전합니다.
Flysystem 3.x로의 마이그레이션
영향도: 높음 Laravel 9는Storage 파사드의 내부 구현을 Flysystem 1.x에서 3.x로 갱신했습니다. 파일 조작 메서드는 가능한 한 호환성을 유지하고 있지만, 예외 · 반환값 · 어댑터 등록 주변에 차분이 있습니다.
드라이버의 추가 설치
Storage의 주요 동작 변경
put/write/writeStream은 기존 파일을 기본으로 덮어쓰기- 쓰기 실패 시는 예외가 아니라
false를 반환 - 존재하지 않는 파일의 읽기는 예외가 아니라
null을 반환 - 존재하지 않는 파일의
delete는true를 반환 - cached adapter는 삭제되었으므로,
disk설정 내의cache키를 삭제할 수 있음
throw 옵션을 설정해 주세요.
Storage::extend()의 콜백이 Illuminate\Filesystem\FilesystemAdapter를 직접 반환하는 구현으로 조정해 주세요.
BelongsToMany의 firstOrNew / firstOrCreate / updateOrCreate
영향도: 중간
Laravel 8에서는 이 메서드들에 전달한 제1인자의 속성 배열이 중간 테이블과 비교되고 있었습니다. Laravel 9에서는 관련 모델의 테이블과 비교됩니다.
firstOrCreate는 제2인자의 $values를 받을 수 있게 되어, 다른 릴레이션과 동작이 맞춰졌습니다.
Custom Casts와 null
영향도: 중간
Laravel 9에서는 캐스트 대상에 null을 대입한 경우에도 custom cast의 set 메서드가 호출됩니다. null을 상정하지 않은 cast는 업그레이드 후에 예외를 낼 가능성이 있습니다.
HTTP 클라이언트의 기본 타임아웃
영향도: 중간 HTTP 클라이언트의 기본 타임아웃은 30초가 되었습니다. 이전에는 무제한으로 계속 대기하는 경우가 있었습니다.PHP Return Types 추가
영향도: 중간 Laravel 9에서는 PHP 본체나 Symfony의 요건에 맞춰 각종 코어 클래스에 반환값 타입이 추가되었습니다. Laravel의 코어 클래스를 상속하여offsetGet, offsetSet, jsonSerialize, open, read 등을 오버라이드하고 있는 경우에는, 자체 구현에도 같은 반환값 타입을 추가해 주세요.
Postgres의 schema 설정명 변경
영향도: 중간
Postgres 접속에서 검색 경로를 설정하고 있는 경우, config/database.php의 키 이름을 schema에서 search_path로 변경해 주세요.
assertDeleted에서 assertModelMissing으로
영향도: 중간
모델의 삭제 확인에 사용하고 있던 assertDeleted는 assertModelMissing으로 대체해 주세요.
lang 디렉터리 이동
영향도: 중간
신규 Laravel 9 애플리케이션에서는 언어 파일의 배치처가 resources/lang이 아니라 프로젝트 루트의 lang이 되었습니다. 기존 앱을 그대로 동작시키기만 하는 경우에는 큰 영향은 적지만, 새 스켈레톤에 맞추는 경우나 패키지에서 번역 파일을 공개하고 있는 경우에는 재검토해 주세요.
패스워드 룰 변경
영향도: 중간 현재 로그인 중인 사용자의 패스워드와 일치하는 것을 검증하는password 룰은 current_password로 리네임되었습니다.
when / unless 메서드의 변경
영향도: 중간
Laravel 8에서는 when이나 unless에 클로저를 전달하면 그 클로저 자체가 truthy로 평가되기 때문에 의도치 않게 조건 분기가 실행되는 경우가 있었습니다. Laravel 9에서는 클로저가 실행되고, 그 반환값이 조건으로 사용됩니다.
미검증 배열 키의 취급
영향도: 중간 Laravel 9에서는validated()가 반환하는 배열에서 미검증의 배열 키가 항상 제외됩니다. Laravel 8의 호환 동작을 유지하고 싶은 경우에만, includeUnvalidatedArrayKeys()를 명시적으로 호출해 주세요.
정리
Laravel 8에서 9로의 업그레이드에서는 PHP 8.0.2로의 갱신, Symfony Mailer로의 마이그레이션, Flysystem 3.x로의 대응이 중심입니다. 메일 송신 · 스토리지 · 독자 캐스트 · 테스트 헬퍼를 먼저 점검하면, 업그레이드 후의 불편을 줄일 수 있습니다.| 변경점 | 영향도 | 대응 |
|---|---|---|
PHP 8.0.2 / laravel/framework:^9.0 | 높음 | composer.json과 실행 환경 갱신 |
| Symfony Mailer로의 마이그레이션 | 높음 | SwiftMailer 계 API와 mail 설정 확인 |
| Flysystem 3.x | 높음 | Storage의 반환값 · 예외 · 드라이버 의존관계 확인 |
BelongsToMany의 upsert 계 메서드 | 중간 | 검색 대상이 관련 모델의 테이블이 된 점 확인 |
Custom Casts와 null | 중간 | set()이 null을 받아도 부서지지 않도록 수정 |
| HTTP 클라이언트의 30초 타임아웃 | 중간 | 필요한 요청만 timeout()을 명시 |
assertDeleted 폐지 | 중간 | assertModelMissing()으로 대체 |
lang 디렉터리 이동 | 중간 | 고정 경로 참조를 app()->langPath()로 변경 |
password 룰 변경 | 중간 | current_password로 갱신 |
| 미검증 배열 키의 제외 | 중간 | 필요 시에만 includeUnvalidatedArrayKeys() 이용 |