데이터베이스 모델 Model 사용법 (Laravel 스타일)
webman 모델은 Eloquent ORM을 기반으로 합니다. 각 데이터베이스 테이블은 해당 테이블과 상호작용할 수 있는 "모델"에 대응됩니다. 모델을 통해 데이터 테이블의 데이터를 조회하고, 새로운 레코드를 데이터 테이블에 삽입할 수 있습니다.
시작하기 전에 config/database.php
에서 데이터베이스 연결을 구성했는지 확인하세요.
주의: Eloquent ORM이 모델 옵저버를 지원하려면 추가로
composer require "illuminate/events"
를 임포트해야 합니다. 예제
데이터베이스 모델 예시
<?php
namespace app\model;
use support\Model;
class User extends Model
{
/**
* 모델과 연결된 테이블 이름
*
* @var string
*/
protected $table = 'user';
/**
* 기본 키 재정의, 기본값은 id
*
* @var string
*/
protected $primaryKey = 'uid';
/**
* 자동으로 타임스탬프를 유지할지 여부
*
* @var bool
*/
public $timestamps = false;
}
테이블 이름
모델에서 table 속성을 정의하여 사용자 지정 데이터 테이블을 지정할 수 있습니다:
class User extends Model
{
/**
* 모델과 연결된 테이블 이름
*
* @var string
*/
protected $table = 'user';
}
기본 키
Eloquent는 각 데이터 테이블이 "id"라는 이름의 기본 키 열을 가지고 있다고 가정합니다. 보호된 $primaryKey
속성을 정의하여 이 규약을 재정의할 수 있습니다.
class User extends Model
{
/**
* 기본 키 재정의, 기본값은 id
*
* @var string
*/
protected $primaryKey = 'uid';
}
Eloquent는 기본 키가 자동 증가하는 정수값이라고 가정합니다. 즉, 기본적으로 기본 키는 자동으로 int 형식으로 변환됩니다. 비증가형 또는 비숫자형 기본 키를 사용하려면 공용 $incrementing
속성을 false로 설정해야 합니다.
class User extends Model
{
/**
* 모델 기본 키가 자동 증가하는지 여부
*
* @var bool
*/
public $incrementing = false;
}
기본 키가 정수가 아닌 경우 모델에서 보호된 $keyType
속성을 string으로 설정해야 합니다:
class User extends Model
{
/**
* 자동 증가 ID의 “형태”.
*
* @var string
*/
protected $keyType = 'string';
}
타임스탬프
기본적으로 Eloquent는 데이터 테이블에 created_at과 updated_at이 존재한다고 예상합니다. Eloquent가 자동으로 이 두 열을 관리하지 않기를 원한다면 모델에서 $timestamps
속성을 false로 설정하세요:
class User extends Model
{
/**
* 자동으로 타임스탬프를 유지할지 여부
*
* @var bool
*/
public $timestamps = false;
}
타임스탬프 형식을 사용자 지정해야 하는 경우 모델에서 $dateFormat
속성을 설정합니다. 이 속성은 데이터베이스에 날짜 속성을 저장하는 방식과 모델을 배열 또는 JSON으로 직렬화하는 형식을 결정합니다:
class User extends Model
{
/**
* 타임스탬프 저장 형식
*
* @var string
*/
protected $dateFormat = 'U';
}
타임스탬프 저장 필드 이름을 사용자 지정해야 하는 경우 모델에서 CREATED_AT 및 UPDATED_AT 상수의 값을 설정하여 가능합니다:
class User extends Model
{
const CREATED_AT = 'creation_date';
const UPDATED_AT = 'last_update';
}
데이터베이스 연결
Eloquent 모델은 기본적으로 애플리케이션에서 구성한 기본 데이터베이스 연결을 사용합니다. 모델에 대해 다른 연결을 지정하려면 $connection
속성을 설정하세요:
class User extends Model
{
/**
* 모델의 연결 이름
*
* @var string
*/
protected $connection = 'connection-name';
}
기본 속성 값
모델의 특정 속성에 대한 기본 값을 정의하려면 모델에서 $attributes
속성을 정의하세요:
class User extends Model
{
/**
* 모델의 기본 속성 값.
*
* @var array
*/
protected $attributes = [
'delayed' => false,
];
}
모델 검색
모델과 그에 연결된 데이터 테이블을 생성한 후, 데이터베이스에서 데이터를 쿼리할 수 있습니다. 각 Eloquent 모델을 강력한 쿼리 생성기로 생각할 수 있으며, 이를 통해 더 빠르게 관련된 데이터 테이블을 쿼리할 수 있습니다. 예를 들어:
$users = app\model\User::all();
foreach ($users as $user) {
echo $user->name;
}
팁: Eloquent 모델도 쿼리 생성기이므로 쿼리 생성기를 읽어 사용할 수 있는 모든 메서드를 확인해야합니다. 이러한 방법들을 Eloquent 쿼리에서 사용할 수 있습니다.
추가 제약
Eloquent의 all 메서드는 모델의 모든 결과를 반환합니다. 각 Eloquent 모델이 쿼리 생성기로 작용하므로 쿼리 조건을 추가한 후 get 메서드를 사용하여 쿼리 결과를 가져올 수 있습니다:
$users = app\model\User::where('name', 'like', '%tom')
->orderBy('uid', 'desc')
->limit(10)
->get();
모델 다시 로드
fresh 및 refresh 메서드를 사용하여 모델을 다시 로드할 수 있습니다. fresh 메서드는 데이터베이스에서 모델을 새로 검색합니다. 기존 모델 인스턴스는 영향을 받지 않습니다:
$user = app\model\User::where('name', 'tom')->first();
$fresh_user = $user->fresh();
refresh 메서드는 데이터베이스의 새로운 데이터로 기존 모델에 값을 다시 할당합니다. 또한 이미 로드된 관계도 다시 로드됩니다:
$user = app\model\User::where('name', 'tom')->first();
$user->name = 'jerry';
$user = $user->fresh();
$user->name; // "tom"
컬렉션
Eloquent의 all 및 get 메서드는 여러 결과를 쿼리할 수 있으며, Illuminate\Database\Eloquent\Collection
인스턴스를 반환합니다. Collection
클래스는 Eloquent 결과를 처리하기 위한 많은 보조 함수를 제공합니다:
$users = $users->reject(function ($user) {
return $user->disabled;
});
커서 사용
cursor 메서드는 커서를 사용하여 데이터베이스를 순회할 수 있도록 하며, 단 한 번의 쿼리만 수행합니다. 대량의 데이터를 처리할 때 cursor 메서드는 메모리 사용량을 크게 줄일 수 있습니다:
foreach (app\model\User::where('sex', 1)->cursor() as $user) {
//
}
cursor는 Illuminate\Support\LazyCollection
인스턴스를 반환합니다. 지연 컬렉션은 Laravel 컬렉션에서 대부분의 컬렉션 메서드를 사용할 수 있게 해주며, 매번 단일 모델만 메모리에 로드됩니다:
$users = app\model\User::cursor()->filter(function ($user) {
return $user->id > 500;
});
foreach ($users as $user) {
echo $user->id;
}
Selects 서브 쿼리
Eloquent는 고급 서브 쿼리 지원을 제공하여, 관련 테이블에서 정보를 단일 쿼리 문으로 추출할 수 있습니다. 예를 들어, 목적지 테이블 destinations와 목적지로 가는 항공편 테이블 flights가 있다고 가정해보겠습니다. flights 테이블에는 항공편이 언제 목적지에 도착하는지를 나타내는 arrival_at 필드가 포함되어 있습니다.
서브 쿼리 기능에서 제공하는 select 및 addSelect 메서드를 사용하면 단일 문으로 모든 목적지 destinations와 각 목적지에 마지막으로 도착한 항공편의 이름을 조회할 수 있습니다:
use app\model\Destination;
use app\model\Flight;
return Destination::addSelect(['last_flight' => Flight::select('name')
->whereColumn('destination_id', 'destinations.id')
->orderBy('arrived_at', 'desc')
->limit(1)
])->get();
서브 쿼리로 정렬
또한 쿼리 생성기의 orderBy 함수는 서브 쿼리도 지원합니다. 이 기능을 사용하여 마지막 항공편의 도착 시간에 따라 모든 목적지를 정렬할 수 있습니다. 이 역시 데이터베이스에 대해 단일 쿼리를 수행합니다:
return Destination::orderByDesc(
Flight::select('arrived_at')
->whereColumn('destination_id', 'destinations.id')
->orderBy('arrived_at', 'desc')
->limit(1)
)->get();
단일 모델 / 컬렉션 검색
지정된 데이터 테이블에서 모든 레코드를 검색하는 것 외에도 find, first 또는 firstWhere 메서드를 사용하여 단일 레코드를 검색할 수 있습니다. 이러한 메서드는 모델 인스턴스를 하나 반환하며, 모델 컬렉션을 반환하지 않습니다:
// 기본 키로 모델을 찾기...
$flight = app\model\Flight::find(1);
// 쿼리 조건에 맞는 첫 번째 모델 찾기...
$flight = app\model\Flight::where('active', 1)->first();
// 쿼리 조건에 맞는 첫 번째 모델을 빠르게 구현...
$flight = app\model\Flight::firstWhere('active', 1);
주 키 배열을 인자로 find 메서드를 호출하여 일치하는 레코드의 컬렉션을 반환할 수도 있습니다:
$flights = app\model\Flight::find([1, 2, 3]);
때때로 첫 번째 결과를 찾으려 할 때 값이 없으면 다른 동작을 수행하고 싶을 수 있습니다. firstOr 메서드는 결과를 찾았을 때 첫 번째 결과를 반환하고, 결과가 없으면 지정된 콜백을 실행합니다. 콜백의 반환 값은 firstOr 메서드의 반환 값이 됩니다:
$model = app\model\Flight::where('legs', '>', 100)->firstOr(function () {
// ...
});
firstOr 메서드는 동일하게 필드 배열을 받아서 쿼리할 수 있습니다:
$model = app\model\Flight::where('legs', '>', 100)
->firstOr(['id', 'legs'], function () {
// ...
});
"찾을 수 없음" 예외
모델을 찾을 수 없을 때 예외를 발생시키고 싶을 수도 있습니다. 이것은 컨트롤러와 라우트에서 매우 유용합니다. findOrFail 및 firstOrFail 메서드는 쿼리의 첫 번째 결과를 검색하며, 찾을 수 없을 경우 Illuminate\Database\Eloquent\ModelNotFoundException
예외를 발생시킵니다:
$model = app\model\Flight::findOrFail(1);
$model = app\model\Flight::where('legs', '>', 100)->firstOrFail();
컬렉션 검색
쿼리 생성기에서 제공하는 count, sum 및 max 메서드와 다른 컬렉션 함수를 사용하여 컬렉션을 조작할 수 있습니다. 이러한 메서드는 모델 인스턴스를 반환하는 것이 아니라 적절한 스칼라 값만 반환합니다:
$count = app\model\Flight::where('active', 1)->count();
$max = app\model\Flight::where('active', 1)->max('price');
삽입
데이터베이스에 새 레코드를 추가하려면 새 모델 인스턴스를 생성하고 인스턴스 속성을 설정한 후 save 메서드를 호출합니다:
<?php
namespace app\controller;
use app\model\User;
use support\Request;
use support\Response;
class FooController
{
/**
* 사용자 테이블에 새 레코드 추가
*
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
// 요청 검증
$user = new User;
$user->name = $request->get('name');
$user->save();
}
}
created_at
및 updated_at
타임스탬프는 자동으로 설정됩니다 (모델의 $timestamps
속성이 true일 때) , 수동으로 할당할 필요는 없습니다.
업데이트
save 메서드는 데이터베이스에서 이미 존재하는 모델을 업데이트하는 데에도 사용될 수 있습니다. 모델을 업데이트하려면 먼저 검색한 다음 업데이트할 속성을 설정하고 save 메서드를 호출합니다. 마찬가지로 updated_at
타임스탬프가 자동으로 업데이트되므로 수동으로 할당할 필요는 없습니다:
$user = app\model\User::find(1);
$user->name = 'jerry';
$user->save();
배치 업데이트
app\model\User::where('uid', '>', 10)
->update(['name' => 'tom']);
속성 변경 체크
Eloquent는 모델의 내부 상태를 확인하고 속성이 처음 로드된 이후 어떻게 변경되었는지 확인하기 위해 isDirty, isClean 및 wasChanged 메서드를 제공합니다.
isDirty 메서드는 로드 이후 모델의 속성이 변경되었는지 여부를 확인합니다. 특정 속성 이름을 전달하여 특정 속성이 변경되었는지를 확인할 수 있습니다. isClean 메서드는 isDirty와 반대이며, 선택적으로 속성 매개변수를 받을 수 있습니다:
$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->isClean(); // false
$user->isClean('title'); // false
$user->isClean('first_name'); // true
$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('first_name'); // false
배치 할당
create 메서드를 사용하여 새 모델을 저장할 수도 있습니다. 이 메서드는 모델 인스턴스를 반환합니다. 그러나 사용하기 전에 모델에서 fillable 또는 guarded 속성을 지정해야 합니다. 모든 Eloquent 모델은 기본적으로 배치 할당이 불가능합니다.
사용자가 요청을 통해 예기치 않은 HTTP 매개변수를 전달하고 해당 매개변수가 데이터베이스에서 변경할 필요가 없는 필드를 변경한다면 배치 할당 취약점이 발생할 수 있습니다. 예: 악의적인 사용자가 HTTP 요청을 통해 is_admin 매개변수를 전달하고 이를 create 메서드에 전달하게 되면, 사용자가 자신을 관리자 권한으로 승격시킬 수 있게 됩니다.
따라서 시작하기 전에 모델에서 어떤 속성이 배치 할당이 가능한지 정의해야 합니다. 모델의 $fillable
속성을 통해 이를 구현할 수 있습니다. 예를 들어, Flight 모델의 name 속성을 배치 할당이 가능하게 할 수 있습니다:
<?php
namespace app\model;
use support\Model;
class Flight extends Model
{
/**
* 배치 할당이 가능한 속성.
*
* @var array
*/
protected $fillable = ['name'];
}
배치 할당 가능한 속성을 설정한 후 create 메서드를 통해 새 데이터를 데이터베이스에 삽입할 수 있습니다. create 메서드는 저장된 모델 인스턴스를 반환합니다:
$flight = app\model\Flight::create(['name' => 'Flight 10']);
이미 모델 인스턴스가 있는 경우 fill 메서드에 배열을 전달하여 값을 할당할 수 있습니다:
$flight->fill(['name' => 'Flight 22']);
$fillable
은 배치 할당의 "화이트리스트"로 간주될 수 있으며, $guarded
속성을 사용하여 구현할 수도 있습니다. $guarded
속성은 배치 할당이 불가능한 배열을 포함합니다. 즉, $guarded
는 기능적으로 "블랙리스트"와 유사합니다. 주의: $fillable
또는 $guarded
중 하나만 사용할 수 있으며 동시에 사용할 수 없습니다. 아래 예제에서는 price 속성을 제외한 모든 속성이 배치 할당 가능합니다:
<?php
namespace app\model;
use support\Model;
class Flight extends Model
{
/**
* 배치 할당이 불가능한 속성.
*
* @var array
*/
protected $guarded = ['price'];
}
모든 속성을 배치 할당이 가능하게 하려면 $guarded
를 빈 배열로 정의할 수 있습니다:
/**
* 배치 할당이 불가능한 속성.
*
* @var array
*/
protected $guarded = [];
기타 생성 방법
firstOrCreate/ firstOrNew
여기에서 사용할 수 있는 두 가지 배치 할당 방법: firstOrCreate와 firstOrNew가 있습니다. firstOrCreate 메서드는 주어진 키/값 쌍으로 데이터베이스의 데이터를 매칭합니다. 데이터베이스에서 모델을 찾을 수 없으면 첫 번째 인수의 속성과 선택적인 두 번째 인수의 속성을 포함한 새 레코드를 삽입합니다.
firstOrNew 메서드는 firstOrCreate 메서드와 유사하게 주어진 속성으로 데이터베이스에서 레코드를 찾으려고 시도합니다. 그러나 firstOrNew 메서드가 해당 모델을 찾을 수 없으면 새로운 모델 인스턴스를 반환합니다. firstOrNew가 반환하는 모델 인스턴스는 아직 데이터베이스에 저장되지 않으므로 수동으로 save 메서드를 호출하여 저장해야 합니다:
// 이름으로 항공편을 검색하고 존재하지 않으면 생성...
$flight = app\model\Flight::firstOrCreate(['name' => 'Flight 10']);
// 이름으로 항공편을 검색하고 이름 및 지연 속성과 도착 시간 속성을 사용하여 생성...
$flight = app\model\Flight::firstOrCreate(
['name' => 'Flight 10'],
['delayed' => 1, 'arrival_time' => '11:30']
);
// 이름으로 항공편을 검색하고 존재하지 않으면 인스턴스를 생성...
$flight = app\model\Flight::firstOrNew(['name' => 'Flight 10']);
// 이름으로 항공편을 검색하고 존재하지 않으면 모델 인스턴스를 생성...
$flight = app\model\Flight::firstOrNew(
['name' => 'Flight 10'],
['delayed' => 1, 'arrival_time' => '11:30']
);
또한 기존 모델을 업데이트하거나 존재하지 않을 경우 새 모델을 생성하는 것이 필요할 수 있습니다. updateOrCreate 메서드를 사용하여 이를 단숨에 구현할 수 있습니다. firstOrCreate 메서드와 유사하게 updateOrCreate는 모델을 지속화하므로 save()를 호출할 필요가 없습니다:
// 오클랜드에서 샌디에이고로 가는 항공편이 있다면 가격은 99달러입니다.
// 일치하는 모델이 없는 경우 새 모델을 생성합니다.
$flight = app\model\Flight::updateOrCreate(
['departure' => 'Oakland', 'destination' => 'San Diego'],
['price' => 99, 'discounted' => 1]
);
모델 삭제
모델 인스턴스에서 delete 메서드를 호출하여 인스턴스를 삭제할 수 있습니다:
$flight = app\model\Flight::find(1);
$flight->delete();
기본 키로 모델 삭제
app\model\Flight::destroy(1);
app\model\Flight::destroy(1, 2, 3);
app\model\Flight::destroy([1, 2, 3]);
app\model\Flight::destroy(collect([1, 2, 3]));
쿼리로 모델 삭제
$deletedRows = app\model\Flight::where('active', 0)->delete();
모델 복사
replicate 메서드를 사용하여 데이터베이스에 저장되지 않은 새로운 인스턴스를 복사할 수 있습니다. 모델 인스턴스가 많은 동일한 속성을 공유할 때 이 메서드는 매우 유용합니다:
$shipping = App\Address::create([
'type' => 'shipping',
'line_1' => '123 Example Street',
'city' => 'Victorville',
'state' => 'CA',
'postcode' => '90001',
]);
$billing = $shipping->replicate()->fill([
'type' => 'billing'
]);
$billing->save();
모델 비교
때때로 두 모델이 "같은지" 판단해야 할 필요가 있을 수 있습니다. is 메서드는 두 모델이 동일한 기본 키, 테이블 및 데이터베이스 연결을 가지고 있는지 빠르게 확인하는 데 사용할 수 있습니다:
if ($post->is($anotherPost)) {
//
}
모델 옵저버
Laravel에서의 모델 이벤트 및 옵저버를 참조하세요.
주의: Eloquent ORM이 모델 옵저버를 지원하려면 추가로 composer require "illuminate/events"
를 임포트해야 합니다.
<?php
namespace app\model;
use support\Model;
use app\observer\UserObserver;
class User extends Model
{
public static function boot()
{
parent::boot();
static::observe(UserObserver::class);
}
}
트랜잭션
데이터베이스 트랜잭션을 참조하세요.