Eloquent: 시작하기

(Eloquent에서 넘어옴)

1 개요[ | ]

Eloquent: Getting Started
엘로퀀트: 시작하기

2 소개[ | ]

Laravel에는 데이터베이스와 상호작용하는 것을 즐겁게 만들어 주는 객체-관계 매퍼(ORM)인 Eloquent가 포함되어 있습니다. Eloquent를 사용할 때 각 데이터베이스 테이블에는 해당 테이블과 상호작용하는 데 사용되는 "모델"이 있습니다. Eloquent 모델을 사용하면 데이터베이스 테이블에서 레코드를 조회하는 것 외에도 테이블에 레코드를 삽입, 업데이트 및 삭제할 수 있습니다.

Note

시작하기 전에 애플리케이션의 config/database.php 설정 파일에서 데이터베이스 연결을 설정해야 합니다. 데이터베이스 설정에 대한 자세한 내용은 데이터베이스 설정 문서를 확인하세요 .

Laravel 부트캠프

Laravel이 처음이라면, Laravel 부트캠프에 참여해 보세요. Laravel 부트캠프는 Eloquent를 사용하여 첫 번째 Laravel 애플리케이션을 구축하는 과정을 안내합니다. Laravel과 Eloquent가 제공하는 모든 것을 체험할 수 있는 좋은 방법입니다.

3 모델 클래스 생성[ | ]

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 --factory
php artisan make:model Flight -f

# 모델과 FlightSeeder 클래스를 생성...
php artisan make:model Flight --seed
php artisan make:model Flight -s

# 모델과 FlightController 클래스를 생성...
php artisan make:model Flight --controller
php artisan make:model Flight -c

# 모델, FlightController 리소스 클래스 및 폼 요청 클래스를 생성...
php artisan make:model Flight --controller --resource --requests
php artisan make:model Flight -crR

# 모델과 FlightPolicy 클래스를 생성...
php artisan make:model Flight --policy

# 모델과 마이그레이션, 팩토리, 시더, 컨트롤러를 생성...
php artisan make:model Flight -mfsc

# 모델, 마이그레이션, 팩토리, 시더, 정책, 컨트롤러 및 폼 요청을 생성하는 단축키...
php artisan make:model Flight --all
php artisan make:model Flight -a

# 피벗 모델을 생성...
php artisan make:model Member --pivot
php artisan make:model Member -p
모델 검사

모델의 모든 속성과 관계를 코드만으로 파악하기 어려울 때가 있습니다. 대신, model:show Artisan 명령어를 사용하여 모델의 모든 속성과 관계를 편리하게 확인할 수 있습니다:

php artisan model:show Flight

4 Eloquent 모델 컨벤션[ | ]

make:mode 명령어로 생성된 모델은 app/Models 디렉토리에 배치됩니다. 기본 모델 클래스를 살펴보고 Eloquent의 주요 컨벤션에 대해 논의해 봅시다:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    // ...
}

4.1 테이블 이름[ | ]

예를 들어 위의 예제를 살펴본 후, Flight 모델이 어떤 데이터베이스 테이블에 대응되는지 Eloquent에 알려주지 않았음을 알 수 있습니다. 관례에 따라, 클래스의 이름을 "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';
}

4.2 주키[ | ]

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가 주키를 자동으로 정수로 변환한다는 의미입니다. 만약 자동증가하지 않거나 숫자가 아닌 주키를 사용하고자 한다면, 모델에 public $incrementing 속성을 false로 설정하여 정의해야 합니다.

<?php
 
class Flight extends Model
{
    /**
     * 모델의 ID가 자동증가하는지 여부를 나타냄.
     *
     * @var bool
     */
    public $incrementing = false;
}

모델의 주키가 정수가 아닌 경우, 모델에 protectd $keyType 속성을 정의해야 합니다. 이 속성의 값은 string이어야 합니다.

<?php
 
class Flight extends Model
{
    /**
     * 주키 ID의 자료형.
     *
     * @var string
     */
    protected $keyType = 'string';
}
"복합(composite)" 주키

Eloquent는 각 모델에 적어도 하나의 고유 식별 "ID"가 있어야 하며, 이는 주키로 사용할 수 있어야 합니다. Eloquent 모델에서는 "복합" 기본키를 지원하지 않습니다. 그러나 테이블의 고유 식별 주키 외에도 데이터베이스 테이블에 다중 컬럼으로 구성된 추가적인 고유 인덱스를 추가할 수 있습니다.

