Skip to content

[Contracts] Add Translation\TranslatorInterface + decouple symfony/validator from symfony/translation #28210

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

Merged
merged 1 commit into from
Sep 3, 2018

Conversation

nicolas-grekas
Copy link
Member

@nicolas-grekas nicolas-grekas commented Aug 16, 2018

Q A
Branch? master
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? yes
Tests pass? yes
Fixed tickets #15714, #18930
License MIT
Doc PR -

Let's decouple Validator from Translation component \o/!

TODO:

  • add TranslatorInterface, deprecate it from Translation
  • add TranslatorTrait, deprecating MessageSelector, Internal and PluralizationRules
  • deprecate ValidatorBuilderInterface(LegacyTranslatorInterface)
  • inject a new identity_translator into translator.formatter.default, deprecate translator.selector
  • copy tests in the Contracts namespace to ensure the TranslatorTrait behaves properly
  • figure out a way to keep throwing InvalidArgumentException from the component
  • update UPGRADING and CHANGELOG files
  • polish the deprecation layer (ensure all needed runtime deprecations are here)

Reviews welcome already.

Copy link
Member

@stof stof 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 removing an extension point, as your translator is now performing replacements of parameters instead of separating the choice of the pluralization variant from the formatting of parameters (which was the reason to create MessageFormatter and MessageSelector)

@@ -295,7 +300,7 @@ public function getLoaders()
/**
* {@inheritdoc}
*/
public function getValidator()
public function getValidator(/*TranslatorInterface $translator = null*/)
Copy link
Member

Choose a reason for hiding this comment

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

that looks wrong to me. This class is a builder, so passing one of the (optional) dependencies to the final building method makes this one very special compared to the other settings.

Copy link
Member Author

Choose a reason for hiding this comment

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

I get that. That's the best I could think of for now. Any better suggestion?

Copy link
Member

Choose a reason for hiding this comment

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

IMO, it should stay on the setter, like today

Copy link
Member Author

@nicolas-grekas nicolas-grekas Aug 16, 2018

Choose a reason for hiding this comment

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

I'd like to. How could we deal with BC then?

Copy link
Member

Choose a reason for hiding this comment

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

Well, too bad that we haven't made this builder a @final class but an interface with an extendable implementation instead. That does not make sense. ValidatorBuilder is not an extension point (the interface is useless as it is not injected anywhere, and would forbid adding any method).
So for now, it would require using a new method as we cannot remove the typehint. to accept both.

Copy link
Member Author

Choose a reason for hiding this comment

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

We can deprecate the interface and make the class final. That should do it.
In order to pass the type hint, we would need to make the new TranslatorInterface extend the old one when the component is found. Would that work for you?

Copy link
Member Author

@nicolas-grekas nicolas-grekas Aug 16, 2018

Choose a reason for hiding this comment

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

new TranslatorInterface extend the old one

done, and made ValidatorBuilder final + deprecated its interface

@stof
Copy link
Member

stof commented Aug 16, 2018

However, note that I'm not sure it is the right extension point anyway, as an implementation based on the ICU message format would not be able to split this (it would not have transChoice at all btw)

@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Aug 16, 2018

This is removing an extension point, as your translator is now performing replacements of parameters instead of separating the choice of the pluralization variant from the formatting of parameters (which was the reason to create MessageFormatter and MessageSelector)

MessageFormatter will remain as extensible as it is currently: you can pass it an identity translator of your own to resolve the plural rules.
You're correct about IdentityTranslator: it would make no sense to make its selector logic extensible when creating a custom identity translator is precisely the way to define a custom selector logic now.

Note that if we really want an interoperable interface, the parsing of choices needs to be in Contracts. That's why TranslatorTrait exists in the first place since its logic is far from being trivial to implement.

There is another extension point that this plans to remove: PluralizationRules::set(). Honestly, I don't think we need to make this extensible (and I don't know how to make it extensible if we really need to BTW. Could be figured out later IMHO, if someone comes with a use case.)

@stof
Copy link
Member

stof commented Aug 16, 2018

