Doctrine2 Paginator, getArrayResult - symfony

I'm dealing with a fairly complex querybuilder (in a repository function), which is using a lot of things like partial c.{id,name,created} and a bunch of fetchjoins to try and keep both the amount of data and the amount of queries down.
This is however not enough, but the front end representation is paged anyway, so I'd like to make some ajax calls to fetch data when needed.
$qb = $this->createQueryBuilder('c')
->select('a whole bunch')
->join('many joins')
->setFirstResult(0)
->setMaxResults(10)
The above method doesn't work for complex joins, I do setMaxResults(10) and get 2 results, and setMaxResults(1000) gets me 118 results. Doctrine advises to use their Pagination class, as it will handle the count/iteration properly.
Now that works all fine if I loop over the Iterator object provided by new Paginator($query, true), but the code calling the repository function expects to get an Array from getArrayResult.
The iterator contains full entity objects, which means I would have to rewrite all the services to use methods instead of array keys.
Is there a way to use the Paginator with an ArrayResult?

Add Hydration Mode to your query.
//Set query hydration mode
$query->setHydrationMode(\Doctrine\ORM\Query::HYDRATE_ARRAY);

Related

Azure Mobile Apps - Overriding the QueryAsync method with custom code in Table Controller

I would like to override the Query Async Method with some Custom code, so I can access an external api, get some data and then use this data to query the db tables to get the results, this should support all the default sync and paging features provided by the base method. I'm using the latest 5.0 DataSync library. Old versions returned a IQueryable so this was easy to do, but now it returns an action result. Is there any solution? Could not find any docs.
e.g. I get a set of guids from api. I query the db tables with these guids and get all the matching rows and return back to client.
I know how to override the method, call external api's, get data and query the db tables, but this provides me with a custom data format not the one that the query async gives by default with paging support.
The way to accomplish this is to create a new repository, swapping out the various CRUD elements with your own implementation. You can use the EntityTableRepository as an example. You will note that the "list" operation just returns an IQueryable, so you can do what you need to do and then return the updated IQueryable.

symfony3 doctrine2 association counter field

Consider the following case: I have two entities: Article and ArticleComment:
// \AppBundle\Entity\Article
/**
* #ORM\OneToMany(targetEntity="ArticleComment", mappedBy="article")
*/
private $comments;
I need to store the amount of comments in a field on the article (eg. articles.comments_count). The field needs to be updated whenever a comment is created or deleted.
Previously I used the CakePHP framework which has built-in CounterCache behavior which does this automatically. I've tried my best to find something similar for Doctrine 2 (starting with DoctrineExtensions library) but nothing seems to do what I'm looking for.
Any library that does this? Or do I have to come up with my own solution?
Edit: I've tried using Entity Events but I require this behavior on many entities so I'm interested in a reusable solution
You can take a look at the extra lazy associations. http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html
This way you don't need to store the comment_counter as you will be able to use the count() function on your collection without loading the full collection.
Internally, Doctrine will issue a "select count" query.
Here is another answer which avoids storing this kind of aggregate and enables you to use the paginator as you've requested in comments. I didn't test it yet so there could be some errors.
$qb = $em->createQueryBuilder();
$qb
->select('a.title, a.author, count(c)')
->from('Article', 'a')
->leftJoin('a.comments', 'c')
->groupBy('a.id');
$paginator = $this->get('knp_paginator');
$pagination = $paginator->paginate($qb, $page, $limit);
As I said, this issue is not really Doctrine related because your initial model design is bad.
Usually, you don't need to store an aggregate which can be computed with a count/groupby query.
This kind of aggregate is useful when you have a lot of joined entities which creates a real overhead during computing. Else, you don't need it.

Doctrine 2.3 Criteria. Accessing a related Object

