Micro-framework의 전성기라고 할 만큼 다양한 환경과 언어로 프레임워크가 쏟아지고 있다. PHP에도 micro-framework가 많이 나와 있는데1 최근 Laravel에서 Lumen을 발표했다. 발표 자료에서는 symfony2 기반인 silex보다 1.9배 빠르다고 하는데 문법적으로는 Silm과 상당히 유사한 느낌도 든다. 기존에 나왔던 프레임워크와 엄청나게 큰 구조 차이를 가지고 있는 것은 아니지만 Laravel과의 호환을 염두한 부분도 많다는 느낌을 받았다. 또한 구조적으로도 silex나 여타 기존에 나온 micro-framework 보다 훨씬 깔끔하고 미려하다는 느낌을 받았다.

lumen logo

이 포스트는 lumen 문서에서 쉽게 볼 수 있는 부분만 다뤘고 더 깊은 내용을 보고 싶다면 Lumen 공식 문서를 보는게 도움이 된다. Lumen는 PHP >= 5.4를 요구하며 Mcrypt, OpenSSL, mbstring, tokenizer 확장을 필요로 한다.

Lumen 설치

lumen을 설치하기 위해서는 composer가 설치되어 있어야 한다.

lumen을 사용해 프로젝트를 시작하는 방법은 lumen installer를 사용하는 방법과 composer의 create-project로 생성하는 방법이 있다. 결과물은 동일한데 installer 속도가 더 빠르다.

composer.json, phpunit.xml 등 단순한 스캐폴딩을 함께 제공한다.

Lumen Installer

다음 명령어로 Lumen Installer를 설치한다. 이 installer는 커맨드라인 환경에서 lumen 프로젝트를 시작할 수 있도록 기능을 제공한다.

composer global require "laravel/lumen-installer=~1.0"

설치가 완료되면 lumen new 명령어로 프로젝트를 생성할 수 있다. 여기서 helloworld라는 이름으로 프로젝트를 생성했다.

lumen new helloworld

다음과 같이 프로젝트가 생성된 것을 확인할 수 있다.

lumen init

composer

위 인스톨러를 사용할 수 없다면 composer create-project 생성할 수 있다.

composer create-project laravel/lumen --prefer-dist

설정하기

lumen은 laravel과 다르게 모든 설정을 .env에 저장한다. 쉽게 활용할 수 있도록 .env.example 템플릿이 제공된다. 데이터베이스, 캐시, 큐, 세션과 관련한 설정값을 지정할 수 있다. 설정에서 가장 먼저 할 일로 해당 템플릿을 열어 APP_KEY에 32자 무작위 문자열을 입력한 후 .env로 저장한다.

URL 설정

Apache 환경을 사용하고 있다면 public/.htaccess를 활용할 수 있고 Nginx에서는 다음과 같이 설정할 수 있다.

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

HTTP 라우팅

라우팅 기초

route는 app/Http/routes.php에 작성한다. 여타 micro-Framework과 크게 다르지 않은 문법이다.

$app->get('/', function() {
  return 'Hello World';  
});
$app->post('foo/bar', function() {
  // do something
});
$app->patch('foo/bar', function() {
  // do something
});
$app->put('foo/bar', function() {
  // do something
});
$app->delete('foo/bar', function() {
  // do something
});

이렇게 생성한 route에서 URL을 다른 곳에서 사용하고 싶다면 url 헬퍼를 쓴다.

$list_link = url('foo');

router에서 파라미터는 다음과 같이 사용한다.

$app->get('user/{id}', function($id) {
  return 'User '.$id;
});

$app->get('user/{name:[A-Za-z]+}', function($name) {
  // do something
  // 이 문법은 라라벨과 호환되지 않는다.
});

컨트롤러와도 쉽게 연결할 수 있다.

$app->get('user/{id}', 'UserController@showProfile');

위 route와 같이 복잡한 URL 구조를 가지고 있다면 route에 as로 별칭을 지정해 쉽게 활용할 수 있다.

$app->get('user/profile', ['as' => 'profile', function() {
  // show user profile
}]);

