ZF2 将 InputFilter 注入 Fieldset 无法自动工作

ZF2 injecting InputFilter into Fieldset not working automatically

我正在使用 ZF2 和 Doctrine2 构建一个小型应用程序。以具有大量可重用代码和技术的方式进行设置。然而,我的 InputFilter 没有自动注入到它应该关联的 Fieldset 中,这让我很困惑。

我已确认使用 Fieldset 的表单有效(没有 InputFilter)。 InputFilter 在调试期间也可见。

接下来的问题是,我做错了什么以及如何解决在 ZF2 中将单独的 InputFilterFieldset 耦合的问题?


旁注:

1 - 我知道通过使用 InputFilterInterface 我可以在 Fieldset class 内部使用 getInputFilterSpecification() 函数获得 InputFilter。但是,由于我试图保持干燥和可重复使用,如果我要创建一个需要使用 Entity 和 [=22= 的 API,则不必复制它],但只能让后者加上一个Fieldset

2 - 使用了很多摘要 classes,在使用的地方我会在片段中指出它们具有相关的内容

3 - 问题行在 CustomerFieldsetFactory.php

============================================= ============================

实体:Customer.php

/**
 * Class Customer
 * @package Customer\Entity
 *
 * @ORM\Entity
 * @ORM\Table(name="customers")
 */
class Customer extends AbstractEntity //Contains $id
{
    /**
     * @var string
     * @ORM\Column(name="name", type="string", length=255, nullable=false)
     */
    protected $name;
}

形式:CustomerForm.php

class CustomerForm extends AbstractForm
{
    public function __construct($name = null, array $options)
    {
        parent::__construct($name, $options); // Adds CSRF
    }

    public function init()
    {
        $this->add([
            'name' => 'customer',
            'type' => CustomerFieldset::class,
            'options' => [
                'use_as_base_fieldset' => true,
            ],
        ]);

        //Call parent initializer. Check in parent what it does.
        parent::init(); //Adds submit button if not in form
    }
}

字段集:CustomerFieldset.php

class CustomerFieldset extends AbstractFieldset //Contains EntityManager property and constructor requirement (as we're managing Doctrine Entities here)
{
    public function init()
    {
        $this->add([ //For now id field is here, until InputFilter injection works
            'name' => 'id',
            'type' => Hidden::class,
            'attributes' => [
                'id' => 'entityId',
            ],
        ]);

        $this->add([
            'name' => 'name',
            'type' => Text::class,
            'options' => [
                'label' => _('Name'),
            ],
        ]);
    }
}

输入过滤器:CustomerInputFilter.php

class CustomerInputFilter extends AbstractInputFilter
{
    public function init()
    {
        parent::init();

        $this->add([
            'name' => 'name',
            'required' => true,
            'filters' => [
                ['name' => StringTrim::class],
                ['name' => StripTags::class],
            ],
            'validators' => [
                [
                    'name' => StringLength::class,
                    'options' => [
                        'min' => 3,
                        'max' => 255,
                    ],
                ],
            ],
        ]);
    }
}

在 class 之上。工厂下方

表格工厂:CustomerFormFactory.php

class CustomerFormFactory implements FactoryInterface, MutableCreationOptionsInterface
{
    /**
     * @var array
     */
    protected $options;

    /**
     * @param array $options
     */
    public function setCreationOptions(array $options)
    {
        //Arguments checking removed
        $this->options = $options;
    }

    /**
     * @param ServiceLocatorInterface|ControllerManager $serviceLocator
     * @return CustomerForm
     */
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $serviceManager = $serviceLocator->getServiceLocator();

        $form = new CustomerForm($this->options['name'], $this->options['options']);

        $form->setTranslator($serviceManager->get('translator'));

        return $form;
    }
}

字段集工厂:CustomerFieldsetFactory.php

class CustomerFieldsetFactory implements FactoryInterface, MutableCreationOptionsInterface
{
    /**
     * @var string
     */
    protected $name;

    public function setCreationOptions(array $options)
    {
        //Argument checking removed

        $this->name = $options['name'];
    }

    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $serviceManager = $serviceLocator->getServiceLocator();

        $fieldset = new CustomerFieldset($serviceManager->get('Doctrine\ORM\EntityManager'), $this->name);

        $fieldset->setHydrator(new DoctrineObject($serviceManager->get('doctrine.entitymanager.orm_default'), false));
        $fieldset->setObject(new Customer());
        $fieldset->setInputFilter($serviceManager->get('InputFilterManager')->get(CustomerInputFilter::class));

