Symfony: join request using QueryBuilder - php

I have a database model with 2 tables: user and experimentation. Relation is n:n, so I created a third table with the 2 foreign keys.
| Experimentation | 1-------0..n |ExperimentationUser| 0..n-------1 |User|
Entity are generated thanks to Doctrine (with annotations). I got UserEntity and ExperimentationEntity, but not ExperimentationUserEntity. When I look into USerEntity, I can find a collection of "associated experimentation IDs".
I would like to get all experimentation names (not IDs) for a specified User. In common SQL, I'll have join Experimentation and ExperimentationUser with a WHERE clause (for User ID selection). But since I'm starting a Symfony (2.8) project, I'd like to use the QueryBuilder.
I'm not familiar with the syntax and I have no idea of how I can achieve this. What I have tried:
$repository = $this->getDoctrine()->getRepository('AppBundle:Experimentation');
$query = $repository->createQueryBuilder('e')
->join('...')
->setParameter('id',$id)
->getQuery();
But I don't know what to put in the join clause. I'm not even sure I need it.
Thanks for your help!

This is the way I do this kind of things. I don't use query builder but you con get the idea.
In the entity
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Experimentation
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="AppBundle\Entity\ExperimentationRepository")
*/
class Experimentation
{
//...
}
In the repository class
namespace AppBundle\Entity;
use Doctrine\ORM\EntityRepository;
class ExperimentationRepository extends EntityRepository
{
public function getExperimentationByUser($id) {
$em = $this->getEntityManager();
$query = $em->createQuery('SELECT e '
. 'FROM AppBundle:Experimentation e '
. 'JOIN e.experimentationUsers eu '
. 'JOIN eu.user u '
. 'WHERE u.id = :id ')
->setParameter('id', $id);
return $query->getResult();
}
}
Then in controller
//..
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('AppBundle:Experimentation')->getExperimentationByUser($user_id);
//..
In that way you can get the list of experimentation a then get it's name, or also in the query put just $query = $em->createQuery('SELECT e.name ' . If you also need data related to user can put $query = $em->createQuery('SELECT e, u ' and possibly you can save some database request.
I hope this help you.

Related

Return ORM object and convert SQL to Doctrine QueryBuilder syntax

I'm creating a function that should return an array of User ORM object. The function should run a query to the DB and return the users where the users' contact persons has 1 company (not more or less). The relationship is like this: every user has one or more contact person and every contact person has one or more companies.
The SQL to locate these users are like this. We are using PHP 7.1, Symfony 3.4 and Doctrine 2.7.
The problem that I have is that I cannot manage to describe this in Doctrine QueryBuilder syntax so that an array of User ORM objects are returned. Can anybody give me some advice?
SELECT users.email
FROM company
INNER JOIN contact_person ON contact_person.id = company.belongs_to_contact_person_id
INNER JOIN users ON users.id = contact_person.belongs_to_user_id
GROUP BY users.email
HAVING COUNT(company.id) = 1
Depending on how your mapping is on your entities, you have multiple solution.
It would be nice if you can show us what you tried so we can see what you miss.
The best is to use the repository of the entity you whish to have an array of:
namespace App\Repository;
use App\Entity\User;
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
/**
* #return User[]
*/
public function findUsersHavingAtLeastOneCompany():array
{
return $this->createQueryBuilder('user')
->join('user.contact', 'contact')
->join('contact.company', 'company')
->where('contact.company = 1')
->getQuery()
->getResult();
}
}
When using the createQueryBuilder function, it will auto populate the select and the from.
The getResult will return an array of entity (if you have not defined a select)
I fixed this by using the following code using createNativeQuery. It can probably be done by using fever lines of code, but it does the job for me :)
$em = $this->getEntityManager();
$sql = <<<SQL
SELECT users.id
FROM company
INNER JOIN contact_person ON contact_person.id = company.belongs_to_contact_person_id
INNER JOIN users ON users.id = contact_person.belongs_to_user_id
GROUP BY users.id
HAVING COUNT(company.id) = 1
SQL;
$rsm = new ResultSetMapping();
$rsm->addScalarResult('id', 'text');
$query = $em->createNativeQuery($sql, $rsm);
$locatedUsers = [];
foreach ($query->getResult() as $lUser) {
foreach ($lUser as $user) {
$locatedUser = $em->find("Project\User\User", $user);
array_push($locatedUsers, $locatedUser);
}
}
return $locatedUsers;

Get username with queryBuilder join using FOSUserBundle

