Updated: Comment #364
Suggested commit message:
Issue #1862202 by plach, Berdir, katbailey, ParisLiakos, alexpott, chx, sun, larowlan, Gábor Hojtsy, cosmicdreams, vijaycs85, YesCT, penyaskito, andypost, Albert Volkman, joelpitett: Objectify the language system.
Problem/Motivation
Since only the parts strictly needed to objectify lower-level subsystems were converted to OOP, the language system is currently an unsettling mixture of object-oriented and procedural code, best exemplified by the following snippet from the LanguageManager
:
public function getLanguage($type) {
if (isset($this->languages[$type])) {
return $this->languages[$type];
}
// @todo Objectify the language system so that we don't have to do this.
if (language_multilingual()) {
include_once DRUPAL_ROOT . '/core/includes/language.inc';
$this->languages[$type] = language_types_initialize($type, $this->request);
}
else {
$this->languages[$type] = language_default();
}
return $this->languages[$type];
}
This has several drawbacks, as the current code:
- is not autoloadable: additionally, since parts of it live in
language.inc
andlanguage.negotiation.inc
to avoid loading unnecessary code on monolingual sites, those need to be manually included wherever a language API function or constant is needed; - makes code hardly unit-testable, whenever the language system is involved;
- is highly inconsistent and hard to integrate with most of the other D8 subsystems;
introduces a circular dependency with the configuration system:we need configuration to know which language negotiation methods should be used to determine the values of the current language types;if interface translation is enabled,LocaleConfigSubscriber
will try to set overridden values for any loaded config object with data matching the current interface language.
Proposed resolution
Objectify the language system by moving most procedural code and constants to regular classes. Remove language.inc
, language.negotiation.inc
and the related explicit includes, together with the code living in bootstrap.inc
.
Details
- Define a basic core language manager to be used on language-unaware sites.
- Replace it with a fully-functional implementation provided by the Language module. This depends on the configuration system to retrieve the list of configured languages.
The circular dependency is fixed by initializing per-language config overrides only after instantiating the language manager (this will no longer be necessary after #2098119: Replace config context system with baked-in locale support and single-event based overrides). - Introduce a separate language negotiation service, provided by the Language module, which is provided to the language manager through setter injection. This can rely on the regular
config.factory
service as the latter does not need the language negotiation service to be instantiated. - Register the Language module request subscriber and path processor only on multilingual sites. This allows to avoid loading most of the language system code on monolingual sites, as the negotiator is instantiated and injected while setting the request onto the language manager.
- Convert language negotiation methods to plugins handled by the language negotiation service. Path processing and language switching code is moved on the related plugins, since the logic implemented by those is strictly tied to the language detection logic implemented by the plugin.
- Resolve a circular dependency with the string translation service through setter injection.
- Move the browser language detection logic to a reusable standalone component, which can then be used by the related language negotiation plugin and by the installer code, without introducing a dependency on the Language module.
Notes
- Procedural BC wrappers are still in place for the most used functions to limit the size of the patch.
- Some functions used mainly by the Language admin UI were not converted to methods yet to limit the size of the patch.
- The language entity CRUD API has been left out to limit the patch size. Since these functions are rarely used (at least outside tests), we can afford to wait and introduce a dedicated language storage manager in a follow-up.
Remaining tasks
Agree on an approachWrite a patchFix test failures- Reviews (enough? As of Jan 7, reviews were done by @Berdir @larowlan @joelpitett @sun)
Create follow-up issuesDone Jan 3 2014- Decide: "Do we want to commit this and leave the rest to follow-up or quickly discuss the remaining non-controversial bullets?" (See #353)
- (Maybe) another round of follow-up issues need to be made
User interface changes
None
API changes
hook_language_negotiation_info()
and the related procedural callbacks are converted to plugins.- The
language_count
state entry was removed, as getting the language list from config and counting them has the same performance of getting the language count from state. This allows to remove all the synchronization code. - Most functions in the language system are moved to the Language manager or the Language negotiation services:
bootstrap.inc language()
LanguageManagerInterface::getCurrentLanguage()
language_default()
LanguageManagerInterface::getDefaultLanguage()
language_list()
LanguageManagerInterface::getLanguages()
language_load()
LanguageManagerInterface::getLanguage()
language_default_locked_languages()
LanguageManagerInterface::getDefaultLockedLanguages()
language_types_get_default()
LanguageManagerInterface::getLanguageTypes()
language_name()
LanguageManagerInterface::getLanguageName()
language_is_locked()
LanguageManagerInterface::isLanguageLocked()
language.inc language_negotiation_get_switch_links()
LanguageManagerInterface::getLanguageSwitchLinks()
language_types_info()
ConfigurableLanguageManagerInterface::getDefinedLanguageTypesInfo()
language_types_get_configurable()
ConfigurableLanguageManagerInterface::getLanguageTypes()
language_types_disable()
Removed, no actual use case for this. language_update_locked_weights()
ConfigurableLanguageManagerInterface::updateLockedLanguageWeights()
language_types_get_all()
ConfigurableLanguageManagerInterface::getDefinedLanguageTypes()
language_types_set()
LanguageNegotiatorInterface::updateConfiguration()
language_types_initialize()
LanguageNegotiatorInterface::initializeType()
language_negotiation_method_get_first()
LanguageNegotiatorInterface::getPrimaryNegotiationMethod()
language_negotiation_method_enabled()
LanguageNegotiatorInterface::isNegotiationMethodEnabled()
language_negotiation_purge()
LanguageNegotiatorInterface::purgeConfiguration()
language_negotiation_set()
LanguageNegotiatorInterface::saveConfiguration()
language_negotiation_info()
LanguageNegotiatorInterface::getNegotiationMethods()
language_negotiation_method_invoke()
LanguageNegotiator::negotiateLanguage()
(protected)language_url_split_prefix()
LanguageNegotiationUrl::processInbound()
language.negotiation.inc language_from_selected()
LanguageNegotiationSelected::getLangcode()
language_from_browser()
LanguageNegotiationBrowser::getLangcode()
language_from_user()
LanguageNegotiationUser::getLangcode()
language_from_user_admin()
LanguageNegotiationUserAdmin::getLangcode()
language_from_session()
LanguageNegotiationSession::getLangcode()
language_from_url()
LanguageNegotiationUrl::getLangcode()
language_url_fallback()
LanguageNegotiationUrlFallback::getLangcode()
language_switcher_session()
LanguageNegotiationSession::getLanguageSwitchLinks()
language_switcher_url()
LanguageNegotiationUrl::getLanguageSwitchLinks()
language_url_rewrite_session()
LanguageNegotiationSession::processOutbound()
Related
- #2098119: Replace config context system with baked-in locale support and single-event based overrides
- #2108599: Convert language_default to CMI
- #2102477: Convert remainder of language negotiation settings to configuration system
Follow ups
bugs Caused
architecture follow-ups
- #2174611: Reconsider language system class, method and constant names to make them more developer-friendly
- #2174615: Benchmark/profile PathProcessorLanguage
- #2174619: Make language negotiation plugins stateless
- #2166915: Remove uses of deprecated language functions in tests and procedural code
- #2225427: Remove remaining uses of deprecated language functions (mostly in object oriented code)
- #2166923: Move language entity (CR)UD API to a dedicated service
- #2239497: [Meta] Fix ConfigurableLanguageManager getLanguages()
DX follow-up
some coding-style issues
bugs Caused
architecture follow-ups
- #2174611: Reconsider language system class, method and constant names to make them more developer-friendly
- #2174615: Benchmark/profile PathProcessorLanguage
- #2174619: Make language negotiation plugins stateless
- #2166915: Remove uses of deprecated language functions in tests and procedural code
- #2225427: Remove remaining uses of deprecated language functions (mostly in object oriented code)
- #2166923: Move language entity (CR)UD API to a dedicated service
- #2239497: [Meta] Fix ConfigurableLanguageManager getLanguages()
DX follow-up
some coding-style issues
Comment | File | Size | Author |
---|---|---|---|
#372 | language-oo-1862202-372.patch | 366.39 KB | plach |
Comments
Comment #1
Anonymous (not verified) CreditAttribution: Anonymous commentedyes please. this is on my list, depending on how the config stuff goes.
Comment #2
catchClosely related is #1859110: Circular dependencies and hidden bugs with configuration overrides.
Comment #3
YesCT CreditAttribution: YesCT commentedd8mi tag?
Comment #4
Gábor HojtsyWell, it is probably unsettling because the request was converted to OOP without language being converted :) I agree having them using the same approach would be good, language was built out way earlier than request and OOP was not at all a universal approach back then. Looking forward to ideas as to how to best refactor this :)
BTW there is also #1512424: Add a LanguageInterface, and property setters/getters to Language class about the proper OOP-ifying of the Language objects themselves, where there is not really a great agreement as to how it should look like. So there is definitely a need for good ideas and feedback :)
Comment #5
plachThis was on my todo list (actually not in very high position :), as well as converting language detection methods to plugins.
I'd be totally happy to work on this but I have some more ugent tasks to complete right now. If someone wishes to step up I'll gladly review the patches, otherwise this will have to wait...
Comment #6
chx CreditAttribution: chx commentedComment #7
chx CreditAttribution: chx commentedTo recap: language, when on, might want to override every CMI object we load so we have a circular dependency where you need to read various CMI objects to determine the language. To avoid this, I am converting the language negotiation callbacks into plugins and then copy the language negotiation settings into the container on compile time. To do the latter, I will change DrupalKernel::buildContainer to add the configStorage as a synthetic service and then LanguageBundle can use that to read whatever CMI objects are necessary and store them neatly in a container parameter. Thus, when we have a kernel, we have the negotiation services and we have the negotiation settings so as soon as we have a request to extract things from it we have the language and can override. Hurray!
Comment #8
sunHm. #7 scares me a bit. We need to be able to change the language mid-request (language is context, not config).
Comment #9
plach@chx:
Let me recap your plan myself, just to be sure I understood it correctly :)
Problem
Atm we have a circular dependency between language and configuration:
LocaleConfigSubscriber
will try to set overridden values for any loaded config object with data matching the current interface language.Proposed solution
LocaleConfigSubscriber
to hook-in here too. Is this related to #1868028: Raw (original) config data is not accessible?@sun:
If the scenario above is what @chx has in mind I don't see particular problems to switch languages during a request: actually we would use language negotiation settings to determine language values for the topmost scope. Then in any point of the execution flow we can set whichever value for the languages we want, based on any required logic (e.g. user's preferred language) just before entering a new scope. Am I missing anything?
Comment #10
chx CreditAttribution: chx commentedEdit: I didn't see plach's comments only sun's.
I do not think there will be any functional changes. It's just moving things around -- and practically moving the CMI object caching for these objects into the container. No big deal IMO.Comment #11
chx CreditAttribution: chx commented#9 the bootstrap config reader is using the storage directly (by default, YAML files), there is no caching, there are no events firing, nothing. It's just bare metal reading. We can't have anything else because we do not have a container to read the config factory, event dispatcher, subscribers from. While slow this is fine because it only happens on container rebuild.
Comment #13
plach@chx:
Not sure whether this is blocked on my feedback, or @sun's, or you're just busy with something else, but +1 from me for the outlined plan :)
Comment #14
chx CreditAttribution: chx commentedI got entangled with metadata :(
Comment #15
YesCT CreditAttribution: YesCT commentedadding challenging tag for the focus board (+d8mi +challenging) to help new contributors jump in.
I'll update the issue summary: approach suggested.
Next steps, this is available for someone to pick up and make an initial patch.
Comment #15.0
YesCT CreditAttribution: YesCT commentedUpdated issue summary to make easier for people to jump in and join this issue, encorporate the clarification of the proposal, and use the issue summary template
Comment #16.0
YesCT CreditAttribution: YesCT commentedfixed html close list tag
Comment #17
chx CreditAttribution: chx commentedHere's a start. It's not working or anything but it's a start.
Comment #18
plachSome random observations:
This namespace does not really make sense to me, what about
Drupal\language\Negotiation
?Moreover, as we start converting stuff in bootstrap.inc and language.inc, we will have classes in
Drupal\Core\Language
referencing stuff inDrupal\language\Plugin
, hence I'd move at least the interfaces in theDrupal\Core\Language\Negotiation
namespace.I am not sure about the methode names here: while those are good as keys to identify the callbacks, I guess we should have something more self-documenting here, like:
Did you mean
NegotiationManager
here?We spent quite some time trying to standardize on a single terminology for this stuff, I think we should rename this to
NegotiationMethodBase
.Not sure about the current general approach for plugins, but it would be nice if we could inject only the configuration concerning this particular plugin. It could still live in a single language negotiation config object, but plugins here would get just their specific stuff. This would force better separation.
Currently the only reason we pass the
$languages
array to each negotiation method is avoid a call tolanguage_list()
for each one. We may want to move this to the constructor and inject it like the the other global-scope info.Are these supposed to throw
NegotiationNotImplementedException
?I'd remove this method and just access the language manager directly if needed.
Why lower case?
Not sure whether this would end up being too much verbose, but perhaps
BrowserNegotationMethod
(et al.) would be more self-documenting.Class/filename/PHPDoc mismatch here :)
I think I understaand why this is needed becuase I saw t() having troubles during an upgrade from a multilingual D7 installation to a D8 one, but would you please clarify what this is actually fixing?
Comment #19
andypostSuppose this issue duplicates #1754246: Languages should be configuration entities
The only question here is how to make entity system accessible at early boostrap and installer
Comment #20
Gábor Hojtsy@andypost: please take a look at the actual patch, not just the issue title :) Retitled to avoid future misunderstandings.
Comment #21
katbailey CreditAttribution: katbailey commentedI can't help thinking that our decision to make the language manager dependent on the request was misguided. It seems we are constantly tying ourselves in knots because of that. So I'm wondering if getting rid of this dependency and having an event subscriber that listens to the request and sets the request object on the language manager would be a sensible first step for this issue? Here's a first pass at doing this - I ran the language and path tests locally and got some fails but I think they'd be easy enough to work out, want to see what else explodes and find out what people think of this general idea? For starters it allows us to effectively get rid of the horrible language() function.
Comment #23
plachFrom the top of my head proposed code looks cleaner, but we need to check with @Rob Loach who was behind the initial conversion. I'm not really familiar with the new
language()
function.Comment #24
chx CreditAttribution: chx commentedComment #25
katbailey CreditAttribution: katbailey commentedFixed the installer so we should at least get a fail count this time...
@plach actually I wrote the LanguageManager class originally, along with the horrible language() function - that was all part of the bundles patch: #1599108: Allow modules to register services and subscriber services (events), and actually I just found a comment there from sun where he says
Ha! :-) Anyway at the time we had had this big long discussion in irc with one of the symfony guys and he did in fact mention the possibility of using a listener to set the request, but for one reason or another we decided a scoped service was the way to go. And now I am thinking maybe sun was right all along :-P
Comment #29
katbailey CreditAttribution: katbailey commentedI have no idea what's going wrong with that patch - try this one?
Comment #31
YesCT CreditAttribution: YesCT commentedFails likely not the problem of this patch/issue
It's a misbehaving testbot #1891598: 983 is misbehaving (failed to create checkout database)
#983
Comment #32
jthorson CreditAttribution: jthorson commentedGetting this in the testbot watchdog log:
So once the test completes, the bot attempts to send the results to qa.d.o. Something in the response (maybe an individual assertion, maybe in the log file, maybe just an attribute of the response ... to big?) causes qa.d.o to reject the report. As a result, the test fails, is sent back to the queue ... and assigned back to the testbot which just finished testing it since it's the first one in line waiting for a new test.
Also seeing this in the apache error log:
Comment #33
katbailey CreditAttribution: katbailey commentedAh, so as it turns out, it wasn't testbot, it was the patch. Basically we can't assume we'll always have a language_manager in the container because unit tests use a totally bare container with no services and then they call the t() function for assertions. Badness.
So, yeah - I guess there would have been a huge number of fails resulting from this. Sorry testbot! Thanks @jthorson for the info.
Comment #34
Crell CreditAttribution: Crell commentedThere are efforts underway (#500866: [META] remove t() from assert message) to remove t() from tests, for this exact reason. An actual unit test should definitely not have t() in it in the first place.
I know, doesn't help much now but will eventually. :-)
Comment #36
katbailey CreditAttribution: katbailey commentedTo clarify my plan of attack here - I'm going to try and get those tests passing locally and then move on to marrying this to the work chx started in #17.
One implication of not having the language manager be dependent on the request scope is that we can only set the request on it for the master request, not subrequests (as we'd have no way of setting it back after the subrequest was handled) - I think this should be totally fine unless someone can think of a scenario where a subrequest would have different implications for language negotiation than the master request.
Comment #37
Crell CreditAttribution: Crell commentedWhile I can think of hypothetical cases, I'm happy to move forward as-is and deal with them if/when they arise in practice. (I think they're situations we don't handle right now anyway.)
Comment #38
Gábor Hojtsy@katbailey: I feel like the problems you are trying to solve are very important but that kind of stumped on @chx's patch which went to convert negotiation methods to OO and plugins/annotations. I think we'll need that too eventually, it would be good if we don't loose that. We'll probably need yet another issue to open for that work now that this is taken to a whole different direction.
Comment #39
katbailey CreditAttribution: katbailey commented@Gabor I'm not quite following :-/ By stumped, did you mean I stomped on chx's patch? My intention was to back up a bit before any more work was done in that direction because I felt we'd be tying ourselves in knots if we continued with a language manager that's tied to the request scope.
This was the red flag for me.
As I mentioned in my previous comment, I was going to merge this in with chx's work (after fixing the 5 fails). If you feel the two issues need to be separated out then I can create a separate issue for the change I'm proposing and block this one on that..?
Comment #40
Gábor Hojtsy@katbailey: yeah I probably did not look at the patches close enough. I was afraid @chx's work would be lost. Maybe my reading of #21 was not clear then :) Thanks!
Comment #41
katbailey CreditAttribution: katbailey commentedAs it turns out, the work I started here to make the language manager independent of request scope is also needed in order to move forward with #1888424: Make Drupal's URL generation logic available to HttpKernel, and minimize code repetition/divergence, so I've decided to break it off into a separate issue: #1899994: Disentangle language initialization from path resolution
Comment #42
Gábor Hojtsy#1899994: Disentangle language initialization from path resolution is now committed, so should we go back to @chx's suggested changes?
Comment #43
katbailey CreditAttribution: katbailey commentedI hope I am not stomping again but I really want to make progress on this issue, and it was unclear to me how to make this work with plugins - specifically, how the plugins could be wired up to get services injected into them from the DIC. So here's an alternative approach that uses a compiler pass to register negotiators to the language manager, which then calls them directly, depending on the negotiation settings.
I've only run the tests in the Language group locally and am getting 15 fails in the UI Language Negotiation test, but that entire test would need to be rewritten (including getting rid of the LanguageTestManager that messes with $_SERVER['HTTP_HOST']) - in fact, I'm wondering if a lot of these couldn't be converted to unit tests that use a mock $request object...
Anyway, of course this breaks the installer, because... https://twitter.com/Crell/status/298871064795168768. So there's no point letting testbot at it until I figure out how to get around that.
But my main concern at the moment is that this might be over-engineered and "clunky" compared with the existing procedural code - language negotiation experts, please weigh in!
Comment #44
Crell CreditAttribution: Crell commentedTagging for myself, as Kat asked for general cleanliness feedback. And bumping for the language geeks. :-)
Comment #45
plach@Crell:
I'll try to review this ASAP. I'd be glad to see your feedback on #43 :)
Comment #46
andypostAlso we have #1919002: Upgrade to D8 broken when D7 has more then one language enabled that points that current tests are not enough
Comment #47
Crell CreditAttribution: Crell commentedCertainly this is more approachable than the old code. I actually sort of understand it. :-) I still defer to the language gurus to review the actual business logic.
Comments on general code flow, architecture, etc. below:
Nit: $method_id makes me think it's identifying the ID of a method, like a language method, not a language-deriving method. $negotiator_id?
That said, why isn't the negotiator ID a value retrieved from the object?
Also, needs docblock.
Since order apparently matters, we should include a priority flag in the DIC configuration like we do for other order-sensitive objects.
Wha? Isn't that what getNegotiationForType() should be doing? We shouldn't get back negotiators that don't apply. Refiltering that list shouldn't be our job here.
*cries*
This should be the other way around. Make this the real method, and the function should be a dumb wrapper for this object and method call.
Why is this constant not just on the LanguageManager?
And even if it has to stay here, no need to repeat the class name in the constant name.
Needs docblocks.
*cries* again.
See above regarding constants.
$request should be type-hinted.
Also, reading through the code of this method... *facepalm*. Dear god the web is messed up. :-)
Why is LANGUAGE_TYPE_INTERFACE still a global constant rather than a class constant?
Can we not inline this function, or turn it into a method?
Don't use $_SERVER. Use the $request object.
Strictly speaking this isn't an Override. It's an Implements. An interface has nothing to override, by definition.
Comment #48
plachI couldn't review #43, just wanted to mention that we have an issue to move all the constants in the language manager, although it seems some of them would be better fit into the
LanguageNegotiation
class: #1620010: Move LANGUAGE constants to the Language class.Not sure we want to merge it here since it has the potential to make this patch huge.
Comment #49
plach@katbailey:
I just reviewed #43, sorry for taking so long. TBH I don't like very much the proposed approach: registering every negotiation method into the DIC feels a bit cumbersome. Moreover it doesn't address (yet) the circular dependencies cited in the OP. I think @chx's proposal makes sense and we should start incorporating in the current work.
If you don't mind I'd like to try and merge #43 and #17, taking #9 and following into account. I should be able to work on this tomorrow.
Comment #50
Crell CreditAttribution: Crell commentedplach: Registering every negotiator in the DIC is a pattern we're using in a number of places. ParamConverters are doing that. Route Enhancers. Route Filters. Access Checkers, although they have an extra lazy-load layer involved. I don't think that's an issue.
Comment #51
plachMy understanding of #43 was that @kat went this way because she was not sure how to make negotiaton methods plugins. IMHO plugins would be way better for DX and they fit very well the concept of pluggable negotiation methods already implemented in D7 through procedural callbacks. Unless we have a strong reason (mainly performance, I guess) to go the other way, I'd really wish to try and use plugins as originally planned with @chx.
Did you guys have any look to #17? Aside from it being a raw proof-of-concept and thus missing lots of DI stuff, it looks promising to me.
Comment #52
katbailey CreditAttribution: katbailey commentedTo be clear, what I was unclear about was "how the plugins could be wired up to get services injected into them from the DIC" ;-) (from #43 above). I had looked at #17 before starting on this.
The issue dealing with injecting services into plugins is #1863816: Allow plugins to have services injected - looks like yched has put a lot of work into it so hopefully that will be resolved soon.
So I guess my fear of the approach being too clunky was well-founded - thanks for the feedback @plach, I look forward to seeing what you come up with!
Comment #53
plach@katbailey:
Sorry, my sentence was unfortunate, in no way I meant to diminish your knowledge on this matter. To clarify: my understanding was you were unsure about the best approach to make negotiation methods plugins ;)
Starting to work on this right now, I ain't sure I'll be able to post a new patch tonight, but I'll try.
Comment #54
plachI have a working branch implementing #43 + #7. Still cleaning-up stuff, tomorrow I should be able to post a patch.
Comment #55
plachThe attached patch merges #43 and #17 and should address most of Crell's review. I left out the terminology stuff since right now we have such a messy mix between the old terminology and the one introduced in this patch that this could use some discussion before addressing it.
I think we should probably complete the conversion of the configuration stuff to CMI in this patch to have a clean end result.
Aside from that what still needs work is making language types plugins, converting the rest of language.inc in OO code, not sure whether everything in there belongs to the language manager. AAMOF I was thinking that a we could provide a very minimal version of the language manager and use it in monolingual sites, Language module would swap in the full implementation when installed.
We also need to add an alter decorator to the discovery process, but this should happen only when performing it in a non-bootstrap context. Still thinking about which could be the best approach. Suggestions welcome :)
One last thing: I tried to remove the
initializing
flag but I found that parsing plugin definitions triggerst()
calls while bootstrapping a request with cold plugin discovery cache. For now I left it there but the current solution sounds a bit hackish. I'm wondering whether we can do better than that.Comment #58
aspilicious CreditAttribution: aspilicious commented*** Didn't read every comment ***
LanguageNegotiationUI::METHOD_ID
Shouldn't we use derivatives for this?
So just add "LanguageNegotiationUI" as the plugin ID. LanguageInterface as the class name. Reference a derivative for this plgin and we are done. (ater creating the derivative)
Comment #59
plachSorry, I don't understand your point. What would derivatives be useful for here? We don't need multiple variations of a language negotiation method.
Comment #60
aspilicious CreditAttribution: aspilicious commentedThan I don't know why there is "METHOD_ID" in the plugin id.
Just looks a bit strange...
Comment #61
plachIt's used to reference the negotiation method in external code without hardcoding the string id. Basically in some places we need to check whether the negotiation method is enabled in the language negotiations settings.
Comment #62
aspilicious CreditAttribution: aspilicious commentedhmm
Not sure but isn't cleaner to hardcode the string id. And make a helper to get the negotation method. This feels so not like everything else we have in core. :s
Or make it a property with a helper function or whatever.
:s
Comment #63
plachI really cannot understand your resistance to using a constant: that's what they exist for. I can understand the consistency argument, but the fact that [IMHO]somewhere else we are not doing things the right way[/IMHO] is not a good reason for doing them wrong once more.
Comment #64
plachAdding to the D8MI focus issues.
Comment #65
aspilicious CreditAttribution: aspilicious commentedNot really resistance, just giving my opinion.
Comment #66
vijaycs85Re-rolling...
Comment #67
tstoecklerI might be mistaken, but I think before the patch the code always set 'default' to TRUE and with the patch it's not always applied. If 'default' is set to FALSE in variable_get() then that will be used.
Doesn't this make it impossible for contrib to provide negotiation plugins?
Comment #68
plachThanks for reviewing!
You're right on the first one, although I don't think it would actually happen in real life.
I think that applies only to installer code. In the normal execution flow the regular namspeaces should be used.
Comment #69
tstoecklerD'uh, missed that little detail. :-) Thanks for the explanation.
Comment #70
jthorson CreditAttribution: jthorson commentedThe patch in #66 is cycling endlessly in the queue, re-testing without reporting ... due to the testbot tripping a "Failed to send result: Parse error. Request not well formed." error. I presume this may be due to the size of the results which are being generated, since it appears to be generating the following error in the apache error log at a fairly frequent pace:
As a result, I'm killing testing on the patch, and would ask that you do not re-test it.
Comment #72
vijaycs85Sorry about #70. Updating plugin location and re-rolling with current code base.
Comment #73
plachThanks, hope to get back to this as soon as I'm done with the ML node issue.
Comment #75
jthorson CreditAttribution: jthorson commented#72 was also cycling on the testbot, with 'Parse error. Request not well formed' responses. The Doctrine errors are gone; and the only thing that doesn't look familiar is a Plugin exception:
That doesn't mean that the above is coming from the patch ... it could also simply be something that is appearing on all D8 test runs; I just haven't seen it before today.
Comment #76
katbailey CreditAttribution: katbailey commentedNeeded a reroll after services moving to yaml - also the compiler pass was still being run, I wonder if that was the reason it made testbot choke? I've removed the compiler pass and just made minor changes to a couple of tests.
Comment #78
dawehnerAmazing work, here are some mostly nitpicks.
Should be probably Drupal::service('language_manager')
I guess it's now just the configuration as an array, not the service anymore...
Should have some documentation.
I know this comes directly from the previous code, though shouldn't variable_get('cache') be now config('system.performance')->get('cache.page.use_internal')?
Missing @return
Should the ModuleHandler() be injected?
Should the db connection be injected?
It's a bit odd to start with weight = 1, instead of weight = 0.
Missing "\"
Missing array()
Missing documentation.
I try to understand whether we need the constant on the class + the id. I can't find a place where the METHOD_ID is used elsewhere
> 80 chars :(
Should be Contains.
The docs needs some adaption.
It would be possible to just store the language.negotiation config object, as nothing else is needed on here, but that's out of scope of this issue.
Contains ...
Comment #79
vijaycs85Thanks for the review @dawehner. Except below items, others in #78 are fixed in this patch:
1. Should the db connection be injected?
2. I try to understand whether we need the constant on the class + the id. I can't find a place where the METHOD_ID is used elsewhere - Not sure this can be removed.
3. It would be possible to just store the language.negotiation config object, as nothing else is needed on here, but that's out of scope of this issue.
Additionally added missing method in LanguageTestLanguageNegotiation::setLanguageManager.
Comment #80
Crell CreditAttribution: Crell commentedYes, the DB connection should always be injected if possible. Although if this issue is now using plugins the "if possible" is kinda iffy still. :-(
The class names here should be "use"d and then just listed here by their short name.
Comment #81
vijaycs85Thanks for the review @Crell. Seems the patch doing some crazy stuff to testbot. Check the attachment.
Comment #83
chx CreditAttribution: chx commentedThis happens when there are so many errors that the XML-RPC request times out between the bot and qa.drupal.org so I cancelled this patch because it seemed never to finish. If tests pass locally, let me know and we will see what we can do.
Comment #84
YesCT CreditAttribution: YesCT commentedsomething similar with the tests for #1813762-50: Introduce unified interfaces, use dependency injection for interface translation
Comment #89
Albert Volkman CreditAttribution: Albert Volkman commentedRe-roll of #79.
Comment #91
ParisLiakos CreditAttribution: ParisLiakos commentedi will give this a shot during the weekend
Comment #92
plachThanks! We probably want to #2020249: Create Drupal::languageManager for improved DX before this one lands.
Comment #93
ParisLiakos CreditAttribution: ParisLiakos commentedhere is a straight reroll, so that i can interdiff
Comment #94
ParisLiakos CreditAttribution: ParisLiakos commentedhead is broken so i cant do much, i fixed obvious stuff, and what i could test locally, some language tests fail about half..
I fixed installation and moved plugin manager to use ContainerFactory.
I moved Plugins to Core/Language namespace, they shouldnt be in Core/Plugin
Also, why not have them in the language module? well we need for the installer one, but couldnt the rest be moved to language module?
Comment #95
ParisLiakos CreditAttribution: ParisLiakos commentedi think i should wait for #1903346: Establish a new DefaultPluginManager to encapsulate best practices
Comment #96
plach@ParisLiakos:
Thanks, I'll review the changes tonight.
Well, theoretically the language negotiation methods are part of the Language system, not the Language module, which only exposes a UI to configure them. Since we need the installer one in the Core namespace anyway, I thought it would be more consistent to place them together.
Comment #97
plachBtw, I was playing with the idea of defining a container-backed cache backend to cache the plugin definitions. The reason would be avoiding cache queries and in general any dependency on the DB.
Comment #99
plachChanges look good, only one thing:
I know this was already there, but we should inject the module handler, I guess. Or better we should find a way to avoid needing to know anything about the Language module, if possible :)
Comment #100
ParisLiakos CreditAttribution: ParisLiakos commentedyeah i would rather we do the second as well
back on track now that #1903346: Establish a new DefaultPluginManager to encapsulate best practices is in
Comment #101
plachParis is working on this, so back on sprint.
Comment #102
plach@Paris:
Rerolled #94 and pushed the changes.
I was working on this in https://drupal.org/sandbox/plach/1719670, branch
8.x-language-oo-1862202-plach
. I granted you access just in case you wished to keep working there.Comment #103
ParisLiakos CreditAttribution: ParisLiakos commentedoh, thats great, sandbox rocks! thanks! i am flying in a bit, will get back to this first thing in Dublin;) (and/or during the flight)
thanks for the reroll!
Comment #104
plachAwesome! See you there soon :)
Comment #105
BerdirIs there a reason this wasn't sent to testbot? It will get picked up the next time it's set to needs review anyway :)
Comment #107
andypostWe should postpone #1754246: Languages should be configuration entities and #1856976: Convert language_count to the state system. on this!
It much easy to attach config storage for language on top of this conversion
Comment #108
andypostOverall this is a awesome clean-up! But some tasks still badly implemented...
Most visible is the injection of services to language manager
- strong dependency moduleHandler - to check that language module enabled (no module handler - only default language?)
- storage (optional) to load languages or mock/fallback to get default list (maybe better implement separate managers for install and runtime?)
- config/state services (optional) to replace all variable_get|set()
Also
global $user
in some pluginsLanguage manager initialized with empty config
I'd suggest to pass here $active_config_storage and memory mock for install time
+1
this needs @todo url to replace
variables should be moved to config too.
you should use injected config storage to replace config()
I have trying to implement this in #1856976: Convert language_count to the state system.
both module handler and storage needs to be injected (optionally?)
This makes me use config_get_storage_names_with_prefix() in #1754246: Languages should be configuration entities - patch is green
t() needs injection too
is it possible to replace Global $user?
maybe there's some useful code for that in core\libs?
suppose better use accountInterface here, or session (still in progress?)
Maybe introduce languageManager()->setStorage() method?
Comment #109
plach@andypost:
I granted you access to the sandbox if you wish to help with this too. Paris is actively working on it right now.
Comment #110
ParisLiakos CreditAttribution: ParisLiakos commentedLets leave the $user global out of this. If things go well in #1890878: Add modular authentication system, including Http Basic; deprecate global $user plugins will be able to pull it out from the request.
This should also wait unless really necessary for #2018411: Figure out a nice DX when working with injected translation
I added @todo for variable_conversions
Removed a few test classes not being used
Splitted the module_exists check to another language manager provided by language module.
I cant move at all with tests..i just cant reproduce simpletest's failures, debugging html prints output twice ending with a twig error from UI...from cli debug html pages appear normally, but failures rise a lot..
Comment #111
andypostLet's see that tests, related to variables #1833022: Only display interface language detection options to customize more granularity
Comment #113
ParisLiakos CreditAttribution: ParisLiakos commentedi am probably just wasting time..maybe its easier for someone else
Comment #114
BerdirOk....
Spent some time on this and found and fixed two problems.
- We didn't reset the language list when adding a language, so when updating the prefix defaults, the new language wasn't there and it didn't get updated.
- We need to rebuild the container when adding a language and changing the default language. Alternatively, we need to do it when calling language_save(), not sure yet what makes more sense.
Additionally to that, I also have very crazy issues with the container being rebuilt or better, not being rebuilt in tests. I can't trigger the rebuild on a page request properly (it doesn't seem to rebuild) and when I do it from within the test method, it does not see the new configuration?!
Possibly more tomorrow, we'll see.
Comment #116
andypostAll over core
drupal_static_reset('language_list');
now does nothing, so trying to clean-up its usageSo all static reset should be replaced
hard solution!
Suppose better to implement special method a-la resetLanguages() in language manager
Comment #118
Gábor HojtsyAs per #1754246-145: Languages should be configuration entities, this is a cleanup required to make languages injectable (eg. for the installer) and also make the new Drupal 8 language API independent of module functions. Some of the workarounds required for #1754246: Languages should be configuration entities can/should be cleaned up here.
Comment #119
katbailey CreditAttribution: katbailey commentedHere's a reroll, possibly with more breakage - it was a gnarly reroll.
Comment #121
tim.plunkettTagging.
Comment #122
tim.plunkett#2039051: Convert hook_language_negotiation_info() to plugin system was marked as a duplicate of this, but it doesn't seem to actually do anything with that hook...
Comment #123
plachProbably this is not getting rid of
hook_language_negotiation_info()
yet but it already converts all the language negotiation methods to plugins. The patch is incomplete AFAIR.Comment #124
Gábor HojtsyTagging for massive API change. I'm not 100% sure this is maintainer approved. Ideally we'd have written record.
Comment #125
alexpottThis is issue a blocker for #1827038: Remove stale references to language_content_type variable - without this we can't resolve the circular dependency between language and config - and this will change the multilingual API.
Comment #126
catchIf it's critical then it's guaranteed to get in. The only exception would be something wrongly classified as critical, hopefully those don't stay that way too long.
Comment #127
Gábor Hojtsy@catch: well, that *someone* marks an issue critical does not make it critical for maintainers. I've been asked to validate any such issues that need API changes with maintainers to ensure there is consent involved. I also think its fine to keep the API change tag(?)
Comment #128
BerdirSecond #119... tough re-rolls...
Comment #130
BerdirGrml.
Comment #132
BerdirMissed one merge conflict.
Comment #134
BerdirThis should fix the installer, state needs to be optional.
Comment #136
BerdirForgot the language.module language manager.
Comment #138
BerdirI need to stop posting untested patches, sorry for the spam. This should be better.
Comment #140
BerdirFixing some unit tests and the user negotation plugin.
Comment #142
BerdirMost of those fails are I think related to a weird behavior with the php storage and simpletest.
Somehow, that handling isn't consistent:
Calling PhpStorageFactory::get('service_container')->deleteAll() inside a test deletes this directory:
DELETE: .../sites/default/files/simpletest/330378/php/service_container
But on the next request, when booting again, it loads *this* file, which is something completely else:
.../sites/default/files/php/service_container/service_container_prod_simpletest330378.php/8a5a978dc3eb937fc9c0d632e4a6bbb1a4606a4ccf2b78beebb7083d247ecbf0.php
So it is within the normal service_container folder but a different filename. WTF?
Comment #143
BerdirTurns out, we've fixed that already! The problem is that the file public path isn't overridden in time during a test request, so we sometimes use the parent directory, but we can't delete that later on or clean it up like all other test data.
#1856766-50: Convert file_public_path to the new settings system (make it not configurable via UI or YAML) fixes it, because it converts the file public path to settings and that overrides it in a test environment much more consistently. Tried one of the failing tests and it passes with that applied. Might even make most of DrupalKernel::getClassName() unnecessary.
This patch combines the two, but this is now blocked on that getting in.
Comment #145
BerdirOk, that didn't fix as many tests as I hoped, *but* it's now actually possible fix most of them. This should fix a lot, still a few that I don't understand. And still a ton of crazy procedural code, and the old hook is still around.. me confused a bit ;)
Comment #146
andypostdeprecated, $this->config !?
bad indent
using direct path in annotation is totally wrong
Comment #147
plach@Berdir:
Yep, the patch is still incomplete, I couldn't fix everything I would like to :(
@andypost:
Can you please explain why?
Comment #148
Berdir@andypost: The patch isn't ready for code reviews, I'm just trying to get it somehow working for most tests and then improve it.
@plach: Can we discuss your ideas/plans with this? Would like to know how far you wanted to go here, don't really understand why we still have hook_language_negotation_info(), I suppose you just didn't get to that yet, it's not that it's still supposed to exist?
Comment #150
plach@Berdir:
I'll be in IRC this evening after 21:00. Btw I've been working on this in http://drupalcode.org/sandbox/plach/1719670.git/tree/refs/heads/8.x-lang..., you should already have full access to the sandbox if you wish to go on there :)
Comment #151
BerdirSome clean-up, caching the negotiation plugins, adding a separate install-time language manger to avoid the conditional state stuff, I think we might also be able skip the negotiation stuff there and only support the default language. Updated the plugins a bit and replaced the unused injected database with the configuration storage. Maybe we don't want to use the config factory at all there and directly load from storage, not sure.
The config factory can not be injected into the language manager as there is a circular dependency due to the locale stuff.
Let's see what I broke...
Comment #153
BerdirOk, that's why language_default() doesn't use the language manager...
Comment #155
plachI had a look to the current patch and I think most of the legacy procedural code can be moved onto the language manager, more details below.
I noticed the latest iterations introduced a Language Manager provided by the Language module. This fits perfectly one improvement I was planning, not sure whether I mentioned it, that is providing a basic core Language Manager and a complete one provided by the Language module, which would swap it in when more than one language is enabled. This approach would have the advantage of not requiring to load most of the code currently in language.inc on monolingual sites. AAMOF the basic LM would assume a monolingual scenario and would provide only very simple versions of the exposed methods (an interface would be nice here :). OTOH the full LM would provide the full method implementations including the language negotiation stuff. This would more or less match the current situation where we load
language.inc
andlanguage.negotation.inc
only in multilingual environments.One thing that is not clear to me is that the Language module LM currently receives a
$config_storage
instance and a config array. Unless I am missing something, it would make more sense to either always use the config storage or to put the info additionally required in the config array. Having both is confusing.Anyway, this how I would proceed with the OO-fication:
bootstrap.inc
language_types_get_all()
-> Move to the LM.language_types_get_default()
-> Move to the LM.language_load()
-> Move to the LM.language.inc
language_negotiation_get_switch_links()
-> Move to the LM.language_fallback_get_candidates()
-> Skip this, it is handled in #2019055: Switch from field-level language fallback to entity-level language fallback.language_from_selected()
-> Already replaced by theLanguageNegotiationSelected
plugin, DIE!language_negotiation_info()
-> This must DIE along with the info hook and the related docs in language.api.php.language_types_info()
-> Language types should become plugins as well, I guess. If that happens this must DIE along with the info hook and the related docs in language.api.php. Otherwise this should somehow move to the LM.In any case we should retain the ability to alter plugin definitions, I'd simply keep the current alter hooks. This might be tricky as we will end-up invoking hooks very early in the bootstrap phase, however I've done a quick test and things seem to work properly.
language.negotiation.inc
What's left in here is mostly obsolete constants and the language switcher callbacks, which I guess should become plugins too, or maybe plugins could implement both the
LanguageNegotiationMethodInterface
and a newLanguageSwitcherInterface
. This might make sense as the implemented logics are usually tied.The session URL rewriter needs to become a path processor as
PathProcessorLanguage
(which should be renamed to something likePathProcessorLanguageURL
to matchPathProcessorLanguageSession
).The other functions/constants could move to the LM, I guess.
language.module
language_negotiation_include()
should DIE along with the related calls when we are done with the two files above. It would be nice to alter the negotiation plugin definitions to add the config paths (which should use the routing system patterns btw) from here, instead of hardcoding them in the core namespace where they wouldn't make sense. AAMOF the UI is provided the Language module.Sorry, if this sounds as a lot of stuff, but I wanted to be sure not to forget about all of this :)
Comment #156
Berdir@plach: Thanks, haven't read in detail yet but that makes a lot of sense to me :) Agree that we could move all the multilingual and and negotiation stuff completely to language module, that will make the install time language manager unnecessary.
file_public_path is in, will do a re-roll and start converting this stuff asap.
Comment #157
plachI was thinking about the conversion of language types to plugin: atm we have the
system.language.types
config object which will no longer make sense, as the info contained there would be provided by the plugin definitions. The primary reason behind the original variable was avoiding to perform a hook invocation during bootstrap, a sort of early cache. I think this role maps way more accurately to state, that is we could cache the language type definitions into state, which is available to the language manager.If we do the same with language negotiation methods, we should no longer need to inject the config storage/array to break the circular dependency. AAMOF, as it happens now, language negotiation and type info will be (re)built, altered and eventually stored in the state while configuring language settings in the UI. Then they will be always available during bootstrap.
Comment #158
BerdirComment #159
BerdirStarted moving more stuff, removed the negotiation info hook.
Comment #161
andypostSo language manager depends only on state service!
Suppose we need to rename the issue to somehow #2084567: Implement a EntityLockedInterface and service to allow locking entities
I think better to store default language by the same and list of locked languages (probably EntityStatableInterface)
Comment #162
BerdirAlways fun to re-roll this one...
Comment #164
BerdirFixed a few easy test fails.
Most test fails are content translation UI test fails, something is off there, not exactly sure what...
Comment #166
plachDidn't check why (too late :) but tests are failing because the
language_negotiation_language_content
variable has an empty values instead of holding thelanguage-interface
method.Comment #167
plachHere is a reroll
Comment #169
webflo CreditAttribution: webflo commentedI try to re-roll this one.
Comment #170
webflo CreditAttribution: webflo commentedSorry i have no idea whats wrong. But it looks like the Field UI to enable/disable Field translation is broken for fields with existing data. (Path: admin/structure/types/manage/article/fields/node.article.body/field)
Comment #171
andypostcontent translation is totally broken #1946462-52: Convert content_translation_translatable_form() to the new form interface and according @plach should be removed
Comment #172
ParisLiakos CreditAttribution: ParisLiakos commentedso lets fix the annotations after #2090353: Don't require to put the use statement into plugin classes went in..we need to put the full namespace there, which kinda sucks
Comment #174
ParisLiakos CreditAttribution: ParisLiakos commentedlets get rid of all those exceptions
Comment #176
BerdirRe-roll.
Comment #177
Tor Arne Thune CreditAttribution: Tor Arne Thune commentedTypo. Mentioning it so it's not forgotten.
Comment #179
BerdirI understand we're blocked on #2097585: Allow plugin managers to register namespaces to the annotation reader?
Comment #179.0
BerdirUpdated issue summary.
Comment #179.1
plachUpdated issue summary.
Comment #180
ParisLiakos CreditAttribution: ParisLiakos commentedNope, just putting the full namespace in the annotation still works so we can do that for now
Comment #181
BerdirRe-roll, fixed that constant for now and fixed the typo pointed above.
I'm not sure why we even need that METHOD_ID stuff. I don't think we do that anywhere else, why not just put the ID as a string in there. This means that to parse the annotation, we need to load the file in PHP, something that we're currently trying to avoid.
Comment #183
Gábor Hojtsy@Berdir: as for the method_id, we put it in the negotiation system on the negotiated language, so code later on can tell how Drupal arrived to the language in question. At least tell the method that picked the language at the end. Also, it is used in the tests to ensure proper behavior, so not only the right language is picked but with the right method :) Not sure if this helps at all :)
Comment #184
BerdirYes. So it's the plugin id. Why does it need to be a constant on the class that is then self-referenced in the plugin definition, that is my question :) Of the 37 (rough guess ;)) other plugin systems, nobody else does it like this.
Comment #185
Gábor HojtsyWell, its used on initializeType(), depending on how that is implemented, we may need it or not. The current code seems to store the methods keyed by ID. If we just get it as a plugin ID, then that is fine to use as-is as well IMHO.
Comment #186
BerdirAnother re-roll, and a hell of a debug run, that language negotiation stuff is so messed up :(
The problem turned out to be very simple, the URL didn't have the types set, which apparently have to be. Which is weird, because he lists all that core has be default, and only has to do that because during the test, content is not seen as configurable and therefore not part of the default. Then we somewhere in the middle dropped the method for the content negotation and ended up with the default en.
Also found a lot of other things, various dead drupal_static_reset() calls, the interface negotiation plugin was missing (shouldn't that be the default and not URL?
Anywy, this should hopefully fix the translation UI test fails.
And I also had very weird class already defined fatal errors for the test negotiation implementations when using the constant for the ID, so hardcoded it there for now.
Comment #188
BerdirRe-roll....
Comment #190
BerdirFixed two stupid mistakes.
Go testbot, go!
Comment #191
BerdirFixing some more tests. No clue about a few of those :(
Comment #193
andypostAny reason to build links here and use the globals?
Same here. The method will be orphaned from interface so maybe better to find another place for that (some manager at least)
Comment #194
ianthomas_ukI'm concerned #1775842: [meta] Convert all variables to state and/or config systems is blocked on this via #2108599: Convert language_default to CMI.
How close is this patch to being committed? Is it worth doing to variable > configuration step first? Is it worth splitting into multiple patches so we can get one committed and then do a variable > configuration step?
Comment #195
Berdir#194: The reason of this issue is to be able to do the variable to configuration conversion. Yes, it's a blocker, because this needs to happen first. This is too low level to convert to CMI directly.
#193: Those methods are needed, that's why I moved them there. They're part of the negotiation plugins and yes I'll need to find a better way to deal with them and make them nicer, I just moved them. But I just want to get the tests to pass first.
Fixing more tests. Also got confused, the interface negotation is called UI now. Cleaned that up again.
Comment #197
BerdirRe-roll.
Comment #199
BerdirAdded the alter hook and fixed cache clearing, replaced the crazy domain tests with unit tests.
Challenging the testbot gods: This will be green.*
* This is however by far not done.
Comment #200
plachWell, we have some business-logic that requires to be able to explictly tell which detection method was used to determine language, hence having a constant is way better for DX instead of using a plain string. OTOH is there's a good reason not to use it, and performance might be one, then we can also skip it.
Comment #206
BerdirThis had to happen.
Comment #208
BerdirTime to go to bed...
Comment #209
jibranYay!!! it is green. I know it is WIP so discard anything which is temporary.
We should add @deprecated here.
Issue id should be nice.
typehint missing.
@inheritdoc
80 char.
should be multiline.
debuging :D
Comment #210
BerdirOk, starting to gradually clean this stuff up, I fear the patch will still get quite a bit bigger, we'll see.
What this does:
- Drop the old constants, replace with the new
- simplify the language_negotiation_$type variables
- Remove some probably unnecessary code. :)
- Some other cleanup
-
Comment #211
plachI was skimming through the interdiff and I noticed this typo.
Comment #213
BerdirFixing upgrade path and apparently that call in content_translation() is needed, seems wrong but so be it.
Comment #214
plachIf you are referring to this one, it should not happen when installing CT. Content language is defined by core and it should inherit interface language even when CT is disabled. This is how D7 works, btw.
Comment #215
Berdir@plach: Absolutely agree, that's what I expected to happen. But looks like at least in the test, something is making content language negotiation configurable and then it has none, because by the time language_install() runs, it is not. Will figure out why.
Comment #217
BerdirRe-roll and using ServiceProviderBase. Stay tuned for more changes ;)
Comment #219
cosmicdreams CreditAttribution: cosmicdreams commentedBerdir I see a lot of documentation on a few other code changes that are asking to be done. I can take a shot at fixing them when you give the all clear.
Comment #220
BerdirFixing those tests.
Done with this for the weekend, feel free to work on it.
Comment #221
cosmicdreams CreditAttribution: cosmicdreams commentedComment #222
jibranYay!! it is green. Here is little help :).
@deprecated missing.
@todo with issue id perhaps.
\Drupal\Core\Config\Config[] now.
@todo here when/how we are going to remove it with issue id.
Why not $this->config?
Why not $this->moduleHandler.
80 chars.
80 chars
Comment #223
cosmicdreams CreditAttribution: cosmicdreams commented1. Fixed
2. Don't know which issue id you're talking about
3. Fixed
4. Don't know which issue id you're talking about
5. Because then the config you inject via the contructor would have to know about all of the config within the system.
6. Because the object has no moduleHandler as a property
7. Fixed
8. Fixed
P.S. Having weird problems with interdiff. hopefully this works
Comment #224
ianthomas_ukIssues are #2108599: Convert language_default to CMI and #2102477: Convert remainder of language negotiation settings to configuration system
Comment #225
Berdir3. No, $config is a raw php array.
5. The config system needs the language manager, we can't inject config because that would result in a circular dependency.
Comment #226
cosmicdreams CreditAttribution: cosmicdreams commentedBecause of #225,
Reverted type for $config so that it's an array again.
Because of #224,
added more specific issues to the todo's of methods in LanguageManager that include a variable_get/set
P.S.: interdiffs are still giving me issues, I don't have a good fix so my interdiffs might be wrong from now on.
Comment #226.0
cosmicdreams CreditAttribution: cosmicdreams commentedUpdated issue summary.
Comment #227
YesCT CreditAttribution: YesCT commentedHere is the interdiff from 223 to 226.
needs a reroll though. I'll try that now. (following https://drupal.org/patch/reroll)
Comment #228
YesCT CreditAttribution: YesCT commentedthat file was modified by #1999388: Use Symfony Request for language module
checked if that code was moved anywhere in that issue and it did not look like it.
so thought I would just remove it, same as in our previous patch.
@berdir confirmed it was ok to kill it.
deleting.
#1999388: Use Symfony Request for language module modified the hunk this patch is removing. checked that change it did. I think it is still ok to remove the hunk. removing.
this one is:
head is setting the accept language differently and this patch removed the old way with this one...
since this is in a test, we can just create the request the way we want it.
and we dont want to be calling out to \Drupal->request() because we want to be able to test it.
Note: @berdir says a follow up can be to move this to a unit test since it is close to one anyway.
So taking out the lines from head and keeping the lines from the patch.
this one is
use's should have been under the @file. that can be a followup.... wait.
next chunk of conflict is a huge chunk being removed in this patch. diffed what it was before that was removed with what this is removing and the only diff was this patch also removed one of the use's that was after the @file.
so just removed the hunk. when I do, phpStorm tells me all the use's are unused. removing them all. even LanguageNegotiationSession which this patch was adding.
no need for a followup to move the uses.
attached a txt to show exactly how the conflicts were resolved.
removing needs reroll tag
unasigning from me
still needs review?
Comment #231
YesCT CreditAttribution: YesCT commentedin language.negotiation.inc
looking at the code before, and what this patch was trying to do, I ended up with keeping what was in head and just making the change to the one query_rewrite = line that it was trying to do.
adding back the uses to make that ok.
---
at the end of language_url_rewrite_session() there may not be a return if it goes in those last if's there. I'm not sure this issue needs to worry about that.
Comment #232
podarokComment #233
xjmBeta blocker as it blocks #2108599: Convert language_default to CMI which is a part of #1775842: [meta] Convert all variables to state and/or config systems.
Comment #234
Gábor HojtsyIn #2107427: Regression: Language names should display in their native names in the language switcher block it is evident we need a version of language_list() which returns the language names in their native form (vs. right now all localized to the request's language). The former is needed for things like the language switcher block. That issue so far extends language_list() with arguments and different caches. Any better suggestions? Sounds like there are things we could do there that would fit better with this refactor :) Either way, the OO version will need to take care of that too.
Comment #238
BerdirAnother re-roll.
Comment #242
plachWorking on this.
Comment #243
plachHopefully this should be green again. More work tomorrow.
Comment #245
plach...
Comment #247
plachMore test fixes.
Comment #248
penyaskitoAwesome!
Some thoughts while reviewing/bikeshedding/actual reviews above:
I was confused by this removal, but it's needed now that we use two different language managers in installation and on an installed system. That's why we have to register the service by code later on the patch.
We need follow-ups for removing uses of all the deprecated methods.
Bikeshedding: If we had to reroll this, I would suggest to rename this class to InstallationLanguageManager.
First occurence of Language::defaultValues really annoyed me, but later in review found that there is a ToDo for moving that to config and we can do that in a follow-up.
I'm not sure why we have two lists of languages ($languages, $languageList), I have to investigate that later.
Cool that we don't need to duplicate the strings, didn't know that annotations support that.
This issue is blocking some other issues in the D8MI, so I would say that we need the follow-ups and we can RTBC this one.
Comment #249
plachHere are some clean-ups. In Vienna I'll work on the plan outlined in #155.
Comment #250
plachTemporary patch. Let's see how many failures we have.
Comment #252
plachAnother interim patch, still needs work. Let's see how it behaves.
Comment #254
plachLet's try again.
Comment #256
plachReverted an uninteded change.
Comment #258
plachThis should fix the DI errors.
Comment #260
plachHopefully this should fix some more failures.
Comment #262
plachAnd more fixes.
Comment #264
plachHopefully this should be green again.
Aside from fixing a couple of bugs this removes from tests the assumption that the host and the test environments share the same language configuration.
Comment #266
plachLast fix?
Comment #268
plachAnd now even with patch attached!
Comment #270
plachAnother reroll
Comment #272
penyaskitoRerolled. Attached diff -u language-oo-1862202-270.patch language-oo-1862202-272.patch > interdiff-between-patches.txt
Comment #275
plachSorry, I am in the middle of a complete revamp to move most of the language negotiation functionality to the Language module and leave only the basic stuff in core/lib. I should have a new patch ready tonight.
Comment #276
plachAnd here it is, lots of stuff going on here so no interdiff. Basically most of the language manager now lives in the language module and only the basic stuff is still in the core namespace. One relevant thing is that now only the complete LM depends on config data. This patch also factors the language negotiation code out of the LM and moves it into a dedicated class, which is injected in the LM when needed. In that phase the full config factory is available to language negotiation plugins, whitout re-introducing the ciruclar dependency on the LM. Finally every reference to language.inc and language.negotiation.inc has been removed and the few leftovers of those files now live in bootstrap.inc (BC layer) and language.module (configurable language negotiation API).
I am afraid this will re-introduce quite some failure, but I think it's a big step ahead: wrt the plan outlined in #155 we should be missing only the "pluginification" of the language types and then we should be done.
Let's see what breaks.
Comment #278
plachSome test fixes, more to come.
Comment #280
plachRerolled after #1786490: Add caching to the state system. Since checking language count no longer is an expensive operation, I just removed the
language_count
state entry. Lost the interdiff during the merge, sorry.Comment #281
penyaskitoThere are some calls to language_update_count() in the patch. If we remove the state entry, we should remove those calls and probably the function itself. Not sure if it is in scope.
Comment #282
plachReverted some uninteded changes and removed some
language_count
removal leftovers.Comment #285
plachOops
Comment #287
plachAnd more fixes
Comment #289
plachFYI we have a green (again) patch in #2156425-16: [ignore] Test issue for "Objectify the language negotiation system". I will try to get the last things done tomorrow (mostly converting language types) and then we should be ready for review.
Meanwhile could a power-user delete the bot messages? We are approaching the 300 threshold...
Comment #290
plachOk, we should be ready for review. Posting here the patch from #2156425-37: [ignore] Test issue for "Objectify the language negotiation system" which should come back green as the only failure over there was a bot fluke.
I did some quick profiling and couldn't spot relevant stuff, as the differences between runs on the same branch by far outnumbered the differences between head/patch, both in a monolingual and in a multilingual environment. Here is the XHProf diff between head and patch on a (non-default language) front page with 25 nodes (session files attached):
I picked the most performant runs for both head and patch, but it would be good if someone else could do some more profiling to confirm my results. The query log showed no apparent differences in the query number/duration.
Edit: please, ignore the interdiff, it's totally meaningless here, sorry.
Comment #291
plachBetter title :)
Comment #292
BerdirNot a full review, but picking out some things that I noticed.
I think the fact that we can move all the dynamic stuff to language.module and strip down the core language manager to being so simple and hardcoded is really cool.
Also, the patch is too big for poor dreditor ;) And to review it in detail in a single go, but I think this is really getting ready and we need to move forward to be able to unblock the CMI stuff and just as important, unblock @plach to work on other issues again :)
No . after a @see
another one.
Missing @deprecated.
Wondering how many of those function we really need to keep? Because if we keep it, we'll have to fix coding style like the missing types on @param and @return.
There's a @todo in the default plugin manager about using the language manager for the language list, should be easy to get rid of that now.
Is it really ok to replace them like this? I know I had a test once where this didn't work.
another .
I don't think method references like this should have arguments.
If we use the injected language manager in the base class, then we can check for it's existence and don't need this override.
Is this still necessary (it being conditional I mean)
The complexity of this save/purge/update stuff is extremely complicated and caused all sorts of craziness when I was working on fixing tests with things getting lost in the middle. Not here, but is there a chance to simplify this afterwards?
I was wondering about this too, would be nice if we could make it lazy-initate the language types that we need, would avoid to load and execute all the negotation stuff for every language on every request.
We should be able to simplify those lines by using the interface now for the mock.
Comment #293
plachThanks! Addressed #292 except for:
6. I may be wrong, but the rationale is that we want to test a fixed behavior, that is language prefixes being there, thus we need to hardcode that in the test. This is more or less the same thing we do, for instance, when testing storage: we use the API to load/save stuff and directly access the DB to perform assertions. In this particular case, if I am not mistaken, using the API would mean relying on the host environment language configuration, which usually does not match the test site's one.
11. Totally. I get a headache every time I try to wrap my mind around that code (and I designed it).
Note: 12 is in the patch although I couldn't make it appear in the interdiff due to a previous merge. It should be ok, see #2156425-40: [ignore] Test issue for "Objectify the language negotiation system".
Comment #294
larowlancan we inject the string translation service here?
ah, looks like that's on the radar already :)
Any reason this can't be a static method on the object? Seems unfortunate that an issue to convert stuff to OO has to add a new global function :)
Same - should there be follow ups for moving these to a service?
ouch
this is nice cleanup!
nitpick: missing $container
This could be simplified to use Request->getHost(), which (unlike ::getHttpHost()) excludes the port.
can we really hardcode the port to 80 when the request is null? In what circumstances is this called without a request?
there's a lot going on here, if we don't have unit-tests for this code-path, we need a followup to add it.
fyi, working to remove _current_path in #2016629: Refactor bootstrap to better utilize the kernel
wrapping path_is_admin() in a method that could be mocked would enabled unit testing
oh yeah!
question: where is this moved to?
this looks like where it was moved to?
neat!
Comment #295
Berdir#293.6 Hardcoding it makes sense. I think the problem is that just by making it hardcoded, it will still run through the url altering, so if the current language would have a prefix configured, you'd end up with a double prefix, I think I had that once.
13: Not exactly what I meant and there are a few more like this:) Here's a patch that updates all language managers that I could find to use the interface, had to update a few classes to type hint on the interface for that.
Needs tests was added in reference to upgrade tests I think, which we no longer need.
@larowlan:
1. Not sure if we can inject the string translation service into the language manger.
3. Agree that adding language_rebuild_services() as a function feels wrong, not changed yet.
7. Fixed.
8. Nice, simplified.
9. Not sure what else we could do, I assume this is the same right now...
14/15 and others: Yes, I moved a few of the domain tests to a unit test because they were impossible to re-create in the old way. In general, I think the test coverage of those negotiation methods is quite good, we're just moving code around, so I'd suggest to do any further unit test additions/conversions in follow-ups.
What do we need to finish this? I can't RTBC myself,
Comment #296
plach@larowlan:
Thanks!
This should address #294.1-2 by using setter injection on the LM to provide the string translation service. This allowed also to move the last language-related function from bootstrap.inc to the LM, yay! This required some additional DI, at least where it wouldn't require to touch dozen of files.
#294.3 was also addressed. As a bonus the function body has been simplified as there were leftovers of a previous approach.
Additionally this moves also
language_update_locked_weights()
onto the LM.4. I think would be ok to move those to the respective classes (the related negotiation plugins) in a follow-up, we are already touching too much stuff here.
5. We should be able to get rid of that during the CMI conversion.
9. I'd avoid touching the business logic here, we are just moving stuff around.
12. Done :)
@berdir:
I think we should be done. I'd like to move also language CRUD functions on the configurable language manager, but to achieve that we'd need to fix another circular dependency between the language manager and the entity manager. I think it's ok to leave that for a follow-up since there are many tests that need to be updated.
I will ping @tha_sun to check whether he is available to review this until RTBC, unless @larowlan feels bold enough to, obviously :)
Comment #300
plach@Berdir:
I just noticed that the test session takes way longer here (~1h 45') than for the D8 head (~1h 10'): is there anything obvious we should look for?
Comment #301
BerdirTest runs differ a lot, those numbers don't mean anything, some bots are physical machines and twice as fast as the virtual ones and even the same servers often differ quite a bit.
Would be more meaningful to e.g. run the a large group of tests, specifically those related to this (Language/Locale tests?) and compare how long they take on a local, stable system.
Comment #302
penyaskitoLast run: Test run duration: 1 hour 11 min.
Comment #303
plachCool :)
On my machine the Language group actually took less time (7' 41'' vs 8' 09'') with the patch applied. Sorry for the false alarm.
Comment #304
plachRerolled after #2067079: Remove the Field Language API.
Comment #305
plachUpdated the issue summary
Comment #310
joelpittetThis patch is looking really good! Though dreditor is having a hissy fit with the size I noticed some minor things that could be picked off.
Sorry super nitpicky things to follow:
There are a few 80 char run overs:
(optional) Specifies the state of the languages that have to be returned.
An associative array of languages, keyed by the language code, ordered by
There are two spots that say:
@deprecated as of Drupal 8.0, use
which should be, this to be consistent.
@deprecated as of Drupal 8.0. Use
Comment #311
plachRerolled and fixed #310, thanks.
Comment #312
Gábor HojtsyReviewed the patch. It is very hard to do line by line obviously, and dreditor will not work with this patch anymore... The architectural changes explained in the issue summary and evident in the services setup are superb IMHO.
I only noticed "minor" problems like getDefaultLanguage(), getLanguageList() but loadLanguage(). (Also getLanguageList() could be getLanguages(), no?). Also the naming of core/lib/Drupal/Component/Utility/Browser.php may be too generic? It only provides language extraction from the list so far. (But the abstraction of that from the data providers is also nicely done).
All-in-all this patch seems to be full of win, and the naming stuff can be cleaned up afterwards as well.
While there are several @todos removed, there are also several added, and not all of them are qualified with URLs to issues. It would be important to open the followups desired and reflect them in the @todos. Other than that I have no complaints that would hold back this patch :)
Comment #313
plachThanks a lot for reviewing this! I'd wish to avoid further API changes after this is committed if we can, so any suggestion for new method names is welcome, but I'd prefer to implement it here.
About
getLanguages()
: in itself sounds perfect but I'm wondering whether it might clash withgetLanguage()
, which does a completely different thing. Maybe we should rename the latter togetCurrentLanguage()
? This wayloadLanguage()
would becomegetLanguage()
, which in this scenario would make perfectly sense.Yup, I was trying to be future-proof :)
Should we have any other browser-related utility function it would be better not to be too specific in class naming.
Comment #314
Gábor HojtsyWell, for educated suggestions on API changes (especially if you want this to be the final-final API) it would be good to collect a table of API changes. Either way, it would be good to have before/after code examples for the issue summary for some typical tasks. Those will be good to copy-paste into D8 docs later on, so not wasted time at all :)
Comment #316
plachThis should fix the @todos as per @Gabor's request. I left out the one in TestBase since we are only fixing a whitespace there and I have no idea whether there is an issue covering it.
Meanwhile the language negotiation settings UI was converted to OO code so we have a big interdiff.
Comment #317
plachUpdated the API changes section.
PS: I don't want this to be necessarily the final-final version, but I'd like to avoid avoidable API changes :)
Comment #318
plachRe-uploading #316 as the bot didn't pick it up.
Comment #319
plachFollow-ups created.
Comment #320
catchThanks for the updated issue summary. The main bit I'm not sure about is this:
Why can't we do the following?
1. Read the config with no language override at all, but using config.factory
2. Add the language context dynamically after this has happened (similar approach ought to be possible once the built-in language support/single override patch is in too).
However this feels like something we could open a follow-up for - even if that follow-up is another critical this patch unblocks lots of others.
Comment #321
plachI think this addresses #320 by introducing a new language manager provided by the Locale module. It extends the one provided by the Language module with a method performing registration of the locale config subscriber after the language manager is instantiated.
It also renames
LanguageNegotiationMethodInterface::getLanguage()
toLanguageNegotiationMethodInterface::getLangcode()
, since it returns a language code and not a language object.@Gabor:
After compiling the API changes list, I'd be definitely in favor of doing the following renames:
afterwards we should be done IMHO :)
Comment #323
plachThis should fix most failing tests. I still need to figure out what's going wrong with the last one.
Comment #325
Gábor HojtsyRe #320/#321: The direction in #2098119: Replace config context system with baked-in locale support and single-event based overrides is to move the config override entirely to language module, so the language manager in language module would handle it, no need for extending it and overwriting it from locale module AFAIS. (The reason for that is locale module is not needed to do these overrides given #2098119: Replace config context system with baked-in locale support and single-event based overrides would load and manage them on the config factory directly in itself). So if this separation is introduced here, it would need to be removed there again :)
Re the renames suggested in #320, sounds good :)
Also *huge* thanks for the issue summary update, looks clear now :)
Comment #326
plachGabor:
So, as a temporary measure, would it be ok to move the
initConfigSubscriber()
method onto the Language module language mananger?Comment #327
Gábor Hojtsy@plach: I think so yes.
Comment #328
catchFine with #326 as well (although I'd also be fine with #321 since both solves reading config from disk and we'll need to preserve not doing that).
Comment #329
plachOk, this should fix config overrides and performs the renames suggested in #320. Hopefully I didn't break anything :)
Comment #330
plachUpdated the issue summary.
Comment #331
plachComment #332
plachI forgot @Gabor suggested to rename also
LM::getLanguageList()
toLM::getLanguages()
.Comment #336
plachAnd this should fix the last failures.
Comment #337
plachComment #338
Gábor HojtsyBased on several deep reviews above from @Berdir (suggesting to RTBC already in December), @larowlan, @joelpitett and our discussion with the remaining questions with @catch and @plach being resolved to everyone's satisfaction, I think this is clearly ready to get in. This is also in agreement in config approach with #2098119: Replace config context system with baked-in locale support and single-event based overrides even though the first that is committed will make the other one need a reroll.
Comment #339
jessebeach CreditAttribution: jessebeach commentedI'm stubbing out the Change Record here: http://piratepad.net/c5nQEfPwZi
Please contribute details. This patch will need some substantial explanation.
Comment #340
jessebeach CreditAttribution: jessebeach commentedThis is my breakdown of the changes from the patch in #336. Does it make sense to include removed Classes/Services/Plugins/etc as well?
New Objects
\Drupal\Component\Uticodety\Browser
\Drupal\language\ConfigurableLanguageManager
\Drupal\Core\EventSubscriber\LanguageRequestSubscriber
\Drupal\language\EventSubscriber\LanguageRequestSubscriber
\Drupal\language\LanguageNegotiationMethodBase
\Drupal\language\LanguageNegotiationMethodManager
\Drupal\language\LanguageNegotiator
New Interfaces
\Drupal\Core\Language\LanguageManagerInterface
\Drupal\language\ConfigurableLanguageManagerInterface
\Drupal\language\LanguageNegotiationMethodInterface
\Drupal\language\LanguageNegotiatorInterface
\Drupal\language\LanguageSwitcherInterface
New Plugins
\Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationBrowser
\Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationSelected
\Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationSession
\Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUI
\Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl
\Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrlFallback
\Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl
\Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUserAdmin
New Services
\Drupal\language\LanguageServiceProvider
plugin.manager.language_negotiation_method
language_negotiator
Deprecated functions in bootstrap.inc
Comment #341
sunThanks a ton for working on this! Awesome job. Here comes a deeper technical review.
Before you read on, let me clarify a few thoughts upfront:
That's not meant to downplay the enormous effort that went into this patch. Thanks a lot to @plach and @Berdir for making this happen! :)
This should be moved into the
else
control structure path...However, quickly checking the surrounding code of install_begin_request() within that spot, there appears to be more stuff going on that lives in the wrong spot, so let's not do that here, but ensure to create a follow-up issue to clean up the language related stuff in install_begin_request() instead.
"Browser" is a term used in common speech, which is ambiguous without context.
The proper technical term is User Agent. Let's ensure to create a follow-up issue to rename Utility\Browser into Utility\UserAgent.
"default locked languages" requires one to understand what Drupal understands under that term. Wasn't there a formal expression for these linguistic/language codes à la "universal languages" or "non-linguistic languages" or similar?
Would be great to have a follow-up issue to find a better API/method name for this.
getLangcode() does not really encompass what this method is doing. It's rather a getBestMatchingLangcode() or similar.
Let's create a follow-up issue to find a better name for this method.
This addition is very concerning -- the bootstrap config storage used within DrupalKernel should not be exposed nor used anywhere else in the system, because it is entirely uncached.
If it is required for any reason for this patch here, then let's create a major/critical follow-up issue to resolve that dependency and remove this addition.
I wonder why we're accessing a public $id property directly everywhere, instead of using an id() method?
Any chance to clean that up in a follow-up issue?
Would it make sense to move the instantiation of $defaultLanguage into getDefaultLanguage(), so as to instantiate upon access only?
Would it make sense to move the $defaultLangcode property initialization into the initLanguageManager() method?
Renaming this method from loadLanguage() to getLanguage() does not really make sense to me. Neither the old nor the new one really makes sense. The method does not load or get a language. It loads translations for a language.
loadTranslations($langcode) or getTranslations($langcode) would be more accurate.
Happy to defer this rename to a follow-up issue though, unless you want to quickly do it here.
The current ConfigurableLanguageManagerInterface approach cannot really work in practice:
1. The interface is defined in/by Language module, but if Language module is not installed, then its namespace is not registered/available, hence the interface cannot be autoloaded.
2. If our intention is to globally declare a swappable API for configurable language managers - in other words - allowing for alternative implementations of the exact same concept and features in contrib, then we need to declare and supply that interface in Core.
3. However, I wonder whether there really is a use-case for swapping out Language module and/or ConfigurableLanguageManager in the first place? Did anyone ever attempt to do that? Why would you want to do that? If there is no use-case, then we do not really need a separate interface.
4. Perhaps more fundamentally, I wonder why any kind of other module has to be aware of a non-configurable vs. configurable implementation of LanguageManager? — Shouldn't the additional configurable concepts be transparent for the rest of the system? Why does it matter to e.g. Block module or any other module whether languages are configurable or not? → All they should care about is whether the site is multilingual or not. (?)
5. The Configurable…Interface should not define any methods that are outside of the scope of "Configurable"… — It appears that at least language type info has been mixed into that, which has nothing to do with languages being configurable or not. → Unless there is a very good reason for why the base/core LanguageManager cannot expose (hard-code?) a predefined list of language types & Co, then all of those methods should (1) either be moved into LanguageManagerInterface or (2) we need additional interfaces (in core).
The important parts are (1) separation of concerns through well-defined strategy interfaces and (2) ensuring that the implementation is properly swappable (and can be feature-detected, if necessary).
Slightly OT, but then again also not — in general, I'm very skeptical whether we really need to provide the level of abstractions of e.g. language types in Drupal core.
Almost no one needs these facilities, but yet, we're shipping with tons of complexity. Based on what I've heard, people much rather found use-cases for e.g. additional language types, which could have been core patches to extend a built-in and hard-coded list at any time (minor feature addition).
Removing all unnecessary pluggability would vastly simplify the language system in Drupal core, for the benefit of everyone.
My considerations are not only based on technical aspects, but also product/organizational community aspects: It is extraordinarily concerning that (literally) only a handful of core contributors are able to digest the (utterly complex) language subsystem in core in order to work on issues like this here.
So as a completely separate follow-up to this issue, I'd really appreciate to have a discussion about getting rid of as many pluggable language stuff as possible, so as to simplify the implementation provided by Drupal core, and if necessary (at all), to leave a more complex and more pluggable re-implementation for contrib.
Why is the entire data reset when setting a negotiator?
I think this should be removed, or if it is strictly required, then we should have a comment to explain why that is necessary.
I wonder whether we really need to repeat the term "Language" in 90% of all methods on classes that are named LanguageManager already?
That's like having Entity::getEntityId() and similar, which we don't have either.
I realize this remark is quite debatable, so I'd be more than happy to discuss a rename/shortening of these methods in a separate follow-up issue.
1. What is this method good for, or why would anyone want to disable language types?
2. Why is there no corresponding method to enable language types?
1. Hm, that's a lot of contextual state being set on a service, no?
2. A setContext() method with a bunch of arguments is a legacy pattern in my mind... Can't we split that into setUser() and setRequest()?
This looks like a lot of runtime overhead in the critical path?
Can we find a way to prepare this information ahead of time; e.g., in a new Container CompilerPass?
Happy to defer to a separate follow-up issue though, as this patch is large enough already.
That said, the dependent code in this class is manually probing whether $processors[$type] is set already, which could be inlined (once) into the initProcessors() method.
1. Why return FALSE instead of just NULL/nothing?
2. Why do we need so much state on these plugin class instances? Wouldn't it be much simpler to inject the contextual data into the method?
…and make the plugin use the (existing?) config factory on the language manager instead? — That said, why do these plugins need access to other configuration in the first place? Normally a plugin gets its configuration injected and that is self-contained; i.e., a plugin should not need any additional configuration besides its own?
It's not clear to me what a "selected language" is.
Given the implementation, this rather seems to be a "negotiation fallback to a statically configured language"?
Shouldn't the check whether the returned language of a(ny) negotiator plugin actually exists be rather part of the negotiation manager, instead of possibly duplicated one-off implementations like this?
I'd expect this method to just return its negotiation result. Whether that result is valid and can be used or not is a completely different question?
These lines of code are breaking the contract of the getLangcode() API method — instead of distilling information from given context, they are manipulating global state (and context).
The operation is conceptually wrong and misplaced, because this negotiator may not necessarily yield the final negotiation result to begin with.
Given that there is a use-case for such an operation, we need to enhance the negotiator plugin API to allow a negotiator to perform operations in the event that its negotiation result is actually used, through a new method; e.g., persist().
Here + elsewhere (please grep):
Bogus D6-style leading space in the added class name.
Given that these are plugins (for now), is there really a use-case for having the METHOD_ID constant, instead of using and relying on the plugin ID everywhere?
"config" is a weird annotation property name for declaring a URL?
Can we at least append "_path" there?
And why is that a system URL/path to begin with? :) Aren't we supposed to use "route" everywhere now?
How could it not be an instance of Language?
Did you mean !is_object() || !isset()?
Otherwise, passing a langcode string as $options['language'] won't be caught?
All of the constants in the language classes/interfaces (including STATE_…) are poorly named, because they (1) require one to understand deep internals and (2) do not map at all to their intended purpose/meaning.
E.g., these here appear to be defining the source for URL negotiation, but yet, they are named "CONFIG_…" instead of "SOURCE_…".
Let's create a follow-up issue to re-evaluate and rename all language system constants.
Merge conflict?
Comment #342
jessebeach CreditAttribution: jessebeach commentedOk, I pulled a succinct distillation from the issues summary for the Change Record and added some info from my analysis of the patch:
http://piratepad.net/c5nQEfPwZi
For the authors of the patch, please edit/reword as you see appropriate.
Comment #343
BerdirThanks for the detailed review @sun. I think some of these changes are easier to do before committing this instead of creating follow-up issues (that might be critical too if they result in API changes), maybe @plach has time to do an update tonight?
Thanks for starting to prepare the change notice. I think it would make sense to have at least two change notices here, one that targets users of the Language Manager and all the different API functions/methods. And the second to describe how language negotiation implementions have changed, by moving from a hook + callbacks to a plugin.
Comment #344
plachThanks Daniel, that sounds like a needs work to me :)
No time for updating this tonight (got to get up early tomorrow): I just wanted to point out that most of your points make total sense to me, but I'll provide a detailed answer + new patch tomorrow.
Maybe we should pause the work on change notices until it's clear what the final patch here will look like ;)
Comment #345
catchI just committed #2098119: Replace config context system with baked-in locale support and single-event based overrides which is going to mean a (non-trivial unfortunately) re-roll here. Anything else that's not also a difficult critical issue let's postpone on this though if it's going to conflict.
Comment #346
alexpottRerolled and addressed two of @sun's points - 5 and 7. I don't think it is necessary to use the kernel config storage - just plain all config.storage will do and that has a listAll() cache so it should make the isMultilingual quicker - but actually this should not really matter as this called during compiling the container so not critical path. But exposing kernel.config.storage as a service seems like it could cause trouble so lets not.
Comment #348
alexpottOkay we do need to use the bootstrap config storage! Also resolved a todo with respect to how language is set on the config factory.
Comment #349
plach@alexpott:
Thanks for the reroll, changes look good to me :)
@sun:
To be honest my goal for this issue was "just" modernizing the existing code to match the D8 architecture. There was agreement in the D8MI team that the language (negotiation) system was good enough for our current goals, and that we should focus our efforts in other areas needing more love. This is not to say that the current system is perfect, but we didn't see the need of spending too much energies in refactoring it.
I certainly was expecting some follow-up work, but honestly I did not foresee major changes after the initial commit: we are moving towards beta and, if I didn't miss something big, the current recommendation is to commit stuff that bring us closer to it, and not the opposite. I guess that adding more major/critical API changes to the queue after this is committed would not help with that. Additionally I need to get back to the entity stuff ASAP, so I don't think I'd be able to work and other big changes in this area. This is why I'd like to address your main concerns now, and leave "optional" ones to anyone having the time to work on them.
I'd be ok with this if it turns out we cannot find an agreement quickly.
About #10:
Are you sure about this? From my tests it just retuns FALSE if the interface does not exist.
The answer is obviously yes :)
AAMOF we have only three references to
ConfigurableLanguageManagerInterface
outside the Language module (excluding unit tests):BlockFormController
where it makes evident an architectural problem: the Block module exposes a (kind of) visilibity API, that other modules should implement. It should be responsibility of the Language module to alter the configuration form, provide its visibility conditions, and implement access control based on those.You are right: definable language types should be part of a different interface, something like
\Drupal\Core\Language\ExtensibleLanguageManager
.I understand the goal, although from my (very biased) POV I don't think the Language system is more complex than many other core subsystems. My personal feeling about this is that very few people actually care about knowing it :)
Anyway, already in D7 one of my design goals was allowing for the whole system to be swapped/replaced, hence, as long as we retain the capability to implement the same feature set in contrib, I have nothing against such a discussion. I probably won't have the time to work on that, though.
About the rest of your review:
getLanguage()
just returns a (valid) language object given its language code, there are no translations involved here.We could open a follow-up to explore this, but I'm not very optimistic about it.
LanguageNegotiationConfigured
?I am still working on addressing the rest of your review.
Comment #352
alexpottRerolled - since #2171015: Drupal 8 HEAD broken: installing Language module fails, after that cannot install any other module is partially responsible for needing it.
Plus this change means that language_test module had an undeclared dependency on Language module. But the test added by #2171015: Drupal 8 HEAD broken: installing Language module fails, after that cannot install any other module tests Language install using the language_test module therefore had to do the following:
Comment #353
plachThis should address the "easy" stuff of #341:
initLanguageManager()
updates the language manager status, while retrieving the default language from it seems unrelated to me. AAMOF we don't need the translation manager to retrieve the default language.Do we want to commit this and leave the rest to follow-up or quickly discuss the remaining non-controversial bullets?
Comment #356
Berdir@param type doesn't match and missing description. I guess $anguage is used in some of the subclasses?
Comment #357
plachThis should fix failures and #356.
Yup, in the session plugin.
Comment #358
webchickNote that #2161397: Update to Symfony 2.4.1 (which seemed innocuous enough) conflicted with this, so I rolled it back. However, it may be worth looking at the fixes in that patch to see if they make sense to pull in here.
Comment #360
Gábor HojtsyThe immediate resolutions of @sun's concerns look good.
I agree with @sun that this is important to get in sooner than later and that we can discuss details in followups, although as said above a few times, admittedly the D8 multilingual team has other loose ends we are working on (schemas, content entity stuff, etc). This blocks way too much other things, so it would be good to get it as soon as possible.
Comment #361
catchI'd personally like to see this in as soon as possible, and we can open clear follow-ups for anything remaining.
Comment #362
penyaskitoRTBC++
However, if it was rerolled, please fix the typo:
Looking forward to see this one land ASAP.
Comment #363
plachRerolled and fixed #362.
FWIW I am ok with committing this and opening follow-ups for the rest.
Comment #366
plachComment #368
sunThanks for taking my concerns into account and also for the elaborate replies.
I'm not sure whether I agree with @alexpott's changes in #348:
(1) They are hard-coding the BootstrapConfigFactory even more than it was before? — Given this use-case, perhaps it wasn't actually wrong to expose it as a service, and instead, (i) we need to sure that it is no longer registered/available in the compiled/regular Container and (ii) for clarity, it should be renamed to ContainerBuilderConfigFactory.
(2) They are further hard-coding a baked-in concept of a ConfigurableLanguageManager override vs. a non-configurable LanguageManager, whereas the config factory needs to get the current, request-specific language in all cases. Neither the fact that the site is multilingual nor the fact that languages are configurable plays any role.
However, let's re-evaluate and address that in a follow-up, if necessary.
Given the major changes to one of Drupal's most fundamental base systems here, I believe that's as if I'd (1) refactor the entire installer and (2) wouldn't expect any kind of major or possibly even critical follow-up issues. ;-)
Even the best patch authored by the best developers would have that consequence here. :) My main intention was to clarify (for core maintainers) that this change will require a good amount of follow-up work, both concerning already known issues as well as unexpected bugs.
In other words, I tried to underline the importance of getting this patch committed rather sooner than later, so we can move forward. :)
In my mind, renaming methods is a simple search&replace across core, so even if it does present an API change (on top of this here), ensuring well-named class methods could happily happen in a follow-up issue, IMO.
Let's have that follow-up issue to not only discuss those method names, but also, all of the other (weird) naming like the "selected language" (statically configured fallback language).
Likewise, #353 introduced some major inconsistencies between LanguageNegotiation vs. LanguageNegotiator namespaces and class names, both within plugins but also outside of plugins... The change to "negotiator" for plugins makes sense to me, but let's make sure that the whole shebang makes sense (in that follow-up) :-)
/me LOLs at UserAgent::getBestMatchingLangcode()! :-D — That was really meant as a stupid suggestion only ;) → One more name to re-evaluate :)
Let's create a follow-up issue for this. Paths/URLs definitely depend on the current (sub-)request [think ESI], so any kind of static cache can very easily not be a given and not reliable/performant anymore.
IMO, this aspect definitely needs a major follow-up issue, because the current implementation of language negotiator plugins massively diverges from any other plugin implementation in D8 that happens to rely on plugin-specific configuration.
We've invented very smart concepts in the meantime. By now, I'm absolutely sure that someone like @tim.plunkett will be able to refactor this plugin code in a few minutes to leverage them. :-)
Likewise, even with the additional changes, I still don't see why we need to maintain so much state on language negotiation plugin instances instead of injecting the current request + current user into the getLangcode() [negotiate()] methods. — Architecturally, I don't see why these language negotiation plugins shouldn't be able to operate in a "stand-alone" way; i.e., (almost) comparable to static classes/methods.
EDIT: Architecturally, I'd compare language negotiator plugins to text/input filter plugins → they may get additional context injected, but in essence, each of them ought to be able to operate without.
But again, while that's going to be a major Language Negotiation API change, let's move forward here and clean up in a separate follow-up issue.
And once more to ensure I'm coming across like a parrot:
The gist of this change is fine. Let's do this now. Separate follow-up issues, pretty please. :)
Comment #369
webchickThough I'm loathe to put more things on catch's plate, it really does seem like he's been the most involved here.
Comment #370
catchWas about to commit this, but it no longer applies.
Comment #371
plachI will reroll it in a few minutes
Comment #372
plachComment #373
plach@catch:
Suggested commit message:
Comment #374
catchCommitted/pushed to 8.x, thanks!
Comment #375
plachWhoo-hoo!
Thanks to everybody helping here :)
Comment #376
plachHere is the change notice based on the draft provided by @jessebeach (thanks!):
https://drupal.org/node/2174591
Comment #377
plach@sun:
Sure, what I was not expecting were major architectural changes like stripping out the whole plugin-based language negotiation system :)
We need a follow-up to discuss that, I guess.
I created these follow-ups:
Comment #378
Gábor HojtsyThe change notice looks good and comprehensive.
Comment #379
effulgentsia CreditAttribution: effulgentsia commentedGreat work, everyone!
Comment #381
znerol CreditAttribution: znerol commentedFollow ups for some coding-style issues:
Comment #382
YesCT CreditAttribution: YesCT commentedcaused #2240007-4: Regression: early installer is not in RTL after selecting RTL language
tried to categorize the relates/follow-ups in the issue summary.
note there is still a remaining task to check if all the follow-ups have been opened.
Comment #383
Patrick Bauer CreditAttribution: Patrick Bauer commentedThis is the issue which introduced an important line inside LanguageNegotiationURL::processOutbound() which may introduce a bug, described in https://www.drupal.org/node/2883450
Maybe someone still following this issue could confirm that its a bug?