4.3 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 대신 "ULIDs"를 사용할 수도 있습니다. ULIDs는 UUID와 유사하지만 길이가 26자에 불과합니다. 정렬된 UUID와 마찬가지로, ULIDs는 효율적인 데이터베이스 인덱싱을 위해 사전 순으로 정렬할 수 있습니다. ULIDs를 사용하려면 모델에 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"

4.4 타임스탬프[ | ]

기본적으로, Eloquent는 모델의 해당 데이터베이스 테이블에 created_atupdated_at 컬럼이 존재할 것으로 기대합니다. Eloquent는 모델이 생성되거나 업데이트될 때 이 컬럼들의 값을 자동으로 세팅합니다. 이 컬럼들이 Eloquent에 의해 자동으로 관리되지 않도록 하려면, 모델에 $timestamps 속성을 false 값으로 정의해야 합니다:

<?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_ATUPDATED_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']));

4.5 데이터베이스 연결[ | ]

기본적으로 모든 Eloquent 모델은 애플리케이션에 설정된 기본 데이터베이스 연결을 사용합니다. 특정 모델과 상호작용할 때 사용해야 하는 다른 연결을 지정하려면 모델에 $connection 속성을 정의해야 합니다:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    /**
     * 모델에서 사용해야 하는 데이터베이스 연결.
     *
     * @var string
     */
    protected $connection = 'mysql';
}

4.6 기본 속성 값[ | ]

기본적으로 새로 인스턴스화된 모델 인스턴스는 어떤 속성 값도 포함하지 않습니다. 모델의 속성에 대한 기본 값을 정의하고 싶다면, 모델에 $attributes 속성을 정의할 수 있습니다. $attributes 배열에 배치된 속성 값은 데이터베이스에서 읽은 것처럼 원시적이고 "저장가능한" 형식으로 있어야 합니다:

<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
 
class Flight extends Model
{
    /**
     * 모델의 속성에 대한 기본 값.
     *
     * @var array
     */
    protected $attributes = [
        'options' => '[]',
        'delayed' => false,
    ];
}

4.7 Eloquent 엄격성 설정[ | ]

Laravel은 다양한 상황에서 Eloquent의 동작과 "엄격성(strictness)"을 설정할 수 있는 여러 가지 방법을 제공합니다.

먼저, preventLazyLoading 메소드는 지연 로딩이 방지될지 여부를 나타내는 선택적 불리언 인수를 허용합니다. 예를 들어, 비-프로덕션 환경에서만 지연 로딩을 비활성화하고, 실수로 지연 로딩된 관계가 프로덕션 코드에 존재하더라도 프로덕션 환경은 정상적으로 작동하도록 할 수 있습니다. 일반적으로 이 메소드는 애플리케이션의 AppServiceProviderboot 메소드에서 호출됩니다:

use Illuminate\Database\Eloquent\Model;
 
/**
 * 애플리케이션 서비스를 부트스트랩.
 */
public function boot(): void
{
    Model::preventLazyLoading(! $this->app->isProduction());
}

또한, preventSilentlyDiscardingAttributes 메소드를 호출하여 채울 수 없는 속성을 채우려고 할 때 예외를 던지도록 Laravel에 지시할 수 있습니다. 이는 모델의 fillable 배열에 추가되지 않은 속성을 설정하려고 할 때 발생할 수 있는 예기치 않은 오류를 로컬 개발 중에 방지하는 데 도움이 됩니다:

Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());

5 모델 조회[ | ]

모델 및 마이그레이션 작성|관련 데이터베이스 테이블을 생성하고 나면 데이터베이스에서 데이터 조회을 시작할 준비가 된 것입니다. 각 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();

Note

Eloquent 모델은 쿼리 빌더이므로 Laravel의 쿼리 빌더가 제공하는 모든 메소드를 검토해야 합니다. Eloquent 쿼리를 작성할 때 이러한 방법 중 하나를 사용할 수 있습니다.

모델 새로고침

데이터베이스에서 조회된 Eloquent 모델의 인스턴스가 이미 있는 경우 freshrefresh 메소드를 사용하여 모델을 "새로고침"할 수 있습니다. fresh 메소드는 데이터베이스에서 모델을 다시 조회합니다. 기존 모델 인스턴스는 영향을 받지 않습니다.

$flight = Flight::where('number', 'FR 900')->first();
 