I am trying to set up a Criteria according to the Doctrine Docs.
Unfortunately they don't tell you how to access attributes of an related Object. Let me give you an example.
I have an ArrayCollection of Products. Every Product has a Category. I want to filter the ArrayCollection for a Category Name. Now I am trying to set up a Criteria as follows:
$criteria = Criteria::create()
->where(Criteria::expr()->eq("category.name", "SomeCategoryName"));
Now I get the following Exception:
An exception has been thrown during the rendering of a template ("Unrecognized field: category.name")
How can I access a related Object?
I looked into the source code Criteria::expr()->eq("name", --- second value ---). Second value expects an instance of Doctrine\Common\Collections\Expr\Value. So it's not possible to put another Expr or criteria in there. Only the Expr And and Or take another Expr.
I'm pretty sure you are suppose to solve this with other functions like filter() or get an iterator with getIterator(). This is how it can be done with the filter() method.
$filteredProducts =
$products->filter(function($key, $element) use ($categoryName) {
return $element->getCategory()->getName() === categoryName;
});
If you can an Iterator for each next relation you can nest foreach loops and filter inside those.
That probably belongs in a repository method, rather than a filter method. If you're wanting to get a pre-filtered list of Products in a collection on a parent object (like an Order or something), you can filter the child collection in the query builder. However, you have to deal with the possibly confusing side-effect of not having fully hydrated objects.
This should give you a list of Order objects, which only having Product children matching a category name.
class OrderRepository extends EntityRepository {
public function findOrderWithProductCategory($category)
{
$builder = $this->createQueryBuilder('o')
->select('o, p')
->leftJoin('o.products', 'p')
->join('p.category', 'c', 'WITH', 'c.name = :category')
->setParameter('category', $category);
}
}
If you don't know what kind of categories you're interested until later, then you're probably better using #Flip's solution anyway, and pre-hydrating all the categories. Using partial hydration and standard ArrayCollection::filter() closures, performs pretty well in most cases.
That said, it would be quite nice as a feature. I suspect the Doctrine guys would be reluctant because the current Criteria implementation is very light-weight and they probably want to keep it that way.

How can I pass a parameter to a Doctrine2 custom function in the Query Builder select() method?

In my Symfony2 project I am retrieving an ordered set of entity IDs from an Elasticsearch index. I'm then passing this list to Doctrine2 to retrieve the actual entities, by way of a WHERE IN() call.
This doesn't return them in the correct order, so I think I need to use the MySQL-specific FIELD() function. I've created a custom DQL function to allow the functionality.
So now I'm using the following code to build a Doctrine query object, but the parameters aren't being parsed into the select() method:
$itemIds = array(4,8,2,1);
$this->getRepository()
->createQueryBuilder('i')
->select('i, FIELD(i.id, :ids_string) AS HIDDEN fixed_order')
->where('i.id IN (:ids)')
->setParameters(array(
'ids_string' => implode(',', $itemIds),
'ids' => $itemIds))
->orderBy('fixed_order', 'ASC')
->getQuery()
;
This fails with the error "Invalid parameter number: number of bound variables does not match number of tokens", so apparently it's not "seeing" the :ids_string in the select() method.
I initially tried putting the FIELD() function in the orderBy() call, but it doesn't look like this is getting parsed for custom DQL function calls, and I imagine I'd run into the same problem as above.
EDIT 1 I'm aware I could put the base data directly into the select() call.
EDIT 2 I've given up and put the bare data into the select() call (which I wanted to avoid). This worked, but then it became necessary to implement Koc's suggestion of using the HIDDEN keyword to prevent Doctrine returning array(Object i, array(fixed_order)) instead of just Object i
From Doctrine 2.2 you can use HIDDEN keyword for avability field in order by without hydration them.
Try:
->select('i, FIELD(i.id, :ids_string) AS HIDDEN fixed_order')
You're going to kick yourself when you notice the problem...
Try re-reading your sentence: "so apparently it's not "seeing" the :ids_string in the select() method".
And then take a close look at your code: 'id_string' => implode(',', $itemIds)

Why is doctrine updating every single object of my form?

I've got a big Symfony 2 form on a huge collection (over 10k objects). For simple reasons, I cannot display a form of thousands of objects. I am displaying a form of about 300 objects.
I have found no way to filter a collection into a form and thus do the following :
$bigSetOfObjects = array(
'myObject' => $this
->getDoctrine()
->getRepository('MyObject')
->findBy(... )
);
$form = $this->createForm(new MyObjectForm(), $bigSetOfObjects);
// And a little further
if ($this->getRequest()->getMethod() == 'POST') {
$form->bindRequest($this->getRequest());
$this->getDoctrine()->getEntityManager()->flush();
}
Everything works great. Form is displayed with the correct values and the update works fine also. Data is correctly saved to the database. The problem is that Doctrine is executing a single update statement per object meaning the whole page is about 300 SQL statements big causing performance issues.
What I do not understand is that I'm updating only a couple of values of the form, not all of them. So why is Doctrine not able to detect the updated objects and thus update only those objects in the database?
Is there anything I'm doing wrong? I might have forgotten?
By default Doctrine will detect changes to your managed objects on a property-by-property basis. If no properties have changed then it should not be executing an update query for it. You may want to check that the form isn't inadvertently changing anything.
You can, however, change how doctrine determines that an object has changed by modifying the tracking policy. Since you are working with a lot of objects you may want to change to the DEFERRED_EXPLICIT tracking policy. With this method you would specifically call:
$em->persist($object);
on the entities that you want to be updated. You would have to implement your own logic for determining if an object needs to be persisted.

Resources