컴포저는 PHP 생태계를 발전시킨 여러 제품중에서도 특히 중요한 발명품이라고 생각합니다. 

컴포저로 인해 다양한 PHP 라이브러리를 include나 require 의 지옥에 빠지지 않고도 쉽게 설치 및 사용할 수 있게 되었고 라이브러리 개발자는 자기가 만든 라이브러리로 인해 사용자가 의존성 지옥에 빠지지 않게 배포할 수 있게 되었습니다.


이 글은 PHP 개발자들이 기존의 PHP 라이브러리 사용자의 입장을 넘어서 공통 기능을 라이브러리로 작성한 후에 컴포저로 배포하기 위해 필요한 지식과 기법들에 대해서 설명합니다.


만약 현재의 PHP 라이브러리 제작 및 배포 방법(FTP, RSync 등) 이 무언가 어색하고 불편하다는 생각이 들고 개선 방향을 찾고 있다면 도움이 될 것이라고 생각합니다.


PHP 라이브러리를 만들고 컴포저로 배포하기 위해서는 사전에 다음과 같은 지식이 필요하며 중요도에 따라 별을 3개에서 1개로 분류했습니다(많을수록 중요) 


용어 정리

Repository

PHP 패키지를 보관하고 검색과 다운로드 기능을 제공하는 웹 서비스로 유명한 공용 저장소로는 packagist.org 가 있습니다.


Repository Manager

자체 저장소 서비스를 제공하기 위한 설치형 SW 로 Java 진영의 sonatype nexus나 artifactory 등이 유명하며 artifactory 는 composer 용 저장소 기능도 제공하고 있습니다.

(저는 sonatype nexus 를 사용하므로 artifactory를 사용하지 않아서 컴포저 저장소로 어느 정도 기능이 제공되는지는 모릅니다.)


현재 PHP composer 를 위한 사설 저장소를 만드려면 https://packagist.com 가 유일한 대안으로 보입니다.


의존성 지옥

의존성 지옥(dependency hell) 은 다양한 라이브러리간의 의존성이 충돌하여 발생하는 복잡한 문제로 언어와 프레임워크를 막론하고 발생하며 windows 의 DLL 지옥(MFC.dll 충돌로 인한 문제가 유명)이나 자바의 jar hell 등이 있습니다.


VCS

Version Control System 으로 subversion이나 git 등이 있습니다.

사전 필요 지식

PHP Namespace

(star)(star)(star)

라이브러리를 만들고 배포할 때 User, Database, Session 등의 일반 명사를 사용하면서 다른 라이브러리와 충돌하지 않기 위해서는 PHP의 네임스페이스에 대한 이해가 필수입니다.


PHP 의 네임스페이스에 대해서는 다음 내용을 참고하세요.


PSR-4 Auto Loader

(star)(star)

