Doctrine 2 delete with query builder - php

I have two Entities with relation OneToMany, Project and Services. Now i want to remove all the services by project_id.
First attempt:
$qb = $em->createQueryBuilder();
$qb->delete('Services','s');
$qb->andWhere($qb->expr()->eq('s.project_id', ':id'));
$qb->setParameter(':id',$project->getId());
This attempt fails with the Exception Entity Service does not have property project_id. And it's true, that property does not exists, it's only in database table as foreign key.
Second attempt:
$qb = $em->createQueryBuilder();
$qb->delete('Services','s')->innerJoin('s.project','p');
$qb->andWhere($qb->expr()->eq('p.id', ':id'));
$qb->setParameter(':id',$project->getId());
This one generetate a non valid DQL query too.
Any ideas and examples will be welcome.

You're working with DQL, not SQL, so don't reference the IDs in your condition, reference the object instead.
So your first example would be altered to:
$qb = $em->createQueryBuilder();
$qb->delete('Services', 's');
$qb->where('s.project = :project');
$qb->setParameter('project', $project);

If you really can't get project object and you have to handle only with id you can use this.
Citation from doctrine documentation:
There are two possibilities for bulk deletes with Doctrine. You can either issue a single DQL DELETE query or you can iterate over results removing them one at a time. (Below I paste only first solution)
DQL Query
The by far most efficient way for bulk deletes is to use a DQL DELETE query.
Example that worked in my project
$q = $em->createQuery('delete from AcmeMyTestBundle:TemplateBlock tb where tb.template = '.intval($templateId));
$numDeleted = $q->execute();
In entity TemplateBlock I have property called template which is mapped to template_id in db.
But I agree that highly preferred way of doing it is using objects.

Related

Doctrine QueryBuilder groupBy relation not working

I have the following query:
$query = $qb->select('p')
->from(get_class($page), 'p')
->innerJoin('p1.translations', 't')
->groupBy('p.id')
->addGroupBy('t.id')
->getQuery();
Doctrine returns the above like:
Page entity -> [translation 1, translation 2, translation 3]
But I want the result like:
Page entity 1 -> translation 1
Page entity 1 -> translation 2
Page entity 1 -> translation 3
Does anyone know how I can do this? I want to return a list of entities.
First of all, both groupBy are completely superfluous, assuming both id fields you are grouping by are the primary keys of their respective tables. For any given (p.id, t.id) combination there will only be at most one result, whether you are grouping or not.
Second, even though you are joining the the translations table, you are not fetching any data from it (t is absent from your ->select()). It just appears that way since doctrine "magically" loads the data in the background when you call $page->getTranslations() (assuming your getter is called that way).
Third, your issue isn't with the groupBy. You are completely misunderstanding what an ORM actually does when hydrating a query result. The SQL query that doctrine generates from your code will actually return results in a fashion just like you expect, with "Page entity 1" repeated multiple times.
However, now comes the hydration step. Doctrine reads the first result row, builds a "Page entity 1" and a "Translation 1" entity, links them and adds them to it's internal entity registry. Then, for the second result, Doctrine notices that it has already hydrated the "Page entity 1" object and (this is the crucial part!) reuses the same object from the first row, adding the second translation to the ->translation collection of the existing object. Even if you read the "Page entity 1" again in a completely different query later in your code, you still get the same PHP object again.
This behaviour is at the core of what Doctrine does. Basically, any table row in the database will always be mirrored by a single object on the PHP side, no matter how often or in what way you actually read it from the database.
To summarize, your query should look like this:
$query = $qb->select('p, t')
->from(get_class($page), 'p')
->innerJoin('p.translations', 't')
->getQuery();
If you really need to iterate over all (p, t) combinations, do so with nested loops:
foreach ($query->getResult() as $p) {
foreach ($p->getTranslations() as $t) {
// do something
}
}
EDIT: If your relationship is bidirectional, you could also turn your query around:
$query = $qb->select('t, p')
->from('My\Bundle\Entity\Translation', 't')
->innerJoin('t.page', 'p')
->getQuery();
Now in your example you actually get 3 results that you can iterate over in a single foreach(). You would still only get a single "Page entity 1" object, with all the translations in the query result pointing to it.

Symfony Doctrine QueryBuilder OneToMany filtering

I have following code in my Repository
// ProductBundle/Repository/ProductRepository.php
$qb->where($qb->expr()->eq('afp.id', 15));
$qb->andWhere($qb->expr()->eq('afp.id', 14));
return $qb
->select('a', 'afp')
->leftJoin('a.productFields', 'afp')
->getQuery()
->getResult();
But I always get null return, but I want to get products which have both productFields (so orWhere is not good).
You want to use MEMBER OF instead of comparing id. Otherwise, you're looking for a record that has two different id values, which of course isn't possible.
This will do what you want:
$qb->where($qb->expr()->isMemberOf(15, 'a.productFields'));
$qb->andWhere($qb->expr()->isMemberOf(14, 'a.productFields'));
Try something like this (Symfony 5):
$qb->andWhere(':c MEMBER OF a.productFields');
$qb->setParameter('c', 15);