I use FOSUserBundle, and I need to get the user names who wrote comments on my post.
I use function createQuery to get the post comments, and add this join to trying to get the names:
<?php
namespace CommentsBundle\Entity;
use FOS\UserBundle\Entity\User;
/**
* CommentsRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class CommentsRepository extends \Doctrine\ORM\EntityRepository
{
public function getPostComments($planId)
{
$arrPlanComments = array();
$query = $this->getEntityManager()->createQuery(
'SELECT
c.id,
c.fkUser,
c.fkReply,
c.comment,
c.createdAt,
c.likes,
c.unlikes
FROM CommentsBundle:Comments c
INNER JOIN UserBundle:User u
WITH (c.fkUser = u.id)
WHERE
c.fkPlan = :planId
ORDER BY c.createdAt DESC'
)->setParameter('planId', $planId);
try
{
$arrPlanComments = $query->getArrayResult();
}
catch(\Doctrine\ORM\NoResultException $ex)
{
echo $ex->getMessage();
}
return $arrPlanComments;
}
}
I have extended the FOSUserBundle to my custom bundle named 'UserBundle', and it works fine, but I don't know how to add the relationship with this entity.
I am having this error when i add the join relationship:
[Semantical Error] line 0, col 164 near 'UserBundle:User': Error: Class 'UserBundle\Entity\User' is not defined.
What is wrong?
According to the documentation, you have to create own class for User entity, which will extend one provided by FOSUser. It seems like you haven't done that. That's what your error message probably indicates.

How to count related table records using Doctrine2

I'm quite new to doctrine, And i want to carry out a certain task.
I have jobs table with category_id column, And obviously categories table.
In Symfony2, I have this repository
<?php
namespace Ibw\JobeetBundle\Repository;
use Doctrine\ORM\EntityRepository;
class CategoryRepository extends EntityRepository
{
public function getWithAllJobs()
{
$qb = $this->createQueryBuilder('c')
->select('c, j')
->leftJoin('c.jobs', 'j');
return $qb->getQuery()->getResult();
}
}
Now when i get the result of getWithAllJobs function, It returns all categories even if it have no jobs related.
I want to only return the categories with related jobs. I'm thinking of counting c.jobs and select categories with c.jobs more than 0 or something. How to do that in doctrine ?
And if there's a better way, What is it ?
The only right way to do what you want is using inner join instead of left join. Your code should look like this:
$qb = $this->createQueryBuilder('c')
->select('c, j')
->innerJoin('c.jobs', 'j');

Delete record from many to many entity in Zend using Doctrine 2

I am working on Zend Doctrine. I have a many-to-many entity groups_contacts which has fields group_id and contact_id linked to relevant tables group and contact and created in group entity.
I am creating groups_contacts in group entity which is a many-to-many relationship.
Following is the code of delete action:
public function deleteGroupMemberAction() {
$auth_service = $this->getServiceLocator()->get('doctrine.authenticationservice.orm_default');
$objectManager = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
$em = $this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
$user = $auth_service->getIdentity();
//die($_POST['g_id'] . ' removed');
$query_deleteMember = $em->createQuery('delete from groups_contacts gc where gc.contact_id = 7 and gc.group_id = 1');
$numDeleted = $query_deleteMember->execute();
die($query_deleteMember. ' removed');
$objectManager->flush();
die($title . ' removed');
}
This function called on ajax call which is working perfectly.
I don't know why the delete query is not working, I tried other ways but get the same result. Does anyone have any ideas?
You have to specify the mapping to your entity
delete from MyMapping:groups_contacts gc where gc.contact_id = 7 and gc.group_id = 1
Or put your namespace:
delete from Your\Name\Space\groups_contacts gc where gc.contact_id = 7 and gc.group_id = 1
But that's not enough because you can not access these contact_id and group_id columns directly. So you would have to write:
delete from MyMapping:groups_contacts gc JOIN gc.contact c JOIN gc.group g where c.id = 7 and g.id = 1
Wow two joins .. this should be simpler in native SQL. Does it even execute those JOINS? Perhaps Doctrine will optimize it and not use the JOIN in the final query.
The use case is a bit weird though. Suppose you have a website which lists groups and their contacts. The relation is represented by groups_contacts (on a site note you should only make this a seperate entity when the relation needs to hold data itself). Then when the user wants to remove a relationship (disconnect a contact from a group) the relationship (represented by groups_contacts) can just be identified with it's own id. Then your query would become:
DELETE FROM MyMapping:groups_contacts gc WHERE gc.id = <user clicked relation>
Here is the solution
controller
$group = $em->find("Application\Entity\Group", $group_id);
if ($group->getCreatedBy()->getId() == $user->getId()) {
$contact = $em->find("Application\Entity\UserProfile", $contact_id);
if (isset($contact)) {
$group->getContacts()->removeElement($contact);
$objectManager->persist($group);
$objectManager->flush();
}
Group Entity
/**
*
* #ORM\ManyToMany(targetEntity="UserProfile")
* #ORM\JoinTable(name="groups_contacts",
* joinColumns={#ORM\JoinColumn(name="group_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="contact_id", referencedColumnName="id")}
* )
*/
private $contacts;
It's better to handle many-to-many relationship this way, in which you have to get the group and the contact ids you want to remove from associative entity by usingthe removeElement and then persist with the relevant id of group

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