컨트롤러는 MVC 패턴의 구성 요소로 입력된 정보를 처리하고 모델을 호출하고 뷰를 생성하여 결과를 전달하는 역할을 수행합니다.

라라벨은 전 장에서 설명한 artisan 명령어를 사용하면 새로운 컨트롤러를 만들 수 있습니다.

컨트롤러를 생성하기 구문을 확인하기 위해 artisan make:controller 에 -h 옵션을 붙여서 실행해 봅시다.

컨트롤러 생성 구문 확인

$ php artisan make:controller -h
 
Usage:
  make:controller [options] [--] <name>
Arguments:
  name                  The name of the class
Options:
      --resource        Generate a resource controller class.
  -h, --help            Display this help message
CODE

인수를 보면 컨트롤러의 이름을 주어야 하고 옵션중에는 리소스 컨트롤러 클래스를 생성하는 --resource 옵션이 있다는 것을 알 수 있습니다. 먼저 빈 컨트롤러를 생성하기 위해 아무 옵션도 주지 않고 컨트롤러명인 TaskController 만 전달하여 기본 컨트롤러를 생성해 보겠습니다.

$ php artisan make:controller TaskController 
Controller created successfully.
CODE

라라벨 5.1 은 빈 컨트롤러를 생성하는 --plain 이 옵션이고 기본 값은 뒤에서 설명할 Resource Controller 였지만 5.2는 --resource 가 옵션이고 --plain 은 기본 설정으로 변경되었습니다.

 

생성된 컨트롤러는 app/Http/Controllers/TaskController.php 에 위치합니다. 에디터로 이 파일을 열어 봅시다.

app/Http/Controllers/TaskController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;

class TaskController extends Controller
{
    //
}
PHP

모든 컨트롤러는 라라벨의 Controller 클래스를 상속받게 되며 App\Http\Controllers 네임스페이스에 속하게 됩니다.

전 장에서는 task/list3 라우트에 모델을 호출하고 뷰를 생성하는 코드를 작성했는데 이런 코드는 라우트가 아닌 컨트롤러에 있는게 MVC 취지에 맞으므로 해당 코드를 TaskController list3 () 메소드로 옮겨 봅시다.

 /**
     * 할 일 목록을 출력
     * 
     * @return Response
     */
    public function list3()
    {
    	$tasks= [ 
        	['name' => 'Response 클래스 분석', 'due_date' => '2015-06-01 11:22:33'],
        	['name' => '블레이드 예제 작성', 'due_date' => '2015-06-03 15:12:42'],        
    	];  
    	return view('task.list3')->with('tasks', $tasks);
    }
CODE

라우트에 있던 코드를 list3 메소드로 이동하고 메소드에 주석을 달은 것을 볼 수있습니다.

