OneToMany or OneToOne, am I on the right or wrong path?
I have this DB model:
Then I created these objects (I'll just leave the relationship part as the other is not relevant):
Orders.php
class Orders {
/**
* @ORM\ManyToOne(targetEntity="Person", inversedBy="orders")
* @ORM\JoinColumn(name="person_id", referencedColumnName="id")
* */
protected $person;
public function setPerson(Person $person)
{
$this->person = $person;
return $this;
}
public function getPerson()
{
return $this->person;
}
}
Person.php
class Person {
/**
* @ORM\OneToMany(targetEntity="NaturalPerson", mappedBy="person")
* */
private $naturals;
/**
* @ORM\OneToMany(targetEntity="LegalPerson", mappedBy="person")
* */
private $legals;
/**
* @ORM\OneToMany(targetEntity="Orders", mappedBy="person")
* */
private $orders;
public function __construct()
{
$this->naturals = new ArrayCollection();
$this->legals = new ArrayCollection();
$this->orders = new ArrayCollection();
}
public function getNaturals()
{
return $this->naturals;
}
public function getLegals()
{
return $this->legals;
}
public function getOrders()
{
return $this->orders;
}
}
NaturalPerson.php
class NaturalPerson {
/**
* @ORM\Id
* @ORM\ManyToOne(targetEntity="Person", inversedBy="naturals")
* @ORM\JoinColumn(name="person_id", referencedColumnName="id")
*/
protected $person;
/**
* @ORM\Column(name="identification_type", type="ci_type", nullable=false)
* @DoctrineAssert\Enum(entity="Tanane\FrontendBundle\DBAL\Types\CIType")
*/
protected $identification_type;
/**
* @ORM\Column(name="ci", type="integer", nullable=false)
*/
protected $ci;
public function setPerson(Person $person)
{
$this->person = $person;
return $this;
}
public function getPerson()
{
return $this->person;
}
public function setIdentificationType($identification_type)
{
$this->identification_type = $identification_type;
return $this;
}
public function getIdentificationType()
{
return $this->identification_type;
}
public function setCI($ci)
{
$this->ci = $ci;
return $this;
}
public function getCI()
{
return $this->ci;
}
}
I missed LegalPerson
since it is almost the same as NaturalPerson
here is the problem. The display looks good, but how do I get related posts from Orders
?
The idea behind this is for everyone Orders
I need to know to which Person
(Orders) belongs , as well as additional information stored in NaturalPerson
or LegalPerson
depending on person.type
.
See this code:
public function getOrdersAction()
{
$response = array();
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository("FrontendBundle:Orders")->findAll();
if (!$entities)
{
$response['message'] = "No se encontraron resultados";
}
$orders = array();
foreach ($entities as $entity)
{
$personType = $entity->getPerson()->getPersonType();
$order = array();
$order[] = $entity->getNickname();
// Here I'm trying to access to `Naturals` methods from `Orders`
if ($personType == 1)
{
$order[] = $entity->getPerson()->getNaturals()[0]->getIdentificationType() . $entity->getPerson()->getNaturals()[0]->getCI();
}
elseif ($personType == 2)
{
$order[] = $entity->getPerson()->getLegals()[0]->getIdentificationType() . $entity->getPerson()->getLegals()[0]->getRIF();
}
$orders[] = $order;
}
$response['data'] = $orders;
return new JsonResponse($response);
}
But I am getting this error:
Error: Calling member function getIdentificationType () on a non-object in /var/www/html/tanane/src/Tanane/BackendBundle/Controller/OrderController.php line 115
Maybe my mapping is wrong, since I must have OneToOne
between Person
and NaturalPerson
(and it doesn't sound right to my logic, as DER shows) or maybe not, but then I don't know how to get bound properties for just one entry, I read the docs here and also in here , but they didn't talk about this part, or I don't see it, any advice? ideas? tips?
Trying to use repositories and DQL to solve the problem
I am creating a function in a class Repository
to retrieve the data and it doesn't get complicated as apparently my problem, so I did this:
public function getOrders($person_type = 1)
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb
->select('ord.*, ps.*')
->from("FrontendBundle:Orders", "ord")
->join('FrontendBUndle:Person', 'ps', 'WITH', 'ps.id = ord.person_id')
->orderBy('ord.created', 'DESC');
if ($person_type == 1)
{
$qb
->select('np.*')
->join('FrontendBundle:NaturalPerson', 'np', 'WITH', 'ps.id = np.person'); // Join NaturalPerson table
}
elseif ($person_type == 2)
{
$qb
->select('lp.*')
->join('FrontendBundle:LegalPerson', 'lp', 'WITH', 'ps.id = lp.person'); // Join NaturalPerson table
}
return $qb->getQuery()->getResult();
}
I haven't tested it yet, so maybe it won't work, but if the idea is to get more information for both tables, then with this DQL I did how to pass $person_type
which is inside the Person
Table? This gets a little tricky, at least for me.
Running a raw query to see if columns are NULL
I am creating this simple query just to check if the results are NULL
:
SELECT
ord.id,
ord.person_id as ord_person_id,
ord.nickname,
ps.id,
ps.description,
np.person_id as natural_person_id,
np.identification_type,
np.ci
FROM
orders ord
LEFT JOIN person ps ON ord.person_id = ps.id
LEFT JOIN natural_person np ON np.person_id = ps.id
WHERE
ps.person_type = 1;
And this query returns:
So there are no NULL columns here.
CRUD for creating new orders
// Set Person entity
$entityPerson = new Person();
$person_type === 1 ? $entityPerson->setDescription($orders['nat']['person']['description']) : $entityPerson->setDescription($orders['leg']['person']['description']);
$person_type === 1 ? $entityPerson->setContactPerson($orders['nat']['person']['contact_person']) : $entityPerson->setContactPerson($orders['leg']['person']['contact_person']);
$entityPerson->setPersonType($person_type);
$em->persist($entityPerson);
$em->flush();
...
if ($person_type === 1)
{
// Set NaturalPerson entity
$entityNatural = new NaturalPerson();
$entityNatural->setIdentificationType($orders['nat']['identification_type']);
$entityNatural->setCI($orders['nat']['ci']);
$em->persist($entityNatural);
$em->flush();
}
elseif ($person_type === 2)
{
// Set LegalPerson entity
$entityLegal = new LegalPerson();
$entityLegal->setIdentificationType($orders['leg']['identification_type']);
$entityLegal->setRIF($orders['leg']['rif']);
$em->persist($entityLegal);
$em->flush();
}
Since LegalPerson
and NaturalPerson
are specializations Person
, I would recommend using something that causes Doctrine Class Inheritance table ( documentation ).
You will have:
Person.php
/**
* @ORM\Table(name="person")
* @ORM\Entity
* @ORM\InheritanceType("JOINED")
* @ORM\DiscriminatorColumn(name="discr", type="string")
* @ORM\DiscriminatorMap({
* "natural" = "NaturalPerson",
* "legal" = "LegalPerson",
* })
*/
class Person {
/**
* @ORM\OneToMany(targetEntity="Orders", mappedBy="person")
* */
private $orders;
public function __construct()
{
$this->orders = new ArrayCollection();
}
public function getOrders()
{
return $this->orders;
}
}
NaturalPerson.php
/**
* @ORM\Table(name="natural_person")
* @ORM\Entity
*/
class NaturalPerson extends Person {
/**
* @ORM\Column(name="identification_type", type="ci_type", nullable=false)
* @DoctrineAssert\Enum(entity="Tanane\FrontendBundle\DBAL\Types\CIType")
*/
protected $identification_type;
/**
* @ORM\Column(name="ci", type="integer", nullable=false)
*/
protected $ci;
public function setIdentificationType($identification_type)
{
$this->identification_type = $identification_type;
return $this;
}
public function getIdentificationType()
{
return $this->identification_type;
}
public function setCI($ci)
{
$this->ci = $ci;
return $this;
}
public function getCI()
{
return $this->ci;
}
}
Order.php
remains unchanged.
As you can see now NaturalPerson
and LegalPerson
expand Person
. Since you changed the definition of entities, you will have to update the database schema.
Now, in yours, Controller
you only need to do this:
foreach ($entities as $entity)
{
$person = $entity->getPerson();
$order = array();
$order[] = $entity->getNickname();
if ($person instanceof NaturalPerson)
{
$order[] = $person->getIdentificationType() . $person->getCI();
}
else // it has to be LegalPerson
{
$order[] = $person->getIdentificationType() . $person->getRIF();
}
$orders[] = $order;
}
Don't forget to add an operator use
for NaturalPerson
!
This way, you only work with instances of NaturalPerson
or LegalPerson
. I'm sure you can improve this.
Finally, you will have to change your CRUD for this. You are not working with anymore Person
(actually it should be abstract
), so now you need to handle CRUD for NaturalPerson
and for LegalPerson
separately. Each of them will have Type
, Controller
, presentation, etc.
Your code will now look like this:
if ($person_type === 1)
{
$entityPerson = new NaturalPerson();
$entityPerson->setDescription($orders['nat']['person']['description']);
$entityPerson->setContactPerson($orders['nat']['person']['contact_person']);
$entityPerson->setIdentificationType($orders['nat']['identification_type']);
$entityPerson->setCI($orders['nat']['ci']);
$em->persist($entityPerson);
$em->flush();
}
elseif ($person_type === 2)
{
$entityPerson = new LegalPerson();
$entityPerson->setDescription($orders['leg']['person']['description']);
$entityPerson->setContactPerson($orders['leg']['person']['contact_person']);
$entityPerson->setIdentificationType($orders['leg']['identification_type']);
$entityPerson->setRIF($orders['leg']['rif']);
$em->persist($entityPerson);
$em->flush();
}
Perhaps the problem lies elsewhere. You may forget to assign an object to NaturalPerson
or LegalPerson
to Person
. Therefore, you need to check it before calling getIdentificationType()
:
if($personType == 1){
if(null !== $natural = $entity->getPerson()->getNaturals()[0]){
$order[] = $natural->getIdentificationType() . $natural->getCI();
}
}elseif($personType == 2){
if(null !== $legal = $entity->getPerson()->getLegals()[0]){
$order[] = $legal->getIdentificationType() . $legal->getRIF();
}
}