Merge two queries with DQL - symfony

I have my main entity poi that has two OneToOne relation with my two other entities people and places.
App/Entity/People.php
class People
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToOne(targetEntity="App\Entity\Poi", cascade={"persist", "remove"})
* #ORM\JoinColumn(nullable=false)
*/
private $poi;
App/Entity/Places.php
class Places
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToOne(targetEntity="App\Entity\Poi", cascade={"persist", "remove"})
*/
private $poi;
Now I need to get the peopleand places in the same array or something. I got that by merging two queries :
$query = $entityManager->createQuery('select p from App:People p'); //same for places then merge in array...
Q : Is there a way to get the same result with one DQL query? (especially if the entities don't have the same columns).

To simply get all People and Places in one array you can go with:
$query = $entityManager->createQuery('select people, places from App:People people, App:Places places');
I guess you will want to fetch them for a given Poi, in that case if you only have the poi id you can try this:
$query = $entityManager
->createQuery('select people, places from App:People people, App:Places places where people.poi = :poi and places.poi = :poi')
->setParameter('poi', $poiId)
;
Naturally, if you have a Poi entity object at your disposal, you can simply do
$peopleAndPlaces = [$poi->getPeople(), $poi->getPlaces()]
Note that in order for above code to work you have to add those methods to respective entity classes and make sure the relations with Poi are bidirectional
If you for whatever reason often need to get that array and you have a Poi entity object, it might be a good idea to add a method to the Poi class
class Poi
{
...
public function getPeopleAndPlaces()
{
return [$this->getPeople(), $this->getPlaces()];
}
and then call $poi->getPeopleAndPlaces() when needed.

Related

Symfony: Filter ArrayCollection by associated entity id

I have a User entity and a Usecase entity. This 2 entities are associated by a ManyToMany association, but this association also holds another property, called "environment". To implement this relationship I also have an entity called UserUsecase that has a ManyToOne relationship with User, a ManyToOne relationship with Usecase and the extra field "environment". When fetching a user from the database, his usecases are being fetched as well, so the user has an ArrayCollection of objects of type UserUsecase that represent all the usecases a user has. What I want to do is to filter this ArrayCollection by usecase_id. The UserUsecase class has the structure below:
class UserUsecase
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="userUsecases")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
/**
* #ORM\ManyToOne(targetEntity="Usecase", inversedBy="userUsecases")
* #ORM\JoinColumn(name="usecase_id", referencedColumnName="id")
*/
protected $usecase;
/**
* #ORM\Column(type="integer")
*/
protected $environment;
}
So I tried this inside the User class:
public function filterUsecases($usecase_id){
$criteria = Criteria::create();
$criteria->where(Criteria::expr()->eq('usecase', $usecase_id));
return $this->userUsecases->matching($criteria);
}
It makes sense to me that even the field usecase of the class UserUsecase is an object of type Usecase, it should resolve to its id and the equation would hold when the ids matched. Still this doesn't seem to work, and I cannot find how to implement this filtering. Isn't it possible to be done this way? I found a relevant article that seems to do exactly what I want but this is not working in my case. Here is the article! Am I doing something wrong?
Thanks in advance!
Unless you have many many use cases per user(thousands) I recommend:
public function filterUsecases(Usecase $useCase){
$criteria = Criteria::create();
$criteria->where(Criteria::expr()->eq('usecase', $useCase));
return $this->userUsecases->matching($criteria);
}
Then:
$user->filterUsecases($useCase);
Or passing reference
$user->filterUsecases($em->getReference(Usecase::class, $id));

How do I map this relationship?

