August 8, 2020

PHP MVC - Laravel Routing

Title: PHP Laravel - Routing
Author: DongDongE
Tags: Programming
Release: 2020.08.08


Routing


기본 라우팅 - Basic Routing

가장 기본적인 라라벨 경로는 단순히 URL와 Closure 경로를 정의하는 매우 간단하고 표현적인 방법을 제공합니다.

Route::get('foo', function () {
    return 'Hello World!';
});

기본 라우트 파일


모든 Laravel 라우트는 라우트 디렉터리에 있는 라우트 파일에 정의됩니다. 이러한 파일은 프레임워크에 의해 자동로딩되며 "routes/web.php" 웹 인터페이스를 위한 라우트(경로)를 정의합니다. 이러한 경로에는 세션 상태 및 CSRF 보호와 같은 기능을 제공하는 "web" 미들웨어 그룹이 할당되어 있습니다. "routes/api.php"안에 들어 있는 경로는 상태 비저장이며, API 미들웨어 그룹에 할당됩니다.

대부분의 애플리케이션에서는 "routes/web.php" 파일에 라우트를 정의해서 시작할 수 있습니다.



사용 가능한 라우터 메소드


라우터를 사용하면 HTTP 응답하는 경로(라우트)를 등록할 수 있습니다.

Route::get($uri,     $callback);
Route::post($uri,    $callback);
Route::put($uri,     $callback);
Route::patch($uri,   $callback);
Route::delete($uri,  $callback);
Route::options($uri, $callback);


가끔은 여러 HTTP 메소드에 응답하는 라우티(경로)를 등록해야하는 경우가 있습니다. 그럴 경우는 "match" 메소드를 사용하며 됩니다. 또는 "any" 메소드를 사용하여 모든 HTTP 메소드에 응답하는 라우트(경로)를 등록할 수 있습니다.


Route::match(['get', 'post'], '/', function () {
    /* HTTP GET, POST 메소드를 통하여 http://<IP>/로 접근시 로직 수행한다. */
    //
});

Route::any('foo', function () {
    /* HTTP 모든 메소드를 통하여 http://<IP>/foo로 접근시 로직 수행한다. */
    //
});



CSRF 보호하기


"web" 경로 파일에 정의된 "POST", "PUT", "DELETE" 경로를 가리키는 라우트들은 모두 CSRF 토큰 필드를 포함해야 합니다. 그렇지 않으면, 해당 request(요청)들은 거부됩니다.

<form method="POST" action="/profile">
    {{ csrf_field() }}
    ...
</form>

<form method="POST" action="/profile">
    <input type="hidden" name="_token" value="EGWnh6kWworctcxBr8J0aXDHWzxr4Mt3bY6poiJb">
</form>




Route Parameters - 라우트 파라미터

필수적 파라미터


라우트 경로내에서 URI 세그먼트를 필요로 할 수 있습니다. 예를 들어 URL에서 사용자의 ID를 전달 받을 수 있으며, 라우트 매개변수(파라미터)를 정의하여 수행할 수 있습니다.


Route::get('user/{id}', function ($id) {
    return 'User ' . $id;
});

또는 아래와 같이 필요한 만큼 파라미터를 여러개 전달 받을 수 있습니다.


Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) {
    //
});

라우트 매개변수는 항상 "{}" 중괄호로 쌓여 있으며 알파벳 문자로 구성되어야 합니다. 경로 파라미터는 특수문자 "-"를 포함할 수 없습니다. 대신 "_" 밑줄(언더스코어)을 사용할 수 있습니다.



선택적 파라미터


경우에 따라 파라미터를 지정해야 하지만, 경우에 따라 파라미터의 존재 여부를 선택적으로 설정할 수 있습니다.
"?"를 사용하여 처리할 수 있으며, 파라미터 이름 뒤에 표시를 하면 됩니다. 또한 라우트의 해당 변수에 기본값을 지정해야 합니다. 보다 자세한건 아래 예시 코드로 대처 하겠습니다.


