How to get the join ID when using Doctrine? - symfony

I am working on a project of my company. However, I am not familiar with Doctrine. I am the old-styled query-guy.
Table-A and Table-B is in one-to-many relation, linking up by "a_id" on Table-B. In the Entity-B, $a_name is specified.
Table-A
a_id
a_name
a_attr
Table-B
b_id
a_id
b_name
b_attr
Entity-B
/**
* #ORM\ManyToOne(targetEntity="EntityA")
* #ORM\JoinColumn(name="a_id", referencedColumnName="a_id")
* #var Timezone
*/
protected $a_name;
Now I am writing a method to get a set of records using IN()
/**
* #param array $ids
*
* #return array
*/
public function getByIds($ids) {
$query = $this->getEntityManager()->createQuery('SELECT t FROM Entity-B t INDEX BY t.id WHERE t.id IN (:ids)');
}
The above line "INDEX" and "WHERE" with the Entity-B ID. How can I "INDEX" and "WHERE" with Entity-A's ID (a_id on Table-B)?
Thanks.

Try this or similar for your DQL:
SELECT a.a_id, t.b_id, t.b_name, t.b_attr FROM Entity-B t LEFT JOIN t.a_name a INDEX BY a.id WHERE a.a_id IN (:ids)
We just add a join to Entity-A, include a.id in the select and then index by a.id.
As we working with entities in doctrine, we need to join the entity to get at its id to then index by it. Also, the naming of your properties within each entity could be simpler and more intuitive. So rather than Entity-A (a) having properties a_id, a_attr, etc, just use id, attribute, etc. I assume you just generalised your code for the question and you probably have nicer property names in your project.
Let me know how that DQL works out for you.

Related

DQL joining related entities

