Doctrine query builder - any of joined elements - mysql

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

Related

Doctrine, Many to Many relationship issue

I have Entities: User and Catalog, related relations (as below). The user can create catalogs, and then can share them with other users. I want to search for all the directories assigned to the user (that is, those which he created himself and those that were made available to him).
public function findAllCatalogForUser($id){
return $this->createQueryBuilder('catalog')
->leftJoin('catalog.users','us')
->innerJoin('catalog.user','u')
->where('u.id = :id OR us.id =:id ')
->setParameters([':id'=>$id])
->getQuery()
->getResult();
}
However, when the query is executed, it receives an error:
Type of association must be one of _TO_ONE OR MANY_TO_MANY
It seems to me that the relationships between the tables are correct, but I can not make the query.
How to fix this problem? Thank you in advance for your answer.
User Entity
/**
* User
*
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
* #UniqueEntity(fields="email", message="Ten email już istnieje, musisz użyć innego.")
* #UniqueEntity(fields="username", message="Nazwa użytkownika już istnieje, musisz użyć innej.")
*/
class User implements AdvancedUserInterface, \Serializable
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="username", type="string", length=255, unique=true)
* #Assert\NotBlank(message="Pole nie może być puste!")
*/
private $username;
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Catalog", inversedBy="user")
* #ORM\JoinTable(name="user_catalog",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="catalog_id", referencedColumnName="id")} )
*/
protected $catalogs;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Catalog", mappedBy="user")
*/
protected $catalog;
Catalog Entity
/**
* Catalog
*
* #ORM\Table(name="catalog")
* #ORM\Entity(repositoryClass="AppBundle\Repository\CatalogRepository")
*/
class Catalog
{
/**
* #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;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="catalog")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\User", mappedBy="catalog")
*/
protected $users;
/**
* Constructor
*/
public function __construct()
{
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
$this->user = new \Doctrine\Common\Collections\ArrayCollection();
}
In your user entity:
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Catalog", inversedBy="users")
* #ORM\JoinTable(name="user_catalog",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="catalog_id", referencedColumnName="id")} )
*/
protected $catalogs;
In your catalog entity:
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\User", mappedBy="catalogs")
*/
protected $users;

How to make effective index for search

How to make the index on Table for speeding up searching.
I have two tables like these
(I created tables with doctorine from symfony2, but in fetching use plain mysql from python script.)
Now I want to exec this sql many times(changing x value)
select recordDate,closePrice from PriceDay where company_price=x ORDER BY recordDate DESC
So I wan to set the index like indexes={#ORM\Index(name="code_index",columns={"company_price","recordDate"})}) , but I am not sure it is the best solution or not. Pairs of company_price and recordDate are unique. Could you help some ideas??
* #ORM\Table(indexes={#ORM\Index(name="code_index",columns={"company_price","recordDate"})})
PriceDay table
class PriceDay
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\Company")
* #ORM\JoinColumn(name="company_price", referencedColumnName="id")
*/
private $company;
/**
* #ORM\Column(type="float")
*/
private $closePrice = 0;
/**
*
* #ORM\Column(type="date")
*/
private $recordDate;
Company Table
class Company
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string",nullable=false)
*/
private $name;
/**
* #var boolean
* #ORM\Column(name="enabled",type="boolean")
*/
private $enabled = true;

Doctrine does not automatically load joined relation using GROUP BY

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.

Doctrine2 One2Many related entity filter results

Basically, I have two entities, Place and Event:
/**
* Place
*
* #ORM\Table(name="place")
* #ORM\Entity(repositoryClass="AppBundle\Repository\PlaceRepository")
*/
class Place
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Event", mappedBy="place")
*/
private $events;
}
and
class Event
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var \DateTime
* #JMS\Type("DateTime<'H:i'>")
* #ORM\Column(name="event_starts", type="datetime")
*/
private $eventStarts;
/**
* #var \DateTime
* #JMS\Type("DateTime<'Y/m/d H:i'>")
* #ORM\Column(name="event_ends", type="datetime", nullable=true)
*/
private $eventEnds;
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* #var string
*
* #ORM\Column(name="event_id", type="string", length=70)
*/
private $event_id;
/**
* #JMS\Exclude()
* #ORM\ManyToOne(targetEntity="Place")
* #ORM\JoinColumn(name="place_id", referencedColumnName="id")
*/
private $place;
When I'm querying Places, I would like that not all Events would be joined, but only those which are taking place today (eventStarts = today). Is there a way to do it only with Doctrine or MySQL?
Pretty straightforward.
$qb = $this->getEntityManager()->createQueryBuilder();
If you want to only get places which have an event in a specific date range:
$data = $qb->select('p, e')
->from('YourBundle:Place', 'p')
->join('p.events', 'e')
->where($qb->expr()->between('e.eventStarts', ':start', ':end'))
->setParameter('start', $someStartDateTimeObject)
->setParameter('end', $someEndDateTimeObject)
->getQuery()
->getResult();
If you would like to get all places, even if they don't have an event, but join an event if there is one in the specified range:
$data = $qb->select('p, e')
->from('YourBundle:Place', 'p')
->leftJoin('p.events', 'e', 'WITH', $qb->expr()->between('e.eventStarts', ':start', ':end'))
->setParameter('start', $someStartDateTimeObject)
->setParameter('end', $someEndDateTimeObject)
->getQuery()
->getResult();

Join is not populating the object graph as expected

I have a small misunderstanding in how the joining in Doctrine2 work.
We have a pretty complex structure in our app and we are building management screens for it.
The area of concern is as follows:
Once of the objects 'Application' looks like this:
class Application
{
/**
* List of supported statuses
*
* #var array
*/
private $statuses = array('released', 'expired');
/**
*
* #var integer $id
*
* #Column(name="id", type="integer", columnDefinition="INT(10) UNSIGNED")
* #Id
* #GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #Column(name="Name", type="string", length=100)
*/
private $name;
/**
* #var Customer
*
* #ManyToOne(targetEntity="libraries\persona\Entity\Customer")
* #JoinColumn(name="Customers_id", referencedColumnName="id", nullable=true, columnDefinition="INT(10) UNSIGNED")
*/
private $customer;
/**
* #var integer
*
* #Column(name="Partners_id", type="integer", nullable=true)
*/
private $partnerId;
/**
* #var string
*
* #Column(name="appID", type="string", length=48)
*/
private $appId;
/**
* #var string
*
* #Column(name="status", type="string", length=15)
*/
private $status;
/**
* #var \DateTime
*
* #Column(name="eventStartDate", type="date")
*/
private $eventStartDate;
/**
* #var \DateTime
*
* #Column(name="eventEndDate", type="date")
*/
private $eventEndDate;
/**
* #var string
*
* #Column(name="timeZone", type="string", length=45)
*/
private $timeZone;
/**
* #ManyToOne(targetEntity="libraries\application\Entity\ApplicationType", inversedBy="applications")
* #JoinColumn(name="ApplicationTypes_id", referencedColumnName="id")
*/
private $applicationType;
/**
* #var integer
*
* #Column(name="syncPeriod", type="integer", length=10, nullable=true)
*/
private $syncPeriod;
/**
* #var \DateTime
*
* #Column(name="lastSync", type="datetime", nullable=true)
*/
private $lastSync;
/**
* #var string
*
* #Column(name="lastSyncStatus", type="string", length=127, nullable=true)
*/
private $lastSyncStatus;
/**
* #var string
*
* #Column(name="syncScript", type="string", length=250, nullable=true)
*/
private $syncScript = '/var/www/quickstart/application/controllers/scripts/qdissync.php';
/**
* #var integer
*
* #Column(name="size", type="integer", length=10)
*/
private $size = 400;
/**
* #var text
*
* #Column(name="metaData", type="text", nullable=true)
*/
private $metaData;
/**
* #var \DateTime
*
* #Column(name="metaDataChangedTime", type="datetime", nullable=true)
*/
private $metaDataChangedTime;
/**
* #var tinyint
*
* #Column(name="isSecure", type="smallint", length=1)
*/
private $isSecure = 2;
/**
* #var integer
*
* #Column(name="Users_id", type="integer", nullable=true)
*/
private $userId;
/**
* #var string
*
* #Column(name="singleEventAppId", type="string", length=48, nullable=true)
*/
private $singleEventAppId;
/**
* #var string
*
* #Column(name="project_db", type="string", length=50)
*/
private $projectDb;
/**
* #var string
*
* #Column(name="appName", type="string", length=12)
*/
private $appName;
/**
* #var boolean
*
* #Column(name="mobileLog", type="boolean")
*/
private $mobileLog = false;
/**
* #var tinyint
*
* #Column(name="eventType", type="smallint", length=1)
*/
private $eventType = 1;
/**
* #ManyToOne(targetEntity="libraries\application\Entity\Project", inversedBy="applications")
* #JoinColumn(name="project_id", referencedColumnName="id")
*/
private $project;
/**
* #OneToMany(targetEntity="libraries\platform\Entity\Platform", mappedBy="application")
**/
private $platforms;
/**
* #var SnapEventAttributes
*
* #OneToOne(targetEntity="libraries\application\Entity\SnapEventAttributes", mappedBy="application")
**/
private $snapEventAttributes;
//GETTERS & SETTERS
}
One of the related objects 'SnapEventAttributes':
class SnapEventAttributes
{
/**
*
* #var integer
*
* #Column(name="id", type="integer", columnDefinition="INT(11)")
* #Id
* #GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #Column(name="directSelectId", type="string", length=10, nullable=true)
*/
private $directSelectId;
/**
* #var string
*
* #Column(name="description", type="text", nullable=true)
*/
private $description;
/**
* #var string
*
* #Column(name="access", type="string", length=10, nullable=false)
*/
private $access;
/**
* #var string
*
* #Column(name="status", type="string", length=10, nullable=false)
*/
private $status;
/**
* #var string
*
* #Column(name="snapAppVersion", type="string", length=5, nullable=true)
*/
private $snapAppVersion;
/**
* #var string
*
* #Column(name="pwd", type="string", length=250, nullable=true)
*/
private $password;
/**
* #var string
*
* #Column(name="thumbnailUrl", type="string", length=250, nullable=true)
*/
private $thumbnailUrl;
/**
* #var string
*
* #Column(name="location", type="string", length=45, nullable=true)
*/
private $location;
/**
* #OneToOne(targetEntity="libraries\application\Entity\Application", inversedBy="snapEventAttributes")
* #JoinColumn(name="Applications_id", referencedColumnName="id", unique=true, nullable=false)
*/
private $application;
// GETTERS & SETTERS
}
In one of our work flows we need to join these two with bunch of additional joins and we got something like this:
$qb = $this->createQueryBuilder('e');
$qb->innerJoin('e.applicationType', 'et', Expr\Join::WITH, 'et.mobileEvent = :mobileEvent AND et.snapApp = :snapApp AND et.snapEvent = :snapEvent');
$qb->innerJoin('e.snapEventAttributes', 'attrs');
$qb->innerJoin('e.project', 'p');
$qb->innerJoin('p.applications', 'a');
$qb->innerJoin('a.applicationType', 'at', Expr\Join::WITH, 'at.mobileEvent = :vMobileEvent AND at.snapApp = :vSnapApp AND at.snapEvent = :vSnapEvent');
$qb->leftJoin('e.customer', 'cust');
$qb->leftJoin('p.partner', 'partn');
$qb->setParameters(array(
'mobileEvent' => false,
'snapApp' => false,
'snapEvent' => true,
'vMobileEvent' => false,
'vSnapApp' => true,
'vSnapEvent' => false,
));
return $qb;
All the logic works perfectly fine, however once I execute this query:
$qb->getQuery()->getResult();
the main query is getting executed as expected but automatically doctrine executes bunch of queries to get the SnapEventAttributes objects:
SELECT t0.id AS id1, t0.directSelectId AS directSelectId2, t0.description AS description3, t0.access AS access4, t0.status AS status5, t0.snapAppVersion AS snapAppVersion6, t0.pwd AS pwd7, t0.thumbnailUrl AS thumbnailUrl8, t0.location AS location9, t0.Applications_id AS Applications_id10 FROM SnapEventAttributes t0 WHERE t0.Applications_id = ?
What am I missing? What could be a reason for this behavior?
Thanks,
A.
get the repository
Controller
$snapEvents = $this->em->snapEvents ( 'Entity\SnapEventAttributes' )->findAll ();
$data ['snapEvents '] = $snapEvents ;
then on the view
View
foreach ($snapEvents $sanpEvent) {
echo "<tr>".
"<td>" . $sanpEvent->getAppliction()->getApplicationType() . "</td>".
"<td>" . $sanpEvent->getAppliction()->getCutomer() . "</td>".
}
Like this keep on calling what u want since u have already joined columns at the model