PHP 프레임워크 상호 운영성 그룹((PHP-FIG; PHP Framework Interop Group - http://www.php-fig.org)은 Drupal, Laravel, Symfony, CakePHP, Composer 등을 만든 권위있는 PHP 개발자들이 모인 커뮤니티 그룹으로서 PHP 언어와 프레임워크를 더욱 유연하고 상호 운용 가능하게 만들수 있도록 사실상의 표준을 제정하는 그룹입니다.

PSR 에서 제정한 여러 표준중 PSR-4 Auto Loader 덕에 include/require 지옥에 빠지지 않고 쉽게 외부 라이브러리를 가져다가 사용할 수 있으므로 PSR-4 에 대한 이해가 필요합니다.


PSR-4 는 다음과 같은 정규화된 클래스 이름을 규정하고 있으며 이를 따를 경우 composer 로 손쉽게 로딩할 수 있습니다. 

\<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>
CODE

예로 로깅 프레임워크인 모노로그(Monolog)는 다음과 같이 PSR-4 규격을 준수합니다.


<?php
namespace Monolog\Handler;
  
class FilterHandler extends AbstractHandler
{
CODE
네임스페이스는 실제 디렉터리에 일치하며 최종 php 소스 파일은 클래스명.php 로 존재하게 됩니다.

예로 FilterHandler를 구현한 파일은 src/Monolog/Handler/FilterHandler.php 에 위치하게 됩니다.


사용자는 composer 가 생성한 vendor/autoload.php 파일만 include 하면 외부 패키지를 바로 사용할 수 있습니다.

<?php
require 'vendor/autoload.php';
CODE


VCS

(star)(star)

VCS 사용법 및 버전 관리에 익숙해야 하며 특히 branch, tag 의 개념에 대해서 이해해야 합니다.


유의적 버전

(star)(star)

개발하는 소프트웨어의 규모가 커지고 외부 라이브러리를 많이 사용할 수록 의존성 지옥에 빠지기 쉬운 이유중 하나는 라이브러리의 버전을 변경할 때 어떤 규칙에 의해서 버전이 매겨지는지 통용되는 명확한 규칙이 없기 때문입니다.

유의적 버전(Semantic versioning)은 이런 의존성 문제를 해결하고자 나온 라이브러리의 버전 명시 규칙과 요구 사항으로 요약하면 다음과 같습니다.


먼저 외부에서 사용할 수 있는 공개 API 를 선언하고 라이브러리의 버전은 MAJOR.MINOR.PATCH 와 같은 형식으로 한다.

  • 기존 버전과 호환되지 않게 API가 변경되면 "MAJOR 버전"을 올린다.
  • 기존 버전과 호환되면서 새로운 기능이 추가 되었을 때는 "MINOR 버전"을 올린다.
  • 기존 버전과 호환되면서 버그를 수정했을 경우 "PATCH 버전" 을 올린다.
  • 버전 형식 뒤에 "정식 출시전" 이나 빌드 메타데이타를 위한 레이블을 추가할 수 있다.


위와 같은 정의를 기반으로 외부 라이브러리를 사용할 경우 다음과 같은 연산자를 사용하여 의존성을 기술할 수 있습니다.

이름예제의미
정확한 버전1.0.1버전 1.0.1과 일치하는 버전
범위 지정(Range)>=1.01.0 보다 크거나 같은 버전중 마지막 버전. 2.0, 3.1 도 포함
>=1.2 <2.01.2 보다 크거나 같고 2.0 보다 작은 버전중 마지막 버전
물결(Tilde) 연산자(~)~1.2바로 위(>=1.2 <2.0)와 동일한 의미
OR 연산자(|)1.2.3 | 1.3.41.2.3 또는 1.3.4
와일드카드 연산자(*)1.0.*1.0.x 대중 가장 큰 버전으로 1.1 보다는 작은 버전. >= 1.0 < 1.1 과 동일
캐럿(Caret) 연산자(^)^1.2.3>=1.2.3 <2.0 와 동일


예로 라라벨 프레임워크는 코어 모듈의 의존성을 다음과 같이 정의하고 있습니다.

 "require": {
        "php": ">=5.6.4",
        "laravel/framework": "5.4.*",
        "laravel/tinker": "~1.0"
    },
CODE


유의적 버전에 대한 추가 정보는 다음 자료를 참고하세요.

컴포저

컴포저는 다음과 같은 기존의 PHP 라이브러리 제작과 배포 방식의 문제를 해결했습니다. (참고: Monolog 의 의존성 트리)

  • 손쉬운 라이브러리 설치/갱신
  • 의존성 지옥 해결
  • 프로젝트별 의존성 분리


컴포저는 이를 위해 풍부한 기능을 제공하고 있으며 라이브러리를 만들고 배포하기 위해서는 다음과 같은 몇 가지 추가 지식이 필요합니다.

composer.json의 구조

컴포저는 프로젝트 정보와 의존성 정보를 composer.json 을 통해 관리합니다. 해당 파일에 대한 자세한 정보는 컴포저 한글 매뉴얼을 참고하시고 이중에서 우선적으로 알아야 하는 항목은 다음과 같습니다.


Property

다양한 프로퍼티를 설정할 수 있으며 컴포저로 라이브러리를 관리하기 위해 알아야 하는 항목은 다음과 같습니다.


name

패키지의 이름으로 /를 구분자로 해서 벤더 이름과 프로젝트 이름을 기술해 줍니다.

monolog/monolog
CODE


version

패키지의 버전으로 유의적 버전에 맞게 X.Y.Z 방식으로 기술해야 하며 특성에 따라 (알파 버전, Release Candidate 등) -alpha, -RC등의 접미사를 붙일수 있으며 아래는 버전 명명 예제입니다.

  • 0.2.5
  • 1.0.0-alpha3
  • 1.0.0-RC5

버전은 생략할 수 있으며 composer.json에 직접 버전을 기술할 경우 실수를 할 수 있으므로 VCS 의 태그를 통해 관리하는 것을 권장하고 있습니다.


type

 패키지의 타입으로 기본은 library 이며 본 예제도 라이브러리 작성 및 배포이므로 type을 library로 기술합니다.



require

현재 프로젝트가 참조하는 외부 의존성을 유의적 버전 형식으로 기술해 주며 다음은 PHP 5.6 이상과 monolog 1.12 이상을 사용하는 프로젝트의 의존성 기술 예제입니다.

"require": {
	"php": ">=5.6.*",
	"monolog/monolog": "~1.12",
	"vlucas/phpdotenv": "~2.0"
},
CODE


require-dev

현재 프로젝트를 개발하거나 테스트할 때 필요한 의존성을 기술하며 production 환경에서는 composer install 시  --no-dev 옵션을 사용하여 건너뛸 수 있습니다.

"require-dev": {
	"phpunit/phpunit": "~4.4",
	"mockery/mockery": "^0.9.4",
	"symfony/var-dumper": "~2.8|~3.0"
},
CODE



안정성 표시 기호(stability flags)

기본적으로 컴포저는 stable 버전의 패키지를 가져오도록 설정 되어 있지만 라이브러리 개발자라면 현재 개발중인 버전을 배포하고 테스트할 필요가 있습니다. 이럴 경우 컴포저의 안정성 표시 기호를 사용하여 개발중인 패키지를 가져올 수 있습니다.


컴포저는 다음과 같이 버전명에 dev- 를 기술하면 개발중인 버전을 사용하는 것으로 인식합니다.

{
    "require": {
        "doctrine/doctrine-fixtures-bundle": "dev-master",
        "doctrine/data-fixtures": "@dev"
    }
}
CODE

SW(라이브러리 포함)를 release 할 경우 버전은 하나밖에 지정하면 안 됩니다. 즉 고객에게 전달한 특정 SW 의 버전 5.1.3 은 하나만 존재해야 하며 5.1.3 이 여러 개 있으면 안 됩니다.(당연한 얘기겠죠.) 

만약 5.1.3 버전의 버그 수정이나 보안 패치등을 통해 변경이 되었다면 5.1.4 나 5.2 등 내부 정책에 맞게 버전이 변경되어야 하며 이를 5.1.3 으로 다시 유통하면 엄청난 혼란이 발생할 것입니다.


이에 맞게 Repository Manager 서비스도 릴리스시 특정 버전(예: 5.1.3)을 지정했으면 5.1.3 버전의 라이브러리를 업로드 못 하도록 차단하고 있습니다.

sonatype nexus 같이 설정으로 풀 수 있는 repository manager 도 있지만 기본 설정은 동일 버전 업로드 금지입니다


이런 릴리스 정책은 타당하지만 내부에서 개발중일 경우 배포시마다 새로 버전을 매겨서 배포 (예: 5.1.4-dev1,  5.1.4-dev2, ...) 하는 건 매우 번거로운 일입니다.

이때 버전명에 dev 를 사용하면 배포시마다 버저닝을 새로 안해도 되므로 개발 단계에서 라이브러리 버저닝 문제를 해결할 수 있습니다.


dev 기능을 이용하는 예제는 아래에서 설명하며 안정성 표시 기호에 대한 자세한 내용은 컴포저의 매뉴얼중 안정성 표시 기호를 참고하세요.

composer 와 비슷한 역할을 하는 Java 진영의 maven 에도 버전명에 SNAPSHOP 을 붙이면 비슷하게 동작합니다. 자세한 내용은 https://stackoverflow.com/questions/5901378/what-exactly-is-a-maven-snapshot-and-why-do-we-need-it 을 참고하세요.


autoload

PHP 오토로더를 위한 매핑 항목입니다.


files

특정 php 파일을 모든 요청때마다 로딩해야 한다면 files로 해당 파일을 기술하면 됩니다.

예로 라라벨은 헬퍼 클래스를 다음과 같이 로딩하고 있습니다.

"autoload": {
	"files": [
		"src/Illuminate/Foundation/helpers.php",
		"src/Illuminate/Support/helpers.php"
	]
}
CODE


PSR-4

컴포저로 라이브러리를 배포하기 위해 꼭 이해해야 할 항목으로 PSR-4에 맞게 오토로딩을 지정하며 소스는 src 폴더에 넣는 것을 권장합니다. 

Monolog 의 오토로더 설정은 다음과 같으며 이 의미는 src/Monolog 폴더 밑의 파일들을 Monolog 라는 네임스페이스에 묶어서 오토로딩 하겠다는 의미입니다.

"autoload": {
	"psr-4": {"Monolog\\": "src/Monolog"}
},
CODE

Monolog 의 루트 폴더에서 tree -L 3 명령어로 하위 계층을 그려보면 다음과 같습니다.

이제 src/Monolog/Logger.php 는  \Monolog\Logger 로 로딩할 수 있습니다.


또 다른 예로 라라벨에도 포함된 파일시스템 추상화 패키지인 Flysystem 의 경우 아래와 같이 PSR-4 에 매핑하고 있습니다.

"autoload": {
	"psr-4": {
		"League\\Flysystem\\": "src/"
	}
},
CODE

Fysystem 패키지의 폴더 구조는 아래와 같으며 src 밑에 클래스명(예: File.php)는 League\Flysystem\File 네임스페이스를 가지는 것을 알 수 있습니다.

라이브러리 만들기

프로젝트 만들기

먼저 hello-world 라는 라이브러리 패키지를 만들겠습니다.

  1. 작업 폴더 생성합니다.

    mkdir hello-world
    cd hello-world
    CODE
  2. 컴포저 init 명령어로 프로젝트 초기 설정을 합니다.

    composer init
    CODE
  3. 패키지명을 기술합니다.

    Package name (<vendor>/<name>) [root/hello-world]: lesstif/hello-world-proj
    CODE
  4. Description 에 설명을 기술합니다.
  5. Author에 저자 정보를 입력합니다.
  6. Minimum Stability 를 입력하며 기본 설정은 stable 입니다.
  7. Package Type 을 입력하며 기본 설정은 library 이므로 생략 가능합니다.
  8. 패키지의 License 정책을 입력합니다.
  9. 의존하는 외부 패키지와 개발용 패키지가 있을 경우 yes 를 누르고 패키지를 검색하면 자동으로 require 와 require-dev에 추가됩니다. (나중에 에디터로 직접 입력해도 됩니다.)

위 내용 asciinema로 보기


 이제 다른 프로젝트에서 현재 패키지를 오토로딩할 수 있도록 composer.json 에 PSR-4  내용을 추가합니다.

"autoload": {
    "psr-4" : {
        "Lesstif\\HelloWorld\\" : "src"
     }
}
CODE

이제 src 폴더를 만들고 Greeting.php 를 구현해 줍니다.

<?php namespace Lesstif\HelloWorld;

class Greeting {
    public function greeting(){
        return 'Hi';
    }
}
CODE

현재 개발 버전을 VCS 에 넣고 커밋을 하고 태깅을 달아 주면 최초 작업이 완료됩니다.

git commit -m "first"
git tag -a 0.1 -m "v0.1"
CODE


https://asciinema.org/a/cx262zfcce8ni7fd8635j29gl


library 사용 프로젝트

  1. hello-world 와 동일한 경로에 사용 예제 프로젝트를 생성합니다.

    mkdir test-proj
    cd test-proj
    CODE
  2. composer.json 을 만들고 의존성을 기술합니다.

    {
        "require": {    
            "lesstif/hello-world" : "~0.1"
        }       
    }   
    CODE

    develop 브랜치 사용할 경우 "dev-develop", master 는 "dev-master"

  3.  패키지를 packagist에서 찾으려고 하기 때문에 검색이 안되므로 저장소가 내부에 있다는 것을 알려줍니다.

    $stringEscapeUtils.escapeHtml($body)
    CODE

    vcs: version control system (https://getcomposer.org/doc/05-repositories.md#loading-a-package-from-a-vcs-repository)

vcs 일 경우 커밋하지 않으면 git hash 가 변경되지 않았으므로 최종 버전을 가져가지 않음


잘 동작하는지 확인하기 위해 test 코드를 작성해 봅니다.

  1. mkdir tests

  2. vi tests/Test.php 

    <?php
    require 'vendor/autoload.php';
    use \Lesstif\HelloWorld\Greeting;
    
    $g = new Greeting();
    
    
    echo $g->greeting() . "\n";
    CODE
  3. composer update 로 의존성을 생성합니다.

    $ composer update --verbose
     
    Dependency resolution completed in 0.000 seconds
      - Updating lesstif/hello-world (0.1)
        Checking out c6adad979bd7bd31a8070e2137810f80b2054132
      
    CODE
  4. 이제 잘 작동하는지 실행해 봅니다.
$ php tests/Test.php


Hi
CODE


이제 hello-world 프로젝트의 Greeting을 다음과 같이 수정합니다.

class Greeting {
    public function greeting(){
        return 'Hi there!';
    }
}
CODE


커밋하고 버전을 하나 높여서 태깅합니다.

git commit -m "수정"
git tag -a 0.1.1 -m "v0.1.1"
CODE


이제 test-proj에 간 후에 composer update 를 실행하면 새로운 버전(0.1.1)을 받아오는 것을 확인할 수 있습니다.

$ composer update



Loading composer repositories with package information
Updating dependencies (including require-dev)         
  - Updating lesstif/hello-world (0.1 => 0.1.1)
    Checking out 875d7dabc62d80bd0462420b90dc3d1ff413e5b6
CODE


다시 실행하면 수정 사항이 잘 반영됨을 알 수 있습니다.

$ php tests/Test.php


Hi there!
CODE


asciinema 에서 보기


개발 버전 배포하기

릴리스 버전이 아닌 개발중인 버전을 테스트 용도로 배포해야 하는 경우가 자주 있습니다. 이럴 경우 컴포저의 안정성 표시 기호를 dev-브랜치명 형식으로 지정하면 됩니다.

예로 develop 브랜치를 사용할 경우 다음과 같이 지정하면 되며 다음 예는 develop 브랜치에 있는 마지막 버전을 사용합니다.


{
    "require": {
        "lesstif/hello-world": "dev-develop"
    }
}
CODE


만약 해당 브랜치의 특정 커밋을 사용해야 한다면 브랜치명 뒤에 #를 붙이고 커밋 해시를 명시해 주면 됩니다.

{
    "require": {
        "lesstif/hello-world": "dev-develop#2eb0c0978d290a1c45346a1955188929cb4e5db7"
    }
}
CODE


git flow 를 사용할 경우 feature 는 아래와 같이 브랜치명을 기술해 주면 됩니다. 

{
    "require": {
        "lesstif/hello-world": "dev-feature/my-new-feature"
    }
}
CODE




향후 추가




이제 개발한 패키지를 Packagist 에 등록하고github의 hook 을 통해 배포하려면 내가 만든 PHP Composer 패키지를 Packagist.org 에 등록하는 방법 을 참고하세요.

같이 보기