I'm having trouble mapping this relationship in Doctrine. I have a UseCase, which has many UseCaseSteps. A UseCaseStep has many sub-steps, which is a OneToMany on UseCaseStep. Here's the pertinent code I have atm:
/**
* UseCase
*
* #ORM\Table(name="use_cases")
* #ORM\Entity(repositoryClass="DesignCase\Bundle\Bundle\Entity\UseCaseRepository")
*/
class UseCase
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Actor", inversedBy="use_cases", cascade={"persist", "remove"})
* #ORM\JoinTable(name="actors_use_cases")
*/
private $actors;
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="UseCaseStep", mappedBy="useCase", cascade={"persist", "remove"})
* #ORM\OrderBy({"order" = "ASC"})
*/
private $steps;
}
/**
* UseCaseStep
*
* #ORM\Table(name="use_case_steps")
* #ORM\Entity(repositoryClass="DesignCase\Bundle\Bundle\Entity\UseCaseStepRepository")
*/
class UseCaseStep
{
/**
* #var integer
*
* #ORM\ManyToOne(targetEntity="UseCase")
*/
private $useCase;
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="UseCaseStep", mappedBy="parent", cascade={"persist", "remove"})
* #ORM\OrderBy({"order" = "ASC"})
*/
private $subSteps;
/**
* #var UseCase
*
* #ORM\ManyToOne(targetEntity="UseCase")
*/
private $useCaseReference;
/**
* #var UseCaseStep
*
* #ORM\ManyToOne(targetEntity="UseCaseStep")
* #ORM\JoinColumn(nullable=true)
*/
private $parent;
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="BusinessRule", cascade={"persist", "remove"})
*/
private $businessRules;
}
That code isn't complete, but I think it has all the relevant information. What I want to do is create a new entity TestCase, which has many TestCaseSteps. A TestCase IS a UseCase with a little more information... same for TestCaseStep and UseCaseStep. IE, a TestCaseStep is a UseCaseStep with data input and expected output fields added to it. A user can create many TestCases from one UseCase.
I tried making UseCase and UseCaseStep #MappedSuperclass, but that doesn't have the desired effect. I get the obvious, "It is illegal to put an inverse side one-to-many or many-to-many association on mapped superclass" error. Plus, from the docs, I don't think that's the right approach anyway.
I'm having trouble wrapping my brain around it. Any thoughts? I hope I explained that well enough...
You have an issue there that is much more fundamental than Doctrine. You want to show inheritance in the database. For this approach there is no real correct solution. You could go with making an Entity TestCase that extends UseCase and overwrites the respective properties (need to make them protected) with another relation to TestCaseStep.
You do something similar with UseCaseStep and TestCaseStep. That way you have inheritance in the entities. Now you would have to make sure that you use another table and you end up with completely seperate entities database-wise. They just share the same properties in the entities but are seperate in database.
That approach would be in my opinion the easiest one to follow. Everything else seems to be very complicated as you cannot properly use one table with a different amount of properties for each entity. Each database table has a fixed set of properties that need to be reflected in an entity.
Another approach would surely be to just use the properties in the sub-entity that is used by this entity and create another relationship (many-to-one) between the TestCase and the UseCase or TestCaseStep and UseCaseStep respectively. But the latter approach isn't very easy and can end up being very complicated if you don't have a lot of knowledge about Doctrine, Symfony and databases in general.

Doctrine2 - How can i make a relationship with string column