Using Query Builder in Symfony 2

I am trying to use Query Builder in Symfony2 to get some records from a database. I run the normal query in SQL and it returns the correct results. The query is
SELECT pg.name, pg.description
FROM pm_patentgroups pg
LEFT JOIN pm_portfolios pp ON pp.id = pg.portfolio_id
I want to use the exact query using Doctorine query builder in Symfony2. What I have tried so far is
$repository = $this->getDoctrine()
->getRepository('MunichInnovationGroupBundle:PmPatentgroups');
$query = $repository->createQueryBuilder('pg')
->from('pm_patentgroups', 'pg')
->leftJoin('pg','pm_portfolios','pp','pp.id = pg.portfolio_id')
->getQuery();
$portfolio_groups = $query->getResult();
but its giving me the following error:
Warning: Missing argument 1 for Doctrine\ORM\EntityRepository::createQueryBuilder()
I am new to Symfony2 and Doctorine. Can you please tell me what is going wrong here?
Thanks
You are missing the alias when using createQueryBuilder. Since you have the repository you can drop the from portion and just use
$query = $repository->createQueryBuilder('pg')
Something like:
$qb = $this->getDoctrine()->createQueryBuilder();
$qb->addSelect('pm_patentgroups');
$qb->addSelect('pm_portfolios');
$qb->from('MunichInnovationGroupBundle:PmPatentgroups','pm_patentgroups');
$qb->leftJoin('pm_patentgroups.pm_portfolios','pm_portfolios');
This assumes you have your two entities properly related.
Lots of examples in the D2 manual. Just keep in mind that query builder works with objects, not sql.
And by the way, your error message comes from the fact that the entity repository (as opposed to the entity manager) requires an alias.

What does this doctrine query do?

$this->facebook_applications = Doctrine::getTable('FacebookApplication')
->createQuery('a')
->execute();
I don't understand how this works at all. Why is the query just 'a' and why does that seem to get a list of the applications?
The static method Doctrine::getTable() gets an object that represents the FacebookApplication table.
That object has a method called createQuery(), which creates a Doctrine_Query object for querying that table. The argment ('a'), specifies an alias for the table in the query.
So essentially Doctrine::getTable('FacebookApplication')->createQuery('a') creates a query that translates to SQL like:
SELECT * FROM FacebookApplication as a
Which, naturally, returns all rows from that table.
You can see it by using :
$this->facebook_applications->getSqlQuery()

Getting Doctrine to use MySQL "FORCE INDEX"

I have a query in Doctrine's DQL that needs to be able to use MySQL's "FORCE INDEX" functionality in order to massively reduce the query time. Below is what the query basically looks like in plain SQL:
SELECT id FROM items FORCE INDEX (best_selling_idx)
WHERE price = ... (etc)
LIMIT 200;
I assume I have to extend some Doctrine component to be able to do this with DQL (or is there a way to inject arbitrary SQL into one of Doctrin's queries?). Anyone have any ideas?
I've found very few helpful Doctrine_RawSql examples online, so here's what I ended up doing to create my query.
$q = new Doctrine_RawSql();
$q->select('{b.id}, {b.description}, {c.description}')
->from('table1 b FORCE INDEX(best_selling_idx) INNER JOIN table2 c ON b.c_id = c.id')
->addComponent('b', 'Table1 b')
->addComponent('c', 'b.Table2 c');
If you don't like native SQL, you can use the following patch. https://gist.github.com/arnaud-lb/2704404
This patch suggests to create only one custom Walker and set it up as follows
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'UseIndexWalker');
$query->setHint(UseIndexWalker::HINT_USE_INDEX, 'some_index_name');
I created a Doctrine SqlWalker extension to apply USE INDEX and FORCE INDEX hints using DQL on top of MySql. Works with both createQuery and createQueryBuilder. You can set different index hints per DQL table aliases.
use Ggergo\SqlIndexHintBundle\SqlIndexWalker;
...
$query = ...;
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, SqlIndexWalker::class);
$query->setHint(SqlIndexWalker::HINT_INDEX, [
'your_dql_table_alias' => 'FORCE INDEX FOR JOIN (your_composite_index) FORCE INDEX FOR ORDER BY (PRIMARY)',
'your_another_dql_table_alias' => 'FORCE INDEX (PRIMARY)',
...
]);
https://github.com/ggergo/SqlIndexHintBundle

Categories