我正在将代码从ZF2移植到ZF3。
在ZF2中,当我通过FormElementManager创建表单时,我可以访问init方法上的服务定位器,并配置如下内容:
public function init()
{
$this->serviceLocator = $this->getFormFactory()->getFormElementManager()->getServiceLocator();
$this->translator = $this->serviceLocator->get('translator');
}这在非常大的应用程序中很方便。实际上,我所有的表单都继承自一个BaseForm类。
在ZF3中,这是不好的,pratic和serviceLocator被弃用。获得相同结果的最佳方法是什么?一种方法是向ControllerFactory或ServiceFactory中的每个表单注入所需的内容,但这非常繁琐。
任何帮助都是非常感谢的。
发布于 2018-12-10 17:51:14
首先,您不应该在表单对象中提供ServiceManager和/或其子对象(如FormElementManager)。
使用工厂模式,您应该创建功能齐全的独立表单、字段集和InputFilter对象。
正如你所说,肯定会有一些乏味的工作,但你只需要做一次。
假设您想要创建一个位置。位置由name属性和OneToOne单向Address引用组成。这就产生了以下需求:
用于above
上的6个类中的每一个
在这个答案中,我将把所有东西混合到最低限度,并使用我自己的存储库中的类和示例,因此对于完整的代码,您可以转到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)来处理一些默认值。在我的完整版本中(在链接的repo中),还有更多,但对于这里来说,这已经足够了。这将为表单添加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并在工厂中设置InputFilter即可。后者,设置正确的InputFilter,是通过Setter注入工厂完成的。如下所示。
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、代码注释/提示、错误、属性和变量检查而不进行测试时,您可能会遇到一些错误。但它应该是有效的;)
然而,我们几乎完成了。我们仍然需要:
视图中Controller
配置很简单:
'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,
],
],就这样。这一点将上面所有的类联系在一起。
要将表单放入控制器中,您可以在Factory中执行类似以下操作:
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() ?: '',
];
}视图部分将如下所示(基于上述操作中的return ):
表单($form) ?>
所以,就是这样。完全成熟的、可重用的类。单一设置。最后,在工厂中只有一行代码用于控制器。
不过,请注意:
如果您对此功能有任何疑问,请先阅读我链接的repo's中的文档,然后再问下面的问题。这里有更多的东西可以帮助你,比如集合处理。
https://stackoverflow.com/questions/53696899
复制相似问题