10강 - 엘로퀀트 ORM

엘로퀀트는 라라벨의 ORM (Object Relational Mapper, Active Record Pattern 의 구현체)이다. 데이터베이스는 테이블간 관계를 가지고 있다. 데이터베이스를 추상화한 모델 클래스 간에 관계를 맺어 주는 구현체를 일반적으로 ORM이라 칭한다. 뿐만 아니라 라라벨에서는 config/database.php에 의해 설정된 DB Driver와 ORM 사용으로 인해 데이터베이스와 어플리케이션 간에 디커플링 효과도 얻게 된다. 어플리케이션 코드 수정 한 줄 없이 mySQL을 SQLite로 바꿀 수 있다는 의미이다.

모델

라라벨이 MVC 프레임웍인데 우리가 이제까지 본 것은 V(뷰) 뿐이었다. 드디어 M에 해당하는 모델을 볼 차례이다. authors 라는 테이블을 생성하자.

$ mysql -uhomestead -p
mysql > CREATE TABLE authors(
    -> id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    -> email VARCHAR(255) NOT NULL,
    -> password VARCHAR(60) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
mysql > INSERT INTO authors(email, password) VALUES('john@example.com', 'password');

참고 이번까지는 수작업으로 테이블을 생성했다. 다음 번 부터는 migration을 통해 테이블을 생성하고, seed를 통해 데이터를 생성할 것이다.

모델을 생성하자. artisan CLI에서 제공하는 Generator 를 이용하자. 코맨드를 수행한 후 app/Post.php, app/Author.php 파일이 생성되었는 지 확인하자.

$ php artisan make:model Post
$ php artisan make:model Author

참고 테이블 이름을 users 와 같이 복수로, 모델 이름은 User 처럼 단수로 하는 것이 규칙이다. DB 테이블은 Collection을 담고 있고, 모델은 DB 테이블에 담긴 하나의 레코드를 클래스로 표현한 것이기 때문이다. 규칙을 지킬 수 없는 경우에는, 가령 모델명을 Author, 테이블을 users 라 했을 경우, 라라벨이 둘 간의 관계를 알 수 있도록 Author 모델에 protected $table = 'users'; 코드를 추가해 주어야 한다.

처음 만나는 엘로퀀트 쿼리

전체 Collection을 가져오자. 이번엔 DB Facade를 이용하지 않고, App\Author 모델을 이용한 것을 눈여겨 보자.

$ php artisan tinker
>>> App\Author::get(); # == DB::table('authors')->get();
=> Illuminate\Database\Eloquent\Collection {#677
     all: [
       App\Author {#678
         id: 1,
         email: "john@example.com",
         password: "password",
       },
     ],
   }

참고 쿼리 빌더 에서 사용할 수 있는 대부분의 메소드는 Eloquent Model에서도 사용할 수 있다. 예를 들면, App\Author::orderBy('id', 'desc')->limit(1)->lists('email');와 같이. 사실, 이번 강좌에서 보는 것은 엘로퀀트를 상속한 모델을 이용해서 쿼리 빌더를 쓸 수 있다는 정도이다. 엘로퀀트의 전부가 아니란 얘기다. 엘로퀀트 ORM의 꽃은 모델간의 관계 맺기 이며, 18강에서 다루고 있다.

새로운 Instance를 생성하고 데이터베이스에 저장해 보자.

$ php artisan tinker
>>> $author = new App\Author;
>>> $author->email = 'foo@bar.com';
>>> $author->password = 'password';
>>> $author->save(); # 메모리에만 존재하던 인스턴스를 데이터베이스에 저장한다.
# Illuminate\Database\QueryException with message '... updated_at in ...'

QueryException

save() 메소드 호출에서 예외가 발생했을 것이다. 엘로퀀트는 모든 모델이 updated_atcreated_at 필드가 있다고 가정하고, 새로운 Instance가 생성될 때 현재의 timestamp값을 입력하려한다. 그런데, 위 테이블들은 수작업으로 만든 테이블이라 앞서 말한 필드들이 존재하지 않는다. 방법은 필드를 추가하는 방법과, timestamp 입력을 모델에서 끄는 방법이 있는데, 실습을 위해 일단 끄자.

// Post 모델도 적용해 주자.
class Author extends Model
{
    public $timestamps = false;
}

중요 모델에 변경이 생기면 실행중이던 tinker 를 다시 실행해 주어야 한다. tinker 가 로드될 시점에 라라벨 구동을 위한 모든 환경이 로드되므로, 이후 변경을 반영하기 위함이다. ctrl + C 또는 >>> exit 명령으로 종료할 수 있다. tinker를 재실행 한 후 up 화살표로 이전 코맨드 이력을 탐색할 수 있다.

>>> $author = new App\Author;
>>> $author->email = 'foo@bar.com';
>>> $author->password = 'password';
>>> $author->save();
=> true

다른 메소드를 이용한 모델 생성

이번에는 save() 대신 create() 메소드를 이용할 것이다.

$ php artisan tinker
>>> App\Author::create([
... 'email' => 'bar@baz.com',
... 'password' => bcrypt('password')
... ]);
# Illuminate\Database\Eloquent\MassAssignmentException with message 'email'

참고 bcrypt(string $value) 은 암호화된 60byte스트링를 만들어 준다. Facade로 쓰면 Hash::make(string $value) 와 같다.

MassAssignmentException

timestamps를 무력화 시킨 후에도, create() 메소드를 이용할 때는 에러가 발생했다. create() 메소드로 모델 인스턴스를 생성할 때는 해당 모델에 $fillable 속성을 지정해 주어야 한다. 폼을 통해 사용자가 넘긴 값을 그대로 DB에 넣을 경우를 대비해, 악의적인 필드가 입력되는 것을 방지하기 위한 조치이다. Post와 Author 모델을 열고 $fillable 속성을 지정하자.

class Author extends Model
{
    protected $fillable = ['email', 'password'];
}
class Post extends Model
{
    protected $fillable = ['title', 'body'];
}

tinker 를 재시작하고 up 키를 눌러, create() 메소드를 다시 실행해 보자.

$ php artisan tinker
>>> App\Author::create([
... 'email' => 'bar@baz.com',
... 'password' => bcrypt('password')
... ]);
=> App\Author {#680 # bcrypt() Helper에 의해 암호화된 60 byte 패스워드를 확인하자.
     email: "bar@baz.com",
     password: "$2y$10$tL/9voTNRtH7dfE9yULVaOybUWTcNkLRws9gTawcU85L3PEwRotUS",
     id: 3,
   }



comments powered by Disqus
목록 토글
keyboard_arrow_up