PHP 예제로 알아보는 프런트 컨트롤러 (Front Controller) 패턴

jijipapa
11 min readJul 15, 2016

--

PHP 입문서와 튜토리얼의 함정이라는 글에서 현대적인 웹 개발에서 권장하지 않는 방식을 두 가지를 소개한 바 있습니다.

  1. HTML(표현)과 PHP(비즈니스 로직)를 한 파일에 섞어 사용한다.
  2. 마치 HTML처럼 웹페이지별로 PHP 파일이 존재하고 이를 웹 브라우저로 접근한다.

이 두 가지 중 1번은 PHP 예제로 알아보는 MVC 패턴이라는 글을 통해 더 나은 방식으로 개선하는 방법에 대해 알아보았습니다. 이 글에서는 2번, URL과 파일 구조가 결합한 구조를 프런트 컨트롤러 패턴을 이용하여 개선하는 방법을 알아보도록 하겠습니다.

프런트 컨트롤러 패턴이란

프런트 컨트롤러 패턴은 웹 애플리케이션으로 오는 모든 리소스 요청을 처리해주는 하나의 진입점(예를 들면 index.php)을 두는 패턴입니다.[1] 이 패턴은 많은 웹 프레임워크에서 MVC 패턴과 함께 사용됩니다.

일반적으로 MVC 패턴과 프런트 컨트롤러가 함께 적용된 웹 애플리케이션은 [그림 1]과 같은 순서로 요청을 처리합니다.

  1. 사용자가 브라우저를 통해 요청을 전송하면 프런트 컨트롤러가 받아 요청을 처리하기 위한 기본 작업을 수행합니다.
  2. 프런트 컨트롤러는 사용자의 요청을 파악하여 적절한 컨트롤러에 처리를 위임합니다.
  3. 컨트롤러가 모델에게 데이터 처리를 요청하고
  4. 모델은 처리된 데이터를 컨트롤러에 반환합니다.
  5. 컨트롤러는 모델로부터 되돌려받은 데이터를 뷰에 전달합니다.
  6. 뷰는 응답을 생성하여 컨트롤러에 반환합니다.
  7. 컨트롤러가 브라우저에 뷰로 부터 되돌려받은 응답을 전송합니다.
[그림 1] 프런트 컨트롤러와 MVC 패턴이 적용된 웹 애플리케이션의 요청 처리 흐름

프런트 컨트롤러를 사용하면 좋은 점

프런트 컨트롤러를 사용하면 다음과 같은 장점이 있습니다.

  • 모든 요청에 대해 항상 수행해야 하는 작업을 한 곳에서 수행할 수 있다.
  • URL을 더 의미 있게 제공할 수 있다.
  • 파일 구조가 바뀌어도 URL을 유지할 수 있다.
  • 보안성이 강화된다.

간단한 예제를 통해 각 장점을 살펴보겠습니다.

웹서버의 도큐먼트 루트에 다음과 같이 task-list.php 와 task-view.php 가 있다고 합시다. 웹 브라우저를 통해 http://example.com/task-list.php 를 방문하면 할 일 목록이 나타나고, http://example.com/task-view.php 를 방문하면 하나의 할 일에 대한 세부 정보가 나옵니다. task-list.php 와 task-view.php 가 사용자의 요청을 받아서 결과를 응답하는 컨트롤러 역할을 하고 있음을 알 수 있는데, 페이지별로 컨트롤러가 있는 이러한 형태를 페이지 컨트롤러 패턴이라고 합니다.

task-list.php

<?php
require_once __DIR__ . “./vendor/autoload.php”;
$taskModel = new TaskModel();
$tasks = $taskModel->all();
$mode = filter_var($_GET[‘mode’], FILTER_SANITIZE_STRING);if($mode === “app”) {
include “./task-list-json.php”;
} else {
include “./task-list-html.php”;
}

task-view.php

<?php
require_once __DIR__ . "./vendor/autoload.php";
$id = filter_var($_GET[‘id’], FILTER_SANITIZE_NUMBER_INT);$taskModel = new TaskModel();
$task = $taskModel->findById($id);
include "./task-view-html.php";

