ZF2/Doctrine2-字段集未验证,数据始终有效 [英] ZF2/Doctrine2 - Fieldsets not validated, data is always valid

查看:88
本文介绍了ZF2/Doctrine2-字段集未验证,数据始终有效的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经为Forms,Fieldsets和InputFilters使用Abstract类建立了一个结构.在FieldsetFactory创建和设置InputFilter的过程中,表单和字段集具有工厂(使用MutableCreationOptionsInterface传递选项)

I've set up a structure using Abstract classes for Forms, Fieldsets and InputFilters. Forms and Fieldsets have Factories while InputFilters are created and set on the Fieldsets by the FieldsetFactory (uses the MutableCreationOptionsInterface to pass along options)

我遇到的问题是为表单加载了InputFilters ,但没有用于验证数据.所有输入均被接受为有效.

The problem I have is that InputFilters are loaded for the Form, but are not used to validate the data. All input is accepted as valid.

例如,我有一个具有name属性的Country实体.国家/地区的名称必须至少为3个字符,最多255个字符.名称为"ab"时,将被视为有效.

E.g. I have a Country Entity with a name property. The name of the Country must be at least 3 chars and max 255. When the name is "ab", it's found to be valid.

在有人问之前:不会引发错误,该数据才被视为有效.

Before someone asks: no error is thrown, the data is just accepted as valid.

过去的几天里,我一直在努力解决这个问题,试图找出我犯了一个错误的地方,但找不到它.

I've been breaking my head over this for the passed few days trying to find where I've made a mistake, but cannot find it.

还有很多代码.我将其限制在我认为相关的范围内,但是:如果您需要更多,将会有更多.我已经删除了很多类型检查,docblock和类型提示以限制代码行/读取时间;)

Also, there's quite a bit of code. I've limited it to what I think is relevant, although: if you need more, there will be more. I've removed a lot of type checking, docblocks and type hints to limit code lines/reading time ;)

module.config.php

'form_elements' => [
    'factories' => [
        CountryForm::class => CountryFormFactory::class,
        CountryFieldset::class => CountryFieldsetFactory::class,
    ],
],

Country.php

class Country extends AbstractEntity // Creates $id + getter/setter
{
    /**
     * @var string
     * @ORM\Column(name="name", type="string", length=255, nullable=false)
     */
    protected $name;

    // Other properties
    // Getters/setters
}

CountryController.php -扩展AbstractActionController

CountryController.php - Extends the AbstractActionController

public function editAction() // Here to show the params used to call function
{
    return parent::editAction(
        Country::class,
        CountryForm::class,
        [
            'name' => 'country',
            'options' => []
        ],
        'id',
        'admin/countries/view',
        'admin/countries',
        ['id']
    );
}

AbstractActionController.php -(在这里出错)-由 CountryController#editAction()

AbstractActionController.php - (goes wrong in here) - Used by CountryController#editAction()

public function editAction (
    $emEntity,
    $formName,
    $formOptions,
    $idProperty,
    $route,
    $errorRoute, array
    $routeParams = []
) {
    //Check if form is set

    $id = $this->params()->fromRoute($idProperty, null);

    /** @var AbstractEntity $entity */
    $entity = $this->getEntityManager()->getRepository($emEntity)->find($id);

    /** @var AbstractForm $form */
    $form = $this->getFormElementManager()->get($formName, (is_null($formOptions) ? [] : $formOptions));
    $form->bind($entity);

    /** @var Request $request */
    $request = $this->getRequest();
    if ($request->isPost()) {
        $form->setData($request->getPost());

        if ($form->isValid()) { // HERE IS WHERE IT GOES WRONG -> ALL IS TRUE
            try {
                $this->getEntityManager()->flush();
            } catch (\Exception $e) {
                //Print errors & return (removed, unnecessary)
            }

            return $this->redirectToRoute($route, $this->getRouteParams($entity, $routeParams));
        }
    }

    return [
        'form' => $form,
        'validationMessages' => $form->getMessages() ?: '',
    ];
}

CountryForm.php

class CountryForm extends AbstractForm
{
    // This one added for SO, does nothing but call parent#__construct, which would happen anyway
    public function __construct($name = null, array $options)
    {
        parent::__construct($name, $options);
    }

    public function init()
    {
        //Call parent initializer.
        parent::init();

        $this->add([
            'name' => 'country',
            'type' => CountryFieldset::class,
            'options' => [
                'use_as_base_fieldset' => true,
            ],
        ]);
    }
}

CountryFormFactory.php

