Eloquent: 시작하기
소개
라라벨은 데이터베이스와 상호 작용하는 것을 즐겁게 만들어주는 객체-관계 매퍼(ORM)인 Eloquent를 포함합니다. Eloquent를 사용할 때, 각 데이터베이스 테이블은 해당 테이블과 상호 작용하는 데 사용되는 "모델"을 갖습니다. 데이터베이스 테이블에서 레코드를 검색하는 것 외에도, Eloquent 모델을 사용하면 테이블에서 레코드를 삽입, 업데이트 및 삭제할 수도 있습니다.
시작하기 전에, 애플리케이션의 config/database.php 구성 파일에서 데이터베이스 연결을 구성해야 합니다. 데이터베이스 구성에 대한 자세한 내용은 데이터베이스 구성 문서를 참조하십시오.
Laravel Bootcamp
라라벨을 처음 사용하는 경우, Laravel Bootcamp으로 자유롭게 이동하십시오. Laravel Bootcamp는 Eloquent를 사용하여 첫 번째 라라벨 애플리케이션을 구축하는 과정을 안내합니다. 라라벨과 Eloquent가 제공하는 모든 것을 둘러보는 좋은 방법입니다.
모델 클래스 생성
시작하려면 Eloquent 모델을 만들어 보겠습니다. 모델은 일반적으로 app\Models 디렉토리에 있으며 Illuminate\Database\Eloquent\Model 클래스를 확장합니다. make:model Artisan 명령어를 사용하여 새 모델을 생성할 수 있습니다:
php artisan make:model Flight
모델을 생성할 때 데이터베이스 마이그레이션을 함께 생성하고 싶다면 --migration 또는 -m 옵션을 사용할 수 있습니다.
php artisan make:model Flight --migration
모델을 생성할 때 팩토리, 시더, 정책, 컨트롤러, 폼 요청과 같은 다양한 유형의 클래스를 생성할 수도 있습니다. 또한 이러한 옵션을 결합하여 여러 클래스를 한 번에 생성할 수 있습니다.
# 모델과 FlightFactory 클래스 생성...php artisan make:model Flight --factoryphp artisan make:model Flight -f # 모델과 FlightSeeder 클래스 생성...php artisan make:model Flight --seedphp artisan make:model Flight -s # 모델과 FlightController 클래스 생성...php artisan make:model Flight --controllerphp artisan make:model Flight -c # 모델, FlightController 리소스 클래스 및 폼 요청 클래스 생성...php artisan make:model Flight --controller --resource --requestsphp artisan make:model Flight -crR # 모델과 FlightPolicy 클래스 생성...php artisan make:model Flight --policy # 모델, 마이그레이션, 팩토리, 시더 및 컨트롤러 생성...php artisan make:model Flight -mfsc # 모델, 마이그레이션, 팩토리, 시더, 정책, 컨트롤러 및 폼 요청을 생성하는 단축키...php artisan make:model Flight --allphp artisan make:model Flight -a # 피벗 모델 생성...php artisan make:model Member --pivotphp artisan make:model Member -p
모델 검사하기
때로는 모델의 코드를 훑어보는 것만으로는 모델에서 사용 가능한 모든 속성과 관계를 파악하기 어려울 수 있습니다. 그 대신 모델의 모든 속성과 관계를 편리하게 개요로 제공하는 model:show Artisan 명령어를 사용해 보세요.
php artisan model:show Flight
Eloquent 모델 규칙
make:model 명령어로 생성된 모델은 app/Models 디렉토리에 위치하게 됩니다. 기본적인 모델 클래스를 살펴보고 Eloquent의 주요 규칙에 대해 논의해 보겠습니다.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ // ...}
테이블 이름
위의 예시를 보면, Eloquent에게 Flight 모델에 해당하는 데이터베이스 테이블을 알려주지 않았다는 것을 알 수 있습니다. 규칙에 따라, 클래스 이름의 "스네이크 케이스(snake case)" 복수형 이름이 명시적으로 지정되지 않은 경우 테이블 이름으로 사용됩니다. 따라서 이 경우 Eloquent는 Flight 모델이 flights 테이블에 레코드를 저장한다고 가정하고, AirTrafficController 모델은 air_traffic_controllers 테이블에 레코드를 저장한다고 가정합니다.
모델에 해당하는 데이터베이스 테이블이 이 규칙에 맞지 않으면, 모델의 table 속성을 정의하여 모델의 테이블 이름을 수동으로 지정할 수 있습니다.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * 모델과 연결된 테이블. * * @var string */ protected $table = 'my_flights';}
기본 키
Eloquent는 또한 각 모델에 해당하는 데이터베이스 테이블에 id라는 이름의 기본 키 열이 있다고 가정합니다. 필요한 경우, 모델의 기본 키 역할을 하는 다른 열을 지정하기 위해 모델에 protected $primaryKey 속성을 정의할 수 있습니다.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * 테이블과 연결된 기본 키. * * @var string */ protected $primaryKey = 'flight_id';}
또한, Eloquent는 기본 키가 증가하는 정수 값이라고 가정합니다. 이는 Eloquent가 자동으로 기본 키를 정수로 형변환한다는 것을 의미합니다. 증가하지 않거나 숫자가 아닌 기본 키를 사용하려면 모델에 false로 설정된 public $incrementing 속성을 정의해야 합니다.
<?php class Flight extends Model{ /** * 모델의 ID가 자동 증가하는지 여부를 나타냅니다. * * @var bool */ public $incrementing = false;}
모델의 기본 키가 정수가 아니면 모델에 protected $keyType 속성을 정의해야 합니다. 이 속성의 값은 string이어야 합니다.
<?php class Flight extends Model{ /** * 기본 키 ID의 데이터 유형입니다. * * @var string */ protected $keyType = 'string';}
"복합" 기본 키
Eloquent는 각 모델이 기본 키 역할을 할 수 있는 고유하게 식별 가능한 "ID"를 하나 이상 가져야 합니다. "복합" 기본 키는 Eloquent 모델에서 지원되지 않습니다. 그러나 테이블의 고유하게 식별 가능한 기본 키 외에도 데이터베이스 테이블에 추가적인 다중 열, 고유 인덱스를 자유롭게 추가할 수 있습니다.
UUID 및 ULID 키
Eloquent 모델의 기본 키로 자동 증가하는 정수를 사용하는 대신 UUID를 사용할 수 있습니다. UUID는 36자 길이의 전역적으로 고유한 영숫자 식별자입니다.
모델이 자동 증가하는 정수 키 대신 UUID 키를 사용하도록 하려면 모델에서 Illuminate\Database\Eloquent\Concerns\HasUuids 트레이트를 사용할 수 있습니다. 물론 모델에 UUID에 해당하는 기본 키 열이 있는지 확인해야 합니다.
use Illuminate\Database\Eloquent\Concerns\HasUuids;use Illuminate\Database\Eloquent\Model; class Article extends Model{ use HasUuids; // ...} $article = Article::create(['title' => 'Traveling to Europe']); $article->id; // "8f8e8478-9035-4d23-b9a7-62f4d2612ce5"
기본적으로 HasUuids 트레이트는 모델에 대해 "정렬된" UUID를 생성합니다. 이러한 UUID는 사전순으로 정렬할 수 있으므로 인덱싱된 데이터베이스 스토리지에 더 효율적입니다.
모델에서 newUniqueId 메서드를 정의하여 특정 모델의 UUID 생성 프로세스를 재정의할 수 있습니다. 또한 모델에서 uniqueIds 메서드를 정의하여 어떤 열이 UUID를 받아야 하는지 지정할 수 있습니다.
use Ramsey\Uuid\Uuid; /** * 모델에 대한 새로운 UUID를 생성합니다. */public function newUniqueId(): string{ return (string) Uuid::uuid4();} /** * 고유 식별자를 받아야 하는 열을 가져옵니다. * * @return array<int, string> */public function uniqueIds(): array{ return ['id', 'discount_code'];}
원하는 경우 UUID 대신 "ULID"를 사용할 수 있습니다. ULID는 UUID와 유사하지만 길이는 26자에 불과합니다. 정렬된 UUID와 마찬가지로 ULID는 효율적인 데이터베이스 인덱싱을 위해 사전순으로 정렬할 수 있습니다. ULID를 사용하려면 모델에서 Illuminate\Database\Eloquent\Concerns\HasUlids 트레이트를 사용해야 합니다. 또한 모델에 ULID에 해당하는 기본 키 열이 있는지 확인해야 합니다.
use Illuminate\Database\Eloquent\Concerns\HasUlids;use Illuminate\Database\Eloquent\Model; class Article extends Model{ use HasUlids; // ...} $article = Article::create(['title' => 'Traveling to Asia']); $article->id; // "01gd4d3tgrrfqeda94gdbtdk5c"
타임스탬프
기본적으로 Eloquent는 모델에 해당하는 데이터베이스 테이블에 created_at 및 updated_at 열이 존재해야 한다고 예상합니다. Eloquent는 모델이 생성되거나 업데이트될 때 이러한 열의 값을 자동으로 설정합니다. 이러한 열이 Eloquent에 의해 자동 관리되는 것을 원하지 않으면 모델에 false 값으로 설정된 $timestamps 속성을 정의해야 합니다.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * 모델이 타임스탬프를 사용해야 하는지 여부를 나타냅니다. * * @var bool */ public $timestamps = false;}
모델의 타임스탬프 형식을 사용자 지정해야 하는 경우 모델에 $dateFormat 속성을 설정합니다. 이 속성은 모델이 배열 또는 JSON으로 직렬화될 때 날짜 속성이 데이터베이스에 저장되는 방식과 형식을 결정합니다.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * 모델의 날짜 열의 저장 형식입니다. * * @var string */ protected $dateFormat = 'U';}
타임스탬프를 저장하는 데 사용되는 열의 이름을 사용자 지정해야 하는 경우 모델에 CREATED_AT 및 UPDATED_AT 상수를 정의할 수 있습니다.
<?php class Flight extends Model{ const CREATED_AT = 'creation_date'; const UPDATED_AT = 'updated_date';}
모델의 updated_at 타임스탬프를 수정하지 않고 모델 작업을 수행하려면 withoutTimestamps 메서드에 제공된 클로저 내에서 모델을 조작할 수 있습니다.
Model::withoutTimestamps(fn () => $post->increment('reads'));
데이터베이스 연결
기본적으로 모든 Eloquent 모델은 애플리케이션에 구성된 기본 데이터베이스 연결을 사용합니다. 특정 모델과 상호 작용할 때 사용해야 하는 다른 연결을 지정하려면 모델에 $connection 속성을 정의해야 합니다.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * 모델에서 사용해야 하는 데이터베이스 연결입니다. * * @var string */ protected $connection = 'mysql';}
기본 속성 값
기본적으로 새로 인스턴스화된 모델 인스턴스에는 속성 값이 포함되지 않습니다. 모델 속성에 대한 기본값을 정의하려면 모델에 $attributes 속성을 정의할 수 있습니다. $attributes 배열에 배치된 속성 값은 데이터베이스에서 읽은 것처럼 원시 "저장 가능한" 형식이어야 합니다.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * 속성에 대한 모델의 기본값입니다. * * @var array */ protected $attributes = [ 'options' => '[]', 'delayed' => false, ];}
Eloquent 엄격도 구성
Laravel은 다양한 상황에서 Eloquent의 동작과 "엄격도"를 구성할 수 있는 여러 가지 방법을 제공합니다.
첫째, preventLazyLoading 메서드는 지연 로딩을 방지해야 하는지 여부를 나타내는 선택적 부울 인수를 허용합니다. 예를 들어, 개발 환경에서만 지연 로딩을 비활성화하여 프로덕션 코드에 지연 로드된 관계가 실수로 있는 경우에도 프로덕션 환경이 정상적으로 계속 작동하도록 할 수 있습니다. 일반적으로 이 메서드는 애플리케이션의 AppServiceProvider의 boot 메서드에서 호출해야 합니다.
use Illuminate\Database\Eloquent\Model; /** * 모든 애플리케이션 서비스를 부트스트랩합니다. */public function boot(): void{ Model::preventLazyLoading(! $this->app->isProduction());}
또한, preventSilentlyDiscardingAttributes 메서드를 호출하여 채울 수 없는 속성을 채우려고 시도할 때 Laravel이 예외를 발생시키도록 지시할 수 있습니다. 이는 모델의 fillable 배열에 추가되지 않은 속성을 설정하려고 할 때 로컬 개발 중에 예기치 않은 오류를 방지하는 데 도움이 될 수 있습니다.
Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());
모델 검색
모델과 관련 데이터베이스 테이블을 생성했다면 데이터베이스에서 데이터를 검색할 준비가 된 것입니다. 각 Eloquent 모델을 강력한 쿼리 빌더로 생각하여 모델과 연결된 데이터베이스 테이블을 유연하게 쿼리할 수 있습니다. 모델의 all 메서드는 모델과 연결된 데이터베이스 테이블에서 모든 레코드를 검색합니다.
use App\Models\Flight; foreach (Flight::all() as $flight) { echo $flight->name;}
쿼리 빌드
Eloquent all 메서드는 모델 테이블의 모든 결과를 반환합니다. 그러나 각 Eloquent 모델은 쿼리 빌더 역할을 하므로 쿼리에 추가 제약 조건을 추가한 다음 get 메서드를 호출하여 결과를 검색할 수 있습니다.
$flights = Flight::where('active', 1) ->orderBy('name') ->take(10) ->get();
Eloquent 모델은 쿼리 빌더이므로 Laravel의 쿼리 빌더에서 제공하는 모든 메서드를 검토해야 합니다. Eloquent 쿼리를 작성할 때 이러한 메서드를 사용할 수 있습니다.
모델 새로 고침
데이터베이스에서 검색된 Eloquent 모델 인스턴스가 이미 있는 경우 fresh 및 refresh 메서드를 사용하여 모델을 "새로 고칠" 수 있습니다. fresh 메서드는 데이터베이스에서 모델을 다시 검색합니다. 기존 모델 인스턴스는 영향을 받지 않습니다.
$flight = Flight::where('number', 'FR 900')->first(); $freshFlight = $flight->fresh();
refresh 메서드는 데이터베이스의 최신 데이터를 사용하여 기존 모델을 다시 채웁니다. 또한 로드된 모든 관계도 새로 고쳐집니다.
$flight = Flight::where('number', 'FR 900')->first(); $flight->number = 'FR 456'; $flight->refresh(); $flight->number; // "FR 900"
컬렉션
살펴본 바와 같이 all 및 get과 같은 Eloquent 메서드는 데이터베이스에서 여러 레코드를 검색합니다. 그러나 이러한 메서드는 일반 PHP 배열을 반환하지 않습니다. 대신 Illuminate\Database\Eloquent\Collection의 인스턴스가 반환됩니다.
Eloquent Collection 클래스는 Laravel의 기본 Illuminate\Support\Collection 클래스를 확장하며, 데이터 컬렉션과 상호 작용하기 위한 다양한 유용한 메서드를 제공합니다. 예를 들어 reject 메서드를 사용하여 호출된 클로저의 결과를 기반으로 컬렉션에서 모델을 제거할 수 있습니다.
$flights = Flight::where('destination', 'Paris')->get(); $flights = $flights->reject(function (Flight $flight) { return $flight->cancelled;});
라라벨의 기본 컬렉션 클래스에서 제공하는 메서드 외에도, Eloquent 컬렉션 클래스는 Eloquent 모델 컬렉션과 상호 작용하기 위해 특별히 고안된 몇 가지 추가 메서드를 제공합니다.
라라벨의 모든 컬렉션은 PHP의 iterable 인터페이스를 구현하므로, 컬렉션을 배열처럼 반복 처리할 수 있습니다:
foreach ($flights as $flight) { echo $flight->name;}
결과 청크 처리
all 또는 get 메서드를 통해 수만 개의 Eloquent 레코드를 로드하려고 하면 애플리케이션의 메모리가 부족할 수 있습니다. 이러한 메서드를 사용하는 대신 chunk 메서드를 사용하여 많은 수의 모델을 보다 효율적으로 처리할 수 있습니다.
chunk 메서드는 Eloquent 모델의 하위 집합을 검색하여 처리를 위해 클로저에 전달합니다. 현재 Eloquent 모델 청크만 한 번에 검색되므로 chunk 메서드는 많은 수의 모델을 사용할 때 메모리 사용량을 크게 줄여줍니다.
use App\Models\Flight;use Illuminate\Database\Eloquent\Collection; Flight::chunk(200, function (Collection $flights) { foreach ($flights as $flight) { // ... }});
chunk 메서드에 전달되는 첫 번째 인수는 "청크"당 수신하려는 레코드 수입니다. 두 번째 인수로 전달되는 클로저는 데이터베이스에서 검색된 각 청크에 대해 호출됩니다. 클로저에 전달된 각 레코드 청크를 검색하기 위해 데이터베이스 쿼리가 실행됩니다.
결과를 반복하는 동안 업데이트할 열을 기반으로 chunk 메서드의 결과를 필터링하는 경우 chunkById 메서드를 사용해야 합니다. 이러한 시나리오에서 chunk 메서드를 사용하면 예기치 않거나 일관성 없는 결과가 발생할 수 있습니다. 내부적으로 chunkById 메서드는 항상 이전 청크의 마지막 모델보다 큰 id 열을 가진 모델을 검색합니다.
Flight::where('departed', true) ->chunkById(200, function (Collection $flights) { $flights->each->update(['departed' => false]); }, column: 'id');
chunkById 및 lazyById 메서드는 실행 중인 쿼리에 자체 "where" 조건을 추가하므로 일반적으로 클로저 내에서 고유한 조건을 논리적으로 그룹화해야 합니다.
Flight::where(function ($query) { $query->where('delayed', true)->orWhere('cancelled', true);})->chunkById(200, function (Collection $flights) { $flights->each->update([ 'departed' => false, 'cancelled' => true ]);}, column: 'id');
Lazy 컬렉션을 사용한 청크 처리
lazy 메서드는 내부적으로 쿼리를 청크 단위로 실행한다는 점에서 the chunk method와 유사하게 작동합니다. 그러나 각 청크를 콜백에 직접 전달하는 대신 lazy 메서드는 Eloquent 모델의 평면화된 LazyCollection을 반환하여 결과를 단일 스트림으로 조작할 수 있습니다.
use App\Models\Flight; foreach (Flight::lazy() as $flight) { // ...}
만약 결과를 반복 처리하는 동안 업데이트할 컬럼을 기반으로 lazy 메서드의 결과를 필터링하는 경우, lazyById 메서드를 사용해야 합니다. 내부적으로 lazyById 메서드는 이전 청크의 마지막 모델보다 큰 id 컬럼을 가진 모델을 항상 검색합니다.
Flight::where('departed', true) ->lazyById(200, column: 'id') ->each->update(['departed' => false]);
lazyByIdDesc 메서드를 사용하여 id의 내림차순을 기준으로 결과를 필터링할 수 있습니다.
커서
lazy 메서드와 유사하게 cursor 메서드는 수만 개의 Eloquent 모델 레코드를 반복 처리할 때 애플리케이션의 메모리 소비를 크게 줄이는 데 사용할 수 있습니다.
cursor 메서드는 단일 데이터베이스 쿼리만 실행합니다. 그러나 개별 Eloquent 모델은 실제로 반복 처리될 때까지 하이드레이션되지 않습니다. 따라서 커서를 반복하는 동안 한 번에 하나의 Eloquent 모델만 메모리에 유지됩니다.
cursor 메서드는 한 번에 하나의 Eloquent 모델만 메모리에 저장하므로 관계를 즉시 로드할 수 없습니다. 관계를 즉시 로드해야 하는 경우, 대신 lazy 메서드를 사용하는 것을 고려하십시오.
내부적으로 cursor 메서드는 PHP 제너레이터를 사용하여 이 기능을 구현합니다.
use App\Models\Flight; foreach (Flight::where('destination', 'Zurich')->cursor() as $flight) { // ...}
cursor는 Illuminate\Support\LazyCollection 인스턴스를 반환합니다. Lazy 컬렉션을 사용하면 한 번에 하나의 모델만 메모리에 로드하면서 일반적인 Laravel 컬렉션에서 사용할 수 있는 많은 컬렉션 메서드를 사용할 수 있습니다.
use App\Models\User; $users = User::cursor()->filter(function (User $user) { // 사용자 ID가 500보다 큰 사용자를 필터링합니다. return $user->id > 500;}); // 필터링된 사용자들을 순회합니다.foreach ($users as $user) { // 각 사용자의 ID를 출력합니다. echo $user->id;}
cursor 메서드는 일반 쿼리보다 훨씬 적은 메모리를 사용하지만(한 번에 하나의 Eloquent 모델만 메모리에 유지하므로), 결국에는 메모리가 부족해질 수 있습니다. 이는 PHP의 PDO 드라이버가 내부적으로 모든 원시 쿼리 결과를 버퍼에 캐시하기 때문입니다. 매우 많은 수의 Eloquent 레코드를 처리하는 경우, 대신 lazy 메서드를 사용하는 것을 고려해 보세요.
고급 서브쿼리
서브쿼리 선택
Eloquent는 고급 서브쿼리 지원 기능도 제공하며, 이를 통해 단일 쿼리에서 관련 테이블의 정보를 가져올 수 있습니다. 예를 들어, 비행 destinations 테이블과 목적지로 가는 flights 테이블이 있다고 가정해 보겠습니다. flights 테이블에는 비행기가 목적지에 도착한 시간을 나타내는 arrived_at 열이 있습니다.
쿼리 빌더의 select 및 addSelect 메서드에서 사용할 수 있는 서브쿼리 기능을 사용하여 단일 쿼리로 모든 destinations와 해당 목적지에 가장 최근에 도착한 비행기 이름을 선택할 수 있습니다.
use App\Models\Destination;use App\Models\Flight; return Destination::addSelect(['last_flight' => Flight::select('name') ->whereColumn('destination_id', 'destinations.id') ->orderByDesc('arrived_at') ->limit(1)])->get();
서브쿼리 정렬
또한, 쿼리 빌더의 orderBy 함수는 서브쿼리를 지원합니다. 비행기 예시를 계속 사용하면서, 이 기능을 사용하여 마지막 비행기가 목적지에 도착한 시간을 기준으로 모든 목적지를 정렬할 수 있습니다. 또한, 이 작업은 단일 데이터베이스 쿼리를 실행하는 동안 수행할 수 있습니다.
return Destination::orderByDesc( Flight::select('arrived_at') ->whereColumn('destination_id', 'destinations.id') ->orderByDesc('arrived_at') ->limit(1))->get();
단일 모델 / 집계 검색
주어진 쿼리와 일치하는 모든 레코드를 검색하는 것 외에도 find, first 또는 firstWhere 메서드를 사용하여 단일 레코드를 검색할 수도 있습니다. 이러한 메서드는 모델의 컬렉션을 반환하는 대신 단일 모델 인스턴스를 반환합니다.
use App\Models\Flight; // 기본 키로 모델 검색...$flight = Flight::find(1); // 쿼리 제약 조건과 일치하는 첫 번째 모델 검색...$flight = Flight::where('active', 1)->first(); // 쿼리 제약 조건과 일치하는 첫 번째 모델을 검색하는 대안...$flight = Flight::firstWhere('active', 1);
결과가 없을 경우 다른 작업을 수행할 수도 있습니다. findOr 및 firstOr 메서드는 단일 모델 인스턴스를 반환하거나, 결과가 없을 경우 주어진 클로저를 실행합니다. 클로저에서 반환된 값은 메서드의 결과로 간주됩니다.
$flight = Flight::findOr(1, function () { // ...}); $flight = Flight::where('legs', '>', 3)->firstOr(function () { // ...});
찾을 수 없음 예외
모델을 찾을 수 없을 경우 예외를 발생시키고 싶을 수 있습니다. 이는 특히 라우트 또는 컨트롤러에서 유용합니다. findOrFail 및 firstOrFail 메서드는 쿼리의 첫 번째 결과를 검색합니다. 그러나 결과를 찾을 수 없는 경우 Illuminate\Database\Eloquent\ModelNotFoundException이 발생합니다.
$flight = Flight::findOrFail(1); $flight = Flight::where('legs', '>', 3)->firstOrFail();
ModelNotFoundException을 잡지 못하면 404 HTTP 응답이 자동으로 클라이언트로 다시 전송됩니다.
use App\Models\Flight; Route::get('/api/flights/{id}', function (string $id) { return Flight::findOrFail($id);});
모델 검색 또는 생성
firstOrCreate 메서드는 주어진 열/값 쌍을 사용하여 데이터베이스 레코드를 찾으려고 시도합니다. 데이터베이스에서 모델을 찾을 수 없는 경우 첫 번째 배열 인수와 선택적 두 번째 배열 인수를 병합하여 생성된 속성으로 레코드가 삽입됩니다.
firstOrNew 메서드는 firstOrCreate와 마찬가지로 주어진 속성과 일치하는 데이터베이스의 레코드를 찾으려고 시도합니다. 그러나 모델을 찾을 수 없는 경우 새 모델 인스턴스가 반환됩니다. firstOrNew에서 반환된 모델은 아직 데이터베이스에 저장되지 않았습니다. 이를 저장하려면 save 메서드를 수동으로 호출해야 합니다.
use App\Models\Flight; // 이름으로 비행기를 검색하거나 존재하지 않으면 생성...$flight = Flight::firstOrCreate([ 'name' => 'London to Paris']); // 이름으로 비행기를 검색하거나 이름, 지연 및 도착 시간 속성으로 생성...$flight = Flight::firstOrCreate( ['name' => 'London to Paris'], ['delayed' => 1, 'arrival_time' => '11:30']); // 이름으로 비행기를 검색하거나 새 Flight 인스턴스화...$flight = Flight::firstOrNew([ 'name' => 'London to Paris']); // 이름으로 비행기를 검색하거나 이름, 지연 및 도착 시간 속성으로 인스턴스화...$flight = Flight::firstOrNew( ['name' => 'Tokyo to Sydney'], ['delayed' => 1, 'arrival_time' => '11:30']);
집계 검색
Eloquent 모델과 상호 작용할 때 Laravel 쿼리 빌더에서 제공하는 count, sum, max 및 기타 집계 메서드를 사용할 수도 있습니다. 예상대로 이러한 메서드는 Eloquent 모델 인스턴스 대신 스칼라 값을 반환합니다.
$count = Flight::where('active', 1)->count(); $max = Flight::where('active', 1)->max('price');
모델 삽입 및 업데이트
삽입
물론 Eloquent를 사용할 때 데이터베이스에서 모델을 검색하기만 하면 되는 것은 아닙니다. 새 레코드를 삽입해야 할 수도 있습니다. 다행히 Eloquent는 이를 간단하게 처리합니다. 데이터베이스에 새 레코드를 삽입하려면 새 모델 인스턴스를 인스턴스화하고 모델에 속성을 설정해야 합니다. 그런 다음 모델 인스턴스에서 save 메서드를 호출합니다.
<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller;use App\Models\Flight;use Illuminate\Http\RedirectResponse;use Illuminate\Http\Request; class FlightController extends Controller{ /** * 데이터베이스에 새 비행기를 저장합니다. */ public function store(Request $request): RedirectResponse { // 요청 유효성 검사... $flight = new Flight; $flight->name = $request->name; $flight->save(); return redirect('/flights'); }}
이 예제에서는 들어오는 HTTP 요청의 name 필드를 App\Models\Flight 모델 인스턴스의 name 속성에 할당합니다. save 메서드를 호출하면 레코드가 데이터베이스에 삽입됩니다. 모델의 created_at 및 updated_at 타임스탬프는 save 메서드가 호출될 때 자동으로 설정되므로 수동으로 설정할 필요가 없습니다.
또는 create 메서드를 사용하여 단일 PHP 문을 사용하여 새 모델을 "저장"할 수도 있습니다. 삽입된 모델 인스턴스는 create 메서드에서 반환됩니다.
use App\Models\Flight; $flight = Flight::create([ 'name' => 'London to Paris',]);
그러나 create 메서드를 사용하기 전에 모델 클래스에 fillable 또는 guarded 속성을 지정해야 합니다. 이러한 속성은 기본적으로 모든 Eloquent 모델이 대량 할당 취약점으로부터 보호되므로 필요합니다. 대량 할당에 대한 자세한 내용은 대량 할당 문서를 참조하세요.
업데이트
save 메서드는 데이터베이스에 이미 존재하는 모델을 업데이트하는 데에도 사용할 수 있습니다. 모델을 업데이트하려면 모델을 검색하고 업데이트하려는 속성을 설정해야 합니다. 그런 다음 모델의 save 메서드를 호출해야 합니다. 다시 말하지만 updated_at 타임스탬프가 자동으로 업데이트되므로 수동으로 값을 설정할 필요가 없습니다.
use App\Models\Flight; $flight = Flight::find(1); $flight->name = 'Paris to London'; $flight->save();
경우에 따라 기존 모델을 업데이트하거나 일치하는 모델이 없는 경우 새 모델을 생성해야 할 수 있습니다. firstOrCreate 메서드와 마찬가지로 updateOrCreate 메서드는 모델을 유지하므로 save 메서드를 수동으로 호출할 필요가 없습니다.
아래 예제에서 departure 위치가 Oakland이고 destination 위치가 San Diego인 비행기가 있는 경우 해당 price 및 discounted 열이 업데이트됩니다. 이러한 비행기가 없는 경우 첫 번째 인수 배열과 두 번째 인수 배열을 병합하여 생성된 속성을 가진 새 비행기가 생성됩니다.
$flight = Flight::updateOrCreate( ['departure' => 'Oakland', 'destination' => 'San Diego'], ['price' => 99, 'discounted' => 1]);
대량 업데이트
주어진 쿼리와 일치하는 모델에 대해 업데이트를 수행할 수도 있습니다. 이 예제에서는 active이고 destination이 San Diego인 모든 비행기가 지연된 것으로 표시됩니다.
Flight::where('active', 1) ->where('destination', 'San Diego') ->update(['delayed' => 1]);
update 메서드는 업데이트해야 하는 열을 나타내는 열과 값 쌍의 배열을 예상합니다. update 메서드는 영향을 받은 행 수를 반환합니다.
Eloquent를 통해 대량 업데이트를 실행할 때 업데이트된 모델에 대해 saving, saved, updating 및 updated 모델 이벤트가 발생하지 않습니다. 이는 대량 업데이트를 실행할 때 실제로 모델이 검색되지 않기 때문입니다.
속성 변경 검토
Eloquent는 모델의 내부 상태를 검사하고 모델이 원래 검색된 이후 속성이 변경된 방식을 확인하기 위해 isDirty, isClean 및 wasChanged 메서드를 제공합니다.
isDirty 메서드는 모델이 검색된 이후 모델의 속성이 변경되었는지 여부를 확인합니다. 특정 속성 이름 또는 속성 배열을 isDirty 메서드에 전달하여 속성이 "더티"인지 여부를 확인할 수 있습니다. isClean 메서드는 모델이 검색된 이후 속성이 변경되지 않은 상태인지 여부를 확인합니다. 이 메서드는 선택적 속성 인수를 허용합니다.
use App\Models\User; $user = User::create([ 'first_name' => 'Taylor', 'last_name' => 'Otwell', 'title' => 'Developer',]); $user->title = 'Painter'; $user->isDirty(); // true$user->isDirty('title'); // true$user->isDirty('first_name'); // false$user->isDirty(['first_name', 'title']); // true $user->isClean(); // false$user->isClean('title'); // false$user->isClean('first_name'); // true$user->isClean(['first_name', 'title']); // false $user->save(); $user->isDirty(); // false$user->isClean(); // true
wasChanged 메서드는 모델이 현재 요청 주기 내에서 마지막으로 저장되었을 때 변경된 속성이 있는지 여부를 확인합니다. 필요한 경우 속성 이름을 전달하여 특정 속성이 변경되었는지 확인할 수 있습니다.
$user = User::create([ 'first_name' => 'Taylor', 'last_name' => 'Otwell', 'title' => 'Developer',]); $user->title = 'Painter'; $user->save(); $user->wasChanged(); // true$user->wasChanged('title'); // true$user->wasChanged(['title', 'slug']); // true$user->wasChanged('first_name'); // false$user->wasChanged(['first_name', 'title']); // true
getOriginal 메서드는 모델이 검색된 이후 모델의 변경 사항에 관계없이 모델의 원래 속성을 포함하는 배열을 반환합니다. 필요한 경우 특정 속성의 원래 값을 가져오기 위해 특정 속성 이름을 전달할 수 있습니다.
$user = User::find(1); $user->name; // John $user->name = "Jack";$user->name; // Jack $user->getOriginal('name'); // John$user->getOriginal(); // 원래 속성의 배열...
대량 할당
create 메서드를 사용하여 단일 PHP 문으로 새 모델을 "저장"할 수 있습니다. 삽입된 모델 인스턴스는 메서드에서 반환됩니다.
use App\Models\Flight; $flight = Flight::create([ 'name' => 'London to Paris',]);
그러나 create 메서드를 사용하기 전에 모델 클래스에 fillable 또는 guarded 속성을 지정해야 합니다. 이러한 속성은 기본적으로 모든 Eloquent 모델이 대량 할당 취약점으로부터 보호되므로 필요합니다.
대량 할당 취약점은 사용자가 예상치 못한 HTTP 요청 필드를 전달하고 해당 필드가 예상하지 못한 데이터베이스의 열을 변경하는 경우에 발생합니다. 예를 들어, 악의적인 사용자가 HTTP 요청을 통해 is_admin 매개변수를 보내고 이 매개변수가 모델의 create 메서드에 전달되어 사용자가 스스로를 관리자로 승격시킬 수 있습니다.
시작하려면 대량 할당이 가능하도록 만들 모델 속성을 정의해야 합니다. 모델의 $fillable 속성을 사용하여 이 작업을 수행할 수 있습니다. 예를 들어 Flight 모델의 name 속성을 대량 할당 가능하게 만들어 보겠습니다.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * 대량 할당 가능한 속성입니다. * * @var array<int, string> */ protected $fillable = ['name'];}
대량 할당 가능한 속성을 지정한 후에는 create 메서드를 사용하여 데이터베이스에 새 레코드를 삽입할 수 있습니다. create 메서드는 새로 생성된 모델 인스턴스를 반환합니다.
$flight = Flight::create(['name' => 'London to Paris']);
이미 모델 인스턴스가 있는 경우 fill 메서드를 사용하여 속성 배열로 채울 수 있습니다.
$flight->fill(['name' => 'Amsterdam to Frankfurt']);
대량 할당 및 JSON 열
JSON 열을 할당할 때 각 열의 대량 할당 가능 키는 모델의 $fillable 배열에 지정해야 합니다. 보안을 위해 Laravel은 guarded 속성을 사용할 때 중첩된 JSON 속성 업데이트를 지원하지 않습니다.
/** * 대량 할당 가능한 속성입니다. * * @var array<int, string> */protected $fillable = [ 'options->enabled',];
대량 할당 허용
모든 속성을 대량 할당 가능하게 만들려면 모델의 $guarded 속성을 빈 배열로 정의할 수 있습니다. 모델을 보호하지 않기로 선택한 경우 Eloquent의 fill, create 및 update 메서드에 전달되는 배열을 항상 수작업으로 작성하도록 특별히 주의해야 합니다.
/** * 대량 할당할 수 없는 속성입니다. * * @var array<string>|bool */protected $guarded = [];
대량 할당 예외
기본적으로 $fillable 배열에 포함되지 않은 속성은 대량 할당 작업을 수행할 때 자동으로 삭제됩니다. 프로덕션 환경에서는 이것이 예상되는 동작이지만, 로컬 개발 중에는 모델 변경 사항이 적용되지 않는 이유에 대한 혼란을 초래할 수 있습니다.
원하는 경우 preventSilentlyDiscardingAttributes 메서드를 호출하여 채울 수 없는 속성을 채우려고 할 때 Laravel이 예외를 발생시키도록 지시할 수 있습니다. 일반적으로 이 메서드는 애플리케이션의 AppServiceProvider 클래스의 boot 메서드에서 호출해야 합니다.
use Illuminate\Database\Eloquent\Model; /** * 모든 애플리케이션 서비스를 부트스트랩합니다. */public function boot(): void{ Model::preventSilentlyDiscardingAttributes($this->app->isLocal());}
Upsert
Eloquent의 upsert 메서드를 사용하여 단일 원자적 작업으로 레코드를 업데이트하거나 생성할 수 있습니다. 메서드의 첫 번째 인수는 삽입하거나 업데이트할 값으로 구성되고, 두 번째 인수는 연결된 테이블 내에서 레코드를 고유하게 식별하는 열을 나열합니다. 메서드의 세 번째이자 마지막 인수는 데이터베이스에 일치하는 레코드가 이미 있는 경우 업데이트해야 하는 열의 배열입니다. upsert 메서드는 모델에서 타임스탬프가 활성화된 경우 created_at 및 updated_at 타임스탬프를 자동으로 설정합니다.
Flight::upsert([ ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99], ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]], uniqueBy: ['departure', 'destination'], update: ['price']);
SQL Server를 제외한 모든 데이터베이스는 upsert 메서드의 두 번째 인수에 있는 열에 "기본" 또는 "고유" 인덱스가 있어야 합니다. 또한 MariaDB 및 MySQL 데이터베이스 드라이버는 upsert 메서드의 두 번째 인수를 무시하고 테이블의 "기본" 및 "고유" 인덱스를 사용하여 기존 레코드를 항상 감지합니다.
모델 삭제
모델을 삭제하려면 모델 인스턴스에서 delete 메서드를 호출하면 됩니다.
use App\Models\Flight; $flight = Flight::find(1); $flight->delete();
truncate 메서드를 호출하여 모델과 관련된 모든 데이터베이스 레코드를 삭제할 수 있습니다. truncate 작업은 모델과 관련된 테이블에서 자동 증가 ID도 재설정합니다.
Flight::truncate();
기본 키로 기존 모델 삭제
위의 예에서는 delete 메서드를 호출하기 전에 데이터베이스에서 모델을 검색하고 있습니다. 그러나 모델의 기본 키를 알고 있는 경우 destroy 메서드를 호출하여 명시적으로 검색하지 않고도 모델을 삭제할 수 있습니다. 단일 기본 키를 허용하는 것 외에도 destroy 메서드는 여러 기본 키, 기본 키 배열 또는 기본 키의 컬렉션을 허용합니다.
Flight::destroy(1); Flight::destroy(1, 2, 3); Flight::destroy([1, 2, 3]); Flight::destroy(collect([1, 2, 3]));
소프트 삭제 모델을 사용하는 경우 forceDestroy 메서드를 통해 모델을 영구적으로 삭제할 수 있습니다.
Flight::forceDestroy(1);
destroy 메서드는 각 모델을 개별적으로 로드하고 delete 메서드를 호출하여 각 모델에 대해 deleting 및 deleted 이벤트가 적절하게 디스패치되도록 합니다.
쿼리를 사용하여 모델 삭제
물론 Eloquent 쿼리를 빌드하여 쿼리의 기준과 일치하는 모든 모델을 삭제할 수 있습니다. 이 예에서는 비활성으로 표시된 모든 비행기를 삭제합니다. 대량 업데이트와 마찬가지로 대량 삭제는 삭제된 모델에 대한 모델 이벤트를 디스패치하지 않습니다.
$deleted = Flight::where('active', 0)->delete();
Eloquent를 통해 대량 삭제 문을 실행할 때 삭제된 모델에 대해 deleting 및 deleted 모델 이벤트가 디스패치되지 않습니다. 이는 삭제 문을 실행할 때 실제로 모델이 검색되지 않기 때문입니다.
소프트 삭제
데이터베이스에서 실제로 레코드를 제거하는 것 외에도 Eloquent는 모델을 "소프트 삭제"할 수도 있습니다. 모델이 소프트 삭제되면 실제로 데이터베이스에서 제거되지 않습니다. 대신 모델이 "삭제"된 날짜와 시간을 나타내는 deleted_at 속성이 모델에 설정됩니다. 모델에 대한 소프트 삭제를 활성화하려면 모델에 Illuminate\Database\Eloquent\SoftDeletes 트레이트를 추가합니다.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\SoftDeletes; class Flight extends Model{ use SoftDeletes;}
SoftDeletes 트레이트는 자동으로 deleted_at 속성을 DateTime / Carbon 인스턴스로 캐스팅합니다.
또한 데이터베이스 테이블에 deleted_at 열을 추가해야 합니다. Laravel 스키마 빌더에는 이 열을 만드는 헬퍼 메서드가 포함되어 있습니다.
use Illuminate\Database\Schema\Blueprint;use Illuminate\Support\Facades\Schema; Schema::table('flights', function (Blueprint $table) { $table->softDeletes();}); Schema::table('flights', function (Blueprint $table) { $table->dropSoftDeletes();});
이제 모델에서 delete 메서드를 호출하면 deleted_at 열이 현재 날짜 및 시간으로 설정됩니다. 그러나 모델의 데이터베이스 레코드는 테이블에 그대로 남아 있습니다. 소프트 삭제를 사용하는 모델을 쿼리할 때 소프트 삭제된 모델은 모든 쿼리 결과에서 자동으로 제외됩니다.
주어진 모델 인스턴스가 소프트 삭제되었는지 여부를 확인하려면 trashed 메서드를 사용할 수 있습니다.
if ($flight->trashed()) { // ...}
소프트 삭제된 모델 복원
경우에 따라 소프트 삭제된 모델을 "삭제 취소"할 수도 있습니다. 소프트 삭제된 모델을 복원하려면 모델 인스턴스에서 restore 메서드를 호출하면 됩니다. restore 메서드는 모델의 deleted_at 열을 null로 설정합니다.
$flight->restore();
쿼리에서 restore 메서드를 사용하여 여러 모델을 복원할 수도 있습니다. 다시 말하지만, 다른 "대량" 작업과 마찬가지로 복원된 모델에 대한 모델 이벤트는 디스패치되지 않습니다.
Flight::withTrashed() ->where('airline_id', 1) ->restore();
관계 쿼리를 빌드할 때 restore 메서드를 사용할 수도 있습니다.
$flight->history()->restore();
모델 영구 삭제
경우에 따라 데이터베이스에서 모델을 완전히 제거해야 할 수도 있습니다. forceDelete 메서드를 사용하여 소프트 삭제된 모델을 데이터베이스 테이블에서 영구적으로 제거할 수 있습니다.
$flight->forceDelete();
Eloquent 관계 쿼리를 빌드할 때 forceDelete 메서드를 사용할 수도 있습니다.
$flight->history()->forceDelete();
소프트 삭제된 모델 쿼리
소프트 삭제된 모델 포함
위에서 언급했듯이 소프트 삭제된 모델은 쿼리 결과에서 자동으로 제외됩니다. 그러나 쿼리에서 withTrashed 메서드를 호출하여 소프트 삭제된 모델을 쿼리 결과에 강제로 포함시킬 수 있습니다.
use App\Models\Flight; $flights = Flight::withTrashed() ->where('account_id', 1) ->get();
관계 쿼리를 빌드할 때 withTrashed 메서드를 호출할 수도 있습니다.
$flight->history()->withTrashed()->get();
소프트 삭제된 모델만 검색
onlyTrashed 메서드는 소프트 삭제된 모델만 검색합니다.
$flights = Flight::onlyTrashed() ->where('airline_id', 1) ->get();
모델 정리
경우에 따라 더 이상 필요하지 않은 모델을 주기적으로 삭제해야 할 수 있습니다. 이를 수행하려면 주기적으로 정리하려는 모델에 Illuminate\Database\Eloquent\Prunable 또는 Illuminate\Database\Eloquent\MassPrunable 트레이트를 추가하면 됩니다. 모델에 트레이트 중 하나를 추가한 후에는 더 이상 필요하지 않은 모델을 확인하는 Eloquent 쿼리 빌더를 반환하는 prunable 메서드를 구현합니다.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Prunable; class Flight extends Model{ use Prunable; /** * 정리 가능한 모델 쿼리를 가져옵니다. */ public function prunable(): Builder { return static::where('created_at', '<=', now()->subMonth()); }}
모델을 Prunable로 표시할 때 모델에 pruning 메서드를 정의할 수도 있습니다. 이 메서드는 모델이 삭제되기 전에 호출됩니다. 이 메서드는 모델이 데이터베이스에서 영구적으로 제거되기 전에 저장된 파일과 같이 모델과 관련된 추가 리소스를 삭제하는 데 유용할 수 있습니다.
/** * 정리를 위해 모델을 준비합니다. */protected function pruning(): void{ // ...}
정리 가능한 모델을 구성한 후에는 애플리케이션의 routes/console.php 파일에서 model:prune Artisan 명령을 예약해야 합니다. 이 명령을 실행해야 하는 적절한 간격을 자유롭게 선택할 수 있습니다.
use Illuminate\Support\Facades\Schedule; Schedule::command('model:prune')->daily();
내부적으로 model:prune 명령은 애플리케이션의 app/Models 디렉터리 내에서 "Prunable" 모델을 자동으로 감지합니다. 모델이 다른 위치에 있는 경우 --model 옵션을 사용하여 모델 클래스 이름을 지정할 수 있습니다.
Schedule::command('model:prune', [ '--model' => [Address::class, Flight::class],])->daily();
감지된 다른 모든 모델을 정리하는 동안 특정 모델을 정리에서 제외하려면 --except 옵션을 사용할 수 있습니다.
Schedule::command('model:prune', [ '--except' => [Address::class, Flight::class],])->daily();
--pretend 옵션을 사용하여 model:prune 명령을 실행하여 prunable 쿼리를 테스트할 수 있습니다. 시뮬레이션 시 model:prune 명령은 실제로 명령이 실행될 경우 정리될 레코드 수만 보고합니다.
php artisan model:prune --pretend
php artisan model:prune --pretend
소프트 삭제된 모델은 prunable 쿼리와 일치하는 경우 영구적으로 삭제(forceDelete)됩니다.
대량 가지치기
모델이 Illuminate\Database\Eloquent\MassPrunable 트레이트로 표시되면, 모델은 대량 삭제 쿼리를 사용하여 데이터베이스에서 삭제됩니다. 따라서 pruning 메서드는 호출되지 않으며, deleting 및 deleted 모델 이벤트도 디스패치되지 않습니다. 이는 모델이 삭제되기 전에 실제로 검색되지 않기 때문이며, 따라서 가지치기 프로세스가 훨씬 더 효율적입니다.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\MassPrunable; class Flight extends Model{ use MassPrunable; /** * 가지치기 가능한 모델 쿼리를 가져옵니다. */ public function prunable(): Builder { return static::where('created_at', '<=', now()->subMonth()); }}
모델 복제
replicate 메서드를 사용하여 기존 모델 인스턴스의 저장되지 않은 복사본을 만들 수 있습니다. 이 메서드는 동일한 속성을 많이 공유하는 모델 인스턴스가 있을 때 특히 유용합니다.
use App\Models\Address; $shipping = Address::create([ 'type' => 'shipping', 'line_1' => '123 Example Street', 'city' => 'Victorville', 'state' => 'CA', 'postcode' => '90001',]); $billing = $shipping->replicate()->fill([ 'type' => 'billing']); $billing->save();
새 모델로 복제되지 않도록 하나 이상의 속성을 제외하려면 배열을 replicate 메서드에 전달할 수 있습니다.
$flight = Flight::create([ 'destination' => 'LAX', 'origin' => 'LHR', 'last_flown' => '2020-03-04 11:00:00', 'last_pilot_id' => 747,]); $flight = $flight->replicate([ 'last_flown', 'last_pilot_id']);
쿼리 스코프
글로벌 스코프
글로벌 스코프를 사용하면 주어진 모델에 대한 모든 쿼리에 제약 조건을 추가할 수 있습니다. Laravel 자체의 소프트 삭제 기능은 글로벌 스코프를 활용하여 데이터베이스에서 "삭제되지 않은" 모델만 검색합니다. 자신만의 글로벌 스코프를 작성하면 주어진 모델에 대한 모든 쿼리가 특정 제약 조건을 수신하도록 하는 편리하고 쉬운 방법을 제공할 수 있습니다.
스코프 생성
새로운 글로벌 스코프를 생성하려면 make:scope Artisan 명령을 호출할 수 있습니다. 이 명령은 생성된 스코프를 애플리케이션의 app/Models/Scopes 디렉토리에 배치합니다.
php artisan make:scope AncientScope
글로벌 스코프 작성하기
글로벌 스코프를 작성하는 것은 간단합니다. 먼저 make:scope 명령어를 사용하여 Illuminate\Database\Eloquent\Scope 인터페이스를 구현하는 클래스를 생성합니다. Scope 인터페이스는 apply라는 하나의 메소드를 구현하도록 요구합니다. apply 메소드는 필요에 따라 where 제약 조건이나 다른 유형의 절을 쿼리에 추가할 수 있습니다:
<?php namespace App\Models\Scopes; use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Scope; class AncientScope implements Scope{ /** * 주어진 Eloquent 쿼리 빌더에 스코프를 적용합니다. */ public function apply(Builder $builder, Model $model): void { $builder->where('created_at', '<', now()->subYears(2000)); }}
글로벌 스코프가 쿼리의 select 절에 컬럼을 추가하는 경우, select 대신 addSelect 메소드를 사용해야 합니다. 이렇게 하면 쿼리의 기존 select 절이 의도치 않게 교체되는 것을 방지할 수 있습니다.
글로벌 스코프 적용하기
모델에 글로벌 스코프를 할당하려면 모델에 ScopedBy 속성을 간단히 배치할 수 있습니다:
<?php namespace App\Models; use App\Models\Scopes\AncientScope;use Illuminate\Database\Eloquent\Attributes\ScopedBy; #[ScopedBy([AncientScope::class])]class User extends Model{ //}
또는, 모델의 booted 메소드를 오버라이드하고 모델의 addGlobalScope 메소드를 호출하여 수동으로 글로벌 스코프를 등록할 수 있습니다. addGlobalScope 메소드는 스코프의 인스턴스를 유일한 인수로 받습니다:
<?php namespace App\Models; use App\Models\Scopes\AncientScope;use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * 모델의 "booted" 메소드입니다. */ protected static function booted(): void { static::addGlobalScope(new AncientScope); }}
위 예시에서 App\Models\User 모델에 스코프를 추가한 후, User::all() 메소드를 호출하면 다음 SQL 쿼리가 실행됩니다:
-- `users` 테이블에서 `created_at` 컬럼의 값이 0021-02-18 00:00:00 보다 작은 모든 레코드를 선택합니다.select * from `users` where `created_at` < 0021-02-18 00:00:00
익명 글로벌 스코프
Eloquent는 클로저를 사용하여 글로벌 스코프를 정의할 수도 있습니다. 이는 자체 클래스가 필요하지 않은 간단한 스코프에 특히 유용합니다. 클로저를 사용하여 글로벌 스코프를 정의할 때 addGlobalScope 메서드의 첫 번째 인자로 직접 선택한 스코프 이름을 제공해야 합니다.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * 모델의 "booted" 메서드. */ protected static function booted(): void { static::addGlobalScope('ancient', function (Builder $builder) { $builder->where('created_at', '<', now()->subYears(2000)); }); }}
글로벌 스코프 제거
주어진 쿼리에 대해 글로벌 스코프를 제거하려면 withoutGlobalScope 메서드를 사용할 수 있습니다. 이 메서드는 글로벌 스코프의 클래스 이름을 유일한 인자로 받습니다.
User::withoutGlobalScope(AncientScope::class)->get();
또는 클로저를 사용하여 글로벌 스코프를 정의한 경우 글로벌 스코프에 할당한 문자열 이름을 전달해야 합니다.
User::withoutGlobalScope('ancient')->get();
쿼리의 여러 개 또는 모든 글로벌 스코프를 제거하려면 withoutGlobalScopes 메서드를 사용할 수 있습니다.
// 모든 글로벌 스코프 제거...User::withoutGlobalScopes()->get(); // 일부 글로벌 스코프 제거...User::withoutGlobalScopes([ FirstScope::class, SecondScope::class])->get();
로컬 스코프
로컬 스코프를 사용하면 애플리케이션 전체에서 쉽게 재사용할 수 있는 일반적인 쿼리 제약 조건 집합을 정의할 수 있습니다. 예를 들어, "인기있는" 것으로 간주되는 모든 사용자를 자주 검색해야 할 수 있습니다. 스코프를 정의하려면 Eloquent 모델 메서드에 scope 접두사를 붙입니다.
스코프는 항상 동일한 쿼리 빌더 인스턴스 또는 void를 반환해야 합니다.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * 인기있는 사용자만 포함하도록 쿼리 범위를 지정합니다. */ public function scopePopular(Builder $query): void { $query->where('votes', '>', 100); } /** * 활성 사용자만 포함하도록 쿼리 범위를 지정합니다. */ public function scopeActive(Builder $query): void { $query->where('active', 1); }}
로컬 스코프 활용
스코프가 정의되면 모델을 쿼리할 때 스코프 메서드를 호출할 수 있습니다. 그러나 메서드를 호출할 때 scope 접두사는 포함하지 않아야 합니다. 다양한 스코프 호출을 연결할 수도 있습니다.
use App\Models\User; $users = User::popular()->active()->orderBy('created_at')->get();
or 쿼리 연산자를 통해 여러 Eloquent 모델 스코프를 결합하려면 올바른 논리적 그룹화를 달성하기 위해 클로저를 사용해야 할 수 있습니다.
$users = User::popular()->orWhere(function (Builder $query) { $query->active();})->get();
그러나 이는 번거로울 수 있으므로 Laravel은 클로저를 사용하지 않고도 스코프를 유연하게 연결할 수 있는 "고차" orWhere 메서드를 제공합니다.
$users = User::popular()->orWhere->active()->get();
동적 스코프
때로는 매개변수를 허용하는 스코프를 정의하고 싶을 수도 있습니다. 시작하려면 스코프 메서드 시그니처에 추가 매개변수를 추가하기만 하면 됩니다. 스코프 매개변수는 $query 매개변수 다음에 정의해야 합니다.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * 주어진 유형의 사용자만 포함하도록 쿼리 범위를 지정합니다. */ public function scopeOfType(Builder $query, string $type): void { $query->where('type', $type); }}
예상되는 인수가 스코프 메서드의 시그니처에 추가되면 스코프를 호출할 때 인수를 전달할 수 있습니다.
$users = User::ofType('admin')->get();
모델 비교
때로는 두 모델이 "동일"한지 여부를 확인해야 할 수도 있습니다. is 및 isNot 메서드를 사용하여 두 모델이 동일한 기본 키, 테이블 및 데이터베이스 연결을 갖는지 여부를 빠르게 확인할 수 있습니다.
if ($post->is($anotherPost)) { // ...} if ($post->isNot($anotherPost)) { // ...}
is 및 isNot 메서드는 belongsTo, hasOne, morphTo 및 morphOne 관계를 사용할 때도 사용할 수 있습니다. 이 메서드는 해당 모델을 검색하기 위한 쿼리를 발행하지 않고 관련 모델을 비교하려는 경우에 특히 유용합니다.
if ($post->author()->is($user)) { // ...}
이벤트
Eloquent 이벤트를 클라이언트 측 애플리케이션으로 직접 브로드캐스트하시겠습니까? Laravel의 모델 이벤트 브로드캐스팅을 확인해 보세요.
Eloquent 모델은 여러 이벤트를 디스패치하여 모델 수명 주기의 다음 순간에 연결할 수 있습니다. retrieved, creating, created, updating, updated, saving, saved, deleting, deleted, trashed, forceDeleting, forceDeleted, restoring, restored, 및 replicating.
retrieved 이벤트는 기존 모델을 데이터베이스에서 검색할 때 디스패치됩니다. 새 모델을 처음 저장하면 creating 및 created 이벤트가 디스패치됩니다. 기존 모델이 수정되고 save 메서드가 호출되면 updating / updated 이벤트가 디스패치됩니다. 모델이 생성 또는 업데이트될 때 모델의 속성이 변경되지 않은 경우에도 saving / saved 이벤트가 디스패치됩니다. -ing로 끝나는 이벤트 이름은 모델 변경 사항이 유지되기 전에 디스패치되는 반면, -ed로 끝나는 이벤트 이름은 모델 변경 사항이 유지된 후에 디스패치됩니다.
모델 이벤트 수신을 시작하려면 Eloquent 모델에 $dispatchesEvents 속성을 정의합니다. 이 속성은 Eloquent 모델 수명 주기의 다양한 지점을 사용자 정의 이벤트 클래스에 매핑합니다. 각 모델 이벤트 클래스는 생성자를 통해 영향을 받는 모델의 인스턴스를 받을 것으로 예상해야 합니다.
<?php namespace App\Models; use App\Events\UserDeleted;use App\Events\UserSaved;use Illuminate\Foundation\Auth\User as Authenticatable;use Illuminate\Notifications\Notifiable; class User extends Authenticatable{ use Notifiable; /** * 모델에 대한 이벤트 맵. * * @var array<string, string> */ protected $dispatchesEvents = [ 'saved' => UserSaved::class, 'deleted' => UserDeleted::class, ];}
Eloquent 이벤트를 정의하고 매핑한 후에는 이벤트 리스너를 사용하여 이벤트를 처리할 수 있습니다.
Eloquent를 통해 대량 업데이트 또는 삭제 쿼리를 발행하는 경우 영향을 받는 모델에 대해 saved, updated, deleting 및 deleted 모델 이벤트가 디스패치되지 않습니다. 이는 대량 업데이트 또는 삭제를 수행할 때 모델을 실제로 검색하지 않기 때문입니다.
클로저 사용
사용자 정의 이벤트 클래스를 사용하는 대신 다양한 모델 이벤트가 디스패치될 때 실행되는 클로저를 등록할 수 있습니다. 일반적으로 이러한 클로저는 모델의 booted 메서드에 등록해야 합니다.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * 모델의 "booted" 메서드. */ protected static function booted(): void { static::created(function (User $user) { // ... }); }}
필요한 경우 모델 이벤트를 등록할 때 큐에 넣을 수 있는 익명 이벤트 리스너를 활용할 수 있습니다. 이렇게 하면 Laravel에서 애플리케이션의 큐를 사용하여 백그라운드에서 모델 이벤트 리스너를 실행하도록 지시합니다.
use function Illuminate\Events\queueable; static::created(queueable(function (User $user) { // ...}));
옵저버
옵저버 정의
주어진 모델에서 많은 이벤트를 수신하는 경우 옵저버를 사용하여 모든 리스너를 단일 클래스로 그룹화할 수 있습니다. 옵저버 클래스에는 수신하려는 Eloquent 이벤트를 반영하는 메서드 이름이 있습니다. 이러한 각 메서드는 영향을 받는 모델을 유일한 인수로 받습니다. make:observer Artisan 명령은 새 옵저버 클래스를 만드는 가장 쉬운 방법입니다.
php artisan make:observer UserObserver --model=User
php artisan make:observer UserObserver --model=User
는 User 모델에 대한 옵저버 클래스인 UserObserver를 생성하는 Artisan 명령어입니다. 이 명령어는 app/Observers 디렉토리에 UserObserver.php 파일을 생성하고, 지정된 User 모델을 관찰하는 데 필요한 기본 메소드를 포함합니다.
-
php artisan: Artisan 커맨드라인 인터페이스를 실행합니다. -
make:observer: 새로운 옵저버 클래스를 생성하는 Artisan 명령어입니다. -
UserObserver: 생성할 옵저버 클래스의 이름입니다. -
--model=User: 옵저버가 관찰할 모델을User모델로 지정합니다.
이 명령어를 실행하면 UserObserver 클래스가 생성되어, User 모델의 생성, 업데이트, 삭제 등과 같은 이벤트에 반응하는 로직을 구현할 수 있게 됩니다.
이 명령어는 새 옵저버를 app/Observers 디렉토리에 배치합니다. 해당 디렉토리가 존재하지 않으면 Artisan이 자동으로 생성합니다. 새로 생성된 옵저버는 다음과 같은 모습일 것입니다:
<?php namespace App\Observers; use App\Models\User; class UserObserver{ /** * Handle the User "created" event. */ public function created(User $user): void { // ... } /** * Handle the User "updated" event. */ public function updated(User $user): void { // ... } /** * Handle the User "deleted" event. */ public function deleted(User $user): void { // ... } /** * Handle the User "restored" event. */ public function restored(User $user): void { // ... } /** * Handle the User "forceDeleted" event. */ public function forceDeleted(User $user): void { // ... }}
옵저버를 등록하려면 해당 모델에 ObservedBy 속성을 추가하면 됩니다:
use App\Observers\UserObserver;use Illuminate\Database\Eloquent\Attributes\ObservedBy; #[ObservedBy([UserObserver::class])]class User extends Authenticatable{ //}
또는, 관찰하려는 모델에서 observe 메서드를 호출하여 수동으로 옵저버를 등록할 수 있습니다. 애플리케이션의 AppServiceProvider 클래스의 boot 메서드에서 옵저버를 등록할 수 있습니다:
use App\Models\User;use App\Observers\UserObserver; /** * Bootstrap any application services. */public function boot(): void{ User::observe(UserObserver::class);}
옵저버가 수신할 수 있는 추가 이벤트(예: saving, retrieved)가 있습니다. 이러한 이벤트는 이벤트 문서에 설명되어 있습니다.
옵저버와 데이터베이스 트랜잭션
모델이 데이터베이스 트랜잭션 내에서 생성될 때, 데이터베이스 트랜잭션이 커밋된 후에만 옵저버가 이벤트 핸들러를 실행하도록 지시할 수 있습니다. 옵저버에 ShouldHandleEventsAfterCommit 인터페이스를 구현하여 이를 수행할 수 있습니다. 데이터베이스 트랜잭션이 진행 중이 아니면 이벤트 핸들러가 즉시 실행됩니다:
<?php namespace App\Observers; use App\Models\User;use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit; class UserObserver implements ShouldHandleEventsAfterCommit{ /** * Handle the User "created" event. */ public function created(User $user): void { // ... }}
이벤트 음소거
모델에서 발생하는 모든 이벤트를 일시적으로 "음소거"해야 할 수 있습니다. withoutEvents 메서드를 사용하여 이를 수행할 수 있습니다. withoutEvents 메서드는 클로저를 유일한 인수로 받습니다. 이 클로저 내에서 실행되는 코드는 모델 이벤트를 디스패치하지 않으며, 클로저에서 반환된 값은 withoutEvents 메서드에서 반환됩니다:
use App\Models\User; $user = User::withoutEvents(function () { User::findOrFail(1)->delete(); return User::find(2);});
이벤트 없이 단일 모델 저장
경우에 따라 이벤트를 디스패치하지 않고 특정 모델을 "저장"하려는 경우가 있습니다. saveQuietly 메서드를 사용하여 이를 수행할 수 있습니다:
$user = User::findOrFail(1); $user->name = 'Victoria Faith'; $user->saveQuietly();
또한 이벤트를 디스패치하지 않고 주어진 모델을 "업데이트", "삭제", "소프트 삭제", "복원", "복제"할 수도 있습니다:
$user->deleteQuietly();$user->forceDeleteQuietly();$user->restoreQuietly();