New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Operator filter #83
Operator filter #83
Conversation
Hi @CheapHasz ! This looks nice ! My suggestions to improve implementation and extensibility :
Hoping i'm clear enough. Please let me know if you need help and/or more details ! Anyway thanks for this first implementation, this seems to be a good base ! |
Hi @alterphp ,
I didn't get what you mean by that
That part is only about replacing aliases ? wouldn't adding them to the
It might be a good idea indeed, I'll think about it.
This idea stems from the fact that multiple FormType might be used for GreaterThan (off the top of my head, Number, Date and DateTime). But we still could factor the logic in one place, and the transformer seemed a good place. It also allows an external to create a new GreaterThan*Type and keep all the logic by simply adding the transformer. Now if the logic is indeed refactored using an OperatorModel, that logic might indeed be obsolete . (if some points are easier to explain in French, fell free to pm me directly using it). |
Hi @CheapHasz ! No worries for not having time to work on it every day, we all have some work and fortunately time doing anything but coding ! While thinking about the feature, I'm not sure handling the operator into the form type is the best implementation. Maybe the good move is to apply operator as a modifier of the form type => Transformer seems matching our needs. What about a single ListFilterType that adds a form type (text, number, date) based on a Operator list :
Every list form filters (even Forget about the manager and compiler pass (the idea was to allow custom filter), this is over-engineered for now. What do you think ? I'm working on upgrade for EasyAdmin 2.0 at the time, I'll be able to help on operators after that. And I think those operators will be available for 2.x brnach only if there is an important BC break... |
Hi @alterphp . New implementation, not quite sure if it was what you had in mind, but it probably is better than what I did previously. To prevent BC breaks, I didn't refactor everything using the ListFilter, and instead use it more as an OperatorModel. Tell me what you think |
Thanks for this update, this seems simpler. I did not expect the property/field (when you have many filters on the same property), I'm not willing to delegate the responsibility to ListFilter objects. I'm thinking about dealing with it straight in list.form_filters config... |
I have changed the target branch so that I could merge and continue working on it on my side. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @CheapHasz, and thank you for restarting this PR ! I've mostly been working on new EasyAdmin last weeks but I'm willing to advance on your PR.
Do you need the support of EasyAdmin 1.x on the ListFilter feature ?
@@ -32,7 +32,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) | |||
->add('operator', HiddenType::class, [ | |||
'data' => self::$operators[$options['operator']] | |||
]); | |||
if ($options['property'] !== null) { | |||
if (isset($options['property'])) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If property
is not intended to be modified on client side, it's not required to pass it to the view form. WDYT about setting the value on ListFilter object data on POST_SET_DATA FormEvent ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with the logic, and think it could be applied to the operator
property
'required' => false | ||
]) | ||
->add('operator', HiddenType::class, [ | ||
'data' => self::$operators[$options['operator']] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's better to use static::$operators
for a inheritance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way, you use static
at line 56.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed
|
||
class ListFilterType extends AbstractType | ||
{ | ||
public static $operators = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$operators
do not need to hold ORM version here, only the keys. I think this is QueryBuilder job to translate equals
into =
and so on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use constant to define the string for operators like :
const OPERATOR_EQUALS = 'equals';
const OPERATOR_GT = 'gt';
const OPERATOR_GTE = 'gte';
const OPERATOR_LT = 'lt';
const OPERATOR_LTE = 'lte';
private static $operators = [
static::OPERATOR_EQUALS,
static::OPERATOR_GT,
static::OPERATOR_GTE,
static::OPERATOR_LT,
static::OPERATOR_LTE,
];
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm all for using constants.
The problem I see with delegating the translation to symbol in the QueryBuilder is that it couples the PostQueryBuilderSubscriber and ListFilterType, with no simple way of implementing any new operator, modifying existing ones.
Not that we need that, but my implementation allowed to handle all that logic in the FilterType
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a reason behind this willing : I developed a port of EasyAdmin for MongoDB documents, and EasyAdminExtension has a branch that is compatible with both entities and documents.
Operators like =
or >=
have no sense for Mongo query builder... That's why I prefer delegating DB query building to the builder.
I know this is more work to add filters, but decoupling the form from the DB operators makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok it makes sense
public function configureOptions(OptionsResolver $resolver) | ||
{ | ||
$resolver->setDefaults(array( | ||
'operator' => 'equals', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better not use a raw value here. Constant as suggested earlier ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed
break; | ||
} | ||
|
||
$filterDqlPart = $field.' ' .$value->getOperator() .' :'.$parameter; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, QueryBuilder is responsible of selecting the ORM operator matching data returned by ListFilter::getOperator
method
$property = $field; | ||
if ($value instanceof ListFilter && $value->getProperty()) { | ||
// if a property is specified in the ListFilter, it is on that property that we must filter | ||
$property = $queryBuilder->getRootAlias().'.' .$value->getProperty(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefixing with root alias is not only for ListFilter. It has to be done just after this if statement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
which is what happens at lign 128, no ?
Maybe my mistake is not doing that test before the $field
assignation though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right.
On line 132, prefixing by root alias must only be done when there is no dot in the property returned by $value->getProperty()
. This allows to filter on a relation property.
// Add root entity alias if none provided | ||
$field = false === \strpos($field, '.') ? $queryBuilder->getRootAlias().'.'.$field : $field; | ||
$property = $field; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer $ormField
instead of $property
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fine by me
Sorry, my mistake |
@CheapHasz I'm taking care of requested changes. I will open another PR to compare. |
Closing in favor of #92 |
Here is a first implementation to address #64.
Is there a place I should add type shortcuts ? Something like 'alterphp_gte_text'.
I used a TextType, but maybe a NumberType would be more useful.
I was also thinking of experimenting with DateTypes , which would be a obvious candidate for operator filters.