코드이그나이터4 데이터베이스 다루기 - 3 - 모델의 정의와 기본 사용법

koeunyeon·2021년 3월 29일
0

코드이그나이터4의 모델이란?

코드이그나이터4에서 모델은 데이터베이스 테이블과 매핑되는 PHP 클래스입니다. 모델을 통해서 데이터베이스에 생성, 읽기, 수정, 삭제(Create, Read, Update, Delete) 를 할 수 있습니다.
혹시 ORM을 다루어본 적이 있으시다면 주의해야 할 점이 있습니다. 코드이그나이터의 모델은 "컬럼"과 1:1로 매핑되지 않습니다. 오히려 리파지터리(repository)에 가까운 객체입니다.

기본 모델 다루기

이번 챕터의 코드는 https://github.com/koeunyeon/ci4/tree/model-basic 에 있습니다.

모델 클래스 만들기

이제 모델을 생성해 보겠습니다. /app/Models 디렉토리 아래에 SampleModel.php 파일을 생성하고 아래의 내용으로 대체합니다.

/app/Models/SampleModel.php

<?php


namespace App\Models; // (1)

use CodeIgniter\Model; // (2)

class SampleModel extends Model  // (3)
{
    protected $table = 'sample';  // (4)
    protected $allowedFields = ['name','age'];  // (5)
    protected $primaryKey = "sample_id";  // (6)
}
 // (7)

한 줄씩 살펴보겠습니다.
(1) 네임스페이스를 파일 경로와 동일하게 App\Models로 설정합니다.

(2) (3) 코드이그나이터4에서 모든 모델은 CodeIgniter\Model 클래스를 상속합니다. 따라서 CodeIgniter\Model 클래스를 사용하기 위해 use문으로 클래스를 가지고 옵니다.

(3) Model 클래스를 상속한 MemberModel 을 선언합니다. 모델 클래스 이름은 PHP 표준 권고안인 PSR-1에 따라 PascalCase를 따릅니다.

(4) 데이터베이스 테이블 이름을 $table 변수에 설정합니다. $table 변수는 모델의 필수값입니다.

(5) allowedFields 은 외부에서 설정 가능한 열을 나열합니다.
sample 테이블을 예시로 들어보면, ['name', 'age']allowedFields에 설정되어 있으므로 데이터베이스에 열의 값 생성, 수정이 가능합니다. 하지만 sample_id 열은 allowedFields 항목에 지정되어 있지 않으므로 혹여 모델에 값을 설정했다고 하더라도 실제 데이터베이스에 저장되지 않습니다.
$allowedFields 변수는 모델의 필수값입니다.

(6) 데이터베이스의 키 이름을 $primarKey 변수에 지정합니다. 반드시 필요한 것은 아니지만, update, delete 메소드를 사용할 때는 필요합니다. 생략되면 id 라고 가정합니다.

(7) PSR-12(구버전-PSR2)에 따라 PHP code만 존재하는 파일에서는 닫는 ?>태그를 생략합니다. 이는 닫는 태그 ?> 이후 공백이 있으면 출력에 영향이 있을 수도 있기 때문입니다.

모델로 데이터 입력하기

모델 기능을 테스트하기 위해 간단한 컨트롤러를 만들겠습니다. /app/Controllers 디렉토리 아래에 Model.php 파일을 생성하고 아래의 내용으로 대체합니다.

/app/Controllers/Model.php

<?php


namespace App\Controllers;


use App\Models\SampleModel; // (1)

class Model extends BaseController
{
    public function create()
    {
        $sampleModel = new SampleModel();  // (2)
        $data = [  // (3)
            'name' => 'ci4',
            'age' => 1
        ];

        try {  // (4)
            $result = $sampleModel->insert($data);  // (5)
            return "$result";  // (6)
        } catch (\ReflectionException $e) {  // (7)
            return $e->getMessage();
        } catch(\DataException $e){ // (8)
            return $e->getMessage();
        }
    }
}

