Unverified Commit 9b94f122 authored by Alex Pott's avatar Alex Pott
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
Loading
Loading
Loading
Loading
+27 −0
Original line number Diff line number Diff line
@@ -9,6 +9,33 @@
 */
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().
   *
+5 −0
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@

use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Variable;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Cache\Cache;
@@ -353,6 +354,10 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
    // Build the element if it is still empty.
    if (isset($elements['#lazy_builder'])) {
      $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.
      CacheableMetadata::createFromRenderArray($elements)
        ->merge(CacheableMetadata::createFromRenderArray($new_elements))
+72 −0
Original line number Diff line number Diff line
@@ -20,6 +20,78 @@
 */
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().
   *
+12 −0
Original line number Diff line number Diff line
@@ -962,6 +962,18 @@ public function testCreatePlaceholderPropertyWithoutLazyBuilder() {
    $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
   * #lazy_builder callback, but with different contexts. They don't modify
+11 −1
Original line number Diff line number Diff line
@@ -317,11 +317,21 @@ public static function callbackTagCurrentTemperature($animal) {
    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}
   */
  public static function trustedCallbacks() {
    return ['callbackTagCurrentTemperature', 'callbackPerUser', 'callback'];
    return ['callbackTagCurrentTemperature', 'callbackPerUser', 'callback', 'callbackNonArrayReturn'];
  }

}