Problem/Motivation
Deprecated function: strnatcasecmp(): Passing null to parameter #2 ($string2) of type string is deprecated in Drupal\Core\Layout\LayoutPluginManager->Drupal\Core\Layout\{closure}() (line 204 of core/lib/Drupal/Core/Layout/LayoutPluginManager.php).
appears on pages, where layouts are being listed in a select which uses $this->layoutPluginManager->getLayoutOptions()
to get its options.
Example:
$form['field_layouts']['field_layout'] = [
'#type' => 'select',
'#title' => $this->t('Select a layout'),
'#options' => $this->layoutPluginManager->getLayoutOptions(),
'#default_value' => $layout_plugin->getPluginId(),
'#ajax' => [
'callback' => '::settingsAjax',
'wrapper' => 'field-layout-settings-wrapper',
'trigger_as' => ['name' => 'field_layout_change'],
],
];
(FieldLayoutEntityDisplayFormTrait.php)
layout_builder's layout_builder_blank
aka BlankLayout neither has a label nor a category defined in its annotation:
/**
* Provides a layout plugin that produces no output.
*
* @see \Drupal\layout_builder\Field\LayoutSectionItemList::removeSection()
* @see \Drupal\layout_builder\SectionListTrait::addBlankSection()
* @see \Drupal\layout_builder\SectionListTrait::hasBlankSection()
*
* @internal
* This layout plugin is intended for internal use by Layout Builder only.
*
* @Layout(
* id = "layout_builder_blank",
* )
*/
class BlankLayout extends LayoutDefault {
it should be sorted out by
/**
* Implements hook_plugin_filter_TYPE_alter().
*/
function layout_builder_plugin_filter_layout_alter(array &$definitions, array $extra, $consumer) {
// Hide the blank layout plugin from listings.
unset($definitions['layout_builder_blank']);
}
(layout_builder.module)
but that hook never gets called!
So the layout_builder_blank
layout is being passed through without label or category, but these methods rely on these values to be present. They are being called indirectly by $this->layoutPluginManager->getLayoutOptions()
/**
* {@inheritdoc}
*
* @return \Drupal\Core\Layout\LayoutDefinition[]
*/
public function getSortedDefinitions(array $definitions = NULL, $label_key = 'label') {
// Sort the plugins first by category, then by label.
$definitions = $definitions ?? $this->getDefinitions();
uasort($definitions, function (LayoutDefinition $a, LayoutDefinition $b) {
if ($a->getCategory() != $b->getCategory()) {
return strnatcasecmp($a->getCategory(), $b->getCategory());
}
return strnatcasecmp($a->getLabel(), $b->getLabel());
});
return $definitions;
}
/**
* {@inheritdoc}
*
* @return \Drupal\Core\Layout\LayoutDefinition[][]
*/
public function getGroupedDefinitions(array $definitions = NULL, $label_key = 'label') {
$definitions = $this->getSortedDefinitions($definitions ?? $this->getDefinitions(), $label_key);
$grouped_definitions = [];
foreach ($definitions as $id => $definition) {
$grouped_definitions[(string) $definition->getCategory()][$id] = $definition;
}
return $grouped_definitions;
}
/**
* {@inheritdoc}
*/
public function getLayoutOptions() {
$layout_options = [];
foreach ($this->getGroupedDefinitions() as $category => $layout_definitions) {
foreach ($layout_definitions as $name => $layout_definition) {
$layout_options[$category][$name] = $layout_definition->getLabel();
}
}
return $layout_options;
}
This is why the error appears.
This deprecation warning didn't appear in older versions of PHP, so the bug wasn't visible.
TL;DR:
/**
* Implements hook_plugin_filter_TYPE_alter().
*/
function layout_builder_plugin_filter_layout_alter(array &$definitions, array $extra, $consumer) {
// Hide the blank layout plugin from listings.
unset($definitions['layout_builder_blank']);
}
(layout_builder.module)
is not called, so the layout_builder_blank
layout, which neither has a label nor a description is not being sorted out, which leads to this error!
Steps to reproduce
See above description. For this issue to appear, it needs
- layout_builder to be enabled, so the layout_builder_blank layout is added
- A form which uses
FieldLayoutEntityDisplayFormTrait
or directly calls $this->layoutPluginManager->getLayoutOptions(),
See #3089418: Layout selection weight sorting for another example and #3316811: strnatcasecmp(): Passing null to parameter #1 ($string) of type string is deprecated for an older (duplicate) report of this issue.
Simple code example:
As a contrib example, see https://www.drupal.org/project/layout_disable where this happens in
https://git.drupalcode.org/project/layout_disable/-/blob/2.x/src/Form/La...
It seems as like the LayoutPluginManager (DI-injected by $container->get('plugin.manager.core.layout'),
) is entirely missing FilteredPluginManagerTrait
, so that its alterations are never being called:
/**
* Implements \Drupal\Core\Plugin\FilteredPluginManagerInterface::getFilteredDefinitions().
*/
public function getFilteredDefinitions($consumer, $contexts = NULL, array $extra = []) {
if (!is_null($contexts)) {
$definitions = $this->getDefinitionsForContexts($contexts);
}
else {
$definitions = $this->getDefinitions();
}
$type = $this->getType();
$hooks = [];
$hooks[] = "plugin_filter_{$type}";
$hooks[] = "plugin_filter_{$type}__{$consumer}";
$this->moduleHandler()->alter($hooks, $definitions, $extra, $consumer);
$this->themeManager()->alter($hooks, $definitions, $extra, $consumer);
return $definitions;
}
meanwhile, they are being called in Layout builder (e.g. admin/structure/types/manage/page/display/default/layout) when adding a section.
So maybe layout_builder is doing something different or special?
If layout_builder is not installed, everything is fine!
Proposed resolution
- Find out why
layout_builder_plugin_filter_layout_alter()
is not being called and layout_builder_blank
layout plugin is not being sorted out, when layout_builder is enabled. - Make layout_builder_plugin_filter_layout_alter() work or sort out
layout_builder_blank
in another way
This should also solve the side-effect of layout_builder_blank appearing in selects!
Additionally, we might cast the NULL value to string, like in other similar issues (see related). But that doesn't solve the root cause, just prevents strnatcasecmp from running into this error.
Additionally, we might add a label and category to the layout_builder_blank
layout so it never returns NULL for these values.
But these are not valid solutions to fix the root cause!
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet