Doctrine SQLFilter in Symfony6 - php

I tried to enable SQLFilter in request subscriber. I get EntityManagerInterface in KernelEvents::REQUEST and run filter enable simple as in this cast
https://symfonycasts.com/screencast/doctrine-queries/filters#enabling-a-filter-globally
It is not working in Symfony6 on priority set to 0 or >0 - filter is not enablend.
I check filter variable And entity manager is null on filter object - this is a problem.
When set priority to -10 it works fine, entity manager in filter object is set. Is it good solution?

Related

ApiPlatform/Symfony 6 - add searchfilter on custom getter (not in database)

I'm working on a project whit api platform 2.6 and symfony 6.
I have an Entity based on a table from my database.
My API exposes the fields of my table.
but I have a specific need to add information that does not exist in the database.
I create a custom getter, I integrated it into the normalization group, and it shows fine when responding from API.
But I would like to add a filter on this field.
ApiFilter(
SearchFilter::class, properties: [
'customInformation' => 'exact',
It does not work.
How to make Api-Platform take it into account as if it were a field of my database ?
Thank !
William
Duplicate of How to apply an ApiFilter(SearchFilter: class) to a dynamic getter of an entity? [doctrine-orm, api-platform, graphql]
ApiPlatform filters only work on properties for now.
You could probably accomplish that with a custom filter but i find it hard to imagine a case where you need to filter on data that does not exist.
There is probably a change on your conception that will do the job.
Update based on your comment :
I strongly advise you to change this
function getEmergency(): string
{
if ( $this->createdby == 'admin' and createdAt > days + 3) {
return 'urgent';
}
else
{
return 'not urgent';
}
}
To something like this
function isUrgent(): bool
{
return $this->createdby === 'admin' and createdAt > days + 3;
}
Much simplier method since there is only 2 case possible.
Then for your problem with api platform why dont you simply filter like this for example
&createdBy=admin&createadAt[before]=20220105
And inside your class
#[ApiFilter(SearchFilter::class, properties: ['createdBy' => 'exact'])]
#[ApiFilter(DateFilter::class, properties: ['createdAt'])]
class foo {}
Look at this Syntax on the link bellow to better understand date filter with api platform: ?property[<after|before|strictly_after|strictly_before>]=value
https://api-platform.com/docs/core/filters/#date-filter

Bad Performance when using a output DTO with doctrine entities with a set of relations

API Platform version(s) affected:
/srv/api # composer show | grep api-platform
api-platform/core v2.6.8 Build a fully-featured hypermedia or GraphQL API in minutes!
Description
To define the response of our API endpoints, we have used attributes on the generated Doctrine entity such as:
/**
* #ORM\Table(name = "products")
* #ORM\Entity(repositoryClass=ProductRepository::class)
*/
#[ApiResource(
collectionOperations: [
'get' => [
'path' => '/products',
],
],
itemOperations: [
'get' => [
'path' => '/products/{id}',
],
],
normalizationContext: [
'groups' => [
'product:read',
],
],
output: ProductOutput::class,
)]
class Product {
.... // properties and getters+setters
}
The Product entity has a 1:n relation to the Variant entity which is also a ApiResource with an different endpoint /variants. The Variant entity has several relations to other entities and some values of all entities are translatable with https://github.com/doctrine-extensions/DoctrineExtensions/blob/main/doc/translatable.md.
The performance was as expected => good enough.
Later on, it was required to "enrich" the response of /products and /variants with some data, which was not mapped in relations between Product <> additional-data | Variant <> additional-data, so we decided to use Outputs DTO with DataTransformers, as documented in the API-Platform docs.
The DataTransformer's method transform puts the data into the DTO by using therespective getters of the entities, e. g.:
$output = new ProductOutput();
$output->id = $object->getId();
$output->category = null !== $object->getCategory() ?
$this->iriConverter->getIriFromItem($object->getCategory()) :
'';
$output->identifierValue = $object->getIdentifierValue();
$output->manufacturer = $object->getManufacturer();
$output->variants = $object->getVariants();
The $object is a Product entity, in this case.
The DTO contains only public properties, such as
/**
* #var Collection<int, Variant>
*/
#[Groups(['product:read'])]
public Collection $variants;
and the Groups attributes, which are also defined in the normalizationContext of the ApiResource attribute in the Product entity above.
After that, we found the performance had drastically deteriorated: A request to the /products endpoint which "lists" 30 products with the related variants needs around 25 seconds.
After analyzing, we determined the following:
without DTO: Doctrine runs one single query with a lot of joins to retrieve all the related data from the database.
with DTO: Doctrine runs in sum 3.155 single queries to get the data.
by default, API-Platform uses Eager-Fetching (see https://api-platform.com/docs/core/performance/#force-eager), but it seems so that will be ignored if the getters of a entity are used in the DTO.
the serialization process needs the most time. That is maybe (also) a Symfony issue.
In a try to reduce the Doctrine queries, we created a DataProvider to fetch the related data. This actually worked, as using the DataProvider reduced the number of queries to +/- 50, but the serialization process also needed around 25s. So the cause of the performance problem does not seem to be the lazy-loading of doctrine, which is now done.
The question is: Why is using a DTO so much slower how would it be it possible to get performance back to an acceptable level?
I may be wrong, but I guess you are mistaking EAGER and LAZY loading in doctrine. As far as I understand, EAGER loaded associations will be fully loaded immediately, while LAZY loaded associations will only be fully loaded when the property is accessed via the property getter (what is actually happening in your transformer).
https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/working-with-objects.html#by-eager-loading
(There is also the EXTRA_LAZY option that won't even fully load related entities if you only access them with a couple of allowed methods)
From my opinion, as long as you are not serializing the related entities as nested documents (but as IRIs only), as far as I understand you shouldn't even need to fully load those entities. So LAZY (and don't accessing the related entities via getter) would be the way to go. This way we've been able to speed up entity loading a lot for entities which only needed to be serialized as an IRI (although we aren't using DTO and transformers).
It was not possible to improve performance when using a DTO for this data-structure. Instead of a DTO and data transformer we used a Doctrine Event Listener (postLoad) to set the value which is a unmapped property (Doctrine/Symfony: Entity with non-mapped property) now.