한 줄씩 살펴보겠습니다.
(1) 모델을 사용하기 위해 use문으로 모델을 불러옵니다.

(2) 모델 객체를 $sampleModel이라는 이름으로 생성합니다. 일반 객체와 사용법은 동일합니다.

(3) 입력할 데이터를 설정합니다. 대부분의 경우 이 데이터는 어디선가에서 전달받은 값이겠지만, 지금은 기능 확인을 위해 임의의 값을 넣었습니다.

(4) 코드이그나이터4에서는 insert가 실패할 경우 오류를 발생시킵니다. 오류 메세지가 사용자에게 그대로 보여지는 것은 좋지 않으므로 try catch로 감쌉니다.
전통적으로 PHP는 예외(exception)를 느슨하게 사용하고 오류(error)를 적극적으로 사용하며 왠만해서는 경고(warning) 으로 그치는데다가 심각한 경우에만 치명적인 오류(fatal error)를 발생시키는 언어였습니다. 이는 언어의 디자인과 철학이 "어떻게든 돌아가게는 만든다"였기 때문에, "논리적인 오류가 있어도 무슨 수를 써서든 동작은 된다"는 논리와 동일했기 때문입니다.
처음 PHP가 나왔던 시절에는 괜찮은 생각이었을 지도 모르겠으나, 지금은 그다지 현명한 방법으로 여겨지는 것은 아닙니다. try catch로 감싸도 오류가 날 수 있다는 것은 시스템의 안정성에 문제가 있다는 뜻이거든요.
다행이도 PHP7에서는 오류도 try catch로 잡아낼 수 있게 변경되었습니다. 이제 치명적인 오류만 아니라면 PHP 는 정상적으로 실행됩니다.

(5) $sampleModel 인스턴스를 이용해 데이터를 입력합니다. 파라미터는 입력할 데이터입니다. 이미 SampleModel 클래스는 어떤 테이블에 데이터를 넣어야 할 지와 어떤 필드를 사용해야 할 지 알고 있기 때문에 데이터를 입력할 수 있습니다.

/app/Models/SampleModel.php

protected $table = 'sample';
protected $allowedFields = ['name','age'];

(6) 정상적으로 값이 입력되었다면 $result 변수에는 자동으로 sample테이블의 PK인 sample_id의 값이 담깁니다.

(7) 코드이그나이터4의 insert메소드 정의에는 ReflectionException을 발생시킨다고 정의하고 있습니다.
vender/codeigniter4/framework/system/Model.php

* @throws \ReflectionException

따라서 ReflectionException을 잡을 수 있도록 catch를 설정합니다.

catch (\ReflectionException $e)

(8) 하지만 실제 코드이그나이터4 소스코드를 보면 throw 구문이 DataException을 던집니다.
vender/codeigniter4/framework/system/Model.php

throw DataException::forEmptyDataset('insert');

DataException 클래스와 ReflectionException 클래스는 아무런 연관관계가 없으므로 주석 버그가 아닌지 싶기는 합니다만, 정확한 내부 동작을 몰라 일단 ReflectionExceptionDataException 둘 다 catch에서 잡아내도록 정의합니다.

catch(\DataException $e)

브라우저에서 http://localhost:8080/model/create에 접속해 봅니다.


실제로 데이터베이스에 데이터가 들어갔는지 확인해 보겠습니다.
PHPStorm - Database - Schemas - ci4db - sample 항목을 더블클릭해서 데이터를 확인할 수 있습니다.

모델로 데이터 읽기

모델을 이용해서 데이터베이스의 데이터를 읽는 방법을 알아보겠습니다. Model 컨트롤러에 아래의 메소드 세개를 추가합니다. 각각의 메소드는 여러개의 목록을 반환하는 방법, 첫번째 행을 반환하는 방법, 그리고 ID로 편리하게 첫번째 행을 찾는 방법을 보여줍니다.

