ZF2 / Doctrine2 - 未验证的字段集,数据始终有效

时间:2017-05-30 14:58:19

标签: doctrine-orm zend-framework2 zend-form zend-validate zend-form-fieldset

我使用抽象类为Forms,Fieldsets和InputFilters设置了一个结构。表单和字段集具有工厂,而InputFilters由FieldsetFactory在Fieldsets上创建和设置(使用MutableCreationOptionsInterface传递选项)

我遇到的问题是为Form加载了 ,但不用于验证数据。所有输入都被接受为有效。

例如我有一个Country实体,其name属性。国家/地区的名称必须至少为3个字符且最多为255个。当名称为" ab"时,它被认为有效。

在某人提出之前:没有引发任何错误,数据才会被接受为有效。

在过去的几天里,我一直在试图找到我犯了错误的地方但却找不到它。

此外,还有相当多的代码。我将它限制在我认为相关的内容中,但是:如果你需要更多,那么会有更多。我删除了很多类型检查,docblocks和类型提示来限制代码行/阅读时间;)

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

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()

使用
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))

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 - IN INE INPUTFILTER设置在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 - 最后一个! :)

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天,并且没有发现错误。

总结一下:

在上面,创建了CountryForm。它使用CountryFieldset预加载(CountryFieldsetFactoryCountryInputFilter

在验证数据时,所有内容都被视为有效。例如。 - 国家名称" ab"是有效的,但StringLength验证程序已将'min' => 3,定义为选项。

2 个答案:

答案 0 :(得分:1)

由于您已经设置了所有类,因此有另一种方法(来自@AlexP),通过构造和添加Fieldsets的InputFilters到Forms InputFilter。而不是使用InputFilterSpecifications。

因此,请将输入过滤器添加到input_filters配置密钥:

'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:

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而不是包含实例的数组。

答案 1 :(得分:0)

$fieldset->setInputFilter($inputFilter); 

假设$fieldsetZend\Form\Fieldset的实例,则该方法不存在。这是因为您需要在表单Zend\Form\Form)上设置输入过滤器。

如果我要修改你的代码,我会按以下方式这样做。修改CountryForm以通过表单提供输入过滤器。这可以在不需要为每个表单定义自定义输入过滤器的情况下完成,方法是使用Zend\InputFilter\InputFilterProviderInterface并在type键下引用自定义输入过滤器。

当表单元素管理器创建表单时,它还会注入输入过滤器管理器,该管理器将能够找到自定义CountryInputFilter

例如:

use Zend\InputFilter\InputFilterProviderInterface;

class CountryForm extends AbstractForm implements InputFilterProviderInterface
{

    public function init() 
    { 
        //.. add the country fieldset here
    }

    /**
     * getInputFilterSpecification
     *
     * Return the form's input filter specification array.
     *
     * @return array
     */
    public function getInputFilterSpecification()
    {
        return [
            // Refers to the country element (happens to be a fieldset)
            'country' => [

                // Tell the input filter factory what type of input filter.
                'type' => 'Country\\InputFilter\\CountryInputFilter',

                // You could even overload/change the default defined in the input filter
                // here. This is useful as sometimes the requirements of the FORM are 
                // different from what the FIELDSET expects.
                // [
                //     'name'        => 'name',
                //     'required'    => false,
                //     'allow_empty' => true,
                // ],
            ],
        ];
    }

}