How to get doctrine2 entity one to many entities that are set "active"

Let's assume there is a OneToMany doctrine2 association between blogposts and comments. A blogposts might have many comments. Every comment remains inactive and therefore hidden in the frontend until a moderator will activate the comment manually.
I'm now trying to have some kind of security facade to ensure that only "active" comments will be provided to the view by accessing them in a loop over the {{blogpost.comments}} variable in the twig template.
Trying to use the getComments() method in the blogpost entity I was trying to filter the ArrayCollection of comments like so
/**
* #return ArrayCollection
*/
public function getComments()
{
return $this->comments->filter(function ($condition) {
return $condition->getActive() === true;
});
}
unfortunately Doctrine will entirely load every single comment even if the relations fetch mode is set to "EXTRA_LAZY". So that would influence the performance of an application in a way i'd like to avoid.
Is there any way to hide the inactive comments globally or do I have to take care of filtering them every time I'm accessing the blogpost.comments relations in the view?
You should use the matching method of your collection. If your collection is not loaded, it will add filters to the SQL query to only load what you need. If your collection is already loaded, it will filter the PHP array.
use Doctrine\Common\Collections\Criteria;
public function getComments()
{
return $this->comments->matching(
Criteria::create()->where(
Criteria::expr()->eq('active', true)
)
);
}
More informations here: http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/working-with-associations.html#filtering-collections
Regards

Apigility Doctrine filter all fetch