/app/Controllers/Model.php

public function readall(): \CodeIgniter\HTTP\Response // (1)
{
    $sampleModel = new SampleModel();
    $data = $sampleModel
        ->where('name', 'ci4') // (2)
        ->findAll(); // (3)

    $last_query = $sampleModel->db->getLastQuery(); // (4)
    error_log(print_r($last_query, true)); // (5)

    return $this->response->setJSON($data);
}

public function readfirst(): \CodeIgniter\HTTP\Response //(6)
{
    $sampleModel = new SampleModel();
    $data = $sampleModel
            ->where(['name'=>'ci4']) // (7)
            ->orderBy('sample_id', 'desc') // (8)
            ->first(); // (9)

    error_log($sampleModel->db->showLastQuery()); // (10)
    

    return $this->response->setJSON($data);
}

public function find(){ // (11)
    $sampleModel = new SampleModel();
    $data = $sampleModel->find(1); // (12)
    return $this->response->setJSON($data);
}

(1) 첫번째 메소드 readall은 모든 데이터를 읽습니다.

(2) where는 "검색 조건"을 나타냅니다. 데이터베이스의 where와 동일한 의미입니다. where(키,값) 형태로 쓰이면 키 = 값, 즉 "과 같다면" 이라는 뜻이 됩니다.
where처럼 모델에 메소드를 통해 데이터베이스에 질의를 하는 것을 "쿼리 빌더(Query Builder)" 라고 부릅니다.

(3) findAll 메소드는 모든 데이터를 순차배열 형태로 가지고 올 때 사용합니다.

(4) 코드이그나이터4에서 마지막으로 실행된 쿼리를 가져오기 위해서는 모델->db->getLastQuery() 로 읽을 수 있습니다.
다만 객체를 DUMP하는 것은 가능하지만, 객체 내부는 protected로 선언되어 있으므로 직접 사용은 불가능합니다. 만약 getLastQuery()의 내부 데이터를 꼭 가져와야 한다면 CodeIgniter\Database\Query를 상속한 클래스 A를 만들고, 결과를 A로 캐스팅한 다음 getter등을 이용해 가져와야 합니다.

(5) getLastQuery 메소드의 결과를 출력합니다.
error_log는 출력을 브라우저에 보내는 응답이 아니라 시스템 로그에 씁니다. print_r 함수의 두번째 파라미터가 true이면 출력을 바로 응답(response) 스트림에 보내는 것이 아니라 출력을 반환하므로, 반환한 값을 error_log 함수를 이용해 시스템 로그에 쓰게 되는 것입니다.
getLastQuery() 는 원본 쿼리, 최종 쿼리, 파라미터, 커넥션 정보 등 여러가지 정보를 한번에 보여줍니다. 보통은 쿼리와 파라미터가 실행되는 것을 볼 일이 많으므로 두가지 정보만 예제로 보면 아래와 같습니다.

[originalQueryString:protected] => SELECT *
FROM `sample`
WHERE `name` = :name:
    [finalQueryString:protected] => SELECT *
FROM `sample`
WHERE `name` = 'ci4'
    [binds:protected] => Array
        (
            [name] => Array
                (
                    [0] => ci4
                    [1] => 1
                )

        )

originalQueryString은 원본 쿼리 문자열을, binds는 쿼리 파라미터를 나타냅니다.finalQueryStringbinds 쿼리 파라미터가 originalQueryString에 바인딩 된 후의 최종 쿼리 문자열입니다.

(6) 두번째 엔드포인트 readfirst 메소드는 검색 결과 중 첫번째 열만 가지고 옵니다.

(7) 쿼리 빌더의 where 메소드는 (2) 처럼 where(키,값) 파라미터 형태로 사용할 수도 있지만 where(연관배열) 형태로 사용할 수도 있습니다. 주로 조건이 하나인 경우에는 키,값 형태를 사용하고 조건이 여러개인 경우에는 연관배열 형태를 사용하게 됩니다.아래의 두개의 where는 똑같은 쿼리를 생성합니다.