이제 이 코드를 프런트 컨트롤러 패턴으로 변경해보겠습니다. 우선 할 일과 관련된 요청을 도맡아 처리하는 TaskController 클래스를 만듭니다.

TaskController.php

<?php
class TaskController {
private $taskModel; function __construct(TaskModel $taskModel)
{
$this->taskModel = $taskModel;
}
public function list()
{
$tasks = $this->taskModel->all();
$mode = filter_var($_GET[‘mode’], FILTER_SANITIZE_STRING); if($mode === “app”) {
include “./task-list-json.php”;
} else {
include “./task-list-html.php”;
}
}
public function view()
{
$id = filter_var($_GET[‘id’], FILTER_SANITIZE_NUMBER_INT);
$task = $this->taskModel->findById($id); include "./task-view-html.php";
}
}

task-list.php 와 task-view.php 에 있던 코드를 각각 TaskController 클래스의 list() 와 view() 메소드로 옮겼습니다. 논의의 편의를 위해 중복 코드와 의존성이 있지만 무시합니다.

URL 재작성 (URL rewrite)을 통해 모든 요청을 index.php 로 보내도록 하고 다음과 같이 index.php 를 작성합니다.

index.php

<?phprequire_once __DIR__ . "/vendor/autoload.php";$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

switch($path)
{
case '/task-list.php':
$taskController = new TaskController();
$taskController->list();
break;
case '/task-view.php':
$taskController = new TaskController();
$taskController->view();
break;
default:
require __DIR__ . '/404.php';
}

index.php 에서는 요청받은 URL에 따라 이를 처리할 컨트롤러의 메소드를 호출합니다. 지정되지 않은 URL에 대해서는 404 페이지를 표시합니다.

프런트 컨트롤러를 위한 URL 재작성 방법은 라라벨 5.1 메뉴얼의 Pretty URLs[2] 을 참고하세요.

중복 코드 제거 및 공통 작업 관리

위의 예제에서 페이지 컨트롤러인 task-list.php 와 task-view.php 가 TaskController 클래스로 통합되고 task-list.php와 task-view.php 에서 각각 호출하던 `require_once __DIR__ . “/vendor/autoload.php”` 가 index.php 에서 한 번만 호출하도록 변경된 것을 확인할 수 있습니다.

간단한 예제라 페이지마다 중복되는 한 줄이 제거되었지만, 규모 있는 웹 애플리케이션에서는 세션, 인증, 로그 남기기 등 다양한 공통 코드의 중복이 제거되는 효과를 얻을 수 있습니다.

더 알기 쉽고, 의미 있는 URL 제공 가능

index.php 에서 요청받은 파일명에 따라 컨트롤러와 메소드를 각각 호출하고 있습니다. 간략하게 구조만 보면 다음과 같습니다.

switch($path)
{
case '/tast-list.php':

case '/task-view.php':
}

URL에 따라 컨트롤러와 메소드를 호출하는 것이기 때문에 URL이 파일명과 일치할 필요가 없습니다. 따라서 다음과 같이 변경할 수 있습니다.

switch($path)
{
case '/task/list': # 할일 목록 조회

case '/task/view': # 개별 할일 조회
}
혹은switch($path)
{
case '/tasks': # 할일 목록 조회
case '/task: # 개별 할일 조회
}

프런트 컨트롤러를 더 정교하게 구현한다면 `http://example.com/task/list/page/3` 와 같이 보기 좋고 의미 파악이 쉬운 URL을 제공할 수도 있게 됩니다. 그뿐만 아니라, URL이 파일명에 구애받지 않게 됨에 따라 이른바 RESTful API 를 구현하는 기반이 될 수 있습니다.

파일 구조 변경 시에도 URL이 유지됨

URL이 변경되면 외부 사이트에 걸린 링크나 사용자의 즐겨찾기 등이 망가지게 되고, 검색 엔진 최적화에도 불리합니다. 따라서 같은 내용을 제공하는 페이지라면 URL이 변경되지 않는 것이 좋습니다.

