簡體   English   中英

如何將InputFilter驗證器應用於ZF3中的字段集元素

[英]How to apply InputFilter validators to fieldset elements in ZF3

我的表格有兩個字段。 一個帶有驗證器的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()來將字段集類相應地更新為@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());
    }
}

只需對您的InputFilterProviderInterface使用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方法調用上執行。

之前嘗試過答案,但用完了字符數(上限為30k),因此創建了一個repo 回購包含下面答案的抽象,這是一個有效的示例。

您的問題表明您有正確的想法,但尚未實現。 它還包含一些錯誤,例如為Fieldset名稱設置FQCN。 希望以下內容可以幫助您正常運行。

作為用例,我們將有一個基本的“地址”表單。 國家/地區,時區和其他事物的關系我將不在討論范圍之內。 有關Fieldset的更多信息和嵌套(也包括Collections),我將向您介紹我的存儲庫。


常規設置

首先創建基本設置。 創建實體和配置。

基本實體

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)類。 這是因為它包含我們將要處理的實際對象。

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

輸入過濾器

在上方,我們創建了字段集。 這樣就可以在表單中生成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();
    }
}

表格和驗證

所以。 上面我們創建了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() ?: '',
        ];
    }
}

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一起使用。


希望對您有所幫助;-)如果您還有任何疑問,請隨時提出。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM