Symfony2 Doctrine Query, only populate single field in OneToMany Relationship - symfony

Is it possible to only populate a single field in a QueryBuilder query that fetches the relationship, my case:
$query = $em->createQuery(
'SELECT s FROM IREnterpriseAppBundle:StockItem s
WHERE s.user = :currentUser AND s.deleted = 0
ORDER BY s.id DESC'
)->setParameters(array('currentUser' => $user));
Now the entity StockItem has a relationship to the user:
/**
* StockItem
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="IREnterprise\AppBundle\Entity\StockItemRepository")
* #ORM\HasLifecycleCallbacks
*
* #ExclusionPolicy("all")
*
*/
class StockItem
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #Expose
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="IREnterprise\UserBundle\Entity\User", inversedBy="stockItems")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* #Expose
**/
private $user;
}
On the aforementioned query, the entire User object is fetched alongside with the stockitem, is it possible to only fetch/set a single field on the user object?
Since i am having issues excluding the rest of the user object with #exclusion policy: JMSSerializerbundle #expose relationship, ignores other entities policies
As a last resort i have to unset the fields of the user object in code, but that just seems retarded.

It is possible to only fetch a single field from a related entity but doing so, you won't be able to persist any change to the result.
If that's ok with you, you should use a JOIN and specify which fields from the related entity you want to fetch.
As mentioned in another answer, you can use createQueryBuilder instead of createQuery.
createQueryBuilder code sample:
$result = $em->getRepository('IREnterpriseAppBundle:StockItem')
->createQueryBuilder('s')
->select('s.id, u.id as user_id')
->join('s.user', 'u')
->where('s.user = :currentUser')
->andWhere('s.deleted = 0')
->setParameter('currentUser', $user)
->getQuery()
->getResult();
createQuery code sample:
$query = $em->createQuery(
'SELECT s.id, u.id as user_id FROM IREnterpriseAppBundle:StockItem s
JOIN s.user u
WHERE s.user = :currentUser AND s.deleted = 0
ORDER BY s.id DESC'
)->setParameters(array('currentUser' => $user));

Related

Doctrine: select a table that is not managed by doctrine

Using the Doctrine QueryBuilder, I want to execute a query which in native SQL looks like this:
`SELECT image FROM image i INNER JOIN about_images a ON i.id = a.image_id`;
The result in DQL is as follows:
ImageRepository.php:
return $this->createQueryBuilder('i')
->select('i')
->innerJoin('about_images', 'a', 'WITH', 'i.id = a.imageId')
->getQuery()
->getResult();
Where image is an entity, and about_images is a join table (the result of a #ManyToMany relationship). However, I get the error that about_images is not defined, which makes sense as it is not managed by doctrine.
AboutPage.php (i.e the entity where the join table is created)
/**
* #var Image[]|ArrayCollection
*
* #ORM\ManyToMany(targetEntity="App\Entity\Image", cascade={"persist", "remove"})
* #ORM\JoinTable(name="about_images",
* joinColumns={#ORM\JoinColumn(name="about_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="image_id", referencedColumnName="id", unique=true)})
*/
private $images;
Fields from Image entity:
/**
* #var int
*
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var string
*
* #ORM\Column(type="string", length=255)
*/
private $image;
/**
* #var File
*
* #Vich\UploadableField(mapping="collection_images", fileNameProperty="image")
* #Assert\File(maxSize="150M", mimeTypes={"image/jpeg", "image/jpg", "image/png", "image/gif"},
* mimeTypesMessage="The type ({{ type }}) is invalid. Allowed image types are {{ types }}")
*/
private $imageFile;
/**
* #var string
*
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $imageAlt;
/**
* #var DateTime
*
* #ORM\Column(type="datetime")
*/
private $updatedAt;
/**
* #var string
*
* #ORM\Column(type="string", nullable=true)
*/
private $alt;
How can I solve this problem? The results should be Image entities.
You can write native SQL and then map the output to your entities using a ResultSetMapper.
For your example it could look something like this in your Repository class:
public function findImagesWithAboutImages()
{
$sql = 'SELECT i.* FROM image i INNER JOIN about_images a ON i.id = a.image_id';
$entityManager = $this->getEntityManager();
$mappingBuilder = new ResultSetMappingBuilder($entityManager);
$mappingBuilder->addRootEntityFromClassMetadata(Image::class, 'i');
$query = $entityManager->createNativeQuery($sql, $mappingBuilder);
// If you want to set parameters e.g. you have something like WHERE id = :id you can do it on this query object using setParameter()
return $query->getResult();
}
If you want related data you will have to add it to the select clause with an alias and then use $mappingBuilder->addJoinedEntityFromClassMetadata() to assign these fields to the joined entity much like above with the root entity.
Your annotations in your entity already define how each field maps to a property and what type it has, so basically you should get an array of Image-entities with everything (but the related entities) loaded usable.
It is not quite clear the example sql with the code you have provided, but if you have a relation defined in your entities, you can join them with a query builder just by telling the relation field of the entity, so I think this should work
return $this->createQueryBuilder('i')
->select('i')
->innerJoin('i.images', 'a')
->getQuery()
->getResult();
As you have defined already your relations in your entities, Doctrine knows how to join your tables, so you just have to specify the relation field name and the alias.
And always remember that you have to use the field name in your entity (normally cameCasedStyle), not the column name at your database tables (normally snake_cased_style).

How to get the newest entity in a OneToMany relation with Doctrine

Intro
For this question the following two entities are the main characters:
ObjectElementTask
Activitites
One ObjectElementTask can have many activities.
Goal
I want to get a collection of ALL Activities and order them by ObjectElementTask and get only the last Activity (based on the 'executiondate' of the activities).
Question
How can i setup the querybuilder from Doctrine to achieve my goal? BTW: I am using Symfony 2.8.
Entities
ObjectElementTask:
class ObjectElementTask
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="Activity", mappedBy="objectelementtask", cascade={"persist", "remove"}, orphanRemoval=true)
* #ORM\OrderBy({"executionDate" = "DESC"})
* #Expose
*/
private $activities;
Activity
class Activity {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="ObjectElementTask", inversedBy="activities", cascade={"persist"})
* #ORM\JoinColumn(name="objectelementtask_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $objectelementtask;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
protected $executionDate;
Thanks in advance!
Edit
Based on the answer in this question:
DQL Select every rows having one column's MAX value
i have come up with the following code:
$qb = $this->getDoctrine()
->getRepository('AppBundle:Activity')
->createQueryBuilder('a')
->select('MAX(a.executionDate) maxDate');
$qb2 = $this->getDoctrine()
->getRepository('AppBundle:ObjectElementTask')
->createQueryBuilder('o');
$qb2->innerJoin('AppBundle:ObjectElementTask', 'oe', 'WITH', $qb2->expr()->eq('oe.activities', '(' . $qb->getDQL() . ')'));
But as you can guess, this doesn't work. It only returns one row (the newest in the Activity). And i want the newest activity of each ObjectElementTask.
Edit 2
I tried this code:
$activities = $this->getDoctrine()
->getRepository('AppBundle:Activity')
->createQueryBuilder('a')
->select('a, MAX(a.executionDate) AS HIDDEN max_date')
->groupBy('a.objectelementtask')
->getQuery()
->getResult();
And the max date returns the value of the last activitie but not the full entity (only the max date...)
Edit 3:
This is the final solution:
This is how i finally got what i needed:
$em = $this->getEntityManager();
$rsm = new ResultSetMappingBuilder($em);
$query = $em->createNativeQuery('SELECT a1.* FROM activity a1 INNER JOIN ( SELECT id, max(execution_date) MaxPostDate, objectelementtask_id FROM activity GROUP BY objectelementtask_id ) a2 ON a1.execution_date = a2.MaxPostDate AND a1.objectelementtask_id = a2.objectelementtask_id order by a1.execution_date desc', $rsm);
$rsm->addRootEntityFromClassMetadata('AppBundle:Activity', 'a1');
$activities = $query->getResult();
This is how i finally got what i needed:
$em = $this->getEntityManager();
$rsm = new ResultSetMappingBuilder($em);
$query = $em->createNativeQuery('SELECT a1.* FROM activity a1 INNER JOIN ( SELECT id, max(execution_date) MaxPostDate, objectelementtask_id FROM activity GROUP BY objectelementtask_id ) a2 ON a1.execution_date = a2.MaxPostDate AND a1.objectelementtask_id = a2.objectelementtask_id order by a1.execution_date desc', $rsm);
$rsm->addRootEntityFromClassMetadata('AppBundle:Activity', 'a1');
$activities = $query->getResult();

How to get a collection of related entities by using Doctrine ResultSetMapping?

