このドキュメントは現在翻訳中です。一部のページが韓国語で表示される場合があります。
メインコンテンツまでスキップ
バージョン: 11.x

컨트롤러 (Controllers)

소개

모든 요청 처리 로직을 라우트 파일의 클로저로 작성하는 대신, "컨트롤러" 클래스를 사용해 이러한 동작을 구성할 수 있습니다. 컨트롤러는 서로 관련된 요청 처리 코드를 하나의 클래스로 묶어줄 수 있습니다. 예를 들어, UserController 클래스는 사용자와 관련된 모든 요청 처리(조회, 생성, 수정, 삭제 등)를 담당할 수 있습니다. 기본적으로 컨트롤러는 app/Http/Controllers 디렉터리에 저장됩니다.

컨트롤러 작성하기

기본 컨트롤러

새 컨트롤러를 빠르게 생성하려면 make:controller Artisan 명령어를 사용할 수 있습니다. 기본적으로 애플리케이션의 모든 컨트롤러는 app/Http/Controllers 디렉터리에 저장됩니다.

php artisan make:controller UserController

기본적인 컨트롤러 예시를 살펴보겠습니다. 컨트롤러는 여러 개의 public 메서드를 가질 수 있으며, 각각은 들어오는 HTTP 요청에 응답합니다.

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\View\View;

class UserController extends Controller
{
/**
* Show the profile for a given user.
*/
public function show(string $id): View
{
return view('user.profile', [
'user' => User::findOrFail($id)
]);
}
}

컨트롤러 클래스와 메서드를 작성한 후, 다음과 같이 해당 컨트롤러 메서드와 라우트를 연결할 수 있습니다.

use App\Http\Controllers\UserController;

Route::get('/user/{id}', [UserController::class, 'show']);

요청이 위에서 지정한 URI와 일치하면, App\Http\Controllers\UserController 클래스의 show 메서드가 호출되고, 라우트 파라미터가 해당 메서드에 전달됩니다.


컨트롤러는 반드시 특정 베이스 클래스를 상속받을 필요는 없습니다. 하지만 여러 컨트롤러에서 공통적으로 사용할 메서드를 베이스 컨트롤러 클래스에 작성해두면 관리가 편리할 수 있습니다.

단일 액션 컨트롤러

특정 컨트롤러의 동작이 특히 복잡하다면, 그 동작을 하나의 컨트롤러 클래스에 전담시키는 방식을 쓸 수 있습니다. 이를 위해 컨트롤러에 단 하나의 __invoke 메서드를 정의하면 됩니다.

<?php

namespace App\Http\Controllers;

class ProvisionServer extends Controller
{
/**
* Provision a new web server.
*/
public function __invoke()
{
// ...
}
}

단일 액션 컨트롤러의 라우트를 등록할 때는, 컨트롤러 메서드 이름을 따로 지정하지 않아도 됩니다. 대신 컨트롤러 클래스 이름만 전달하면 됩니다.

use App\Http\Controllers\ProvisionServer;

Route::post('/server', ProvisionServer::class);

make:controller Artisan 명령어에서 --invokable 옵션을 사용하면 바로 호출 가능한(invokable) 컨트롤러를 빠르게 생성할 수 있습니다.

php artisan make:controller ProvisionServer --invokable


컨트롤러 스텁은 스텁 퍼블리싱을 통해 커스터마이즈할 수 있습니다.

컨트롤러 미들웨어

미들웨어는 라우트 파일에서 컨트롤러의 라우트에 할당할 수 있습니다.

Route::get('/profile', [UserController::class, 'show'])->middleware('auth');

또는, 컨트롤러 클래스 안에서 미들웨어를 지정할 수도 있습니다. 이 경우, 컨트롤러가 HasMiddleware 인터페이스를 구현해야 하며, 이 인터페이스는 컨트롤러에 static middleware 메서드를 요구합니다. 이 메서드 내에서 컨트롤러의 액션에 적용할 미들웨어 배열을 반환할 수 있습니다.

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;

class UserController extends Controller implements HasMiddleware
{
/**
* Get the middleware that should be assigned to the controller.
*/
public static function middleware(): array
{
return [
'auth',
new Middleware('log', only: ['index']),
new Middleware('subscribed', except: ['store']),
];
}

// ...
}