Route::get('user/{name?}', function ($name = null) {
    /* http://<IP>/users/로 접근시 null 반환 */
    return $name;
});

Route::get('user/{name?}', function ($name = 'John') {
    /* http://<IP>/users/로 접근시 John 반환 */
    return $name;
});



정규표현식 제약


라우트 경로 인스턴스에서 "where" 메소드를 사용하여 라우트의 파라미터 형식(포맷)을 제한할 수 있습니다.
"where" 메소드는 파라미터의 이름과 파라미터를 제한하는 방법을 정의하는 정규표현식을 허용합니다.


Route::get('/user/{name}', function ($name) {
    /* "name" 파라미터에는 영어 대소문자로 시작하여 끝날 경우 실행 */
    //
})->where('name', '[A-Za-z]+');

Route::get('/user/{id}', function ($id) {
    /* "id" 파라미터에 숫자만 있을 경우 실행 */
    //
})->where('id', '[0-9]+');

Route::get('user/{id}/{name}', function ($id, $name) {
    /*
       "id" 파라미터에는 숫자만
       "name" 파라미터에는 영문 소문자만 있을 경우 실행
    */
    //
})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);



글로벌 제약


라우트 파라미터가 항상 주어진 정규표현식에 의해 제한되도록 하려면 아래와 같이 "pattern" 메소드를 사용할 수 있습니다. "RouteServiceProvider"의 "boot" 메소드 안에서 사용해야 합니다.

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;

class RouteServiceProvider extends ServiceProvider
{

    protected $namespace = 'App\Http\Controllers';

