1

我创建了一个自定义验证器,称为 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 实例?

预先感谢您。

4

1 回答 1

1

我认为您可以为CredentialsValidator. 然后在里面或里面注册工厂内部validators配置。module.config.phpgetValidatorConfig()Module.php

例子:module.config.php

'service_manager' => [
    'factories' => [
    ]
],
'validators' => [
    'factories' => [
          CredentialsValidator::class => CredentialsValidatorFactory::class
     ]
]

或者Module.php

public function getValidatorConfig()
{
    return [
        'factories' => [
             CredentialsValidator::class => new                                         CredentialsValidatorFactory::class($param, $param2)
        ]
    ]
}

因为validator已经注册了,你可以在InputFilter配置中注册name

$this->inputFilter->add([
     'name'     => 'Credential',
    'required' => true,        
    'validators' => [
         [
            'name' => CredentialsValidator::class,
         ]
    ],
]);
于 2016-11-21T17:28:38.563 回答