컨트롤러 미들웨어를 클로저(Closure)로 정의할 수도 있습니다. 이 방법을 사용하면 별도의 미들웨어 클래스를 만들지 않고도 인라인 미들웨어를 빠르게 작성할 수 있습니다.

use Closure;
use Illuminate\Http\Request;

/**
* Get the middleware that should be assigned to the controller.
*/
public static function middleware(): array
{
return [
function (Request $request, Closure $next) {
return $next($request);
},
];
}


Illuminate\Routing\Controllers\HasMiddleware를 구현하는 컨트롤러는 Illuminate\Routing\Controller를 상속받아서는 안 됩니다.

리소스 컨트롤러

애플리케이션에서 각 Eloquent 모델을 "리소스"라고 생각한다면, 보통 각 리소스에 대해 동일한 세트의 작업을 수행하게 됩니다. 예를 들어, Photo 모델과 Movie 모델이 있다면, 사용자는 이 리소스들을 생성, 조회, 수정, 삭제할 수 있습니다.

이런 일반적인 상황을 위해, 라라벨의 리소스 라우팅은 대표적인 생성, 조회, 수정, 삭제(CRUD) 라우트를 단 한 줄의 코드로 컨트롤러에 할당할 수 있습니다. 먼저, make:controller Artisan 명령어의 --resource 옵션을 사용해 이 동작을 처리할 컨트롤러를 빠르게 생성할 수 있습니다.

php artisan make:controller PhotoController --resource

이 명령어는 app/Http/Controllers/PhotoController.php 위치에 컨트롤러를 생성합니다. 생성된 컨트롤러에는 각 리소스 작업을 위한 메서드가 미리 구현된 형태로 들어있습니다. 이제 다음과 같이 리소스 라우트를 컨트롤러에 매핑할 수 있습니다.

use App\Http\Controllers\PhotoController;

Route::resource('photos', PhotoController::class);

이 한 줄의 라우트 선언만으로, 해당 리소스에 대한 다양한 작업을 처리하는 여러 라우트가 자동으로 생성됩니다. 만들어진 컨트롤러에는 이미 각 액션용 스텁 메서드가 포함되어 있습니다. 참고로, route:list Artisan 명령어를 실행하면 애플리케이션의 전체 라우트 개요를 빠르게 확인할 수 있습니다.

여러 리소스 컨트롤러를 한 번에 등록할 때는 resources 메서드에 배열을 전달할 수 있습니다.

Route::resources([
'photos' => PhotoController::class,
'posts' => PostController::class,
]);

리소스 컨트롤러가 처리하는 액션

메서드URI액션라우트 이름
GET/photosindexphotos.index
GET/photos/createcreatephotos.create
POST/photosstorephotos.store
GET/photos/{photo}showphotos.show
GET/photos/{photo}/editeditphotos.edit
PUT/PATCH/photos/{photo}updatephotos.update
DELETE/photos/{photo}destroyphotos.destroy

모델을 찾을 수 없을 때 동작 커스터마이징

일반적으로 암묵적 바인딩된 리소스 모델을 찾지 못하면 404 HTTP 응답이 반환됩니다. 그러나, missing 메서드를 사용해 라우트 정의 시 이 동작을 원하는 대로 커스터마이즈할 수 있습니다. missing 메서드는 암묵적 모델을 바인딩할 수 없을 때 호출할 클로저를 인수로 받습니다.

use App\Http\Controllers\PhotoController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;

Route::resource('photos', PhotoController::class)
->missing(function (Request $request) {
return Redirect::route('photos.index');
});

소프트 삭제(Soft Delete) 모델

기본적으로, 암묵적 모델 바인딩은 소프트 삭제된 모델을 조회하지 않고 404 HTTP 응답을 반환합니다. 그러나, 라우트를 정의할 때 withTrashed 메서드를 사용하면 소프트 삭제된 모델도 함께 조회할 수 있습니다.

use App\Http\Controllers\PhotoController;

Route::resource('photos', PhotoController::class)->withTrashed();

인수를 지정하지 않고 withTrashed를 호출하면, show, edit, update 리소스 라우트에서 소프트 삭제된 모델을 허용합니다. 배열을 전달하면 허용할 라우트만 골라서 설정할 수도 있습니다.

Route::resource('photos', PhotoController::class)->withTrashed(['show']);

리소스 모델 지정하기

