Problem/Motivation
In BooleanItem, we do t('On') and t('Off') when defining the default values for boolean items. The config schema checker has a problem with this. When it is enabled during a test, it fails on some config with TranslationWrapper objects in it, with the message "The configuration property settings.on_label.*string* doesn't exist.", while checking core.base_field_override.node.page.promote during installation of the standard profile.
Proposed resolution
We cast TranslationWrappers in config to string before saving the config objects.
We also turn the labels in the default field settings for booleans into a TranslationWrapper as this is what would happen anyway when #2557113: Make t() return a TranslationWrapper object to remove reliance on a static, unpredictable safe list lands and as we often load these without rendering. This way we also have implicit test coverage (besides the explicit test coverage included in this patch), as contrib modules also might be using t() in values that will end up in config such as field default settings.
Ideally the on_label/off_label should be translatable in schema but this is out of scope for this issue.
Remaining tasks
N/A
User interface changes
N/A
API changes
We now explicitly allow passing TranslationWrappers into config only to cast it to string upon saving. This as contrib may also use t() in config (like core used to in BooleanItem)
Beta phase evaluation
Part of a major issue.
| Comment | File | Size | Author |
|---|---|---|---|
| #43 | 2562155.43.patch | 5.84 KB | alexpott |
| #43 | 40-43-interdiff.txt | 1008 bytes | alexpott |
| #40 | 2562155.40.patch | 6.07 KB | alexpott |
| #38 | 2562155-38.patch | 4.88 KB | stefan.r |
| #38 | interdiff-27-38.txt | 4.91 KB | stefan.r |
Comments
Comment #2
stefan.r commentedDebug traces for reference:
Comment #3
stefan.r commentedAdding two fail patches to illustrate.
Comment #4
stefan.r commentedComment #5
joelpittetNice find debugging! Added that same pattern to StorableConfigBase which solve most if not all of the remaining failures.
Comment #6
joelpittetActually I think they should do the cast to string on the $value instead of in the loop on the nested value that way the string on a simple key:value will also be cast appropriately. Does that make sense?
Comment #12
stefan.r commented@joelpittet let's solve that remaining test fail then and rename the issue to reflect the expanded scope here?
Comment #13
joelpittetRename the issue seems fine to me. That last fail seems tricky. It's going into the symfony yaml::dump(). Trying to breakpoint the test
Comment #14
stefan.r commentedI doubt the problem is in FileStorage->write() and in the schema checker here, we are reading config from the database with a TranslationWrapper in on_label which seems wrong considering that:
So probably that should have been stored as a string instead. I think ModuleInstaller::install() is where we'll want to look, if we solve it there some of the other changes may not be needed.
Comment #15
dawehnerIMHO we should cast it to string before we pass it to config entries ... in order to keep the existing behaviour, IMHO.
Comment #16
stefan.r commented@dawehner yes, this was just for purposes of the test, but if we do #2557113: Make t() return a TranslationWrapper object to remove reliance on a static, unpredictable safe list this is what will happen in contrib modules using the same pattern. So I think we'll need to cater to this and cast these to string in castValue(), just to stick with historical behavior and have less of a BC break.
And then there's the fact that the on_label and off_labels are defined as untranslatable strings in the config schema, they should actually be translatable as well, but I think that would be out of scope for this issue...
Comment #17
stefan.r commentedSo here's why: #2432791: Skip Config::save schema validation of config data for trusted data
We do $entity->trustData()->save(); in ConfigInstaller::createConfiguration(), which means we skip the typeddata casting :/
Comment #18
stefan.r commentedComment #19
stefan.r commented#18 still had debug code
Comment #22
stefan.r commentedComment #23
stefan.r commentedThis has implicit test coverage through the TranslationWrapper change in BooleanItem, which I think we can actually keep considering the default options are only rendered on the options screen.
Comment #24
joelpittetI don't know what has_trusted_data but before it would never do the simple validation on trusted data and now it does because of the moved condition. Is this change needed?
Comment #25
joelpittetI like how the simplification has come along on this, it looks way better!
Comment #26
stefan.r commentedTrusted data was an optimization (see #2432791: Skip Config::save schema validation of config data for trusted data), we'll still need to do simple casting to string of TranslationWrappers even on trusted data. We could make a 3rd condition that only does that but if we look at what ->validateValue() actually does (in addition to casting to string it does simple recursion through the array and makes sure final values are not NULL or non-scalar), I'm thinking we might as well use that so as to not complicate the code unnecessarily.
Maybe that could use a code comment explaining this...
Comment #27
stefan.r commentedAdding a comment and a test
Comment #28
stefan.r commentedPosting interdiff, updating IS and tagging Performance as we now have to translate 2 less strings on some page loads.
Comment #29
stefan.r commentedComment #30
stefan.r commentedComment #31
fabianx commentedIt was exactly that what we wanted to optimize.
Would it not be enough to just use in the original else:
Comment #32
stefan.r commentedit's a bit of extra complexity but if it gets rid of the performance penalty let's do that instead! Care to roll a quick patch @Fabianx?
Comment #33
stefan.r commentedActually I am looking at this and I am struggling to understand how the current patch could possibly hurt performance? The string casting would need to happen anyway so all we're doing on top of that is a NULL and a scalar check:
Also #31 would not work as we still need to recursively walk through the array :/
Comment #34
lauriiiI can profile the different options if someone has an idea of a good use case where that could be tested.
Comment #35
stefan.r commentedDouble checked with @Fabianx about what the worry was, it may be unfounded as it's about things that are happening in ::castValue() only, which is still only called under the same conditions both before and after the patch (ie when
!$has_trusted_data && $this->typedConfigManager->hasConfigSchema($this->name))<Fabianx-screen> stefan_r: I think the expensive part was getting the schema definition as that needs to build all those caches and ask all the plugin managers (IIRC)Comment #36
alexpottI'm not sure about this change.
I don't like this change for an number of reasons:
There's been a lot of tension throughout the D8 cycle about putting too much logic inside the Config object and its storage classes - personally I think this is a step in the wrong direction for the reasons given above.
If these are the only translated things that end up in config why don't we just cast to string here.
Comment #37
stefan.r commentedActually, looking at the parent issue it seems like there may be more t() strings that end up in config than just this one. I could dig deeper and find out how many of them?
Also the string cast in the default field settings (and wherever else we may use t()) would lead to unnecessary translation calls as it's being loaded on more than just the options screen, which is the only place where we actually need the translation of the default value.
And what about contrib? If they have output from t() that ends up in config they'll have a hard time tracking down these test failures. If we're going to break things by making t() return an object I'd rather we make that transition as painless as possible. So for point 2 I'd rather do a refactoring that makes this look less invasive and convoluted, because in the end all we're doing is a recursive string cast for BC reasons. If we can't do that anywhere it makes me wonder if we don't have a design problem elsewhere?
If the string cast in config/storage truly is still too "magical" and we're going to prevent people from passing objects with __toString() methods into config, let's at least make the BC break explicit and explicitly disallow it, i.e. by failing when they do so?
Comment #38
stefan.r commentedSo we do have more config items holding output from t() than just the boolean item one, the boolean item is just the only one in core that's in ConfigInstaller::installDefaultConfig(), the others are elsewher.
Talked to @alexpott on IRC and we may want to keep the automatic string cast after all so as to make life easier for developers.
edit:
this was wrong.. and the extra foreach was unneeded
Comment #40
alexpottI think we should cast on set. This means that
$config->set('foo', t('bar')); $config->get('foo');will return the string'bar'rather than a safe string. To do this I've swapped some logic around in initWithData so we don't cast everything unnecessary when loading config from storage.Comment #41
alexpottI didn't post an interdiff with #40 cause it would have been bigger than the patch.
Comment #42
stefan.r commentedIf this is green I think this is great too... so we get rid of validate_keys which is only FALSE on InitWithData so we don't have any problem with load()
not needed anymore
with
Comment #43
alexpottFixed nits from #42.
Comment #45
stefan.r commentedRandom test failures in MigrateDrupal6Test and MigrateUserConfigsTest on PIFR and in MigrateUserTest::testUser / MigrateDrupal6Test::testDrupal on CI ... couldn't reproduce them locally.
Comment #46
alexpottWell MigrateDrupal6Test runs all the tests including MigrateUserConfigsTest and MigrateUserTest so it's not surprising it has failed too.
I've run MigrateUserConfigsTest 100 times locally - no fails whatsoever and I can't see how this is introduced by this patch.
Comment #48
fabianx commentedRTBC, looks great to me.
We'll need a CR though.
Comment #49
stefan.r commentedComment #50
stefan.r commentedAdded draft CR: https://www.drupal.org/node/2564977
Comment #52
catchCommitted/pushed to 8.0.x thanks!
Comment #54
jackbravo commentedI know this is closed, but right now I'm getting the same install error:
> PHP message: Uncaught PHP Exception Symfony\Component\Routing\Exception\RouteNotFoundException: "Route "search.view_node_search" does not exist." at core/lib/Drupal/Core/Routing/RouteProvider.php line 191
Comment #55
joelpittet@jackbravo that doesn't look like this error at all. I couldn't find references to that route except in tests. Are you sure you have the latest 8.0.1 or dev release? If so try to run
/core/rebuild.phpordrush crto rebuild the container.Comment #56
jackbravo commentedSorry, it probably isn't related. I saw the same error on some tests running for this issue (patch 15): https://www.drupal.org/pift-ci-job/27901
But probably it was because of something else because the next patch did not generate that error (patch 16): https://www.drupal.org/pift-ci-job/27903