컨트롤러
컨트롤러는 HTTP 요청을 처리하는 방식을 결정하므로 애플리케이션의 핵심입니다.
컨트롤러란 무엇인가요?
컨트롤러는 단순히 HTTP 요청을 처리하는 클래스 파일입니다. URI 라우팅은 URI를 컨트롤러와 연결합니다. 컨트롤러는 뷰 문자열이나 Response 객체를 반환합니다.
작성하는 모든 컨트롤러는 BaseController 클래스를 상속받아야 합니다. 이 클래스는 모든 컨트롤러에서 사용할 수 있는 몇 가지 기능을 제공합니다.
생성자
CodeIgniter의 컨트롤러에는 특별한 생성자인 initController()가 있습니다. 이는 PHP의 생성자인 __construct()가 실행된 후 프레임워크에 의해 호출됩니다.
initController()를 오버라이드하고 싶다면, 메서드 내에 parent::initController($request, $response, $logger);를 추가하는 것을 잊지 마십시오.
<?php
namespace App\Controllers;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
class Product extends BaseController
{
public function initController(
RequestInterface $request,
ResponseInterface $response,
LoggerInterface $logger,
) {
parent::initController($request, $response, $logger);
// Add your code here.
}
// ...
}
중요
생성자 내에서는 return을 사용할 수 없습니다. 따라서 return redirect()->to('route');와 같은 코드는 작동하지 않습니다.
initController() 메서드는 다음 세 가지 속성을 설정합니다.
포함된 속성
CodeIgniter의 컨트롤러는 다음 속성들을 제공합니다.
Request 객체
애플리케이션의 메인 요청 인스턴스는 항상 클래스 속성인 $this->request를 통해 사용할 수 있습니다.
Response 객체
애플리케이션의 메인 응답 인스턴스는 항상 클래스 속성인 $this->response를 통해 사용할 수 있습니다.
Logger 객체
Logger 클래스의 인스턴스는 클래스 속성인 $this->logger를 통해 사용할 수 있습니다.
헬퍼(Helpers)
클래스 속성으로 헬퍼 파일들의 배열을 정의할 수 있습니다. 컨트롤러가 로드될 때마다 이러한 헬퍼 파일들이 자동으로 메모리에 로드되어 컨트롤러 내부 어디에서나 해당 메서드들을 사용할 수 있게 됩니다.
<?php
namespace App\Controllers;
class MyController extends BaseController
{
protected $helpers = ['url', 'form'];
}
forceHTTPS
모든 컨트롤러에는 메서드에 HTTPS로만 접근하도록 강제하는 편의 메서드가 제공됩니다.
<?php
if (! $this->request->isSecure()) {
$this->forceHTTPS();
}
기본적으로 HTTP Strict Transport Security(HSTS) 헤더를 지원하는 최신 브라우저에서 이 호출은 비-HTTPS 요청을 1년 동안 HTTPS 요청으로 강제 전환합니다. 첫 번째 매개변수로 기간(초 단위)을 전달하여 이를 수정할 수 있습니다.
<?php
if (! $this->request->isSecure()) {
$this->forceHTTPS(31536000); // one year
}
참고
YEAR, MONTH 등과 같은 여러 시간 관련 상수들을 언제나 사용할 수 있습니다.
데이터 유효성 검사
$this->validateData()
Added in version 4.2.0.
데이터 확인을 간소화하기 위해 컨트롤러는 validateData()라는 편의 메서드를 제공합니다.
이 메서드는 (1) 검증할 데이터 배열, (2) 규칙 배열, (3) 데이터가 유효하지 않을 때 표시할 커스텀 에러 메시지 배열(선택 사항), (4) 사용할 데이터베이스 그룹(선택 사항)을 인수로 받습니다.
유효성 검사 라이브러리 문서에는 규칙 및 메시지 배열 형식과 사용 가능한 규칙에 대한 자세한 정보가 있습니다.
<?php
namespace App\Controllers;
class StoreController extends BaseController
{
public function product(int $id)
{
$data = [
'id' => $id,
'name' => $this->request->getPost('name'),
];
$rule = [
'id' => 'integer',
'name' => 'required|max_length[255]',
];
if (! $this->validateData($data, $rule)) {
return view('store/product', [
'errors' => $this->validator->getErrors(),
]);
}
// ...
}
}
$this->validate()
중요
이 메서드는 하위 호환성을 위해서만 존재합니다. 새 프로젝트에서는 사용하지 마십시오. 이미 사용 중이더라도 validateData() 메서드로 교체할 것을 권장합니다.
컨트롤러는 또한 validate()라는 편의 메서드도 제공합니다.
경고
validate() 대신 validateData()를 사용하여 POST 데이터만 검증하십시오. validate()는 $request->getVar()를 사용하며, 이는 php.ini의 request-order 설정에 따라 $_GET, $_POST, $_COOKIE 순서로 데이터를 반환합니다. 나중에 들어온 값이 이전 값을 덮어씁니다. 이름이 같은 경우 POST 값이 쿠키에 의해 덮어씌워질 수도 있습니다.
이 메서드는 첫 번째 매개변수로 규칙 배열을, 선택 사항인 두 번째 매개변수로 항목이 유효하지 않을 때 표시할 커스텀 에러 메시지 배열을 받습니다.
내부적으로 이 메서드는 검증할 데이터를 가져오기 위해 컨트롤러의 $this->request 인스턴스를 사용합니다.
유효성 검사 라이브러리 문서에는 규칙 및 메시지 배열 형식과 사용 가능한 규칙에 대한 자세한 정보가 있습니다.
<?php
namespace App\Controllers;
class UserController extends BaseController
{
public function updateUser(int $userID)
{
if (! $this->validate([
'email' => "required|is_unique[users.email,id,{$userID}]",
'name' => 'required|alpha_numeric_spaces',
])) {
// The validation failed.
return view('users/update', [
'errors' => $this->validator->getErrors(),
]);
}
// The validation was successful.
// Get the validated data.
$validData = $this->validator->getValidated();
// ...
}
}
경고
validate() 메서드를 사용할 때는 getValidated() 메서드를 사용하여 검증된 데이터를 가져와야 합니다. 왜냐하면 validate() 메서드는 내부적으로 Validation::withRequest()를 사용하며, 이는 $request->getJSON()나 $request->getRawInput() 또는 $request->getVar()에서 데이터를 검증하므로 공격자가 검증 대상을 변경할 위험이 있기 때문입니다.
참고
$this->validator->getValidated() 메서드는 v4.4.0부터 사용할 수 있습니다.
설정 파일에 규칙을 보관하는 것이 더 간단하다면, $rules 배열을 app/Config/Validation.php에 정의된 그룹 이름으로 대체할 수 있습니다.
<?php
namespace App\Controllers;
class UserController extends BaseController
{
public function updateUser(int $userID)
{
if (! $this->validate('userRules')) {
// The validation failed.
return view('users/update', [
'errors' => $this->validator->getErrors(),
]);
}
// The validation was successful.
// Get the validated data.
$validData = $this->validator->getValidated();
// ...
}
}
참고
유효성 검사는 모델에서 자동으로 처리할 수도 있지만, 때로는 컨트롤러에서 하는 것이 더 쉬울 때도 있습니다. 어디서 할지는 여러분의 선택입니다.
메서드 보호하기
어떤 경우에는 특정 메서드를 외부 접근으로부터 숨기고 싶을 수 있습니다. 이를 위해서는 단순히 메서드를 private이나 protected로 선언하면 됩니다. 이렇게 하면 URL 요청을 통해 해당 메서드가 실행되는 것을 방지할 수 있습니다.
예를 들어 Helloworld 컨트롤러에 다음과 같이 메서드를 정의하고,
<?php
namespace App\Controllers;
class Helloworld extends BaseController
{
protected function utility()
{
// some code
}
}
해당 메서드에 대한 라우트(helloworld/utility)를 정의했더라도, 다음 URL을 통해 접근하려고 하면 작동하지 않습니다:
example.com/index.php/helloworld/utility
자동 라우팅(Auto-routing)도 작동하지 않습니다.
자동 라우팅 (개선됨)
Added in version 4.2.0.
자동 라우팅 (개선됨)은 새롭고 더 안전한 자동 라우팅 시스템입니다.
자세한 내용은 자동 라우팅(향상)를 참조하십시오.
자동 라우팅 (레거시)
중요
이 기능은 하위 호환성을 위해서만 존재합니다. 새 프로젝트에서는 사용하지 마십시오. 이미 사용 중이더라도 자동 라우팅(향상)로 전환할 것을 권장합니다.
이 섹션은 CodeIgniter 3 방식의 라우팅 시스템인 자동 라우팅 (레거시)의 기능을 설명합니다. 이는 라우트 정의 없이 HTTP 요청을 자동으로 라우팅하고 해당하는 컨트롤러 메서드를 실행합니다. 자동 라우팅은 기본적으로 비활성화되어 있습니다.
경고
설정 오류나 코딩 실수를 방지하기 위해 자동 라우팅 (레거시)을 사용하지 않을 것을 권장합니다. 이를 사용하면 컨트롤러 필터나 CSRF 보호가 우회되는 취약한 앱을 만들기 쉽습니다.
중요
자동 라우팅 (레거시)은 모든 HTTP 메서드의 요청을 컨트롤러 메서드로 라우팅합니다.
중요
v4.5.0부터 자동 라우팅 (레거시)이 컨트롤러를 찾지 못할 경우, 컨트롤러 필터가 실행되기 전에 PageNotFoundException 예외가 발생합니다.
다음 URI를 고려해 보십시오:
example.com/index.php/helloworld/
위의 예에서 CodeIgniter는 Helloworld.php라는 이름의 컨트롤러를 찾아 로드하려고 시도합니다.
참고
컨트롤러의 짧은 이름(short name)이 URI의 첫 번째 세그먼트와 일치하면 해당 컨트롤러가 로드됩니다.
시도해 봅시다: Hello World! (레거시)
실제로 어떻게 작동하는지 확인하기 위해 간단한 컨트롤러를 만들어 봅시다. 텍스트 에디터를 사용하여 Helloworld.php라는 파일을 만들고 다음 코드를 입력하세요. Helloworld 컨트롤러가 BaseController를 상속받고 있는 것을 확인할 수 있습니다. 만약 BaseController의 기능이 필요 없다면 CodeIgniter\Controller를 직접 상속받을 수도 있습니다.
BaseController는 모든 컨트롤러에서 공통으로 필요한 컴포넌트를 로드하거나 기능을 수행하는 데 편리한 장소를 제공합니다. 새로운 컨트롤러를 만들 때 이 클래스를 확장하여 사용할 수 있습니다.
보안을 위해 새로운 유틸리티 메서드는 반드시 protected나 private으로 선언하십시오.
<?php
namespace App\Controllers;
class Helloworld extends BaseController
{
public function index()
{
return 'Hello World!';
}
}
그런 다음 파일을 app/Controllers 디렉토리에 저장하십시오.
중요
파일 이름은 대문자 H로 시작하는 Helloworld.php여야 합니다. 자동 라우팅을 사용할 때 컨트롤러 클래스 이름은 반드시 대문자로 시작해야 하며, 오직 첫 번째 글자만 대문자여야 합니다.
이제 다음과 유사한 URL을 통해 사이트에 접속해 보십시오:
example.com/index.php/helloworld
올바르게 수행했다면 다음과 같이 나타날 것입니다:
Hello World!
유효한 형식:
<?php
namespace App\Controllers;
class Helloworld extends BaseController
{
// ...
}
유효하지 않은 형식:
<?php
namespace App\Controllers;
class helloworld extends BaseController
{
// ...
}
유효하지 않은 형식:
<?php
namespace App\Controllers;
class HelloWorld extends BaseController
{
// ...
}
또한, 컨트롤러가 부모 컨트롤러 클래스를 상속받아 모든 메서드를 물려받을 수 있도록 항상 확인하십시오.
참고
시스템은 정의된 라우트에서 일치하는 항목을 찾지 못하면, URI의 각 세그먼트를 app/Controllers의 디렉토리/파일과 대조하여 컨트롤러를 찾으려고 시도합니다. 이것이 디렉토리와 파일 이름이 반드시 대문자로 시작하고 나머지는 소문자여야 하는 이유입니다.
다른 명명 규칙을 원하신다면 정의된 라우트 라우팅을 사용하여 수동으로 정의해야 합니다. 다음은 PSR-4 오토로더를 기반으로 한 예입니다.
<?php
/*
* Folder and file structure:
* \<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>
*/
$routes->get('helloworld', '\App\Controllers\HelloWorld::index');
메서드 (레거시)
위의 예에서 메서드 이름은 index()입니다. URI의 두 번째 세그먼트가 비어 있으면 항상 기본적으로 index() 메서드가 로드됩니다. “Hello World” 메시지를 보여주는 또 다른 방법은 다음과 같습니다:
example.com/index.php/helloworld/index/
URI의 두 번째 세그먼트가 컨트롤러 내에서 어떤 메서드가 호출될지를 결정합니다.
시도해 봅시다. 컨트롤러에 새 메서드를 추가하세요.
<?php
namespace App\Controllers;
class Helloworld extends BaseController
{
public function index()
{
return 'Hello World!';
}
public function comment()
{
return 'I am not flat!';
}
}
이제 다음 URL을 로드하여 comment 메서드를 확인하세요:
example.com/index.php/helloworld/comment/
새로운 메시지가 보일 것입니다.
메서드에 URI 세그먼트 전달하기 (레거시)
URI에 세 개 이상의 세그먼트가 포함되어 있으면, 해당 세그먼트들은 메서드의 매개변수로 전달됩니다.
예를 들어, 다음과 같은 URI가 있다고 가정해 봅시다:
example.com/index.php/products/shoes/sandals/123
메서드에는 URI 세그먼트 3과 4('sandals' 및 '123')가 전달됩니다.
<?php
namespace App\Controllers;
class Products extends BaseController
{
public function shoes($sandals, $id)
{
return $sandals . $id;
}
}
기본 컨트롤러 (레거시)
기본 컨트롤러는 URI가 디렉토리 이름으로 끝나거나 사이트 루트 URL만 요청되어 URI가 존재하지 않을 때 사용되는 특별한 컨트롤러입니다.
기본 컨트롤러 정의하기 (레거시)
Helloworld 컨트롤러로 시도해 봅시다.
기본 컨트롤러를 지정하려면 app/Config/Routing.php 파일을 열고 다음 속성을 설정하십시오:
public string $defaultController = 'Helloworld';
여기서 Helloworld는 사용하려는 컨트롤러 클래스의 이름입니다.
그리고 app/Config/Routes.php에서 다음 라인을 주석 처리하십시오.
$routes->get('/', 'Home::index');
이제 아무런 URI 세그먼트를 지정하지 않고 사이트에 접속하면 “Hello World” 메시지가 나타납니다.
참고
$routes->get('/', 'Home::index'); 라인은 실제 서비스 앱에서 사용하고 싶은 최적화 설정입니다. 하지만 여기서는 시연을 위해 해당 기능을 사용하지 않겠습니다. $routes->get()에 대한 설명은 URI 라우팅을 참조하십시오.
자세한 정보는 구성 옵션 (Legacy) 문서를 참조하십시오.
하위 디렉토리에 컨트롤러 구성하기 (레거시)
규모가 큰 애플리케이션을 구축하는 경우 컨트롤러를 하위 디렉토리에 계층적으로 구성하고 싶을 수 있습니다. CodeIgniter는 이를 허용합니다.
단순히 메인 app/Controllers 아래에 하위 디렉토리를 만들고 그 안에 컨트롤러 클래스들을 배치하면 됩니다.
중요
디렉토리 이름은 반드시 대문자로 시작해야 하며, 오직 첫 번째 글자만 대문자여야 합니다.
이 기능을 사용할 때 URI의 첫 번째 세그먼트는 디렉토리를 지정해야 합니다. 예를 들어 다음과 같은 위치에 컨트롤러가 있다고 가정해 봅시다:
app/Controllers/Products/Shoes.php
위의 컨트롤러를 호출하기 위한 URI는 다음과 같은 형태가 됩니다:
example.com/index.php/products/shoes/show/123
참고
app/Controllers와 public/ 안에 동일한 이름의 디렉토리를 가질 수 없습니다. 이는 해당 디렉토리가 존재하면 웹 서버가 직접 그 디렉토리를 찾으려 시도하고 CodeIgniter로 라우팅되지 않기 때문입니다.
각 하위 디렉토리에는 해당 하위 디렉토리만 포함된 URL로 접근했을 때 호출될 기본 컨트롤러를 포함할 수 있습니다. app/Config/Routing.php 파일에 지정된 기본 컨트롤러 이름과 일치하는 컨트롤러를 해당 디렉토리에 배치하면 됩니다.
CodeIgniter는 또한 정의된 라우트 라우팅을 사용하여 URI를 매핑할 수 있게 해줍니다.
메서드 호출 리매핑
참고
자동 라우팅 (개선됨)은 의도적으로 이 기능을 지원하지 않습니다.
앞서 언급했듯이 일반적으로 URI의 두 번째 세그먼트가 컨트롤러에서 호출될 메서드를 결정합니다. CodeIgniter는 _remap() 메서드를 사용하여 이 동작을 재정의할 수 있게 해줍니다.
<?php
namespace App\Controllers;
class Products extends BaseController
{
public function _remap()
{
// Some code here...
}
}
중요
컨트롤러에 _remap()이라는 이름의 메서드가 포함되어 있다면, URI에 상관없이 항상 이 메서드가 호출됩니다. 이는 URI가 호출될 메서드를 결정하는 일반적인 동작을 재정의하여, 여러분만의 메서드 라우팅 규칙을 정의할 수 있게 해줍니다.
재정의된 메서드 호출(일반적으로 URI의 두 번째 세그먼트)은 _remap() 메서드의 매개변수로 전달됩니다.
<?php
namespace App\Controllers;
class Products extends BaseController
{
public function _remap($method)
{
if ($method === 'some_method') {
return $this->{$method}();
}
return $this->default_method();
}
}
메서드 이름 뒤의 추가적인 세그먼트들도 _remap()에 전달됩니다. 이러한 매개변수들을 메서드에 전달하여 CodeIgniter의 기본 동작을 흉내 낼 수 있습니다.
예제:
<?php
namespace App\Controllers;
class Products extends BaseController
{
public function _remap($method, ...$params)
{
$method = 'process_' . $method;
if (method_exists($this, $method)) {
return $this->{$method}(...$params);
}
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
}
}
컨트롤러 확장하기
컨트롤러를 확장하고 싶다면 컨트롤러 확장를 참조하십시오.
마치며
간단히 말해, 이것이 컨트롤러에 대해 알아야 할 전부입니다.