Skip to content

Allow serializer context configuration #28927

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

Closed
wants to merge 1 commit into from

Conversation

soyuka
Copy link
Contributor

@soyuka soyuka commented Oct 19, 2018

Q A
Branch? master
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets n/a
License MIT
Doc PR todo if we go with it

This allow to configure the context for normalizer's on a high level:

serializer:
    default_context:
        enable_max_depth: true

This feature was asked in api-platform/core#1764 (comment) and I thought it could be in symfony instead. The messenger component has the same configuration.

@soyuka soyuka force-pushed the configure-serializer-context branch 2 times, most recently from 32c07e3 to 2d373bd Compare October 23, 2018 07:59
@soyuka
Copy link
Contributor Author

soyuka commented Oct 23, 2018

Updated to reflect the changes from #28709

@soyuka soyuka force-pushed the configure-serializer-context branch from 2d373bd to fc79c72 Compare October 23, 2018 08:21
@ro0NL
Copy link
Contributor

ro0NL commented Oct 31, 2018

also see #19456 btw :)

@soyuka
Copy link
Contributor Author

soyuka commented Nov 13, 2018

@dunglas updated, lmk if this isn't what you had in mind.

@soyuka soyuka force-pushed the configure-serializer-context branch 3 times, most recently from 606f504 to dbe12fe Compare November 13, 2018 16:45
@weaverryan
Copy link
Member

Ping! This is a pretty important feature, right? Because in 4.2, for example, there is no way to set up a circular reference handler globally, except by using the deprecated circular_reference_handler option (correct me if I'm wrong).

@soyuka it looks like a few changes were requested - could you make this?

Cheers!

@soyuka soyuka force-pushed the configure-serializer-context branch from dbe12fe to f872a36 Compare January 29, 2019 08:58
@soyuka
Copy link
Contributor Author

soyuka commented Jan 29, 2019

I rebased only for now.

I think the best practice would be to:

A) For core services, wire it up manually in the services XML file - don't use binding.
B) For user-implemented normalizers, I can see 2 options:

1. Adding some sort of `DefaultContextAwareNormalizerInterface` with a `setDefaultContext()` method (we check for the interface, then add this call)
   or

2. Wrap the normalizer context into a service (e.g. `serializer.default_context`) and document that people can DI this into their custom normalizers. We could even add an autowireable alias for this service so that simply type-hinting the service class will pass it to them.

@dunglas any opinion about this? I still have issues with the default_context argument because it's not declared in the services file (serializer.xml). I also this that having the context in a service might be a good idea.

@soyuka soyuka force-pushed the configure-serializer-context branch 5 times, most recently from 60c9bc8 to 1036834 Compare February 6, 2019 13:20
Copy link
Member

@dunglas dunglas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost done!

@@ -236,6 +236,7 @@
<xsd:complexType name="serializer">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="mapping" type="file_mapping" />
<xsd:element name="default_context" type="metadata" minOccurs="0" maxOccurs="1" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default-context, for consistency

$this->addDefaultContextParameterBinding($encoders, $container);
}

private function addDefaultContextParameterBinding($services, $container)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private function addDefaultContextParameterBinding($services, $container)
private function addDefaultContextParameter($services, $container)

Binding is confusing, let's remove it

@soyuka soyuka force-pushed the configure-serializer-context branch 2 times, most recently from fa568f1 to 8ae1e25 Compare February 6, 2019 13:41
Copy link
Member

@dunglas dunglas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

->useAttributeAsKey('name')
->defaultValue([])
->prototype('variable')->end()
->end()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't allow for things like the circular_reference_handler. I mean, you can set it, but there's no way to set this to a service, for example - none of the handler stuff will work. We need to check for the handler options explicitly in FrameworkExtension and convert them to References. In the docs, I guess we'll need to document these handlers as services that should use the __invoke() method.

Copy link
Member

@dunglas dunglas Nov 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe could be also do something more generic: check if a service corresponding to a given string exists, if it's the case, convert it to a reference. WDYT?

Copy link
Member

@weaverryan weaverryan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really important one - thanks for the work! But I don't think it's quite done yet - handlers aren't possible, so it's still not really possible to handle the circular reference in the serializer in the framework.

