네임스페이스 – 10.오토로딩

네임스페이스 기능을 사용할 규모의 프로그램이라면 개발된 소스가 여러 파일/디렉토리로 분산되어 관리되고 있을 것이며, 외부 라이브러리까지 포함된다면 더욱 많은 소스 파일들이 곳곳에 산재해 있을 것입니다.

이러한 소스들을 필요할 때마다 불러들여 사용해야 하는데 이 작업을 위해서 전통적으로 각 문서 상단에 require 또는 include로 일일이 불러들이게 됩니다.

예를 들어, 웹서버 홈 디렉토리에 아래와 같이 소스 파일들이 분산되어 관리되고 있는 경우를 살펴보겠습니다.

웹서버의 파일시스템 계층 구조

각 php 파일은 파일명과 같은 이름의 클래스가 아래와 같이 정의되어 있습니다.

홈 디렉토리에 있는 index.php 파일에서 Connection, Foo, Bar 클래스에 접근하기 위해 해당 파일을 불러들입니다.

수 많은 프레임워크, 콤포넌트, 일반 라이브러리에 있는 유용한 클래스를 사용하기 위해서는 해당 소스 파일을 일일이 확인하고 include 또는 require로 불러들이는 일은 간단한 일이 아닙니다.

만약 클래스에 접근하는 과정에서 해당 소스 파일을 자동으로 불러들일 수 있다면 프로그램 개발이 매우 수월해 질 것입니다.

spl_autoload_register 함수

spl_autoload_register의 첫 번째 매개변수에 오토로더를 등록하게 되면, 정의되지 않은 클래스 또는 인터페이스에 접근을 시도할 때, 콜백 함수인 오토로더를 자동으로 호출함으로 오토로더에서  해당 클래스 또는 인터페이스가 정의된 파일을 include 또는 require할 수 있도록 합니다.

아래는 오토로더를 클로저로 작성하여 spl_autoload_register 함수에 등록한 예제입니다. 여기서는 해당 클래스를 require하는 대신에 전달된 매개변수 $class에 전달된 값을 확인하고 있습니다.

위 예제에서 Connection 클래스가 정의되지 않고 객체를 생성하려고 하기 때문에 spl_autoload_register 함수에 등록된 오토로더인 function ($class)을 호출합니다. 이 때 오토로더의 매개변수로 전달되는 $class에는 객체 생성하려는 클래스의 이름이며, 이름 구성은 선행 백슬래시를 제거한 절대-클래스-이름fully qualified class name인 MyProject\Component\Debug\Connection입니다.

클래스 이름을 \MyProject\Component\Debug\Connection()와 같이 절대-클래스-이름fully qualified class name으로 호출하지 않더라도 namaspace와 use 문에 따른 네임스페이스 이름변환규칙name resolution rule을 적용하여 변환된 선행 백슬래시를 제거한 절대-클래스-이름fully qualified class name이 오토로더의 매개변수로 전달됩니다.

아래 예제에서 MyProject\Component\Debug 네임스페이스 내에서 백슬래시가 없는 엘리먼트-이름unqualified name Connection으로 객체 생성하려고 접근하였지만 오토로더에 전달된 매개변수값은 이름변환규칙name resolution rule에 따라 위의 예제와 동일하게절대-클래스-이름fully qualified class name인 MyProject\Component\Debug\Connection입니다.

네임스페이스와 파일시스템의 이름 및 계층 구조 일치화

만약 아래 그림과 같이 클래스 이름과 클래스가 정의된 파일 이름을 일치시키고, 오토로더로 전달된 네임스페이스 계층 구조와 클래스 파일의 파일시스템의 계층 구조를 동일하게 한다면 클래스 파일의 물리적 위치를 쉽게 알 수 있습니다.

  • 클래스 이름과 파일 이름의 일치화: Connection -> Connection.php, Foo -> Foo.php, Bar -> Bar.php
  • 네임스페이스와 파일시스템의 계층 구조 일치화

웹서버와 네임스페이스의 이름 및 계층 구조 일치

이름 및 계층 구조를 일치시키면, 오토로더로 전달된  절대-클래스-이름fully qualified class name인 MyProject\Component\Debug\Connection에서 백슬래시를 슬래시로 바꾸고 문자열 끝에 ‘.php’를 붙이면 MyProject/Component/Debug/Connection.php가 되며, 이것이 바로 Connection 클래스가 정의된 소스 파일의 절대 경로(웹서버 홈을 기준으로 하는 상대 경로 )가 됩니다.

