Doctrine and Symfony: magic methods and caching - symfony

I'm currently implementing the doctrine result cache so I've set
result_cache_driver: apc
into my configuration.
Then I've correctly got query cache working inside the Repository, using for example
->setResultCacheId(sprintf('posts_for_user_%d', $userId))
First problem come when I used these things in doctrine:
$repository->findOneBy(array)
$repository->findBy(array)
which can maybe easily overridden in the repository.
The problem which I can't get past is to use the ParamConverter to use doctrine caching and also entities association.
For example, if I have a Team entity with a OneToMany relation to Player I usually do
$team->getPlayers()
I don't have the control over the caching of that query. Is that possible in some way?

When you run methods like find/findBy or process over PersistentCollection doing $team->getPlayers(), there is UoW which loads the data using EntityPersister and ObjectHydrator to hydrate an object. These objects have no support of result cache driver.
In the other hand, when you use DQL or QueryBuilder, your code products Query object that extends AbstractQuery. If you look inside AbstractQuery::execute you will see this pretty piece of code which makes using of result cache driver possible
$cache = $queryCacheProfile->getResultCacheDriver();
$result = $cache->fetch($cacheKey);
if (isset($result[$realCacheKey])) {
return $result[$realCacheKey];
}
So my suggestion - try load your entities using QueryBuilder and leftJoins on children Collections.
$this->createQueryBuilder('x')->select('x, p')->leftJoin('x.players', 'p')->....;
It'll create the possibility of using result cache driver.

Related

How to update/replace an existing object/entity in Doctrine without using merge?

Currently I am working with Doctrine 2 within a Symfony 2.8 project. The online Data can be sync with mobile apps. When the online project receives data from the mobile apps, the received entities are de-serialized from JSON and merged into the online database. Using $entityManger->merge($receivedEntity) automatically ensures that new entities are inserted and existing entities are updated.
This works fine EntityManager::merge will no longer be supported in Doctrine 3.
To prepare for this I would like to handle the merging manually:
public function mergeEntity($receivedEntity, $class) {
$repo = $this->em->getRepository($class);
$existingEntity = $repo->findOneById($receivedEntity->getId());
if (!$existingEntity) {
// Insert NEW entity
$this->em->persist($receivedEntity);
$this->em->flush();
} else {
// Update EXISTING entity
$existingEntity->setProperty1($receivedEntity->getProperty1());
$existingEntity->setProperty2($receivedEntity->getProperty2());
...
$existingEntity->setPropertyN($receivedEntity->getPropertyN());
$this->em->persist($existingEntity);
$this->em->flush();
}
}
While this work, it is quite cumbersome and not very flexible. Every time the entity class changes, e.g. a property is added, removed or updated, the merge method would have to updated as well. Additionally the method only works for one specific class and every entity class would need its own merge method.
While reflections could be used to limit these problems it is still far more complex than the existing EntityManager::merge...
Isn't it possible somehow to replace an existing entity with a new "version"?
// Is something like this possible/available?
$this->em->replaceEntity($receivedEntity);
In Short: Is creating completely custom update/replace methods the correct way to update existing entities? Or are there any build in features (beside merge) which can be used to achieve this?

Avoid doctrine postloadEvent if unnecessary

For an blog entry entity I am loading data from an api via the doctrine postloadEvent. For this i created a listener service with an postloadMethod in it.
public function postLoad(BlogEntry $blogEntry)
{
$blogentry->setName($apiClient->getName($blogEntry->getId()))
$blogentry->setContent($apiClient->getContent($blogEntry->getId()))
...
}
This means, there is already a local repository with blog entries. These blogentries are connected to a blog. If i now only want to count the blogentries for each blog, i would implement a getBlogEntryCount() method on the BlogEntry entity and call it.
$blog->getBlogEntryCount();
The problem now is, that the postLoad event is triggered unnecessary, even if i need no data from the api.
How can i avoid this behaviour in doctrine/symfony/sonata admin? Is there mechanism like "lazy loading" for doctrine entities?
Update to Jose M. González Solution
To get only the count of the collection, the extra_lazy loading solution will do it.
For getting local information without triggering the api call i used the said repository function. To get this information working in list view in sonata admin, i created a non-doctrine-related array field "blogEntriesSimple" in my Blog Entity next to my "blogEntries" (which is normally holding the complete entity) field.
I attached an entitylistener with postLoad function to the Blog Entity, which is filling up my blogEntriesSimple array with the information from my custom repository function.
Thats it.
I think this solution is a bit hacky, but until no cleaner solution is available, this will do it.
i think that you can achieve this with the extra lazy associations that permit that you count your related entities without hydrating it
Edited
Also you can do a DQL query that only hidrate a partial view of your entity and it can be used to count the rows and access to your properties, for example:
select be.id,be.title from AppBundle\Entity\BlogEntry be
This query must not trigger the postLoad event
I hope this can help you