라우트 모델 바인딩을 사용하는 경우, 컨트롤러 메서드에서 모델 인스턴스를 타입-힌트(type-hint)로 사용할 수 있습니다. 이를 위해 컨트롤러를 생성할 때 --model 옵션을 사용할 수 있습니다.

php artisan make:controller PhotoController --model=Photo --resource

폼 리퀘스트 클래스 자동 생성

리소스 컨트롤러 생성 시 --requests 옵션을 추가하면, 저장 및 수정 액션에 대한 폼 리퀘스트 클래스도 함께 생성됩니다.

php artisan make:controller PhotoController --model=Photo --resource --requests

부분 리소스 라우트

리소스 라우트를 선언할 때 컨트롤러가 처리해야 할 액션의 일부만 지정하고 싶다면, 기본 모든 액션을 다 등록하는 대신 필요한 것만 선택할 수 있습니다.

use App\Http\Controllers\PhotoController;

Route::resource('photos', PhotoController::class)->only([
'index', 'show'
]);

Route::resource('photos', PhotoController::class)->except([
'create', 'store', 'update', 'destroy'
]);

API 리소스 라우트

API에서 사용할 리소스 라우트는 템플릿을 반환하는 createedit 라우트를 제외하는 경우가 많습니다. 편리하게도, apiResource 메서드를 사용하면 두 라우트를 자동으로 빼고 등록할 수 있습니다.

use App\Http\Controllers\PhotoController;

Route::apiResource('photos', PhotoController::class);

여러 API 리소스 컨트롤러를 한 번에 등록할 때는 apiResources 메서드를 사용할 수 있습니다.

use App\Http\Controllers\PhotoController;
use App\Http\Controllers\PostController;

Route::apiResources([
'photos' => PhotoController::class,
'posts' => PostController::class,
]);

make:controller 명령어에서 --api 옵션을 활용하면, createedit 메서드가 없는 API 리소스 컨트롤러를 바로 생성할 수 있습니다.

php artisan make:controller PhotoController --api

중첩 리소스

상황에 따라 중첩된 리소스에 대한 라우트가 필요할 수 있습니다. 예를 들어, 포토(photo) 리소스에 여러 개의 코멘트(comment)가 달릴 수 있습니다. "점(dot) 표기법"을 사용해 중첩된 리소스 컨트롤러를 등록할 수 있습니다.

use App\Http\Controllers\PhotoCommentController;

Route::resource('photos.comments', PhotoCommentController::class);

이렇게 등록하면 아래와 같은 형태로 중첩된 리소스 접근이 가능합니다.

/photos/{photo}/comments/{comment}

중첩 리소스 스코프 지정

라라벨의 암묵적 모델 바인딩 기능은 중첩된 모델 바인딩시 자식 모델이 반드시 부모 모델에 속해 있는지 자동으로 확인할 수 있습니다. 중첩 리소스를 선언할 때 scoped 메서드를 사용하면 이 기능을 활성화할 수 있으며, 자식 리소스를 어떤 필드로 가져올지 지정할 수도 있습니다. 자세한 사용법은 리소스 라우트의 스코프 적용 문서를 참고하세요.

얕은 중첩(Shallow Nesting)

실제로는 URI에 부모와 자식의 ID를 모두 포함시키지 않아도 될 때가 있습니다. 예를 들어 자식의 ID(주로 증가형 기본 키)가 유니크하다면, "얕은 중첩(shallow nesting)"을 사용할 수 있습니다.

use App\Http\Controllers\CommentController;

Route::resource('photos.comments', CommentController::class)->shallow();

이렇게 하면 다음과 같은 라우트가 정의됩니다.

메서드URI액션라우트 이름
GET/photos/{photo}/commentsindexphotos.comments.index
GET/photos/{photo}/comments/createcreatephotos.comments.create
POST/photos/{photo}/commentsstorephotos.comments.store
GET/comments/{comment}showcomments.show
GET/comments/{comment}/editeditcomments.edit
PUT/PATCH/comments/{comment}updatecomments.update
DELETE/comments/{comment}destroycomments.destroy

리소스 라우트 이름 지정

기본적으로 모든 리소스 컨트롤러의 액션에는 라우트 이름이 지정됩니다. 하지만 names 배열을 전달하여 원하는 대로 이름을 오버라이드할 수 있습니다.

use App\Http\Controllers\PhotoController;

Route::resource('photos', PhotoController::class)->names([
'create' => 'photos.build'
]);

리소스 라우트 파라미터 이름 지정

기본적으로 Route::resource는 리소스 이름의 단수형을 사용해 라우트 파라미터를 생성합니다. 파라미터 이름을 변경하고 싶다면 parameters 메서드에 연관 배열을 전달해서 지정할 수 있습니다.

use App\Http\Controllers\AdminUserController;

Route::resource('users', AdminUserController::class)->parameters([
'users' => 'admin_user'
]);

위 예시에서는 해당 리소스의 show 라우트가 다음과 같은 URI를 갖게 됩니다.

/users/{admin_user}

리소스 라우트 스코프 적용

라라벨의 scoped 암묵적 모델 바인딩 기능으로, 중첩된 바인딩에서 자식 모델이 부모 모델에 속해 있는지 자동으로 확인할 수 있습니다. 중첩 리소스를 선언할 때 scoped 메서드를 사용하면 자동 스코핑을 활성화하고, 자식 리소스를 어떤 필드로 검색할지 지정할 수 있습니다.

use App\Http\Controllers\PhotoCommentController;

Route::resource('photos.comments', PhotoCommentController::class)->scoped([
'comment' => 'slug',
]);

이렇게 하면 아래와 같은 URL에서 스코프가 적용된 중첩 리소스를 조회할 수 있습니다.

/photos/{photo}/comments/{comment:slug}

커스텀 키가 적용된 암묵적 바인딩을 중첩 라우트 파라미터로 사용할 때, 라라벨은 부모 모델의 연관관계 명(위 예시에서는 route 파라미터 이름의 복수형, 즉 comments)로 자식 모델을 검색하는 쿼리를 자동으로 스코프합니다.

리소스 URI 현지화

기본적으로 Route::resource는 영어 동사와 복수 규칙을 따릅니다. 만약 createedit 등 액션 동사를 현지화해야 한다면, Route::resourceVerbs 메서드를 사용할 수 있습니다. 이 설정은 애플리케이션의 App\Providers\AppServiceProviderboot 메서드 시작 부분에 넣어줄 수 있습니다.

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Route::resourceVerbs([
'create' => 'crear',
'edit' => 'editar',
]);
}

라라벨의 복수화 기능은 여러 언어를 지원하므로, 필요에 따라 언어를 지정하여 사용할 수 있습니다. 동사와 복수화 언어를 커스터마이즈한 경우, 예를 들어 Route::resource('publicacion', PublicacionController::class)로 등록하면 다음과 같은 URI가 생성됩니다.

/publicacion/crear

/publicacion/{publicaciones}/editar

리소스 컨트롤러 확장

기본 리소스 라우트 외에 추가로 라우트를 더 하고 싶다면, Route::resource 메서드보다 먼저 해당 supplemental(보조) 라우트를 정의해야 합니다. 그렇지 않으면, resource 메서드에서 생성된 라우트가 보조 라우트보다 우선시될 수 있습니다.

use App\Http\Controller\PhotoController;

Route::get('/photos/popular', [PhotoController::class, 'popular']);
Route::resource('photos', PhotoController::class);


컨트롤러의 역할이 너무 커지지 않게 주의하세요. 리소스 액션 이외의 메서드를 자주 추가하게 된다면, 컨트롤러를 더 작고 역할이 명확한 두 개 이상의 컨트롤러로 분리하는 것이 좋습니다.

싱글턴 리소스 컨트롤러

애플리케이션에서 한 인스턴스만 존재할 수 있는 리소스가 있을 수 있습니다. 예를 들어, 사용자의 "프로필(profile)" 같은 것은 한 명의 사용자가 여러 개를 가질 수 없습니다. 마찬가지로 이미지에 "썸네일(thumbnail)"이 하나만 있을 수 있습니다. 이러한 경우를 "싱글턴 리소스"라고 하며, 해당 리소스는 오직 하나의 인스턴스만 존재합니다. 이처럼 한 개만 존재하는 리소스를 위해 "싱글턴" 리소스 컨트롤러를 등록할 수 있습니다.

use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;

Route::singleton('profile', ProfileController::class);