$freshFlight = $flight->fresh();

refresh 메소드는 데이터베이스의 새로운 데이터를 사용하여 기존 모델을 재수화(re-hydrate)합니다. 또한 로드된 모든 관계도 새로 고쳐집니다.

$flight = Flight::where('number', 'FR 900')->first();
 
$flight->number = 'FR 456';
 
$flight->refresh();
 
$flight->number; // "FR 900"

5.1 콜렉션[ | ]

지금까지 살펴본 바와 같이, Eloquent 메소드 allget은 데이터베이스에서 여러 레코드를 조회합니다. 그러나 이러한 메소드는 플레인 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;
});

Laravel의 기본 콜렉션 클래스에서 제공하는 메소드 외에도, Eloquent 콜렉션 클래스는 Eloquent 모델 콜렉션과 상호작용하기 위해 특별히 의도된 몇 가지 부가 메소드를 제공합니다.

Laravel의 모든 콜렉션은 PHP의 iterable 인터페이스를 구현하므로 배열처럼 콜렉션을 반복할 수 있습니다:

foreach ($flights as $flight) {
    echo $flight->name;
}

5.2 결과 청킹[ | ]

수만건의 Eloquent 레코드를 all 또는 get 메소드를 통해 로드하려고 하면 애플리케이션의 메모리가 부족해질 수 있습니다. 이러한 메소드 대신, 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');

위 코드는 departedtrueFlight 모델을 검색하고, 각 청크의 레코드를 반복하면서 departed 값을 false로 업데이트합니다.

5.3 게으른 콜렉션을 사용한 청크[ | ]

lazy 메소드는 chunk 메소드와 유사하게 작동합니다. 배후에서 쿼리를 청크 단위로 실행한다는 점에서 비슷합니다. 그러나 각 청크를 그대로 콜백에 전달하는 대신 lazy 메소드는 단일 스트림처럼 결과를 상호작용할 수 있는 평탄화된 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]);

결과를 id의 내림차순으로 필터링하려면 lazyByIdDesc 메소드를 사용할 수 있습니다.

5.4 커서[ | ]

lazy 메소드와 유사하게 커서 메소드를 사용하면 수만 개의 Eloquent 모델 레코드를 반복 처리할 때 애플리케이션의 메모리 소비를 크게 줄일 수 있습니다.

커서 메소드는 단일 데이터베이스 쿼리만 실행하지만, 개별 Eloquent 모델은 실제로 반복될 때까지 활성화되지 않습니다. 따라서 커서를 반복하는 동안 한 번에 하나의 Eloquent 모델만 메모리에 유지됩니다.

Warning

커서 메소드는 한 번에 하나의 Eloquent 모델만 메모리에 유지하기 때문에 관계를 사전 로드할 수 없습니다. 관계를 사전 로드해야 하는 경우, 대신 lazy 메소드를 사용하십시오.

내부적으로 커서 방법은 PHP 제네레이저를 사용하여 이 기능을 구현합니다:

use App\Models\Flight;

foreach (Flight::where('destination', 'Zurich')->cursor() as $flight) {
    // ...
}

커서는 Illuminate\Support\LazyCollection 인스턴스를 반환합니다. Lazy 콜렉션을 사용하면 일반적인 Laravel 콜렉션에서 사용할 수 있는 많은 콜렉션 메소드를 단일 모델만 메모리에 로드하면서 사용할 수 있습니다:

use App\Models\User;

$users = User::cursor()->filter(function (User $user) {
    return $user->id > 500;
});

foreach ($users as $user) {
    echo $user->id;
}

커서 방법은 일반 쿼리보다 훨씬 적은 메모리를 사용하지만(한 번에 하나의 Eloquent 모델만 메모리에 유지하므로) 결국 메모리가 부족하게 됩니다. 이는 PHP의 PDO 드라이버가 내부적으로 모든 원시 쿼리 결과를 버퍼에 캐시하기 때문입니다. 매우 많은 수의 Eloquent 레코드를 다루는 경우, 대신 lazy 메소드를 사용하는 것을 고려하십시오.

5.5 고급 서브쿼리[ | ]

서브쿼리 셀렉트

Eloquent는 또한 고급 서브쿼리 지원을 제공하여 관련 테이블에서 정보를 단일 쿼리로 가져올 수 있습니다. 예를 들어, 목적지(destinations) 테이블과 목적지로 가는 항공편(flights) 테이블이 있다고 가정해봅시다. flights 테이블에는 항공편이 목적지에 도착한 시간을 나타내는 arrived_at 컬럼이 있습니다.

쿼리 빌더의 selectaddSelect 메소드에서 제공하는 서브쿼리 기능을 사용하여 모든 목적지(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();

6 단일 모델/집계 조회[ | ]

주어진 쿼리와 매치하는 모든 레코드를 조회하는 것 외에도, find, first, firstWhere 메소드를 사용하여 단일 레코드를 조회할 수 있습니다. 이러한 메소드는 모델 콜렉션 대신 단일 모델 인스턴스를 반환합니다:

use App\Models\Flight;

// 기본 키로 모델을 검색...
$flight = Flight::find(1);

// 쿼리 제약 조건과 매치하는 첫 번째 모델을 조회...
$flight = Flight::where('active', 1)->first();

// 쿼리 제약 조건과 매치하는 첫 번째 모델을 조회하는 대안...
$flight = Flight::firstWhere('active', 1);

때로는 결과가 없을 경우 다른 작업을 수행하고 싶을 수 있습니다. findOrfirstOr 메소드는 단일 모델 인스턴스를 반환하거나, 결과가 없을 경우 주어진 클로저를 실행합니다. 클로저에서 반환된 값은 메소드의 결과로 간주됩니다:

$flight = Flight::findOr(1, function () {
    // ...
});

$flight = Flight::where('legs', '>', 3)->firstOr(function () {
    // ...
});
Not Found 예외

모델을 찾을 수 없는 경우 예외를 발생시키고 싶을 때가 있습니다. 이는 특히 라우트나 컨트롤러에서 유용합니다. findOrFailfirstOrFail 메소드는 쿼리의 첫 번째 결과를 검색합니다. 그러나 결과를 찾을 수 없는 경우, 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);
});

6.1 모델 조회 또는 생성[ | ]

6.2 집계 조회[ | ]

7 모델 삽입과 업데이트[ | ]

7.1 삽입[ | ]

물론, 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
{
    /**
     * Store a new flight in the database.
     */
    public function store(Request $request): RedirectResponse
    {
        // Validate the request...
 
        $flight = new Flight;
 
        $flight->name = $request->name;
 
        $flight->save();
 
        return redirect('/flights');
    }
}

이 예시에서는, 들어온 HTTP 요청의 name 필드를 App\Models\Flight 모델 인스턴스의 name 속성에 할당합니다. save 메소드를 호출하면 레코드가 데이터베이스에 삽입됩니다. 모델의 created_atupdated_at 타임스탬프는 메소드가 호출될 때 자동으로 설정되므로, 수동으로 세팅할 필요가 없습니다.

또는, 단일 PHP문을 사용하여 새 모델을 "저장(save)"하는 create 메소드를 사용할 수 있습니다. 삽입된 모델 인스턴스는 create 메소드에 의해 반환됩니다.

use App\Models\Flight;
 
$flight = Flight::create([
    'name' => 'London to Paris',
]);

하지만, create 메소드를 사용하기 전에 모델 클래스에 fillable 또는 guarded 속성을 지정해야 합니다. 모든 Eloquent 모델은 기본적으로 대량 할당 취약점으로부터 보호되기 때문에 이러한 속성이 필요합니다. 대량 할당에 대해 자세히 알아보려면 대량 할당 문서를 참조하세요.

7.2 업데이트[ | ]

save 메소드는 데이터베이스에 이미 존재하는 모델을 업데이트하는 데에도 사용될 수 있습니다. 모델을 업데이트하려면 모델을 검색하고 업데이트하려는 속성을 설정해야 합니다. 그런 다음 모델의 save 메소드를 호출해야 합니다. 이번에도 updated_at 타임스탬프가 자동으로 업데이트되므로 해당 값을 수동으로 설정할 필요가 없습니다.

use App\Models\Flight;
 
$flight = Flight::find(1);
 
$flight->name = 'Paris to London';
 
$flight->save();

때로는 기존 모델을 업데이트하거나 일치하는 모델이 없는 경우 새 모델을 만들어야 할 수도 있습니다. firstOrCreate 메소드와 마찬가지로 updateOrCreate 메소드도 모델을 유지하므로 수동으로 save 메소드를 호출할 필요가 없습니다.

