doctrine create query with 2 association - symfony

i have this entity
Post
/**
* #ORM\ManyToOne(targetEntity="SottoCategoria")
* #ORM\JoinColumn(name="sottocategoria_id", referencedColumnName="id", nullable=false)
*/
public $sottocategoria;
SottoCategoria
/**
* #ORM\ManyToOne(targetEntity="Categoria")
* #ORM\JoinColumn(name="categoria_id", referencedColumnName="id", nullable=false)
*/
public $categoria;
Categoria
/**
* #ORM\OneToMany(targetEntity="SottoCategoria", mappedBy="categoria")
*/
protected $sottocategorie;
how can I make this query? i need to find all post from categoria
post.sottocategoria.categoria
$query = $repository->createQueryBuilder('p')
->where('p.enabled = :enabled AND p.sottocategoria.categoria = :categoria')
->setParameters(array(
'enabled' => true,
'categoria' => $idCat,
))
i can't use p.categoria because i not have relation with post
my relation is post -> sottocategoria -> categoria
so my question is how i get all post from categoria? i have to use innerjoin?

$em = $this->getDoctrine()->getEntityManager();
$query = $em->createQuery(
'SELECT p,g,c FROM AcmeBlogBundle:Post p JOIN p.sottocategoria g JOIN g.categoria c WHERE p.enabled = :enabled AND g.categoria = :categoria ORDER BY p.id DESC')
solved

Related

Symfony-Doctrine : join a table with a view after persisting entity

In my database, I have a table T and a view V.
The view has some columns of my table and other data (from other tables).
In Symfony, I declared my view as a read-only Entity.
/**
* #ORM\Table(name="V")
* #ORM\Entity(readOnly=true, repositoryClass="AppBundle\Entity\Repository\VRepository")
*/
class V
{
In my T entity, I did a Join :
/**
* #ORM\OneToOne(targetEntity="V")
* #ORM\JoinColumn(name="T_id", referencedColumnName="V_id")
*/
private $view;
And I added just the getter :
/**
* Get view
*
* #return \AppBundle\Entity\V
*/
public function getView()
{
return $this->view;
}
Everything is working well when I want to read and show data.
But I have a problem after persisting a new T entity.
Symfony seems to lost posted data of my form when I create a new T entity (editAction() works perfectly).
An exception occurred while executing 'INSERT INTO T (T_id, T_name, updated_at) VALUES (?, ?, ?)' with params [null, null, "2017-09-01 15:30:41"]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Field 'T_id' cannot be empty (null)
When I remove ORM annotations of the $view property, it creates correctly my new record T in the database.
I think the problem is due to the fact that the V entity (the record in my SQL view) will exist just after the creation of T. And when I persist/flush data in Symfony, V doesn't exist yet. They are "created" at the same time.
I tried to add Doctrine #HasLifecycleCallbacks on my T entity and the #PostPersist event on the getView() method but it doesn't change anything...
Any idea to differ the Join after the creation of the entity ?
I know it's not conventional to use views as entities with Symfony but I haven't other choice.
I've just checked, it works fine with Bidirectional One-To-One relation
In my case tables are defined like:
create table T (`id` int(11) NOT NULL AUTO_INCREMENT, name varchar(100), primary key (id));
create view V as select id as entity, name, '123' as number from T;
Annotations in T:
/**
* #ORM\Table(name="T")
* #ORM\Entity()
*/
class T
{
/**
* #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, nullable=true)
*/
private $name;
/**
* #var V
*
* #ORM\OneToOne(targetEntity="V", mappedBy="entity")
*/
private $view;
Annotations in V:
/**
* #ORM\Table(name="V")
* #ORM\Entity(readOnly=true)
*/
class V
{
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, nullable=true)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="number", type="string", length=255, nullable=true)
*/
private $number;
/**
* #var T
*
* #ORM\Id
* #ORM\OneToOne(targetEntity="T", inversedBy="view")
* #ORM\JoinColumn(name="entity", referencedColumnName="id")
*/
private $entity;
And a test snippet to prove that it saves, updates and reads fine:
public function testCRUD()
{
/** #var EntityManager $manager */
$manager = $this->client->getContainer()->get('doctrine.orm.default_entity_manager');
$t = new T();
$t->setName('Me');
$manager->persist($t);
$manager->flush();
$t->setName('He');
$manager->flush();
$manager->clear();
/** #var T $t */
$t = $manager->find(T::class, $t->getId());
$this->assertEquals('He', $t->getView()->getName());
}
Based on the #Maksym Moskvychev answer : Prefer a bidirectional One-to-One relation.
T Entity :
/**
* #ORM\OneToOne(targetEntity="V", mappedBy="entity")
*/
private $view;
V Entity :
/**
* #ORM\OneToOne(targetEntity="T", inversedBy="view")
* #ORM\JoinColumn(name="V_id", referencedColumnName="T_id")
*/
private $entity;
Fix the loss of data after posting the addAction() form (new T instance).
In the form where I list all T records :
$builder->add('entity', EntityType::class, array(
'class' => 'AppBundle:T',
'choice_label' => 'id',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('t')
->orderBy('t.name', 'ASC')
->setMaxResults(25); // limit the number of results to prevent crash
}
))
Fix the too consuming resources problem (show 25 entities instead of 870+).
Ajax request :
$(".select2").select2({
ajax: {
type : "GET",
url : "{{ path('search_select') }}",
dataType : 'json',
delay : 250,
cache : true,
data : function (params) {
return {
q : params.term, // search term
page : params.page || 1
};
}
}
});
Response for Select2 :
$kwd = $request->query->get('q'); // GET parameters
$page = $request->query->get('page');
$limit = 25;
$offset = ($page - 1) * $limit;
$em = $this->getDoctrine()->getManager();
$repository = $em->getRepository('AppBundle:V');
$qb = $repository->createQueryBuilder('v');
$where = $qb->expr()->orX(
$qb->expr()->like('v.name', ':kwd'),
$qb->expr()->like('v.code', ':kwd')
);
$qb->where($where);
// get the DQL for counting total number of results
$dql = $qb->getDQL();
$results = $qb->orderBy('m.code', 'ASC')
->setFirstResult($offset)
->setMaxResults($limit)
->setParameter('kwd', '%'.$kwd.'%')
->getQuery()->getResult();
// count total number of results
$qc = $em->createQuery($dql)->setParameter('kwd', '%'.$kwd.'%');
$count = count($qc->getResult());
// determine if they are more results or not
$endCount = $offset + $limit;
$morePages = $count > $endCount;
$items = array();
foreach ($results as $r) {
$items[] = array(
'id' => $r->getCode(),
'text' => $r->getName()
);
}
$response = (object) array(
"results" => $items,
"pagination" => array(
"more" => $morePages
)
);
if (!empty($results))
return new Response(json_encode($response));

