tag: php

레거시 PHP에서 모던 PHP로 – 뷰 분리하기

먼 길을 가도 그 시작은 첫 발을 내딛는 일에서, 구렁이 담 넘어가듯 모던 PHP 넘어가기 시리즈.

2016년 10월 12일

흔히 모던 PHP라고 말하는 현대적인 PHP 개발 방식에 대해 많은 이야기가 있다. 새 방식을 사용하면 협의된 명세를 통해 코드 재사용성을 높이고 패키지를 통해 코드 간 의존성을 낮출 수 있는 등 다른 프로그래밍 언어에서 사용 가능했던 좋은 기능을 PHP에서도 활용할 수 있게 된 것이다. 이 방식은 과거 PHP 환경에 비해 확실히 개선되었다. 하지만 아무리 좋은 개발 방식이라 해도 현장에서 쉽게 도입하기 어렵다. 코드 기반이 너무 커서 일괄 전환이 어렵거나, 이전 환경에 종속적인 경우(mysql_* 함수를 여전히 사용), 새로운 개발 방식을 적용하기 위한 재교육 비용이 너무 크고, 신규 프로젝트와 구 프로젝트가 공존하는 동안 전환 비용이 발생할 수 있다는 점이 걸림돌이 된다. 이런 이유로 사내 정책 상 예전 환경을 계속 사용하기로 결정할 수도 있고, 개개인의 선택에 따라 계속 이전 버전을 사용하는 경우도 있을 것이다. 그 결과, 좋은 개발 방식임을 이미 알고 있지만 마치 다른 나라 이야기처럼 느끼는 사람도 많다.

A: 팀장님 모던 PHP 도입합시다 +_+
B:

지금 다니고 있는 회사에서도 모든 코드를 일괄적으로 모던 PHP로 이전할 수 없었다. 가장 큰 문제는 코드란 혼자서만 작성하는 것이 아니며 다른 개발자와의 협업도 고려해야 하기 때문에 구성원 간의 협의가 필요하다. 그래서 일괄적인 도입보다는 점진적으로 코드는 개선해 가면서 새 개발 방식에 천천히 적응하는 방법은 없는지 고민하게 되었다. 작은 코드부터 시작해서 먼저 도입할 수 있는 부분부터 차근차근 도입하기 시작했다. 아직 회사에서 사용하는 대다수의 프레임워크와 CMS가 이전 방식을 기반으로 하고 있기 때문에 100% 모던 PHP를 사용하는 것은 아직 멀었지만, 팀 내에서 작은 크기의 코드인데도 새 방식의 장점과 필요성을 설득하기에 충분한 역할을 할 수 있었다.

레거시 PHP 코드에서 모던 PHP 코드로

이전 PHP 코드를 사용하면서도 현대적인 PHP를 도입하기 위해 고민하고 있다면 도움이 되지 않을까 하는 생각으로 이렇게 정리하게 되었다. 이 글에서 다루는 내용은 내가 소속된 회사에서도 현재 진행형이다. 즉, 이 글을 따라서 해야만 정답인 것은 아니다. 프로젝트의 수 만큼 다양한 경우의 수가 존재하기 때문에 하나의 방법만 고집할 수는 없다. 이 글이 그 정답을 찾기 위한 과정에서 도움이 되었으면 좋겠다.

먼 길을 가도 그 시작은 첫 발을 내딛는 일에서 시작한다. 이전의 코드 기반에서 여전히 작업하고 있고, 그 코드 양이 너무 많다 하더라도 점진적으로 코드를 개선할 수 있는 방법이 있다. 여기에서 다룰 내용은 모던 PHP를 도입하기 위해 회사에서 작업했던 작은 부분을 정리한 것이다. 코드를 개선하면서 전혀 지식이 없는 구성원도 자연스레 학습할 수 있도록 학습 곡선을 완만하게 만들기 위해 노력했던 부분도 함께 정리해보려고 한다. 가장 먼저 뷰를 분리하는 것에 대해 다뤄보려고 한다.


뷰 분리하기

코드를 분리해서 작성하는 과정은 중요하다. 각 코드가 서로에게 너무 의존적이거나 한 쪽이 다른 한 쪽을 너무 잘 아는 경우에는 코드 재사용도 어렵고 제대로 구동되는지 확인하기도 어렵다. PHP는 언어적인 특징 때문인지 몰라도 뷰 부분이 특히 심하게 붙어있는 모습을 자주 발견할 수 있다.

MVC 패턴을 사용하는 PHP 프레임워크 또는 플랫폼을 사용하는 프로젝트라면 별도로 뷰를 분리하는 노력 없이도 자연스럽게 로직과 뷰를 구분해서 작성할 수 있다. 하지만 여전히 많은 PHP 프로젝트는 echo로 HTML 문자열을 직접 출력하거나 include를 사용해서 별도의 파일을 불러오는 방식으로 개발되어 있다. 예를 먼저 확인해 보자.

직접 출력하는 방식은 대략 다음처럼 작성되어 있을 것이다.

<?php
// user_list.php
require_once('lib.php');
$config = get_config();
?>
<body>
    <?php
    $session = get_session();
    if (isset($session['user'])) { ?>
        <p><?php echo $session['user']['username'];?>님 환영합니다.</p>
    <?php } else { ?>
        <p>손님 환영합니다.</p>
    <?php } ?>
    <table>
        <thead>
            <tr>
                <th>#</th>
                <th>사용자명</th>
                <th>별명</th>
            </tr>
        </thead>
        <tbody>
        <?php
        $users = get_users();
        foreach($users as $user){?>
            <tr>
                <td><?php echo $user['id'];?></td>
                <td><?php echo $user['username'];?></td>
                <td><?php echo $user['nickname'];?></td>
            </tr>
        <?php }?>
        </tbody>
    </table>
</body>

이렇게 데이터를 가져오는 부분과 페이지를 출력하는 부분이 뒤범벅되기 쉽다. 이 방식보다는 조금 더 개선된 형태로 include를 사용해서 외부 파일을 불러오는 경우도 있다.

<?php
// user_list.php
require_once('lib.php');

$config = get_config();
$session = get_session();
$users = get_users();

include('themes/' . $config['theme_name'] . '/user/list.php');
?>
<!--themes/default/user/list.php-->
<body>
    <?php if (isset($session['user'])) { ?>
        <p><?php echo $session['user']['username'];?>님 환영합니다.</p>
    <?php } else { ?>
        <p>손님 환영합니다.</p>
    <?php } ?>
    <table>
        <thead>
            <tr>
                <th>#</th>
                <th>사용자명</th>
                <th>별명</th>
            </tr>
        </thead>
        <tbody>
        <?php foreach($users as $user){?>
            <tr>
                <td><?php echo $user['id'];?></td>
                <td><?php echo $user['username'];?></td>
                <td><?php echo $user['nickname'];?></td>
            </tr>
        <?php }?>
        </tbody>
    </table>
</body>

이 코드를 보면 앞서 방식보다는 뷰가 분리된 것처럼 보인다. 하지만 이 코드는 뷰를 분리했다기 보다는 두개의 PHP 파일로 나눠서 작성하는 방식에 가깝다.

여기서 살펴본 두 경우는 이전에 작성된 코드라면 쉽게 찾을 수 있는 방식이다. 빠르고 간편하게 작성할 수 있을지 몰라도 재사용성이 높지 않고 관리가 쉽지 않다. 그나마 두 번째 방식은 분리되어 있지만 그렇다고 편리하다고는 할 수 없는 구석이 많다. 왜 이렇게 작성된 코드는 불편한 것일까?

