의존성 주입(DI)과 PHP-DI 컨테이너
Slim 프레임워크는 내부적으로 어떤 DI(Dependency Injection) 컨테이너든 결합할 수 있는 유연한 구조(PSR-11)를 지원합니다. 그중에서도 가장 강력하고 사용하기 쉬운 PHP-DI를 연동하여, 객체 간의 의존성을 깔끔하게 해결하고 테스트하기 쉬운 코드를 작성하는 방법을 배웁니다.
1. 의존성 주입(DI)이란?
특정 클래스가 다른 클래스(예: 데이터베이스 연결 객체, 로거 등)를 내부에서 직접 생성(new)하지 않고, 외부에서 주입받는(Inject) 설계 패턴입니다. 결합도를 낮추고 단위 테스트(Unit Test) 시 Mock 객체로 교체하기 쉽게 만들어 줍니다.
| 안 좋은 예 (강한 결합) |
좋은 예 (의존성 주입) |
class UserController {
private $db;
public function __construct() {
// ❌ 클래스 내부에서 직접 생성
$this->db = new Database();
}
}
|
class UserController {
private $db;
// ✅ 외부(컨테이너)에서 객체를 주입받음
public function __construct(Database $db) {
$this->db = $db;
}
}
|
2. PHP-DI 설정 및 수동 주입
php-di/php-di 패키지를 설치한 후 AppFactory를 통해 앱을 생성하기 전 컨테이너를 세팅합니다. 컨테이너 저장소에 필요한 객체(예: PDO)를 미리 정의해 둡니다.
<?php
use DI\Container;
use Slim\Factory\AppFactory;
// 1. PHP-DI 컨테이너 인스턴스 생성
$container = new Container();
// 2. 수동으로 컨테이너에 객체 등록 (예: PDO DB 연결 인스턴스)
$container->set(PDO::class, function () {
return new PDO('mysql:host=localhost;dbname=test', 'root', 'password');
});
// 3. 컨테이너를 Slim 앱에 주입
AppFactory::setContainer($container);
$app = AppFactory::create();
?>
3. PHP-DI의 핵심: 자동 주입(Autowiring)
앱이 커지면 $app->get() 안에 익명 함수로 로직을 다 적는 것은 무리가 있습니다. 별도의 컨트롤러 클래스로 분리하면 PHP-DI가 생성자의 타입 힌트(Type-hint)만 보고도 필요한 객체를 알아서 찾아 주입(Autowiring)해 줍니다.
<?php
namespace App\Controllers;
use PDO;
class UserController {
private $db;
// 💡 PHP-DI가 'PDO' 타입 힌트를 보고 컨테이너에서 객체를 알아서 찾아 주입(Autowiring) 해줍니다!
public function __construct(PDO $db) {
$this->db = $db;
}
// 비즈니스 로직 메서드
public function getUser($request, $response, $args) {
$stmt = $this->db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$args['id']]);
$response->getBody()->write(json_encode($stmt->fetch()));
return $response->withHeader('Content-Type', 'application/json');
}
}
// index.php 에서는 클래스와 메서드를 문자열로만 연결하면 끝납니다.
$app->get('/users/{id}', '\App\Controllers\UserController:getUser');
?>
DI Container Resolution Logs
[Router] GET /users/7 매칭 성공. 호출 타겟: '\App\Controllers\UserController:getUser'
[PHP-DI] UserController 클래스 분석 중... __construct(PDO $db) 타입 힌트 발견.
[PHP-DI] 컨테이너에서 PDO::class 매핑 탐색... 성공!
[PHP-DI] PDO 인스턴스 자동 주입(Autowiring) 완료 및 UserController 객체 생성.
[Controller] 비즈니스 로직(DB 쿼리) 정상 실행. HTTP 200 OK 응답 반환.
⚠️ 주의: Autowiring 남용
Autowiring은 매우 편리하지만, 마법처럼 동작하기 때문에 내부 흐름을 파악하기 어려울 수 있습니다. 명확한 인터페이스(Interface)를 정의하고 컨테이너 설정 파일에서 인터페이스와 구현체를 명시적으로 매핑해 주는 것이 규모가 큰 프로젝트에서는 더 좋은 설계입니다.
자동 주입(Autowiring) 동작 확인
위의 코드 실행 결과 블록에서 볼 수 있듯, 클라이언트가 API를 요청하면 라우터가 컨트롤러를 호출하기 직전에 PHP-DI 컨테이너가 중간에 개입하여 __construct의 타입 힌트를 분석하고, 필요한 부품(PDO 객체 등)들을 컨테이너에서 꺼내어 자동으로 조립(Autowiring) 해주는 과정을 확인할 수 있습니다.