If a filter's settings form has form elements inside of a fieldset, an error condition seems to happen which (at least on my local dev machine) triggers an error which then triggers an infinite loop, or something near enough to one that I run out of memory and get served a WSOD.
I've attached an example module which will trigger this. Enable the module and add the filter it provides to a text format to trigger the error.
Here's relevant PHP logs:
[19-Nov-2014 11:59:33 US/Mountain] PHP Fatal error: Allowed memory size of 536870912 bytes exhausted (tried to allocate 72 bytes) in /Users/Albright/Sites/d8.test/www/core/vendor/symfony/debug/Symfony/Component/Debug/Exception/FlattenException.php on line 228
[19-Nov-2014 11:59:33 US/Mountain] PHP Stack trace:
[19-Nov-2014 11:59:33 US/Mountain] PHP 1. {main}() /Users/Albright/Sites/d8.test/www/index.php:0
[19-Nov-2014 11:59:33 US/Mountain] PHP 2. Drupal\Core\DrupalKernel->handle() /Users/Albright/Sites/d8.test/www/index.php:22
[19-Nov-2014 11:59:33 US/Mountain] PHP 3. Stack\StackedHttpKernel->handle() /Users/Albright/Sites/d8.test/www/core/lib/Drupal/Core/DrupalKernel.php:569
[19-Nov-2014 11:59:33 US/Mountain] PHP 4. Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle() /Users/Albright/Sites/d8.test/www/core/vendor/stack/builder/src/Stack/StackedHttpKernel.php:23
[19-Nov-2014 11:59:33 US/Mountain] PHP 5. Drupal\Core\StackMiddleware\PageCache->handle() /Users/Albright/Sites/d8.test/www/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php:58
[19-Nov-2014 11:59:33 US/Mountain] PHP 6. Drupal\Core\StackMiddleware\KernelPreHandle->handle() /Users/Albright/Sites/d8.test/www/core/lib/Drupal/Core/StackMiddleware/PageCache.php:52
[19-Nov-2014 11:59:33 US/Mountain] PHP 7. Symfony\Component\HttpKernel\HttpKernel->handle() /Users/Albright/Sites/d8.test/www/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php:53
[19-Nov-2014 11:59:33 US/Mountain] PHP 8. Symfony\Component\HttpKernel\HttpKernel->handleException() /Users/Albright/Sites/d8.test/www/core/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpKernel.php:74
[19-Nov-2014 11:59:33 US/Mountain] PHP 9. Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch() /Users/Albright/Sites/d8.test/www/core/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpKernel.php:222
[19-Nov-2014 11:59:33 US/Mountain] PHP 10. Drupal\Core\EventSubscriber\HttpExceptionSubscriberBase->onException() /Users/Albright/Sites/d8.test/www/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php:116
[19-Nov-2014 11:59:33 US/Mountain] PHP 11. Symfony\Component\Debug\Exception\FlattenException::create() /Users/Albright/Sites/d8.test/www/core/lib/Drupal/Core/EventSubscriber/HttpExceptionSubscriberBase.php:91
[19-Nov-2014 11:59:33 US/Mountain] PHP 12. Symfony\Component\Debug\Exception\FlattenException->setTraceFromException() /Users/Albright/Sites/d8.test/www/core/vendor/symfony/debug/Symfony/Component/Debug/Exception/FlattenException.php:52
[19-Nov-2014 11:59:33 US/Mountain] PHP 13. Symfony\Component\Debug\Exception\FlattenException->setTrace() /Users/Albright/Sites/d8.test/www/core/vendor/symfony/debug/Symfony/Component/Debug/Exception/FlattenException.php:175
[19-Nov-2014 11:59:33 US/Mountain] PHP 14. Symfony\Component\Debug\Exception\FlattenException->flattenArgs() /Users/Albright/Sites/d8.test/www/core/vendor/symfony/debug/Symfony/Component/Debug/Exception/FlattenException.php:208
[19-Nov-2014 11:59:33 US/Mountain] PHP 15. Symfony\Component\Debug\Exception\FlattenException->flattenArgs() /Users/Albright/Sites/d8.test/www/core/vendor/symfony/debug/Symfony/Component/Debug/Exception/FlattenException.php:223
[19-Nov-2014 11:59:33 US/Mountain] PHP 16. Symfony\Component\Debug\Exception\FlattenException->flattenArgs() /Users/Albright/Sites/d8.test/www/core/vendor/symfony/debug/Symfony/Component/Debug/Exception/FlattenException.php:223
[snip]
[19-Nov-2014 11:59:33 US/Mountain] PHP 25. Symfony\Component\Debug\Exception\FlattenException->flattenArgs() /Users/Albright/Sites/d8.test/www/core/vendor/symfony/debug/Symfony/Component/Debug/Exception/FlattenException.php:223
Interestingly, when I first noticed this issue while working with Pathologic earlier today, I saw this line before ones like those above, though I can no longer replicate getting this line to show up (possibly since I've updated Drupal core since then). It may be a clue, though; 'local_settings' was the key for the fieldset in my form definition, and 'protocol_style' is a field in the fieldset.
[19-Nov-2014 06:36:15 US/Mountain] Uncaught PHP Exception InvalidArgumentException: "The configuration property filters.filter_pathologic.settings.local_settings.protocol_style doesn't exist." at /Users/Albright/Sites/d8.test/www/core/lib/Drupal/Core/Config/Schema/Mapping.php line 66
I'm both too unfamiliar with D8/Symfony's guts and jetlagged to diagnose further what might be going on here at the moment, but hopefully it rings a bell for someone. If not I'll try to take a closer look at it later (perhaps over the weekend).
| Comment | File | Size | Author |
|---|---|---|---|
| #29 | filter_fieldsets_example 2.zip | 9.04 KB | Garrett Albright |
Comments
Comment #1
Garrett Albright commentedComment #2
Garrett Albright commentedI'm now not so sure that the problem was due to an infinite loop. Maybe it actually can complete, if you give it enough RAM… I set memory_limit to 2G, but still ran out of memory. At any rate, I was getting into the weeds debugging that, so I took another approach.
This patch will "flatten" values of the settings form before they're saved. Not 100% sure that this is the correct approach (please post your argument if you don't think so), but it fixes the bug while making things work more like they did in Drupal 7. It will allow Pathologic for Drupal 7 and Drupal 8 to have the same UI, at least.
Comment #3
Garrett Albright commentedAnd here's a less-buggy version of the test filter which demonstrates this bug if you enable it before the patch. (For some reason I wasn't able to attach two files to my previous post.)
Comment #5
Garrett Albright commentedOkay, let's try this approach, then.
Comment #6
Garrett Albright commentedComment #7
cilefen commentedI can't enable the test module.
Drupal\Component\Plugin\Exception\PluginNotFoundException: The "filter_fieldsets_example" plugin does not exist. in Drupal\Core\Plugin\DefaultPluginManager->doGetDefinition() (line 57 of /Library/WebServer/Documents/drupal8x/core/lib/Drupal/Component/Plugin/Discovery/DiscoveryTrait.php).
Must be a period after this.
This should have tests.
Comment #8
Garrett Albright commentedWell, splendid. Something must have broken recently, because now Pathologic causes the same error when enabled too.
I'll bisect around and see if I can find a cause.
Comment #9
Garrett Albright commentedYep… Enabling modules which provide filters is currently broken in core.
Comment #10
Garrett Albright commented(Tried to add a related issue, but it didn't take…? Trying again. It's https://www.drupal.org/node/2348925 BTW)
(EDIT: I give up. :( )
Comment #11
tien.xuan.vo commentedI got this error too, when I go to this path: /node/add/my-custom-content
I applied this patch, but it does not work, I think because I did not submit any form.
Comment #12
tien.xuan.vo commentedI think the real issue is: every times we throw an exception, this issue occur. To know what is the exception, we can set 'args' to array() in Symfony\Component\Debug\Exception\FlattenException::setTrace(). After fixing the exception, this issue gone away.
Comment #13
nicholasthompsonI can confirm that the above patch lets the filter settings form submit again...
Comment #14
nicholasthompsonScratch that - this breaks things which need to be nested, like checkbox arrays...
Comment #15
nicholasthompsonAlso this only seems to fix edit - I get the error on Add too...
Comment #16
nicholasthompsonIn FilterFormatFormBase, I did this to the submit:
This produces:
So The bug seems to be inside the format saving...
Comment #17
nicholasthompsonDrupal/Core/Config/Config.php
in the save() method there is a section for casting the value... This seems to be where a failure happens:
foreach ($this->data as $key => $value) { echo "casting $key to : "; var_dump($value); $this->data[$key] = $this->castValue($key, $value); } echo 'done';casting uuid to : string(36) "8105978f-4ac7-4351-9e00-00144ecc281a" casting langcode to : string(2) "en" casting status to : bool(true) casting dependencies to : array(1) { ["module"]=> array(2) { [0]=> string(6) "editor" [1]=> string(14) "path_corrector" } } casting name to : string(10) "Basic HTML" casting format to : string(10) "basic_html" casting weight to : int(0) casting filters to : array(10) { ["filter_html"]=> array(5) { ["id"]=> string(11) "filter_html" ["provider"]=> string(6) "filter" ["status"]=> bool(true) ["weight"]=> int(-50) ["settings"]=> array(3) { ["allowed_html"]=> string(106) "Comment #18
nicholasthompsonComment #19
nicholasthompsonIt seems to be inside the "castValue" method in StorableConfigBase...
Seems to be dying when trying to get the element for the "a" checkbox in my modules form...
Comment #20
nicholasthompsonSo all my debugging has led me back to Mapping.php as Garret has found originally.
In my case, the final item
filters.path_corrector.settings.tags.aproduces a NULL$element....Comment #21
nicholasthompsonIf I debug the class of
$elementat each step, then var_dump the element if there is no "get" method... I get this:KEY: uuid KEY: langcode KEY: status KEY: dependencies KEY: dependencies.module Drupal\Core\Config\Schema\Mapping KEY: dependencies.module.0 Drupal\Core\Config\Schema\Mapping Drupal\Core\Config\Schema\Sequence KEY: dependencies.module.1 Drupal\Core\Config\Schema\Mapping Drupal\Core\Config\Schema\Sequence KEY: name KEY: format KEY: weight KEY: filters KEY: filters.filter_html Drupal\Core\Config\Schema\Sequence KEY: filters.filter_html.id Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_html.provider Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_html.status Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_html.weight Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_html.settings Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_html.settings.allowed_html Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping Drupal\Core\Config\Schema\Mapping KEY: filters.filter_html.settings.filter_html_help Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping Drupal\Core\Config\Schema\Mapping KEY: filters.filter_html.settings.filter_html_nofollow Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping Drupal\Core\Config\Schema\Mapping KEY: filters.filter_align Drupal\Core\Config\Schema\Sequence KEY: filters.filter_align.id Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_align.provider Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_align.status Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_align.weight Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_align.settings Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_caption Drupal\Core\Config\Schema\Sequence KEY: filters.filter_caption.id Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_caption.provider Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_caption.status Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_caption.weight Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_caption.settings Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_html_image_secure Drupal\Core\Config\Schema\Sequence KEY: filters.filter_html_image_secure.id Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_html_image_secure.provider Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_html_image_secure.status Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_html_image_secure.weight Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_html_image_secure.settings Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_htmlcorrector Drupal\Core\Config\Schema\Sequence KEY: filters.filter_htmlcorrector.id Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_htmlcorrector.provider Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_htmlcorrector.status Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_htmlcorrector.weight Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_htmlcorrector.settings Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.editor_file_reference Drupal\Core\Config\Schema\Sequence KEY: filters.editor_file_reference.id Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.editor_file_reference.provider Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.editor_file_reference.status Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.editor_file_reference.weight Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.editor_file_reference.settings Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_url Drupal\Core\Config\Schema\Sequence KEY: filters.filter_url.id Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_url.provider Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_url.status Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_url.weight Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_url.settings Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.filter_url.settings.filter_url_length Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping Drupal\Core\Config\Schema\Mapping KEY: filters.path_corrector Drupal\Core\Config\Schema\Sequence KEY: filters.path_corrector.id Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.path_corrector.provider Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.path_corrector.status Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.path_corrector.weight Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.path_corrector.settings Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping KEY: filters.path_corrector.settings.tags Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping Drupal\Core\Config\Schema\Sequence KEY: filters.path_corrector.settings.tags.a Drupal\Core\Config\Schema\Sequence Drupal\Core\Config\Schema\Mapping Drupal\Core\Config\Schema\Sequence object(Drupal\Core\TypedData\Plugin\DataType\String)#811 (5) { ["value":protected]=> array(2) { ["a"]=> int(0) ["img"]=> int(0) } ["definition":protected]=> object(Drupal\Core\TypedData\DataDefinition)#810 (1) { ["definition":protected]=> array(4) { ["type"]=> string(6) "string" ["label"]=> string(5) "Value" ["class"]=> string(45) "\Drupal\Core\TypedData\Plugin\DataType\String" ["definition_class"]=> string(37) "\Drupal\Core\TypedData\DataDefinition" } } ["name":protected]=> string(4) "tags"So, for some reason, it's interpreting my "tags" array as a string?!
Comment #22
nicholasthompsonAhh... I have a feeling this might be a "feature"...
https://api.drupal.org/api/drupal/core%21modules%21filter%21src%21Plugin...
It looks like FilterFormat's expect all settings to be strings. There doesn't seem to be anything in there to consider a filter format setting data type to be anything other that string.
EDIT: I got to this by looking in TypedConfigManager.php and when it gets a definition of a fomat, we see:
Array ( [type] => filter [label] => Filter [class] => \Drupal\Core\Config\Schema\Mapping [definition_class] => \Drupal\Core\TypedData\MapDataDefinition [mapping] => Array ( [id] => Array ( [type] => string [label] => ID ) [provider] => Array ( [type] => string [label] => Provider ) [status] => Array ( [type] => boolean [label] => Status ) [weight] => Array ( [type] => integer [label] => Weight ) [settings] => Array ( [type] => filter_settings.[%parent.id] ) ) )Comment #23
nicholasthompsonAhhh - I wonder if we need to define a schema for our format settings?!
https://api.drupal.org/api/drupal/core!modules!filter!config!schema!filt...
Comment #24
nicholasthompsonAh ha!!! This is not a Drupal 8 bug - this is a documentation issue IMHO ;)
Filter Settings are (I believe) assumed to be Strings, unless you specify otherwise.
I have fixed this on Path Corrector by adding this to config/schema/path_corrector.schema.yml:
filter_settings.path_corrector: type: filter label: 'Path Corrector' mapping: tags: type: sequence: label: 'Tags' sequence: type: string label: 'Tag' string_replacements: type: string label: 'String Replacements'My clue was when I saw filter_settings.[%parent.id] above... I ended up finding /core/modules/filter/config/schema/filter.schema.yml and saw that Filter HTML and Filter URL all define their own config schema.
Arguably, Drupal should handle this error better. Some kind of "incompatible DataType" or something - without a full on WSOD...
EDIT: This is pretty handy - https://www.drupal.org/node/1905070
Comment #25
Garrett Albright commentedDo you think you could share what your form that goes along with those settings looks like?
I added a .schema.yml file and now I see the general Drupal error page upon saving instead of the WSOD, but the error in the watchdog seems to imply it's still trying to save settings at another level "down" with the name of the fieldset. Do I really have to replicate my form structure in the structure of my schema?
Comment #26
nicholasthompsonI'm on my phone right now so can't get to the code easily... But checkout the Drupal 8 branch of Path Corrector.
TLDR is "yes" - you need to replicate quite a bit. In my case with the checkboxes I had to define that the checkboxes were a mapping and that each child would be a string.
Comment #27
nicholasthompsonSequence, not mapping ;)
http://cgit.drupalcode.org/path_corrector/tree/config/schema/path_correc...
Comment #28
Garrett Albright commentedI was looking for the form that corresponds to the schema, not the schema itself. I guess this is it: http://cgit.drupalcode.org/path_corrector/tree/src/Plugin/Filter/FilterP...
But it looks like your form isn't using fieldsets after all, so perhaps we were talking past each other…
Comment #29
Garrett Albright commentedOkay. After some experimentation, I've gotten things to work.
The keys are, one, you *do* need to replicate the structure of the form in the schema (argh), and two, you also need to do so in the "settings" part of the filter annotation. In $this->settings, the field values will be in sub-arrays also corresponding to everything.
I've attached an updated version of the example module I posted in the OP which works. Check out the 8.x-1.x branch of Pathologic for a more complex example.