/** 로 시작하는 주석은 Javadoc 처럼 phpDocumentor 같은 PHP의 문서화 도구를 사용하여 손쉽게 문서화할 수 있고 또 주석을 달면서 로직을 정리할 수 있으므로 주석 달기는 좋은 개발 습관이지만 앞으로의 예제 코드에서는 주석을 제외할 것입니다.

 

이제 컨트롤러  메소드를 만들었으니 이를 라우트와 연결해야 합니다. app/Http/routes.php 파일을 열어서 기존의 'task/list3' 라우트 설정을 삭제하고 아래와 같이 라우팅을 구성합니다.

app/Http/routes.php

Route::get('task/list3', 'TaskController@list3');
PHP

위 구문은 task/list3 URL로 GET 요청이 들어올 경우 TaskController  list3() 메소드를 실행하라면 의미입니다.

 

이제 라우트 설정이 아주 깔끔해 졌습니다. 웹 브라우저로 http://homestead.app/task/list3 에 연결하여 정상 동작 여부를 확인해 봅시다.

 

컨트롤러 파라미터

라우트에서 파라미터를 받게 설정하고 전달받은 파라미터를 컨트롤러에 넘겨야 할 필요가 있을 경우 다음과 같이 라우트에 파라미터를 받을 수 있도록 설정하고 처리할 컨트롤러를 지정합니다. 

Route::get('task/param/{id?}/{arg?}', 'TaskController@param');
CODE

 

이제 컨트롤러에 param 메소드를 만들고 메소드 파라미터로 라우트에서 설정한 파라미터를 적어 주면 됩니다. 주의할 점은 라우트에 id 와 arg 가 옵션이므로 기본 값을 설정해 주어야 합니다.

class TaskController extends Controller
{
	public function param($id = 1, $arg = 'argument')
	{
    	return ['id' => $id, 'arg' => $arg];
	}
CODE

이제 브라우저로 http://homestead.app/task/param/3/option7 에 연결하면 컨트롤러에 파라미터가 전달되는 것을 확인할 수 있습니다.

HTTP 요청 클래스

라라벨은 클라이언트가 전송한 HTTP 요청을 Illuminate\Http\Request 라는 객체를 통해 접근할 수 있으며 손쉽게 사용하려면 컨트롤러의 메서드의 파라미터로 Request 객체를 타입 힌팅을 주면 됩니다.

위에서 작성한 param() 메서드를 아래와 같이 Request 객체를 받도록 수정해 봅시다. 컨트롤러의 상단에는 use 키워드로 Request 클래스가 속한 네임스페이스를 지정해 주어야 합니다.

Request 접근

use Illuminate\Http\Request;

class TaskController extends Controller
{
	public function param(Request $request, $id = 1, $arg = 'argument')
	{
		// $request 처리..    	
PHP

Request 클래스의 여러 메서드를 통해 요청 값을 얻을 수 있으며 예로 path() 메서드는 현재 URL 의 경로를 표시하며 url() 은 쿼리 스트링(Query String) 을 제외한 url 문자열을, fullUrl() 은 쿼리 스트링을 포함한 url 을 리턴합니다.

만약 클라이언트가 HTTP 요청에 name=value 라는 데이타를 보냈을 경우 입력 값을 얻으려면 $request->get('name') 또는 $request->input('name') 과 같이 사용하면 됩니다.

 

이제 위 param 메서드를 요청 값을 출력하도록 수정해 봅시다.

Request 접근

use Illuminate\Http\Request;

class TaskController extends Controller
{
	public function param(Request $request, $id = 1, $arg = 'argument')
	{
		dump( ['path' => $request->path(),
  			  'url' => $request->url(),
  			  'fullUrl' => $request->fullUrl(),
   			  'method' => $request->method(),
		      'name' => $request->get('name'),
		      'ajax' => $request->ajax(),
			  'header' => $request->header(),
		]);    	
	}
PHP

메서드중에 method() 는 HTTP 요청 메서드를 반환하며 ajax() 는 현재 요청이 AJAX(Asynchronous JavaScript and XML) 요청인지 여부를 boolean 형식으로 반환하는 메서드로 내부적으로는 요청 헤더중에 "X-Requested-With: XMLHttpRequest "가 있는지를 검사합니다.

header($key) 메서드는 파라미터로 주어진 $key 와 일치하는 헤더를 반환하고 $key 가 null 일 경우 모든 헤더를 리턴합니다.

이제 브라우저를 열고 주소창에 http://homestead.app/task/param/2/arg?name=value 를 입력해 보면 다음과 같이 요청 객체를 출력합니다.

 

Request 클래스는 이외에도 유용한 메서드가 많이 있으며 다른 메서드를 확인하려면 http://laravel.kr/docs/5.2/requests 을 참고하면 되며 라라벨의 Request 는 심포니 프레임워크의 Request(http://symfony.com/doc/current/book/http_fundamentals.html) 클래스를 상속받아서 확장하였으므로 심포니의 문서도  많은 도움이 됩니다.

GET 이외의 메소드 라우팅

POST, PUT, DELETE 메소드로 요청이 들어올 경우 라우팅하려면 get 대신 HTTP 메소드 이름을 적어주면 됩니다.

Route::post('task', 'TaskController@addTask');
Route::put('task', 'TaskController@updateTask');
Route::delete('task', 'TaskController@deleteTask');
CODE

POST, PUT, DELETE 는 라라벨이 자동으로 CSRF 토큰을 확인하므로 토큰 처리 부분을 작성해 주어야 하며 이 부분은 미들웨어와 실전 프로젝트에서 상세히 설명할 것 입니다.

여러 개의 메소드 등록

여러 개의 HTTP 메소드를 하나의 컨트롤러 메소드에서 처리하고 싶다면 Route::match() 를 사용하면 됩니다. 첫 번째 파라미터는 HTTP 메소드 이름을 배열로 전달하고 두 번째는 URL, 세 번째는 처리할 메소드나 클로져를 전달하면 됩니다.

다음은 GET과 POST 방식으로 /task URI에 요청이 들어올 경우 TaskController 의 getPostProcess() 메소드가 처리한다는 의미입니다.

Route::match(['get', 'post'], '/task', 'TaskController@getPostProcess');
CODE

만약 모든 HTTP 메소드를 처리하는 라우팅을 등록하려면 Route::any 를 사용하면 됩니다.

Route::any('/task', 'TaskController@anyProcess');
CODE

 

컨트롤러 네임스페이스

애플리케이션이 크고 복잡하다면 컨트롤러를 계층 구조로 나눠야 할 필요도 있습니다. 예로 관리자 등록을 처리하는 컨트롤러 소스의 위치는 app/Http/Controllers/Admin/Register.php 일 수 있습니다. 

이렇게 계층 구조에서 하위 컨트롤러를 호출할 경우 다음과 같이 네임스페이스를 지정하면 됩니다. 

Route::get('admin/create', 'Admin/Register@create');
CODE

라라벨은 컨트롤러를 찾을 때 App\Http\Controllers 를 루트 네임스페이스를  처리하므로  전체 패키지의 이름을 적을 필요가 없습니다.

 

artisan 에서 계층적으로 컨트롤러를 생성할 경우 다음과 같이 / 를 상위 폴더를 지정하면 됩니다.

$ php artisan make:controller Admin/Register
CODE

 

이름이 지정된 컨트롤러 라우트

라우트에서 컨트롤러 지정시 이름을 부여할 수 있으며 이렇게 하려면 두 번째 파라미터에 배열을 전달하며 배열의 키는 as uses 가 포함되어야 합니다.

as 는 라우팅에 부여할 이름이고 uses 는 처리할 컨트롤러의 메소드입니다.

다음 코드는 POST task/add 로 들어오는 요청을 처리하는 라우팅에 대해 task.add 라는 이름을 지정합니다.

Route::post('task/add', ['as' => 'task.add', 'uses' => 'TaskController@add']);
CODE

 

라우트에 이름을 지정할 경우의 장점은 뷰나 다른 컨트롤러등 특정 라우팅을 호출하는 URL 을 연결해야 하는 경우 변경에 따른 수정이 최소화 된다는 점입니다.

예를 들어 할일의 목록을 출력하는 뷰 페이지에 할일을 추가하는 기능을 연결할 경우 이 기능은 위의 라우팅과 연결되는 URL 을 설정해야 합니다.

< a href="task/add">할일 등록</a>
CODE

 

라우팅에 이름을 지정했다면 다음과 같이 route 헬퍼 함수를 사용하여 URL 을 생성할 수 있습니다.

< a href="{{ route('task.add') }}">할일 등록</a>
PHP

이제 위 실행 결과가 어떻게 나오는지 확인하기 위해 tinker 를 구동하고 dump() 헬퍼 함수를 사용하여 출력해 보면 해당 라우트로 가는 URL 이 표시되는 것을 알 수 있습니다.

$ php artisan tinker
 
>>> dump(route('task.add'))
"http://localhost/task/add"
CODE

그러면 이름있는 라우팅의 장점은 무엇일까요? 만약 서비스가 커져서 새로운 기능이 많이 들어가서 전체적인 라우팅 조정이 필요하다고 가정해 봅시다.

그래서 task 라우팅은 /project/task/ 로 이동하고 add 라는 URI는 store 로 변경하기로 결정했을 경우 바로 URL 을 링크했다면 링크한 소스를 찾아서 모두 수정해야 합니다.

 

하지만 라우팅에 이름을 지정하고 이를 뷰에서 사용했다면 URL 이 변경되거나 이름이 변경되어도 이를 링크하고 있는 코드도 자동으로 routes.php 의 변경을 따라가므로 코드를 수정할 필요가 없습니다.

이를 확인하기 위해 routes.php 를 수정해서 task/add/project/task/store 로 변경하고 as 에는 라우팅 이름을 지정하고 uses 에는 처리할 메소드를 지정해 봅시다.

Route::post('/project/task/store', ['as' => 'task.add', 'uses' => 'TaskController@add']);
CODE

이제 다시 tinker 를 구동하여 라우팅 이름인 task.add 를 호출하면 새로운 URL 이 출력되는 것을 알수 있으며 라우팅이 변경되어도 이름이 변경되지 않으면 이를 참조하고 있는 소스는 변경할 필요가 없는 것을 알수 있습니다.

$ php artisan tinker
 
>>> dump(route('task.add'))
"http://localhost/project/task/store"
CODE

라우트 그룹

크고 복잡한 애플리케이션의 경우 라우트 설정을 그룹화하면 전체 애플리케이션의 구조를 일관성 있게 관리할 수 있고 뷰나 다른 컨트롤러에서 해당 라우트를 참고할 경우 유용합니다.

task 라우팅 밑에 지속적으로 새로운 기능이 추가되어야 할 경우 as 로 task.xxx 와 같이 하지 않고 라우트 그룹을 사용하는게 좋습니다.

라우트 그룹은 Route::group 을 호출하며 첫 번째 파라미터로는 배열을 두 번째는 클로져를 전달합니다.

첫 번째 파라미터의 prefix 키는 라우팅에 사용할 URI를 지정하며 as 는 라우팅 그룹이름앞에 사용할 키워드를 의미합니다.

 

다음 코드는 task 로 들어오는 URI 를 처리하는 라우팅에 대해 task. 으로 시작하겠다는 의미입니다.

Route::group(['prefix' => 'task', 'as' => 'task.'], function() {
    Route::get('add', ['as' => 'add', 'uses' => 'TaskController@add']);
    Route::get('show/{id}', ['as' => 'show', 'uses' => 'TaskController@show']);
});
CODE

위 라우트 그룹을 정리하면 다음과 같으며 위와 같이 그룹화 해놓으면 전체 애플리케이션의 구성을 손쉽게 파악할수 있으며 새로운 라우팅(ex: delete)이 필요할 경우 해당 그룹에 추가하면 됩니다.

URI라우트 이름컨트롤러 메소드
task/addtask.addadd
task/showtask.showshow