Doctrine2 query error in OneToMany relation - php

I've got a doctrine2 problem with a MySQL database:
I have a model User and a model Documents. Each User may have 0 or n Documents. Each Document is assigned to exactly one User. My models:
User
<?php
namespace Entity;
/**
* #Entity(repositoryClass="Entity\Repository\UserRepository")
* #Table(name="user")
*/
class User extends Object
{
/**
* #Id #GeneratedValue(strategy="UUID")
* #Column(type="string", length=36)
* #var string
*/
protected $id;
/**
* #OneToMany(targetEntity="\Entity\Document", mappedBy="user")
*/
private $documents;
public function __construct($options = array())
{
$this->documents = new \Doctrine\Common\Collections\ArrayCollection;
}
}
Document
<?php
namespace Entity;
/**
* #Entity(repositoryClass="Entity\Repository\DocumentRepository")
* #Table(name="document")
*/
class Document extends Object
{
/**
* #Id #Column(type="string")
* #var string
*/
protected $id;
/**
* #ManyToOne(targetEntity="\Entity\User", inversedBy="documents")
* #JoinColumn(name="user_id", referencedColumnName="id")
* #var User
*/
private $user;
}
Now I want to get the User of a given Document ID. The SQL-query would be:
SELECT u.*
FROM `user` u
INNER JOIN `document` d ON d.user_id = u.id
WHERE d.id = 'mydocumentid'
But this does't work:
$user = $queryBuilder
->select('u.*')
->from('\\Entity\\User', 'u')
->innerJoin('\\Entity\\Document', 'd', \Doctrine\ORM\Query\Expr\Join::ON, 'd.user_id = u.id')
->where('d.id = :documentId')
->setParameter('documentId', 'mydocumentid')
->setMaxResults(1)
->getQuery()
->getSingleResult();
Also the direct query doesn't work:
$query = $em->createQuery('
SELECT
u.*
FROM
Entity\\User u
INNER JOIN
Entity\\Document d ON d.user_id = u.id
WHERE
d.id = "mydocumentid"
');
Could you please help me to get this run?
Error message
[Semantical Error] line 0, col 66 near 'd ON d.user_id': Error: Identification Variable \Entity\Document used in join path expression but was not defined before.

Instead of using:
->innerJoin('\\Entity\\Document', 'd', \Doctrine\ORM\Query\Expr\Join::ON, 'd.user_id = u.id')
Try using this:
->innerJoin('u.documents', 'd', \Doctrine\ORM\Query\Expr\Join::ON, 'd.user_id = u.id')
This is because Doctrine needs to know to which field in user the documents have to be joined. Knowing the field Doctrine will fetch the target entity and that way Doctine directly knows the class.

Related

Doing a COUNT(*) GROUP BY based on a ManyToMany relation using DQL?

I have two entities Person and Skill, where a Person may have multiple skills.
Person
/**
* #ORM\Entity(repositoryClass="App\Repository\PersonRepository")
*/
class Person
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Skill", inversedBy="people")
*/
private $skills = [];
// other fields and getters/setters
}
Skill
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\SkillRepository")
*/
class Skill
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Person", mappedBy="skills")
*/
private $people = [];
// other fields and getters/setters
}
I have a form where I can filter people by skills, each skill is a checkbox and I want the number of people having that skill along with the checkbox's label.
The result is that:
I got it working using the following native query:
SELECT s.id, COUNT(*) AS c
FROM skill s
JOIN person_skill ps /* table required by the M2M relation between person and skill */
ON s.id = ps.skill_id
GROUP BY s.id
As you can see, I require a JOIN on the ManyToMany table in order to get those counts.
How could I do this using Doctrine's DQL instead of using a native query?
Actually when mapping entities with relations, Doctrine uses a custom object named ArrayCollection.
It comes with many methods, such as filter() and count().
You can add a method to your skill entity if you want that would use the count method of the ArrayCollection (people).
To make sure you use the ArrayCollection properly you'll have to change your Skill class like this:
class Skill
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Person", mappedBy="skills")
*/
private $people; //<-- Removed the default array definition
public function __construct()
{
$this->people = new ArrayCollection(); //Add this line in your constructor
}
public function countPeople()
{
return $this->people->count(); //Will return the number of people joined to the skill
}
// other fields and getters/setters
}
Allright, I found the solution:
$rows = $this->_em->createQueryBuilder('s')
->select('s.id, COUNT(p.id) AS c')
->from(Skill::class, 's')
->join('s.people', 'p')
->groupBy('s.id')
->getQuery()
->getArrayResult();
It generates the following query:
SELECT s0_.id AS id_0, COUNT(p1_.id) AS sclr_1
FROM skill s0_
INNER JOIN person_skill p2_
ON s0_.id = p2_.skill_id
INNER JOIN person p1_
ON p1_.id = p2_.person_id
GROUP BY s0_.id