There is another extension point that this plans to remove: PluralizationRules::set(). Honestly, I don't think we need to make this extensible (and I don't know how to make it extensible if we really need to BTW. Could be figured out later IMHO, if someone comes with a use case.)

I think we're fine with deprecating this. If the rules for an existing locale are wrong, people should contribute a fix in Symfony instead of fixing it in their own project. And custom locales would be weird.

@nicolas-grekas nicolas-grekas force-pushed the contract-trans branch 3 times, most recently from 1f4367e to d1063a2 Compare August 16, 2018 20:36
@nicolas-grekas
Copy link
Member Author

TODO list completed and tests green. Ready for final review.

@@ -33,6 +32,7 @@
"symfony/expression-language": "~3.4|~4.0",
"symfony/cache": "~3.4|~4.0",
"symfony/property-access": "~3.4|~4.0",
"symfony/translation": "~3.4|~4.0",
Copy link
Member

Choose a reason for hiding this comment

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

This looks wrong to me. Older version of the component do not implement the new interface. And don't we also need a conflict rule for older component versions?

UPGRADE-4.2.md Outdated
Translation
-----------

* The `TranslationInterface` has been deprecated in favor of `Symfony\Contracts\Translation\TranslationInterface`
Copy link
Member

Choose a reason for hiding this comment

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

TranslatorInterface (same below)

@stof
Copy link
Member

stof commented Aug 17, 2018

should we actually use class_alias for the BC layer ? This makes it impossible to throw a deprecation warning for people using the old name (and so we cannot really remove it)

@nicolas-grekas
Copy link
Member Author

Older version of the component do not implement the new interface

It works because this uses class_alias(), but see below.

should we actually use class_alias for the BC layer ? This makes it impossible to throw a deprecation warning for people using the old name (and so we cannot really remove it)

Whatever the deprecation layer, we cannot throw a deprecation, because our own implementations would trigger it. We've already done like that before I think. The deprecation will be triggered by DebugClassLoader actually so the forward path is fine.

What worries me more is the class_alias in Contracts: we won't ever be able to remove it.
All the complexity resolves around finding a BC path to preserve the type-hint of ValidatorBuilder::setTranslator().
But in the end, I'm wondering if we shouldn't just break BC here: nobody reasonably implements this interface nor extends this class. Don't you think?

@stof
Copy link
Member

stof commented Aug 17, 2018

But in the end, I'm wondering if we shouldn't just break BC here: nobody reasonably implements this interface nor extends this class. Don't you think?

yeah, we might end up with this option (and deprecating the interface and marking the class as @final maybe too ?)

@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Aug 17, 2018

I found https://github.com/tomazahlin/symfony-core-bundle/blob/master/src/Ahlin/Bundle/CoreBundle/Tests/Mock/ValidatorBuilderMock.php and https://github.com/oro-subtree/EntityExtendBundle/blob/master/Validator/ValidatorBuilder.php that implement ValidatorBuilderInterface.
I think both could extend ValidatorBuilder instead.

Which means we should maybe not make the class final. Let's just deprecate the interface (it's really not a type we need) and do the BC break on the type hint. The listed use cases will need to extend ValidatorBuilder instead and remove the custom implementation for setTranslator at least. That would provide them with a way to keep compatibility with all versions of the Translation component. OK for you?

@nicolas-grekas nicolas-grekas force-pushed the contract-trans branch 2 times, most recently from 00ea207 to d1bea0d Compare August 17, 2018 12:00
@nicolas-grekas
Copy link
Member Author

It took me a while, but I finally figured out a way to move forward without breaking BC.
See LegacyTranslatorProxy and AddValidatorInitializersPass.

PR green.

