Doctrine2 ignore undefined discriminator classes - symfony

While using the doctrine2 "class table inheritance" to schedule events of different types.
Hypothetical scheme:
This is all fine but the fact is that - because of our modular in-house system - some classes could be "unregistered" from the discriminator map (in cache?).
When this happens the findAll will throw an exception telling us he cannot find the correct object mapping.
So the option i thought of were:
Rewrite the repository methods (findOne, findBy, etc.) to add a filter to the query.
$discrClasses = array();
foreach($this->_em->getMetadataFactory()->getAllMetadata() as $class) {
$discrClasses[] = $class->getName();
}
$discrQuery = sprintf('f.discr INSTANCE OF (%s)', implode(',', $discrClasses));
My question is: is this the right way to handle this or is there a better solution?

Related

Where to put function/method to test result of SQL Query

I continue to struggle with Symfony in regards to where to put some logic and functions.
In this case, I have a simple query. I want to know how many "Profiles" depend on an address.
the query:
SELECT count(*)
FROM beneficiary_profile AS bp
JOIN person AS p ON bp.beneficiary_id = p.id
JOIN contact_address AS ca ON p.contact_address_id = ca.id
WHERE ca.id = 2108 -- address id
the poor way of doing this [in my controller] with entity methods is this
$dependant = 0;
foreach ($address->getPeople() as $person) {
if ($person->getBeneficiaryProfile() !== null) {
$dependant++;
}
}
ultimately, this serves as a flag to print a warning about editing a dependent address record.
My first thought was to add a new method to the entity, $address->isDependent() that would return bool based on ($count > 1) but this would require me to get the entity repository from the entity.
Soon there will be a whole host of logic to go with unlinking then deletion (or not) to prevent orphan records. The logic is not as simple as cascading as there can be many people between the address and profile. there is a house record in the mix as well.
Should I just build my query in the address repository, then set the flag in the controller? (twig reads the flag and displays the warring or not)
Entity repository is the class which holds all your queries to database related to some entity, in your case it is address. So, yes, you should create new method in AddressRepository and use where you need.
Not sure what you mean by code reuse, you can get repository almost everywhere in symfony classes. So, you write something like
$count = $this->getDoctrine()
->getRepository(ContactAddress::class)
->getDependencyCount($address->getId());
And use this $count variable in your code. As I already said it is simple and clear.
And in symfony4 you can inject even the repository in your controller, so code can be reduced to something like:
public function __construct(AddressRepository $repo)
{
$this->repo = $repo;
}
public function someAction()
{
$count = $this->repo->getDependencyCount($address->getId());
}

How to replace EntityManager::merge in Doctrine 3?

I am working an Symfony 2.8 based web app project which currently uses Doctrine 2. The project is basically a simple ToDo list application which can be synced with a mobile app (iOS/Android).
While reading the Update notes of Doctrine 3 I discovered, that EntityManager::merge will no longer be supported.
An alternative to EntityManager#merge() is not provided by ORM 3.0,
since the merging semantics should be part of the business domain
rather than the persistence domain of an application. If your
application relies heavily on CRUD-alike interactions and/or PATCH
restful operations, you should look at alternatives such as
JMSSerializer.
I am not sure what is the best/correct way to replace EntityManager::merge?
Where do I use merge:
During the sync of the mobile apps with the web app the data is transferred as serialized JSON which is than de-serialized by JMSSerializer to an entity object. When the web app receives a ToDoEntry object this way, it can be a new ToDo-Entry (not known in the web app yet) or an updated existing entry. Either way, the received object is not managed by the EntityManager. Thus $em->persist($receivedObject) will always try to insert a new object. This will fail (due to the unique constraint of the id) if the ToDo-Entry already exists in the web app and needs to be updated.
Instead $em->merge($receivedObject) is used which automatically checks wether an insert or update is required.
Hot wo solve this?
Of course I could check for every received objects if an entity with the same ID already exists. In this case could load the existing object and update its properties manually. However this would be very cumbersome. The real project of course uses many different entities and each entity type/class would need its own handling to check which properties needs to be updated. Isn't there a better solution?
You can try to use registerManaged() method of Doctrine\ORM\UnitOfWork.
// $this->em <--- Doctrine Entity Manager
// $entity <--- detached Entity (and we know that this entity already exists in DB for example)
$id = [$entity->getId()]; //array
$data = $entity->toArray(); //array
$this->em->getUnitOfWork()->registerManaged($entity, $id, $data);
Of course, You can check the state of Your Entity using getEntityState() of Doctrine\ORM\UnitOfWork before/after perfoming needed actions.
$this->eM->getUnitOfWork()->getEntityState($entity, $assert = 3)
$assert <-- This parameter can be set to improve performance of entity state detection by potentially avoiding a database lookup if the distinction between NEW and DETACHED is either known or does not matter for the caller of the method.
While I have posted this question quite a while ago, it is still quite active. Until now my solution was to stick with Doctrine 2.9 and keep using the merge function. Now I am working on new project which should be Doctrine 3 ready and should thus not use the merge anymore.
My solution is of course specific for my special use case. However, maybe it is also useful for other:
My Solution:
As described in the question I use the merge method to sync deserialized, external entities into the web database where a version of this entity might already exist (UPDATE required) or not (INSERT required).
#Merge Annotation
In my case entities have different properties where some might be relevant for syncing and must be merged while others are only used for (web) internal housekeeping and must not be merged. To tell these properties appart, I have created a custom #Merge annotation:
use Doctrine\Common\Annotations\Annotation;
/**
* #Annotation
* #Target("PROPERTY")
*/
final class SyncMerge { }
This annotation is then be used to mark the entities properties which should be merged:
class ToDoEntry {
/*
* #Merge
*/
protected $date;
/*
* #Merge
*/
protected $title;
// only used internally, no need to merge
protected $someInternalValue;
...
}
Sync + Merge
During the sync process the annotation is used to merge the marked properties into existing entities:
public function mergeDeserialisedEntites(array $deserializedEntities, string $entityClass): void {
foreach ($deserializedEntities as $deserializedEntity) {
$classMergingInfos = $this->getMergingInfos($class);
$existingEntity = $this->entityManager->find($class, $deserializedEntity->getId());
if (null !== $existingEntity) {
// UPDATE existing entity
// ==> Apply all properties marked by the Merge annotation
foreach ($classMergingInfos as $propertyName => $reflectionProperty) {
$deserializedValue = $reflectionProperty->getValue($deserializedEntity);
$reflectionProperty->setValue($existingEntity, $deserializedEntity);
}
// Continue with existing entity to trigger update instead of insert on persist
$deserializedEntity = $existingEntity;
}
// If $existingEntity was used an UPDATE will be triggerd
// or an INSERT instead
$this->entityManager->persist($deserializedEntity);
}
$this->entityManager->flush();
}
private $mergingInfos = [];
private function getMergingInfos($class) {
if (!isset($this->mergingInfos[$class])) {
$reflectionClass = new \ReflectionClass($class);
$classProperties = $reflectionClass->getProperties();
$propertyInfos = [];
// Check which properties are marked by #Merge annotation and save information
foreach ($classProperties as $reflectionProperty) {
$annotation = $this->annotationReader->getPropertyAnnotation($reflectionProperty, Merge::class);
if ($annotation instanceof Merge) {
$reflectionProperty->setAccessible(true);
$propertyInfos[$reflectionProperty->getName()] = $reflectionProperty;
}
}
$this->mergingInfos[$class] = $propertyInfos;
}
return $this->mergingInfos[$class];
}
That's it. If new properties are added to an entity I have only to decide whether it should be merged or not and add the annotation if needed. No need to update the sync code.
Actually the code to handle this can be just a few lines. In background Doctrine will issue a query to search for your entity if not already in memory, so you can do the same by doing the query yourself with result cache enabled, and then just use PropertyAccessor to map the data.
https://symfony.com/doc/current/components/property_access.html
See this gist for a POC https://gist.github.com/stevro/99060106bbe54d64d3fbcf9a61e6a273

