如何将InputFilter验证器应用于ZF3中的fieldset元素

时间:2018-03-19 21:44:27

标签: zend-framework3 zend-inputfilter zend-form-fieldset

我有一个有两个字段的表单。带有验证器的InputFilter应用于它。它工作正常。然后我将字段移动到字段集并将字段集添加到表单。现在不存在字段的赋值验证器。验证器对象isValid方法根本不会被触发。那么如何将InputFilter验证器应用于字段集中的字段?在这里你是班级:

文本类验证器

namespace Application\Validator;

use Zend\Validator\StringLength;
use Zend\Validator\ValidatorInterface;

class Text implements ValidatorInterface
{
    protected $stringLength;
    protected $messages = [];

    public function __construct()
    {
        $this->stringLengthValidator = new StringLength();
    }

    public function isValid($value, $context = null)
    {       
        if (empty($context['url'])) {
            if (empty($value)) return false;
            $this->stringLengthValidator->setMin(3);
            $this->stringLengthValidator->setMax(5000);

            if ($this->stringLengthValidator->isValid($value)) {
                return true;
            }
            $this->messages = $this->stringLengthValidator->getMessages();

            return false;
        }
        if (!empty($value)) return false;
        return true;
    }

    public function getMessages()
    {
        return $this->messages;
    }
}

测试类InputFilter

namespace Application\Filter;

use Application\Fieldset\Test as Fieldset;
use Application\Validator\Text;
use Application\Validator\Url;
use Zend\InputFilter\InputFilter;

class Test extends InputFilter
{
    public function init()
    {
        $this->add([
            'name' => Fieldset::TEXT,
            'required' => false,
            'allow_empty' => true,
            'continue_if_empty' => true,
            'validators' => [
                ['name' => Text::class],
            ],
        ]);
        $this->add([
            'name' => Fieldset::URL,
            'required' => false,
            'allow_empty' => true,
            'continue_if_empty' => true,
            'validators' => [
                ['name' => Url::class],
            ],
        ]);
    }
}

测试类Fieldset

namespace Application\Fieldset;

use Zend\Form\Fieldset;

class Test extends Fieldset
{
    const TEXT = 'text';
    const URL = 'url';
    public function init()
    {
        $this->add([
            'name' => self::TEXT,
            'type' => 'textarea',
            'attributes' => [
                'id' => 'text',
                'class' => 'form-control',
                'placeholder' => 'Type text here',
                'rows' => '6',
            ],
            'options' => [
                'label' => self::TEXT,

            ],
        ]);
        $this->add([
            'name' => self::URL,
            'type' => 'text',
            'attributes' => [
                'id' => 'url',
                'class' => 'form-control',
                'placeholder' => 'Type url here',
            ],
            'options' => [
                'label' => self::URL,

            ],
        ]);
    }
}

测试类表单

namespace Application\Form;

use Application\Fieldset\Test as TestFieldset;
use Zend\Form\Form;

class Test extends Form
{
    public function init()
    {
        $this->add([
            'name' => 'test',
            'type' => TestFieldset::class,
            'options' => [
                'use_as_base_fieldset' => true,
            ],
        ]);
        $this->add([
            'name' => 'submit',
            'attributes' => [
                'type' => 'submit',
                'value' => 'Send',
            ],
        ]);
    }
}

TestController类

namespace Application\Controller;

use Application\Form\Test as Form;
use Zend\Debug\Debug;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

class TestController extends AbstractActionController
{
    private $form;

    public function __construct(Form $form)
    {
        $this->form = $form;
    }

    public function indexAction()
    {
        if ($this->getRequest()->isPost()) {
            $this->form->setData($this->getRequest()->getPost());
            Debug::dump($this->getRequest()->getPost());
            if ($this->form->isValid()) {
                Debug::dump($this->form->getData());
                die();
            }
        }
        return new ViewModel(['form' => $this->form]);
    }
}

TestControllerFactory类

namespace Application\Factory;

use Application\Controller\TestController;
use Application\Form\Test;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;

class TestControllerFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $form = $container->get('FormElementManager')->get(Test::class);

        return new TestController($form);
    }
}

测试课

namespace Application\Factory;

use Application\Filter\Test as Filter;
use Application\Entity\Form as Entity;
use Application\Form\Test as Form;
use Interop\Container\ContainerInterface;
use Zend\Hydrator\ClassMethods;
use Zend\ServiceManager\Factory\FactoryInterface;

