ZF2 - 文本元素,数组名称,验证

时间:2018-02-07 09:37:08

标签: forms validation zend-framework2

我已经在ZF2中阅读了一些关于表单集合/字段集的内容,因为我想在表单中添加一些数组字段。 但是据我所知 - fieldsets / collections是在考虑域实体的情况下构建的。在我的情况下,这个解决方案似乎并没有接近好主意。

我的表单与任何实体无关,它只是将params传递给pdf生成器。这些参数被序列化并保存在db。中的一列中。

表单本身很大,有很多字段,如:

Question:
a) smth
b) smth
c) smth
d) other
   a) other 1 
   b) other 2 
   ...
   x) other 22

因此,我有多个元素可供用户额外输入,必须动态添加。

为包含... 1个额外字段的字段集构建一个单独的类,每个字段将导致至少20个额外的类。

我以为我可以简单地通过:

$element = new Element\Text('citiesOther[]');
$element->setAttributes(array(
    'id' => 'citiesOther',
    'placeholder' => 'Other city',
));
$this->add($element);

//view:
$this->formElement( $form->get( 'citiesOther[]' )

然后在前端有一个按钮"添加另一个"这只是克隆整个输入。它工作得很好,我可以从帖子中收到参数。

事情是......我无法过滤/验证这些字段。如果我传了一个名字" citiesOther []"对于inputFilter,它甚至没有验证其中一个字段,甚至没有提到整个数组。

1 个答案:

答案 0 :(得分:2)

Zend Framework中的集合有点难以理解。基本上,您需要一个集合的基本字段集。此字段集实现InputFilterProviderInterface以进行过滤和验证。考虑到这一点,您必须定义所有可重复输入字段以及集合中此字段的验证。不验证此数据不是一个好主意。在pdf中注入非常糟糕的数据有几种情况。因此,请验证来自表单的所有数据。总是!

以下是Zend Framework集合的快速示例。

namespace Application\Form;

use Zend\Form\Fieldset;
use Zend\Form\Element\Text;
use Zend\InputFilter\InputFilterProviderInterface;

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

    public function getInputFilterSpecification()
    {
        return [
            'simple' => [
                'required' => true,
                'filters' => [
                    ...
                ],
                'validators' => [
                    ...
                ],
            ],
        ];
    }
}

这是基本的fieldset类,它充当您希望在表单中重复的所有输入字段的集合。此基本字段集通过实现InputFilterProviderInterface来过滤和验证自身。通过使用实现的方法,您可以为输入放置过滤器和验证器 如果您有依赖输入字段,则必须在此字段集中定义它们。

要正确使用它,您需要一个实体,该实体将在本文后面的基本字段集中绑定。接下来,我们为此字段集创建实体。

namespace Application\Entity;

class YourCollectionEntity
{
    protected $simple;

    public function getSimple()
    {
        return $this->simple;
    }

    public function setSimple($simple)
    {
        $this->simple = $simple;
        return $this;
    }
}

这是一个非常简单的实体。这将作为您的数据持有者,并将在以后绑定到该集合。接下来我们需要一个fieldset,它包含集合和集合计数的隐藏字段。使用另一个字段集听起来有点复杂。但在我看来,这是最好的方式。

namespace Application\Form;

class YourCollectionFieldset extends Fieldset implements InputFilterProviderInterface
{
    public function init()
    {
        $this->add([
            'name' => 'collectionCounter',
            'type' => Hidden::class,
            'attributes' => [
                ...
            ],
        ]);

        // add the collection fieldset here
        $this->add([
            'name' => 'yourCollection',
            'type' => Collection::class,
            'options' => [
                'count' => 1, // initial count
                'should_create_template' => true,
                'template_placeholder' => '__index__',
                'allow_add' => true,
                'allow_remove' => true,
                'target_element' => [
                    'type' => YourCollection::class,
                ],
            ],
        ]);
    }

    public function getInputFilterSpecification()
    {
        return [
            'collectionCounter' => [
                'required' => true,
                'filters' => [
                    [
                        'name' => ToInt::class,
                    ],
                ],
            ],
        ];
    }
}

此字段集实现您的集合和隐藏字段,该字段充当您的集合的计数器。当您想要处理集合时,您需要两者。案例可能是编辑收集内容。然后你必须设置集合的数量。

您可以想象,您也需要此字段集的实体类。我们来写一个。

namespace YourCollectionFieldsetEntity
{
    protected $collectionCounter;

    protected $yourCollection;

    public function getCollectionCounter()
    {
        return $this->collectionCounter;
    }

    public function setCollectionCounter($collectionCounter)
    {
        $this->collectionCounter = $collectionCounter;
        return $this;
    }

    public function getYourCollection()
    {
        return $this->yourCollection;
    }

    public function setYourCollection(YourCollectionEntity $yourCollection)
    {
        $this->yourCollection = $yourCollection;
        return $this;
    }
}

