Doctrine does not automatically load joined relation using GROUP BY - mysql

I have two Doctrine entity classes: Vertriebsschiene and Filiale:
/**
* Vertriebsschiene
*
* #ORM\Table(name="vertriebsschiene", indexes={
* #ORM\Index(columns={"name"}, flags={"fulltext"})
* }))
* #ORM\Entity(repositoryClass="CRMBundle\Repository\VertriebsschieneRepository")
*/
class Vertriebsschiene
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=100)
*/
private $name;
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Filiale", mappedBy="vertriebsschiene", fetch="EAGER")
*/
private $filialen;
...
}
/**
* Filiale
*
* #ORM\Table(name="filiale")
* #ORM\Entity(repositoryClass="CRMBundle\Repository\FilialeRepository")
*/
class Filiale extends Lieferant
{
/**
* #var Vertriebsschiene
*
* #ORM\ManyToOne(targetEntity="CRMBundle\Entity\Vertriebsschiene", inversedBy="filialen", fetch="EAGER")
*/
private $vertriebsschiene;
...
}
The Vertriebsschine objects have a non-uniqe name. Now I try to display a list of Vertriebsschiene objects with their Filiale objects.
My findAllQuery method looks like this:
/**
* #param User $user
* #return \Doctrine\ORM\Query
*/
public function findAllQuery(User $user){
$qb = $this->createQueryBuilder('v')
->select('v as vertriebsschiene')
->addSelect('COUNT(f) as filial_num')
->leftJoin('v.filialen', 'f')
->groupBy('v.name');
$this->restrictAccess($user, $qb);
return $qb->getQuery();
}
/**
* #param User $user
* #param $qb
*/
protected function restrictAccess(User $user, QueryBuilder &$qb)
{
if ($user->hasRole(RoleVoter::AUSSENDIENST)) {
$qb ->leftJoin('f.vertreter', 'u')
->leftJoin('u.vertretungen', 'vx')
->andWhere($qb->expr()->orX(
'f.vertreter = :userid',
'f.vertreter IS NULL',
$qb->expr()->andX(
'vx.proxy = :userid',
$qb->expr()->between(':currentDate', 'vx.start', 'vx.end')
)
))
->setParameter('userid', $user->getId())
->setParameter('currentDate', new \DateTime(), \Doctrine\DBAL\Types\Type::DATETIME);
}
}
My problem is, that the Vertriebsschiene::$filiale array collection is not automatically loaded, but is loaded for every Vertriebsschiene resulting in many DB connections.
This also has the problem, that the WHERE statement is ignored when the Vertriebsschiene::$filiale is fetched.
The COUNT(f) returns the correct amount of Filiale objects.
I suspect this is an issue with the GROUP BY statement.

I think the problem is that you do not tell doctrine to select filiale fields.
Try to add the filiale alias in your select :
public function findAllQuery(User $user){
$qb = $this->createQueryBuilder('v')
->select('v as vertriebsschiene', 'f')
->addSelect('COUNT(f) as filial_num')
->leftJoin('v.filialen', 'f')
->groupBy('v.name');
$this->restrictAccess($user, $qb);
return $qb->getQuery();
}
If you check your query in the profiler, i think you'll see that doctrine add a LEFT JOIN fialiale f0_ ON v0_.id = f0_.vertriebsschiene_id (or something like this but does not add SELECT ... f0_.id, f0_.xxxx.
So every time you'll call $vertriebsschiene->getFieliale()->getXXX() doctrine will have to execute the corresponding query to get the filiale data.

Related

How do I fetch association data from a list of users through a many to many relationship in doctrine query builder