소스 파일의 절대 경로를 가지고 include 또는 require로 현재 페이지에 포함시키면 오류 없이 클래스 객체가 생성되고 프로그램이 진행될 것 입니다.

autoload.php 파일 공유

오토로더를 등록하는 spl_autoload_register함수를 다른 소스 파일에서도 공유하기 위해서 별도의 파일 autoload.php로 분리시킬 필요가 있습니다.

위 예제에서는 autoload.php를 메인 파일과 동일한 디렉토리에 저장하였으나 필요에 따라 다른 디렉토리로 이동시킬 수 있습니다. 단 다른 디렉토리로 이동시킬 때는 autoload.php 파일에 정의된 오토로더 내의 $baseDir의 값을 메인 파일이 저장된 디렉토리로 변경해야만 합니다.

만약 파일시스템과 네임스페이스 간의 계층 구조와 파일 이름/클래스 이름이 다르게 구성되었다면, 오토로더 내에서 파일시스템의 php 파일의 물리적 위치를 찾는 개발자만의 알고리즘이 필요합니다. 그러나 개발자만의 알고리즘에 의해 오토로더를 작성한다면, 외부 라이브러리와의 상호 운용에는 많은 어려움이 따르게 됩니다.

PSR-4: Autoloader

PHP 프레임워크 간의 유연하면서도 상호 운용성을 높이기 위해, 프레임워크를 개발한 개발자들의 모여 만든 모임인 PHP-FIG(PHP Framework Interop Group)(한글판)에서 제일 먼저 발표한 것이 바로 PSR-0: 오토로딩 표준autoloading standard입니다. 2014년에 PSR-0는 폐지되고 대신 PSR-4: 오토로더autoloader가 새로운 표준으로 제시되었습니다.

PSR-4: 오토로더autoloader 표준은 파일 경로에서 클래스를 오토로드하기 위해 요구되는 사양specification을 권고recommendation하고 있으며, 그 중에 일부 중요한 내용을 소개하면 아래와 같습니다.

첫째, 오토로딩 대상은 클래스class뿐만 아니라 인터페이스interface, 트레이트trait 및 이와 유사한 구조similar structure를 포함합니다.

둘째, 절대-클래스-이름fully qualified class name은 아래의 형식을 갖추어야 합니다.

셋째, 절대-클래스-이름fully qualified class name에 해당하는 파일을 로딩하려면,

  1. 네임스페이스-프리픽스namespace prefix를 베이스-디렉토리base directory의 역할을 하는 파일시스템의 임의의 한 디렉토리에 대응시킵니다.
    ­
    여기서 네임스페이스-프리픽스namespace prefix절대-클래스-이름fully qualified class name에서 선행 네임스페이스 구분자namespace separator를 뺀 최상위 네임스페이스로 구성되며, 필요에 따라 일부 서브네임스페이스가 포함될 수 있습니다.
    ­
  2. 네임스페이스-프리픽스namespace prefix에 이어지는 나머지 서브네임스페이스 이름은 베이스-디렉토리base directory 내의 서브디렉토리에 해당하며 네임스페이스 구분자namespace separator는 디렉토리 구분자directory separator를 나타냅니다. 서브디렉토리 이름과 서브네임스페이스 이름의 대소문자는 서로 일치해야 합니다.
    ­
  3. 후행 클래스 이름은 .php로 끝나는 파일 이름과 같아야 합니다. 파일 이름과 클래스 이름의 대소문자는 서로 일치해야 합니다.
절대-클래스-이름
fully qualified class name
네임스페이스-프리픽스
namespace prefix
베이스-디렉토리
base directory
해당 파일 경로
resulting file path
\Acme\Log\Writer\File_Writer Acme\Log\Writer ./acme-log-writer/lib/ ./acme-log-writer/lib/File_Writer.php
\Aura\Web\Response\Status Aura\Web /path/to/aura-web/src/ /path/to/aura-web/src/Response/Status.php
\Symfony\Core\Request Symfony\Core ./vendor/Symfony/Core/ ./vendor/Symfony/Core/Request.php
\Zend\Acl Zend /usr/includes/Zend/ /usr/includes/Zend/Acl.php

베이스-디렉토리base directory

일반적으로 프로그램이 시작되는 메인 파일의 위치를 중심으로 베이스-디렉토리를 설정하게 됩니다.