class CountryFormFactory extends AbstractFormFactory
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $serviceManager = $serviceLocator->getServiceLocator();

        /** @var EntityManager $entityManager */
        $entityManager = $serviceManager->get('Doctrine\ORM\EntityManager');

        $form = new CountryForm($this->name, $this->options);
        $form->setObjectManager($entityManager);
        $form->setTranslator($serviceManager->get('translator'));

        return $form;
    }
}

AbstractFormFactory.php -使用MutableCreationOptionsInterface从Controller函数调用中接收选项:$form = $this->getFormElementManager()->get($formName, (is_null($formOptions) ? [] : $formOptions))

AbstractFormFactory.php - Uses MutableCreationOptionsInterface to receive options from the Controller function call: $form = $this->getFormElementManager()->get($formName, (is_null($formOptions) ? [] : $formOptions))

abstract class AbstractFormFactory implements FactoryInterface, MutableCreationOptionsInterface
{
    protected $name;
    protected $options;

    /**
     * @param array $options
     */
    public function setCreationOptions(array $options)
    {
        // Check presence of required "name" (string) parameter in $options
        $this->name = $options['name'];

        // Check presence of required "options" (array) parameter in $options
        $this->options = $options['options'];
    }
}

CountryFieldset.php -由 CountryForm.php 用作基础字段集

class CountryFieldset extends AbstractFieldset
{
    public function init()
    {
        parent::init();

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

AbstractFieldset.php

abstract class AbstractFieldset extends Fieldset
{
    use InputFilterAwareTrait;
    use TranslatorAwareTrait;

    protected $entityManager;

    public function __construct(EntityManager $entityManager, $name)
    {
        parent::__construct($name);

        $this->setEntityManager($entityManager);
    }

    public function init()
    {
        $this->add([
            'name' => 'id',
            'type' => Hidden::class,
        ]);
    }

    // Getters/setters for $entityManager
}

CountryFieldsetFactory.php -在此处将输入器设置到FIELDSET上

class CountryFieldsetFactory extends AbstractFieldsetFactory
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        parent::createService($serviceLocator);

        /** @var CountryRepository $entityRepository */
        $entityRepository = $this->getEntityManager()->getRepository(Country::class);

        $fieldset = new CountryFieldset($this->getEntityManager(), $this->name);
        $fieldset->setHydrator(new DoctrineObject($this->getServiceManager()->get('doctrine.entitymanager.orm_default'), false));
        $fieldset->setObject(new Country());
        $fieldset->setTranslator($this->getTranslator());

        // HERE THE INPUTFILTER IS SET ONTO THE FIELDSET THAT WAS JUST CREATED
        $fieldset->setInputFilter(
            $this->getServiceManager()->get('InputFilterManager')->get(
                CountryInputFilter::class,
                [ // These are the options read by the MutableCreationOptionsInterface
                    'object_manager' => $this->getEntityManager(),
                    'object_repository' => $entityRepository,
                    'translator' => $this->getTranslator(),
                ]
            )
        );

        return $fieldset;
    }
}

AbstractFieldsetFactory.php

abstract class AbstractFieldsetFactory implements FactoryInterface, MutableCreationOptionsInterface
{

    protected $serviceManager;
    protected $entityManager;
    protected $translator;
    protected $name;


    public function setCreationOptions(array $options)
    {
        $this->name = $options['name'];
    }

    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        /** @var ServiceLocator $serviceManager */
        $this->serviceManager = $serviceLocator->getServiceLocator();

        /** @var EntityManager $entityManager */
        $this->entityManager = $this->getServiceManager()->get('Doctrine\ORM\EntityManager');

        /** @var Translator $translator */
        $this->translator = $this->getServiceManager()->get('translator');
    }

    // Getters/setters for properties
}

CountryFieldsetInputFilter.php

class CountryInputFilter 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, // This is just completely ignored
                        'max' => 255,
                    ],
                ],
            ],
        ]);

        // More adding
    }
}

AbstractFieldsetInputFilter.php -最后一个! :)

AbstractFieldsetInputFilter.php - Last one! :)

abstract class AbstractInputFilter extends InputFilter
{
    use TranslatorAwareTrait;

    protected $repository;
    protected $objectManager;

    public function __construct(array $options)
    {
        // Check if ObjectManager|EntityManager for InputFilter is set
        $this->setObjectManager($options['object_manager']);

        // Check if EntityRepository instance for InputFilter is set
        $this->setRepository($options['object_repository']);

        // Check for presence of translator so as to translate return messages
        $this->setTranslator($options['translator']);
    }

    public function init()
    {
        $this->add([
            'name' => 'id',
            'required' => false,
            'filters' => [
                ['name' => ToInt::class],
            ],
            'validators' => [
                ['name' => IsInt::class],
            ],
        ]);
    }