        //RIGHT HERE! THE LINE ABOVE IS THE ONE THAT DOES NOT WORK!!!

        return $fieldset;
    }
}

输入过滤器工厂:CustomerInputFilterFactory.php

class CustomerInputFilterFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $repository = $serviceLocator->getServiceLocator()
            ->get('Doctrine\ORM\EntityManager')
                ->getRepository(Customer::class);

        return new CustomerInputFilter($repository);
    }
}

配置:module.config.php

'controllers' => [
    'factories' => [
        CustomerController::class => CustomerControllerFactory::class,
    ],
],
'form_elements' => [
    'factories' => [
        CustomerForm::class => CustomerFormFactory::class,
        CustomerFieldset::class => CustomerFieldsetFactory::class,
    ],
],
'input_filters' => [
    'factories' => [
        CustomerInputFilter::class => CustomerInputFilterFactory::class,
    ],
],
'service_manager' => [
    'invokables' => [
        CustomerControllerService::class => CustomerControllerService::class,
    ],
],

我希望你们中的一位能帮我解决这个问题。


编辑:更新实际错误

CustomerFieldset.php(以上)中的以下行触发错误。

$fieldset->setInputFilter($serviceManager->get('InputFilterManager')->get(CustomerInputFilter::class));

错误:

Fatal error: Call to undefined method Customer\Fieldset\CustomerFieldset::setInputFilter() in D:\htdocs\server-manager\module\Customer\src\Customer\Factory\CustomerFieldsetFactory.php on line 57

如上面的代码片段所示,InputFilter(及其工厂)被称为 InputFilterManager

错误表明它不知道 Fieldset 上的 getInputFilter() 函数。这在某种程度上是正确的,它不存在。接下来的问题是,如何让函数存在以便注入 InputFilter 将起作用,或者如何将此 InputFilter 绑定到 Fieldset?


编辑 2:根据威尔特的回答更新use InputFilterAwareTrait 添加到摘要 class AbstractInputFilter 以创建以下内容(来自答案):

use Zend\InputFilter\InputFilterAwareTrait;

abstract class AbstractFieldset extends Fieldset
{
    use InputFilterAwareTrait;
    // ... Other code
}

原来我在上面的(原始)代码中还有另一个错误: 在文件 module.config.php 中,input_filters 应该是 input_filter_specs。这是(在使用 Trait 之后)一个 Invalid Factory registered 错误(标题为 ServiceNotCreatedException)。

以下内容可能对某人有用,使用 Hydrator 创建 Fieldset 的工厂,Object 和 Inputfilter 具有以下 createService() 功能:

public function createService(ServiceLocatorInterface $serviceLocator)
{
    /** @var ServiceLocator $serviceManager */
    $serviceManager = $serviceLocator->getServiceLocator();
    /** @var CustomerRepository $customerRepository */
    $customerRepository = $serviceManager->get('Doctrine\ORM\EntityManager')->getRepository(Customer::class);

    $fieldset = new CustomerFieldset($serviceManager->get('Doctrine\ORM\EntityManager'), $this->name);
    $fieldset->setHydrator(new DoctrineObject($serviceManager->get('doctrine.entitymanager.orm_default'), false));
    $fieldset->setObject(new Customer());
    $fieldset->setInputFilter($serviceManager->get('InputFilterManager')->get(CustomerInputFilter::class, $customerRepository));

    return $fieldset;
}

您的问题中添加了很多信息。我建议您以后尝试缩小问题范围。在此处阅读有关好问题指南的更多信息:How to create a Minimal, Complete, and Verifiable example.

Zend 框架提供了一个 InputFilterAwareTraitsetInputFiltergetInputFilter 方法。你可以很容易地 implement/use 你的 CustomerFieldset class:

这个特质
use Zend\InputFilter\InputFilterAwareTrait;

class CustomerFieldset extends AbstractFieldset
{
    use InputFilterAwareTrait;

    //...remaining code

}

如果您希望所有 class 中的输入过滤器扩展您的摘要 AbstractFieldset class,您还可以决定在那里添加特征:

use Zend\InputFilter\InputFilterAwareTrait;

class AbstractFieldset
{
    use InputFilterAwareTrait;

    //...remaining code

}

请参阅以下问题:How to validate nested fieldsets。字段集不包含 InputFilters,但您应该扩展表单的基础 InputFilter。

为每个字段集创建一个 InputFilter,并将它们添加到表单的 InputFilter 中,并与您的字段集同名。正如我链接的另一个问题的答案中所见。

如果您不想这样做,您可以考虑使用 InputSpecification。