where('name', 'ci4')
where(['name' => 'ci4'])

(8) 쿼리 빌더에서 정렬은 데이터베이스처럼 orderBy 메소드를 이용합니다. 첫번째 인수는 컬럼 이름, 두번째 인수는 순차/역순 정렬 여부로 각각 'asc' 혹은 'desc' 입니다. 두번째 인수가 생략되면 순차 정렬을 뜻하는 'asc'로 가정합니다.

(9)first 함수는 "첫번째" 행만 가지고 옵니다.

(10) showLastQuery() 메소드는 마지막 실행한 쿼리를 "문자열"로 가지고 옵니다. 예시의 출력은 아래와 같습니다.

SELECT *
FROM `sample`
WHERE `name` = 'ci4'
ORDER BY `sample_id` DESC
LIMIT 1

코드이그나이터4는 데이터베이스 종류에 따라 서로 다른 쿼리를 만들어내는데, MySQL 계열의 데이터베이스에서는 갯수를 제한하기 위해 limit 1을 사용하는 것을 알 수 있습니다.

(11) 세번째 엔드포인트 메소드 find는 데이터베이스의 주 키(Primary Key)를 가지고 간단하게 데이터를 가져올 수 있는 방법을 보여줍니다.

(12) 모델의 find 메소드는 파라미터로 primary key의 값을 입력받습니다. find 메소드를 사용하려면 반드시 모델 클래스의 $primaryKey 멤버변수를 설정하거나, 혹은 데이터베이스의 주 키 이름이 id여야 합니다.


브라우저로 http://localhost:8080/model/readall 에 접속해서 데이터가 모두 잘 나오는지 확인해 봅시다.

브라우저로 http://localhost:8080/model/readfirst 에 접속해서 데이터가 잘 나오는지 확인해 봅시다.

브라우저로 http://localhost:8080/model/find 에 접속해서 데이터가 잘 나오는지 확인해 봅시다.

모델로 데이터 수정하기

모델을 이용해서 데이터베이스의 데이터를 수정하는 방법을 알아보겠습니다.
Model 컨트롤러에 아래 세개의 메소드를 추가합니다.

/app/Controllers/Model.php

public function update() // (1)
{
    $sampleModel = new SampleModel();
    $sampleModel->update(1, ['name' => 'update']); // (2)

    return $this->response->setJSON($sampleModel->find(1)); // (3)

}

public function save() // (4)
{
    $sampleModel = new SampleModel();
    $sample_data = $sampleModel->find(1);  // (5)
    $sample_data['name'] = "save";  // (6)
    $sampleModel->save($sample_data);  // (7)
    return $this->response->setJSON($sampleModel->find(1));
}

public function qbupdate()  // (8)
{
    $sampleModel = new SampleModel();
    $sampleModel
        ->where("sample_id", 1)  // (9)
        ->set(['name' => 'qbupdate'])  // (10)
        ->update();  // (11)

    return $this->response->setJSON($sampleModel->where('sample_id', 1)->first());
}

코드를 확인해 보겠습니다.
(1) 첫번째 엔드포인트 update는 모델 클래스의 update 메소드를 이용해 데이터를 수정하는 방법을 보여줍니다.

(2) 모델 클래스의 update 메소드는 파라미터를 2개 입력받습니다. 첫번째는 주 키의 값, 두번째는 갱신할 데이터입니다.
find 메소드와 마찬가지로 update메소드도 주 키 이름이 모델 클래스->$primaryKey에 설정되어 있거나 데이터베이스 테이블 주 키 이름이 id여야 합니다.
두번째 파라미터는 연관 배열입니다. 각 키는 열 이름, 값은 변경할 데이터입니다.