I use Doctrine 2.3.4. and Symfony 2.3.0
I have two entities: Person and Application.
Application gets created when some Person applies for a job.
Relation from Person to Application is OneToMany, bidirectional.
Using the regular Doctrine documentation here I managed to get a correct result set only when working with a single entity.
However, when I add joined entity, I get a collection of root entities but joined to a wrong related entity.
In other words, the problem is that I get a collection of Applications but all having the same Person.
Native sql query, when executed directly returns a correct result.
This is the code:
$sql = "SELECT a.id, a.job, p.first_name, p.last_name
FROM application a
INNER JOIN person p ON a.person_id = p.id";
$rsm = new ResultSetMapping;
$rsm->addEntityResult('\Company\Department\Domain\Model\Application', 'a');
$rsm->addFieldResult('a','id','id');
$rsm->addFieldResult('a','job','job');
$rsm->addJoinedEntityResult('\Company\Department\Domain\Model\Person' , 'p', 'a', 'person');
$rsm->addFieldResult('p','first_name','firstName');
$rsm->addFieldResult('p','last_name','lastName');
$query = $this->em->createNativeQuery($sql, $rsm);
$result = $query->getResult();
return $result;
Here are the Entity classes:
namespace Company\Department\Domain\Model;
use Doctrine\ORM\Mapping as ORM;
/**
* Person
*
* #ORM\Entity
* #ORM\Table(name="person")
*/
class Person
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string First name
*
* #ORM\Column(name="first_name",type="string",length=255)
*/
private $firstName;
/**
* #var string Last name
*
* #ORM\Column(name="last_name",type="string",length=255)
*/
private $lastName;
/**
*
* #var Applications[]
* #ORM\OneToMany(targetEntity="Application", mappedBy="person")
*/
private $applications;
Application class:
namespace Company\Department\Domain\Model;
use Doctrine\ORM\Mapping as ORM;
/**
* Application (Person applied for a job)
*
* #ORM\Entity
* #ORM\Table(name="application")
*/
class Application
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var Person
*
* #ORM\ManyToOne(targetEntity="Person", inversedBy="applications")
* #ORM\JoinColumn(name="person_id", referencedColumnName="id")
*/
private $person;
/**
* #var string
* #ORM\Column(name="job",type="string", length=100)
*/
private $job;
I must be missing something here?
Found out where the error was:
The Person->id property has to be mapped too.
Also, order of columns in SELECT clause has to match the order of addFieldResult() statements.
Therefore, $sql should look like this:
SELECT a.id, a.job, p.id AS personId, p.first_name, p.last_name
FROM application a
INNER JOIN person p ON a.person_id=p.id
And mapping for related property like this:
$rsm->addJoinedEntityResult('\Company\Department\Domain\Model\Person' , 'p', 'a', 'person');
$rsm->addFieldResult('p','personId','id');
$rsm->addFieldResult('p','first_name','firstName');
$rsm->addFieldResult('p','last_name','lastName');
So, the mapped field result column name corresponds to sql result column name, and third parameter, id in this case, should be the property actual name.

Entity containing other entities without having a table