아래 예시에서는 departure위치가 Oakland이고 위치 destinationSan Diego인 항공편이 있는 경우, 해당 항공편의 price과 discounted 열이 업데이트됩니다. 해당 항공편이 없으면 첫 번째 인수 배열과 두 번째 인수 배열을 병합한 결과 속성을 갖는 새 항공편이 생성됩니다.

$flight = Flight::updateOrCreate(
    ['departure' => 'Oakland', 'destination' => 'San Diego'],
    ['price' => 99, 'discounted' => 1]
);
대량 업데이트

특정 쿼리와 일치하는 모델에 대해 업데이트를 수행할 수도 있습니다. 이 예시에서는, 다음 active이고 destinationSan Diego인 모든 항공편이 지연된 것으로 마킹합니다.

Flight::where('active', 1)
      ->where('destination', 'San Diego')
      ->update(['delayed' => 1]);

update 메소드에는 업데이트해야 하는 열을 나타내는 열 및 값 쌍의 배열이 필요합니다. 이 update 메소드는 영향을 받은 행의 수를 반환합니다.

Eloquent를 통해 대량 업데이트를 실행하면 업데이트된 모델에 대해 saving, saved, updating, updated 모델 이벤트가 실행되지 않습니다. 이는 대량 업데이트를 실행할 때 모델이 실제로 검색되지 않기 때문입니다.

7.3 대량 할당[ | ]

7.4 Upsert[ | ]

Eloquent의 upsert 메소드는 단일 원자적 작업으로 레코드를 업데이트하거나 생성하는 데 사용됩니다. 메소드의 첫 번째 인수는 삽입하거나 업데이트할 값을 포함하고, 두 번째 인수는 관련된 테이블 내에서 레코드를 고유하게 식별하는 컬럼(들)을 나열합니다. 마지막 세 번째 인수는 데이터베이스에 이미 존재하는 일치하는 레코드가 있을 경우 업데이트해야 할 컬럼들의 배열입니다. 타임스탬프가 모델에 활성화되어 있으면 upsert 메소드는 자동으로 created_atupdated_at 타임스탬프를 설정합니다:

Flight::upsert([
    ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
    ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
], uniqueBy: ['departure', 'destination'], update: ['price']);

Warning

SQL Server를 제외한 모든 데이터베이스는 upsert 메소드의 두 번째 인수에 있는 컬럼들이 "기본(primary)" 또는 "고유(unique)" 인덱스를 가져야 합니다. 또한, MySQL 데이터베이스 드라이버는 upsert 메소드의 두 번째 인수를 무시하고 항상 테이블의 "기본" 및 "고유" 인덱스를 사용하여 기존 레코드를 감지합니다.

8 모델 삭제[ | ]

모델을 삭제하려면 모델 인스턴스에서 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]));

Warning

destroy 메소드는 각 모델을 개별적으로 로드하고 delete 메소드를 호출하여 각 모델에 대해 deletingdeleted 이벤트가 적절히 디스패치되도록 합니다.

쿼리를 사용하여 모델 삭제하기

당연히, 쿼리를 작성하여 쿼리의 기준에 맞는 모든 모델을 삭제할 수 있습니다. 이 예제에서는 비활성 상태로 표시된 모든 항공편을 삭제합니다. 대량 업데이트와 마찬가지로, 대량 삭제는 삭제된 모델에 대한 이벤트를 디스패치하지 않습니다:

$deleted = Flight::where('active', 0)->delete();

Warning

엘로퀀트(Eloquent)를 통해 대량 삭제 명령문을 실행할 때는 삭제된 모델에 대해 deletingdeleted 모델 이벤트가 디스패치되지 않습니다. 이는 삭제 명령문을 실행할 때 모델이 실제로 조회되지 않기 때문입니다.

8.1 소프트 삭제[ | ]

데이터베이스에서 실제로 레코드를 제거하는 것 외에도, 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;
}

Note

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();

8.2 소프트 삭제된 모델 쿼리하기[ | ]

소프트 삭제된 모델 포함하기

앞서 언급한 바와 같이 소프트 삭제된 모델은 자동으로 쿼리 결과에서 제외됩니다. 그러나 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();

9 모델 정리[ | ]

모델이 더 이상 필요하지 않은 경우 주기적으로 삭제하고자 할 때, Illuminate\Database\Eloquent\Prunable 또는 <ocde>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
{
    // ...
}