    //Getters/setters for properties
}


任何帮助将不胜感激.希望您不会在上面的代码中重载过多.但是我一直在将问题反复解决3-4天,而且还没有偶然发现问题所在.


Any help would very much be appreciated. Hopefully you're not too overloaded with the code above. But I've been mashing this issue back'n'forth for about 3-4 days and haven't stumbled onto what's going wrong.

总结:

在上面创建了CountryForm.它使用CountryFieldset,该CountryFieldsetCountryInputFilter一起预加载了(在CountryFieldsetFactory中).

In the above a CountryForm is created. It uses the CountryFieldset which gets preloaded (in CountryFieldsetFactory) with the CountryInputFilter.

在验证数据时,所有内容均视为有效.例如. -国家名称"ab"有效,尽管StringLength验证程序将'min' => 3,定义为选项.

When it comes to validating the data, everything is accepted as valid. E.g. - Country name "ab" is valid, though StringLength validator has 'min' => 3, defined as option.

推荐答案

既然已经设置了所有类,则可以通过构造字段集的InputFilters并将其添加到Forms中来实现另一种方法(来自@AlexP) InputFilter.而不是使用InputFilterSpecifications.

Since you've got all the classes set up already there is another approach (from @AlexP), by constructing and adding the InputFilters of the Fieldsets to the Forms InputFilter. Instead of using the InputFilterSpecifications.

因此将输入过滤器添加到您的input_filters配置键:

So add the input filters to your input_filters config key:

'form_elements' => [
    'factories' => [
        CountryForm::class => CountryFormFactory::class,
        CountryFieldset::class => CountryFieldsetFactory::class,
    ],
],
'input_filters' => [
    'factories' => [
        CountryInputFilter::class => CountryInputFilterFactory::class,
        CountryFieldsetInputFilter::class => CountryFieldsetInputFilterFactory::class,
    ],
],

工厂类:

class CountryInputFilterFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $serviceManager = $serviceLocator->getServiceLocator();

        $inputFilter = new CountryInputFilter(
            $serviceLocator->get(CountryFieldsetInputFilter::class),
            $serviceManager()->get('Doctrine\ORM\EntityManager'),
            $serviceManager()->get('translator')
        );

        return $inputFilter;
    }
}

class CountryFieldsetInputFilterFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $serviceManager = $serviceLocator->getServiceLocator();

        return new CountryFieldsetInputFilter(
            $serviceManager()->get('Doctrine\ORM\EntityManager'),
            $serviceManager()->get('translator')
        );
    }
}

class CountryFormFactory implements AbstractFormFactory
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $serviceManager = $serviceLocator->getServiceLocator();

        $form = new CountryForm($this->name, $this->options);
        $form->setObjectManager($serviceManager->get('Doctrine\ORM\EntityManager'));
        $form->setTranslator($serviceManager->get('translator'));

        $form->setInputFilter($serviceManager->get('InputFilterManager')->get(CountryInputFilterFactory::class));
        return $form;
    }
}

表格:

class CountryForm extends AbstractForm
{
    const COUNTRY_FIELDSET_NAME = 'country';

    // This one added for SO, does nothing but call parent#__construct, which would happen anyway
    public function __construct($name = null, array $options)
    {
        parent::__construct($name, $options);
    }

    public function init()
    {
        //Call parent initializer.
        parent::init();

        $this->add([
            'name' => self::COUNTRY_FIELDSET_NAME,
            'type' => CountryFieldset::class,
            'options' => [
                'use_as_base_fieldset' => true,
            ],
        ]);
    }
}

InputFilters:

InputFilters:

class CountryInputFilter extends AbstractInputFilter
{
    /** @var CountryFieldsetInputFilter  */
    protected $countryFieldsetInputFilter;

    public function __construct(CountryFieldsetInputFilter $filter, $objectManager, $translator)
    {
        $this->countryFieldsetInputFilter = $filter;
        // other dependencies
    }

    public function init()
    {
        $this->add($this->countryFieldsetInputFilter, CountryForm::COUNTRY_FIELDSET_NAME);
    }
}

class CountryFieldsetInputFilter extends AbstractInputFilter
{
    public function __construct($objectManager, $translator)
    {
        // dependencies
    }

    public function init()
    {
        $this->add([
            // configuration
        ]);
    }
}

请注意,我是通过实例而不是包含实例的数组将依赖项注入到每个参数的InputFilters中.

Note that I injected the dependencies to the InputFilters per argument by instance instead of an array holding the instances.

这篇关于ZF2/Doctrine2-字段集未验证,数据始终有效的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