Doctrine query with nullable/optional join - php

I got a method on my repository class to get me Article with their join (category, image ...) and not every Article have categories or image, what don't give me back all expected result, cause my current repository function return only Article who have Categories and Image not null and just ignore what having null value.
My Article entity have the following relationship.
/**
* #ORM\ManyToMany(targetEntity="App\ArticleBundle\Entity\Category", cascade={"persist"})
*/
private $categories;
/**
* #ORM\ManyToOne(targetEntity="App\ArticleBundle\Entity\Image", cascade={"persist"})
*
*/
private $image;
And this is my repository function
public function getArticle($id)
{
$qb = $this->createQueryBuilder('a')
->where('a.id = :theId')
->setParameter('theId', $id)
->join('a.author', 'auth')
->addSelect('auth')
->join('a.categories', 'cat')
->addSelect('cat')
->join('a.image', 'img')
->addSelect('img');
return $qb->getQuery()->getOneOrNullResult();
}
Now I want to know if I can get Article which have categories, image or not in one query with the join. I want to say when using the Doctrine lazy loading (by avoid the join in the query) I get the expected result.

Use ->leftJoin() to get Article which have categories, image or not in one query:
public function getArticle($id)
{
$qb = $this
->createQueryBuilder('a')
->addSelect('auth', 'cat', 'img')
->join('a.author', 'auth')
->leftJoin('a.categories', 'cat')
->leftJoin('a.image', 'img')
->where('a.id = :theId')
->setParameter('theId', $id)
;
return $qb->getQuery()->getOneOrNullResult();
}
Thus, avoids extra queries when Doctrine try to load the related properties in a lazy way.
Explanation:
Using ->join() or ->innerJoin():
This is the simplest, most understood Join and is the most common. This query will return all of the records in the left table (table A) that have a matching record in the right table (table B).
Using ->leftJoin():
This query will return all of the records in the left table (table A) regardless if any of those records have a match in the right table (table B). It will also return any matching records from the right table.
Source: Visual-Representation-of-SQL-Joins explained in detail by C.L. Moffatt

Related

Doctrine - relation of three tables

I have trouble with writing Doctrine Query Builder.
I have a Posts table which is related to the AccountToken table like:
class Posts
/**
* #ORM\ManyToOne(targetEntity="App\Entity\AccountToken")
* #ORM\JoinColumn(name="account_token_id", referencedColumnName="uuid", nullable=false)
*/
protected $accountTokenId;
I am writing a query builder where I want to pull all the results which are related to that table via UUID and have a specific status. The type value is defined in the AccountToken table.
In that table, type has relation also and it's defined in AccountToken table:
class AccountToken
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Types")
* #ORM\JoinColumn(name="type_id", referencedColumnName="id", nullable=false)
*/
protected $type;
It's a tricky one for me because I have to relations.
Does someone know how to pull the results:
"Return me posts which have the type='success' value".
$this->createQueryBuilder('p')
->select('p')
->join('App\Entity\AccountToken', 'at', Expr\Join::WITH, 'at.uuid = p.accountTokenId')
->join('App\Entity\ChannelType', 'ct', Expr\Join::WITH, 'ct.id = at.type')
->where('p.accountTokenId = :accountTokenId')
->setParameter('successStatus', Types::STATUS_SUCCESS)
->getQuery()
->getResult();
This is my draft of how I am trying to accomplish it but as I am new with Doctrine, don't have an idea how to join a table that has value in its related table. It's confusing.
Firstly you don't have any parameter successStatus in your query.
I think you over complicate the things.
return $this->createQueryBuilder('p')
->leftJoin('p.accountToken', 'a')
->leftJoin('a.type', 'ct')
->where('ct.successStatus = :successStatus')
->setParameter('successStatus', Types::STATUS_SUCCESS)
->getQuery()
->getResult()
;

Fetching all objects of one class with self-referencing ManyToMany