JPA 2 Criteria Query Projection

Using a JPA2 Criteria Query we can project the final result on a DTO (for instance) like this:
query.select(builder.construct( ProductGridDTO.class,
root.get(Productos_.proId),
root.get(Productos_.proAlias),
root.get(Productos_.proNombre),
companies.get(Companias_.ciaNombre),
companies.get(Companias_.ciaId)));
However this method is dependant of the order of arguments in my DTO class, which is plain wrong. Using the old (now deprecated) hibernate criteria API we could use the projection list:
ProjectionList projectionList = Projections.projectionList();
projectionList.add(Projections.property("id"), "id");
projectionList.add(Projections.property("name"), "name");
Which is not dependent of the parameter order of the DTO.
Is it possible to use a similar strategy in JPA?
I don't think so, the argument order must match in JPA. This could be because, until Java 8, parameter names were only available if the bytecode includes debug information. My guess is that most JPA providers end up calling Constructor.newInstance() and here the argument must be ordered correctly.
I moved on to QueryDSL which provides a higher level abstraction and solves this projection issue. So a query to load an entity and return a DTO for said entity becomes:
public List<CompanyDTO> findByCompanyId(String companyId) {
JPAQuery<?> query = new JPAQuery<Void>(em);
QCompany company = QCompany.company;
return query.from(company)
.where(company.companyId.eq(companyId))
.select(Projections.constructor(CompanyDTO.class, company))
.fetch();
}
Where CompanyDTO contains a constructor for the company Entity.

getViewData vs. getNormData and DataTransformers - different results depending on context

I have a form with several fields.
Then I bound DataTransformer to the one of them. Transformer works correctly if I get data getViewData and getNormData.
But if I issue these methods on whole form, data processed by transformers are completely ignored.
In Form data is being overwritten due to the:
$childrenIterator = new InheritDataAwareIterator($this->children);
$childrenIterator = new \RecursiveIteratorIterator($childrenIterator);
$this->config->getDataMapper()->mapFormsToData($childrenIterator, $viewData);
My mapper is PropertyPathMapper. Unfortunately, it bases on getData method of each child.
Is it possible to bypass viewData overwrite / achieve correctly transformed data without writing a global transformer?
a workaround:
$data = array();
foreach ($form->all() as $k => $el) {
$data[$k] = $el->getData(); // or getNormData or getViewData
}
Unfortunately I have no solution.
The current behaviour is not what I expected. It should at least be mentioned in the symfony cookbook.
As you see I have the same problem. I wrote a test case to be sure that I do not misunderstand how the transformers work. I found your question later.
my test case:
https://github.com/SimonHeimberg/SymfonyFormForm_DataTransformer_Demo

Retrieve annotations and propreties

I have multiple #groups({"group1","group2"}) annotations in my entities , I want to know if there is a way to retrieve the groups in order to use them .
i.e : An array of all the groups mentionned in a certain entity
You must use the SPL library PHP for do this.
In particular the reflectionClass. It seems that has method for inspect all the DocBlock in class.
You can read this for more understand : ReflectionClass::getDoccomment
for have a little idea of implementation you can inplement this kind of code :
function getAnnotations($class)
{
$inspectedClass = new ReflectionClass($class);
$inspectedClassDoc = $inspectedClass->getDocComment();
}

Resources