I having a following issue, I need to make a relationship with two tables, but with no regular id, i need to use strings column. Something like this:
/**
* #ORM\Entity
* #ORM\Table(name="sigtap_tb_procedimento")
*/
class Procedimento
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="ExcecaoCompatibilidade", mappedBy="procedimento_restricao")
* #ORM\JoinColumn(name="co_procedimento_restricao", referencedColumnName="co_procedimento")
*/
private $restricoes;
}
And another Entity
/**
* #ORM\Entity
* #ORM\Table(name="sigtap_rl_excecao_compatibilidade")
*/
class ExcecaoCompatibilidade
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Procedimento", inversedBy="restricoes")
* #ORM\JoinColumn(name="co_procedimento_restricao", referencedColumnName="co_procedimento")
*/
private $procedimento_restricao;
}
co_procedimento_restricao and co_procedimento_restricao are string type, The relation does not working. How can i solve this issue?
Your relation needs to reference a primary key in the other table.
May be I misunderstood your question but cant you reference the id collumn the relationship like this:
/**
* #ORM\OneToMany(targetEntity="ExcecaoCompatibilidade", mappedBy="procedimento_restricao")
*/
private $restricoes;
/**
* #ORM\ManyToOne(targetEntity="Procedimento", inversedBy="restricoes")
* #ORM\JoinColumn(name="co_procedimento_restricao", referencedColumnName="id")
*/
private $procedimento_restricao;
Take a look here:
http://docs.doctrine-project.org/en/2.0.x/reference/association-mapping.html
using one-to-many relations with doctrine
The side using #OneToMany is always the inverse side of a relation from doctrine's pov ( possibly not what you consider being the inverse side ) and never has a join-column definition.
Remove the #JoinColumn annotation from class Procedimento.
#OneToMany has to use mappedBy and #ManyToOne (the owning side) uses inversedBy.
The join-column (or join-table) definition has to be on the owning side together with #ManyToOne.
When using a join-column the name of this column (which will be added to the table of the owning side entity aka the side being "many") will be specified by name="column_name" and the referenced foreign key to store in there is the referencedColumnName="id"definition of the #JoinColum annotation.

Doctrine2 doesn't load Collection until a method is called

When does Doctrine2 loads the ArrayCollection?
Until I call a method, like count or getValues, I have no data
Here is my case. I have a Delegation entity with OneToMany (bidirectional) relation to a Promotion Entity, like this:
Promotion.php
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Promotion
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Delegation", inversedBy="promotions", cascade={"persist"})
* #ORM\JoinColumn(name="delegation_id", referencedColumnName="id")
*/
protected $delegation;
}
Delegation.php
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Delegation
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="Promotion", mappedBy="delegation", cascade={"all"}, orphanRemoval=true)
*/
public $promotions;
public function __construct() {
$this->promotions = new \Doctrine\Common\Collections\ArrayCollection();
}
}
Now I do something like the following (with a given delegation)
$promotion = new Promotion();
$promotion = new Promotion();
$promotion->setDelegation($delegation);
$delegation->addPromotion($promotion);
$em->persist($promotion);
$em->flush();
Looking for the relation into the database is ok. I have my promotion row with the delegation_id set correctly.
And now my problem comes: if I ask for $delegation->getPromotions() I get an empty PersistenCollection, but if I ask for a method of the collection, like $delegation->getPromotions()->count(), everything is ok from here. I get the number correctly. Asking now for $delegation->getPromotions() after that I get the PersistenCollection correctly as well.Why is this happening? When does Doctrine2 loads the Collection?
Example:
$delegation = $em->getRepository('Bundle:Delegation')->findOneById(1);
var_dump($delegation->getPromotions()); //empty
var_dump($delegation->getPromotions()->count()); //1
var_dump($delegation->getPromotions()); //collection with 1 promotion
I could ask directly for promotions->getValues(), and get it ok, but I'd like to know what is happening and how to fix it.
As flu explains here Doctrine2 uses Proxy classes for lazy loading almost everywhere. But acessing $delegation->getPromotions() should automatically invoke the corresponding fetch. A var_dump get an empty collection, but using it- in a foreach statement, for example- it is working ok.
Calling $delegation->getPromotions() only retrieves the un-initialized Doctrine\ORM\PersistentCollection object. That object is not part of the proxy (if the loaded entity is a proxy).
Please refer to the API of Doctrine\ORM\PersistentCollection to see how this works.
Basically, the collection itself is again a proxy (value holder in this case) of a real wrapped ArrayCollection that remains empty until any method on the PersistentCollection is called. Also, the ORM tries to optimize cases where your collection is marked as EXTRA_LAZY so that it is not loaded even when you apply some particular operations to it (like removing or adding an item).

JMSSerializerBundle serialization groups in entities with relations