Prunable 모델을 구성한 후, model:prune 아티즌 명령어를 애플리케이션의 routes/console.php 파일에서 일정하게 실행되도록 예약해야 합니다. 이 명령어가 실행될 적절한 간격을 자유롭게 선택할 수 있습니다:

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 옵션을 사용하여 prunable 쿼리를 테스트할 수 있습니다. 이 옵션을 사용하면 실제로 명령어가 실행되었을 때 얼마나 많은 레코드가 삭제될 것인지 보고만 합니다:

php artisan model:prune --pretend

Warning

소프트 삭제 모델은 prunable 쿼리와 일치할 경우 영구적으로 삭제(forceDelete)됩니다.

대량 정리

모델이 Illuminate\Database\Eloquent\MassPrunable 트레이트로 표시되면, 모델들은 대량 삭제 쿼리를 사용하여 데이터베이스에서 삭제됩니다. 따라서, prune 메소드는 호출되지 않으며, deletingdeleted 모델 이벤트도 발생하지 않습니다. 이는 삭제 전에 모델이 실제로 조회되지 않기 때문에 정리 과정이 훨씬 더 효율적으로 이루어집니다.

<?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());
    }
}

10 모델 복제하기[ | ]

기존 모델 인스턴스의 복사본을 생성하려면 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'
]);

11 쿼리 스코프[ | ]

11.1 전역 스코프[ | ]

전역 스코프를 사용하면 주어진 모델에 대한 모든 쿼리에 제약조건을 추가할 수 있습니다. 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
{
    /**
     * Apply the scope to a given Eloquent query builder.
     */
    public function apply(Builder $builder, Model $model): void
    {
        $builder->where('created_at', '<', now()->subYears(2000));
    }
}

Note

전역 스코프가 쿼리의 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 쿼리가 실행됩니다:

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();

11.2 로컬 스코프[ | ]

로컬 스코프를 사용하면 애플리케이션 전반에서 쉽게 재사용할 수 있는 공통 쿼리 제약조건 세트를 정의할 수 있습니다. 예를 들어, "인기 있는" 사용자만 자주 검색해야 할 경우를 생각해봅시다. 스코프를 정의하려면 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);
    }
}
로컬 스코프 사용하기

스코프가 정의되면 모델을 쿼리할 때 스코프 메소드를 호출할 수 있습니다. 그러나 메소드를 호출할 때는 스코프 접두어를 포함해서는 안 됩니다. 다양한 스코프 호출을 체인으로 연결할 수도 있습니다:

use App\Models\User;

$users = User::popular()->active()->orderBy('created_at')->get();

또한, 여러 Eloquent 모델 스코프를 or 쿼리 연산자로 결합하려면 올바른 논리적 그룹핑을 위해 클로저를 사용해야 할 수도 있습니다:

$users = User::popular()->orWhere(function (Builder $query) {
    $query->active();
})->get();

하지만, 이는 번거로울 수 있으므로 Laravel에서는 클로저를 사용하지 않고 스코프를 유창하게 체인으로 연결할 수 있는 "고차(higher order)" 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();

12 모델 비교하기[ | ]

때로는 두 모델이 "같은지" 여부를 확인해야 할 필요가 있습니다. isisNot 메소드를 사용하면 두 모델이 동일한 주키, 테이블, 데이터베이스 연결을 가지고 있는지 빠르게 확인할 수 있습니다:

if ($post->is($anotherPost)) {
    // ...
}
 
if ($post->isNot($anotherPost)) {
    // ...
}

isisNot 메소드는 belongsTo, hasOne, morphTo, morphOne 관계를 사용할 때도 사용가능합니다. 이 메소드는 해당 모델을 조회하기 위해 쿼리를 발행하지 않고 관련된 모델을 비교하고자 할 때 특히 유용합니다:

if ($post->author()->is($user)) {
    // ...
}

13 이벤트[ | ]

Note

Eloquent 이벤트를 클라이언트 사이드 애플리케이션에 직접 브로드캐스트하고 싶으신가요? Laravel의 모델 이벤트 브로드캐스팅을 확인해보세요.

Eloquent 모델은 여러 이벤트를 디스패치하여 모델의 수명주기 동안 다음 순간에 후크할 수 있습니다: retrieved, creating, created, updating, updated, saving, saved, deleting, deleted, trashed, forceDeleting, forceDeleted, restoring, restored, replicating.

