Doctrine 2: Find by entity with composite primary key - php

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()
...

Related

Filter sub entity in doctrine

I have two entities that have a one to many relation.
There is a project:
class Project
{
// ...
/**
* #var \Doctrine\ORM\PersistentCollection|Template[]
*
* #ORM\OneToMany(targetEntity="Template", mappedBy="project")
*/
private $templates;
// ...
}
And I have templates which might be limited to certain users:
class Template
{
/**
* #var Project
*
* #ORM\ManyToOne(targetEntity="Project", inversedBy="templates")
* #ORM\JoinColumn(referencedColumnName="project_id", nullable=false)
*/
private $projectId;
/**
* #var array|null
*
* #ORM\Column(type="simple_array", nullable=true)
*/
private $userIds;
// ...
}
I now want to get all the projects. But the templates variable of each project should only have the templates that have either userIds NULL or the userId of the current user.
In my repository I already tried the following:
public function findForUser(int $userId): array
{
$qb = $this->entityManager->createQueryBuilder();
$qb->select('p')
->from(Project::class, 'p')
->leftJoin(Template::class, 't', Join::WITH, 't.project = p.projectId')
->where(
$qb->expr()
->isNull('t.userIds')
)
->orWhere('FIND_IN_SET(:userId, t.userIds) > 0');
$qb->setParameter(':userId', $userId);
return $qb->getQuery()
->execute();
}
But when I call getTemplates() I still have all templates in it even the ones the user is not allowed to see.
I also tried instead of ->select('p') to use ->select('p, t') as I hoped doctrine would then already fill the templates field with the selected templates but instead it then returns a mixed array of Templates and Projects.
I think that you entity schema is not quite optimised for this kind of operation.
You must not save the IDs of the Users in a simple array, but instead, use a ManyToMany relation :
class Template
{
// ...
/**
* #var User[]|Collection
*
* #ORM\ManyToMany(targetEntity="User", inversedBy="templates")
*/
private $users;
}
Then, the dql query should look something like that :
public function findForUser(User $user): array
{
$qb = $this->entityManager->createQueryBuilder();
$qb->select('p')
->from(Project::class, 'p')
->leftJoin(Template::class, 't', Join::WITH, 't.project = p.projectId')
->leftJoin(User::class, 'u', Join::WITH, 't.users = u.id')
->where('COUNT(t.users) = 0')
->orWhere('u = :user');
$qb->setParameter(':user', $user);
return $qb->getQuery()
->execute();
}

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 : Filter query by child attribute

I'm trying to execute a query to obtain all the Repos from an specific Organization, but it returns an empty result... (I have data in the DB!)
Repos and Organization are a Many to Many relation.
Here's the Repos Entity:
/**
* Repos
*
* #ORM\Table(name="repos")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ReposRepository")
*/
class Repos
{
...
/**
*
* #ManyToMany(targetEntity="Organization", inversedBy="repos")
*
*/
protected $orgs;
...
Here's the Organization Entity:
/**
* Organization
*
* #ORM\Table(name="organization")
* #ORM\Entity(repositoryClass="AppBundle\Repository
* \organizationRepository")
*/
class Organization
{
...
/**
*
* #ORM\ManyToMany(targetEntity="Repos", mappedBy="orgs")
*
*/
protected $repos;
...
}
Here's the Repository with the QueryBuilder:
/**
* ReposRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class ReposRepository extends \Doctrine\ORM\EntityRepository
{
public function findReposByName($name){
$qb = $this->createQueryBuilder('r');
// Build query
$qb->select('r')
->andWhere(
':searchName MEMBER OF r.orgs'
);
$qb->setParameter('searchName',$name);
return $qb->getQuery()->getResult();
}
}
The $name is the name of an Organization and I want to obtain all the Repos with the the same organization name.
If you want to filter with the name attribute of your Organization, you have to do a join query.
With the query builder
public function findReposByName($name){
$qb = $this->createQueryBuilder('r');
$qb->select('r')
->join('r.orgs', 'o')
->where('o.name = :searchName')
->setParameter('searchName', $name);
return $qb->getQuery()->getResult();
}
In DQL
public function findReposByName($name){
$dql =<<<EOF
SELECT r
FROM AppBundle:Repos r
INNER JOIN r.orgs o
WHERE o.name = :searchName
EOF;
return $this->_em
->createQuery($dql)
->setParameter('searchName',$name)
->getResult();
}

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

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;
}

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"

Categories