I have a problem with serializing entity with many relations using groups.
I have a problem with serializing related entities this way.
Let's say I have two entities: Product and related Element.
/**
*
* #Serializer\ExclusionPolicy("none")
*/
class Product {
/**
* Primary key
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*
* #Serializer\Groups({"list","details"})
* #Serializer\Type("integer")
*/
protected $id;
/**
* #Serializer\Groups({"list","details"})
* #Serializer\Type("string")
*/
protected $name;
/**
* #ORM\Column(name="description", type="string", length=4096, nullable=true)
*
* #Serializer\Groups({"details"})
* #Serializer\Type("string")
*/
protected $description;
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Madden\ProjectBundle\Entity\ProjectResource", mappedBy="project")
* #Serializer\Groups({"details"})
* #Serializer\Type("ArrayCollection<Element>")
*/
protected $details1;
/**
* Relation to project tasks
* #ORM\OneToMany(targetEntity="Madden\ProjectBundle\Entity\ProjectTask", mappedBy="project")
* #Serializer\Exclude()
* #Serializer\Type("ArrayCollection<Element>")
*/
protected $details2;
...
}
Element entity has a similar structure:
/**
*
* #Serializer\ExclusionPolicy("none")
*/
class Element {
/**
* Primary key
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*
* #Serializer\Groups({"list","details"})
* #Serializer\Type("integer")
*/
protected $id;
/**
* #Serializer\Groups({"list","details"})
* #Serializer\Type("string")
*/
protected $name;
/**
* #ORM\Column(name="description", type="string", length=4096, nullable=true)
*
* #Serializer\Groups({"details"})
* #Serializer\Type("string")
*/
protected $description;
...
}
My problem is that when I'm serializing Product with 'details' group entity I want to serialize only id's of Elements but as you see entity has defined same groups as Product (in case that I would need details of element object) because I want have unified groups on all my entities and prevent making hundreds of groups like 'product_details','element_details', and so on.
Is there a way to eventualy change serialization group when I visit relation or something like that? Handler maybe or something like that?
Regards and thanks for any help
Unfortunately, you can't really (but keep reading ;-)), well at least not without changes to the serializer library. The culprit is that the list of groups is fixed within a GroupExclusionStrategy (which is referenced by the Context) the minute you start the serialization process. There is actually an assertion within the code that prevents modification of the exclusion strategy once the (de-)serialization is running.
But as it happens, I had the exact same problem in a project of mine as well, and I hacked the necessary changes into the serializer code. I have cleaned the code up a bit and uploaded it to Github (https://github.com/andreasferber/serializer/tree/recursion-groups).
It adds new property metadata with which you can add, remove or override the groups when descending into subobjects. With annotations it looks like this:
/**
* #Serializer\RecursionGroups(set={"foo", "bar"}, add={"baz"}, remove={"Default"})
*/
private $myProperty;
You should be able to use XML or Yaml metadata as well, however this is untested since I don't use them and I haven't added test cases yet. Have a look at the reference documentation. Since I haven't done any optimizations yet either, if your entities are really large and deeply nested, it might have a noticable performance impact.
Please let me know if you find this useful, or if you have any suggestions, because if this isn't only needed by me, I will add some tests and try to submit it upstream.
A solution for this is actually described in the official documentation.
That being said the solution proposed by #aferber seems better on many points: easier to maintain, less verbose, more flexible...
You need to use setGroups.
The _group suffix used in the official documentation is not needed.
$context->setGroups([
'Default', //if you want
// use this linked entity but show only its id
'group_of_linked_field',
'group_of_linked_field' => [
'id' // you will need to define this group first
],
// use this linked entity and show fields as described
'group_of_other_linked_field',
'group_of_other_linked_field' => [
// just as an example
'Default',
'details',
],
]);
This does not work with addGroup or addGroups! Both of them won't accept associative arrays. setGroups is your (only?) solution.

Resources