서비스(Services)
소개
서비스란 무엇인가요?
CodeIgniter 4의 서비스(Services)는 새로운 클래스 인스턴스를 생성하고 공유하는 기능을 제공합니다. 이는 Config\Services 클래스로 구현되어 있습니다.
CodeIgniter의 모든 핵심 클래스는 “서비스”로 제공됩니다. 이것은 단순히 로드할 클래스 이름을 하드코딩하는 대신, 호출할 클래스들을 아주 간단한 설정 파일 내에 정의한다는 것을 의미합니다. 이 파일은 필요한 클래스의 새 인스턴스를 생성하는 팩토리(factory)와 같은 역할을 합니다.
왜 서비스를 사용하나요?
빠른 예제를 통해 이해를 돕겠습니다. Timer 클래스의 인스턴스가 필요하다고 가정해 봅시다. 가장 간단한 방법은 단순히 해당 클래스의 새 인스턴스를 생성하는 것입니다.
<?php
$timer = new \CodeIgniter\Debug\Timer();
그리고 이것은 아주 잘 작동합니다. 하지만 나중에 그 자리에 다른 타이머 클래스를 사용하기로 결정했다면 어떻게 될까요? 아마도 새로운 클래스는 기본 타이머가 제공하지 않는 고급 보고 기능을 가지고 있을 것입니다. 이를 변경하려면 이제 애플리케이션에서 타이머 클래스를 사용한 모든 위치를 찾아야 합니다. 애플리케이션의 성능 로그를 위해 곳곳에 코드를 남겨두었다면, 이는 매우 시간이 많이 걸리고 에러가 발생하기 쉬운 작업이 될 것입니다. 바로 이런 상황에서 서비스가 유용합니다.
직접 인스턴스를 생성하는 대신, 중앙 클래스가 우리를 위해 인스턴스를 생성하게 합니다. 이 클래스는 매우 단순하게 유지되며, 서비스로 사용하려는 각 클래스에 대한 메서드만 포함합니다. 이 메서드는 일반적으로 해당 클래스의 공유 인스턴스(shared instance)를 반환하며, 필요한 모든 의존성을 함께 전달합니다. 그런 다음 기존의 타이머 생성 코드를 이 전역 함수나 서비스 클래스를 호출하는 코드로 대체합니다.
<?php
$timer = service('timer');
// The code above is the same as the code below.
$timer = \Config\Services::timer();
사용되는 구현체를 변경해야 할 때 서비스 설정 파일만 수정하면 애플리케이션 전체에 변경 사항이 자동으로 반영됩니다. 여러분은 그저 새로운 기능을 활용하기만 하면 됩니다. 매우 간단하고 에러에 강한 방식입니다.
참고
서비스는 컨트롤러 내에서만 생성하는 것을 권장합니다. 모델이나 라이브러리와 같은 다른 파일들은 의존성을 생성자를 통해 전달받거나 세터(setter) 메서드를 통해 받아야 합니다.
서비스를 가져오는 방법
많은 CodeIgniter 클래스들이 서비스로 제공되므로 다음과 같이 가져올 수 있습니다.
<?php
$timer = service('timer');
$timer는 Timer 클래스의 인스턴스이며, 다시 service('timer')를 호출하면 정확히 동일한 인스턴스를 얻게 됩니다.
서비스는 일반적으로 클래스의 공유 인스턴스를 반환합니다. 다음 코드는 첫 번째 호출에서 CURLRequest 인스턴스를 생성합니다. 그리고 두 번째 호출에서는 정확히 동일한 인스턴스를 반환합니다.
<?php
$options1 = [
'baseURI' => 'http://example.com/api/v1/',
'timeout' => 3,
];
$client1 = service('curlrequest', $options1);
$options2 = [
'baseURI' => 'http://another.example.com/api/v2/',
'timeout' => 10,
];
$client2 = service('curlrequest', $options2);
// $options2 does not work.
// $client2 is the exactly same instance as $client1.
따라서 $client2에 대한 $options2 매개변수는 작동하지 않으며 그냥 무시됩니다.
새 인스턴스 가져오기
Timer 클래스의 새로운 인스턴스를 얻고 싶다면 $getShared 인수에 false를 전달해야 합니다.
<?php
$timer = \Config\Services::timer(false);
편의 함수
서비스를 가져오기 위한 두 가지 함수가 제공됩니다. 이 함수들은 항상 사용 가능합니다.
service()
첫 번째는 요청된 서비스의 인스턴스를 반환하는 service()입니다. 유일한 필수 매개변수는 서비스 이름입니다. 이는 서비스 파일 내의 메서드 이름과 동일하며 항상 클래스의 공유(SHARED) 인스턴스를 반환합니다. 따라서 함수를 여러 번 호출해도 항상 동일한 인스턴스가 반환됩니다.
<?php
$logger = service('logger');
// The code above is the same as the code below.
$logger = \Config\Services::logger();
참고
v4.5.0부터 서비스에 매개변수를 전달하지 않는 경우 성능 향상을 위해 전역 함수 service()를 사용하는 것이 권장됩니다.
생성 메서드에 추가 매개변수가 필요한 경우 서비스 이름 뒤에 전달할 수 있습니다.
<?php
$renderer = service('renderer', APPPATH . 'views/');
// The code above is the same as the code below.
$renderer = \Config\Services::renderer(APPPATH . 'views/');
single_service()
두 번째 함수인 single_service()는 service()와 똑같이 작동하지만 클래스의 새로운 인스턴스를 반환합니다.
<?php
$logger = single_service('logger');
// The code above is the same as the code below.
$logger = \Config\Services::logger(false);
서비스 정의하기
서비스가 잘 작동하려면 각 클래스가 일관된 API 또는 사용할 인터페이스를 가지고 있다는 점에 의존할 수 있어야 합니다. 거의 모든 CodeIgniter 클래스는 준수해야 할 인터페이스를 제공합니다. 핵심 클래스를 확장하거나 교체할 때 인터페이스의 요구 사항을 충족하기만 하면 해당 클래스들이 호환된다는 것을 확신할 수 있습니다.
예를 들어, RouteCollection 클래스는 RouteCollectionInterface를 구현합니다. 라우트를 생성하는 다른 방식을 제공하는 대체 클래스를 만들고 싶다면, 단순히 RouteCollectionInterface를 구현하는 새 클래스를 만들기만 하면 됩니다.
<?php
namespace App\Router;
use CodeIgniter\Router\RouteCollectionInterface;
class MyRouteCollection implements RouteCollectionInterface
{
// Implement required methods here.
}
마지막으로 app/Config/Services.php에 routes() 메서드를 추가하여 CodeIgniter\Router\RouteCollection 대신 MyRouteCollection의 새 인스턴스를 생성하도록 합니다.
<?php
namespace Config;
use CodeIgniter\Config\BaseService;
class Services extends BaseService
{
// ...
public static function routes()
{
return new \App\Router\MyRouteCollection(static::locator(), config('Modules'));
}
}
매개변수 허용하기
때로는 인스턴스화 중에 클래스에 설정을 전달하는 옵션이 필요할 수 있습니다. 서비스 파일은 매우 단순한 클래스이므로 이를 쉽게 구현할 수 있습니다.
좋은 예는 renderer 서비스입니다. 기본적으로 이 클래스가 APPPATH . 'views/'에서 뷰를 찾을 수 있기를 원합니다. 하지만 개발자가 필요에 따라 해당 경로를 변경할 수 있는 옵션을 제공하고 싶습니다. 그래서 이 클래스는 생성자 매개변수로 $viewPath를 받습니다. 서비스 메서드는 다음과 같습니다.
<?php
namespace Config;
use CodeIgniter\Config\BaseService;
class Services extends BaseService
{
// ...
public static function renderer($viewPath = APPPATH . 'views/')
{
return new \CodeIgniter\View\View($viewPath);
}
}
이는 생성자 메서드에서 기본 경로를 설정하지만, 사용하는 경로를 쉽게 변경할 수 있도록 해줍니다.
<?php
$renderer = \Config\Services::renderer('/shared/views/');
서비스 검색(Service Discovery)
CodeIgniter는 정의된 모든 네임스페이스 내에서 사용자가 생성했을 수 있는 Config/Services.php 파일들을 자동으로 검색할 수 있습니다. 이를 통해 모듈의 서비스 파일들을 간편하게 사용할 수 있습니다. 커스텀 서비스 파일이 검색되려면 다음 요구 사항을 충족해야 합니다.
해당 네임스페이스가 app/Config/Autoload.php에 정의되어 있어야 합니다.
네임스페이스 내부의 Config/Services.php 위치에 파일이 있어야 합니다.
CodeIgniter\Config\BaseService를 상속받아야 합니다.
간단한 예제를 통해 명확히 이해해 봅시다.
프로젝트 루트 디렉토리에 Blog라는 새 디렉토리를 만들었다고 가정해 봅시다. 여기에는 컨트롤러, 모델 등이 포함된 Blog 모듈이 들어 있으며, 그중 일부 클래스를 서비스로 제공하고 싶습니다. 첫 번째 단계는 Blog/Config/Services.php 파일을 만드는 것입니다. 파일의 뼈대는 다음과 같습니다.
<?php
namespace Blog\Config;
use CodeIgniter\Config\BaseService;
class Services extends BaseService
{
public static function postManager()
{
// ...
}
}
이제 위에서 설명한 대로 이 파일을 사용할 수 있습니다. 어떤 컨트롤러에서든 posts 서비스를 가져오고 싶다면, 단순히 프레임워크의 Config\Services 클래스를 사용하여 여러분의 서비스를 가져오면 됩니다.
<?php
$postManager = service('postManager');
참고
여러 서비스 파일에 동일한 메서드 이름이 있는 경우, 가장 먼저 발견된 인스턴스가 반환됩니다.
서비스 캐시 초기화
Added in version 4.6.0.
프레임워크 초기화 과정의 초기 단계에서 서비스 클래스가 처음 호출될 때, 자동 검색(auto-discovery)을 통해 발견된 서비스 클래스들은 속성에 캐시되며 이후에는 업데이트되지 않습니다.
나중에 모듈이 동적으로 로드되고 해당 모듈에 서비스가 포함되어 있다면 캐시를 업데이트해야 합니다.
이는 Config\Services::resetServicesCache()를 실행하여 수행할 수 있습니다. 이렇게 하면 캐시가 지워지고, 필요한 시점에 서비스 검색을 다시 강제로 수행하게 됩니다.