class Test implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        return (new Form())
            ->setHydrator($container
                ->get('HydratorManager')
                ->get(ClassMethods::class))
            ->setObject(new Entity())
            ->setInputFilter($container->get('InputFilterManager')->get(Filter::class));
    }
}

测试场集

namespace Application\Factory;

use Application\Entity\Fieldset as Entity;
use Application\Fieldset\Test as Fieldset;
use Interop\Container\ContainerInterface;
use Zend\Hydrator\ClassMethods;
use Zend\ServiceManager\Factory\FactoryInterface;

class TestFieldset implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        return (new Fieldset())
            ->setHydrator($container->get('HydratorManager')->get(ClassMethods::class))
            ->setObject(new Entity());
    }
}

更新

我通过添加setInputFilter()相应地将fieldset类更新为@Nukeface建议。但它没有奏效。它甚至没有执行InpuFilter类init方法。也许我做错了:

<?php

namespace Application\Fieldset;

use Application\Filter\Test as Filter;
use Zend\Form\Fieldset;
use Zend\InputFilter\InputFilterAwareTrait;

class Test extends Fieldset
{
    use InputFilterAwareTrait;

    const TEXT = 'text';
    const URL = 'url';

    public function init()
    {
        $this->add([
            'name' => self::TEXT,
            'type' => 'textarea',
            'attributes' => [
                'id' => 'text',
                'class' => 'form-control',
                'placeholder' => 'Type text here',
                'rows' => '6',
            ],
            'options' => [
                'label' => self::TEXT,

            ],
        ]);
        $this->add([
            'name' => self::URL,
            'type' => 'text',
            'attributes' => [
                'id' => 'url',
                'class' => 'form-control',
                'placeholder' => 'Type url here',
            ],
            'options' => [
                'label' => self::URL,

            ],
        ]);
        $this->setInputFilter(new Filter());
    }
}

2 个答案:

答案 0 :(得分:0)

只需将InputFilterProviderInterface类用于您的字段集即可。这为您的fieldset实现了getInputFilterSpecification方法,该方法执行此方法中提到的输入过滤器。

class MyFieldset extends Fieldset implements InputFilterProviderInterface
{
    public function init()
    {
        $this->add([
            'name' => 'textfield',
            'type' => Text::class,
            'attributes' => [
                ...
            ],
            'options' => [
                ...
            ]
        ]);
    }

    public function getInputFilterSpecification()
    {
        return [
            'textfield' => [
                'required' => true,
                'filters' => [
                    ...
                ],
                'validators' => [
                    [
                        'name' => YourTextValidator::class,
                        'options' => [
                            ...
                        ],
                    ],
                ],
            ],
        ];
    }
}

只要在表单中添加此字段集,绑定的过滤器和验证器就会在表单的isValid方法调用中执行。

答案 1 :(得分:0)

之前尝试过一个答案并且用完了字符(30k限制),所以created a repo instead。回购包含下面答案的抽象,这是一个有效的例子。

你的问题表明你有正确的想法,只是还没有实施。它还包含一些错误,例如为Fieldset名称设置FQCN。希望下面的内容可以帮助您正常运行。

作为一个用例,我们将有一个基本的地址表单。国家,时区和其他事物的关系,我将超出范围。为了更深入地和嵌套Fieldsets(还有收藏集),我将引用您的回购。

常规设置

首先创建基本设置。创建实体和配置。

基本实体

namespace Demo\Entity;

class Address
{
    protected $id;     // int - primary key - unique - auto increment
    protected $street; // string - max length 255 - not null
    protected $number; // int - max length 11 - not null
    protected $city;   // string - max length 255 - null

    // getters/setters/annotation/et cetera
}

要以通用且可重复使用的方式处理此问题,我们需要:

  • AddressForm(一般容器)
  • AddressFormFieldset(需要验证表单)
  • AddressFieldset(包含实体输入)
  • AddressFieldsetInputFilter(必须验证输入的数据)
  • AddressController(处理CRUD操作)
  • 以上所有的工厂类
  • 表单部分

配置

要在Zend Framework中将这些绑定在一起,需要在配置中注册这些。使用明确的命名,您可以添加这些命名。如果您正在使用像PhpStorm这样的IDE作为IDE,那么您可能希望将其保留到最后,因为可以为您生成use语句。

