首页 文章

ZF3 FormElementManager:如何获取ServiceManager

提问于
浏览
0

我正在将代码从ZF2移植到ZF3 .

在ZF2中,当我通过FormElementManager创建表单时,我可以访问init方法上的servicelocator并配置一些这样的东西:

public function init()
{
    $this->serviceLocator = $this->getFormFactory()->getFormElementManager()->getServiceLocator();
    $this->translator = $this->serviceLocator->get('translator');
}

这在非常大的应用中很方便 . 实际上我的所有表单都继承自BaseForm类 .

在ZF3中,这是不好的pratic和serviceLocator已被弃用 . 获得相同结果的最佳方法是哪种?一种方法是使用所需的东西注入ControllerFactory或ServiceFactory中的每个表单,但这非常繁琐 .

任何帮助都很感激 .

1 回答

  • 0

    首先,您不应该在Form对象中使用ServiceManager和/或它的子项(如FormElementManager) .

    使用Factory模式,您应该创建功能齐全的独立Form,Fieldset和InputFilter对象 .

    肯定会有一些乏味的工作,正如你所说,但你只需要做一次 .

    假设您要创建一个位置 . 位置由 name 属性和OneToOne单向 Address 引用组成 . 这创造了以下需求:

    • LocationForm(-InputFilter)

    • LocationFieldset(-InputFilter)

    • AddressFieldset(-InputFilter)

    • 以上配置

    • 上述6个 class 中的每个 class 的工厂

    在这个答案中,我将把所有内容混合到最低限度并使用我自己的存储库中的类和示例,因此对于完整代码,您可以使用here和示例here .

    在创建类本身之后,我将向您展示此用例所需的配置以及将所有这些配置在一起的工厂 .


    AbstractFieldset

    abstract class AbstractFieldset extends Fieldset
    {
        public function init()
        {
            $this->add(
                [
                    'name'     => 'id',
                    'type'     => Hidden::class,
                    'required' => false,
                ]
            );
        }
    }
    

    AbstractInputFilter

    abstract class AbstractFieldsetInputFilter extends AbstractInputFilter
    {
        public function init()
        {
            $this->add([
                'name' => 'id',
                'required' => false,
                'filters' => [
                    ['name' => ToInt::class],
                ],
                'validators' => [
                    ['name' => IsInt::class],
                ],
            ]);
        }
    }
    

    AddressFieldset

    class AddressFieldset extends AbstractFieldset
    {
        public function init()
        {
            parent::init();
    
            $this->add([
                'name' => 'street',
                'required' => true,
                'type' => Text::class,
                'options' => [
                    'label' => 'Address',
                ],
            ]);
        }
    }
    

    AddressInputFilter

    class AddressFieldsetInputFilter extends AbstractFieldsetInputFilter
    {
        public function init()
        {
            parent::init();
    
            $this->add([
                'name' => 'street',
                'required' => true,
                'filters' => [
                    ['name' => StringTrim::class],
                    ['name' => StripTags::class],
                    [
                        'name' => ToNull::class,
                        'options' => [
                            'type' => ToNull::TYPE_STRING,
                        ],
                    ],
                ],
                'validators' => [
                    [
                        'name' => StringLength::class,
                        'options' => [
                            'min' => 3,
                            'max' => 255,
                        ],
                    ],
                ],
            ]);
        }
    }
    

    到目前为止,很容易 . 现在,我们需要创建LocationFieldset和LocationFieldsetInputFilter . 这些将使用Address(-Fieldset)类 .

    LocationFieldset

    class LocationFieldset extends AbstractFieldset
    {
        public function init()
        {
            parent::init();
    
            $this->add([
                'name' => 'name',
                'required' => true,
                'type' => Text::class,
                'options' => [
                    'label' => 'Name',
                ],
            ]);
    
            $this->add([
                'type' => AddressFieldset::class,
                'name' => 'address',
                'required' => true,
                'options' => [
                    'use_as_base_fieldset' => false,
                    'label' => 'Address',
                ],
            ]);
        }
    }
    

    LocationFieldsetInputFilter

    class LocationFieldsetInputFilter extends AbstractFieldsetInputFilter
    {
        /**
         * @var AddressFieldsetInputFilter
         */
        protected $addressFieldsetInputFilter;
    
        public function __construct(AddressFieldsetInputFilter $addressFieldsetInputFilter) 
        {
            $this->addressFieldsetInputFilter = $addressFieldsetInputFilter;
        }
    
        public function init()
        {
            parent::init();
    
            $this->add($this->addressFieldsetInputFilter, 'address');
    
            $this->add(
                [
                    'name'       => 'name',
                    'required'   => true,
                    'filters'    => [
                        ['name' => StringTrim::class],
                        ['name' => StripTags::class],
                        [
                            'name'    => ToNull::class,
                            'options' => [
                                'type' => ToNull::TYPE_STRING,
                            ],
                        ],
                    ],
                    'validators' => [
                        [
                            'name'    => StringLength::class,
                            'options' => [
                                'min' => 3,
                                'max' => 255,
                            ],
                        ],
                    ],
                ]
            );
        }
    }
    

    好的,所以这还不是很令人兴奋 . 请注意,LocationFieldset使用AddressFieldset作为类型 . 相反,在InputFilter类中,需要一个完整的类对象(一个InputFilter实例) .

    所以,表格 . 我还使用AbstractForm(在您的情况下为BaseForm)来处理一些默认值 . 在我完整的(在链接回购中),还有一点,但在这里这就足够了 . 这会为表单添加CSRF保护,如果表单没有,则添加提交按钮 . 只有当您调用init时Form类没有任何一个时才能完成此操作,因此您可以覆盖这些设置 .

    AbstractForm

    abstract class AbstractForm extends \Zend\Form\Form implements InputFilterAwareInterface
    {
        protected $csrfTimeout = 900; // 15 minutes
    
        public function __construct($name = null, $options = [])
        {
            $csrfName = null;
            if (isset($options['csrfCorrector'])) {
                $csrfName = $options['csrfCorrector'];
                unset($options['csrfCorrector']);
            }
    
            parent::__construct($name, $options);
    
            if ($csrfName === null) {
                $csrfName = 'csrf';
            }
    
            $this->addElementCsrf($csrfName);
        }
    
        public function init()
        {
            if (!$this->has('submit')) {
                $this->addSubmitButton();
            }
        }
    
        public function addSubmitButton($value = 'Save', array $classes = null)
        {
            $this->add([
                'name'       => 'submit',
                'type'       => Submit::class,
                'attributes' => [
                    'value' => $value,
                    'class' => (!is_null($classes) ? join (' ', $classes) : 'btn btn-primary'),
                ],
            ]);
        }
    
        public function get($elementOrFieldset)
        {
            if ($elementOrFieldset === 'csrf') {
                // Find CSRF element
                foreach ($this->elements as $formElement) {
                    if ($formElement instanceof Csrf) {
                        return $formElement;
                    }
                }
            }
    
            return parent::get($elementOrFieldset);
        }
    
        protected function addElementCsrf($csrfName = 'csrf')
        {
            $this->add([
                'type'    => Csrf::class,
                'name'    => 'csrf',
                'options' => [
                    'csrf_options' => [
                        'timeout' => $this->csrfTimeout,
                    ],
                ],
            ]);
        }
    }
    

    LocationForm

    class LocationForm extends AbstractForm
    {
        public function init()
        {
            $this->add([
                'name' => 'location',
                'type' => LocationFieldset::class,
                'options' => [
                    'use_as_base_fieldset' => true,
                ],
            ]);
    
            parent::init();
        }
    }
    

    现在我们有了制作表格的所有内容 . 我们仍然需要验证 . 我们现在创建这些:

    AddressFieldsetInputFilter

    class AddressFieldsetInputFilter extends AbstractFieldsetInputFilter
    {
        public function init()
        {
            parent::init();
    
            $this->add([
                'name' => 'street',
                'required' => true,
                'filters' => [
                    ['name' => StringTrim::class],
                    ['name' => StripTags::class],
                    [
                        'name' => ToNull::class,
                        'options' => [
                            'type' => ToNull::TYPE_STRING,
                        ],
                    ],
                ],
                'validators' => [
                    [
                        'name' => StringLength::class,
                        'options' => [
                            'min' => 3,
                            'max' => 255,
                        ],
                    ],
                ],
            ]);
        }
    }
    

    LocationFieldsetInputFilter

    class LocationFieldsetInputFilter extends AbstractFieldsetInputFilter
    {
        protected $addressFieldsetInputFilter;
    
        public function __construct(AddressFieldsetInputFilter $addressFieldsetInputFilter) 
        {
            $this->addressFieldsetInputFilter = $addressFieldsetInputFilter;
        }
    
        public function init()
        {
            parent::init();
    
            $this->add($this->addressFieldsetInputFilter, 'address');
    
            $this->add(
                [
                    'name'       => 'name',
                    'required'   => true,
                    'filters'    => [
                        ['name' => StringTrim::class],
                        ['name' => StripTags::class],
                        [
                            'name'    => ToNull::class,
                            'options' => [
                                'type' => ToNull::TYPE_STRING,
                            ],
                        ],
                    ],
                    'validators' => [
                        [
                            'name'    => StringLength::class,
                            'options' => [
                                'min' => 3,
                                'max' => 255,
                            ],
                        ],
                    ],
                ]
            );
        }
    }
    

    LocationFormInputFilter

    class LocationFormInputFilter extends AbstractFormInputFilter
    {
        /** @var LocationFieldsetInputFilter  */
        protected $locationFieldsetInputFilter;
    
        public function __construct(LocationFieldsetInputFilter $filter) 
        {
            $this->locationFieldsetInputFilter = $filter
        }
    
        public function init()
        {
            $this->add($this->locationFieldsetInputFilter, 'location');
    
            parent::init();
        }
    }
    

    是的,这就是所有课程本身 . 你知道它们将如何嵌套在一起吗?这会创建可重用的组件,这就是为什么我说你只需要这样做一次 . 下次需要地址或位置时,只需确保加载AddressFieldset并在Factory中设置InputFilter . 设置正确的InputFilter的后者是通过Setter Injection the Factories完成的 . 如下所示 .


    AbstractFieldsetFactory

    abstract class AbstractFieldsetFactory implements FactoryInterface
    {
    
        /**
         * @var string
         */
        protected $name;
    
        /**
         * @var string
         */
        protected $fieldset;
    
        /**
         * @var string
         */
        protected $fieldsetName;
    
        /**
         * @var string
         */
        protected $fieldsetObject;
    
        public function __construct($fieldset, $name, $fieldsetObject)
        {
            $this->fieldset = $fieldset;
            $this->fieldsetName = $name;
            $this->fieldsetObject = $fieldsetObject;
    
            $this->hydrator = new Reflection(); // Replace this with your own preference, either Reflection of ZF or maybe the Doctrine EntityManager...
        }
    
        public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
        {
            $fieldset = $this->fieldset;
            $fieldsetObject = $this->fieldsetObject;
    
            /** @var AbstractFieldset $fieldset */
            $fieldset = new $fieldset($this->hydrator, $this->name ?: $this->fieldsetName);
            $fieldset->setHydrator(
                new DoctrineObject($this->hydrator)
            );
            $fieldset->setObject(new $fieldsetObject());
    
            return $fieldset;
        }
    }
    

    AddressFieldsetFactory

    class AddressFieldsetFactory extends AbstractFieldsetFactory
    {
        public function __construct()
        {
            parent::__construct(AddressFieldset::class, 'address', Address::class);
        }
    }
    

    LocationFieldsetFactory

    class LocationFieldsetFactory extends AbstractFieldsetFactory
    {
        public function __construct()
        {
            parent::__construct(LocationFieldset::class, 'location', Location::class);
        }
    }
    

    AbstractFieldsetInputFilterFactory

    abstract class AbstractFieldsetInputFilterFactory implements FactoryInterface
    {
        /**
         * @var ContainerInterface
         */
        protected $container;
    
        /**
         * @var HydratorInterface
         */
        protected $hydrator;
    
        /**
         * @var InputFilterPluginManager
         */
        protected $inputFilterManager;
    
        /**
         * Use this function to setup the basic requirements commonly reused.
         *
         * @param ContainerInterface $container
         * @param string|null $className
         * @throws \Psr\Container\ContainerExceptionInterface
         * @throws \Psr\Container\NotFoundExceptionInterface
         */
        public function setupRequirements(ContainerInterface $container, $className = null)
        {
            $this->inputFilterManager = $container->get(InputFilterPluginManager::class);
        }
    }
    

    AddressFieldsetInputFilterFactory

    class AddressFieldsetInputFilterFactory extends AbstractFieldsetInputFilterFactory
    {
        public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
        {
            parent::setupRequirements($container, Address::class);
    
            return new AddressFieldsetInputFilter($this->hydrator);
        }
    }
    

    LocationFieldsetInputFilterFactory

    class LocationFieldsetInputFilterFactory extends AbstractFieldsetInputFilterFactory
    {
        public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
        {
            parent::setupRequirements($container, Location::class);
    
            /** @var AddressFieldsetInputFilter $addressFieldsetInputFilter */
            $addressFieldsetInputFilter = $this->inputFilterManager->get(AddressFieldsetInputFilter::class);
    
            return new LocationFieldsetInputFilter(
                $addressFieldsetInputFilter,
                $this->hydrator
            );
        }
    }
    

    它负责FieldsetInputFilterFactory类 . 只是表格离开了 .

    在我的例子中,我使用与Fieldset类相同的抽象工厂类 .


    LocationFormInputFilterFactory

    class LocationFormInputFilterFactory extends AbstractFieldsetInputFilterFactory
    {
        public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
        {
            parent::setupRequirements($container);
    
            /** @var LocationFieldsetInputFilter $locationFieldsetInputFilter */
            $locationFieldsetInputFilter = $this->getInputFilterManager()->get(LocationFieldsetInputFilter::class);
    
            return new LocationFormInputFilter(
                $locationFieldsetInputFilter,
                $this->hydrator
            );
        }
    }
    

    所以,这就是所有课程 . 这是一个完整的设置 . 您可能会遇到一些错误,因为我修改了自己的代码以删除getter / setter,代码注释/提示,错误,属性和变量检查而不进行测试 . 但它应该工作;)

    但是,我们差不多完成了 . 我们还需要:

    • 配置
      控制器中的

    • 用法

    • 打印/使用视图中的表单


    配置很简单:

    'form_elements' => [
        'factories' => [
            AddressFieldset::class  => AddressFieldsetFactory::class,
            LocationFieldset::class => LocationFieldsetFactory::class,
            LocationForm::class     => LocationFormFactory::class,
        ],
    ],
    'input_filters' => [
        'factories' => [
            AddressFieldsetInputFilter::class  => AddressFieldsetInputFilterFactory::class,
            LocationFieldsetInputFilter::class => LocationFieldsetInputFilterFactory::class,
            LocationFormInputFilter::class     => LocationFormInputFilterFactory::class,
        ],
    ],
    

    而已 . 这有点将所有上述类别联系在一起 .


    要将表单转换为控制器,您可以在工厂中执行以下操作:

    class EditControllerFactory implements FactoryInterface
    {
        public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
        {
            $hydrator = new Reflection(); // or $container->get('hydrator') or $container->get(EntityManager::class), or whatever you use
    
            /** @var FormElementManagerV3Polyfill $formElementManager */
            $formElementManager = $container->get('FormElementManager');
    
            /** @var LocationForm $form */
            $form = $formElementManager->get(LocationForm::class); // See :) Easy, and re-usable
    
            return new EditController($hydrator, $form);
        }
    }
    

    一个典型的“编辑”动作就像这样(介意,这个使用Doctrine的EntityManager作为保湿器):

    public function editAction()
    {
        $id = $this->params()->fromRoute('id', null);
    
        /** @var Location $entity */
        $entity = $this->getObjectManager()->getRepository(Location::class)->find($id);
    
        /** @var LocationForm $form */
        $form = $this->form;
        $form->bind($entity);
    
        /** @var Request $request */
        $request = $this->getRequest();
        if ($request->isPost()) {
            $form->setData($request->getPost());
    
            if ($form->isValid()) {
                /** @var Location $object */
                $object = $form->getObject();
    
                $this->getObjectManager()->persist($object);
    
                try {
                    $this->getObjectManager()->flush();
                } catch (Exception $e) {
                    // exception handling
                }
    
                return $this->redirect()->toRoute('route/name', ['id' => $object->getId()]);
            }
        }
    
        return [
            'form'               => $form,
            'validationMessages' => $form->getMessages() ?: '',
        ];
    }
    

    View Partial看起来像这样(基于上面动作中的 return ):
    表格($ form)?>

    就是这样了 . 完全成熟,可重复使用的课程 . 单一设置 . 最后,控制器工厂只有一行 .

    请注意:

    • Form,Fieldset和InputFilter使用"address"输入名称 . 非常重要的是保持这些相同,因为Zend根据要匹配的名称做了一些魔术带有InputFilter的Fieldset .

    如果您对此工作原理有任何疑问,请先阅读我首先链接的回购文件中的文档,然后再询问此问题 . 还有更多可以帮助你更多,例如集合处理 .

相关问题