전자의 경우는 외부의 함수를 그대로 사용하고 있어서 뷰의 의존도가 높다. 함수의 반환 값이나 사용 방식이 달라지면 뷰에서 해당 함수를 사용한 모든 위치를 찾아서 변경해야 할 것이다. 그리고 이렇게 생성된 html을 다른 곳에서 다시 사용하기는 쉽지 않다. 이 파일을 다른 파일에서 불러오게 되면 파일 내에 포함되어 있는 모든 기능을 호출하게 된다. 이 파일을 다시 사용하더라도 작성했을 당시의 의도를 바꾸기 어렵다.

그래도 후자의 경우는 뷰가 분리되어 있어서 뷰를 다시 사용하는 것은 가능하게 느껴진다. 하지만 뷰에서 전역 변수에 접근하는 방식으로 데이터에 접근하고 있다. 이런 상황에서는 뷰에서 어떤 변수를 사용하고 있는지 뷰 코드를 들여다보기 전까지는 알기 어렵다. 이런 방식으로 뷰를 재사용하게 되면 해당 파일을 include 하기 전에 어떤 변수를 php 내부에서 사용하고 있는지 살펴본 후, 모두 전역 변수로 선언한 다음 include를 수행해야 한다.

결과를 예측할 수 없는 코드

PHP에서는 결과를 출력하는데 수고가 전혀 필요 없다. 위 코드에서 보는 것처럼 <?php ?>로 처리되지 않은 부분과 echo를 사용해서 출력한 부분은 바로 화면에 노출되기 때문이다. 이 특징은 짧은 코드를 작성할 때 큰 고민 없이 빠르게 작성할 수 있도록 하지만 조금이라도 규모가 커지기 시작하면 관리를 어렵게 만든다.

앞에서 확인한 예제처럼 작성한 PHP 코드가 점점 관리하기 어렵게 변하는 이유는 바로 출력되는 결과를 예측하는 것이 불가능하다는 점 에서 기인한다. (물론 e2e 테스트를 수행할 수 있지만 이런 코드를 작성하는 곳에서 e2e 테스트를 사용한다면 특수한 경우다.) 두 파일에서 출력하는 내용은 변수로 받은 후 출력 여부를 결정하는 흐름이 존재하지 않는다. 전자는 데이터를 직접 가져와서 바로 출력하고 있고 후자는 가져올 데이터를 전역 변수를 통해 접근하고 있다. 개발자의 의도에 따라서 통제되는 방식이라고 하기 어렵다. 오히려 물감을 가져와서 종이 위에 어떻게 뿌려지는지 쏟아놓고 보는 방식에 가깝다. 이전 코드를 사용하는 PHP는 대부분 일단 코드를 쏟은 후에 눈과 손으로 직접 확인하는 경우가 많다. 이는 코드가 적을 경우에 문제 없지만 커지면 그만큼 번거로운 일이 된다. 결국에는 통제가 안되는 코드를 수정하는 일은 꺼림칙하고 두려운 작업이 되고 만다.

페이지를 열기 전까지 알 수 없는 결과물

함수에 대해 생각해보자. 프로그래밍을 하게 되면 필연적으로 함수를 작성하게 된다. 함수는 인자로 값을 입력하고, 가공한 후에 결과를 반환한다. 대부분의 함수는 특수한 용도가 아닌 이상에는 같은 값을 넣었을 때 항상 같은 결과를 반환한다. 수학에서는 함수에 인자로 전달할 수 있는 값의 집합을 정의역으로, 결과로 받을 수 있는 값의 집합을 치역으로 정의한다. 프로그래밍에서의 함수도 동일하게 입력으로 사용할 수 있는 집합과 그 입력으로 출력되는 결과 값 집합이 존재한다. 즉, 입력의 범위를 명확히 알면 출력되는 결과물도 명확하게 알 수 있다는 뜻이다.

f

수학에서의 함수

뷰를 입력과 출력이 존재하는 함수라는 관점에서 생각해보자. 위에서 작성했던 코드를 다시 보면 $session$users를 입력받고 html로 변환한 값을 반환하는 함수로 볼 수 있다. 함수 형태로 뷰를 사용한다면 뷰에서 사용할 변수를 인자로 사용할 수 있어서 입력을 명확하게 통제할 수 있다. 앞서 본 함수의 특징처럼 이 뷰 함수도 입력에 따라 그 결과인 html을 예측할 수 있게 된다. 다시 말해, 그동안 사용한 뷰를 함수처럼 바꾼다면 입력과 출력의 범위를 명확하게 파악할 수 있게 되는 것이다.

fphp

php의 뷰 함수

뷰 함수로 전환하기

간단하게 뷰를 불러오는 함수를 구현해보자. 파일을 불러오더라도 출력하는 결과를 예측할 수 있도록 만들 수 있다. 다음처럼 include로 불러온 내용을 결과로 반환하도록 작성한다.

<?php
function view($template) {
    if (is_file($template)) {
        ob_start();
        include $template;
        return ob_get_clean();
    }
    return new Exception("Template not found");
}
?>

출력 버퍼를 제어하는 함수 ob_start(), ob_get_clean()을 사용해서 불러온 결과를 반환했다. 이 함수를 사용해서 외부 파일을 불러와도 바로 출력되지 않고 변수로 받은 후 출력할 수 있게 되었다.

<?php // templates/helloworld.php ?>
<p>Hello World!</p>

helloworld.php 템플릿을 사용하려고 한다. 다음은 php에 내장되어 있는 assert() 함수를 사용한 간단한 테스트 코드다.

<?php // helloworld.test.php

$response = view('templates/helloworld.php');
$expected = '<p>Hello World!</p>';
assert($response === $expected, 'Load a template using view');

위 테스트 코드는 php helloworld.test.php 명령으로 구동할 수 있다. $response$expected를 비교해서 값이 동일하지 않다면 2번째 인자와 함께 오류를 출력한다.

이 글에서는 별다른 설치없이 테스트를 실행해볼 수 있도록 내장된 assert() 함수만 사용할 것이다. 실제로는 이 함수만 사용해서는 제대로 된 테스트를 구성하기 힘들기 때문에 phpunit과 같은 더 나은 테스트 도구를 사용하기를 권장한다.

이제 불러온 파일이 어떤 값을 갖고 있는지 측정할 수 있게 되었다. 뷰는 최종적으로 echo 또는 print로 출력하게 된다.

<?php // helloworld.php
require_once('lib.php');

echo view('templates/helloworld.php');

이렇게 불러온 php 파일은 함수 내에서 불러왔기 때문에 함수 외부에 있는 전역 변수에 접근할 수 없다. 함수 스코프에 의해 전역 변수로부터 통제된 환경이 만들어진 것이다. 덕분에 외부의 영향을 받지 않는 방식으로 php 파일을 불러올 수 있게 되었다.

이제 뷰 파일 내에서 사용하려는 변수를 뷰 함수의 인자로 넘겨주려고 한다. 넘어온 변수를 해당 php 파일을 불러오는 환경에서 사용할 수 있도록 다음처럼 함수를 수정한다.

<?php
function view($template, $data = []) {
    if (is_file($template)) {
        ob_start();
        extract($data);
        include $template;
        return ob_get_clean();
    }
    return new Exception("Template not found");
}
?>

$data로 외부 값을 배열로 받은 후에 extract() 함수를 사용해서 내부 변수로 전환했다. 새로 작성한 함수를 사용한 예시다.

<?php // templates/dashboard.php ?>
<?php if ( isset($user) ): ?>
<div>Welcome, <?php echo $user['nickname'];?>!</div>
<?php else: ?>
<div>I don't know who you are. Welcome, Stranger!</div>
<?php endif; ?>

다음처럼 테스트를 작성할 수 있다.

<?php // dashboard.test.php

$response_not_logged_in = view('templates/dashboard.php');
$expected_not_logged_in = "<div>I don't know who you are. Welcome, Stranger!</div>";

assert($response_not_logged_in === $expected_not_logged_in,
    'Load dashboard without user');

$user = [
    'nickname' => 'Haruair',
];

$response_logged_in = view('templates/dashboard.php', [ 'user' => $user ]);
$expected_logged_in = '<div>Welcome, Haruair!</div>';

assert($response_logged_in === $expected_logged_in,
    'Load dashboard with user');

테스트 코드에서 뷰로 출력할 결과를 명확하게 확인할 수 있다는 점을 볼 수 있다. 이 함수를 실제로 사용한다면 다음과 같을 것이다.

<?php // dashboard.php
require_once('lib.php');

$session = get_session();

if ( $session->is_logged_in() ) {
    $user = get_user($session->get_current_user_id());
} else {
    $user = null;
}

echo view('templates/dashboard.php', [ 'user' => $user ]);

여기서 사용한 뷰 함수는 간단한 예시로, 뷰를 불러오는 환경을 보여주기 위한 예제에 불과하다. 실제로 사용하게 될 때는 레이아웃 내 다양한 계층과 구성 요소를 처리해야 하는 경우가 많다. 이럴 때는 이 함수로는 부족하며 이런 함수 대신 다양한 환경을 고려해서 개발된 템플릿 패키지를 적용하는 게 바람직하다.

템플릿 패키지 사용하기

다양한 PHP 프레임워크는 각자 개성있고 많은 기능을 가진 템플릿 엔진을 사용하고 있다. LaravelBlade, SymfonyTwig을 채택하고 있다. 모두 좋은 템플릿 엔진이고, PHP와는 다르게 독자적인 문법을 채택해서 작성하는 파일이 뷰의 역할만 할 수 있도록 PHP 전체의 기능을 제한하고 최대한 뷰 역할을 수행하도록 적절한 문법을 제공한다. 이런 템플릿 엔진을 사용할 수 있는 환경이라면 편리하고 가독성 높은 템플릿 문법을 사용할 수 있다.

프레임워크를 사용하지 않는 환경이라면 이런 템플릿 엔진이 학습 곡선을 더한다는 인상을 받을 수 있으며 같이 유지보수 하는 사람에게 부담을 줄 수도 있다. 이런 경우에는 PHP를 템플릿으로 사용하는 템플릿 엔진을 선택할 수 있다. 사용하는 템플릿이 여전히 PHP 파일과 같은 문법을 사용하면서도 앞에서 작성해본 뷰 함수와 같이 통제된 환경을 제공할 수 있는 패키지가 있다. 여기서는 Plates를 사용해서 앞에서 작성한 코드를 수정해볼 것이다.

먼저 Platescomposer로 설치한다.

$ composer require league/plates

그리고 php 앞에서 autoload.php를 불러와 내려받은 plates를 사용할 수 있도록 한다.

<?php // dashboard.php
require_once('vendor/autoload.php');
require_once('lib.php');

// templates을 기본 경로와 함께 초기화
$templates = new League\Plates\Engine('templates');

$session = get_session();

if ( $session->is_logged_in() ) {
    $user = get_user($session->get_current_user_id());
} else {
    $user = null;
}

// 앞서 view 함수처럼 사용
echo $templates->render('dashboard', [ 'user' => $user ]);

Plates는 레이아웃, 중첩, 내부 함수 사용이나 템플릿 상속 등 편리한 기능을 제공한다. 자세한 내용은 Plates의 문서를 확인한다.

정리

지금까지 뷰를 분리하는 과정을 간단하게 살펴봤다. MVC 프레임워크처럼 구조가 잘 잡힌 코드를 사용하는 것이 가장 바람직하겠지만, 점진적으로 코드를 개선하려면 여기서 살펴본 방식대로 뷰를 먼저 분리하는 것부터 작게 시작할 수 있다.

뷰 함수 또는 템플릿 엔진을 사용해서 외부 환경의 영향을 받지 않는 독립적인 뷰를 만들 수 있다. 뷰가 결과로 반환되기 때문에 출력 범위를 예측하고 테스트 할 수 있게 된다. 또한 뷰에 집어넣는 값을 통제할 수 있다. 전역 변수 접근을 차단하는 것으로 외부 요인의 영향을 최대한 줄일 수 있다.

그리고 새로운 내용을 배우거나 도입하는데 거리낌이 있는 경우라도 타 템플릿 엔진과 달리 Plates 같은 템플릿 엔진은 PHP 로직을 그대로 사용할 수 있기 때문에 상대적으로 자연스럽게 도입할 수 있다.

이상적인 상황을 가정해보면 이 패키지를 composer를 사용해서 설치하는 것으로 새로운 개발 흐름에 조금씩 관심을 갖게 만들 수 있고 새로운 패키지를 도입하는 것에 대해 좋은 인상을 남길 수 있다. 또한 추후에 MVC 프레임워크를 도입해도 뷰를 분리해서 작성하는 방식에 자연스럽게 녹아들 수 있을 것이다.


글을 리뷰해주신 chiyodad님, minieetea님 감사드립니다.

떠나세요, PHP 개발자여. 아니면 잘하든가!

코드 없는 PHP 이야기. PHP 개발자만 보세요.

2016년 2월 15일

PHP 개발자는 그 태생부터 죄에 속한 것과 같이 업을 쌓고 산다. 아무리 좋은 디자인과 아키텍처, 방법론으로 무장하고 있더라도 그 죄성은 쉽게 씻겨지지 않는다. 어디서든 PHP 개발자라는 얘길 하면 PHP: 잘못된 디자인의 프랙탈 링크를 받게 되고 공개 처형이 이뤄진다. 모던 PHP로 개발하면 된다지만 이전 PHP에 비해 그나마 모던한 것이지 다른 언어와 비교했을 때는 이제 시작한 수준에 불과하다. 개발과 아예 관련이 없는 모임이나 PHP 개발자 모임 외에는 PHP는 쉽고 편한 언어다, 같은 발언은 물론 대화에 PHP를 올리는 것 자체가 금기다. 언급 되더라도 마치 인종차별적 농담과 같이 지저분한 곳에만 사용된다.

어디 가서 PHP 얘기 꺼냈을 때

PHP를 새로 배우려고 하는 사람, 또는 2년 이하의 경력을 가진 사람은 이런 정신적 고통에 시달리지 말고 해방되길 바란다. 평생의 짐으로 껴앉고 살 필요 없이 더 멋진 언어를 선택하고 이 고통에서 벗어나자. 아래 내용도 더 읽을 필요가 없다.