(3) 정상적으로 수정되었는지 확인하기 위해 데이터베이스에 결과를 조회한 후 JSON 형식으로 응답합니다. 이렇게 해 두면 직접 데이터베이스의 데이터를 조회해 보지 않아도 결과를 확인할 수 있습니다.

(4) 두번째 엔드포인트 save는 모델 클래스의 save 메소드를 이용해 데이터를 수정하는 방법을 보여줍니다.

(5) 모델의 find 메소드로 데이터를 검색합니다.

(6) 저장할 데이터의 name 속성을 save로 변경합니다.

(7) 모델의 save메소드를 이용해서 데이터를 저장합니다. 파라미터로는 연관배열 혹은 클래스를 취합니다.
save메소드가 재미있는 점은 upsert로 작동한다는 것입니다. 즉, 만약 기존의 데이터가 존재한다면 update를, 기존의 데이터가 존재하지 않는다면 insert를 합니다.
다만 주의해야 할 것이 코드이그나이터4에서 기존의 데이터가 존재하는지 확인하는 것은 실제로 데이터베이스에 조회를 해 보는 것이 아니라 주 키에 해당하는 데이터가 있는지 여부입니다.
본 예시에서 sample_datafind(1) 메소드를 통해 가져왔습니다. 이 시점에서 이미 sample_data['sample_id'] 의 값은 1임이 정해져 있는 상태입니다. 따라서 코드이그나이터는 save 메소드가 호출될 때 SampleModel->$primaryKeysample_id이고, sample_data에는 sample_id라는 키가 있으므로 update라고 판단합니다.

(8) 세번째 엔드포인트 qbupdate는 쿼리 빌더를 통한 업데이트 방법을 보여줍니다.
많은 경우 업데이트는 주 키를 통해서 하지만, 늘 그런 것은 아닙니다. 조건은 얼마든지 다양화할 수 있지요. 특정 조건에 따른 데이터 수정을 하고 싶을 때는 쿼리 빌더를 통해 업데이트를 할 수 있습니다.
또한 쿼리 빌더를 통한 업데이트는 모델->$primaryKey가 설정되어 있지 않아도 동작한다는 장점이 있습니다.

(9) 수정할 조건을 where로 지정합니다.

(10) 수정할 데이터를 set 메소드를 통해 설정합니다.

(11) update() 메소드에는 파라미터가 없는 것에 주의하세요. 모든 파라미터는 이미 whereset을 통해 설정되어 있습니다.


브라우저로 http://localhost:8080/model/update 에 접속해서 데이터가 수정되었는지 확인해 봅시다.

브라우저로 http://localhost:8080/model/save 에 접속해서 데이터가 수정되었는지 확인해 봅시다.

브라우저로 http://localhost:8080/model/qbupdate 에 접속해서 데이터가 수정되었는지 확인해 봅시다.

모델로 데이터 삭제하기

모델을 이용해서 데이터베이스의 데이터를 삭제하는 방법을 알아보겠습니다.
Model 컨트롤러에 아래 메소드를 추가합니다.

public function delete()
{
    $sampleModel = new SampleModel();
    $sampleModel->delete(1); // (1)
    return $this->response->setJSON($sampleModel->find(1));
}

(1) 모델의 delete 메소드를 이용해 데이터를 삭제합니다. 첫번째 파라미터는 주 키 입니다. 삭제의 경우는 조회, 수정과는 다르게 대부분의 경우 주 키를 이용해 삭제하게 됩니다.


브라우저로 http://localhost:8080/model/delete 에 접속해서 데이터가 수정되었는지 확인해 봅시다.

데이터베이스 뷰에서도 확인하면 확실하겠죠?

profile
스타트업에 관심이 많은 10 + n년차 웹 개발자. 자바 스프링 (혹은 부트), 파이썬 플라스크, PHP를 주로 다룹니다.

0개의 댓글