此实体包含setYourCollection方法,该方法将YourCollectionEntity实例作为参数。我们稍后会看到,我们将如何做那个小怪物。

我们把它包装在工厂里。

namespace Application\Form\Service;

YourCollectionFactory
{
    public function __invoke(ContainerInterface $container)
    {
        $entity = new YourCollectionFieldsetEntity();
        $hydrator = (new ClassMethods(false))
            ->addStrategy('yourCollection', new YourCollectionStrategy());

        $fieldset = (new YourCollectionFieldset())
            ->setHydrator($hydrator)
            ->setObject($entity);

        return $fieldset;
    } 
}

该工厂为保湿剂添加了保湿策略。这将水合绑定到可重复字段集的实体。

如何在表单中使用它?

以上显示的类仅适用于所需的集合。该集合本身尚未形成。我们假设我们有一个简单的形式,它实现了这个集合。

namespace Application\Form;

class YourForm extends Form
{
    public function __construct($name = null, array $options = [])
    {
        parent::__construct($name, $options);

        // add a few more input fields here

        // add your collection fieldset
        $this->add([
            'name' => 'fancy_collection',
            'type' => YourCollectionFieldset::class,
        ]);

        // add other simple input fields here
    }
}

嗯,这是一个简单的表单类,它使用上面显示的集合。正如您所写,您需要一个验证器来验证此表单包含的所有数据。没有实体的形式验证器。首先,我们需要一个这种形式的实体类。

namespace Application\Entity;

class YourFormEntity
{
    // possible other form members
    ...

    protected $fancyCollection;

    public function getFancyCollection()
    {
        return $this->fancyCollection; 
    }

    public function setFancyCollection(YourCollectionFieldsetEntity $fancyCollection)
    {
         $this->fancyCollection = $fancyCollection;
         return $this;
    }

    // other getters and setters for possible other form fields
}

...最后是表单的验证器类。

namespace Application\InputFilter;

class YourFormInputFilter extends InputFilter
{
    // add other definitions for other form fields here
    ...
}

}

我们无需在此处为您的收藏重新定义过滤器和验证器。请记住,当执行包含集合的表单的验证器时,集合本身实现了自动执行的InputFilterProviderInterface。

让我们把它包装在工厂里

namespace Application\Form\Service;

class YourFormFactory
{
    public function __invoke(ContainerInterface $container)
    {
         $container = $container->getServiceLocator();

         $entity = new YourFormEntity();
         $filter = new YourFormInputFilter();
         $hydrator = (new ClassMethods(false))
             ->addStrategy('fancy_collection', new FancyCollectionStrategy());

         $form = (new YourForm())
             ->setHydrator($hydrator)
             ->setObject($entity)
             ->setInputFilter($filter);

         return $form;
    }
}

就是这样。这是包含您的收藏的表单。

保湿策略是你的朋友

以上两种保湿剂策略被添加到您的表格所需的保湿剂中。如果你有复杂的后期数据,那么在水化器中添加水化器策略的工作就像魅力一样,这是一种很好的结果。

namespace Application\Hydrator\Strategy;

class YourCollectionStrategy extends DefaultStrategy
{
    public function hydrate($value)
    {
        $entities = [];
        if (is_array($value)) {
            foreach ($value as $key => $data) {
                $entities[] = (new ClassMethods())->hydrate($data, new YourCollectionEntity());
            }
        }

        return $entities;
    }
}

这种保湿策略将为重复收集字段集四个实体提供水合作用。下一个策略将整个集合数据融合到您的表单实体中。

namespace Application\Hydrator\Strategy;

class FancyCollectionStrategy extends DefaultStrategy
{
    public function hydrate($value)
    {
        return (new ClassMethods())
            ->addStrategy('yourCollection', new YourCollectionFieldsetEntity())
            ->hydrate($value);
    }
}

这个将水合收集计数和重复的字段集。这就是数据水化的全部内容。

在控制器中看起来如何?

嗯,这很简单。现在,因为我们拥有了一个带有集合的复杂形式所需的所有类,所以我们可以继续使用控制器。

namespace Application\Controller;

class YourController extends AbstractActionController
{
    protected $form;

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

    public function indexAction()
    { 
        $request = $this->getRequest();

        if ($request->isPost()) {
            $this->form->setData($oRequest->getPost());

            if ($this->form->isValid()) {
                 // get all data as entity
                 $data = $this->form->getData();

                 // iterate through all collection data
                 foreach ($data->getFancyCollection()->getYourCollection() as $collection) {
                      // get every simple field from every collection
                      echo $collection->getSimple();
                 }
            }
        }
    }
}

当然,这比复制原始帖子数据更复杂。但如前所述,由于安全原因,您不应使用原始数据。始终验证和过滤用户在表单上提供的数据。或者只是为了保持简单:不要相信用户!