I have an Entity ( Invoice ) which is purely for calculation purposes and has no table, that associates with two other entities having relations by tables. (Although there are so many other entities involved ).
class Row{
/**
* #var integer
*
* #ORM\Column(name="row_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="File")
* #ORM\JoinColumn(name="file_id", referencedColumnName="file_id")
*/
protected $file;
/**
* #var \DateTime
*
* #ORM\Column(name="date", type="date")
*/
private $date;
}
class File
{
/**
* #var integer
*
* #ORM\Column(name="file_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
}
class Invoice
{
/**
* #ORM\Id
* #ORM\Column(name="invoice_id", type="integer")
* #ORM\GeneratedValue
*/
protected $id = null;
/**
* #ORM\OneToMany(targetEntity="Row", mappedBy="row_id")
*/
protected $row;
/**
* #ORM\OneToMany(targetEntity="File", mappedBy="file_id")
*/
protected $file;
}
I want to be able to query for Invoices :
$sDate = //Some date
$this->getEntityManager()
->createQuery("SELECT Invoice, Row, File
FROM
ReportsEntitiesBundle:Invoice Invoice
LEFT JOIN
Row.row Row
LEFT JOIN
Row.file File
WHERE date=:date"
)
->setParaMeter(':date', $sDate)
->setFirstResult($iPage*$iLimit)
->setMaxResults($iLimit)
->getResult();
The questions :
# Doctrine tries to query the database, how can I prevent that and have it find the relevant entities?
# How can I relate the date ( which is in Row entity and cannot be in Invoice ) to the query?
Later this Invoice will become a part of another big entity for calculating/search purposes.
Thank you
Short Answer: You can't
Long Answer : You can't because an entity with #ORM annotations means its persisted to a database - querying that entity relates to querying a database table. Why not just create the table ?!?!?
You need somewhere to persist the association between file and row - a database table is a perfect place !!!!
Update
Just to clarify ... an Entity is just a standard class - it has properties and methods ... just like any other class - When you issue doctrine based commands it uses the annotations within the entities to configure the tables / columns / relationships etc if remove those you can use it however you like ... but you will need to populate the values to use it and you wont be able to use it in a Doctrine query and it obviously wont be persisted !
You can use a read-only entity. It's contents are backed by a view which you create manually in SQL.
PHP:
/** #ORM\Entity(readOnly =true) */
class InvoiceView
{ ...
SQL:
CREATE VIEW invoice_view AS (
SELECT ...

Stop related entities to be queried in doctrine2 while using QueryBuilder

I am having two entities User and Profile with one to one relationship.
$qb = $this->getDoctrine()->getEntityManager()->createQueryBuilder();
$qb->add('select', 'u')
->add('from', '\Acme\TestBundle\Entity\User u')
->add('orderBy', 'u.id DESC');
$query = $qb->getQuery();
$customer = $query->execute();
When i Check the number of queries in Symfony profiler I could see n number for queries triggered on Profile table for n users in User table. Is there any way where I can stop the querying of the Profile table.
Please let me know if there is better way of implementing it.
Thanks in advance
Added Entity Classes
class User
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $email
*
* #ORM\Column(name="email", type="string", length=255)
*/
private $email;
/**
* #var Acme\TestBundle\Entity\Profile
*
* #ORM\OneToOne(targetEntity="Acme\TestBundle\Entity\Profile", mappedBy="user")
*/
private $profile;
}
class Profile
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer $user_id
*
* #ORM\Column(name="user_id", type="integer")
*/
private $user_id;
/**
* #var string $user_name
*
* #ORM\Column(name="user_name", type="string", length=100)
*/
private $user_name;
/**
* #var Acme\TestBundle\Entity\User
*
* #ORM\OneToOne(targetEntity="Acme\TestBundle\Entity\User", inversedBy="profile")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
}
Response from the mysql log
120110 15:14:29 89 Connect root#localhost on test
89 Query SET NAMES UTF8
89 Query SELECT c0_.id AS id0, c0_.email AS email1, c0_.password AS password2, c0_.is_demo_user AS is_demo_user3, c0_.status AS status4, c0_.current_service AS current_service5, c0_.registration_mode AS registration_mode6, c0_.verification_code AS verification_code7, c0_.account_type AS account_type8, c0_.activated_date AS activated_date9, c0_.status_updated_at AS status_updated_at10, c0_.created_at AS created_at11, c0_.updated_at AS updated_at12 FROM user c0_ WHERE c0_.id = 1 ORDER BY c0_.email ASC
89 Query SELECT t0.id AS id1, t0.user_id AS user_id2, t0.user_name AS user_name3, t0.age AS age4, t0.created_at AS created_at5, t0.updated_at AS updated_at6, t0.user_id AS user_id7 FROM profile t0 WHERE t0.user_id = '1'
89 Quit
Your answer is here!!
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html
"In many cases associations between entities can get pretty large. Even in a simple scenario like a blog. where posts can be commented, you always have to assume that a post draws hundrets of comments. In Doctrine 2.0 if you accessed an association it would always get loaded completly into memory. This can lead to pretty serious performance problems, if your associations contain several hundrets or thousands of entities.
With Doctrine 2.1 a feature called Extra Lazy is introduced for associations. Associations are marked as Lazy by default, which means the whole collection object for an association is populated the first time its accessed. If you mark an association as extra lazy the following methods on collections can be called without triggering a full load of the collection:"
<?php
namespace Doctrine\Tests\Models\CMS;
/**
* #Entity
*/
class CmsGroup
{
/**
* #ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY")
*/
public $users;
}
It's a bit late, but it might help others out there!

Resources