하지만 3년 이상의 시간을 PHP와 함께 했다면 아무리 PHP가 최악이더라도 쉽게 벗어날 수 없다. 커리어를 이쪽으로 계속 쌓아온 사람이라면 마치 기차가 탈선하는 것과 같은 공포감을 느낄 수 밖에 없다. 그래도 갈아타는 것이 좋다. 3년은 크게 느껴지지만 100세 수명이라면 겨우 3%만 할애한 것이다. 물론 커리어 전환에서의 공포는 경력이다. 앞서 적은 것처럼 어디서도 PHP가 대접받지 못하기 때문에 그 전환에서 챙겨갈 수 있는 경력이 대체로 적다. (대부분의 경우, 신입 취급이다.) 경력을 인정 받지 못하면 자연스레 연봉이나 제반 사항이 발목을 잡는다. 그래서 떠나는 결정은 쉬운 일이 아니다. 내 경우는 호주에서 빨리 정착하기 위해 기존 경력을 살려야 했기에 여전히 PHP 개발자로 남아 있다. 새로운 언어를 배워 새 출발 하는 일은 쉽지 않지만 분명 가치 있는 일이고 나에게 있어서는 이후 과제 중 하나다.

반대로 다른 언어를 바꾸는 이득이 크지 않아서 계속 PHP를 사용할 것이라는 분들은 계속 이쪽 길을 가는 데 고민이 없다. 이득이 작다고 생각하는 사람이라면 PHP를 3년 이상 사용하면서 큰 문제를 느끼지 못해본 사람일 경우가 크다. 물론 언어에서 문제를 느끼지 못했다면 그냥 계속 사용하면 된다. 대체로 이런 케이스는 평생 쓴다. 가장 큰 문제는 이런 분들 중에 학습에 무딘 경우가 많아 잘못되고 오래된 지식을 경험이라는 이름으로 덮어서 오용하는 분이 꽤 있다. 이런 분들이 주로 코드의 정당성을 부여하기 위해서 페이스북이 PHP를 쓴다, 워드프레스가 점유율이 가장 높다는 등의 이야기를 끌어다가 쓴다.

페이스북이 PHP 쓴다고 말할 때


모르는 걸 아는 것은 좋은 일이지만 자신이 무엇을 아는지 알지 못하는 것은 병이다.1 PHP에서 문제를 한번도 느껴보지 못한 사람이라면 어떤 언어든 다른 프로그래밍 언어를 학습하자. 프로그래밍 언어는 다양한 문제를 위한 다양한 해법과도 같다. 각종 php 포럼에서 시시덕거리며 유물과 같은 코드 스니핏 공유하지 말고, 말도 안되는 코드를 블로그에 공유하지 말자. 사람보다 코드가 오래 간다. 그리고 다른 언어나 프레임워크를 비하하는 일은 제발 하지 말자. 본전도 못 찾을 뿐더러 정신승리만 남을 뿐이다. 그리고 제발 공부하자. 내가 대충 짠 코드가 다른 사람을 죽일 수 있다. PHP 코드가 레거시이기 이전에 개발하는 사람이 레거시면 어떡하나.

만약 앞에서 이야기한 모든 고통과 괴로움을 덮고서 PHP 개발을 계속 하려고 한다면 그나마 할 수 있는 조언이 몇 가지 있다. PSR 기반의 코딩 가이드, 네임스페이스 사용 등 모던 PHP라고 불리는 것들을 빠르게 도입하는 것이 그중 하나다. 기초는 PHP The Right Way 한국어판부터 시작하자. 패키지를 작성하는 방법이나 패키지 작성 체크리스트를 보고 모르는 부분이 있다면 심화 학습하자. 앞서 간략하게 설명한 글인 당신이 PHP 개발자라면 2016년 놓치지 말고 해야 할 것들을 봐도 된다. 실무에 빠르게 적용하고 싶다면 Laravel 튜토리얼을 살펴보자. PHP Storm과 같은 IDE를 사용하거나 에디터에서 제공되는 PHP를 위한 플러그인을 찾아 설치하는 것도 잊지 말자. 커뮤니티도 중요하다. 모던 PHP 사용자 모임에 가입해서 살펴보자.

