采用控制器/服务/模型模式及依赖注入

后端开发 2026-07-11

我正在用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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章