Вы должны привязать объект к вашей форме и использовать Doctrine Hydrator. В форме имена полей должны точно соответствовать имени сущности. Так что Entity#name
это Form#name
.
С разделением проблем я категорически против размещения InputFilter для сущности внутри самой сущности. Таким образом, я дам вам пример со всем отделенным, если вы решите смешать все вместе, это ваше дело.
AbstractEntity для идентификатора
/**
* @ORM\MappedSuperclass
*/
abstract class AbstractEntity
{
/**
* @var int
* @ORM\Id
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
// getter/setter
}
Cicade Entity
/**
* @ORM\Entity
*/
class Cidade extends AbstractEntity
{
/**
* @var string
* @ORM\Column(length=50)
*/
protected $nome; // Changed to 'protected' so can be used in child classes - if any
/**
* @var Estado
* @ORM\ManyToOne(targetEntity="Estado", cascade={"persist", "detach"}) // persist added
* @ORM\JoinColumn(name="id_estado", referencedColumnName="id")
*/
protected $estado;
// getters/setters
}
Estado Entity
/**
* @ORM\Entity
*/
class Estado extends AbstractEntity
{
/**
* @var string
* @ORM\Column(length=50)
*/
protected $nome;
//getters/setters
}
Итак, выше приведена настройка сущности для Много к одному - однонаправленное отношение.
Вы хотите управлять этим легко с помощью форм. Поэтому нам нужно создать InputFilters для обоих.
Наличие входных фильтров отдельно от сущности позволяет нам вкладывать их. Это, в свою очередь, позволяет нам создавать структурированные и вложенные формы.
Например, вы можете создать новое Estado на лету. Если бы это было двунаправленное отношение, вы могли бы создать несколько объектов Cicade Entity на лету из / во время создания Estado.
Первый: InputFilters. В духе абстракции, который вы начали с вашими сущностями, давайте сделаем это и здесь:
AbstractDoctrineInputFilter
source AbstractDoctrineInputFilter & source AbstractDoctrineFormInputFilter
Это дает хорошую чистую настройку и требование для выполнения. Я закрываю глаза на более сложные элементы, добавленные в исходные файлы, но не стесняйтесь их искать.
Для обоих объектов (Estado и Cicade) требуется ObjectManager (в конце концов, это сущности Doctrine), поэтому я предполагаю, что у вас может быть больше. Ниже должно пригодиться.
<?php
namespace Application\InputFilter;
use Doctrine\Common\Persistence\ObjectManager;
use Zend\InputFilter\InputFilter;
abstract class AbstractInputFilter extends InputFilter
{
/**
* @var ObjectManager
*/
protected $objectManager;
/**
* AbstractFormInputFilter constructor.
*
* @param array $options
*/
public function __construct(array $options)
{
// Check if ObjectManager|EntityManager for FormInputFilter is set
if (isset($options['object_manager']) && $options['object_manager'] instanceof ObjectManager) {
$this->setObjectManager($options['object_manager']);
}
}
/**
* Init function
*/
public function init()
{
$this->add(
[
'name' => 'id',
'required' => false, // Not required when adding - should also be in route when editing and bound in controller, so just additional
'filters' => [
['name' => ToInt::class],
],
'validators' => [
['name' => IsInt::class],
],
]
);
// If CSRF validation has not been added, add it here
if ( ! $this->has('csrf')) {
$this->add(
[
'name' => 'csrf',
'required' => true,
'filters' => [],
'validators' => [
['name' => Csrf::class],
],
]
);
}
}
// getters/setters for ObjectManager
}
Estado InputFilter
class EstadoInputFilter extends AbstractInputFilter
{
public function init()
{
parent::init();
$this->add(
[
'name' => 'nome', // <-- important, name matches entity property
'required' => true,
'allow_empty' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
[
'name' => ToNull::class,
'options' => [
'type' => ToNull::TYPE_STRING,
],
],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 2,
'max' => 255,
],
],
],
]
);
}
}
Cicade InputFilter
class EstadoInputFilter extends AbstractInputFilter
{
public function init()
{
parent::init(); // Adds the CSRF
$this->add(
[
'name' => 'nome', // <-- important, name matches entity property
'required' => true,
'allow_empty' => true,
'filters' => [
['name' => StringTrim::class],
['name' => StripTags::class],
[
'name' => ToNull::class,
'options' => [
'type' => ToNull::TYPE_STRING,
],
],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'min' => 2,
'max' => 255,
],
],
],
]
);
$this->add(
[
'name' => 'estado',
'required' => true,
]
);
}
}
Итак. Теперь у нас есть 2 InputFilter, основанных на AbstractInputFilter.
EstadoInputFilter
фильтрует только свойство nome
. Добавьте дополнительные, если хотите;)
CicadeInputFilter
фильтрует свойство nome
и имеет обязательное поле estado
.
Имена соответствуют именам определения сущности в соответствующих классах сущности.
Просто чтобы завершить, ниже CicadeForm
, возьмите то, что вам нужно для создания EstadoForm
.
class CicadeForm extends Form
{
/**
* @var ObjectManager
*/
protected $objectManager;
/**
* AbstractFieldset constructor.
*
* @param ObjectManager $objectManager
* @param string $name Lower case short class name
* @param array $options
*/
public function __construct(ObjectManager $objectManager, string $name, array $options = [])
{
parent::__construct($name, $options);
$this->setObjectManager($objectManager);
}
public function init()
{
$this->add(
[
'name' => 'nome',
'required' => true,
'type' => Text::class,
'options' => [
'label' => _('Nome',
],
]
);
// @link: https://github.com/doctrine/DoctrineModule/blob/master/docs/form-element.md
$this->add(
[
'type' => ObjectSelect::class,
'required' => true,
'name' => 'estado',
'options' => [
'object_manager' => $this->getObjectManager(),
'target_class' => Estado::class,
'property' => 'id',
'display_empty_item' => true,
'empty_item_label' => '---',
'label' => _('Estado'),
'label_attributes' => [
'title' => _('Estado'),
],
'label_generator' => function ($targetEntity) {
/** @var Estado $targetEntity */
return $targetEntity->getNome();
},
],
]
);
//Call parent initializer. Check in parent what it does.
parent::init();
}
/**
* @return ObjectManager
*/
public function getObjectManager() : ObjectManager
{
return $this->objectManager;
}
/**
* @param ObjectManager $objectManager
*
* @return AbstractDoctrineFieldset
*/
public function setObjectManager(ObjectManager $objectManager) : AbstractDoctrineFieldset
{
$this->objectManager = $objectManager;
return $this;
}
}
Config
Теперь, когда есть классы, как их использовать? Бросьте их вместе с конфигурацией модуля!
В ваш файл module.config.php
добавьте эту конфигурацию:
'form_elements' => [
'factories' => [
CicadeForm::class => CicadeFormFactory::class,
EstadoForm::class => EstadoFormFactory::class,
// If you create separate Fieldset classes, this is where you register those
],
],
'input_filters' => [
'factories' => [
CicadeInputFilter::class => CicadeInputFilterFactory::class,
EstadoInputFilter::class => EstadoInputFilterFactory::class,
// If you register Fieldsets in form_elements, their InputFilter counterparts go here
],
],
Из этой конфигурации мы читаем, что нам нужна фабрика как для формы, так и для InputFilter набора.
Ниже CicadeInputFilterFactory
class CicadeInputFilterFactory implements FactoryInterface
{
/**
* @param ContainerInterface $container
* @param string $requestedName
* @param array|null $options
*
* @return CicadeInputFilter
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
/** @var ObjectManager|EntityManager $objectManager */
$objectManager = $this->setObjectManager($container->get(EntityManager::class));
return new CicadeInputFilter(
[
'object_manager' => objectManager,
]
);
}
}
Соответствует CicadeFormFactory
class CicadeFormFactory implements FactoryInterface
{
/**
* @param ContainerInterface $container
* @param string $requestedName
* @param array|null $options
*
* @return CicadeForm
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null) : CicadeForm
{
$inputFilter = $container->get('InputFilterManager')->get(CicadeInputFilter::class);
// Here we creazte a new Form object. We set the InputFilter we created earlier and we set the DoctrineHydrator. This hydrator can work with Doctrine Entities and relations, so long as data is properly formatted when it comes in from front-end.
$form = $container->get(CicadeForm::class);
$form->setInputFilter($inputFilter);
$form->setHydrator(
new DoctrineObject($container->get(EntityManager::class))
);
$form->setObject(new Cicade());
return $form;
}
}
Массовая подготовка сделана, время ее использовать
Определенный EditController
для редактирования существующего Cicade
Объекта
class EditController extends AbstractActionController // (Zend's AAC)
{
/**
* @var CicadeForm
*/
protected $cicadeForm;
/**
* @var ObjectManager|EntityManager
*/
protected $objectManager;
public function __construct(
ObjectManager $objectManager,
CicadeForm $cicadeForm
) {
$this->setObjectManager($objectManager);
$this->setCicadeForm($cicadeForm);
}
/**
* @return array|Response
* @throws ORMException|Exception
*/
public function editAction()
{
$id = $this->params()->fromRoute('id', null);
if (is_null($id)) {
$this->redirect()->toRoute('home'); // Do something more useful instead of this, like notify of id received from route
}
/** @var Cicade $entity */
$entity = $this->getObjectManager()->getRepository(Cicade::class)->find($id);
if (is_null($entity)) {
$this->redirect()->toRoute('home'); // Do something more useful instead of this, like notify of not found entity
}
/** @var CicadeForm $form */
$form = $this->getCicadeForm();
$form->bind($entity); // <-- This here is magic. Because we overwrite the object from the Factory with an existing one. This pre-populates the form with value and allows us to modify existing one. Assumes we got an entity above.
/** @var Request $request */
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
/** @var Cicade $cicade */
$cicade = $form->getObject();
$this->getObjectManager()->persist($cicade);
try {
$this->getObjectManager()->flush();
} catch (Exception $e) {
throw new Exception('Could not save. Error was thrown, details: ', $e->getMessage());
}
$this->redirect()->toRoute('cicade/view', ['id' => $entity->getId()]);
}
}
return [
'form' => $form,
'validationMessages' => $form->getMessages() ?: '',
];
}
/**
* @return CicadeForm
*/
public function getCicadeForm() : CicadeForm
{
return $this->cicadeForm;
}
/**
* @param CicadeForm $cicadeForm
*
* @return EditController
*/
public function setCicadeForm(CicadeForm $cicadeForm) : EditController
{
$this->cicadeForm = $cicadeForm;
return $this;
}
/**
* @return ObjectManager|EntityManager
*/
public function getObjectManager() : ObjectManager
{
return $this->objectManager;
}
/**
* @param ObjectManager|EntityManager $objectManager
*
* @return EditController
*/
public function setObjectManager(ObjectManager $objectManager) : EditController
{
$this->objectManager = $objectManager;
return $this;
}
}
Итак, хотелось дать действительно расширенный ответ. Охватывает все это на самом деле.
Если у вас есть какие-либо вопросы по поводу вышесказанного, дайте мне знать ;-)