I can't figure out how to do this using the query builder in Doctrine:
On a high level, I wan to fetch all of the userTags which are associated to each user, and I am fetching each user from user relationship table by their parent id.
Something like this:
from UserRelationship -> get users by parent_id -> also get each user's associated userTags. It's the last bit that I can't get the userTags. Idealy I would select only the id and name params from that table, as the rest, like company and users is redundant.
I have this query builder query in Symfony 3.* Doctrine 2.*
UserRelationshipRepository.php
protected function getBaseSubordinateQuery($managerId ){
$qb = $this->createQueryBuilder('r');
$qb
->select(
'u.id',
'u.username',
'u.email',
'u.first_name',
'u.last_name',
'u.lastLogin',
'u.userTags', // <-- How do I get these values?
'c.phone',
'c.position as jobTitle',
'co.name as company',
'c.createdAt',
'max(cl.expiresAt) as expiresAt',
'cl.expiresAt',
'c.yearsInPosition as titleSince',
'c.skype',
'c.linkedin',
'c.yearsAtCompany',
'up.path as imgPath',
'up.name as imgName',
'up.format as imgFormat'
)
->innerJoin( 'r.child', 'u' )
->innerJoin( 'u.clientInfo', 'c' )
->leftJoin( 'u.userTags', 'ut' )
->where('ut IS NOT NULL')
->leftJoin( 'u.company', 'co' )
->leftJoin( 'c.clientLicense', 'cl' )
->leftJoin( 'u.userPhotos', 'up' )
->where ( $qb->expr()->eq('r.parent', ':manager') )
->setParameter('manager', $managerId)
;
return $qb;
Note: as it is, that query gives me an error: 500 Internal Server Error. [Semantical Error] line 0, col 76 near 'userTags, c.phone,': Error: Invalid PathExpression. Must be a StateFieldPathExpression.
User.php
/**
* #var \Doctrine\Common\Collections\Collection|\Reddin\Bundle\CMSBundle\Entity\UserTag[]
*
* #ORM\ManyToMany(targetEntity="Reddin\Bundle\CMSBundle\Entity\UserTag", inversedBy="users")
* #ORM\JoinTable(name="users_user_tags")
*
* #JMS\Groups({"tags"})
* #JMS\MaxDepth(2)
*/
protected $userTags;
UserTag.php
class UserTag
{
use Timestampable;
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* Many UserTags belong to one Company.
*
* #ORM\ManyToOne(targetEntity="Reddin\Bundle\CMSBundle\Entity\Company", inversedBy="userTags", fetch="EXTRA_LAZY")
* #ORM\JoinColumn(name="company_id", referencedColumnName="id")
* #JMS\MaxDepth(2)
*/
private $company;
/**
* #var \Doctrine\Common\Collections\Collection|\Reddin\Bundle\UserAccountBundle\Entity\User[]
*
* #ORM\ManyToMany(targetEntity="Reddin\Bundle\UserAccountBundle\Entity\User", mappedBy="userTags", fetch="EXTRA_LAZY")
* #JMS\MaxDepth(2)
*/
private $users;
/**
* Company constructor.
*/
public function __construct()
{
$this->users = new ArrayCollection();
}
public function __toString()
{
return "" . $this->getName();
}
/**
* Get id
*
* #return int
*/
public function getId() {
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return UserTag
*/
public function setName($name) {
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName() {
return $this->name;
}
/**
* #return ArrayCollection|User[]
*/
public function getUsers()
{
return $this->users;
}
/**
* #return UserTag
*/
public function getCompany() {
return $this->company;
}
/**
* #param Company $company
* #return UserTag
*/
public function setCompany(Company $company = null) {
$this->company = $company;
return $this;
}
}
Replace
'u.userTags',
with
'ut'
This will solve the error you are getting, but probably you will get others.
I suggest change the query to:
$qb
->select(
'r',
'u',
'ut',
...
Also replace the 2nd where to:
->andWhere ( $qb->expr()->eq('r.parent', ':manager') )
Then you can fetch the tags for each user:
$userRel->getChild()->getUserTags();

Doctrine2 findOneBy returning wrong result

I am working on a legacy project using Symfony2.3 and the following versions of doctrine components (composer show -i | grep doctrine):
doctrine/annotations v1.1.2
doctrine/cache v1.2.0
doctrine/collections dev-master bcb5377
doctrine/common 2.4.x-dev c94d6ff
doctrine/data-fixtures dev-master 8ffac1c
doctrine/dbal 2.3.x-dev 59c310b
doctrine/doctrine-bundle dev-master a41322d
doctrine/doctrine-fixtures-bundle dev-master 3caec48
doctrine/doctrine-migrations-bundle dev-master 1a7f58d
doctrine/inflector dev-master 8b4b3cc
doctrine/lexer dev-master bc0e1f0
doctrine/migrations dev-master e960224
doctrine/orm 2.3.x-dev 66d8b43
I had to implement a simple db cache system.
I have a storage table and a service class to access it.
The table schema is as follows:
CREATE TABLE `mlp_api_storage` (
`id` TINYINT(4) NOT NULL AUTO_INCREMENT,
`key` VARCHAR(250) NOT NULL,
`value` TEXT NOT NULL,
`is_serialized` TINYINT(4) NOT NULL,
`created` DATETIME NOT NULL,
`ttl` INT(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `Index 2` (`key`)
)
The big issue I am having with this is one some occasions when I try to retrieve a value I get the wrong result.
I am tailing the log file and I can see the query being executed with the correct key value and if I run that directly in MySQL I will get the value I expect but not with doctrine.
Here is the relevant code of the storage service:
/**
* #param string $key
* #param string $value
* #param mixed $ttl
* #return self
*/
public function set($key, $value, $ttl = null)
{
$em = $this->getEntityManager();
$store = $this->fetchEntity($key);
$update = true;
if (!$store) {
$update = false;
$store = new Storage();
}
$store->setKey($key);
if (is_object($value) || is_array($value)) {
$value = serialize($value);
$store->setIsSerialized(true);
} else {
$store->setIsSerialized(false);
}
$store->setValue($value);
$store->setTtl($this->calculateTTL($ttl));
$store->setCreated(new \DateTime());
if ($update) {
$em->merge($store);
} else {
$em->persist($store);
}
$em->flush($store);
return $this;
}
/**
* #param string $key
* #param mixed $default
* #return string|null
*/
public function fetch($key, $default = null)
{
$res = $this->fetchEntity($key);
if ($res) {
if ($this->isValidStorage($res)) {
$value = $res->getValue();
if ($res->getIsSerialized()) {
$value = unserialize($value);
}
return $value;
}
$this->remove($res);
}
if ($default) {
$res = (is_callable($default)) ? $default($this) : $default;
} else {
$res = null;
}
return $res;
}
/**
* #param string $key
* #return Storage
*/
private function fetchEntity($key)
{
/* #var $res Storage */
$res = $this->getEntityManager()
->getRepository(Storage::class)
->findOneBy(['key' => $key]);
if ($res) {
return $res;
}
return null;
}
So using the debugger (and mysql log) I can see that everything is fine up until the line with : ->findOneBy(['key' => $key]);
This will be set with an entity with a different key and value.
I have setup a small example:
$storage->set('test', '1111111111111111111');
$storage->set('test2', '22222222222222222222');
var_dump($storage->fetch('test'));
var_dump($storage->fetch('test2'));
This is returning:
string '1111111111111111111' (length=19)
string '1111111111111111111' (length=19)
Here is what is in the table:
Here is the mysql log output:
66 Query SELECT t0.`key` AS key1, t0.value AS value2, t0.is_serialized AS is_serialized3, t0.created AS created4, t0.ttl AS ttl5, t0.id AS id6 FROM storage t0 WHERE t0.`key` = 'test' LIMIT 1
66 Query SELECT t0.`key` AS key1, t0.value AS value2, t0.is_serialized AS is_serialized3, t0.created AS created4, t0.ttl AS ttl5, t0.id AS id6 FROM storage t0 WHERE t0.`key` = 'test2' LIMIT 1
Am I doing something wrong with doctrine2? I am updating the entity instead of deleting because doctrine will try to insert before deleting.
Thanks!
UPDATE:
Here is the entity code
namespace PitchBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Storage
*
* #ORM\Table(name="storage")
* #ORM\Entity
*/
class Storage
{
/**
* #var string
*
* #ORM\Column(name="`key`", type="string", length=250, nullable=false)
*/
private $key;
/**
* #var string
*
* #ORM\Column(name="value", type="text", nullable=false)
*/
private $value;
/**
* #var boolean
*
* #ORM\Column(name="is_serialized", type="boolean", nullable=false)
*/
private $isSerialized;
/**
* #var \DateTime
*
* #ORM\Column(name="created", type="datetime", nullable=false)
*/
private $created;
/**
* #var integer
*
* #ORM\Column(name="ttl", type="integer", nullable=true)
*/
private $ttl;
/**
* #var boolean
*
* #ORM\Column(name="id", type="boolean")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* Set key
*
* #param string $key
* #return Storage
*/
public function setKey($key)
{
$this->key = $key;
return $this;
}
/**
* Get key
*
* #return string
*/
public function getKey()
{
return $this->key;
}
/**
* Set value
*
* #param string $value
* #return Storage
*/
public function setValue($value)
{
$this->value = $value;
return $this;
}
/**
* Get value
*
* #return string
*/
public function getValue()
{
return $this->value;
}
/**
* Set isSerialized
*
* #param boolean $isSerialized
* #return Storage
*/
public function setIsSerialized($isSerialized)
{
$this->isSerialized = $isSerialized;
return $this;
}
/**
* Get isSerialized
*
* #return boolean
*/
public function getIsSerialized()
{
return $this->isSerialized;
}
/**
* Set created
*
* #param \DateTime $created
* #return Storage
*/
public function setCreated($created)
{
$this->created = $created;
return $this;
}
/**
* Get created
*
* #return \DateTime
*/
public function getCreated()
{
return $this->created;
}
/**
* Set ttl
*
* #param integer $ttl
* #return Storage
*/
public function setTtl($ttl)
{
$this->ttl = $ttl;
return $this;
}
/**
* Get ttl
*
* #return integer
*/
public function getTtl()
{
return $this->ttl;
}
/**
* Get id
*
* #return boolean
*/
public function getId()
{
return $this->id;
}
}
You have an error in your entity id annotation. Currently it is:
* #ORM\Column(name="id", type="boolean")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
but it should be
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")

Wrong ID given in Doctrine2 OneToOne mapping

I am seriously stack right now with the problem I have occurred with OneToOne mapping.
So let me show what I currently have:
OrderItem entity
/**
* OrderItem
*
* #ORM\Table(name="order_item")
* #ORM\Entity
*/
class OrderItem
{
/**
* #var integer
*
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
// ... //
/**
* #ORM\OneToOne(targetEntity="UserPricingOption", mappedBy="orderItem")
*/
private $userPricingOption;
/**
* #ORM\ManyToOne(targetEntity="Order", inversedBy="items")
* #ORM\JoinColumn(referencedColumnName="id")
*/
private $order;
// ... //
UserPricingOption entity
/**
* UserPricingOption
*
* #ORM\Table(name="user_pricing_option")
* #ORM\Entity
* #ExclusionPolicy("all")
*/
class UserPricingOption
{
/**
* #var integer
*
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
// ... //
/**
* #ORM\OneToOne(targetEntity="OrderItem", inversedBy="userPricingOption")
* #ORM\JoinColumn(referencedColumnName="id")
*/
private $orderItem;
// ... //
so generated tables look like this:
order_item table
* `id` 5
user_pricing_option table
* `id` 12
* `order_item_id` 5
So now I am trying to do the following:
echo $orderItem->getId(); // gives 5, `GOOD`
echo $orderItem->getUserPricingOption()->getId(); // gives 5 `BAD` (completely different user_pricing_option row), it should return 12.
I seriously have no idea why is that, please find the raw Doctrine query:
Keep in mind that query contains way more info than the showed examples above
SELECT t0.id AS id_1, t0.guid AS guid_2, t0.created_at AS created_at_3, t0.modified_at AS modified_at_4, t5.id AS id_6, t5.guid AS guid_7, t5.created_at AS created_at_8, t5.modified_at AS modified_at_9, t5.user_id AS user_id_10, t5.order_item_id AS order_item_id_11, t5.pricing_option_id AS pricing_option_id_12, t13.id AS id_14, t13.guid AS guid_15, t13.created_at AS created_at_16, t13.modified_at AS modified_at_17, t13.user_id AS user_id_18, t13.order_item_id AS order_item_id_19, t13.product_variant_id AS product_variant_id_20, t0.order_invoice_id AS order_invoice_id_21, t0.order_id AS order_id_22 FROM order_item t0 LEFT JOIN user_pricing_option t5 ON t5.order_item_id = t0.id LEFT JOIN user_product_variant t13 ON t13.order_item_id = t0.id WHERE t0.order_id = ? [131]
So basically $orderItem->getUserPricingOption()->getId() always returns the same ID as $orderItem->getId();
Anyone possibly see what is going on?
OneToOne bidireccional?
Try to define in joinColumn the name with column to reference id.
/**
* #ORM\OneToOne(targetEntity="OrderItem", inversedBy="userPricingOption")
* #ORM\JoinColumn(name="order_item_id", referencedColumnName="id")
*/
private $orderItem;
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#one-to-one-bidirectional

Doctrine query builder - any of joined elements

I have the following entities (simplified):
a work is composed of multiple tasks. Each task has a field storing the time it should be due and a field storing the time it has been completed.
/**
* Work
*
* #ORM\Entity
*/
class Work
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Task", mappedBy="work", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="task_work_id", referencedColumnName="id")
*/
protected $task;
/**
* Constructor
*/
public function __construct()
{
$this->task = new ArrayCollection();
}
}
/**
* Task
*
* #ORM\Entity
*/
class Task
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Work", inversedBy="task")
* #ORM\JoinColumn(name="work_id", referencedColumnName="id")
*/
private $work;
/**
* #var \DateTime
*
* #ORM\Column(name="completed", type="datetime", nullable=true)
*/
private $completed;
/**
* #var \DateTime
*
* #ORM\Column(name="duedate", type="datetime", nullable=true)
*/
private $duedate;
}
I am trying to build a query to retrieve all works that have uncompleted overdue tasks.
I initially thought something like this:
$repository = $this->getEntityManager()
->getRepository('MyBundle:Work');
$query = $repository->createQueryBuilder('v');
$query->leftJoin('v.task', 't');
$query->andWhere(
$query->expr()->isNull('t.completed')
)
->andWhere(
$query->expr()->gte('t.duedate', ':now')
)
;
$query->setParameter('now', new \DateTime(), \Doctrine\DBAL\Types\Type::DATETIME);
However, this retrieves works whose all tasks are overdue (I think).
So I tried with a sub-query:
$repository = $this->getEntityManager()
->getRepository('MyBundle:Work');
$query = $repository->createQueryBuilder('v');
$query2 = $this->getEntityManager()
->createQueryBuilder();
$query2->select('t')
->from('v.task', 't');
$query2->andWhere(
$query->expr()->isNull('t.completed')
)
->andWhere(
$query->expr()->gte('t.duedate', ':now')
)
;
$query2->setParameter('now', new \DateTime(), \Doctrine\DBAL\Types\Type::DATETIME);
$query->andWhere(
$query->expr()->any(
$query2->getDql()
)
);
But I get the following error:
QueryException: [Syntax Error] line 0, col 104: Error: Expected known function, got 'ANY'
Any idea on how I could write the query?
Thank you in advance!
You have not defined a repository for $query2

Can I set up a Symfony2 ORM inheritance relationship using MySQL?

I have one table tech_note and I would like to have 3 tables more tech_note_es, tech_note_en and tech_note_fr (in a future many more tables). And the tech_note can be in the 3 tables at same time.
My idea is attach to tech_note_en and automatically receive attributes from tech_note.
SECOND EDIT:
I would like somthing like that: I search in Tech_note_EN by ID=3 (I recive fields from tech_note_EN with ID=3 and fields tech_note with ID=3.
But if I search in Tech_note_ES BY ID=3 (I recive fields from tech_note_ES with ID=3 and tech_note fields from tech_note with ID=3 (the same fields from above)
The tech_note have a ID=3. Tech_note_EN have ID=3, and Tech_note_ES have ID=3
EDIT WITH NEW CLASSES:
Now I have this error:
Entity 'EVTS\FrontendBundle\Entity\Tech_note_en' has a composite identifier but uses an ID generator other than manually assigning (Identity, Sequence). This is not supported.
TECH_NOTE
abstract class Tech_note {
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var datetime $control_date
*
* #ORM\Column(name="control_date", type="datetime")
*/
private $control_date;
/**
* #var string $comment
*
* #ORM\Column(name="comment", type="string")
*/
private $comment;
TECH_NOTE_EN
class Tech_note_en extends Tech_note {
/**
* #var integer $tech_note_id
*
* #ORM\Id
* #ORM\Column(name="tech_note_id", type="integer")
* #ORM\OneToOne(targetEntity="tech_note")
* #ORM\JoinColumn(name="tech_note_id",referencedColumnName="id")
*/
private $tech_note_id;
/**
* #var text $symptom
*
* #ORM\Column(name="symptom", type="text")
*/
private $symptom;
/**
* #var text $cause
*
* #ORM\Column(name="cause", type="text")
*/
private $cause;
/**
* #var string $solution
*
* #ORM\Column(name="solution", type="string")
*/
private $solution;
OLD CLASSES
TECH_NOTE
class Tech_note {
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var datetime $control_date
*
* #ORM\Column(name="control_date", type="datetime")
*/
private $control_date;
/**
* #var string $comment
*
* #ORM\Column(name="comment", type="string")
*/
private $comment;
TECH_NOTE_EN
class Tech_note_en
{
/**
* #var integer $tech_note_id
*
* #ORM\Id
* #ORM\Column(name="tech_note_id", type="integer")
* #ORM\OneToOne(targetEntity="tech_note")
* #ORM\JoinColumn(name="tech_note_id",referencedColumnName="id")
*/
private $tech_note_id;
/**
* #var text $symptom
*
* #ORM\Column(name="symptom", type="text")
*/
private $symptom;
/**
* #var text $cause
*
* #ORM\Column(name="cause", type="text")
*/
private $cause;
/**
* #var string $solution
*
* #ORM\Column(name="solution", type="string")
*/
private $solution;
There is an awesome solution to your problem, which is single table inheritance
What this does is basically Have one central table with general fields, and links to other table with specific fields.
In your case I would use this architectural model:
abstract class TechNote (abstract common entity, which contains common fields)
class TechNoteEn extends TechNote (specific entity which inherits common fields and adds specific EN fields)
and so on.
I find this as the best architectural solution for your problem.
That's your ORM's job.
// Assuming $tech_note_en is a Tech_note_en instance
$tech_note_en->getTechNote();
(something like this ^^)