由于这是一个解释,我现在向您展示。将其添加到模块的配置中:

// use statements here
return [
    'controllers' => [
        'factories' => [
            AddressController::class => AddressControllerFactory::class,
        ],
    ],
    'form_elements' => [ // <-- note: both Form and Fieldset classes count as Form elements
        'factories' => [
            AddressForm::class => AddressFormFactory::class,
            AddressFieldset::class => AddressFieldsetFactory::class,
        ],
    ],
    'input_filters' => [ // <-- note: input filter classes only!
        'factories' => [
            AddressFormInputFilter::class => AddressFormInputFilterFactory::class,
            AddressFieldsetInputFilter::class => AddressFieldsetInputFilterFactory::class,
        ],
    ],
    'view_manager' => [
        'template_map' => [
            'addressFormPartial'   => __DIR__ . '/../view/partials/address-form.phtml',
    ],
];

字段集

首先,我们创建Fieldset(和Factory)类。这是因为它包含了我们将要处理的实际对象。

AddressFieldset

// other use statements for Elements
use Zend\Form\Fieldset;

class AddressFieldset extends Fieldset
{
    public function init()
    {
        parent::init(); // called due to inheritance

        $this->add([
            'name' => 'id',
            'type' => Hidden::class,
        ]);

        $this->add([
            'name' => 'street',
            'required' => true,
            'type' => Text::class,
            'options' => [
                'label' => 'Name',
            ],
            'attributes' => [
                'minlength' => 1,
                'maxlength' => 255,
            ],
        ]);

        $this->add([
            'name' => 'number',
            'required' => true,
            'type' => Number::class,
            'options' => [
                'label' => 'Number',
            ],
            'attributes' => [
                'step' => 1,
                'min' => 0,
            ],
        ]);

        $this->add([
            'name' => 'city',
            'required' => false,
            'type' => Text::class,
            'options' => [
                'label' => 'Name',
            ],
            'attributes' => [
                'minlength' => 1,
                'maxlength' => 255,
            ],
        ]);
    }
}

AddressFieldsetFactory

// other use statements
use Zend\ServiceManager\Factory\FactoryInterface;

class AddressFieldsetFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $this->setEntityManager($container->get(EntityManager::class));

        /** @var AddressFieldset $fieldset */
        $fieldset = new AddressFieldset($this->getEntityManager(), 'address');
        $fieldset->setHydrator(
            new DoctrineObject($this->getEntityManager())
        );
        $fieldset->setObject(new Address());

        return $fieldset;
    }
}

InputFilter输入

上面我们创建了Fieldset。这允许在表单中生成Fieldset。同时,Zend Framework还为每种输入类型设置了默认值(例如'type' => Text::class)。但是,如果我们想要将它验证为我们自己的,更严格的标准,我们需要覆盖默认值。为此,我们需要一个InputFilter类。

AddressFieldsetInputFilter

// other use statements
use Zend\InputFilter\InputFilter;

class AddressFieldsetInputFilter extends InputFilter
{
    public function init()
    {
        parent::init(); // called due to inheritance

        $this->add([
            'name' => 'id',
            'required' => true,
            'filters' => [
                ['name' => ToInt::class],
            ],
            'validators' => [
                ['name' => IsInt::class],
            ],
        ]);

        $this->add([
            'name' => 'street',
            'required' => true,
            'filters' => [
                ['name' => StringTrim::class], // remove whitespace before & after string
                ['name' => StripTags::class],  // remove unwanted tags 
                [                              // if received is empty string, set to 'null'
                    'name' => ToNull::class,
                    'options' => [
                        'type' => ToNull::TYPE_STRING, // also supports other types
                    ],
                ],
            ],
            'validators' => [
                [
                    'name' => StringLength::class, // set min/max string length
                    'options' => [
                        'min' => 1,
                        'max' => 255,
                    ],
                ],
            ],
        ]);

        $this->add([
            'name' => 'number',
            'required' => true,
            'filters' => [
                ['name' => ToInt::class],    // received from HTML form always string, have it cast to integer
                [
                    'name' => ToNull::class, // if received is empty string, set to 'null'
                    'options' => [
                        'type' => ToNull::TYPE_INTEGER,
                    ],
                ],
            ],
            'validators' => [
                ['name' => IsInt::class], // check if actually integer
            ],
        ]);

        $this->add([
            'name' => 'city',
            'required' => false, // <-- not required
            'filters' => [
                ['name' => StringTrim::class], // remove whitespace before & after string
                ['name' => StripTags::class],  // remove unwanted tags 
                [                              // if received is empty string, set to 'null'
                    'name' => ToNull::class,
                    'options' => [
                        'type' => ToNull::TYPE_STRING, // also supports other types
                    ],
                ],
            ],
            'validators' => [
                [
                    'name' => StringLength::class, // set min/max string length
                    'options' => [
                        'min' => 1,
                        'max' => 255,
                    ],
                ],
            ],
        ]);
    }
}