웹 서비스가 시작되는 홈디렉토리 경로의 index.html 파일의 절대 경로가 /var/www/html/index.html일 때, 해당 웹사이트의 베이스-디렉토리를 /var/www/html/로 정할 수 있습니다.

  • /var/www/html/

그러나 해당 웹사이에 여러가지 서비스를 제공하고 있다면 각 서비스별로 시작되는 디렉토리 위치가 다를 것이고 그에 따라 베이스-디렉토리를 서비스별로 다르게 정할 수도 있습니다.

  • /var/www/html/webmail/
  • /var/www/html/telnet/

더군다나 각 서비스별로 자작 소스뿐만 아니라 다양한 프레임워크, 콤포넌트, 일반 라이브러리들이 포함될 수 있습니다.

이러한 프레임워크, 콤포넌트, 일반 라이브러리를 개발하는 입장에서 보면 개발한 소스들이 웹 홈디렉토리의 어느 서브디렉토리에 위치할지 알 수 없습니다. 자신이 개발한 소스를 기준으로 개발하게 되므로 일반적으로 그 소스의 최상위 위치를 기준으로 베이스-디렉토리를 정하게 될 것입니다.

  • /var/www/html/library/vendor/acme-log-writer/lib/
  • /var/www/html/library/vendor/aura-web/src/
  • /var/www/html/library/vendor/Symfony/Core/
  • /var/www/html/library/vendor/Zend/

베이스-디렉토리는 개발 소스 내의 경로 내에서 개발자가 자신의 선택에 따라 사용하게 됩니다. 따라서 위의 베이스-디렉토리 예는 아래와 같이 변경될 수도 있습니다.

  • /var/www/html/library/vendor/acme-log-writer/
  • /var/www/html/library/vendor/aura-web/
  • /var/www/html/library/vendor/Symfony/
  • /var/www/html/library/vendor/Zend/Acl/

중요한 것은 다음 항목에서 설명할 네임스페이스-프리픽스namespace prefix와의 맵핑mapping만 정확하게 해주면 되는 것입니다.

네임스페이스-프리픽스namespace prefix

여기서 프리픽스prefix는 단순한 접두사prefix라는 일반적인 의미가 아니라 아파치 웹서버 설치할 때 설치 디렉토리를 지정하는 옵션이 –prefix인 것 처럼 특정 콤포넌트 내에서 공통으로 사용되는 기본 경로를 의미합니다.

따라서 네임스페이스-프리픽스namespace prefix는 아파치 웹서버 설치 옵션인 프리픽스–prefix 또는 베이스-디렉토리base directory와 같은 개념이기 때문에, 개념상 기본 경로라는 의미로 베이스 네임스페이스라고 이해해도 큰 무리는 없을 것입니다.

베이스-디렉토리base directory가 개발자의 선택에 따라 지정되기 때문에 네임스페이스-프리픽스namespace prefix도 베이스-디렉토리base directory의 선택에 따라 변경됩니다.

보통 하나의 디렉토리에 특정 콤포넌트의 모든 소스가 포함된 경우에는 해당 디렉토리를 네임스페이스-프리픽스namespace prefix로 대응시킵니다. 예를 들어, PHP 콤포넌트*ehime/hello-world와 michelf/php-markdown의 설치 위치를 vendor 디렉토리를 기준으로 나타내면 아래와 같습니다.

  • 설치 위치: vendor/ehime/hello-world/src/HelloWorld/SayHello.php
    베이스-디렉토리**: vendor/ehime/hello-world/src/HelloWorld/
    절대-네임스페이스-이름: \HelloWorld
    네임스페이스-프리픽스**: HelloWorld
  • 설치 위치: vendor/michelf/php-markdown/Michelf/Markdown.php
    베이스-디렉토리**: vendor/michelf/php-markdown/Michelf/
    절대-네임스페이스-이름: \Michelf
    네임스페이스-프리픽스**: Michelf

PHP 콤포넌트 설치 위치

* PHP 콤포넌트를 설치하기 위해서는 우선 PHP 컴포저를 설치하여야 합니다.

** 각 콤포넌트에서 사용하고 있는 네임스페이스-프리픽스namespace prefix와 이에 대응하는 베이스-디렉토리base directory는 각 콤포넌트 디렉토리에 저장된 composer.json 파일 내의 autoload 속성을 살펴보면 정확히 알 수 있습니다. autoload 속성 내의 오토로드 맵핑autoload mapping 방식을 보면 ehime/hello-world 콤포넌트는 psr-0 방식이고, michelf/php-markdown 콤포넌트는 psr-4 방식인데, 이러한 방식의 차이로 인해 베이스-디렉토리base directory의 마지막 경로에 대한 해석이 달라지니 유의하시기 바랍니다.