$app->get('user/dashboard', [
  'as' => 'dashboard',
  'uses' => 'UserController@showDashboard'
]);

이제 위 route는 profile이라는 이름으로 활용할 수 있다. 파라미터가 있는 경우는 두번째 파라미터에 array로 값을 넣으면 된다.

$url = route('profile');
$redirect = redirect()->route('profile');

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

라우팅 그룹 묶기

route를 그룹으로 묶어 미들웨어나 네임스페이스를 지정할 수 있다. 여기에서 $app->group()를 활용한다.

Closure를 기반으로 한 미들웨어는 다음과 같이 사용할 수 있다.

$app->group(['middleware' => 'foolbar'], function($app) {
  $app->get('/', function() {
    // do something
  });
  $app->get('user/profile', function() {
    // do something
  });
});

namespace로 특정 네임스페이스에 있는 컨트롤러를 불러 활용할 수 있다.

$app->group(['namespace' => 'Admin'], function() {
  // "App\Http\Controllers\Admin" 네임스페이스에 있는 컨트롤러
});

HTTP 예외 발생하기

abort 헬퍼를 이용한다. 응답을 같이 보내줄 수도 있다.

abort(404);
abort(403, 'Unauthorised action.');

이 헬퍼는 상태 코드와 함께 Symfony\Component\HttpFoundation\Exception\HttpException 예외를 던진다.

뷰는 resources/views에 php 파일로 저장한다.

<!-- View stored in resources/views/greeting.php -->

<!doctype html>
<html>
    <head>
        <title>Welcome!</title>
    </head>
    <body>
        <h1>Hello, <?php echo $name; ?></h1>
    </body>
</html>

다음과 같이 사용할 수 있다.

$app->get('/', function() {
  return view('greeting', ['name' => 'James']);
});

$app->get('/admin', function() {
  /* ... */

  // resources/views/admin/dashboard.php
  return view('admin.dashboard', $data);
});

데이터 바인딩을 배열로 넘길 수 있지만 with 메소드나 매직 메소드를 활용할 수도 있다.

$view = view('greeting')
          ->with('name', 'Edward')
          ->with('age', 20)
          ->withLocation('Jeju'); // 매직 메소드

컨트롤러

규모가 커지면 routes.php 파일 하나로만 로직을 다루는 것보다 컨트롤러를 활용해서 구조화하는 것이 낫다. 컨트롤러로 HTTP 요청을 조작하는 로직을 쉽게 묶을 수 있다. 컨트롤러는 app/Http/Conterollers에 저장한다.

컨트롤러는 App\Http\Conterollers\Controller 기초 클래스를 필수적으로 상속해야 한다. 기본적인 컨트롤러는 다음과 같다.

<?php namespace App\Http\Controllers;

use App\User;
use App\Http\Controllers\Controller;

class UserController extends Controller {

    /**
     * Show the profile for the given user
     *
     * @param  int  $id
     * @return Response
     */
    public function showProfile($id)
    {
        return view('user.profile', ['user' => User::findOrFail($id)]);
    }

}

UserController를 라우터에서 다음과 같이 연결할 수 있다.

$app->get('user/{id}', 'App\Http\Controllers\UserController@showProfile');

의존성 주입

Lumen과 Laravel의 서비스 컨테이너는 타입 힌트를 통해 의존성을 해결해준다. 생성자 주입, 메소드 주입 둘 다 사용 가능하다. 다음 코드는 생성자의 타입 힌트 UserRepository, store 메소드의 타입힌트 Request로 의존성이 주입되는 예제다.

<?php namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Repositories\UserRepository;
use Illuminate\Http\Request;

class UserController extends Controller {

    /**
     * The user repository instance.
     */
    protected $users;

    /**
     * Create a new controller instance.
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
     * Store a new user.
     *
     * @param  Request  $request
     * @return Response
     */
    public function store(Request $request)
    {
        $name = $request->input('name');
    }

}

미들웨어

HTTP 미들웨어는 HTTP 요청과 응답을 제어할 수 있도록 돕는 구조로 micro-Framework에서는 흔히 사용되고 있다. (Python의 uWSGI, .Net의 OWIN) 미들웨어는 app/Http/Middleware에 위치한다.

미들웨어를 활용하기 위해 구성해야 하는 메소드는 handle이다.

<?php namespace App\Http\Middleware;

class OldMiddleware {

    /**
     * Run the request filter.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ($request->input('age') < 200) {
            return redirect('home');
        }

        return $next($request);
    }

}

미들웨어로 HTTP 요청과 응답을 모두 제어할 수 있는데 $next($request)를 기준으로 그 앞에서는 요청을 제어하고 뒤에서는 응답을 제어할 수 있다.

다음은 요청을 제어하는 미들웨어로 이 내용을 실행한 후 어플리케이션에 접근하게 된다.

<?php namespace App\Http\Middleware;

class BeforeMiddleware implements Middleware {

    public function handle($request, Closure $next)
    {
        // Perform action

        return $next($request);
    }
}

다음은 응답을 제어하는 미들웨어 예시로 어플리케이션에서 처리가 끝난 후 클라이언트에게 전달되는 응답을 제어할 수 있다.

<?php namespace App\Http\Middleware;

class AfterMiddleware implements Middleware {

    public function handle($request, Closure $next)
    {
        $response = $next($request);

        // Perform action

        return $response;
    }
}

Service Provider와 Service Container

Lumen은 Service ProviderService Container로 기초를 구성하고 있다. Service Provider는 어플리케이션을 시작하기 전에 준비해야 할 작업을 처리할 수 있다. bootstrap/app.php를 열어보면 $app->register() 메소드를 확인할 수 있는데 이 메소드를 통해 추가적인 service provider를 등록할 수 있다.

Service Container는 클래스 간의 의존성을 해결하기 위한 도구로 생성자 또는 “setter” 메소드를 통해 의존성을 주입해준다. $app->bind(), $app->singleton()을 통해 resolver를 등록할 수 있다. 컨테이너에서 의존성을 주입 받기 위해서는 $foobar = $app->make('FooBar'); 방식으로 make 메소드를 사용하는 방법이 있고, 앞서 살펴본 방식인 생성자나 개별 메소드에서 타입 힌팅을 이용해 의존성을 주입할 수 있다. 자세한 내용은 각 문서를 참고하자.


Micro-framework지만 그 말이 무색할 만큼 현대적인 PHP 개발에서 필요한 필수적인 요소는 모두 포함된 강력함을 보여주고 있다. 이 포스트에서 자세하게 다루진 않았지만 많은 서비스도 제공하고 있으며 기존에 laravel을 사용하고 있다면 더 간편하게 사용할 수 있을 것이라 본다. 지금까지 나온 micro-framework 중에서는 가장 마음에 드는 구성이라 차기 프로젝트에서 사용하는 것을 생각해보고 있다. 앞으로 기대가 많이 되는 프레임워크다.

  • PHP 기반의 Micro Frameworks 정리 
  • 근래 들어서는 공개적으로 하는 작업은 아니지만 잔잔하게 프로토타이핑은 꾸준히 하고 있는데 flasksqlalchemy 조합으로 진행하고 있었다.

    • flask는 micro web framework이며 micro 답게 간단하게 작성 가능해 생각나는 대로 작성하기 편리
    • sqlalchemy는 class 정의 만으로 db를 쉽게 구성하고 코드와 스키마를 두번 작성하는 수고를 줄여주는, 짱짱 좋은 ORM

    호기심으로 로컬의 php를 이번달에 공개한 5.5.5으로 업데이트 하면서 micro framework이 없을까 찾아봤더니 비슷한 컨셉의 프레임워크가 많이 보여 간단하게 정리해봤다. (sqlalchemy를 대안으로 사용할 php orm은 제대로 찾아보지 못했다.)

    찾아보니 생각보다 많은 편이었고 flask에서 이용한 파이썬의 delegate과 같은 feature는 php에 존재하지 않기 때문에 다양한 방식으로 구현되어 있었다. 특히 php에서 익명함수(Anonymous function)는 5.3.0 이후 제공되고 있기 때문에 이를 기준으로 지원 여부를 살펴보는 것도 도움이 된다. 1

    대다수 micro framework는 Composer라는 의존성 관리도구를 설치하길 권장한다. Composer 시작하기 문서를 살펴보면 도움이 된다.

    Slim

    Slim은 composer를 통해 간편하게 설치할 수 있으며 PHP 5.3.0 이상을 요구한다.

    $app = new \Slim\Slim();
    $app->get('/hello/:name', function($name){
        echo 'Hello, ' . $name;
    });
    $app->run();
    

    $app에 인스턴스를 할당할 때 mode, debug 등 다양한 옵션을 넣을 수 있으며 use2를 이용한 스코핑 인젝션 등이 가능하다. 익명함수, 일반함수 두가지 모두 가능하다. Route가 명시적이라 코드 리딩이 더 쉬운 편이다. 자세한 내용은 Slim의 문서 http://www.slimframework.com/ 참고.

    Limonade

    Limonade http://limonade-php.github.io/는 루비의 Sinatra와 Camping, Lua의 Orbit의 영감으로 만들어진 PHP micro framework이다.

    require_once 'vendors/limonade.php';
    dispatch('/', 'hello');
      function hello()
      {
          return 'Hello world!';
      }
    run();
    

    각 기능을 함수로 선언해 dispatch를 이용해 각 URL에 라우팅 하는 형태로, 직관적인 코드 작성이 가능하지만 동일한 함수명을 사용하지 않도록 주의해야 한다.

    Flight

    Flight라는 Public class를 이용해 작성해나가는 형태의 framework이다.

    require 'flight/Flight.php';
    
    Flight::route('/', function(){
        echo 'hello world!';
    });
    
    Flight::start();
    

    사용자가 선언한 인스턴스가 아닌 Flight를 반복적으로 입력해야 해서 별로 좋은 것 같진 않다. Flight http://flightphp.com/

    Silex

    Silex http://silex.sensiolabs.org/는 앞서 살펴본 Slim과 상당히 유사하며 Symfony2 컴포넌트를 기반으로 한 특징을 가지고 있다.

    require_once __DIR__.'/../vendor/autoload.php'; 
    
    $app = new Silex\Application(); 
    
    $app->get('/hello/{name}', function($name) use($app) { 
        return 'Hello '.$app->escape($name); 
    }); 
    
    $app->run(); 
    

    Bullet

    PHP 5.3+을 요구, 5.4 이상을 추천하는 framework이다. Composer로 패키지 관리가 가능하다.

    $app = new Bullet\App();
    $app->path('/', function($request) {
        return "Hello World!";
    });
    
    echo $app->run(new Bullet\Request());
    

    확장성을 고려해 구현해둔 부분이 많이 보인다. Bullet 문서 참고.

    GluePHP

    그냥 받아서 압축을 해제하면 바로 사용할 수 있는 micro framework이다. 다른 것들에 비해 가장 생각없이(?) 쉽게 사용할 수 있다.

    <?php
        require_once('glue.php');
        $urls = array(
            '/' => 'index'
        );
        class index {
            function GET() {
                echo "Hello, World!";
            }
        }
        glue::stick($urls);
    ?>
    

    url에 대해 정규표현식으로 지정할 수 있는 특징이 있다. 각각의 메소드를 바인딩 하는 것이 아니라 클래스를 바인딩하며 각 클래스마다 GET(), POST() 등의 메소드를 호출하는 형태다. 3

    Footnotes

    1. 사실 대다수의 micro framework는 5.3 이상을 요구한다. 그래도 예전에 비해 php 최근 버전을 지원하는 호스팅이 많이 늘었다.

    2. 익명함수 내부에서 외부의 변수를 이용하기 위해 사용하는 메소드다.

    3. 클래스의 단위를 잘 고려하지 않으면 쉽게 지저분해질 것 같다.

    색상을 바꿔요

    눈에 편한 색상을 골라보세요 :)

    Darkreader 플러그인으로 선택한 색상이 제대로 표시되지 않을 수 있습니다.