위의 싱글턴 리소스 정의는 다음과 같은 라우트를 등록합니다. "생성" 관련 라우트는 등록되지 않으며, 라우트가 식별자를 요구하지 않습니다. 왜냐하면 오직 하나의 인스턴스만 존재하기 때문입니다.

메서드URI액션라우트 이름
GET/profileshowprofile.show
GET/profile/editeditprofile.edit
PUT/PATCH/profileupdateprofile.update

싱글턴 리소스는 일반 리소스 내에 중첩시킬 수도 있습니다.

Route::singleton('photos.thumbnail', ThumbnailController::class);

이 경우, photos 리소스에는 기본 리소스 라우트가 모두 등록되는 한편, thumbnail에는 아래와 같은 싱글턴 리소스 라우트만 추가됩니다.

메서드URI액션라우트 이름
GET/photos/{photo}/thumbnailshowphotos.thumbnail.show
GET/photos/{photo}/thumbnail/editeditphotos.thumbnail.edit
PUT/PATCH/photos/{photo}/thumbnailupdatephotos.thumbnail.update

생성 가능한 싱글턴 리소스

때때로 싱글턴 리소스도 생성 및 저장 라우트를 정의해야 할 수 있습니다. 이 경우, 싱글턴 리소스 라우트 등록 시 creatable 메서드를 사용하면 됩니다.

Route::singleton('photos.thumbnail', ThumbnailController::class)->creatable();

이 경우, 다음과 같은 라우트가 등록됩니다. 생성 및 저장 뿐만 아니라, 삭제(DELETE) 라우트도 함께 등록되는 것을 볼 수 있습니다.

메서드URI액션라우트 이름
GET/photos/{photo}/thumbnail/createcreatephotos.thumbnail.create
POST/photos/{photo}/thumbnailstorephotos.thumbnail.store
GET/photos/{photo}/thumbnailshowphotos.thumbnail.show
GET/photos/{photo}/thumbnail/editeditphotos.thumbnail.edit
PUT/PATCH/photos/{photo}/thumbnailupdatephotos.thumbnail.update
DELETE/photos/{photo}/thumbnaildestroyphotos.thumbnail.destroy

생성 및 저장 라우트는 빼고 DELETE 라우트만 등록하고 싶을 때는 destroyable 메서드를 사용할 수 있습니다.

Route::singleton(...)->destroyable();

API 싱글턴 리소스

apiSingleton 메서드를 사용하면, API에서 사용할 싱글턴 리소스로 createedit 라우트가 필요 없는 라우트를 등록할 수 있습니다.

Route::apiSingleton('profile', ProfileController::class);

또한 API 싱글턴 리소스도 creatable 메서드로 storedestroy 라우트를 등록할 수 있습니다.

Route::apiSingleton('photos.thumbnail', ProfileController::class)->creatable();

의존성 주입과 컨트롤러

생성자(컨스트럭터) 주입

라라벨의 서비스 컨테이너는 모든 컨트롤러를 자동으로 해결(resolve)합니다. 따라서, 컨트롤러의 생성자에서 필요한 의존성을 타입-힌트로 선언하면 자동으로 주입됩니다.

<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UserController extends Controller
{
/**
* Create a new controller instance.
*/
public function __construct(
protected UserRepository $users,
) {}
}

메서드 주입

생성자 주입 외에도, 컨트롤러의 메서드에서 의존성을 타입-힌트로 명시할 수도 있습니다. 대표적인 예로, 컨트롤러 메서드에서 Illuminate\Http\Request 인스턴스를 주입받는 방식이 많이 사용됩니다.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class UserController extends Controller
{
/**
* Store a new user.
*/
public function store(Request $request): RedirectResponse
{
$name = $request->name;

// Store the user...

return redirect('/users');
}
}

컨트롤러 메서드에서 라우트 파라미터도 함께 받는 경우, 다른 의존성 다음에 라우트 인수를 나열하면 됩니다. 예를 들어 라우트가 다음과 같이 정의되어 있을 때,

use App\Http\Controllers\UserController;

Route::put('/user/{id}', [UserController::class, 'update']);

Illuminate\Http\Request를 타입-힌트로 받고, 두 번째 인수로 라우트의 id 파라미터를 받아서 사용할 수 있습니다.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class UserController extends Controller
{
/**
* Update the given user.
*/
public function update(Request $request, string $id): RedirectResponse
{
// Update the user...

return redirect('/users');
}
}