Unverified Commit 9b94f122 authored by alexpott's avatar alexpott
Browse files

Issue #3143096 by jedihe, phenaproxima, jyotimishra123, shetpooja04, alexpott,...

Issue #3143096 by jedihe, phenaproxima, jyotimishra123, shetpooja04, alexpott, mradcliffe, tim.plunkett: [DX]: throw an exception if #lazy_builder callback does not return a (renderable) array

(cherry picked from commit a3c114e6)
parent cf096205
...@@ -9,6 +9,33 @@ ...@@ -9,6 +9,33 @@
*/ */
class Variable { class Variable {
/**
* Generates a human-readable name for a callable.
*
* @param callable $callable
* A callable.
*
* @return string
* A human-readable name for the callable.
*/
public static function callableToString($callable): string {
if ($callable instanceof \Closure) {
return '[closure]';
}
elseif (is_array($callable) && $callable) {
if (is_object($callable[0])) {
$callable[0] = get_class($callable[0]);
}
return implode('::', $callable);
}
elseif (is_string($callable)) {
return $callable;
}
else {
return '[unknown]';
}
}
/** /**
* Drupal-friendly var_export(). * Drupal-friendly var_export().
* *
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
use Drupal\Component\Render\MarkupInterface; use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Variable;
use Drupal\Component\Utility\Xss; use Drupal\Component\Utility\Xss;
use Drupal\Core\Access\AccessResultInterface; use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\Cache;
...@@ -353,6 +354,10 @@ protected function doRender(&$elements, $is_root_call = FALSE) { ...@@ -353,6 +354,10 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
// Build the element if it is still empty. // Build the element if it is still empty.
if (isset($elements['#lazy_builder'])) { if (isset($elements['#lazy_builder'])) {
$new_elements = $this->doCallback('#lazy_builder', $elements['#lazy_builder'][0], $elements['#lazy_builder'][1]); $new_elements = $this->doCallback('#lazy_builder', $elements['#lazy_builder'][0], $elements['#lazy_builder'][1]);
// Throw an exception if #lazy_builder callback does not return an array;
// provide helpful details for troubleshooting.
assert(is_array($new_elements), "#lazy_builder callbacks must return a valid renderable array, got " . gettype($new_elements) . " from " . Variable::callableToString($elements['#lazy_builder'][0]));
// Retain the original cacheability metadata, plus cache keys. // Retain the original cacheability metadata, plus cache keys.
CacheableMetadata::createFromRenderArray($elements) CacheableMetadata::createFromRenderArray($elements)
->merge(CacheableMetadata::createFromRenderArray($new_elements)) ->merge(CacheableMetadata::createFromRenderArray($new_elements))
......
...@@ -20,6 +20,78 @@ ...@@ -20,6 +20,78 @@
*/ */
class VariableTest extends TestCase { class VariableTest extends TestCase {
/**
* A bogus callable for testing ::callableToString().
*/
public static function fake(): void {
}
/**
* Data provider for testCallableToString().
*
* @return array[]
* Sets of arguments to pass to the test method.
*/
public function providerCallableToString(): array {
$self = static::class;
return [
'string' => [
"$self::fake",
"$self::fake",
],
'static method as array' => [
[$self, 'fake'],
"$self::fake",
],
'closure' => [
function () {
return NULL;
},
'[closure]',
],
'object method' => [
[new static(), 'fake'],
"$self::fake",
],
'service method' => [
'fake_service:method',
'fake_service:method',
],
'single-item array' => [
['some_function'],
'some_function',
],
'empty array' => [
[],
'[unknown]',
],
'object' => [
new \stdClass(),
'[unknown]',
],
'definitely not callable' => [
TRUE,
'[unknown]',
],
];
}
/**
* Tests generating a human-readable name for a callable.
*
* @param callable $callable
* A callable.
* @param string $expected_name
* The expected human-readable name of the callable.
*
* @dataProvider providerCallableToString
*
* @covers ::callableToString
*/
public function testCallableToString($callable, string $expected_name): void {
$this->assertSame($expected_name, Variable::callableToString($callable));
}
/** /**
* Data provider for testExport(). * Data provider for testExport().
* *
......
...@@ -962,6 +962,18 @@ public function testCreatePlaceholderPropertyWithoutLazyBuilder() { ...@@ -962,6 +962,18 @@ public function testCreatePlaceholderPropertyWithoutLazyBuilder() {
$this->renderer->renderRoot($element); $this->renderer->renderRoot($element);
} }
/**
* Tests that an error is thrown if a lazy builder doesn't return an array.
*/
public function testNonArrayReturnFromLazyBuilder(): void {
$element = [
'#lazy_builder' => ['\Drupal\Tests\Core\Render\PlaceholdersTest::callbackNonArrayReturn', []],
];
$this->expectException('AssertionError');
$this->expectExceptionMessage("#lazy_builder callbacks must return a valid renderable array, got boolean from \Drupal\Tests\Core\Render\PlaceholdersTest::callbackNonArrayReturn");
$this->renderer->renderRoot($element);
}
/** /**
* Create an element with a child and subchild. Each element has the same * Create an element with a child and subchild. Each element has the same
* #lazy_builder callback, but with different contexts. They don't modify * #lazy_builder callback, but with different contexts. They don't modify
......
...@@ -317,11 +317,21 @@ public static function callbackTagCurrentTemperature($animal) { ...@@ -317,11 +317,21 @@ public static function callbackTagCurrentTemperature($animal) {
return $build; return $build;
} }
/**
* A lazy builder callback that returns an invalid renderable.
*
* @return bool
* TRUE, which is not a valid return value for a lazy builder.
*/
public static function callbackNonArrayReturn() {
return TRUE;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public static function trustedCallbacks() { public static function trustedCallbacks() {
return ['callbackTagCurrentTemperature', 'callbackPerUser', 'callback']; return ['callbackTagCurrentTemperature', 'callbackPerUser', 'callback', 'callbackNonArrayReturn'];
} }
} }
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment