【问题标题】:How to inject a dependency into a Validator in Zend Framework 3?如何将依赖项注入 Zend Framework 3 中的验证器?
【发布时间】:2016-11-21 06:02:15
【问题描述】:

我创建了一个名为 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',
    ],
]);

以及关联的Model,定义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 实例。

我想出了两个解决这个问题的方法,其中一个我立即放弃了,因为它使用单例,另一个在工作时需要手动传递依赖项。

解决方案 #1:使用在 Module.php 中创建的单例

在onBootstrap(MvcEvent $event)方法中,可以创建单例:

AccountService::getInstance()

然后可以在 CredentialsValidator 中访问它,并调用 Controller。

我放弃了这个解决方案,因为它使用了现在已弃用的单例模式。

解决方案 #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 应用程序中感觉像是一个异物。

我的问题:如何在不从 Controller 手动传递的情况下访问 CredentialsValidator 中的 AccountService 实例?

提前感谢您。

【问题讨论】:

    标签: php zend-framework zend-framework3 zf3


    【解决方案1】:

    我认为你可以为CredentialsValidator 创建一个工厂。然后在validatorsmodule.config.phpgetValidatorConfig()Module.phpgetValidatorConfig()里面注册工厂。

    示例: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)
            ]
        ]
    }
    

    由于验证器已经注册,所以你可以在InputFilter配置中注册名称

    $this->inputFilter->add([
         'name'     => 'Credential',
        'required' => true,        
        'validators' => [
             [
                'name' => CredentialsValidator::class,
             ]
        ],
    ]);
    

    【讨论】:

    • 非常感谢您的回复,但这不起作用。使用您提供的示例代码,CredentialsValidator::class 直接实例化,而不是通过CredentialsValidatorFactory::class,尽管工厂首先在module.config.php 中注册。因此,CredentialsValidator::class 没有所需的依赖项。您或其他任何人有什么进一步的想法吗?
    • 你能告诉我,为什么你CredentialsValidator直接实例化了?不使用工厂?
    • 不确定是否仍然是实际的,但您可能需要先获取InputFilterManager::class,然后从那里获取输入过滤器。 $inputFilterPluginManager = $container->get(InputFilterPluginManager::class); 也有一个类似于getValidatorConfig 有一个getInputFilterConfig wfor 配置键input_filters
    • 确保 'Zend\Validator', 模块已加载到您的 modules.config.php 文件中。
    猜你喜欢
    • 1970-01-01
    • 2011-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多