웹 애플리케이션의 규모가 커짐에 따라 더 효율적인 작업을 위해 파일 구조를 변경해야 할 일이 발생할 수 있습니다. 다음과 같이 컨트롤러, 모델, 뷰 파일들을 각각 Controllers, Models, Views 디렉터리로 옮긴다고 생각해봅시다.

index.php
Controllers/
TaskController.php
Models/
TaskModel.php
Views/
task-list-html.php
task-list-json.php
task-view-html.php
vendor/

이렇게 파일 구조가 바뀌는 경우 TaskController 에서 뷰를 인클루드하는 경로만 바꿔주면 되고, index.php 는 수정할 필요가 없습니다. 즉, 프런트 컨트롤러가 적용되어 있다면 파일 구조가 변경되어도 URL은 변경되지 않아 더 안정적인 서비스를 제공할 수 있게 됩니다.

보안성 강화

현재 예제는 모든 파일이 웹서버의 도큐먼트 루트 하위에 있어 웹 브라우저로 직접 접근이 가능한 상태입니다. 만에 하나 웹서버 설정이 잘못되는 경우 파일이 다운로드 되거나, 웹 브라우저를 통해 소스 코드가 외부에 공개될 가능성이 있습니다. 그렇게 공개된 소스 코드에 데이터베이스 접속에 필요한 값들이라도 들어있는 날에는 정말 큰 일이 나겠죠.

웹서버의 보안성 강화를 위해 서비스를 제공하기 위한 최소한의 파일 이외에는 외부에서 직접 접근하지 못하도록 하는 것이 필요합니다. 그래서 많은 프레임워크들이 프런트 컨트롤러와 정적인 리소스만 외부에 노출되도록 하고 나머지 파일들은 외부에서 접근할 수 없도록 파일 구조를 갖추고 있습니다.

다음과 같이 public 디렉터리를 만들고 index.php 를 public 디렉터리에 이동시킨 뒤, 웹서버의 도큐먼트 루트를 public 디렉터리로 지정하면 외부에서는 public 디렉터리에 있는 index.php 파일 이외에는 어떤 파일에도 직접 접근할 수 없습니다. 예제의 index.php 의 경우 외부에 소스 코드가 유출되어도 보안상 문제 될 내용이 없으므로, 더 보안성이 강화되었다고 할 수 있습니다.

public/
index.php
Controllers/
TaskController.php
Models/
TaskModel.php
Views/
task-list-html.php
task-list-json.php
task-view-html.php
vendor/

이로써 간단한 소스 코드에 프런트 컨트롤러를 적용해 보면서 프런트 컨트롤러를 사용하면 좋은 점을 알아봤습니다. 프런트 컨트롤러를 사용하면 페이지 컨트롤러를 사용하는 것에 비해 중복 코드가 줄어들고, 예제에서는 아주 간단한 프런트 컨트롤러를 직접 만들어서 사용하였습니다. 실전에서는 직접 프런트 컨트롤러를 구현할 필요 없이 auraphp/Aura.Router, thephpleague/routenikic/FastRoute, klein/klein.php, c9s/Pux 같은 잘 만들어진 패키지를 사용하면 더 쉽게 다양한 기능을 사용할 수 있습니다.

참고자료

[1] 디자인 패턴, PHP the Right Way, http://modernpug.github.io/php-the-right-way/pages/Design-Patterns.html

[2] Pretty URLs , 라라벨 5.1 메뉴얼 기본적인 설정하기, https://laravel.kr/docs/5.1/installation#basic-configuration

이 글이 포함된, 바쁜 팀장님 대신 알려주는 신입 PHP 개발자 안내서가 출간되었습니다. 어떤 내용이 담겨있는지 목차를 확인하러 가보세요. 흥미로울 것입니다.

--

--

jijipapa

Seeking Alpha $OHM, $KLIMA, $ABI, $BTRFLY, $LUNA, $MINE