Symfony, Move data from em1 to em2

I use Symfony 2.8.
I have a huge table with a lot of datsa i don't need anymore. I want to move datas in another database with a symfony command.
I have 2 entities managers:
em_archive = mydb_archive
em_default = mydb
I have the code:
$emDefault = $this->getContainer()->get('doctrine')->getManager('em_default');
$emArchive = $this->getContainer()->get('doctrine')->getManager('em_archive');
$repoArchive = $emDefault->getRepository('MyBundle\Datas', 'em_archive');
$repoDefault = $emDefault->getRepository('MyBundle\Datas', 'em_default');
$dataTest = $repoDefault->getOneDataTest();
$dataTest->setOldId($dataTest->getId());
$dataTest->setId(null);
$emArchive->persist($dataTest);
$emArchive->flush();
With the code i have the error "Notice: Undefined index: 000000004618b9830000000172fdd8f3"
Is there a problem with one of my entity links (onetomany, manytoone) ?
Entities are mapped to an Entity Manager by the class type, so you cannot have the same class mapped into two entity manager. This is by design, as you can see from proxy classes, which contains references to the UnitOfWork and/or to the owning EntityManager.
Also, the performance of load-hydrate-persist-dehydrate-flush is very poor, you get lot of memory leak issues, and so on.
So, you can't use the ORM layer, but you can still use Doctrine DBAL.
See DBAL insert for an example of "mapping" syntax, or use good old-fashioned queries.

Possible to use Entity getters in custom repository?

I am in need of grabbing a few fields of an entity, running them through some processing, and returning the processed data. I am wondering if it is possible to call the getters of the Entity for which the custom repository is being built inside that custom repository? The only way I thought of so far seems like it would create an infinite loop by calling the entity's repository, which would include a call to the very custom repository being used as well.
I can write the actual queries, but I figured it it would be cleaner to access the data through existing methods, then why not?
Update: So I built a method in the custom repository that I pass the entity object to that I want to work with. Seems a little weird that I would have to pass the entity to it's own custom repository so that it would know what to act on, but I can't seem to find any other way. So in my Controller right now I am calling the entity, then calling up the repository for the entity, and passing the previously called entity to the repository. Is that right?
$user = $this->get('security.context')->getToken()->getUser();
$user_repository = $this->getDoctrine()->getRepository('AppBundle:User');
$profiles = $user_repository->getAllUsersProfiles($user);
It turns out I had the whole concept of Custom Repositories incorrect. I was using them to store methods that would pull out data relevant to the entity, but only relevant in the sense that the entity was being used to filter something else.
Prior to this epiphany my User Custom Repository had methods like getAllUsersProfiles($user) and getAllUsersCampaigns($user). My previously incorrect understanding was that because the User entity was the thing that was central to filtering the Profiles and Campaigns, that these belonged in the User Custom Repository.
What I have now come to understand is that getAllUserProfiles($user) belongs in the Profiles Custom Repository, and would be more appropriately named getAllByUser($user). Now when I need to use it, I pull up the Profiles Repository and call the appropriate method:
$profile_repository = $this->getDoctrine()->getRepository('AppBundle:Profile');
$all_profiles = $profile_repository->getAllByUser($user);

How to access the Doctrine replace() method?

I am doing my first project with Symfony2 + Doctrine, and currently trying to implement replacing records. However when I try to call
$em->save($product);
or
$em->replace($product);
(instead of)
$em->persist($product);
I get fatal errors. So I started digging around to try to find the persist() method so I could see what other methods were available. I searched the entire contents of the vendors/doctrine directory and could not find any references to the persist or flush methods. Where the heck are these located? I tried following the code but quickly got lost.
So the main question: How can I do a replace() with doctrine in Symfony2?
Sub-question: Where are the persist() and flush() methods? Not being able to find them is frustrating in itself.
Incase somebody else is wondering, try looking into:
$em = $service->get('doctrine.orm.entity_manager');
$entity = $em->merge($entity);
$em->flush();
From the docs, Doctrine\ORM\EntityManager::merge():
Merges the state of a detached entity into the persistence context of
this EntityManager and returns the managed copy of the entity. The
entity passed to merge will not become associated/managed with this
EntityManager.
Should do the trick for you.
I don't think that Doctrine's replace() method is supported in Symfony, or at least it is not accessible through EntityManager.
If you need to update your existing entity, then you can simply do it as described here - http://symfony.com/doc/current/book/doctrine.html#updating-an-object.
As for persist() and flush() methods - you can find them in vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManager.php

Resources