我创建了一个自定义验证器,称为 CredentialsValidator,如果传递的凭据有效,则返回 true。凭据的实际验证由依赖项 AccountService 负责,该依赖项可通过 getAccountService() 方法在 Validator 中使用。CredentialsValidator::isValid($value, $context = null) 的有趣部分是:
$accountService = $this->getAccountService();
$accountService->setEmail($this->getEmail());
$accountService->setPassword($this->getPassword());
try {
$accountService->auth();
} catch (RuntimeException $exception) {
$this->setMessage($exception->getMessage());
$this->error(self::INVALID_CREDENTIALS);
return false;
}
属性 $email 和 $password 填充如下:
if (isset($context['email'])) {
$this->setEmail($context['email']);
}
if (isset($context['password'])) {
$this->setPassword($context['password']);
}
当我在单元测试中实例化 CredentialsValidator 并手动分配依赖项 AccountService 时,它 100% 正确工作。
在实际的 Web 应用程序中,AccountService 是通过 ServiceManager 实例化的,使用 module.config.php 中的标准配置:
return [
'service_manager' => [
'factories' => [
AccountServiceFactory::class => AccountServiceFactory::class,
],
]
];
然而,我的目标是创建一个典型的“登录”表单,它使用 CredentialsValidator 来验证用户凭据。
为此,我创建了一个表单,扩展 Zend\Form\Form:
$this->add([
'type' => 'text',
'name' => 'email',
'attributes' => [
'id' => 'email'
],
'options' => [
'label' => 'Email',
],
]);
$this->add([
'type' => 'password',
'name' => 'password',
'attributes' => [
'id' => 'password'
],
'options' => [
'label' => 'Password',
],
]);
以及关联的模型,定义 getInputFilter() 方法:
public function getInputFilter()
{
if ($this->inputFilter) {
return $this->inputFilter;
}
$this->inputFilter = new InputFilter();
$this->inputFilter->add([
'name' => 'email',
'required' => true,
'filters' => [
['name' => StringTrimFilter::class],
['name' => StripTagsFilter::class],
['name' => StripNewlinesFilter::class],
],
'validators' => [
[
'name' => EmailAddressValidator::class,
'break_chain_on_failure' => true,
'options' => [
'allow' => HostnameValidator::ALLOW_DNS,
'useMxCheck' => false,
],
],
],
]);
$this->inputFilter->add([
'name' => 'password',
'required' => true,
'filters' => [
['name' => StringTrimFilter::class],
['name' => StripTagsFilter::class],
['name' => StripNewlinesFilter::class],
],
'validators' => [
[
'name' => StringLengthValidator::class,
'break_chain_on_failure' => true,
'options' => [
'min' => 1,
'max' => 128
],
]
],
]);
这就是问题开始的地方。当我添加:
[
'name' => CredentialsValidator::class,
'break_chain_on_failure' => true,
],
对于“密码”字段的“验证器”键,我无法注入存储在 ServiceManager 中的所需依赖项,因此,CredentialsValidator 无法工作,因为它无权访问 AccountService 实例。
对于这个问题,我提出了 2 个解决方案,其中一个我立即丢弃,因为它使用单例,另一个在工作时需要手动传递依赖项。
解决方案 #1:使用在 Module.php 中创建的单例
在 onBootstrap(MvcEvent $event) 方法中,可以创建一个单例:
AccountService::getInstance()
然后可以在 CredentialsValidator 中访问它,并调用 Controller。
我放弃了这个解决方案,因为它使用了现在已弃用的 Singleton 模式。
解决方案 #2:手动传递 AccountService 实例
在 Controller 中,可以将 AccountService 实例传递给 Model 的构造函数:
$model = new Model([AccountService::class => $accountService]);
然后在 Model::getInputFilter() 中,将实例传递给 'password' 字段的 'validators' 键,如下所示:
$this->inputFilter->add([
'name' => 'password,
'required' => true,
'filters' => [
['name' => StringTrimFilter::class],
['name' => StripTagsFilter::class],
['name' => StripNewlinesFilter::class],
],
'validators' => [
[
'name' => StringLengthValidator::class,
'break_chain_on_failure' => true,
'options' => [
'min' => 1,
'max' => 128
],
],
[
'name' => CredentialsValidator::class,
'break_chain_on_failure' => true,
'options' => [
AccountService::class => $this->getAccountService(),
],
],
],
]);
然后 CredentialsValidator 只需要通过其构造函数接受依赖项:
if (array_key_exists(AccountService::class, $options)) {
$this->setAccountService($options[AccountService::class]);
}
该解决方案确实有效,并且确实尊重类之间的接口,但是,手动传递 AccountService 实例是相当多的额外工作,实际上,ServiceManager 和注入的全部意义在于避免这种情况。解决方案#2 感觉就像 Zend Framework 3 应用程序中的异物。
我的问题:如何在不从控制器手动传递的情况下访问 CredentialsValidator 中的 AccountService 实例?
预先感谢您。