I'm on an ZF2 Apigility application with doctrine and the QueryBuilderFilter module.
Based on this example
I am trying to add a filter to all entities fetch that contain an IdDealer property.
For that I have attached a FetchListener to all fetch events :
DoctrineResourceEvent::EVENT_FETCH_PRE
DoctrineResourceEvent::EVENT_FETCH_ALL_PRE
And then I try to add a filter to the query if the entity is compatible with this filter.
public function __invoke(DoctrineResourceEvent $event)
{
if ($event->getName() == DoctrineResourceEvent::EVENT_FETCH_ALL_PRE)
$entity_class = $event->getEntity();
else //DoctrineResourceEvent::EVENT_FETCH_PRE
$entity_class = $event->getEntityClassName();
/** Entity has idDealer so we filter for the user */
if (method_exists($entity_class, 'getIdDealer')) {
$em = $event->getObjectManager();
$filterManager = $this->getServiceManager()->get('Zf\Doctrine\QueryBuilder\Filter\ManagerOrm');
$filterManager->filter(
$em->createQueryBuilder()->select('row')->from($entity_class, 'row'),
$em->getMetadataFactory()->getMetadataFor($entity_class),
[
'filter' => [
'type' => 'eq',
'field' => 'idDealer',
'value' => (int)$this->getUser()->getIdDealer()->getId(),
]
]
);
}
}
But I cannot get the filter to work.
I have found some info on internet that says that I should be able to retrieve the QueryBuilder from the event but it doesn't seem to be the case anymore.
Anyone with an idea on how to get this working ?
I have just spent a good few hours trying to do something very similar.
The zf-doctrine-querybuilder guide suggests you can use Apigility Doctrine Events to filter querys:
https://github.com/zfcampus/zf-doctrine-querybuilder#use-with-apigility-doctrine
and links to an example:
https://github.com/zfcampus/zf-doctrine-querybuilder/blob/master/docs/apigility.example.php
However I believe this suggestion and example code is now out of date due to the change "Remove QueryBuilder from DoctrineResourceEvents; add ObjectManager #182 ":
https://github.com/zfcampus/zf-apigility-doctrine/pull/182
I believe the way they want us to do this going forward is via custom Query Providers. See the following documentation that gives an example of how to bind a custom query provider to a specific entity:
https://github.com/zfcampus/zf-apigility-doctrine#query-providers
Query Providers are available for all find operations. The find query
provider is used to fetch an entity before it is acted upon for all
DoctrineResource methods except create.
A query provider returns a QueryBuilder object. By using a custom
query provider you may inject conditions specific to the resource or
user without modifying the resource. For instance, you may add a
$queryBuilder->andWhere('user = ' . $event->getIdentity()); in your
query provider before returning the QueryBuilder created therein.
If you want to look at an example Query Provider listener see ZF\Doctrine\QueryBuilder\Query\Provider\DefaultOrm:
https://github.com/zfcampus/zf-doctrine-querybuilder/blob/master/src/Query/Provider/DefaultOrm.php
For far I've not found any other solutions to getting a handle on the QueryBuilder being used as was previously possible when you could get it from the DoctrineResourceEvent object.
Hope this helps
Alex

Magento: Adding a custom EAV attribute that autopopulates with its default value on entity creation

Is there a way to add a custom EAV attribute that automatically gets set to its default value upon creation of a new entity?
I set eav_attribute.is_required to 1 and eav_attribute.default_value to 0 (the default value), but it's not setting the attribute automatically when I create a new object.
By the way, the EAV entity type is shipment. I'm working on an installation of 1.3.2.4, before sales data was stored in flat tables.
EDIT
Jonathan Day asked "how are you adding the attribute?"
In ModuleDir\sql\module_setup\mysql4-install-0.1.0.php, I have the following code:
$eav = new Mage_Eav_Model_Entity_Setup('sales_setup');
$eav->addAttribute('shipment', 'fieldname', array('type' => 'int'));
I also have the this code for later versions of Magento (after the sales entities went from EAV to flat tables):
$w = $this->_conn;
$table = $this->getTable('sales_flat_shipment');
$w->addColumn($table, 'fieldname', 'int');
$w->addKey($table, 'fieldname', 'fieldname', 'index');
Jonathan Day asked "Have you checked that the attribute is added to eav_attribute with the correct fields?"
Yes, it has been added to eav_attribute. And the attribute is settable and gettable.
If you look at Varien_Object::getData() (in /lib/Varien/Object.php) you can see the following line:
$default = null;
...and any instance of missing data returns $default. So the default value you set in your attribute is being ignored.
Since any undefined 'get' is handled by getData() it means a null will always be the default. It seems the only option is to override Sales_Model_Order_Shipment (or whichever entity model it is) and provide custom getters.
A simple example would be:
function getSample()
{
if (!$this->hasSample())
return 0;
return $this->getData('sample');
}

Categories