외부 프레임워크, 콤포넌트, 일반 라이브러리 이외에도 개발자 자작 소스들도 있습니다. /MyProject/hello.php는 웹사이트에서 직접 접근하려는 메인 실행 파일이며, 관련된 자작 소스들의 설치 위치를 웹 홈디렉토리를 기준으로 절대 경로로 나타내면 아래와 같습니다.

  • 설치 위치: /var/www/html/MyProject/Foo/Bar.php
  • 베이스-디렉토리: /var/www/html/MyProject/Foo
  • 절대-네임스페이스-이름: \Foo
  • 네임스페이스-프리픽스: Foo

개발자 자작 소스 파일 위치

베이스-디렉토리와 네임스페이스-프리픽스의 맵핑

메인 실행 파일 hello.php에서 외부 프레임워크, 콤포넌트, 일반 라이브러리에 사용하려면 우선 해당 외부 라이브러리들을 웹 경로상 임의의 위치에 설치하여야 합니다. 예제에서 웹서버 홈디렉토리 아래 vendor 디렉토리가 있고, vendor 디렉토리에 외부 콤포넌트들이 설치되었습니다.

PHP 컴포저를 설치하면 모든 콤포넌트들이 공유하여 사용할 수 있는 autoload.php가 vendor 디렉토리에 자동으로 설치됩니다. 그러나 이 예제에서는 실험을 위해 PHP 컴포저가 설치해주는 autoload.php를 사용하지 않으며, 실험용 autoload.php를 작성하여 MyProject 디렉토리에 메인 실행 파일인 hello.php와 함께 설치했습니다.

하나의 오토로더를 공유하는 서로 독립적인 개발자 자작 소스와 PHP 콤포넌트인 ehime/hello-world와 michelf/php-markdown을 가지고 각각의 클래스들의 베이스-디렉토리base directory와 네임스페이스-프리픽스namespace prefix를 맵핑mapping시켜 보겠습니다.

개발자 자작 소스 및 PHP 콤포넌트 설치 위치

우선 PHP 콤포넌트를 맵핑을 위해 autoload.php 파일에서 오토로더의 베이스-디렉토리에 해당하는 $baseDir 변수를 외부에서 전역변수를 통해 전달받을 수 있도록 아래와 같이 수정합니다. 전역변수 $baseDir는 네임스페이스-프리픽스namespace prefix를 맵핑한 베이스-디렉토리base directory라는 의미를 가지고 있으며, 절대 경로로 저장됩니다. 그리고 오토로더의 매개변수로 전달되는 $class는 선행 네임스페이스 구분자namespace separator를 제외한 절대-클래스-이름fully qualified class name입니다.

위에서 구현한 오토로더는 네임스페이스-프리픽스namespace prefix에 서브네임스페이스가 포함되지 않은 최상위 네임스페이스로 구성되었을 때만 정상적으로 동작하는 소스입니다.

오토로더에서 각 콤포넌트와 자작 소스에서 전달되는 클래스에 대한 각각의 네임스페이스 정보 $class를 $baseDir 기준으로 변환하면 이는 해당 클래스 파일의 절대 경로가 됩니다. 해당 클래스가 정의된 이 파일을 require시키게 되면 이후 오류없이 정상적으로 해당 클래스에 접근할 수 있습니다.

메인 파일 hello.php에서 제일 먼저 autoload.php 파일을 포함시킵니다. 이후 각 콤포넌트의 네임스페이스-프리픽스namespace prefix를 맵핑한 베이스-디렉토리base directory를 전역변수 $baseDir에 저장한 후 해당 클래스에 접근합니다. 그러면 해당 클래스가 아직 정의되어 있지 않기 때문에 콜백 함수인 오토로더가 자동으로 호출되어 해당 클래스 파일을 require하게 됩니다.

3개의 클래스 파일을 일일히 require하지 않았어도 오토로더에 의해 각각의 클래스 파일들이 자동으로 포함되어 해당 클래스 메소드들을 정상적으로 실행할 수 있습니다.

  • echo Bar::world();
    // prints Hello World, Foo\Bar!
  • echo SayHello::world();
    // prints Hello World, Composer!
  • $html = Markdown::defaultTransform(‘<https://daringfireball.net/>’);
    // returns <p><a href=”https://daringfireball.net/”>https://daringfireball.net/</a></p>

답글 남기기