라라벨에서 쿼리를 작성하는 방법은 간단하다. 레거시 PHP 에서 쿼리를 작성하는 방법은 PDO(PHP Data Object)를 사용하더라도 그다지 보기 좋은 것은 아니었다. 예를 들자면,
$pdo = new PDO(...);
$sth = $pdo->prepare("SELECT * FROM users");
if ($sth->execute()) {
$users = [];
while ($user = $sth->fetchObject()) {
array_push($users, $user);
}
}
이렇게 생긴 기존의 레거시 코드(mysqli, mysql 과 같은 더 low Level API 를 사용하면 보기가 안 좋아진다.)를 라라벨의 쿼리빌더를 다음과 같이 간단하게 표현할 수 있다.
$users = DB::table('users')->get();
더 나아가 옐로퀸트 ORM 을 사용하면 모델(User 클래스와 users 테이블이 연결된)과 조합하여 사용한다면 더 간단하게 표현하는 것도 가능하다.
$users = User::all();
레거시를 사용했을 때는 준비, 실행, 가져오기 과정을 PDO 를 통해 반복해야 했지만, 라라벨에서 제공하는 쿼리빌더, 예로퀸트를 통해 더욱 간단해질 수 있다.
쿼리빌더(Query Builder)
쿼리빌더는 라라벨에서 쿼리를 작성하기 위한 도구라고 볼 수 있다. 별도의 패키지라기 보다는 라라벨에 내장되어 있어서 언제든 사용할 수 있다. DB 파사드를 사용하면 간단하게 사용할 수 있다. 간단한 호출부터 살펴보자.
insert(), update(), select(), delete()
흔히 말하는 CRUD(Create, Read, Update, Delete)에 해당하는 작업들은 직접 쿼리를 적는것과 사실 큰 차이가 없다. 다만 PDO 를 사용하지만, 내부에서 준비과정이 발생하므로 명시적으로 쿼리를 준비할 필요는 없다는 점이다.
// INSERT
DB::insert("INSERT INTO users VALUES (:id)", ['id' => 1]);
// SELECT
DB::select("SELECT * FROM users WHERE id = :id", ['id' => 1]);
// UPDATE
DB::update("UPDATE users SET name = :name WHERE id = :id", ['name' => 'Delfina Kutch', 'id' => 1]);
// DELETE
DB::delete("DELETE FROM users WHERE id = :id", ['id' => 1]);
이는 가장 기본적으로 사용할 수 있는 형태이지만 잘 사용되지는 않는다. 일반적으로는 모델을 통해 쿼리를 만들어 보내기 때문이다.
체이닝
메서드 체이닝을 하듯 쿼리빌더도 체이닝하여 사용할 수 있다. 위에 나왔던 코드는 더 예쁘게 다음과 같이 사용할 수도 있다.
// INSERT
DB::table('users')->insert(['id' => 1]);
// UPDATE
DB::table('users')->where('id', 1)->update(['name' => 'Delfina Kutch']);
// SELECT
DB::table('users')->where('id', 1)->get();
// DELETE
DB::table('users')->where('id', 1)->delete();
위에서 where()
를 썼는데, where()->where()
처럼 연속으로 쓰면 AND 가 되고, orWhere()
를 쓰면 OR 처리가 된다. where()
이외에도 groupBy(), orderBy(), having()
등 사용할 수 있어서 유연하게 호출하여 쿼리를 만들어낼 수 있다. 이 글에서 쿼리빌더에 있는 메서드를 나열하는 것은 불필요하기 때문에 API 문서를 참고해보자.
https://laravel.com/api/8.x/Illuminate/Database/Query/Builder.html
$query->when(Closure)
해당 메서드를 사용하게 되면 조건에 따라 쿼리 포함 여부를 결정할 수 있다. 아래의 코드는 $isOnwer
가 참이면 클로저에 정의된 쿼리를 포함시킨다.
DB::table('users')->when($isOnwer, function ($query) {
return $query->where('role', 'Owner');
});
원시
추가적으로, 쿼리를 원시형태로 하려면 DB::raw()
를 사용하면 되고, DB::selectRaw()
처럼 사용할 수도 있으나 보안에 문제가 발생할 가능성이 있기때문에 사용에 각별히 유의해야 한다.
DB::raw("SELECT * FROM users");
트랜잭션
트랜잭션은 쿼리 여러 개를 묶은 일련의 과정이므로 작업의 단위로써 사용될 수 있다. 트랜잭션 내부에 쿼리를 사용하면서도 포함된 쿼리가 하나라도 실패한 경우, 이전에 실행한 것 또한 롤백(Rollback)된다.
DB::transaction(function () {
// ...
});
// Or
DB::beginTransaction();
//
if (false) {
DB::rolllback();
}
DB::commit();
이렇게 사용하거나 DB::beginTransaction()
을 시작으로 실패시 DB::rollback()
으로 이전에 작업한 것들을 되돌리거나, 성공시 DB::commit()
를 명시적으로 호출하여 사용할 수 있다.
모델 & 옐로퀸트(Eloquent)
옐로퀸트는 라라벨에서 제공하는 ORM(Object Relational Mapping)이다. 데이터베이스 테이블에 대응하는 모델(Model)의 프로퍼티에 매핑되는 액티브레코드 ORM 이다. 예를 들어 데이터베이스에 contracts
테이블이 있다면, 모델은 아래와 같이 생성할 수 있다. 모델은 라라벨에서 데이터베이스 테이블에 대응하는 클래스하고 생각하면 편하다. 모델을 생성할 때는 아티즌의 도움을 받을 수 있다.
php artisan make:model Contract
이렇게 생성된 모델은 관례에 따라 contracts
테이블에 연결되며 이후 $table
프로퍼티를 따로 설정하여 임의로 테이블의 이름을 바꿀 수 있으며 해당 모델을 통해 contracts
테이블의 레코트에 할 수 있는 추가, 갱신 등의 작업들을 처리할 수 있게 된다.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Contract extends Model {}
이렇게 만들어진 모델은 컨트롤러 내부에서 직접 쿼리빌더처럼 사용할 수 있으며 파사드처럼 호출하는 메서드를 호출하는 것이 가능하다.
// INSERT
Contract::create([
'name' => 'Delfina Kutch'
]);
// INSERT
$contract = new Contract([
'name' => 'Delfina Kutch'
]);
$contract->save();
// UPDATE
$contract = Contract::find(1);
$contract->update(['name' => 'Junius Treutel DDS']);
// SELECT
$contract = Contract::findOrFail(1);
// DELETE
$contract->delete();
모델 클래스의 이름을 직접 사용하여 파사드처럼 쓰거나 객체를 만들고 메서드를 호출하여 사용할 수 있다. 쿼리빌더에서 사용가능한 대부분의 메서드를 옐로퀸트 모델에서도 호출할 수 있다. get(), where()
와 같은 것들 말이다. 위와 같은 형태로 사용하려면 마이그레이션(Migration)이나 수동적으로 contracts
테이블을 미리 생성해두어야 하며 모델의 $fillable
프로퍼티에 name
속성이 할당 가능하도록 만들어야 한다.
https://laravel.com/api/8.x/Illuminate/Database/Eloquent/Model.html
스코프(Scoped)
모델이나 DB 파사드를 사용하여 조회할 때 where()
를 사용하여 데이터의 범위를 제한했었는데, 스코프 기능을 사용하면 각 쿼리마다 where()
로 처리해주어야 했던 것들을 간단하게 처리할 수 있다. 예를 들면 name
이 SangWoo Jeong
인 레코드만을 얻어오려면 다음과 같이 한다.
$contracts = Contract::where('name', 'Delfina Kutch')->get();
이 예제는 간단하니까 상관없지만, 복잡한 조건이 달려있는 옐로퀸트 모델 객체를 얻어오기 위해 매번 저렇게 써야한다면 코드가 지저분해 질 것이다. 그래서 라라벨에선 로컬 스코프(Local Scope)와 글로벌 스코프(Global Scope)기능이 있다. 로컬 스코프를 사용하면 다음과 같이 해볼 수 있다.
$contracts = Contract::owner()->get();
로컬 스코프
위와 같이 사용하고 싶다면 다음과 같이 모델에 스코프를 정의해보자. 이렇게 사용하면 복잡한 조건을 가진 모델을 조회할 때 보다 간단하게 처리해줄 수 있다.
class Contract extends Model
{
public function scopeOwner($query)
{
return $query->where('role', 'Owner');
}
}
글로벌 스코프
글로벌 스코프를 사용하면 명시적으로 스코프에 해당하는 메서드를 호출하지 않더라도 해당 모델에 대해 처리할 때 무조건 스코프가 적용된다. 이 작업은 모델의 boot()
메서드에서 할 수 있다. 이 메서드는 모델들이 상속받는 Model
클래스에 이미 정의가 되어있고, 우리는 이를 재정의하는 것이 아니라 확장을 할 것이기 때문에 부모의 메서드도 호출시켜준다.
class Contract extends Model
{
protected static function boot()
{
parent::boot();
static::addGlobalScope('onwer', function (Builder $builder) {
$builder->where('role', '=', 'Owner');
});
}
}
PHP 클래스 내부에서 사용할 수 있는 키워드는 parent, self, static
의 3개가 있는데, static 키워드의 3번째 쓰임새 중 하나는 늦은(Lazy) 정적 바인딩에 쓰이는 것이다. 이를 사용하면 상속관계에서 자식에서 재정의 되지 않은 메서드를 자식 객체에서 호출하더라도 스코프는 자식 객체가 된다. PHP: Static (정적 변수, 정적 메서드, 늦은 정적 바인딩)을 참고하자.
만약 글로벌 스코프를 적용하고 싶지 않다면 withoutGlobalScope()
를 호출한다.
$contracts = Contract::withoutGlobalScope('owner')->get();
모델과 옐로퀸트 부분은 아직 이야기하기 기능이 제법 많다. 접근자, 변경자, 관계 설정 등의 기능이 있다. 그러한 것들은 다음 포스트에서 알아보자.
더 읽을거리
https://laravel.com/docs/8.x/queries
https://laravel.com/docs/8.x/eloquent