Hi have some entities relateds and need to define a dql query to obtain an entity.
MAIN ENTITY
class proyectosSubsecciones
{
...
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="app\SubseccionesBundle\Entity\Subsecciones")
* #ORM\JoinColumn(name="id_subseccion", referencedColumnName="id")
*/
private $subseccion;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="app\ProyectosBundle\Entity\Proyectos")
* #ORM\JoinColumn(name="id_proyecto", referencedColumnName="id")
*/
private $proyecto;
....
RELATED ENTITY
class subsecciones
{
...
/**
* #ORM\ManyToOne(targetEntity="app\SeccionesBundle\Entity\Secciones")
* #ORM\JoinColumn(name="id_seccion", referencedColumnName="id",nullable=false)
*/
private $seccion;
...
I need to obtain the distinct entities of type "app\SeccionesBundle\Entity\Secciones" from each "app\ProyectosBundle\Entity\Proyectos"
I´m trying a query like:
$consulta=$em->createQuery('
SELECT DISTINCT sc
FROM ProyectosSubseccionesBundle:ProyectosSubsecciones p
JOIN p.subseccion s WITH s.id=p.subseccion
JOIN s.seccion sc WITH sc.id=s.seccion
WHERE p.proyecto= :id
');
$consulta->setParameter('id', $id_proyecto);
$subsecciones=$consulta->getResult();
I get an error that says:
"Cannot select entity through identification variables without choosing at least one root entity alias"
But I only need the data from sc.Any idea??
Use query builder in ProyectosSubseccionesRepository:
return $this->createQueryBuilder('p')
->join('p.subseccion', 's', Join::WITH, 's = p.subseccion')
->join('s.seccion', 'sc', Join::WITH, 'sc = s.seccion')
->where('p.proyecto = :id')
->setParameter('id', $id)
->getQuery()
->execute()
For your problem i assume you have defined bidirectional relationship among your entities.
like
Entity RelationType ReferenceEntity Reference
==========================================================================================
ProyectosSubsecciones ManyToOne Subsecciones $subseccion
ProyectosSubsecciones ManyToOne Proyectos $proyecto
Proyectos OneToMany ProyectosSubsecciones $proyectosSubsecciones
Subsecciones OneToMany ProyectosSubsecciones $proyectosSubsecciones
Subsecciones ManyToOne Secciones $seccion
Secciones OneToMany Subsecciones $subsecciones
Considering above bidirectional definitions you can write your DQL as
SELECT DISTINCT s
FROM Secciones s
JOIN s.subsecciones ss
JOIN ss.proyectosSubsecciones pss
JOIN pss.proyecto
WHERE p.id = :id
The above query will select Secciones entity and join with Subsecciones entity using property $subsecciones defined in Secciones entity.
Then query will join Subsecciones with ProyectosSubsecciones using property $proyectosSubsecciones defined in Subsecciones entity.
Finally it will ProyectosSubsecciones with Proyectos entity using $proyecto property defined in ProyectosSubsecciones entity and lastly it will apply a filter according your WHERE clause.
Note there is no need to use WITH clause to join your entities because, In DQL, joining part will be covered by properties which you define as OneToMany/ManyToOne or ManyToMany WITH is used for if there is no relation mapping defined between entities or if you want to add another filter on joining criteria like ON(a.id = b.some_id AND/WITH some = some)

Join from inversed side

given the two following intities:
<?php
/**
* User
* #ORM\Entity()
*/
class User implements AdvancedUserInterface, \Serializable, EncoderAwareInterface
{
/**
* #var Vip
* #ORM\OneToOne(targetEntity="Vip", mappedBy="user", fetch="EAGER")
*/
protected $vip;
// …
<?php
/**
* Vip
* #ORM\Entity()
*/
class Vip
{
/**
* #ORM\id #ORM\OneToOne(targetEntity="User", inversedBy="vip", fetch="EAGER")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
*/
protected $user;
// …
SHORT :
How can I do this SQL in DQL given above entities:
SELECT u.firstName, v.foo FROM User join Vip v ON v.user_id = u.id
In other words how can I retrieve 10 first users ( with their VIP infos if it exists), using DQL join in such a way that only one SQL query will be generated by Doctrine. Is that possible ?
Long story:
The owning side is the Vip entity because it holds the reference/foreign key to a User underneath in the database.
I am trying to retrieve all User with their Vip datas.
Using the knplabs/knp-paginator-bundle, I first set up a simple query:
$dql = "SELECT u, p FROM AppBundle:User u;
In spite of enforcing the fetch attribute as « EAGER », Vip infos where not part of the initial query. As a consequence, calling getter getVip() on each iteration from inside the twig for in loop like
{% for user in pagination %}
{% if user.getVip() %}
<span class="label label-warning">V.I.P</span>
{% endif %}
{{% endfor %}}
.. caused a query to be issued on each iteration !
The Symfony dev bar shows 6 queries:
DQL documentation and says that one can use JOIN keyword. So my query became:
$dql = "SELECT u, v FROM AppBundle:User u JOIN u.vip v;
But now I get this error:
Warning: spl_object_hash() expects parameter 1 to be object, null
given
Here I'm stuck, wondering how I could fetch Vip datas (or null) along with User datas, in a single query.
In other words how can I retrieve 10 first users ( with their VIP
infos if it exists), using DQL join in such a way that only one SQL
query will be generated by Doctrine. Is that possible ?
You should initialize all related entities using select clause to avoid additional queries when accessing to the related objects.
$repository = $em->getRepository(User::class);
$users = $repository->createQueryBuilder('u')
->addSelect('v') // Initialize Vip's
->join('u.vip', 'v')
->getQuery()
->setMaxResults(10)
->getResult();
Yes, one may add associated entities to the SELECT statement.
But more precisely, one should only add relations that are really involved in the expected result , in other words, entities fetched as "EAGER".
I realized that the vip entity had another relation (oneToMany with a vehicule entity). I just want to retrieve users with their vip metas. Adding another JOIN to the query would just bring more datas since I would not use vehicules anyway (and issue extra work behind the scenes).
-> So I simply changed the fetch attribute from "EAGER" to "LAZY" in the vip OneToMany declaration.
To conclude:
Ask yourself «what are involved intities ?», should it be part
of the result (do you simply need those infos).
if NO, you might turn fetch attribute to "[EXTRA_]LAZY" in the relation declaration like
/**
* #ORM\OneToMany(targetEntity="Vehicule", mappedBy="vip", fetch="LAZY", …)
*/
protected $vehicules;
if YES you will have to select those entities in your query.
Using DQL:
SELECT u, v, w FROM AppBundle:User u LEFT JOIN u.vip v LEFT JOIN v.vehicules w
Using queryBuilder:
$repository = $em->getRepository(User::class);
$users = $repository->createQueryBuilder('u')
->addSelect('v')
->join('u.vip', 'v')
->addSelect('w')
->join('v.vehicules', 'w')
// …

limit columns returned in relationnal entity symfony2

Is it possible to filter an entity and display only few columns in symfony2?
I think I can do a custom query for this, but it seems a bit dirty and I am sure there is a better solution.
For example I have my variable $createdBy below, and it contains few data that shouldnt be displayed in this parent entity such as password etc...
/**
* #var Customer
*
* #ORM\ManyToOne(targetEntity="MyCompany\Bundle\CustomerBundle\Entity\Customer")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="created_by", referencedColumnName="id", nullable=false)
* })
*/
protected $createdBy;
So I need to display my Customer entity, but only containing fields like id and name for example.
EDIT :
I already have an instance of Project, the entity with my createdBy field, and I want to grab my customer data 'formatted' for this entity and not returning too much fields like password ...
Thanks
It sounds like expected behavior to me. The doctrine documentation seems to imply that eager fetching is only one level deep.
According to the docs:
Whenever you query for an entity that has persistent associations and
these associations are mapped as EAGER, they will automatically be
loaded together with the entity being queried and is thus immediately
available to your application.
http://doctrine-orm.readthedocs.org/en/latest/reference/working-with-objects.html#by-eager-loading
The entity being queried has eager on createdBy so it will be populated.
to bypass you can create a method in your entity repository as following :
// join entities and load wanted fields
public function findCustom()
{
return $this->getEntityManager()
->createQuery(
'SELECT p FROM AppBundle:Product p ORDER BY p.name ASC'
)
->getResult();
}
hope this helps you
try this and let me know if it works, you should fill the right repository name
'XXXXBundle:CustomerYYYY', 'c'
public function findUser($user_id){
$qb = $this->_em->createQueryBuilder('c')
->select(array('c', 'cb.id', 'cb.name'))
->from('XXXXBundle:Customer', 'c')
->where('c.id <> :id')
->leftJoin('c.createdBy', 'cb')
->setParameter('id', $user_id)->getQuery();
if ($qb != null)
return $qb->getOneOrNullResult();
return null;
}

Custom SELECT for findBy in Doctrine

I intend to build multilingual website. So I have DB table with columns:
id
text_en
text_pl
When getting data from DB I'd like to have only id and text fields. Normally, I'd issue:
SELECT id, text_$locale AS text FROM page ...
How to do it in Doctrine so that I have such SELECT done automatically when I use findBy() or findOneBy()? Is Query Builder my only option?
Creating a query is the semantic option; you should put it in as a custom method in a repository.
I'm posting an example here so that I can use it for reference later on.
Quick example:
<?php
namespace Acme\AppBundle\Entity;
use Doctrine\ORM\EntityRepository;
/**
* PageRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class PageRepository extends EntityRepository
{
/**
* Gets and id and page name based by ID
* #return array
*/
public function findNameById($id)
{
$query = $this->getEntityManager()
->createQuery("SELECT p.id, p.name
FROM Acme\AppBundle\Entity\Page p
WHERE p.id = :pageId");
$query->setParameter('pageId', $id);
$result = $query->getOneOrNullResult();
return $result;
}
}
Correct, you'll have to use a querybuilder or native query if you want to select specific columns.
Specifying individual columns using findBy is not possible. You could lazy-load associations to entities in other tables but at a minimum findBy will return all columns from the entity's table.

DQL to join on property of a property

I am trying to create a DQL query by testing on the property of a property. The objective is to create a DQL query that will indicate in what role a user is fulfilling a job, returning no rows if the user does not have the proper role.
My Entities are Role, Job, and User. User's have roles, Jobs require a role (to fulfill them), and Roles have 'alternates' that link to another role that can fill in for that role.
Stubbed versions of the entites look like:
class User {
//Annotation not needed for question
protected $id;
/**
* #var SystemBundle\Entity\Role
*
* #ORM\ManyToMany(targetEntity="SystemBundle\Entity\Role")
* #ORM\JoinTable(name="User_User__Role",
* joinColumns={
* #ORM\JoinColumn(name="User_ID", referencedColumnName="ID")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="Role_ID", referencedColumnName="ID")
* }
* )
*/
protected $roles;
}
class Job {
//Annotation not needed for question
protected $id;
/**
* #var SystemBundle\Entity\Role $jobRole
*
* #ORM\ManyToOne(targetEntity="Role")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="Role_ID", referencedColumnName="ID")
* })
*/
protected $jobRole;
}
class Role {
//Annotation not needed for question
protected $id;
/**
* #var SystemBundle\Entity\Role $jobRole
*
* #ORM\OneToOne(targetEntity="Role")
* #ORM\JoinColumn(name="BackupRole_ID", referencedColumnName="ID")
*/
protected $backUpRole;
}
The conceptual logic is to join Roles with Jobs, testing if the current Role IS job.JobRole or job.JobRole.backUpRole. I've tried this, and doctrine really doesnt seem to like the job.jobRole.backUpRole and throws this error at the second period:
[Syntax Error] line 0, col 210: Error: Expected =, <, <=, <>, >, >=, !=, got '.'
My DQL attempt looks like this
SELECT r
FROM SystemBundle:Role r
INNER JOIN ProcessBundle:Job j
WITH j = :j AND j.jobRole = r OR j.jobRole.backUpRole = r
LEFT JOIN UserBundle:User u
WITH r MEMBER OF u.roles
WHERE u = :emp AND u IS NOT NULL
ORDER BY r.id
I can accomplish this task with pure SQL, as well as just using php to walk the associations, but I am looking to stay true to use DQL (because it's vexing me and I want to know if it can be done).
If it helps, here is my pure SQL:
#get all roles
select r.*
from Sys_Role as r
inner join
#get the role assigned to the job
(select r.*
FROM Sys_Role as r
INNER JOIN Sys_Job as j
ON j.JobRole_ID = r.ID
WHERE j.ID = ?) as jr
#join roles based on the job-role's id or backup id
# this should filter the list of roles down to the role and backup role for the job
on ar.ID = aar.ID OR ar.ID = aar.BackUpRole_ID
# and filter by the roles the user is assigned
INNER JOIN User_User__Role as uur
ON r.ID = uur.Role_ID
WHERE uur.User_ID = ?
EDIT: My apologies. While editing the question layout i accidently deleted the DQL. I've added that back the question!
UPDATE: I am exploring making the $backUpRole a bidirectional self-join and attacking from that direction. Doctrine does not like the naive attempt, so it looks like if this tact is used, fresh DQL is needed.
Changing the DQL to WITH j = :j AND j.jobRole = r OR j.jobRole = r.roleIBackUp yeilds a new error: A single-valued association path expression to an inverse side is not supported in DQL queries. Use an explicit join instead.
I figured it out. It IS possible... you need to join the sub-objects before you can use them. I had to switch up the order of my joins and I'm not terribly happy with my filters being in WITH clauses instead of WHERE, but it's hard to argue with results!
SELECT r
FROM SystemBundle:Role r
LEFT JOIN UserBundle:User u
WITH u = :emp
INNER JOIN ProcessBundle:Job j
WITH j = :job
JOIN j.jobRole jr
JOIN jr.backUpRole jrbr
WHERE (jr = ar OR jrbr = ar) AND r MEMBER OF u.role
The last 2 Joins let us alias the objects that belong to the linked entities so we can use them to compare.
The "30,000 foot view" of how this DQL works is such:
Foreach Role
Does this Role belong to the user?
Get the Role for the Job we want
Alias That Role
Alias That ROles Back Up
Is this role the Job's Role or Job's Role's Backup Role?
Kind of weird, but fairly simple once you get it.

Resources