Think of this class:
class Person {
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Person", fetch="EXTRA_LAZY")
* #ORM\OrderBy({"name" = "ASC"})
*/
public $friends;
/**
*
* #var Person
* #ORM\ManyToOne(targetEntity="Person")
*/
public $bestFriend;
}
I have to iterate a lot over all Persons, so I'd like to fetch join them all at once.
To save memory, I have to do this partially.
So what I can do is:
$this->em->createQuery('SELECT partial p.{id, name, bestFriend} FROM Person p')->getResult();
This is cool, after this query, all persons are in the UoW, and I can traverse the graph via $aPersion->bestFriend->bestFriend without creating an additional query to the DB, since all Persons are in memory.
However, this does not work with the ToMany association. Adding friends to the partial select gives an error. If I want to iterate over all friends, this will first create a query to the join table...
How can I realise the full hydration of the friends-ToMany-assotiation with one query? Maybe a second query could help? Or a clever join clause?
Thanks in advance!
I would create a query in PersonRepository.php with a leftJoin and a addSelect like so:
$qb = $this->em->getRepository('App:Person')
->createQueryBuilder('p')
->leftJoin('p.friends', 'friends')
->select('partial p.{id, name, bestFriend}'}
->addSelect('partial friends.{id, name}') // Retrieve what you want here
->getQuery()->getResult();
return $qb;
I have not tested this, but believe it should work.
#DirkJFaber your answer was right,
in terms of DQL here is my solution:
$this->em->createQuery('
SELECT partial p.{id, name, bestFriend}, f FROM Person p JOIN f.friends f')->getResult();

Symfony2/Doctrine2 Get data from two tables

I have an Alert Class with some data in it. I then have an Availability class. In my Availability class I have
/**
* #var \Nick\AlertBundle\Entity\Alert
*
* #ORM\ManyToOne(targetEntity="Nick\AlertBundle\Entity\Alert")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="availability_alert_id", referencedColumnName="id")
* })
*/
private $availabilityAlert;
So this is a foreign key back to my Alert class, linked to my Alerts class id.
I am now doing some work on the Availability data, so I have the DQL query
public function getAlertAvailability($id)
{
return $this->getEntityManager()
->createQuery(
'SELECT a.id, a.classLetter, a.flightNumber, a.alertPseudo, a.availability, a.lastUpdated
FROM NickAlertBundle:Availability a
WHERE a.availabilityAlert = :id
ORDER by a.classLetter, a.lastUpdated'
)
->setParameter('id', $id)
->getResult();
}
The way I call that is like this
public function getAvailabilityData(){
$alerts = $this->em->getRepository('NickAlertBundle:Alert')->getActiveAlertIds();
if (!$alerts) {
echo "No Availability";
}
foreach($alerts as $alert){
$alertId = (int)$alert['id'];
$allAvailability = $this->em->getRepository('NickAlertBundle:Availability')->getAlertAvailability($alertId);
}
}
So I essentially get all my active Alerts Ids, and then pass this to get my Availability for these individual Alerts.
Now I have a couple of problems.
Firstly, in the DQL query I make, I need to also get something from my Alert table (a field called command). How would I do a join in this query to get this piece of data?
Secondly, with the data that is returned, how do I access availabilityAlert in my Twig file?
UPDATE
Attempt at join
public function getAlertAvailability()
{
return $this->getEntityManager()
->createQuery(
'SELECT a.id, a.classLetter, a.flightNumber, a.alertPseudo, a.availability, a.lastUpdated, u.searchCommand
FROM NickAlertBundle:Availability a
JOIN a.availabilityAlert u
ORDER BY a.classLetter, a.lastUpdated'
)
->getResult();
}
Doctrine will load that entity as a proxy (for lazy loading) when the Availability entity is loaded.
You can access these via a normal getter / property access, but they will typically be lazy loaded by Doctrine. You can have them joined via a DQL query and the property will be hydrated with all the linked entities already loaded, see Improving Performance with a Join.
You can then access those associated entities in Twig as any other property.

Doctrine2 fetching rows that have manyToMany association by QueryBuilder

everyone.
I have 2 entities City and POI. Mapping looks like this:
class City {
/**
* #ORM\ManyToMany(targetEntity="POI", mappedBy="cities")
* #ORM\OrderBy({"position" = "ASC"})
*/
protected $pois;
and
class POI {
/**
* #ORM\ManyToMany(targetEntity="City", inversedBy="pois")
* #ORM\JoinTable(name="poi_cities")
*/
protected $cities;
I would like to fetch all POIs that have at least 1 association with some City using QueryBuilder. I should probably use exists() function but I don't quiet know how.
You'd have to Left join them and check if cities is null.
$qb->select('p', 'c')
->from('AcmeDemoBundle:POI', 'p')
->leftJoin('p.cities', 'c')
->where('c IS NOT NULL');
I haven't tested it, but I hope it gives you the general direction. You can read more about the QueryBuilder from here.
Docrine2 was changed in 2013, so the other solution displays error Error: Cannot add having condition on a non result variable. Now we cannot use joined alias just as a condition variable. We should use any of its properties like c.id
So you should fix the code to
$qb->select('p', 'c')
->from('AcmeDemoBundle:POI', 'p')
->leftJoin('p.cities', 'c')
->where('c.id IS NOT NULL');
$results = $qb->getQuery()->execute();
If you want to select entities that does not have any cities, use IS NULL.
$qb->leftJoin('p.cities', 'city')
->where('city.id IS NULL')
->getQuery()
->execute();
Description of a problem and link to the commit that responsible for that - http://www.doctrine-project.org/jira/browse/DDC-2780

Doctrine2 many-to-one association won't use JOIN query

I have a Car entity with a many-to-one relationship with an entity Owner. If I select all cars, Doctrine does one query on the Car table, and subsequently one query on the Owner table for each car. So fetching N cars becomes N+1 queries instead of a single JOIN query between the Car and Owner tables.
My entities are as follows:
/** #Entity */
class Car {
/** #Id #Column(type="smallint") */
private $id;
/** #ManyToOne(targetEntity="Owner", fetch="EAGER")
#JoinColumn(name="owner", referencedColumnName="id") */
private $owner;
public function getId() { return $this->id; }
public function getOwner() { return $this->owner; }
}
/** #Entity */
class Owner {
/** #Id #Column(type="smallint") */
private $id;
/** #Column(type="string") */
private $name;
public function getName() { return $this->name; }
}
If I want to list the cars with their owners, I do:
$repo = $em->getRepository('Car');
$cars = $repo->findAll();
foreach($cars as $car)
echo 'Car no. ' . $car->getId() .
' owned by ' . $car->getOwner()->getName() . '\n';
Now this all works very well, apart from the fact that Doctrine issues a query for each car.
SELECT * FROM Car;
SELECT * FROM Owner WHERE id = 1;
SELECT * FROM Owner WHERE id = 2;
SELECT * FROM Owner WHERE id = 3;
....
Of course I'd want my query log to look like this:
SELECT * FROM Car JOIN Owner ON Car.owner = Owner.id;
Whether I have fetch="EAGER" or fetch="LAZY" doesn't matter, and even if I make a custom DQL query with JOIN between the two entities, $car->getOwner() still causes Doctrine to query the database (unless I use EAGER, in which case $repo->findAll() causes all of them).
Am I just too tired here, and this is the way it is supposed to work - or is there a clever way to force Doctrine to do the JOIN query instead?
At least in 1.x Doctrine if you wanted to query for the related objects, you had to use DQL. For your case, the DQL query would look something like this:
//Assuming $em is EntityManager
$query = $em->createQuery('SELECT c, o FROM Car c JOIN c.owner o');
$cars = $query->execute();
Run first a DQL query where you select all the cars joined (DQL JOIN) with the owner. Put the owner in the select().
// preload cars
$qb = $em->createQueryBuilder()
->select('car, owner')
->from('\Entity\Car', 'car')
->leftJoin('c.owner', 'owner');
$query = $qb->getQuery();
// the following seems not needed, but I think it depends on the conf
$query->setFetchMode("\Entity\Car", "owner", "EAGER");
$query->execute(); //you don't have to use this result here, Doctrine will keep it
Doctrine 2 will then perform a JOIN (normally faster as it requires less db queries depending on the number of records).
Now launch your foreach, Doctrine will find the entities internally and it won't run single queries when you need the owner.
Monitor the number of queries first/after each change (eg. mysql general log)
Your query...
$car->getOwner() // "go and fetch this car's owner"
... is in a foreach loop so it will certainly issue the query several times.
If you're writing custom DQL to deal with this, $car->getOwner() shouldn't feature in this at all. This is a function of the Car class. The custom DQL you would write would mimick the exact SQL query you point out and get your join done efficiently.

Categories