Symfony QueryBuilder - exclude objects having specified object connected to it? - symfony

Symfony 2.8. I have ExternalReference, which has its type and can be attached to some objects. Like this:
Entity\ExternalReferenceType.php:
- $id
- $name (string)
Entity\ExternalReference.php:
- $id
- $value
- $type (ManyToOne to ExternalReferenceType)
Now, something I attach references to, Entity\ObjectWithReferences.php has the following, apart from $id and other fields:
/**
* #var ArrayCollection
* #ORM\ManyToMany(targetEntity="MyBundle\Entity\ExternalReference")
* #ORM\JoinTable(name="obj_references",
* joinColumns={
* #ORM\JoinColumn(name="obj_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="reference_id", referencedColumnName="id")
* }
* )
*/
private $references;
(OneToMany unidirectional assotiation, as far as I remember, I found it in Doctrine's documentation):
Now user will pick one of reference types (ExternalReferenceType object) and I want to list all ObjectWithReferences objects which don't have any reference of that type. I'm a bit lost, I probably could do it in plain SQL, but I need it in QueryBuilder format. Can you help me?

Related

Doctrine : Fetch "EAGER" and "Hydrate Array"

With Doctrine, I have fetch=EAGER in my entity :
class TrainingOrganization
{
/**
* #var TrainingOrganizationVersion[]|ArrayCollection
*
* #ORM\OneToMany(
* targetEntity="AppBundle\Entity\TrainingOrganizationVersion",
* mappedBy="trainingOrganization",
* cascade={"persist"},
* fetch="EAGER"
* )
* #ORM\OrderBy({"id" = "ASC"})
* #Assert\Valid()
* #Versionable
*/
private $versions;
Why when i do "hydrate array" it does not work ?
Screen of my dump for same entity (Second is "Hydrate array") :
With Hydration mode Query::HYDRATE_ARRAY, Doctrine will only return information about that 'row'. Since your versions attribute is not a field but a collection, it won't be returned.
If you want to have Collections included, use Objects instead (like your first screenshot).
If you really need your entities serialized (returning a multidimensional array instead of objects), use a serializer. Since you're using Symfony, you can easily use Symfony's Serializer Component. The JMSSerializerBundle is a popular alternative.

Error when I try to make a ManyToMany relation in Symfony 2

I want to make a ManyToMany relation in Doctrine with Symfony 2 between a group and an user : many users can be in many groups and many groups can have many users.
Then in my entities, i do this :
Groupe.php
/**
* Many Groups have Many clients.
* #ORM\ManyToMany(targetEntity="Utilisateurs\UtilisateursBundle\Entity\Client", mappedBy="groupe")
* #ORM\JoinTable(name="client_groupe")
*/
private $clients;
/**
* Get clients
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getClients()
{
return $this->clients;
}
Client.php
/**
* #ORM\ManyToMany(targetEntity="Ecommerce\EcommerceBundle\Entity\Groupe", inversedBy="clients")
* #ORM\JoinTable(name="client_groupe",
* joinColumns={#ORM\JoinColumn(name="client_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="groupe_id", referencedColumnName="id")}
* )
*/
private $groupe;
but when I call the getClients() function on my entity $groupe, following error occured :
FROM client t0 WHERE client_groupe.groupe_id = ?' with params ["2"]:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'client_groupe.groupe_id' in 'where clause'
It doesn't add the client_groupe table in the from clause.
Someone can help me ?
No need to add client_groupe table in from clause. Try after removing * #ORM\JoinTable(name="client_groupe") from Groupe.php. Have a look at following working example of ManyToMany relationship scenario.
Groupe.php
/**
* Many Groups have Many clients
* #ORM\ManyToMany(targetEntity="Utilisateurs\UtilisateursBundle\Entity\Client", mappedBy="groupe")
*/
private $clients;
Client.php
/**
* #ORM\ManyToMany(targetEntity="Ecommerce\EcommerceBundle\Entity\Groupe", inversedBy="clients")
* #ORM\JoinTable(name="client_groupe",
* joinColumns={#ORM\JoinColumn(name="client_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="groupe_id", referencedColumnName="id")}
* )
*/
private $groupe;
client_id and groupe_id is fields of client_groupe table. Generate Getter and setter using doctrine command and update database using bin/console doctrine:schema:update --force command.

Doctrine n:m relation Undefined index: joinColumns in BasicEntityPersister.php

My question is strongly related to this one Undefined index: inverseJoinColumns while trying to define ManyToMany relationship between two entities
I am trying to find all the books which have no section. ->findBy(array("section"=>null)
Here is my Entity config:
EditedBooks
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="Sections", inversedBy="editedBook")
* #ORM\JoinTable(name="edited_book_has_section",
* joinColumns={
* #ORM\JoinColumn(name="edited_book_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="section_id", referencedColumnName="id")
* }
* )
*/
private $section;
Here is my Entity config:
Sections
/**
* Bidirectional (INVERSE SIDE)
*
* #ORM\ManyToMany(targetEntity="EditedBooks", mappedBy="section")
*/
private $editedBook;
The Error that I am getting
Undefined index: joinColumns in BasicEntityPersister.php
I tryied it also with only
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="Sections", inversedBy="editedBook")
* #ORM\JoinTable(name="edited_book_has_section")
*/
or switching the definition but then I receive this error
You cannot search for the association field '\Entity\EditedBooks#section', because it is the inverse side of an association. Find methods only work on owning side associations.
Workaround:
Today I tried to fix my problem with the querybuilder in my editedBooksRepository
$query = $qb->select('books')
->from('Bundle:EditedBooks', 'books')
->leftJoin('books.section', 'sec')
->addSelect('COUNT(sec.id) AS sec_count')
->andWhere('sec_count = 0');
But I receved the following error:
An exception occurred while executing 'SELECT e0_.id AS id0, e0_.doi
AS doi1, e0_.isbn_print AS isbn_print2, e0_.isbn_electronic AS
isbn_electronic3, e0_.publication_date AS publication_date4,
e0_.price_print AS price_print5, e0_.price_electronic AS
price_electronic6, e0_.summary AS summary7, e0_.title AS title8,
e0_.ongoing AS ongoing9, e0_.pages AS pages10, e0_.illustrations AS
illustrations11, e0_.entry_date AS entry_date12, e0_.google_id AS
google_id13, e0_.specialIssue_comment AS specialIssue_comment14,
e0_.deleted AS deleted15, e0_.specialIssue_id AS specialIssue_id16,
COUNT(s1_.id) AS sclr17, e0_.book_series_id AS book_series_id18,
e0_.copyright_id AS copyright_id19, e0_.publisher_id AS publisher_id20
FROM edited_books e0_ LEFT JOIN edited_book_has_section e2_ ON e0_.id
= e2_.editedbooks_id LEFT JOIN sections s1_ ON s1_.id = e2_.sections_id WHERE sclr17 = 0':
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'sclr17' in
'where clause'
but to me it seems like sclr17 exists, am I missing something?
The ugly Workaround
I know it is not the right thing to do but sometimes a man has to to what a man has to do:
$noSection = new Sections();
$noSection->setTitle("Sectionless");
//add all the books
$noSection->setEditedBook(new ArrayCollection($books));
//remove the assigned ones
foreach($sections as $section){
$sBooks = $section->getEditedBook();
foreach($sBooks as $b){
$noSection->removeEditedBook($b);
}
}
With the dirty fix it is now working but I am glad for any other solution.
Here is a working example
class Audio
{
/**
* #ORM\ManyToMany(targetEntity="Acme\MyBundle\Entity\Groupe",inversedBy="audios")
*/
private $groupes;
class Groupe
{
/**
* #ORM\ManyToMany(targetEntity="Acme\MyBundle\Entity\Audio",mappedBy="groupes")
*/
private $audios;
Notice how the mappedby and inversedby, as well at the attributes have an "s" at the end. (Groupe => private $groupe s ). Maybe it will help you
Can't you just check with null?
$query = $qb->select('books')
->from('Bundle:EditedBooks', 'books')
->leftJoin('books.section', 'sec')
->where('sec IS NULL');

Doctrine2 OneToMany without mappedBy

I have an entity 'listing' with OneToMany to entity 'view', the key between these to is view.content_id which holds the ID of listing, however, it also relates to other entities, so by adding
/**
* #var Listing
*
* #ORM\ManyToOne(targetEntity="\Acme\Bundle\Entity\Listing", inversedBy="views")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="content_id", referencedColumnName="id")
* })
*/
private $listing;
To the view it brakes because when saving the view entity the content_id becomes null.
How can I fix it?
Relation on listing side:
/**
* #var views
*
* #ORM\OneToMany(targetEntity="\Acme\Bundle\Entity\View", mappedBy="listing")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id", referencedColumnName="content_id")
* })
*/
private $views;
I'm making queries by joining the Listing.views and adding WITH content_type = :ContentType which discriminates some 'view' results.
I'm coming pretty late to the party, but thought I would answer in case it helps someone else.
Because of the relation you've established between the two entities, you now have to assign an object to that property rather than the FK value itself. Like so:
$listing = $em->getRepository('Acme\Bundle\Entity\Listing')
->find($content_id);
$view->setListing($listing);
And not:
$view->setListing($content_id);
Of course, if it turned out to be something else, I'd be curious to know.
:^)

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