I'm using Zend Framework 3 with Doctrine and I'm trying to save an Entity "Cidade" related to another Entity "Estado" witch is already stored in the database. However, Doctrine is trying to persist Entity "Estado", and the only attribute I have from Estado is the primary key in a HTML combo.
My view forms are built under Zend forms and fieldsets, which means POST data is automatically converted to the target entities using ClassMethods hydrator.
The problem is that if I set the attribute $estado
with cascade={"persist"}
in Cidade Entity, Doctrine tries to persist Estado Entity missing all required attributes but the primary key ID, which comes from POST request (HTML combo). I also considered using cascade={"detach"}
ir order to Doctrine ignore Estado Entity in the EntityManager. But I get this error:
A new entity was found through the relationship 'Application\Entity\Cidade#estado' that was not configured to cascade persist operations for entity: Application\Entity\Estado@000000007598ee720000000027904e61.
I found a similar doubt here and the only way I could find for this matter was first retrieving Estado Entity and setting it on Cidade Entity before saving. If this is the only way, can I tell my form structure won't work unless I retrieve all relationships before saving the dependant entities? In other words, what is the best way of doing such thing in Doctrine (for example):
<?php
/*I'm simulating the creation of Estado Entity representing an
existing Estado in database, so "3" is the ID rendered in HTML combo*/
$estado = new Entity\Estado();
$estado->setId(3);
$cidade = new Entity\Cidade();
$cidade->setNome("City Test");
$cidade->setEstado($estado); //relationship here
$entityManager->persist($cidade);
$entityManager->flush();
How to do that without having to retrieve an Estado all the time I need to save a Cidade? Wouldn't affect performance?
My Cidade Entity:
<?php
namespace Application\Entity;
use Zend\InputFilter\Factory;
use Zend\InputFilter\InputFilterInterface;
use Doctrine\ORM\Mapping as ORM;
/**
* Class Cidade
* @package Application\Entity
* @ORM\Entity
*/
class Cidade extends AbstractEntity
{
/**
* @var string
* @ORM\Column(length=50)
*/
private $nome;
/**
* @var Estado
* @ORM\ManyToOne(targetEntity="Estado", cascade={"detach"})
* @ORM\JoinColumn(name="id_estado", referencedColumnName="id")
*/
private $estado;
/**
* Retrieve input filter
*
* @return InputFilterInterface
*/
public function getInputFilter()
{
if (!$this->inputFilter) {
$factory = new Factory();
$this->inputFilter = $factory->createInputFilter([
"nome" => ["required" => true]
]);
}
return $this->inputFilter;
}
/**
* @return string
*/
public function getNome()
{
return $this->nome;
}
/**
* @param string $nome
*/
public function setNome($nome)
{
$this->nome = $nome;
}
/**
* @return Estado
*/
public function getEstado()
{
return $this->estado;
}
/**
* @param Estado $estado
*/
public function setEstado($estado)
{
$this->estado = $estado;
}
}
My Estado Entity:
<?php
namespace Application\Entity;
use Doctrine\ORM\Mapping as ORM;
use Zend\InputFilter\Factory;
use Zend\InputFilter\InputFilterInterface;
/**
* Class Estado
* @package Application\Entity
* @ORM\Entity
*/
class Estado extends AbstractEntity
{
/**
* @var string
* @ORM\Column(length=50)
*/
private $nome;
/**
* @var string
* @ORM\Column(length=3)
*/
private $sigla;
/**
* @return string
*/
public function getNome()
{
return $this->nome;
}
/**
* @param string $nome
*/
public function setNome($nome)
{
$this->nome = $nome;
}
/**
* @return string
*/
public function getSigla()
{
return $this->sigla;
}
/**
* @param string $sigla
*/
public function setSigla($sigla)
{
$this->sigla = $sigla;
}
/**
* Retrieve input filter
*
* @return InputFilterInterface
*/
public function getInputFilter()
{
if (!$this->inputFilter) {
$factory = new Factory();
$this->inputFilter = $factory->createInputFilter([
"nome" => ["required" => true],
"sigla" => ["required" => true]
]);
}
return $this->inputFilter;
}
}
Both entities extend my superclass AbstractEntity:
<?php
namespace Application\Entity;
use Doctrine\ORM\Mapping\MappedSuperclass;
use Doctrine\ORM\Mapping as ORM;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
/**
* Class AbstractEntity
* @package Application\Entity
* @MappedSuperClass
*/
abstract class AbstractEntity implements InputFilterAwareInterface
{
/**
* @var int
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
/**
* @var InputFilterAwareInterface
*/
protected $inputFilter;
/**
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @param int $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* @param InputFilterInterface $inputFilter
* @return InputFilterAwareInterface
* @throws \Exception
*/
public function setInputFilter(InputFilterInterface $inputFilter)
{
throw new \Exception("Método não utilizado");
}
}
My HTML inputs are rendered as it follows:
<input name="cidade[nome]" class="form-control" value="" type="text">
<select name="cidade[estado][id]" class="form-control">
<option value="3">Bahia</option>
<option value="2">Espírito Santo</option>
<option value="1">Minas Gerais</option>
<option value="9">Pará</option>
</select>
Each option
above is an Estado Entity retrieved from database. My POST data comes as the following example:
[
"cidade" => [
"nome" => "Test",
"estado" => [
"id" => 3
]
]
]
On Zend Form's isValid()
method, this POST data is automatically converted to the target Entities, which makes me crash on this Doctrine issue. How do I move on?