PHP 글 더 읽기

  • 노자 도덕경 71장 지부지상 중 
  • 구석기 PHP와 현대적인 PHP 비교하기

    PHP 개발자라면 알아야 할 현대적인 PHP 교양, 왕릉에서 유물로 출토된 PHP 코드와 비교해서 PSR 살펴보기

    2015년 12월 15일

    PHP는 언어적인 지원은 물론, 환경이나 커뮤니티도 계속 발전하고 있다. 최근 프레임워크 운용 그룹(Framework Interop Group, FIG)에서 제안하는 PSR 문서를 보면 알 수 있듯, 표준화된 라이브러리를 만들기 위해 라이브러리/패키지 개발에 대한 합의도 활발하게 진행되고 있어서 예전 그 난장판이던 분위기와는 사뭇 다르다. PSR에서 다뤄지는 내용은 미래에 사용할 기능이 아니라 지금 현재 PHP에서 당장 활용할 수 있는 기술이다. 더이상 미룰 수 없고, 미뤄서도 안된다는 이야기다.

    구석기 PHP는 농담 짙은 표현이지만 이젠 마치 과거의 유물과도 같은 코드에 그만 집착하고 현대에 맞는 코드를 작성했으면 한다. 이 글은 예전 방식으로 PHP를 개발하고 있다면 자주 접했을 만 한 문제를 정리하고 있다. 이 글에서 PSR의 내용을 직접적으로 다루지는 않지만, PSR를 준수하는 것으로 여기서 말하는 현대적인 개발과 과거의 PHP 개발은 어떤 부분이 다르고, 어떻게 편리한지 확인해보자.

    구석기 PHP

    함수, 변수, 클래스의 전역적인 공해

    파일을 불러오고 나면 각각의 파일이 갖고 있던 경계(스코프)가 전역으로 확장되고 어디서나 사용할 수 있는 함수, 변수, 클래스가 만들어지게 된다.

    <?php 
    // lib/haruair/function.php
    function HelloWorld() {
      return "HelloWorld";
    }
    ?>
    <?php 
    // lib/wordpress/function.php
    function HelloWorld() {
      return "HelloWorld. I'm wordpress btw.";
    }
    ?>
    <?php
    // app.php
    include_once('./lib/haruair/helloWorld.php');
    include_once('./lib/wordpress/helloWorld.php');
    
    // PHP Fatal error:  Cannot redeclare HelloWorld()
    ?>

    같은 함수명이나 클래스명을 사용하면 다시 선언할 수 없는 문제가 발생한다. 특히 워드프레스 개발을 하다보면 플러그인 내에 동일한 함수나 클래스명을 사용하고 있어 이런 문제가 발생하는 경우가 자주 있다. 대부분 function_exists()와 같은 함수를 이용해 미리 확인하고 선언하는 방식으로 처리되어 있는데 여전히 좋은 방법은 아니다.

    그래도 함수나 클래스의 이름이 중복되는 경우에는 문제가 있는걸 바로 자각할 수 있지만, 동일한 명칭의 전역 변수가 있고 각각의 파일에서 그 변수를 활용하고 있다면 그 누구도 결과를 예상할 수 없게 된다. 이런 경우는 어느 하나의 이름을 모두 변경해야 하거나 언제 발생할지 모르는 에러를 감내해야 한다.

    기본으로 모든 파일 로드하기

    이전 방식의 개발에서는 다음과 같은 lib.php를 만들어 의존성을 갖는 모든 파일을 불러와 활용하는 경향이 있다. 이 파일 하나를 불러오면 각각 파일의 함수, 변수, 클래스를 모두 불러와서 사용할 수 있게 된다.

    <?php
    // lib.php
    include_once('./lib/A/Orders.php');
    include_once('./lib/B/Account.php');
    include_once('./lib/B/AccountManagement.php');
    include_once('./lib/B/AccountSomething.php');
    include_once('./lib/C/Report.php');
    include_once('./lib/E/Admin.php');
    ?>

    이런 개발 방식은 오랫동안 큰 대안 없이 활용되고 있고, 지금까지도 많은 코드에서 발견되는 방식이다. 각각 필요에 따라 include하는 경우도 있지만, 각각의 파일끼리도 의존성이 있는 경우도 많기 때문에 하나의 파일에서 모두 불러오는 형태로 많이 사용한다. 다음 코드를 살펴보자.

    <?php
    // some-page.php
    include_once( BASE_DIR . '/lib.php');
    
    function HelloWorld() {
      return speak("Hello World");
    }
    
    // `speak()` 함수가 어느 php 파일에서 나온지 알 수 없다.
    ?>

    IDE를 활용하면 쉽게 speak()가 선언된 부분을 찾을 수 있겠지만 코드만 봐서는 이 speak() 함수가 어디에서 나온 함수인지 알 수 없다. 이런 문제로 인해 대다수의 프레임워크나 CMS에서는 함수명에 접두사를 붙여 사용하는 등의 방식으로 해결했지만 함수를 호출할 때마다 접두사를 붙여 호출하는 일은 누가봐도 지저분한 일이다.

    특히 이런 방식으로 개발된 코드는 함수와 실제 로직과의 결합도가 높아서 코드를 재활용하기도 어렵다. 그 결합도를 낮추기 위한 시도로 변수에 함수명을 넣고 실행하는 방법도 활용되지만 여전히 코드가 장황하고 지저분해지는 경향이 크다.

    코딩 스타일의 차이

    함께 개발하는 개발자가 모두 동일한 코딩 스타일을 준수하는 것은 중요하다. 때로는 공개된 라이브러리를 활용하게 되는 경우도 있는데 이런 라이브러리가 동일한 컨벤션을 준수하고 있지 않는다면 자연스럽게 불편함을 겪게 된다.

    <?php
    include_once('./some_pdf_gen/lib.php');
    include_once('./someCalculatorLibrary/content/library/cal.php');
    
    include_once('./my/lib.php');
    
    $orders = array(
      new My_Product(112, 2.5, 2),
      new My_Product(2303, 30, 1),
      new My_Product(4923, 30, 2)
    );
    
    $pdf = new AcmeSomePdfGen();
    $calculation = new some_calculation($orders);
    $total = $calculation->get_total_price();
    
    $pdf->setTemplate("<div>Total: $total</div>");
    $pdf->download();
    ?>

    인위적으로 만든 예시지만 충분히 있을 법한 코드다. 일관성이 없는 코드는 개발자에게 고스란히 스트레스로 돌아온다.


    현대인의 PHP

    이제 앞서 본 코드와 어떻게 다른지 살펴볼 것이다. PHP 5.3 이후로 사용할 수 있게 된 namespace와 autoloader를 활용하는 것으로 지저분한 문제를 대부분 해결할 수 있다. 이 중요한 두 가지 기능을 사용하는데 있어 어떻게 사용하고 활용하는지 PSR 문서로 정리되어 있다. PSR을 모두 설명하고 있지 않지만 어떤 방식으로 문제를 해결하는지 확인하자.

    namespace 활용하기

    네임스페이스를 다음과 같이 선언하는 것으로 클래스를 네임스페이스 아래로 배정할 수 있게 된다.

    <?php // lib/haruair/helloWorld.php
    namespace Haruair;
    class HelloWorld {
      function say() {
        return "HelloWorld";
      }
    }
    ?>
    <?php // lib/wordpress/helloWorld.php
    namespace WordPress;
    class HelloWorld {
      function say() {
        return "HelloWorld. I'm wordpress btw.";
      }
    }
    ?>

    이러면 다음 코드와 같이 한 파일에서 사용하는데 전혀 문제가 없다.

    <?php // app.php
    include_once('./lib/haruair/helloWorld.php');
    include_once('./lib/wordpress/helloWorld.php');
    
    $haruair = new Haruair\HelloWorld();
    $wordpress = new WordPress\HelloWorld();
    
    echo $haruair->say(); // HelloWorld
    echo $wordpress->say(); // HelloWorld. I'm wordpress btw.
    ?>

    네임스페이스를 활용하면 Haruair\Order\ProductHaruair\Cart\Product가 동일한 Product라는 이름의 클래스라도 하나의 파일에서 두 클래스 모두 처리할 수 있게 된다.

    autoloader 활용하기

    php에서 미리 선언한 함수나 클래스를 사용하려면 당연하게 includerequire 같은 내장 함수를 활용했어야 했다. 하지만 spl_autoload_register 함수를 선언하면 파일을 필요로 할 때 불러오는 방식으로 구현할 수 있다. 다음 코드를 보자.

    <?php
    include_once('./src/haruair/event/ticket.php');
    include_once('./src/haruair/event/attendee.php');
    include_once('./src/haruair/event/coupon.php');
    
    $ticket = new Haruair\Event\Ticket;
    $attendee = new Haruair\Event\Attendee;
    $coupon = new Haruair\Event\Coupon;
    ?>

    이제 직접 include 하는 것이 아니라 autoloader를 활용해서 불러오도록 한다.

    <?php
    spl_autoload_register(function ($class) {
    
        // 프로젝트에 따른 네임스페이스 프리픽스
        $prefix = '';
    
        // 네임스페이스 프리픽스를 위한 기본 디렉토리 경로
        $base_dir = __DIR__ . '/src/';
    
        // 네임스페이스 프리픽스에 해당하는지 검사
        $len = strlen($prefix);
        if (strncmp($prefix, $class, $len) !== 0) {
            // 찾지 못했으므로 반환. 만약 다른 오토로드 함수가 등록되어 있다면 순차적으로 실행함.
            return;
        }
    
        // 네임스페이스를 제외한 상대 클래스명 찾기
        $relative_class = substr($class, $len);
    
        // 네임스페이스 프리픽스를 기본 디렉토리 경로로 치환, 네임스페이스 구분자를 디렉토리 구분자로
        // 전환하고 .php를 끝에 추가함
        $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
    
        // 파일이 존재하면 불러옴
        if (file_exists($file)) {
            require $file;
        }
    });
    
    $ticket = new Haruair\Event\Ticket;
    $attendee = new Haruair\Event\Attendee;
    $coupon = new Haruair\Event\Coupon;
    // autoloader를 호출한다.
    ?>

    $ticketnew Haruair\Event\Ticket이 할당될 때, “Haruair\Event\Ticket” 문자열을 인자로 받는 spl_autoload_register 함수가 실행이 된다. 그래서 함수 내 정의된 방식대로 해당 문자열을 처리해 파일을 불러오게 된다. 이 예제 함수에서는 소문자로 전환한 후, 각각의 네임스페이스를 디렉토리 구조로 변환하고 끝에 .php를 붙여 해당 파일을 불러오는 식으로 작성되어 있다.

    여기서 사용된 함수는 약식 구현이고 모두가 공용으로 사용할 수 있도록 PSR에서 PSR-0, PSR-4로 표준화된 문서를 제공하고 있다. Composer를 사용한다면 더 간편하게 활용할 수 있다. PSR-4의 구현 예시도 참고하자.

    코딩 스타일 일치

    PSR-1 Basic Coding StandardPSR-2 Coding Style Guide를 통해 표준적인 문법을 문서화하고 있다. 이 두 문서를 따라 개발하면 자연스럽게 앞서 다룬 네임스페이스와 autoload를 활용할 수 있게 된다. 특히 composer로 내려받을 수 있는 패키지는 이 두 문서를 준수할 것을 권장하고 있으므로 새로운 코드나 라이브러리, 패키지를 추가하더라도 일관적인 코딩 스타일을 유지하는데 도움이 될 것이다.


    얕은 수준에서 비교한 글이지만 여기서 다룬 기능은 지금 당장에라도 활용할 수 있는 방법이다. 이 기법을 활용하는 것은 단순히 몇가지 기술을 배우는 것에 그치지 않고 더 나은 개발 기법을 학습하는데 도움이 된다. 아직 PSR이나 composer와 같은 도구가 생소하다면 이 글을 읽고서 꼭 살펴봤으면 좋겠다.

    더 읽을 거리

    당신이 PHP 개발자라면 2016년 놓치지 말고 해야 할 것들

    PHP 개발하면서 이런 내용의 글을 처음 본다면, 2016년엔 당신의 코드를 박물관으로 보내고 처음부터 새로 시작해야 합니다.

    2015년 12월 14일

    여전히 PHP가 천덕꾸러기라고 생각하는 사람도 많다. 하지만 다른 언어에서만 볼 수 있었던 좋은 도구와 라이브러리, 의존성 관리도 지원하기 시작했고, PSR을 기준으로 표준도 활발하게 논의되고 있어 예전의 PHP 개발과는 확실히 분위기가 다르다. 한국 내 커뮤니티에서 laravel, symfony와 같은 프레임워크를 쓰는 경우나 XE와 같이 이런 프레임워크를 기반으로 개발한 웹어플리케이션이 보이기 시작했지만 여전히 대부분 “Classic” PHP로 개발하고 있는 것은 분명 아쉬운 부분이다. 좋은 도구가 있는데도 아무도 활용하지 않는다면, 그럴수도 있지 하고 지나치기엔 너무나도 슬픈 일이다. 좋은 기능을 도입하지 않는 사람들이 바로 PHP를 천덕꾸러기로 방치하고 있는 사람들이다.

    Classic PHP의 모습

    PHP를 사용해 예전 방식으로만 개발하는데 수많은 이유가 있을 수 있다. 물론 여기서 얘기하려는 편리한 새 기능이 내년, 혹은 그 후에 추가될 기능이라면 관심을 뒤로 미뤄도 할 말이 없다. 하지만 autoloadnamespace 문법, composer를 사용하는 것 등은 지금 당장 사용할 수 있는 것이기 때문에 더이상 미룰 수 없고 또한 미뤄서는 안된다. 최신 기술이 아니라 이미 널리 사용되고 있고, 이제는 사용하지 않으면 안되는 기술이다. 지금 배워서 지금 사용해야 한다. 만약 지금 안쓰고 있다면 당신만 안쓰고 있는 것이다. 회사에서 사용하지 않고 있다면 먼저 배워서 알려줘라. 그만큼 중요하다.

    그래서 PHP 개발자라면 2016년에는 놓치지 말고 해야 할 것들을 정리하려고 한다. 여기서 다루는 PHP 이야기는 먼 미래의 꿈이 아니라 현재 사용 가능하며, 또 해야만 하는 것들에 대한 이야기다. PHP 개발을 하고 있는데도 이 내용 중 하나라도 놓치고 있는게 있다면 꼭 알아보고 2016년엔 꼭 써먹어야 한다. 글 내내 중요하다는 이야기를 반복해서 하는 것은 정말 중요하기 때문이다. 그리고 이 글에서는 깊은 이야기를 다루진 않고 피상적인 부분만을 정말 간단하게 이야기하려고 노력했다. 이 포스트에 걸려있는 링크와 키워드로 더 깊은 내용을 찾아봤으면 좋겠다.

    PHP 업그레이드 하기

    PHP는 6 버전을 건너뛰고 7.0을 출시해서 현재 7.0이 최신 버전이다. PHP의 버전은 지속적으로 지원 패치를 제공하는 버전이 있고 보안 문제에 대해서만 패치를 제공하는 버전이 있다. PHP 버전 지원 페이지에서 지원 상황을 확인할 수 있다.

    지금 사용하고 있는 PHP의 버전은 몇 버전인지 확인하자. 5.4를 사용하고 있다면 2015년 10월 이후로 보안 패치도 제공되지 않는, 유통기한 지난 버전을 사용하고 있는 것이다. 유통기한 지난 우유를 계속 마실 것인가? 만약 지금 사용하는 버전이 5.3이라면 이미 당신의 웹사이트는 그 누구도 안전하다고 말할 수 없다. 5.5 버전도 앞으로 6개월 후, 즉 2016년 7월이면 보안패치를 제공하지 않는다. 지금 적어도 7.0, 최소한 5.5로 변경해야 한다. 만약 레거시로 인해 업데이트 이후 문제가 발생한다고 방치하고 있다면, 사실 그 사이트는 이미 위험한 웹사이트다. 언제, 어느 순간에 DDoS 공격에 활용될 지 아무도 모른다. 악성코드 배포처로 활용되거나, 최악의 경우 내부의 데이터를 볼모로 협박 메일을 받을지도 모른다.

    0순위가 되어야 할 보안 문제에도 의사결정권자가 마음을 움직이지 않고 오래된 버전을 고수한다면 속도가 더 빠르다는 점을 강조하자. 새버전의 PHP는 구버전에 비해 속도도 점점 빨라지고 메모리 사용량은 점점 줄어들고 있다. 5.6도 과거 버전에 비해 많이 빨라진 속도를 보여줬지만 7.0은 더 빨라졌다.

    버전을 올리기만 하면 더 좋은 기능을 쓸 수 있는 것은 물론, 속도가 빨라지고 보안성이 높아진다. 이 단순한 일을 하지 않는건 게으름 외에는 답이 없다. 레거시가 걱정이라면 changelog를 찾아보고, 최소한 테스트라도 해보자. 서버호스팅을 사용하고 있다면 상위 버전의 PHP를 설치하고 웹호스팅을 사용하고 있다면 호스팅 업체에 문의하자. 아직도 5.3만 지원하는 호스팅이라면 당장 옮겨야 당신의 웹사이트가 안전하다.

    Composer 사용하기

    Composer는 PHP를 위한 의존성 관리 도구다. Python에서 pip, nodeJS에서 npm, Ruby에서 bundle, .Net에서 Nuget을 사용해본 적이 있다면 바로 그 역할을 하는 도구다. 리눅스를 사용해본 경험이 있다면, 필요한 도구를 “어디선가” 내려받는 apt-get이나 yum 같은 명령어를 최소한 복사-붙여넣기로 사용해봤을 것이다.

    위로 든 예를 단 하나라도 써보지 않아 무슨 말을 하는지 모르겠다면, 내가 필요로 하는 기능의 PHP 라이브러리나 패키지를 스마트폰 앱스토어 같은 곳에서 다운로드 받는다고 생각해보자. 각각의 기능을 다운로드 받아 원하는 기능만 조합하는 방법으로 웹사이트, 웹서비스를 개발할 수 있다.

    PHP 웹사이트에서 이메일을 보낼 때 mail() 함수로 보내고 있다면, 매번 지저분한 header를 직접 작성하고, HTML을 직접 변수에 넣어 보내는 지저분한 일을 해본 경험이 있을 것이다. 거기에 첨부파일도 넣어 보내본 경험이 있다면 얼마나 쉽게 난장판이 되는지 알 수 있다. composer를 사용한다면 이런 문제를 깔끔하게 해결할 수 있는 멋진 PHP 패키지를 설치해서 활용할 수 있다. nette/mail로 메일을 쉽게 구성하고, league/plates와 같은 깔끔한 템플릿 엔진을 단 한 줄의 설치 명령어로 바로 사용할 수 있게 된다.

    Composer 로고

    composer를 사용하라고 하는 이유는 단순히 이 도구를 사용하는 과정을 배우는 것으로도 더 나은 개발을 시작할 수 있는 좋은 출발점이 되기 때문이다. composer를 제대로 사용하기 위해서는 기본적으로 namespaceautoload에 대해 이해해야 한다. 더 나아가 객체지향과 같은 개발 페러다임을 이해하는데 좋은 시작점이 되고 의존성을 어떻게 관리하는지, 테스트를 어떻게 수행해야 하는지 등 현대적인 개발에 있어 필수적인 부분을 학습하는데 중요하다. 최근 작성되는 PHP와 관련된 글을 보면 기본적으로 composer를 사용하는 것으로 가정하고 작성되기 때문에 PHP 개발자에게 있어서 필수적으로 배워야 할 도구다.

    PSR 준수하기

    PHP 난개발로 인해 가장 고통 받았던 사람들은 다름 아닌 PHP 프레임워크/라이브러리 개발자다. 범용적인 기능으로 만들어도 자신의 라이브러리에서만 사용할 수 밖에 없던 이유는 공통된 규칙이 없기 때문이었다. 모두 각자의 방식대로 만드는게 일상이었던 PHP 환경에서, 프레임워크나 라이브러리를 만들던 사람들이 모여 프레임워크 운용 그룹(Framework Interop Group, FIG)을 만들었고, PHP의 표준적인 개발을 위한 PSR 문서를 만들었다.

    PSR 문서는 PHP-FIG에서 확인할 수 있다. autoload, 인터페이스의 사용, 코딩 스타일 등 현재 수락된 문서와 진행중인 문서를 확인할 수 있다. 이 문서에서 제안하는 규칙을 따르는 것으로 같은 스타일의 코드를 유지하는데 도움이 된다. PHP 개발자를 채용할 때, “우리는 PSR을 준수해서 개발하고 있습니다.” 라는 한 마디로 어떤 스타일을 따르는지 설명할 수 있는 것이다.

    앞서 언급한 composer도 PSR을 준수해서 만든 도구다. PSR에서 제시하는 방식대로 코드를 작성한다면 composer에서 다른 개발자가 작성한 코드를 내려받아 사용하는 것과 같이 당신의 라이브러리도 누구나 쉽게 사용할 수 있다. 모두에게 공개된 packagist는 서버 코드 또한 공개되어 있어서 사내 전용 packagist를 구성해 사용할 수도 있다. 이 모든 일이 PSR을 준수하고 composer를 사용하는 것으로 가능하다.

    보너스: 현대적인 개발 패러다임 학습하기

    PSR과 composer가 일궈놓은 환경은 이전까지 활용하기 어려웠던 디자인 패턴이나 개발 패러다임을 PHP에서 사용하도록 하는데 큰 도움을 주고 있다. Factory, Strategy와 같은 디자인 패턴의 활용, 단위 테스트나 행위 주도 테스트를 통한 개발, 서비스 코드 간의 의존적인 환경을 줄이기 위한 의존성 주입이나 ORM과 같은 데이터베이스 추상화 등은 더이상 다른 멋진 언어에서만 존재하는 것이 아니라 PHP에서도 현재 가능한 이야기다.

    지금까지 대부분의 프레임워크는 자신들의 코드에 맞게 작성한, 그 프레임워크를 사용하지 않고서는 사용할 수 없는 코드만 제공해왔다면, 현대적인 PHP 개발에서는 누구든 쉽게 필요에 따라 꺼내서 쓸 수 있는 수많은 레고 블럭을 제공한다고 생각하면 된다. 개발 패러다임을 학습하는 것으로 이 수많은 패키지를 더 쉽게, 다시 활용할 수 있는 코드로 만드는데 도움이 된다. 다른 사람의 구현을 이해하는데도 도움이 되고 확장 가능하고 지속 가능한 코드를 작성하는데도 도움이 된다.

    코드에서 문제가 발생할 때마다 print_r()exit(), 그리고 새로고침 키로 디버깅을 한 경험이 있을 것이다. 지금도 그렇게 개발하고 있어도 이해할 수 있다. 이제는 문제가 나타났을 때, 에러를 발생하고, 예외 처리를 하고, monolog/monolog와 같은 패키지로 깔끔하게 로그를 남겨 확인하면 된다. 복잡하고 크고 어려운 문제를 한번에 해소하려고 하는 것은 쉽지 않은 일이기 때문에 이런 작은 변화부터 시작되어야 한다. 다른 언어에서는 흔하게 사용하는 패러다임은 이미 많은 개발자가 편하게 활용할 수 있도록 수많은 패키지로 만들어 제공되고 있다. 배우고, 살펴보고, 활용하자.


    이 글은 공상과학이 아니다. PHP 개발자라면서 여기서 다룬 이야기를 단 하나라도 이해하지 못했다면 정말로 반성하고 공부해야 한다. (취미로 하는 일이고, 집에 돈이 많다면 상관 안하겠지만.) 회사에서 PHP를 사용하는데 이런 이야기가 전혀 없었다면 사내 메일로 이 글을 뿌리고, 인트라넷에 공유하고, 출력해서 화장실 칸마다 붙이고, 당장에 스터디를 꾸려 배워야 한다. 이 글을 읽고 현대적인 PHP에 대해 공부하고 싶어졌다면 감사하게도 PHP The Right Way 한국어판이 있어 이 글에서 시작하는 것으로 충분하다. 좋은 커뮤니티도 학습에 있어 중요한 요소다. 모던 PHP 유저 모임에 가입해 공유되는 다양한 글을 읽어보고 세미나에도 참여해보자.

    이 글을 읽은 PHP 개발자라면, 2016년엔 꼭 복붙된 PHP 코드와 include로 범벅된 PHP 코드에서 벗어나고, 더 나은 추상화와 질서정연한 코드 속에 즐겁게 개발할 수 있기를 기도한다.


    더 읽을 거리

    PHP의 json_encode() 함수에서 JsonSerializable 활용하기

    PHP 5.4 이상에서 활용 가능한 JsonSerializable 인터페이스 소개

    2015년 5월 27일

    PHP에서 데이터를 json 문자열로 변환할 때 json_encode(mixed $value) 함수를 사용하게 된다. 이 함수를 이용해 개체를 변환할 때에도 활용할 수 있다. 기본적으로는 클래스에서 public인 프로퍼티에 대해서만 json으로 반환한다. protected나 private, 또는 데이터를 가공해 json으로 반환해야 한다면 해당 클래스에서 JsonSerializable 인터페이스를 구성해 어떤 형태로 변환할 것인지 정의할 수 있다. 이 인터페이스는 PHP 5.4.0 이상, PHP 7 에서 지원하고 있다.

    <?php
    class Student {
        public $first_name;
        public $last_name;
        protected $school;
    
        public function __construct($first_name, $last_name, $school) {
            $this->first_name = $first_name;
            $this->last_name = $last_name;
            $this->school = $school;
        }
    }
    
    $haruair = new Student("Edward", "Kim", "WeirdSchool");
    print $haruair;
    
    
    // result :
    // {
    //   "first_name": "Edward",
    //   "last_name": "Kim"
    // }
    ?>

    public 프로퍼티만 필요로 한 경우라면 별도의 인터페이스 구성 없이도 사용할 수 있다. 다만 대부분의 라이브러리에서 protected 또는 private으로 프로퍼티를 작성하고 __get(), __set() 매직 메소드를 구현해 사용하고 있고 또 권장하고 있기 때문에 그런 경우엔 다음과 같이 JsonSerializable 인터페이스를 활용할 수 있다.

    <?php
    class Student implements JsonSerializable {
        public $first_name;
        public $last_name;
        protected $school;
    
        public function __construct($first_name, $last_name, $school) {
            $this->first_name = $first_name;
            $this->last_name = $last_name;
            $this->school = $school;
        }
    
        public function jsonSerialize() {
            return [
                'full_name' => "{$this->first_name}, {$this->last_name}",
                'first_name' => $this->first_name,
                'last_name' => $this->last_name,
                'school' => $this->school,
            ];
        }
    }
    
    $haruair = new Student("Edward", "Kim", array("Jeju Univ", "Weird School"));
    print $haruair;
    
    
    // result :
    // {
    //   "full_name": "Edward, Kim",
    //   "first_name": "Edward",
    //   "last_name": "Kim",
    //   "school": [
    //       "Jeju Univ",
    //       "Weird School"
    //   ]
    // }
    ?>

    변환하는 과정에서 json 문자열을 추가하거나 데이터의 구조를 변환하는 등 다양한 형태로 활용할 수 있다.

    더 읽을 거리

    PHP 패키지 체크리스트

    현대 PHP 개발에 필수적인 14가지 항목. 번역 글.

    2015년 4월 17일

    PHP Package Checklist의 번역 글이다. 패키지 개발을 하지 않고 있더라도 PHP 개발을 하고 있다면 충분히 염두해볼 만한 내용이 포함되어 있고 참고할 이야기가 많다.

    패키지명을 현명하게 선택하기

    • 다른 프로젝트에서 사용되고 있지 않은 이름을 선택한다.
    • 패키지명과 PHP 네임스페이스가 일치하도록 관리한다.
    • 성이나 개인 닉네임을 PHP 네임스페이스로 사용하지 않는다.

    소스를 공개적으로 호스팅하기

    • 공개 프로젝트는 GitHub를 무료로 사용할 수 있다.
    • GitHub은 이슈를 관리하고 기능 요청이나 풀 리퀘스트에 도움이 된다.
    • 대안으로 Bitbucket도 사용할 수 있다.

    Autoloader 친화적으로 개발하기

    • PSR-4와 호환이 되는 네임스페이스를 사용한다.
    • 코드는 src 폴더 내에 넣는다.

    Composer를 통해 배포하기

    • PHP를 위한 의존성 관리 도구인 Composer에서 라이브러리를 사용 가능하게 한다.
    • Composer의 주 리포지터리인 Packagist에 등록한다.

    프레임워크에 대해 독립적으로 개발하기

    • 프로젝트를 하나의 프레임워크에 제한을 두지 않는다.
    • 서비스 프로바이더를 제공해 특정 프레임워크에서 사용할 수 있도록 지원한다.

    코딩 스타일을 따르기

    • PSR-2 코딩 스타일 가이드를 철저히 지킬 것을 강력하게 권장한다.
    • 빠르게 자동으로 코드를 수정해주는 PHP Coding Standards Fixer를 사용한다.
    • 코딩 표준에 대해 자동으로 확인해주는 PHP Code Sniffer를 사용한다.

    유닛 테스트를 작성하기

    • 주요 코드를 커버하는 것에 초점을 둔다.
    • PHPUnit은 사실상 표준인 PHP 유닛 테스트 프레임워크다.
    • 대안으로 phpspec, Behat, atoum, Codeception이 있다.

    DocBlock을 사용하기

    • 인라인 문서화를 위해 DocBlock을 제공한다.
    • DocBlock은 PhpStorm과 같은 IDE의 코드 완성을 향상하는데 도움이 된다.
    • phpDocumentor를 활용해 API 문서로 자동 변환이 가능하다.

    유의적 버전을 사용하기

    • 버전 번호를 관리하는데 유의적 버전을 사용한다.
    • 주버전.부버전.수버전(MAJOR.MINOR.PATCH) 시스템을 사용한다.
    • 개발 버전의 업그레이드는 변경으로 인해 깨지는 것을 걱정하지 않도록 안전하게 제공해야 한다.
    • 릴리즈마다 tag로 버전을 적는 것을 잊지 말자.

    변경 로그를 유지하기

    • 매 릴리즈를 할 때마다 변경 로그를 깔끔하게 공개한다.
    • Keep a CHANGELOG 양식을 사용하는 것을 고려한다.

    지속적인 통합(continuous integration)을 사용하기

    • 자동으로 코딩 표준과 테스트를 구동하는 서비스를 사용한다.
    • 다양한 버전의 PHP에서 테스트를 구동하는 좋은 방법이다.
    • 풀 리퀘스트가 제출될 때 자동으로 구동할 수 있다.
    • Travis-CI, scrutinizer, circleci를 사용할 수 있다.

    상세한 문서를 작성하기

    • 좋은 문서화는 성공적인 패키지에 필수적인 요소다.
    • 적어도 설명을 포함한 README를 작성해 리포지터리에 포함한다.
    • 문서를 GitHub Pages로 제공하는 것을 고려한다.
    • 대안으로 Read the Docs를 활용할 수 있다.

    라이센스를 포함하기

    • 라이센스를 포함하는 것은 현재까지 한 일을 보호할 수 있는 작은 방법이다.
    • choosealicense.com 사이트를 참고한다. 대부분의 PHP 프로젝트는 MIT 라이센스를 사용한다.
    • 적어도 LICENSE 파일은 라이브러리에 포함해야 한다.
    • Dockblock에도 라이센스를 포함할 것을 고려해본다.

    기여를 환영하기

    • 당신의 프로젝트를 누군가 돕길 원한다면 당연히 그걸 물어봐야 한다.
    • CONTRIBUTING 파일을 사용해 프로젝트 기여를 환영하자.
    • 이 파일에 테스트와 같은 프로젝트 요구 사항을 설명하는 내용을 작성한다.

    위 리스트의 모든 내용을 한번에 적용할 수 없다면 필요한 부분부터 점차적으로 적용해 나가도록 하자. Autoloader는 익숙해지면 include 지옥에서 벗어날 수 있는 강력한 기능이다. 위에서 소개된, 코딩 스타일을 자동으로 교정해주는 도구들은 문법 고민을 덜어주고 비지니스 로직에 집중할 수 있도록 돕는다. 현대적인 PHP 개발을 생각하고 있다면 위 모든 항목 하나하나 살펴보는 것이 도움이 된다.

    더 읽을 거리