{
$this->selector = $selector ?: new MessageSelector();
$this->translator = $translator instanceof MessageSelector ? new IdentityTranslator($translator) : $translator ?? new IdentityTranslator();
Copy link
Member

Choose a reason for hiding this comment

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

we should throw an exception if we receive something else than TranslatorInterface|MessageSelector|null, as we do in other places having such BC layer

Copy link
Member

Choose a reason for hiding this comment

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

btw, TypeError might be a good one (now that we are on PHP 7+), as that's the same one than thrown by the native typehint

Copy link
Member Author

Choose a reason for hiding this comment

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

done

*
* @throws InvalidArgumentExceptionInterface If the locale contains invalid characters
*/
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need transChoice in contracts? We already have message formatters and unmerged PR #27399 which allow get rid of transChoice.

Copy link
Member Author

@nicolas-grekas nicolas-grekas Aug 24, 2018

Choose a reason for hiding this comment

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

Good point. I think we should do it one step at a time: first merge this PR, then refactor #27399 on top, maybe removing the method if proved ok. We still have a few months to finish these.

@nicolas-grekas
Copy link
Member Author

thanks (just rebased to fix a minor conflict, ready)

namespace Symfony\Contracts\Translation;

/**
* Exception interface for all exceptions thrown by the component.
Copy link
Member

Choose a reason for hiding this comment

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

Looks wrong to me

Copy link
Member

Choose a reason for hiding this comment

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

But anyway, why do we need this interface in the first place?

Copy link
Member Author

Choose a reason for hiding this comment

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

I removed InvalidArgumentExceptionInterface and updated TranslationInterface with @throws \InvalidArgumentException instead.

@nicolas-grekas
Copy link
Member Author

Comments addressed thanks.

@nicolas-grekas nicolas-grekas force-pushed the contract-trans branch 3 times, most recently from 118669d to 2f94d74 Compare September 3, 2018 13:01
@@ -48,7 +49,7 @@ public function load(array $configs, ContainerBuilder $container)
$loader->load('console.xml');
}

if (!interface_exists('Symfony\Component\Translation\TranslatorInterface')) {
if (!interface_exists(TranslatorInterface::class)) {
Copy link
Member

Choose a reason for hiding this comment

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

this change is wrong. We still need to detect the translation component itself to keep the extension, not the contracts component (you could have it without having the translation component)

Copy link
Member Author

Choose a reason for hiding this comment

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

good catch, fixed thanks

@fabpot
Copy link
Member

fabpot commented Sep 3, 2018

Thank you @nicolas-grekas.

@fabpot fabpot merged commit 064e369 into symfony:master Sep 3, 2018
fabpot added a commit that referenced this pull request Sep 3, 2018
…uple symfony/validator from symfony/translation (nicolas-grekas)

This PR was merged into the 4.2-dev branch.

Discussion
----------

[Contracts] Add Translation\TranslatorInterface + decouple symfony/validator from symfony/translation

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | #15714, #18930
| License       | MIT
| Doc PR        | -

Let's decouple Validator from Translation component \o/!

TODO:
- [x] add `TranslatorInterface`, deprecate it from Translation
- [x] add `TranslatorTrait`, deprecating `MessageSelector`, `Internal` and `PluralizationRules`
- [x] deprecate `ValidatorBuilderInterface(LegacyTranslatorInterface)`
- [x] inject a new `identity_translator` into `translator.formatter.default`, deprecate `translator.selector`
- [x] copy tests in the Contracts namespace to ensure the `TranslatorTrait` behaves properly
- [x] figure out a way to keep throwing `InvalidArgumentException` from the component
- [x] update UPGRADING and CHANGELOG files
- [x] polish the deprecation layer (ensure all needed runtime deprecations are here)

Reviews welcome already.

Commits
-------

064e369 [Contracts] Add Translation\TranslatorInterface + decouple symfony/validator from symfony/translation
@nicolas-grekas nicolas-grekas deleted the contract-trans branch September 4, 2018 06:45
nicolas-grekas added a commit that referenced this pull request Sep 20, 2018
…nt (sroze)

This PR was merged into the 4.2-dev branch.

Discussion
----------

[Validator] Allow Validator without the translator component

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #28210
| License       | MIT
| Doc PR        | ø

Validator should be available without the Translator service. #28210 introduced a regression, it was not the case anymore:
```

  You have requested a non-existent service "translator".

```

This fixes it.

Commits
-------

2dc92d7 Allow validator without the translator
@nicolas-grekas nicolas-grekas modified the milestones: next, 4.2 Nov 1, 2018
This was referenced Nov 3, 2018
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.

7 participants