의존성 자동 주입
Webman에서 의존성 자동 주입은 선택적 기능이며, 기본값은 비활성화 되어 있습니다. 의존성 자동 주입이 필요하다면 php-di 사용을 추천하며, 아래는 Webman과 php-di
의 사용법입니다.
설치
composer require psr/container ^1.1.1 php-di/php-di ^6.3 doctrine/annotations ^1.14
구성 파일 config/container.php
를 수정하고, 최종 내용은 아래와 같습니다:
$builder = new \DI\ContainerBuilder();
$builder->addDefinitions(config('dependence', []));
$builder->useAutowiring(true);
$builder->useAnnotations(true);
return $builder->build();
config/container.php
는 최종적으로PSR-11
규격에 맞는 컨테이너 인스턴스를 반환합니다.php-di
를 사용하고 싶지 않은 경우, 이곳에서 다른PSR-11
규격의 컨테이너 인스턴스를 생성하여 반환할 수 있습니다.
생성자 주입
app/service/Mailer.php
를 새로 만들고 (디렉토리가 존재하지 않으면 직접 생성) 내용은 아래와 같습니다:
<?php
namespace app\service;
class Mailer
{
public function mail($email, $content)
{
// 이메일 전송 코드 생략
}
}
app/controller/UserController.php
의 내용은 아래와 같습니다:
<?php
namespace app\controller;
use support\Request;
use app\service\Mailer;
class UserController
{
private $mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
public function register(Request $request)
{
$this->mailer->mail('hello@webman.com', '안녕하세요, 환영합니다!');
return response('ok');
}
}
정상적인 경우, app\controller\UserController
의 인스턴스를 만들기 위해서는 아래의 코드가 필요합니다:
$mailer = new Mailer;
$user = new UserController($mailer);
php-di
를 사용할 경우, 개발자는 컨트롤러 내의 Mailer
를 수동으로 인스턴스화할 필요가 없으며, Webman이 자동으로 처리해 줍니다. 만약 Mailer
인스턴스를 만드는 과정에서 다른 클래스의 의존성이 있다면, Webman은 자동으로 인스턴스화하여 주입해 줍니다. 개발자는 초기화 작업을 전혀 할 필요가 없습니다.
주의
반드시 프레임워크나php-di
에서 생성된 인스턴스만 의존성 자동 주입이 가능합니다. 수동으로new
를 사용하여 생성된 인스턴스는 의존성 자동 주입을 받을 수 없습니다. 주입이 필요한 경우,support\Container
인터페이스를 사용하여new
문장을 대체해야 합니다, 예를 들어:
use app\service\UserService;
use app\service\LogService;
use support\Container;
// new 키워드로 생성된 인스턴스는 의존성 주입이 불가능합니다
$user_service = new UserService;
// new 키워드로 생성된 인스턴스는 의존성 주입이 불가능합니다
$log_service = new LogService($path, $name);
// Container로 생성된 인스턴스는 의존성 주입이 가능합니다
$user_service = Container::get(UserService::class);
// Container로 생성된 인스턴스는 의존성 주입이 가능합니다
$log_service = Container::make(LogService::class, [$path, $name]);
주석 주입
생성자 의존성 자동 주입 외에도 주석을 사용하여 주입할 수 있습니다. 위의 예를 이어서, app\controller\UserController
를 아래와 같이 변경합니다:
<?php
namespace app\controller;
use support\Request;
use app\service\Mailer;
use DI\Annotation\Inject;
class UserController
{
/**
* @Inject
* @var Mailer
*/
private $mailer;
public function register(Request $request)
{
$this->mailer->mail('hello@webman.com', '안녕하세요, 환영합니다!');
return response('ok');
}
}
이 예제는 @Inject
주석을 통해 주입되며, @var
주석을 사용하여 객체 타입을 선언합니다. 이 방식은 생성자 주입과 동일한 효과를 가지지만 코드가 더 간결합니다.
주의
Webman은 1.4.6 버전 이전까지 컨트롤러 매개변수 주입을 지원하지 않습니다. 예를 들어, 아래 코드는 Webman <= 1.4.6에서는 지원하지 않습니다.
<?php
namespace app\controller;
use support\Request;
use app\service\Mailer;
class UserController
{
// 1.4.6 버전 이전에는 컨트롤러 매개변수 주입을 지원하지 않습니다
public function register(Request $request, Mailer $mailer)
{
$mailer->mail('hello@webman.com', '안녕하세요, 환영합니다!');
return response('ok');
}
}
사용자 정의 생성자 주입
때때로 생성자에 전달하는 인자가 클래스의 인스턴스가 아니라 문자열, 숫자, 배열 등일 수 있습니다. 예를 들어, Mailer 생성자는 SMTP 서버 IP와 포트를 전달받아야 합니다:
<?php
namespace app\service;
class Mailer
{
private $smtpHost;
private $smtpPort;
public function __construct($smtp_host, $smtp_port)
{
$this->smtpHost = $smtp_host;
$this->smtpPort = $smtp_port;
}
public function mail($email, $content)
{
// 이메일 전송 코드 생략
}
}
이 경우, 이전에 설명한 생성자 자동 주입을 직접 사용할 수 없습니다. 왜냐하면 php-di
가 $smtp_host
와 $smtp_port
의 값을 알 수 없기 때문입니다. 이럴 때는 사용자 정의 주입을 시도할 수 있습니다.
config/dependence.php
(파일이 존재하지 않으면 직접 생성) 에 아래 코드를 추가합니다:
return [
// ... 다른 설정은 생략
app\service\Mailer::class => new app\service\Mailer('192.168.1.11', 25);
];
이렇게 하면 의존성 주입이 app\service\Mailer
인스턴스를 가져오면, 자동으로 이 설정에서 생성된 app\service\Mailer
인스턴스를 사용하게 됩니다.
우리는 config/dependence.php
에서 new
를 사용하여 Mailer
클래스를 인스턴스화한 것을 알 수 있습니다. 이 예에서는 문제가 없지만, 만약 Mailer
클래스가 다른 클래스를 의존하거나 내부에서 주석 주입을 사용하는 경우, new
로 초기화하면 의존성 자동 주입이 작동하지 않습니다. 해결책은 사용자 정의 인터페이스 주입을 활용하여, Container::get(클래스명)
또는 Container::make(클래스명, [생성자 매개변수])
메서드를 사용하여 클래스를 초기화하는 것입니다.
사용자 정의 인터페이스 주입
실제 프로젝트에서는 구체적인 클래스보다 인터페이스를 활용한 프로그래밍을 선호합니다. 예를 들어 app\controller\UserController
에서는 app\service\MailerInterface
를 가져와야 하며 app\service\Mailer
를 가져오면 안 됩니다.
MailerInterface
인터페이스를 정의합니다.
<?php
namespace app\service;
interface MailerInterface
{
public function mail($email, $content);
}
MailerInterface
인터페이스의 구현을 정의합니다.
<?php
namespace app\service;
class Mailer implements MailerInterface
{
private $smtpHost;
private $smtpPort;
public function __construct($smtp_host, $smtp_port)
{
$this->smtpHost = $smtp_host;
$this->smtpPort = $smtp_port;
}
public function mail($email, $content)
{
// 이메일 전송 코드 생략
}
}
구체적인 구현 대신 MailerInterface
인터페이스를 가져옵니다.
<?php
namespace app\controller;
use support\Request;
use app\service\MailerInterface;
use DI\Annotation\Inject;
class UserController
{
/**
* @Inject
* @var MailerInterface
*/
private $mailer;
public function register(Request $request)
{
$this->mailer->mail('hello@webman.com', '안녕하세요, 환영합니다!');
return response('ok');
}
}
config/dependence.php
에서 MailerInterface
인터페이스의 구현을 아래와 같이 정의합니다.
use Psr\Container\ContainerInterface;
return [
app\service\MailerInterface::class => function(ContainerInterface $container) {
return $container->make(app\service\Mailer::class, ['smtp_host' => '192.168.1.11', 'smtp_port' => 25]);
}
];
이렇게 하면 비즈니스에서 MailerInterface
인터페이스를 사용할 때, 자동으로 Mailer
구현체를 사용하게 됩니다.
인터페이스를 이용한 프로그래밍의 장점은 구성 요소를 교체해야 할 경우, 비즈니스 코드를 변경할 필요 없이
config/dependence.php
에서 구체적인 구현만 변경하면 된다는 점입니다. 이는 단위 테스트를 할 때도 매우 유용합니다.
기타 사용자 정의 주입
config/dependence.php
는 클래스의 의존성뿐만 아니라 문자열, 숫자, 배열 등의 다른 값도 정의할 수 있습니다.
예를 들어 config/dependence.php
에서 아래와 같이 정의할 수 있습니다:
return [
'smtp_host' => '192.168.1.11',
'smtp_port' => 25
];
이 때 우리는 @Inject
를 통해 smtp_host
와 smtp_port
를 클래스 속성에 주입할 수 있습니다.
<?php
namespace app\service;
use DI\Annotation\Inject;
class Mailer
{
/**
* @Inject("smtp_host")
*/
private $smtpHost;
/**
* @Inject("smtp_port")
*/
private $smtpPort;
public function mail($email, $content)
{
// 이메일 전송 코드 생략
echo "{$this->smtpHost}:{$this->smtpPort}\n"; // 192.168.1.11:25가 출력됩니다
}
}
주의:
@Inject("key")
내부는 큰따옴표이어야 합니다.
더 많은 내용
자세한 내용은 php-di 매뉴얼을 참조해 주시기 바랍니다.