-
-
Notifications
You must be signed in to change notification settings - Fork 585
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
Implemented dynamic exclusion using symfony expression language #673
Conversation
@@ -170,6 +182,9 @@ public function fromArray(array $data, $type, DeserializationContext $context = | |||
|
|||
private function visit(VisitorInterface $visitor, Context $context, $data, $format, array $type = null) | |||
{ | |||
if (null !== $this->expressionEvaluator) { | |||
$context->addExclusionStrategy(new DynamicExclusionStrategy($this->expressionEvaluator)); | |||
} |
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.
not really proud on how this is implemented
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.
@schmittjoh any idea on how to do this in a better way?
@lsmith77 @schmittjoh @Ener-Getick @xabbuh @willdurand @stof Some of your opensource projects relies on the serializer, I would be very grateful if somebody of you can have a look at it and review it a bit? |
This and more is implemented in https://github.com/willdurand/Hateoas#exclusion |
@mvrhov I took inspiration from it :) |
@goetas I am sorry, but I am not really familiar with the JMS Serializer internals. So I am not of much help here. |
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 would be good to have an exception if someone uses the new capabilities, but does not have the exclusion strategy enabled to enforce it.
Alternatively, we could also think about automatically enabling the exclusion strategy in some way if it encounters an if
expression if it's not already active.
In general, I like the changes 👍
@@ -158,8 +159,15 @@ public function loadMetadataForClass(\ReflectionClass $class) | |||
$propertyMetadata->serializedName = $annot->name; | |||
} elseif ($annot instanceof Expose) { | |||
$isExpose = true; | |||
if (null !== $annot->if){ | |||
$propertyMetadata->excludeIf = "!" . $annot->if; |
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.
Probably need to wrap this in parentheses to support something like a && b
.
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.
done
|
||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage; | ||
|
||
class ExpressionEvaluator implements ExpressionEvaluatorInterface |
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.
Is there a need to support another evaluator? Even if there were one, we already have the strategy interface which could be implemented, no?
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.
done, removed the classes and interfaces, now the dynamic exclusion strategy (renamed in expression language exlusion strategy) uses directly the symfony expression language
491e752
to
0c30f52
Compare
@schmittjoh can't find any good strategy to throw some exception if the expression language is used for expose/exclude annotation, so i have added this documentation part 65311c4 enabling it automatically is also not possible because the expression language requires always some configuratuion |
@schmittjoh i consider this as ready for review |
Regarding the exception, I think you could add a flag to the class metadata (true/false) whether expressions are used, and then also have a similar flag on the Context whether the strategy is enabled. After loading metadata in the GraphNavigator, you can then compare the flags to see if everything is ok. It's a bit of a code smell to hard code something for a single exclusion strategy in those classes, but I think it's worth it in this case since exposing data that should not be exposed can pose a serious security issue, and it's also better for DX. What do you think? Another question, how does this behave with deserializing? I think it's currently only designed for serialization? |
@schmittjoh regarding the user experience, I definitely agree with you that is way better throwing an exception in the serialization process instead of just ignoring it. I have implemented it with 264f32a but from the code point of view im really not happy... What do you think... should we keep it hard-coded as it is now... putting the user experience first, and the the code cleanness ? |
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 hard-coding is ok for this.
What about deserialization, did you think about that?
public function hasExclusionStrategy($class) | ||
{ | ||
if (isset($this->hasExclusionStrategy[$class])) { | ||
return $class; |
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.
Should return $this->hasExclusionStrategy[$class]
here.
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.
my, bad, fixed
if ($strategy instanceof $class) { | ||
return $this->hasExclusionStrategy[$class] = true; | ||
} elseif ($strategy instanceof DisjunctExclusionStrategy) { | ||
$ref = new \ReflectionProperty($strategy, 'delegates'); |
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 add a getter to that class, maybe even a separate interface like DelegatingStrategyInterface
which other classes could also implement and which exposes a getDelegates
.
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 to add just the method... DelegatingStrategyInterface
looks an overkill to me
@@ -158,8 +158,17 @@ public function loadMetadataForClass(\ReflectionClass $class) | |||
$propertyMetadata->serializedName = $annot->name; | |||
} elseif ($annot instanceof Expose) { | |||
$isExpose = true; | |||
if (null !== $annot->if) { | |||
$propertyMetadata->excludeIf = "!(" . $annot->if . ")"; | |||
$classMetadata->usingExpression = true; |
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 move this to the addPropertyMetadata
method, then we do not have to check this in each driver.
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.
good tip, done
ok, then lets stay with it
It was not my usecase so i have just skipped it. Probably for consistency should be implemented something. My personal opinion is that rarely in deserialization you need to skip some field, so the possible scenario are really few |
@schmittjoh There is one disadvantage in my approach, when using the context factory to add the exclusion strategy... from now on So probably I have already prepared a PR for JMS Serializer Bundle and FOS Rest and will submit it soon. |
@schmittjoh can you give me your feedback on aa4c951 ? Now the creation if contextes is pretty important, and can't be done with a simple static method anymore... what do you think? |
…ude conditional parameter
I've been thinking whether we can change the factory to something that can take a context and populate it with default values where nothing has been explicitly given. I would like to avoid additional complexity for end users if possible. |
Do not have any really good idea on how to do it. One detail that should be considered is the amount of users using jms serializer via FOS rest bundle, and without FOS.
On the other side, really do not like the static method for creating new contextes. Does not play well with future dependencies (this is more my personal opinion and for the Developer Experience, i'm ready to sacrifice my point of view). |
We should probably remove exclusion strategies from the context altogether and rather move them to the serializer. The context was intended as a lightweight data object, but with more complex exclusion strategies it is becoming a lot heavier. With regard to static methods, we can actually deprecate them (they were only needed for older PHP versions). Now, it's just as easy to do |
The only solution i see is adding an extra "if" condition in the The approach should work. In that way, the "expression language" will become part of the graph navigator implementation. Is this an acceptable solution? |
@schmittjoh Implemented as you suggested.
Implementation:
This implementation does not require changes on the fos rest bundle :) |
was time to merge it :) |
@@ -38,6 +38,7 @@ | |||
"symfony/validator": "^2.2", | |||
"symfony/form": "~2.1", | |||
"symfony/filesystem": "^2.1", | |||
"symfony/expression-language": "^2.6|^3.0", |
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.
You add some dependency to composer.json
but not to composer.lock
, so I pull the latest master, run composer install
& then run ./vendor/bin/phpunit
:
Fatal error: Class 'Symfony\Component\ExpressionLanguage\ExpressionFunction' not found in .../tests/JMS/Serializer/Tests/Serializer/BaseSerializationTest.php on line 243
I think this should be fixed.
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.
The composer.lock
file is not committed to git. So you need to run composer update
instead.
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.
My bad, I've seen this file in my IDE and think that it's commited to git.
This PR allows to use dynamic expressions to decide if exclude or not a field during the serialization process.
Under the hood it uses http://symfony.com/doc/current/components/expression_language.html
the "gender" filed will be serialized if the expression
service('some.cool.service').isAllowed(object)
returns false or true depending if the annotation used is exclude or expose.Closes #540