采用控制器/服务/模型模式及依赖注入
我正在用Slim 4构建一个API,并希望采用Controllers/Services/Models的模式,但在依赖注入方面遇到了一些困难。
为了举例起见,我有这些端点:
$app->get('/applyant/{id:[0-9]+}', function (Request $request, Response $response) {
$user_model = new UserModel();
$internal = false;
$user_service = new UserService($user_model, $internal);
});
$app->get('/employee/{id:[0-9]+}', function (Request $request, Response $response) {
$user_model = new UserModel();
$internal = true;
$user_service = new UserService($user_model, $internal);
});
我想按这种方式使用控制器:
$app->get('/applyant/{id:[0-9]+}', UserController::class . ':getApplyant');
$app->get('/employee/{id:[0-9]+}', UserController::class . ':getEmployee');
我还拥有以下这些类:
class UserController
{
public UserService $user_service;
public function __construct(UserService $user_service)
{
$this->user_service = $user_service;
}
public function getEmployee(ServerRequestInterface $request, ResponseInterface $response)
{
// Internal must be true here
}
public function getApplyant(ServerRequestInterface $request, ResponseInterface $response)
{
// Internal must be false here
}
}
class UserService
{
public UserModel = $user_model;
public string $internal;
public function __construct(UserModel $user_model, bool $internal)
{
$this->user_model = $user_model;
$this->internal = $internal;
}
}
class UserModel
{
public int $id;
public string $name;
private \PDO $db;
public function __construct(\PDO $db)
{
$this->pdo = $db;
}
}
我的所有类都必须通过单元测试。使用依赖注入可以在需要时对类进行模拟(例如PDO)。
但我在按照上述模式使用DI时遇到了麻烦。我理解了自动装配的概念(例如使用PHP-DI),但对于像UserService构造函数中的可变参数 $internal,该如何处理?
我可以像PHP-DI的文档中所提到的那样使用values数组(https://php-di.org/doc/php-definitions.html#values):
return [
'database.host' => 'localhost',
'database.port' => 5000,
'report.recipients' => [
'[email protected]',
'[email protected]',
],
];
但该值只有在控制器中才知道。
我是不是遗漏了什么,还是在用DI实现我的类时完全错了?
解决方案
我认为这里的主要问题在于你通过构造函数注入了一个运行时值($internal),这很可能不应该放在构造函数里。
DI主要用于服务和基础设施,这些东西与对象本身的生命周期一样长久地存在。但 $internal 会根据端点而变化,因此最好把它从构造函数中拿出,改为作为方法参数传递。
比如,你可以这样写 UserService:
class UserService
{
private UserModel $user_model;
public function __construct(UserModel $user_model)
{
$this->user_model = $user_model;
}
public function getUser(int $id, bool $internal): array
{
// ...
}
}
然后在控制器中:
class UserController
{
public function __construct(private UserService $user_service) {}
public function getEmployee(Request $request, Response $response, array $args): Response
{
$data = $this->user_service->getUser((int) $args['id'], internal: true);
// ...
}
public function getApplyant(Request $request, Response $response, array $args): Response
{
$data = $this->user_service->getUser((int) $args['id'], internal: false);
// ...
}
}
这样,PHP-DI就可以毫不费力地自动装配一切。你只需要在容器中定义PDO:
return [
PDO::class => function () {
return new PDO('mysql:host=localhost;dbname=mydb', 'user', 'pass');
},
];
现在,也许有人会争辩说有时 $internal 确实是服务身份的一部分,应该放在构造函数中。在那种情况下,你也许可以在容器中定义两个独立的服务……但我认为对你的用例,上述方法更简单、也更干净。测试也更容易。你只需要在测试调用中对 UserModel 进行模拟,并直接传入 true/false。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。