retrieved 이벤트는 기존 모델이 데이터베이스에서 조회될 때 디스패치됩니다. 새로운 모델이 처음 저장될 때는 creatingcreated 이벤트가 디스패치됩니다. 기존 모델이 수정되고 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;
 
    /**
     * The event map for the model.
     *
     * @var array<string, string>
     */
    protected $dispatchesEvents = [
        'saved' => UserSaved::class,
        'deleted' => UserDeleted::class,
    ];
}

Eloquent 이벤트를 정의하고 매핑한 후에는 이벤트 리스너를 사용하여 이벤트를 처리할 수 있습니다.

Warning

Eloquent를 통해 대량 업데이트 또는 삭제 쿼리를 발행할 때, 영향을 받는 모델에 대해 saved, updated, deleting, deleted 모델 이벤트는 디스패치되지 않습니다. 이는 대량 업데이트나 삭제를 수행할 때 실제로 모델이 조회되지 않기 때문입니다.

13.1 클로저 사용[ | ]

커스텀 이벤트 클래스를 사용하는 대신, 다양한 모델 이벤트가 디스패치될 때 실행되는 클로저를 등록할 수 있습니다. 일반적으로 이러한 클로저는 모델의 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) {
    // ...
}));

13.2 옵저버[ | ]

옵저버 정의하기

특정 모델에서 여러 이벤트를 청취하려면 옵저버를 사용하여 모든 리스너를 단일 클래스에 그룹화할 수 있습니다. 옵저버 클래스는 듣고자 하는 Eloquent 이벤트를 반영하는 메소드 이름을 가지고 있습니다. 이러한 메소드 각각은 영향을 받은 모델을 유일한 인수로 받습니다. 새로운 옵저버 클래스를 생성하는 가장 쉬운 방법은 make:observer Artisan 명령어를 사용하는 것입니다:

php artisan make:observer UserObserver --model=User

이 명령어는 새로운 옵저버를 app/Observers 디렉토리에 배치합니다. 이 디렉토리가 존재하지 않으면, Artisan이 자동으로 생성합니다. 새로 생성된 옵저버는 다음과 같이 생겼습니다:

<?php

namespace App\Observers;

use App\Models\User;

class UserObserver
{
    /**
     * User "created" 이벤트를 처리합니다.
     */
    public function created(User $user): void
    {
        // ...
    }

    /**
     * User "updated" 이벤트를 처리합니다.
     */
    public function updated(User $user): void
    {
        // ...
    }

    /**
     * User "deleted" 이벤트를 처리합니다.
     */
    public function deleted(User $user): void
    {
        // ...
    }

    /**
     * User "restored" 이벤트를 처리합니다.
     */
    public function restored(User $user): void
    {
        // ...
    }

    /**
     * User "forceDeleted" 이벤트를 처리합니다.
     */
    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;

/**
 * 애플리케이션 서비스를 부트스트랩합니다.
 */
public function boot(): void
{
    User::observe(UserObserver::class);
}

Note

옵저버가 청취할 수 있는 추가 이벤트로는 savingretrieved가 있습니다. 이러한 이벤트는 이벤트 문서에서 설명되어 있습니다.

옵저버와 데이터베이스 트랜잭션

모델이 데이터베이스 트랜잭션 내에서 생성되는 경우, 옵저버가 이벤트 핸들러를 데이터베이스 트랜잭션이 커밋된 후에만 실행하도록 지시하고 싶을 수 있습니다. 이를 위해 ShouldHandleEventsAfterCommit 인터페이스를 옵저버에 구현할 수 있습니다. 데이터베이스 트랜잭션이 진행 중이지 않은 경우 이벤트 핸들러는 즉시 실행됩니다:

<?php

namespace App\Observers;

use App\Models\User;
use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;

class UserObserver implements ShouldHandleEventsAfterCommit
{
    /**
     * User "created" 이벤트를 처리합니다.
     */
    public function created(User $user): void
    {
        // ...
    }
}

13.3 이벤트 음소거[ | ]

모델에서 발생하는 모든 이벤트를 일시적으로 "음소거(mute)"해야 하는 경우가 있습니다. 이를 위해 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();

또한 주어진 모델을 이벤트를 디스패치하지 않고 "업데이트", "삭제", "소프트 삭제", "복원", "복제(replicate)"할 수도 있습니다:

$user->deleteQuietly();
$user->forceDeleteQuietly();
$user->restoreQuietly();
문서 댓글 ({{ doc_comments.length }})
{{ comment.name }} {{ comment.created | snstime }}