$definition->setArgument('$defaultContext', '%serializer.default_context%');
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love this level of magic. I'd prefer:

A) Just documenting the parameter and allowing people to wire it up manually
B) Creating a DefaultContext service that holds the config and passing that around. For the existing classes that are expecting an array, that's fine - we can pass the array to those classes, but make this service available.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer B) because it will work with projects providing extra context options such as API Platform, and doing it manually is tedious.

What we could do:

  • Introduce a new ContextInterface (that can be extended)
  • Allow all parameters that currently accept an array to also accept this new interface, and deprecacate passing an array
  • Use it both for the "default context" and the context you can pass to methods (it means that this class MUST be safely serializable)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other better option: create a context builder that returns an array. Then it will be consistent with the builder introduced in HttpClient.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussion about this in #30888

@fabpot
Copy link
Member

fabpot commented Mar 31, 2019

@soyuka Can you take into account the last comments from @weaverryan?

if(!$defaultContext instanceof ContextInterface && is_array($defaultContext)){
$defaultContext = new DefaultContext($defaultContext);
@trigger_error("you must pass an object of type ". ContextInterface::class .", the passage of an array is depreciated", E_USER_DEPRECATED);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@weaverryan @dunglas is this what you had in mind? We (me and @flug) are working actively on this, we changed only one normalizer for now and wanted to check the implementation with you before going forward.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it's not. As I've described in the other comments, I think we should keep an array or an iterable here. Using an interface will make the component hard to extend, and it will be a pain to migrate the current API Platform context options to this new mechanism. All other Symfony components use plain arrays for this kind of things, for consistency, and because it's easier to extend, I think we should keep the array but just provide an helper function to build this context instead.

@@ -32,8 +34,12 @@ class ConstraintViolationListNormalizer implements NormalizerInterface, Cacheabl
private $defaultContext;
private $nameConverter;

public function __construct($defaultContext = [], NameConverterInterface $nameConverter = null)
public function __construct(/** ContextInterface */ $defaultContext = null, NameConverterInterface $nameConverter = null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is null a good idea ? It does not seems to be allowed before.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be an iterable defaulting to an empty array IMHO

@soyuka soyuka force-pushed the configure-serializer-context branch from ee29ffa to 97e8829 Compare November 29, 2019 09:06
->useAttributeAsKey('name')
->defaultValue([])
->prototype('variable')->end()
->end()
Copy link
Member

@dunglas dunglas Nov 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe could be also do something more generic: check if a service corresponding to a given string exists, if it's the case, convert it to a reference. WDYT?

<framework:serializer enabled="true" enable-annotations="true" name-converter="serializer.name_converter.camel_case_to_snake_case" circular-reference-handler="my.circular.reference.handler" max-depth-handler="my.max.depth.handler" />
<framework:serializer enabled="true" enable-annotations="true" name-converter="serializer.name_converter.camel_case_to_snake_case" circular-reference-handler="my.circular.reference.handler" max-depth-handler="my.max.depth.handler">
<framework:default-context>
<framework:enable_max_depth>true</framework:enable_max_depth>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this key allow any "array-like" structure? Shouldn't the XSD be updated accordingly?

* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
interface ContextInterface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this interface? Cannot we use the builder pattern as done in HttpClient (the HttpOptions helper) instead? This new class should contain a method for every available options for autocompletion, as in HttpClient.

* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class DefaultContext implements ContextInterface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final, but IMHO we should get read of this class and use an array or an iterable instead.

@@ -32,8 +34,12 @@ class ConstraintViolationListNormalizer implements NormalizerInterface, Cacheabl
private $defaultContext;
private $nameConverter;

public function __construct($defaultContext = [], NameConverterInterface $nameConverter = null)
public function __construct(/** ContextInterface */ $defaultContext = null, NameConverterInterface $nameConverter = null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be an iterable defaulting to an empty array IMHO

if(!$defaultContext instanceof ContextInterface && is_array($defaultContext)){
$defaultContext = new DefaultContext($defaultContext);
@trigger_error("you must pass an object of type ". ContextInterface::class .", the passage of an array is depreciated", E_USER_DEPRECATED);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it's not. As I've described in the other comments, I think we should keep an array or an iterable here. Using an interface will make the component hard to extend, and it will be a pain to migrate the current API Platform context options to this new mechanism. All other Symfony components use plain arrays for this kind of things, for consistency, and because it's easier to extend, I think we should keep the array but just provide an helper function to build this context instead.

@soyuka
Copy link
Contributor Author

soyuka commented Nov 29, 2019

Okay I've give up the Context class, I could add some builder like the HttpOptions but, according to the different normalizers, we have different constants that don't necessarily have the same meaning. To do something that corresponds to

B) Creating a DefaultContext service that holds the config and passing that around. For the existing classes that are expecting an array, that's fine - we can pass the array to those classes, but make this service available.

We'd need to choose which of the options we would declare in this DefaultContext.

@soyuka soyuka force-pushed the configure-serializer-context branch from 97e8829 to b0a7d0e Compare March 5, 2020 09:22
@fabpot fabpot closed this Oct 7, 2020
@fabpot
Copy link
Member

fabpot commented Oct 7, 2020

We've just moved away from master as the main branch to use 5.x instead. Unfortunately, I cannot reopen the PR and change the target branch to 5.x. Can you open a new PR referencing this one to not loose the discussion? Thank you for your understanding and for your help.

fabpot added a commit that referenced this pull request Oct 27, 2021
… context configuration (soyuka)

This PR was merged into the 5.4 branch.

Discussion
----------

[FrameworkBundle][Serializer] Allow serializer default context configuration

| Q             | A
| ------------- | ---
| Branch?       | 5.x
| Bug fix?      | no
| New feature?  | yes <!-- don't forget to update src/**/CHANGELOG.md files -->
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | todo if we go with it <!-- required for new features -->

This allow to configure the `context` for normalizer's on a high level:

```yaml
serializer:
    default_context:
        enable_max_depth: true
```

This feature was asked in api-platform/core#1764 (comment) and I thought it could be in symfony instead. The messenger component has the same configuration.
This re-opens #28927 (for discussion). I'm still unsure about the discussion in #28927 (comment) and what solution I should implement. Please let me know if you come to an agreement.

Commits
-------

43d1ca5 Allow serializer default context configuration
symfony-splitter pushed a commit to symfony/serializer that referenced this pull request Oct 27, 2021
… context configuration (soyuka)

This PR was merged into the 5.4 branch.

Discussion
----------

[FrameworkBundle][Serializer] Allow serializer default context configuration

| Q             | A
| ------------- | ---
| Branch?       | 5.x
| Bug fix?      | no
| New feature?  | yes <!-- don't forget to update src/**/CHANGELOG.md files -->
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | todo if we go with it <!-- required for new features -->

This allow to configure the `context` for normalizer's on a high level:

```yaml
serializer:
    default_context:
        enable_max_depth: true
```

This feature was asked in api-platform/core#1764 (comment) and I thought it could be in symfony instead. The messenger component has the same configuration.
This re-opens symfony/symfony#28927 (for discussion). I'm still unsure about the discussion in symfony/symfony#28927 (comment) and what solution I should implement. Please let me know if you come to an agreement.

Commits
-------

43d1ca5f86 Allow serializer default context configuration
symfony-splitter pushed a commit to symfony/framework-bundle that referenced this pull request Oct 27, 2021
… context configuration (soyuka)

This PR was merged into the 5.4 branch.

Discussion
----------

[FrameworkBundle][Serializer] Allow serializer default context configuration

| Q             | A
| ------------- | ---
| Branch?       | 5.x
| Bug fix?      | no
| New feature?  | yes <!-- don't forget to update src/**/CHANGELOG.md files -->
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | todo if we go with it <!-- required for new features -->

This allow to configure the `context` for normalizer's on a high level:

```yaml
serializer:
    default_context:
        enable_max_depth: true
```

This feature was asked in api-platform/core#1764 (comment) and I thought it could be in symfony instead. The messenger component has the same configuration.
This re-opens symfony/symfony#28927 (for discussion). I'm still unsure about the discussion in symfony/symfony#28927 (comment) and what solution I should implement. Please let me know if you come to an agreement.

Commits
-------

43d1ca5f86 Allow serializer default context configuration
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants