Symfony - Building query for table created by many to many relation - php

I have 2 entities connected with many to many relation into a 3th table, and I want to get every color for an id of product:
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Color", inversedBy="products")
* #ORM\JoinTable(name="products_colors",
* joinColumns={#ORM\JoinColumn(name="product_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="color_id", referencedColumnName="id")}
* )
*/
private $colors;
And in my second entity:
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Product", mappedBy="colors")
*/
private $products;
This is my query: (I was trying to make some joins but cant make it work)
class ProductRepository extends \Doctrine\ORM\EntityRepository
{
public function getColors($id)
{
$query = $this->createQueryBuilder('p')
->join('AppBundle\Entity\Color', 'c')
->where('c.product_id = :id')
->setParameter('id', $id)
->getQuery()
->getResult();
return $query;
}
}
I got this error:
[Semantical Error] line 0, col 85 near 'product_id =': Error: Class
AppBundle\Entity\Color has no field or association named product_id
which I understand, but can't think of a way to make this work.

Symfony expects you to reference the entity (not id) of an object when mapped by ManyToMany relationship. Try:
class ProductRepository extends \Doctrine\ORM\EntityRepository
{
public function getColors(Product $product)
{
$query = $this->createQueryBuilder('p')
->join('AppBundle\Entity\Color', 'c')
->where('c.product = :product')
->setParameter('product', $product)
->getQuery()
->getResult();
return $query;
}
}
Of course you'll have to modify your call to this function accordingly.
You can also skip the query creation altogether with the getter function in your entity; symfony automagically will do the query for you.
// class Product
private $colors; // as you have it set up already
/**
* #return ArrayCollection|Color[]
*/
public function getColors()
{
return $this->colors;
}

Related

How to query the inverse side of a many to many relationship with Doctrine

I want to know which professional diseases are included in all the medical records of a company production unit. The entity MedicalRecord has a many to many relationship with DiseaseTypology as follows:
/**
* AppBundle\Entity\HealthData\MedicalRecord
*
* #ORM\Table(name="medical_record")
* #ORM\Entity(repositoryClass="MedicalRecordRepository")
* #ORM\HasLifecycleCallbacks
*/
class MedicalRecord
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string $companyProductionUnitId
*
* #ORM\Column(name="company_production_unit_id", type="integer",nullable=true)
*/
protected $companyProductionUnitId;
/**
* #var ArrayCollection $professionalDiseases
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\HealthData\Core\DiseaseTypology")
* #ORM\JoinTable(name="medical_record_professional_disease",
* joinColumns={#ORM\JoinColumn(name="medical_record_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="professional_disease_id", referencedColumnName="id")}
* )
*
*/
protected $professionalDiseases;
In the MedicalRecordReposity class I created the following method:
public function getProfessionalDiseasesByProductionUnit($productionUnitId)
{
$em = $this->getEntityManager();
$repository = $em->getRepository('AppBundle:MedicalRecord');
return $repository->createQueryBuilder('m')
->select('m.professionalDiseases')
->where('m.companyProductionUnitId = :productionUnitId')
->setParameter('productionUnitId', $productionUnitId)
->getQuery()
->getArrayResult();
}
But I get the error:
[Semantical Error] line 0, col 9 near 'professionalDiseases': Error: Invalid PathExpression. Must be a StateFieldPathExpression.
How to query the inverse side of a many to many relationship? Thank you!
I don't know if I can understand what you want, but here's my try:
class MedicalRecordRepository extends \Doctrine\ORM\EntityRepository
{
public function getProfessionalDiseasesByProductionUnit($productionUnitId)
{
$qb = $this->createQueryBuilder('m');
$qb
->select('m, pd')
->innerJoin('m.professionalDiseases', 'pd')
->where('m.companyProductionUnitId = :productionUnitId')
->setParameter('productionUnitId', $productionUnitId)
;
return $qb->getQuery()->getArrayResult();
}
}
Explanation: i think you need a join between MedicalRecord and DiseaseTypology, and for that, if you have this setup (in both your entities):
#Entity/MedicalRecord.php
private $companyProductionIUnitId;
/**
* #var \AppBundle\Entity\DiseaseTypology
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\DiseaseTypology", mappedBy="medicalRecords")
*/
private $professionalDiseases;
First, you have to have that mappedBy option, to tell doctrine the inverse side of the relationship.
And
# Entity/DiseaseTypology.php
/**
* #var \AppBundle\Entity\MedicalRecord
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\MedicalRecord", inversedBy="professionalDiseases")
*/
private $medicalRecords;
You have to have that inversedBy option to tell doctrine the owning side of the relationship.
Once we've clarified that, to let doctrine do its things related to joins, you just need to tell it on which field to make the join. And as in my example, the relation between MedicalRecord and DiseaseTypology is made through $professionalDiseases fields. So this one will be the field to make the join with:
->innerJoin('m.professionalDiseases', 'pd') // this professionalDiseases is the $professionalDiseses from MedicalRecord entity
Ok, I've did all those explanations, because I saw how you've did your query, and I feel is not the right approach.
And my results after running the getProfessionalDiseasesByProductionUnit() method was like this:
Note: Use getResult() instead of getArrayResult(), because you fetch entities (DiseaseTypology), not set of fields
There are 2 options here:
Make relation MedicalRecord <=> DiseaseTypology bidirectional See documentation. Then your method would look very simple:
public function getProfessionalDiseasesByProductionUnit($productionUnitId)
{
$em = $this->getEntityManager();
$repository = $em->getRepository(DiseaseTypology::class);
return $repository->createQueryBuilder('dt')
->select('dt')
->join('dt.medicalRecords', 'm')
->where('m.companyProductionUnitId = :productionUnitId')
->setParameter('productionUnitId', $productionUnitId)
->getQuery()
->getResult();
}
Keep existing DB structure and add some logic after query
public function getProfessionalDiseasesByProductionUnit($productionUnitId)
{
$em = $this->getEntityManager();
$repository = $em->getRepository(MedicalRecord::class);
$mediaRecords = $repository->createQueryBuilder('m')
->select('m, dt')
//note: with this join all professionalDiseases will be loaded within one query for all MedicalRecords
->join('m.professionalDiseases', 'dt')
->where('m.companyProductionUnitId = :productionUnitId')
->setParameter('productionUnitId', $productionUnitId)
->getQuery()
->getResult();
$professionalDiseases = [];
foreach($mediaRecords as $mediaRecord) {
foreach($mediaRecord->professionalDiseases as $professionalDisease) {
$professionalDiseases[professionalDisease->id] = $professionalDisease;
}
}
return $professionalDiseases;
}

Symfony doctrine repository return instance of entity

I have an entity which store the 3D objects what I printed.
private $id;
/**
* #ORM\Column(type="array", nullable=true)
*/
private $images;
/**
* #ORM\Column(type="datetime")
*/
private $date_created;
/**
* #ORM\Column(type="datetime")
*/
private $date_modified;
/**
* #ORM\ManyToOne(targetEntity="App\UserBundle\Entity\User")
*/
private $user;
/**
* #ORM\ManyToOne(targetEntity="App\ThreedBundle\Entity\Threedobject", cascade={"all"})
*/
private $threedobject;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $description;
There is a SQL query which looks like this:
select threedobject_id from threed_print where user_id = {RANDOM_NUMBER} group by threedobject_id;
I have to get the $threedobject all instance which (I mean the App\ThreedBundle\Entity\Threedobject instances) which represent the following sql query through Doctrine.
I tried the following querybuilder, but it have returned the array representation of the values, but most of the cases I have to use the methods of the elements, so I want to get the instances.
$query = $this->em->createQueryBuilder();
$result = $query
->select('tp')
->addSelect('to')
->from('ThreedBundle:ThreedPrint', 'tp')
->join('tp.threedobject', 'to')
->join('tp.user', 'u')
->where('u.id = :userID')
->groupby('to.id')
->setParameter('userID', $userID)
->getQuery();
return $result->getResult();
I read about the repository find method, but in this is not what I want, or I'm not totally understand how it is working.
UPDATE:
Basically what I need:
$query = $this->em->createQueryBuilder();
$result = $query
->select('to')
->from('ThreedBundle:ThreedPrint', 'tp')
->join('tp.threedobject', 'to')
->join('tp.user', 'u')
->where('u.id = :userID')
->groupby('to.id')
->setParameter('userID', $userID)
->getQuery();
return $result->getResult();
But I got the following error for that:
'SELECT to FROM': Error: Cannot select entity through identification variables without choosing at least one root entity alias.
You should implement the inverse relationship oneToMany from Threedobject to Threedprint, adding the $threedprints field in Threedobject.
Then you could write this
$threedobjects=$this->em->createQueryBuilder()
->select('to')
->from('ThreedBundle:Threedobject')
->join('to.threedprints', 'tp')
->join('tp.user', 'u')
->where('u.id = :userID')
->setParameter('userID', $userID)
->getQuery()->getResult();
If you want to use the repository, you should create a folder in your project under src (which normally you call it Repository) and create a new class with a name (for example: ThreedobjectRepository).
then you put the following in that class:
namespace NameProject\NameBundle\Repository;
use Doctrine\ORM\EntityRepository;
class ThreedobjectRepository extends EntityRepository
{
function findById3D($a)
{
$query = $this->getEntityManager()
->createQuery("Select th.threedobject_id AS id
FROM NameprojectNameBundle:threed_print th
Where th.user_id=:user_id
GROUP BY threedobject_id")
set parameter (user_id, $a);
return $query->getResult();
}
}
don't forget to put on your threed_print.php this line:
/**
* #ORM\Entity(repositoryClass="Nameproject\NameBundle\Repository\ThreedobjectRepository")
* #ORM\Table
*/
then you can go to the controller you are working on and instead of using findall() or findoneby(), you just use the function that you already create findById3D($a) and use easily the instance you wanted to use.
I hope I helped you.

Query Builder with join and condition in Many to many relation

I have a many to many relation with Employee and MembreFamille. And i want to get all MembreFamilles that have an Employee.
This is my query :
class MembreFamilleRepository extends EntityRepository
{
public function getMembres($emp)
{
$qb = $this->createQueryBuilder('a');
$qb
->leftJoin('a.employees', 'employees');
$qb
->where('employees.id = :id')
->setParameter('id', $emp);
return $qb
->getQuery()
->getResult()
;
}
}
When I test this function in the controller , the function return 0 result.
The mapping in Employee Entity:
/**
* #ORM\ManyToMany(targetEntity="PFE\EmployeesBundle\Entity\MembreFamille", cascade={"persist"})
*/
private $membreFamilles;
The Mapping in MembreFamille Entity :
/**
* #ORM\ManyToMany(targetEntity="PFE\UserBundle\Entity\Employee", cascade={"persist"})
*/
private $employees;
The use in the Controller ($employee is an instance of Employee Entity ) :
$list = $em->getRepository('PFEEmployeesBundle:MembreFamille')->getMembres($employee->getId());
You need to add a JoinTable for your ManyToMany association and set the owning and inverse sides:
/**
* #ORM\ManyToMany(targetEntity="PFE\EmployeesBundle\Entity\MembreFamille",
* cascade={"persist"}, mapped="employees")
*/
private $membreFamilles;
.................................
/**
* #ORM\ManyToMany(targetEntity="PFE\UserBundle\Entity\Employee", cascade={"persist"}, inversedBy="membreFamilles")
* #ORM\JoinTable(name="membre_familles_employees")
*/
private $employees;
You can use a construction called "MEMBER OF".
class MembreFamilleRepository extends EntityRepository
{
public function getMembres($emp)
{
return $this->createQueryBuilder('a');
->where(':employee MEMBER OF a.employees')
->setParameter('employee', $emp)
->getQuery()
->getResult()
;
}
}
You can use a construction called "MEMBER OF"

Doctrine: QueryBuilder Where Exists

Answer
I was able to do the query using IS NOT EMPTY in the where clause.
/**
* Finds all developments having at least one image.
*
* #param string
* #return array
*/
public function withImages()
{
return $this->query->createQueryBuilder('d')
->where('d.images IS NOT EMPTY')
->getQuery()
->getResult();
}
Question
I am using the Doctrine ORM. I would like to be able to get all developments which have at least one image, such that for every Development selected in the query, the following property would be true. $development->getImages()->count()) > 0.
I have a Development entity which has a One to Many relationship with an Image entity.
/**
* The Development class provides an abstraction of a development.
*
* #Entity
* #HasLifecycleCallbacks
* #Table(name="developments")
**/
class Development extends BaseEntitiy {
/** #OneToMany(targetEntity="Exquisite\Entity\Image", mappedBy="development") **/
protected $images;
I have a DevelopmentRepository which has an instance of a EntityManager and an instance of the Repository for the Entity. I have made an attempt to do this in my withImages() method in the DevelopmentRepository class, but not having much luck.
class DevelopmentRepository implements DevelopmentRepositoryInterface {
/**
* Class Constructor
*
* #param Doctinre\ORM\EntityManager The EntityManager instance.
*/
public function __construct(EntityManager $em)
{
$this->em = $em;
$this->query = $this->em->getRepository('Exquisite\Entity\Development');
}
/**
* Finds all developments having at least one image.
*
* #param string
* #return array
*/
public function withImages()
{
$qb = $this->query->createQueryBuilder('d');
$qb2 = $this->em->createQueryBuilder();
return $qb->where($qb->expr()->exists($qb2->select('i.development')->from('Exquisite\Entity\Image', 'i')->getDql()))
->getQuery()
->getResult();
}
Any help would be much appreciated!
I had the same problem, and this worked for me.
I fixed just doing a join (would return only the orders with items):
return $this->createQueryBuilder('so')
->select('so')
->join('so.orderItems', 'soi')
->getQuery()
->getResult();
Or doing a sub query with DQL
SELECT so
FROM Entity\\SalesOrder so
WHERE (SELECT count(soi.id) FROM Entity\\SalesOrderItem soi WHERE soi.salesOrder = so.id ) > 0
I hope this can be helpful

Doctrine 2: Find by entity with composite primary key

I have two entity classes, Product and OrderEntry, defined like this (some annotations left out for compactness):
class Product {
/**
* #Id
*/
protected $id;
/**
* #Column()
* #Id
*/
protected $prodNumber;
/**
* #Column()
* #Id
*/
protected $group;
// more cols here
}
class OrderEntry {
// more cols here
/**
* #ManyToOne(targetEntity="Product")
* #JoinColumns({
* #JoinColumn(name="prodNumber", referencedColumnName="prodNumber"),
* #JoinColumn(name="group", referencedColumnName="group")
* })
*/
protected $Product;
}
Now I want to find an OrderEntry by its associated Product with the query builder. The most logical thing for me would be this:
class OrderEntryRepository extends EntityRepository {
public function findByProduct($product) {
$qb = $this->getQueryBuilder('o');
$qb->where($qb->expr()->eq('o.Product', '?1')
->setParameter(1, $product)
->setMaxResults(1);
return $qb->getQuery()->execute();
}
}
However, this throws an exception that says
A single-valued association path expression to an entity with a
composite primary key is not supported. Explicitly name the components
of the composite primary key in the query.
How do I name the components explicitly? I know I could do it with a JOIN, but I have no use for the Product in this case and it would just make the query more expensive.
You can't avoid joining the products table with the current mapping:
public function findByProduct($product) {
$qb = $this->getQueryBuilder('o');
$qb
->join('o.Product', 'p')
->where('p.prodNumber = ?1')
->setParameter(1, $product->getProdNumber())
->andWhere('p.group = ?2')
->setParameter(2, $product->getGroup())
;
return $qb->getQuery()->getOneOrNullResult();
}
You can add separate properties to OrderEntry, which would use the same columns as the JoinColumns, e.g.:
/**
* #Column(name="prodNumber")
*/
protected $prodNumber;
And then you can use them in conditions:
...
->where('o.prodNumber = ?1')
->setParameter(1, $product->getProdNumber()
...

Categories