의존성 주입 컨테이너 (DIC)는 응용 프로그램의 의존성을 관리할 수 있는 강력한 도구입니다. 최신 PHP 프레임워크에서 중요한 개념으로 사용되며 객체의 인스턴스화와 구성을 관리하는 데 사용됩니다. DIC 라이브러리 예시로는 Dice, Pimple, PHP-DI 및 league/container 등이 있습니다.
DIC는 클래스를 중앙 집중식으로 생성하고 관리할 수 있다는 멋진 방법입니다. 이것은 동일한 객체를 여러 클래스(예: 컨트롤러)에 전달해야 할 때 유용합니다. 간단한 예제가 이를 더 이해하기 쉽게 만들 수 있습니다.
과거에는 다음과 같이 작업하였을 수 있습니다:
require 'vendor/autoload.php'; // 데이터베이스에서 사용자를 관리하는 클래스 class UserController { protected PDO $pdo; public function __construct(PDO $pdo) { $this->pdo = $pdo; } public function view(int $id) { $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id'); $stmt->execute(['id' => $id]); print_r($stmt->fetch()); } } $User = new UserController(new PDO('mysql:host=localhost;dbname=test', 'user', 'pass')); Flight::route('/user/@id', [ $UserController, 'view' ]); Flight::start();
위 코드에서 새로운 PDO 객체를 생성하고 해당 객체를 UserController 클래스에 전달하는 것을 볼 수 있습니다. 이는 작은 응용 프로그램에 대해서는 괜찮지만 응용 프로그램이 성장함에 따라 동일한 PDO 객체를 여러 곳에서 생성해야 한다는 문제가 발생할 수 있습니다. 이때 DIC가 유용합니다.
PDO
UserController
다음은 DIC(다이스(Dice) 사용)를 사용한 동일한 예제입니다:
require 'vendor/autoload.php'; // 위와 동일한 클래스. 아무것도 변경되지 않았습니다 class UserController { protected PDO $pdo; public function __construct(PDO $pdo) { $this->pdo = $pdo; } public function view(int $id) { $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id'); $stmt->execute(['id' => $id]); print_r($stmt->fetch()); } } // 새로운 컨테이너 생성 $container = new \Dice\Dice; // 아래와 같이 다시 할당하는 것을 잊지 마세요! $container = $container->addRule('PDO', [ // shared는 매번 동일한 객체가 반환됨을 의미합니다 'shared' => true, 'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ] ]); // 이것은 Flight가 이를 사용하도록 알도록 컨테이너 핸들러를 등록합니다. Flight::registerContainerHandler(function($class, $params) use ($container) { return $container->create($class, $params); }); // 이제 컨테이너를 사용하여 UserController를 생성할 수 있습니다 Flight::route('/user/@id', [ 'UserController', 'view' ]); // 또는 대안적으로 아래와 같이 라우트를 정의할 수도 있습니다 Flight::route('/user/@id', 'UserController->view'); // 또는 Flight::route('/user/@id', 'UserController::view'); Flight::start();
이 예제에 추가 코드가 많이 추가되었다고 생각할 수 있습니다. 마법은 PDO 객체가 필요한 다른 컨트롤러가 있는 경우에 나타납니다.
// 모든 컨트롤러가 PDO 객체를 필요로 하는 생성자를 가진 경우 // 아래 라우트마다 자동으로 주입됩니다!!! Flight::route('/company/@id', 'CompanyController->view'); Flight::route('/organization/@id', 'OrganizationController->view'); Flight::route('/category/@id', 'CategoryController->view'); Flight::route('/settings', 'SettingsController->view');
DIC를 활용하면 단위 테스트가 훨씬 쉬워집니다. 모의 객체를 생성하고 클래스에 전달할 수 있습니다. 응용 프로그램에 대한 테스트를 작성할 때 이것은 거대한 이점이 됩니다!
Flight는 PSR-11 호환 컨테이너도 사용할 수 있습니다. 이는 PSR-11 인터페이스를 구현하는 어떤 컨테이너든 사용할 수 있다는 것을 의미합니다. 다음은 League의 PSR-11 컨테이너를 사용하는 예제입니다:
require 'vendor/autoload.php'; // 위와 동일한 UserController 클래스 $container = new \League\Container\Container(); $container->add(UserController::class)->addArgument(PdoWrapper::class); $container->add(PdoWrapper::class) ->addArgument('mysql:host=localhost;dbname=test') ->addArgument('user') ->addArgument('pass'); Flight::registerContainerHandler($container); Flight::route('/user', [ 'UserController', 'view' ]); Flight::start();
이전 Dice 예제보다 조금 더 세부적이지만 동일한 이점을 가지고 작동합니다!
사용자 지정 DIC 핸들러를 만들 수도 있습니다. PSR-11이 아닌 사용자 지정 컨테이너를 사용하려는 경우 유용합니다. 기본 예제에서 이를 수행하는 방법을 확인하세요.
추가로, Flight를 사용할 때 일반적인 설정을 보다 쉽게 만들어주는 유용한 기본값들도 있습니다.
컨트롤러/미들웨어에서 Engine 인스턴스를 사용 중이라면 다음과 같이 구성할 수 있습니다:
Engine
// 부트스트랩 파일의 어딘가에서 $engine = Flight::app(); $container = new \Dice\Dice; $container = $container->addRule('*', [ 'substitutions' => [ // 여기에 인스턴스를 전달하세요 Engine::class => $engine ] ]); $engine->registerContainerHandler(function($class, $params) use ($container) { return $container->create($class, $params); }); // 이제 컨트롤러/미들웨어에서 Engine 인스턴스를 사용할 수 있습니다 class MyController { public function __construct(Engine $app) { $this->app = $app; } public function index() { $this->app->render('index'); } }
컨테이너에 추가하려는 다른 클래스가 있는 경우, Dice를 사용할 때는 컨테이너에서 자동으로 해결되므로 간단합니다. 다음은 예시입니다:
$container = new \Dice\Dice; // 클래스에 아무것도 주입할 필요가 없다면 // 아무것도 정의할 필요가 없습니다! Flight::registerContainerHandler(function($class, $params) use ($container) { return $container->create($class, $params); }); class MyCustomClass { public function parseThing() { return 'thing'; } } class UserController { protected MyCustomClass $MyCustomClass; public function __construct(MyCustomClass $MyCustomClass) { $this->MyCustomClass = $MyCustomClass; } public function index() { echo $this->MyCustomClass->parseThing(); } } Flight::route('/user', 'UserController->index');