Database Performance issues with Sonata Admin entity listing

I am using Symfony2 and Sonata Admin Bundle but I am encountering performance issues with entity listing.
I Have this Entity with Relations to other Entities:
class Collection
{
/**
* Primary Key - autoincrement value
*
* #var integer $id
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* OWNING SIDE
*
* #ORM\ManyToOne(targetEntity="\App\Entity\Order")
* #ORM\JoinColumn(name="orderId", referencedColumnName="id")
* #var \App\Entity\Order
*/
protected $order;
/* ... */
}
On my AdminClass i'd like to show for each collection some order details and other related entity information.
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->add('id')
->add(
'order.number',
null,
array(
'template' => 'App:Admin:Field/order-data.html.twig',
'label' => 'Order Number'
)
);
}
But these creates fore each referenced entity one extra query. How can I resolve this?
My first Idea was to extend the createQuery Method:
public function createQuery($context = 'list')
{
/** #var \Doctrine\ORM\QueryBuilder|QueryBuilder $query */
$query = parent::createQuery($context);
$query
->addSelect($query->getRootAliases()[0])
->addSelect('collectionOrder')
;
$query
->leftJoin($query->getRootAliases()[0] . '.order', 'collectionOrder')
;
return $query;
}
But this takes no effect.
So how can i manage to get all needed data with one query to reduce database load time?
Add the select to the table you joined with left join.
Example:
$query
->leftJoin($query->getRootAliases()[0] . '.order', 'collectionOrder')
->addSelect('collectionOrder')
;

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

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

Doctrine and join result type

I've 2 simple tables with many-to-one mono directional relations:
class Event extends BaseEvent
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
*
* #ORM\ManyToOne(targetEntity="Rposition")
* #ORM\JoinColumn(name="owner", referencedColumnName="id",onDelete="CASCADE", nullable=false )
**/
private $owner;
....
and the origin of the owner field class:
class Rposition
{
/**
* #var integer
*
* #ORM\Column(name="id", type="string", length=36)
* #ORM\Id
* #ORM\GeneratedValue(strategy="UUID")
*/
private $id;
/**
*
*
* #ORM\Column(name="scenario", type="string", length=255, nullable=false)
*/
private $scenario;
both class having the right getter/setter and a __tostring()
I need to implements a query like this:
public function findAllByScenario($scenario) {
$q = $this->getEntityManager()->createQuery("SELECT e , r.scenario , r.id
FROM LapoMymeBundle:Event e
JOIN LapoMymeBundle:Rposition r
WITH e.owner = r.id
WHERE r.scenario = :scenario
ORDER BY e.start ASC, e.end ASC
")->setParameter('scenario', $scenario);
try {
return $q->getResult();
} catch (\Doctrine\ORM\NoResultException $e) {
return null;
}
}
-is it the right way to making a join and sending back result from it?
-what are the right way for fetching the fields from the array of objects?
something like that's:
$events=$this->getDoctrine()->getManager()
->getRepository('LapoMymeBundle:Event')->findAllByScenario($scenario);
$outputUsersArray = Array();
foreach($events as $event)
{
$eventArray = Array();
$eventArray[JSONResponseFields::KEY_ID] = $event->getId();
$eventArray[JSONResponseFields::KEY_NAME] = $event->getOwner()->getId();
$outputEventsArray[] = $eventArray;
} ....
rise an error likes:
FatalErrorException: Error: Call to a member function getId() on a non-object in.....
You can use Doctrine Query builder to get this working. Assuming you have Querybuilder instance. The query would look like
$qb = this->getDoctrine()->getRepository('BundleName:Event')->createQueryBuilder('e');
$query = $qb
->select('e','p')
->innerjoin('u.owner','r')
->where('r.scenario= :scenario')
->setParameter('scenario', $scenarioid)
->getQuery();
$chatuser = $query->getResult();

Resources