    /**
     * Define your route model bindings, pattern filters, etc.
     *
     * @return void
     */
    public function boot()
    {
        Route::parttern('id', '[0-9]+');

        parent::boot();
    }
...생략

app/Providers/RouteServiceProvider.php


패턴을 한번 정의하고, 해당 파림터 이름을 사용하는 모든 라우트에게는 자동으로 적용됩니다.


Route::get('user/{id}', function ($id) {
    /* "id" 파라미터는 숫자일 경우 실행한다. */
    //
});



이름 지정된 라우트


이름이 지정한 라우트는 URL이나 특정 지정된 라우트로 리다이렉트를 쉽고 편리하게 생성할 수 있습니다. 라우트 정의에 "name" 메소드를 연결(Chaining)하여 라우트에 대한 이름을 지정할 수 있습니다.

Route::get('user/profile', function () {
    //
})->name('profile');

아래와 같이 컨트롤러 액션에도 라우트 이름을 지정할 수 있습니다.


Route::get('user/profile', 'UserController@showProfile')->name('profile');



이름 지정된 라우트에 대한 URL 생성


지정된 라우트에 이름을 지정하면 URL을 생성하거나 전역 "route" 메소드 기능을 통해 리다이렉트 할 때 라우트의 이름을 사용할 수 있습니다.

/* URLs 생성 */
$url = route('profile');

/* 리다이렉트 생성 */
return redirect()->route('profile');

이름이 지정된 라우트에 파라미터를 정의하는 경우, "route" 메소드의 두 번째 인자로 파라미터를 전달할 수 있습니다. 주어진 파라미터는 자동으로 올바른 위치에 있는 URL에 삽입됩니다.


Route::get('user/{id}/profile', function ($id) {
    //
})->name('profile');

$url = route('profile', ['id' => 1]);




Route Groups - 라우트 그룹

라우트 그룹을 사용하면 각 개별 경로에서 이러한 특성을 정의할 필요 없이 많은 경로에서 "미들웨어" 또는 "네임 스페이스"와 같은 라우트 특성을 공유 할 수 있습니다. 공유 속성은 "Route::group" 메소드에 대한 첫 번째 파라미터로 배열 형식으로 지정됩니다.


미들웨어


그룹내 모든 라우트에 미들웨어를 할당하려면 그룹 속성 배열에서 "middleware" Key를 사용할 수 있습니다. 미들웨어는 배열에 나열된 순서대로 실행됩니다.


Route::group(['middleware' => 'auth'], function () {
    Route::get('/', function () {
        // Uses Auth Middleware
        //
    });
    
    Route::get('user/profile', function () {
        // Uses Auth Middleware
        //
    });
});



네임스페이스


라우트 그룹에 대한 또 다른 일반적인 사용 사례는 그룹 배열 안에서 "namespace" 파라미터를 사용하는 컨트롤러 그룹에게 동일한 PHP "namespace"를 할당하는 경우 입니다.


Route::group(['namespace' => 'Admin'], function () {
    // Controllers Whithin The App\Http\Controllers\Admin Namespace
});

기본적으로 "RouteServiceProvider"에는 네임스페이스 그룹내에 라우트 파일이 포함되어 있으므로 전체 "App\Http\Controllers" 네임스페이스 접두사를 지정하지 않고도 컨트롤러 경로를 등록할 수 있습니다. 즉, 네임스페이스에서 필요한 부분은 "App\Http\Controllers\<네임스페이스명>"에서 뒤에 오는 네임스페이스만 지정하면 됩니다.



서브 도메인 라우팅


라우트 그룹을 사용하면 하위 도메인 라우팅을 처리할 수 있습니다. 하위 도메인에는 라우트 URL와 마찬가지로 라우트 파라미터가 할당될 수 있으므로 라우트 또는 컨트롤러에서 사용할 하위 도메인의 일부를 추출할 수 있습니다. 하위 도메인은 그룹 속성 배열의 "domain" 키를 사용하여 지정할 수 있습니다.

Route::group(['domain' => '{account}.myapp.com'], function () {
    Route::get('user/{id}', function ($account, $id) {
        //
    });
});



라우트 Prefixes


"Prefix" 그룹 속성은 주어진 URI로 그룹의 각 라우트에 접두사를 붙이는 데 사용될 수 있습니다. 예를 들어 그룹내의 모든 라우트 URI에 "admin" 접두사로 지정할 수 있습니다.


Route::group(['prefix' => 'admin'], function () {
    Route::get('users', function () {
        // Matches The "/admin/users" URL
    });
});




Route Model Binding - 라우트 모델 바인딩

라우트 또는 컨트롤러 작업에 모델 ID를 삽입할 때 종종 해당 ID에 모델을 검색하기 위해 쿼리합니다. 라라벨 경로 모델 바인딩은 모델 인스턴스를 경로에 직접 자동으로 주입하는 편리한 방법을 제공합니다.
예를 들어 사용자의 ID를 삽입하는 대신 주어진 ID와 일치하는 전체 "User" 모델 인스턴스를 삽입할 수 있습니다.


무시적 바인딩


라라벨은 변수 이름이 라우트 세그먼트 이름과 일치하는 라우트 또는 컨트롤러 작업에 정의된 "Eloquent" 모델을 자동으로 해결합니다.


Route::get('api/users/{users}', function (App\User $user) {
    return $user->email;
});

해당 예제에서 라우트에 정의된 "Eloquent" $user 변수가 라우트 URI의 "{user}" 세그먼트와 일치하므로 라라벨은 요청 URI의 해당 값과 일치하는 ID를 가진 모델 인스턴스를 자동으로 삽입합니다.
데이터베이스에서 일치하는 모델 인스턴스를 찾을 수 없는 경우 404 HTTP 응답이 자동으로 생성됩니다.



키 이름 변경


주어진 모델 클래스를 검색할 때 "id" 이외의 데이터베이스 열을 사용하도록 모델 바인딩을 원하는 경우 "Eloquent" 모델에서 "getRouteKeyName" 메소드를 재정의 할 수 있습니다.


public function getRouteKeyName() {
    return 'slug';
}



명시적 바인딩


명시적 바인딩을 등록하려면 라우터의 "model" 메소드를 사용하여 주어진 파라미터에 대한 클래스를 지정하면 됩니다. "RouteServiceProvider" 클래스의 부팅 메소드에서 명시적 모델 바인딩을 정의해야 합니다.


public function boot() {
    parent::boot();
    
    Route::model('user', App\User::class);
}

그 다음으로 {user} 파라미터를 포함하는 라우트를 정의해야 합니다.


Route::get('profile/{user}', function (App\User $user) {
    //
});

모든 {user} 파라미터를 "App\User" 모델에 바인딩 했으므로, "User" 인스턴스가 라우테 삽입됩니다. 예를 들어 "profile/1"에 대한 요청은 ID가 "1"인 데이터베이스에서 User 인스턴스를 삽입합니다.

만약 데이터베이스에서 일치하는 모델 인스턴스를 찾을 수 없는 경우 404 HTTP 응답이 자동으로 생성됩니다.



Customizing The Resolution Logic - 의존성 해결 로직 커스터마이징


고유한 의존성 해결 로직을 사용하기 위해 "Route::bind" 메소드를 사용할 수 있습니다. "bind" 메소드에 전달되는 "Closure(클로저)"는 URI 세그먼트의 값을 수신하고 라우트에 삽입해야하는 클래스의 인스턴스를 반환해야 합니다.


public function boot() {
    parent::boot();
    
    Route::bind('user', function ($value) {
        return App\User::where('name', $value)->first();
    });
}




Form Method Spoofing

HTML 폼에는 "PUT", "PATCH", "DELETE" 등 작업을 지원하지 않습니다. 따라서 HTML 폼에서 호출되는 "PUT", "PATCH", "DELETE" 라우트를 정의할 때 숨겨진 "_method" 필드를 양식에 추가해야 합니다.
"_method" 필드와 함께 전송된 값은 HTTP Request 메소드로 사용됩니다.


<form action="/foo/bar" method="POST">
    <input type="hidden" name="_method" value="PUT">
    <input type="hidden" name="_token" value="{{ csrf_token() }}">
</form>

아래와 같이 "method_field" 도우미 메소드(함수)를 사용하여 자동으로 "_method" 입력을 생성할 수 있습니다.


{{ method_field('PUT') }}




현재 라우트에 액세스하기

"Route" 파사드의 "current", "currentRouteName", "currentRouteAction" 메소드를 사용하여, 들어오는 요청을 처리하는 라우트에 대한 정보에 액세스할 수 있습니다.


$route = Route::current();

$name = Route::currentRouteName();

$action = Route::currentRouteAction();




라우트 실습해보기

<h1>test123</h1>

"resources/views/welcome.blade.php"


해당 뷰에 테스트 글자로 변경합니다.



위와 같이 해당 페이지에 접근시 "test123" 문자열이 출력됩니다.


현재 브라우저에 "http://<IP>/"로 "/" 웹 루트 디렉터리에 접근을 요청하였으며, 웹서버는 "welcome.blade.php"의 내용을 표시하였습니다. 이 과정은 "routes/web.php"에서 확인할 수 있습니다.


<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| This file is where you may define all of the routes that are handled
| by your application. Just tell Laravel the URIs it should respond
| to using a Closure or controller method. Build something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

routes/web.php


해당 PHP 로직을 보면 웹 서버 "/"로 요청이 들어오면 view 메소드를 사용하여 "resources/views/selcome.blade.php" 코드를 블레이드 엔진을 사용하여 사용자 브라우저에 출력합니다.


하지만 "view" 메소드 대신 "return" 인자로 문자열을 통하여 해당 문자열을 반환하거나 PHP 코드를 넣어 로직을 수행할 수 있습니다.


Route::get('/', function () {
    return "<h1>test123</h1>";
});

routes/web.php




라우트 파라미터 실습하기

Route::get('/{foo}', function ($foo) {
    return $foo;
});

routes/web.php


"http://<IP>/<파라미터>"로 접근시 해당 파라미터에 전달되는 값이 "/{foo}"에서 함수 "$foo"로 전달되어 return 메소드를 통하여 사용자 브라우저에 파라미터 값이 출력됩니다.
예를 들면, "http://<IP>/abcdefg"로 접속하였다면 브라우저에 "abcdefg" 출력됩니다.



위 "정규표현식 제약" 파트 부분에서 3가지 방법으로 정규표현식을 "where" 메소드를 통하여 사용할 수 있지만 "글로벌 제약" 파트 처럼 "pattern" 메소드를 사용할 수 있습니다.


Route::pattern('name', '[0-9a-zA-Z]+');

Route::get('/{name}', function ($name = 'aaaaa') {
    return $name;
});

해당 파라미터 "pattern" 메소드에 의해 정규표현식으로 숫자, 영대소문자를 포함이 되어 있을 경우 "$name" 변수에 의해 사용자 화면에 파라미터 값이 출력됩니다.


만약 위 파라미터의 정규표현식에 벗어나는 패턴 (Ex. http://<IP>/abcdef@@#) 입력시 "NotFountHTTPException"를 예외 반환을 합니다.


Sorry, the page you are looking for could not be found.

NotFoundHttpException in RouteCollection.php line 161:

in RouteCollection.php line 161
at RouteCollection->match(object(Request)) in Router.php line 766
at Router->findRoute(object(Request)) in Router.php line 621
at Router->dispatchToRoute(object(Request)) in Router.php line 607
at Router->dispatch(object(Request)) in Kernel.php line 268
at Kernel->Illuminate\Foundation\Http\{closure}(object(Request)) in Pipeline.php line 53
at Pipeline->Illuminate\Routing\{closure}(object(Request)) in CheckForMaintenanceMode.php line 46
at CheckForMaintenanceMode->handle(object(Request), object(Closure)) in Pipeline.php line 137
at Pipeline->Illuminate\Pipeline\{closure}(object(Request)) in Pipeline.php line 33
at Pipeline->Illuminate\Routing\{closure}(object(Request)) in Pipeline.php line 104
at Pipeline->then(object(Closure)) in Kernel.php line 150
at Kernel->sendRequestThroughRouter(object(Request)) in Kernel.php line 117
at Kernel->handle(object(Request)) in index.php line 53
at require_once('/var/www/html/myapp/public/index.php') in server.php line 21




라우트 명 지정하여 실습하기

라우트에 이름을 지정하여 컨트롤러에서 다른 라우트로 리다이렉션 처리 하거나, 뷰에서 다른 라우트로 리다이렉션 링크를 설정할 수 있습니다.


Route::get("/", ["as" => "name",
    function () {
        return "My Name is ...";
    }
]);

Route::get("/name", function () {
    return redirect(route("name"));
});

Route::get("/test", function () {
    return redirect(route("name"));
});

routes/web.php


위 로직은 웹 루트 디렉터리 "/" 또는 "/name", "/test"로 접근시 자동으로 "My Name is ..." 구문을 반환하여 사용자 브라우저에 출력됩니다. 한마디로 "redirect" 메소드를 통하여 HTTP 302 Found 반환으로 Location으로 이동하게 됩니다. 이동 목적지는 "route" 메소드 인자로 선언된 부분으로 이동됩니다. 현재 선언된 인자는 "name" 이므로 "as" => "name" 이 지정된 라우트로 이동하게 됩니다.


Route redirect(string $uri, string $destination, int $status = 302)
Create a redirect from one URI to another.

Parameters
string	$uri	
string	$destination	
int	$status	
Return Value
Route

laravel API Doc에 redirect 메소드에 관한 정보입니다. 인자로는 3가지를 받습니다.




# 본글은 라라벨 공식 홈페이지 가이드를 기반으로 번역 및 개인적으로 연구한 내용을 바탕으로 쓴 글입니다.
오타 및 오역이 발생시 언제든지 댓글로 남겨주시면 확인후 변경하도록 하겠습니다.