createQueryBuilder / Join Column - Symfony

I have a trouble to implement this SQL Request :
SELECT *
FROM project
LEFT JOIN user
ON project.idAuthor=user.id
WHERE project.isVisible = 1 AND
user.role = 'agency'
To a simple Symfony Query Builder :
$query = $this->createQueryBuilder('p')
->leftJoin('WebAwardsBundle:User', 'u')
->where('p.isVisible = 1')
->andwhere("u.role = 'agency'")
->orderBy('p.id', 'DESC')
->getQuery();
The response of this query give me all the project, include the project when role !== agency ...
I don't know where I can put the ON project.idAuthor=user.id
Mapping :
Project :
/**
* Project
*
* #ORM\Table(name="project")
*#ORM\Entity(repositoryClass="WebAwardsBundle\Repostory\ProjectRepository")
*/
class Project
{
...
/**
* #var int
*
* #ORM\ManyToOne(targetEntity="User", inversedBy="projects")
* #ORM\JoinColumn(name="idAuthor", referencedColumnName="id")
*/
private $idAuthor;
...
User :
/**
* User
*
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="WebAwardsBundle\Repository\UserRepository")
*/
class User implements UserInterface, \Serializable{
...
/**
* #ORM\OneToMany(targetEntity="Project", mappedBy="idAuthor")
*/
private $projects;
...
The correct answer to implement this SQL Request :
SELECT *
FROM project
LEFT JOIN user
ON project.idAuthor=user.id
WHERE project.isVisible = 1 AND
user.role = 'agency'
is to join the correct column of the entity (project.idAuthor to user in this case) :
$query = $this->createQueryBuilder('p')
->join('p.idAuthor', 'u')
->where('p.isVisible = 1')
->andWhere("u.role = 'agency'")
->orderBy('p.id', 'DESC')
->getQuery();

Doctrine 2 ORM - Search in oneToMany relation (QueryBuilder)

I'm trying resolve following problem.
I have OrderEntity and in OrderEntity is relation ship to OrderStatusHistory (OneToMany).
/**
* #ORM\OneToMany(targetEntity="OrderStatusHistory", mappedBy="order")
*/
protected $statuses;
OrderStatusHistory entity:
class OrderStatusHistory extends BaseEntity
{
/**
* #ORM\ManyToOne(targetEntity="Order",cascade={"persist"})
* #ORM\JoinColumn(name="order_id", referencedColumnName="id")
**/
protected $order;
/**
* #ORM\ManyToOne(targetEntity="OrderStatus",cascade={"persist"})
* #ORM\JoinColumn(name="status_id", referencedColumnName="id")
**/
protected $status;
/**
* #ORM\Column(type="datetime")
*/
protected $time;
public function __construct()
{
$this->time = new \DateTime('now');
}
}
And now i need select order where was last status with ID 4. I'm tried many variants with QB, but any of them does not work.
There's several ways, you can join to that table and use WITH, or you can use WHERE, or you can just use the ID. Here's some examples:
Using DQL:
$dql = "SELECT o
FROM Order o
WHERE o.orderStatus = 4"
or
$dql = "SELECT o
FROM Order o
INNER JOIN o.orderStatus orderStatus
WITH orderStatus.id = 4"
OR:
$dql = "SELECT o
FROM Order o
INNER JOIN o.orderStatus orderStatus
WHERE orderStatus.id = 4"
Or Query Builder:
$queryBuilder->select('o')
->from('Order', 'o')
->where('o.orderStatus = 4');

Issue with joined entity results: Undefined index: id in Doctrine/ORM/UnitOfWork.php

I have the following code:
$rsm = new ResultSetMapping();
$rsm->addEntityResult('App\MainBundle\Entity\InstagramShopPicture', 'p');
$rsm->addFieldResult('p', 'id', 'id');
$rsm->addFieldResult('p','lowresimageurl','lowresimageurl');
$rsm->addFieldResult('p','medresimageurl','medresimageurl');
$rsm->addFieldResult('p','highresimageurl','highresimageurl');
$rsm->addFieldResult('p','caption','caption');
$rsm->addFieldResult('p','numberoflikes','numberoflikes');
$rsm->addFieldResult('p','numberofdislikes','numberofdislikes');
$rsm->addJoinedEntityResult('App\MainBundle\Entity\InstagramShop', 's', 'p', 'shop');
$rsm->addFieldResult('s', 'id', 'id');
$rsm->addFieldResult('s', 'username', 'username');
$query = $em->createNativeQuery('SELECT picture.id, picture.lowresimageurl, picture.medresimageurl, picture.highresimageurl, picture.caption, picture.numberoflikes, picture.numberofdislikes, shop.id AS shop_id , shop.username
FROM App_instagram_picture_category category
INNER JOIN App_instagram_shop_picture picture ON category.picture_id = picture.id
INNER JOIN App_instagram_shop shop ON shop.id = picture.shop_id
WHERE category.first_level_category_id = ?
AND picture.deletedAt IS NULL
AND shop.deletedAt IS NULL
AND shop.isLocked = 0
AND shop.expirydate IS NOT NULL
AND shop.expirydate > ?
AND shop.owner_id IS NOT NULL
GROUP BY shop.id
LIMIT ?'
, $rsm);
$query->setParameter(1, 10);
$query->setParameter(2, '2014-05-20');
$query->setParameter(3, 10);
$itemsFromDifferentShops = $query->getResult();
However I am constantly getting the following error/warning:
Notice: Undefined index: id in /Users/Alex/Sites/App/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php on line 2433
Here's what my entity looks like:
class InstagramShop
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
*
* #var string
* #ORM\Column(name="username", type="string", nullable=true)
*/
private $username;
/**
* #Exclude()
* #ORM\OneToMany(targetEntity="InstagramShopPicture", mappedBy="shop", cascade=
{"persist"})
* #ORM\OrderBy({"createdtimestamp" = "DESC"})
*/
protected $userPictures;
}
class InstagramShopPicture
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #Exclude()
* #ORM\ManyToOne(targetEntity="InstagramShop", inversedBy="userPictures")
* #ORM\JoinColumn(name="shop_id", referencedColumnName="id", nullable=false, onDelete="CASCADE")
*/
protected $shop;
}
Why is this? How do I fix it? My suspicion is because there are two id's. One is the product id and the other is the shop id, both having the same reference. But I tried changing it and it still gives me the warning.
I had a similar problem some weeks ago, and I could solve it as below:
$em = $this->getEntityManager();
$rsm = new \Doctrine\ORM\Query\ResultSetMapping();
$rsm->addEntityResult('MyBundle:Actor', 'a');
$rsm->addFieldResult('a', 'id', 'id');
$rsm->addFieldResult('a', 'name', 'name');
$rsm->addFieldResult('a', 'surname', 'surname');
$rsm->addMetaResult('a', 'presentation_id', 'presentation_id');
$query = $em->createNativeQuery(
'SELECT a.id, a.name, a.surname, a.presentation_id
FROM actors AS a
INNER JOIN presentations AS p
WHERE p.id = a.presentation_id
AND p.finished = 0
AND p.id IN (?)', $rsm);
$query->setParameter(1, $presentations);
$actors = $query->getResult();
Where there are some differences with your code, but perhaps you could adapt it in order to get what you want.
Note the addMetaResult() function which is used for foreign keys or discriminator columns.
You can read about this in chapter 17.3.5 of the official Doctrine documentation http://doctrine-orm.readthedocs.org/en/latest/reference/native-sql.html.
In this function, I pass presentation_id as parameter, which is the field for the foreign key of Presentation in the actors table. Note that I don't request information for the presentation, however, Doctrine hydrates the object and I can access to all the information of the presentation from the Actor entities of the result in this way:
foreach($actors as $a){
//
// Some code here
//
$actorId = $a->getId();
$actorName = $a->getName();
$actorSurname = $a->getSurname();
$preTitle = $a->getPresentation()->getTitle();
$preDesc = $a->getPresentation()->getDescription();
$preDirector = $a->getPresentation()->getDirector()->getFullName();
//
// Some code here
//
}
I think that you could solve your problem in this way too. Maybe, you could do something like this:
$rsm = new ResultSetMapping();
$rsm->addEntityResult('App\MainBundle\Entity\InstagramShopPicture', 'p');
$rsm->addFieldResult('p', 'id', 'id');
$rsm->addFieldResult('p','lowresimageurl','lowresimageurl');
$rsm->addFieldResult('p','medresimageurl','medresimageurl');
$rsm->addFieldResult('p','highresimageurl','highresimageurl');
$rsm->addFieldResult('p','caption','caption');
$rsm->addFieldResult('p','numberoflikes','numberoflikes');
$rsm->addFieldResult('p','numberofdislikes','numberofdislikes');
$rsm->addMetaResult('p', 'shop_id', 'shop_id');
$query = $em->createNativeQuery('SELECT picture.id, picture.lowresimageurl, picture.medresimageurl, picture.highresimageurl, picture.caption, picture.numberoflikes, picture.numberofdislikes
FROM App_instagram_picture_category AS category
INNER JOIN App_instagram_shop_picture AS p ON category.picture_id = p.id
INNER JOIN App_instagram_shop AS shop ON shop.id = p.shop_id
WHERE category.first_level_category_id = ?
AND p.deletedAt IS NULL
AND shop.deletedAt IS NULL
AND shop.isLocked = 0
AND shop.expirydate IS NOT NULL
AND shop.expirydate > ?
AND shop.owner_id IS NOT NULL
GROUP BY shop.id
LIMIT ?'
, $rsm);
$query->setParameter(1, 10);
$query->setParameter(2, '2014-05-20');
$query->setParameter(3, 10);
$itemsFromDifferentShops = $query->getResult();
Thus, you would get the Pictures, and through these, you could get the information of Shops which you need.
The problem is with
$rsm->addFieldResult('s', 'id', 'id');
According to definition
/**
* Adds a field result that is part of an entity result or joined entity result.
*
* #param string $alias The alias of the entity result or joined entity result.
* #param string $columnName The name of the column in the SQL result set.
* #param string $fieldName The name of the field on the (joined) entity.
*/
public function addFieldResult($alias, $columnName, $fieldName)
The second argument must be the name of the column in the SQL result set NOT table
shop.id AS shop_id
use $rsm->addFieldResult('s', 'shop_id', 'id');

Double association between 2 tables with Doctrine2 PHP

I'm trying to do an association of 5 objects with Doctrine2 (PHP).
I'm using PostgreSQL.
Here is the Database schema:
Database schema
A company may have many Hubs, each Hub have one Harbor.
A company may have many Line, each Line have a Linelist.
A Linelist have 2 Harbors.
For example, a Linelist is "Los Angeles-Seattle", and multiple companies may own it thanks to the Line table.
I'm trying to query all the Hub, Harbor, Linelist, and Line for one company.
I have the SQL query:
SELECT *
FROM hub h
JOIN harbor a
ON a.id = h.harbor_id
JOIN linelist l
ON (l.harborstart_id = a.id OR l.harborend_id = a.id)
JOIN line m
ON m.linelist_id = l.id
WHERE h.company_id = 41
AND m.company_id = 41"
I'm trying to do the same using DQL.
I tried this, but it doesn't worked:
$query = $this->getEntityManager()
->createQuery('SELECT h, a, l, m
FROM AmGameBundle:Hub h
JOIN h.harbor a
JOIN a.linelist l
JOIN l.line m
WHERE h.company = :company_id
AND m.company = :company_id')
->setParameter('company_id', $company_id);
As a result, I only have the LineList and Line objects matching harborstart_id, but I want the one matching either harborstart_id or harborend_id.
Do you think this is possible in DQL?
It might be better to change the relation between Harbor and Linelist for a many to many?
I think that's a matter of defining 2 relations from harbor to linelist in your Entities. I imagine you have something like
<?php
/**
* #Entity
* #Table(name="LineList")
*/
class LineList {
/**
* #var object $startHarbor
* #ManyToOne(targetEntity="Harbor", inversedBy="startHarbors")
* #JoinColumn(name="harborstart_id", referencedColumnName="id", nullable=FALSE)
*/
protected $startHarbor;
}
/**
* #Entity
* #Table(name="Harbor")
*/
class Harbor {
/**
* #var object $startHarbors
* #OneToMany(targetEntity="LineList", mappedBy="startHarbor")
*/
protected $startHarbors;
}
That will let you join Harbors to LineLists via harborstart_id (you named the variable linelist, but I think now it's better to change the identifiers as there will be 2 referring to the same foreign table), then if you want to harborend_id
<?php
/**
* #Entity
* #Table(name="LineList")
*/
class LineList {
/**
* #var object $startHarbor
* #ManyToOne(targetEntity="Harbor", inversedBy="startHarbors")
* #JoinColumn(name="harborstart_id", referencedColumnName="id", nullable=FALSE)
*/
protected $startHarbor;
/**
* #var object $endHarbor
* #ManyToOne(targetEntity="Harbor", inversedBy="endHarbors")
* #JoinColumn(name="harborend_id", referencedColumnName="id", nullable=FALSE)
*/
protected $endHarbor;
}
/**
* #Entity
* #Table(name="Harbor")
*/
class Harbor {
/**
* #var object $startHarbors
* #OneToMany(targetEntity="LineList", mappedBy="startHarbor")
*/
protected $startHarbors;
/**
* #var object $endHarbors
* #OneToMany(targetEntity="LineList", mappedBy="endHarbor")
*/
protected $endHarbors;
}
Now you can change the DQL to:
$query = $this->getEntityManager()
->createQuery('SELECT h, a, sh, eh, m
FROM AmGameBundle:Hub h
JOIN h.harbor a
JOIN a.startHarbors sh
JOIN a.endHarbors eh
JOIN l.line m
WHERE h.company = :company_id
AND m.company = :company_id')
->setParameter('company_id', $company_id);
That should get you in the right direction. If it becomes troublesome though, a many-to-many approach as you speculated should be a well documented solution.

Categories