0

我有一个有两个字段的表格。一个带有验证器的 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],
            ],
        ]);
    }
}

测试类字段集

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',
            ],
        ]);
    }
}

测试控制器类

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]);
    }
}

测试控制器工厂类

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

更新

我相应地更新了字段集类,以添加 @Nukeface 建议setInputFilter()。但它没有奏效。它甚至没有执行 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());
    }
}
4

2 回答 2

1

之前尝试过一个答案并且用完了字符(30k 限制),所以创建了一个 repo 代替。回购包含以下答案的抽象,这是一个工作示例。

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

作为一个用例,我们将有一个基本的地址表单。国家、时区和其他事物的关系我将不在讨论范围内。有关 Fieldsets 的更深入和嵌套(也包括 Collections),请参阅我的 repo。


一般设置

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

基本实体

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,你可能想把它留到最后,因为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)类。这是因为它包含我们要处理的实际对象。

地址字段集

// 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,
            ],
        ]);
    }
}

地址字段集工厂

// 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;
    }
}

输入过滤器

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

地址字段集输入过滤器

// 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();
    }
}

表格和验证

所以。上面我们创建了 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',
            ],
        ]);
    }
}

表单工厂

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

地址控制器

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() ?: '',
        ];
    }
}

地址控制器工厂

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]) ?>

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


希望这对您有所帮助;-) 如果您还有任何问题,请随时提出。

于 2018-04-20T09:21:29.817 回答
0

只需将InputFilterProviderInterface类用于您的字段集。这将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表单的方法调用上执行。

于 2018-03-20T12:23:37.203 回答