Doctrine: select a table that is not managed by doctrine - symfony

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).

Related

Symfony2 Doctrine Query, only populate single field in OneToMany Relationship

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));

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.

Write a Doctrine2 query to join two spatial tables

I am using Symfony with Doctrine2 on a Postgre database with PostGIS enabled. I have two tables - property and neighborhood with the following structures:
class Property {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string")
*/
protected $address;
/**
* #var Point $geom
* #ORM\Column(type="Point", nullable=true)
*/
protected $geom;
}
class Neighborhood {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $gid;
/**
* #ORM\Column(type="string")
*/
protected $name;
/**
* #ORM\Column(type="string")
*/
protected $description;
/**
* #var Polygon $geom
*
* #ORM\Column(type="Polygon", nullable=true)
*/
protected $geom;
}
In pgAdminIII, I can write the following query which works fine:
SELECT address, neighborhood.name
FROM property
JOIN neighborhood
ON ST_Contains(neighborhood.geom, property.geom)
How can I write this in DQL? I understand the basics of joins and adding the annotations for the Doctrine2 mapping, but I am not sure how to do the join since the two fields are not equal. I need to use the ST_Contains function to create the join.
I am using the djlambert / doctrine2-spatial bundle for the spatial data types and mapping. I am able to query each table individually and have created maps on each, but am not sure how to select all of the properties in a given neighborhood.
I figured this out and want to post in case someone else has a similar question. I needed to make the ST_Contains part of a condition and not just the function alone.
using the createQueryBuilder, here is the syntax:
$qb = $this->createQueryBuilder('n')
->select("p.address as address, n.ntaname, ST_AsText(p.geom) as function")
->join('Bundle\Entity\Property', 'p', 'WITH', 'ST_Contains(n.geom4326,p.geom)=true');
return $qb->getQuery()
->getResult();
This is not a real answer but just a comment on #George's code.
For me, using creof/doctrine2-spatial:0.0.1 and symfony/symfony:2.3.*, the annotation needed to be lower case for the schema to validate with php app/console doctrine:schema:validate, like this :
/**
* #var Point $geom
* #ORM\Column(type="point", nullable=true)
*/
protected $geom;
With a capital case, I kept getting this error message :
[Doctrine\DBALDBALException]
Unknown column type "Point" requested. Any Doctrine type that you use has to be registered with \Doctrine\DBAL\Types\Type::addType(). [...]

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