AddressFieldsetInputFilterFactory

// other use statements
use Zend\ServiceManager\Factory\FactoryInterface;

class AddressFieldsetInputFilterFactory implements FactoryInterface
{
   public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        // Nothing else required in this example. So it's as plain as can be.
        return new AddressFieldsetInputFilter();
    }
}

表格&amp;验证

因此。上面我们创建了Fieldset,它的InputFilter和2个必需的Factory类。这已经允许我们做很多事情,例如:

  • 在独立设置中使用InputFilter来动态验证对象
  • 在其他Fieldset和InputFilter类中重复使用Fieldset + InputFilter组合进行嵌套

表格

use Zend\Form\Form;
use Zend\InputFilter\InputFilterAwareInterface;
// other use statements

class AddressForm extends Form implements InputFilterAwareInterface
{
    public function init()
    {
        //Call parent initializer. Check in parent what it does.
        parent::init();

        $this->add([
            'type'    => Csrf::class,
            'name'    => 'csrf',
            'options' => [
                'csrf_options' => [
                    'timeout' => 86400, // day
                ],
            ],
        ]);

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

        $this->add([
            'name'       => 'submit',
            'type'       => Submit::class,
            'attributes' => [
                'value' => 'Save',
            ],
        ]);
    }
}

Form Factory

use Zend\ServiceManager\Factory\FactoryInterface;
// other use statements

class AddressFormFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        /** @var AbstractForm $form */
        $form = new AddressForm('address', $this->options);
        $form->setInputFilter(
            $container->get('InputFilterManager')->get(ContactFormInputFilter::class);
        );

        return $form;
    }
}

让所有人聚在一起

我只会显示AddressController#addAction

AddressController

use Zend\Mvc\Controller\AbstractActionController;
// other use statements

class AddressController extends AbstractActionController
{
    protected $addressForm;   // + getter/setter
    protected $entityManager; // + getter/setter

    public function __construct(
        EntityManager $entityManager, 
        AddressForm $form
    ) {
        $this->entityManager = $entityManager;
        $this->addressForm = $form;
    }

    // Add your own: index, view, edit and delete functions

    public function addAction () {
        /** @var AddressForm $form */
        $form = $this->getAddressForm();

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

            if ($form->isValid()) {
                $entity = $form->getObject();
                $this->getEntityManager()->persist($entity);

                try {
                    $this->getEntityManager()->flush();
                } catch (\Exception $e) {
                    $this->flashMessenger()->addErrorMessage($message);

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

                $this->flashMessenger()->addSuccessMessage(
                    'Successfully created object.'
                );

                return $this->redirect()->route($route, ['param' => 'routeParamValue']);
            }

            $this->flashMessenger()->addWarningMessage(
                'Your form contains errors. Please correct them and try again.'
            );
        }

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

AddressControllerFactory

class AddressControllerFactory implements FactoryInterface
{

    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        /** @var AddressController $controller */
        $controller = new AddressController(
            $container->get(EntityManager::class), 
            $container->get('FormElementManager')->get(AddressForm::class);
        );

        return $controller;
    }
}

在addressFormPartial中显示

$this->headTitle('Add address');

$form->prepare();
echo $this->form()->openTag($form);
echo $this->formRow($form->get('csrf'));

echo $this->formRow($form->get('address')->get('id'));
echo $this->formRow($form->get('address')->get('street'));
echo $this->formRow($form->get('address')->get('number'));
echo $this->formRow($form->get('address')->get('city'));

echo $this->formRow($form->get('submit'));
echo $this->form()->closeTag($form);

要使用此部分,请在add.phtml视图中使用:

<?= $this->partial('addressFormPartial', ['form' => $form]) ?>

这段代码将与上面的Controller代码中演示的addAction一起使用。

希望您发现这有用;-)如果您还有任何疑问,请不要犹豫。