Problem/Motivation

Having static methods on RendererInterface causes people to call methods the default implementation of that interface directly.

Consequence: impossible to swap out the renderer.

Proposed resolution

Make the static method non-static.

(If there'd be many callers, we should maybe consider providing a trait similar to StringTranslationTrait. But there's no need for that: there are only five calls to the 2 static methods, and they're all in "low-level" places.)

Remaining tasks

TBD

User interface changes

None.

API changes

TBD

Original report

See #2444231-68: Fix CacheableInterface so it makes sense (was: "Make Config objects & Entities implement CacheableInterface + add BubbleableMetadata::createFromCacheableObject()").1 + #2444231-62: Fix CacheableInterface so it makes sense (was: "Make Config objects & Entities implement CacheableInterface + add BubbleableMetadata::createFromCacheableObject()").1.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

rpayanm’s picture

Status: Active » Needs review
FileSize
5.21 KB

Please review.

Status: Needs review » Needs work

The last submitted patch, 1: 2460695-1.patch, failed testing.

mitrpaka’s picture

Status: Needs work » Needs review
FileSize
5.56 KB
923 bytes

Updated patch to pass unit test cases.

dawehner’s picture

+++ b/core/lib/Drupal/Core/Ajax/AjaxResponse.php
@@ -78,7 +78,7 @@ public function addCommand(CommandInterface $command, $prepend = FALSE) {
-      $attachments = Renderer::mergeAttachments($this->attachments, $attachments);
+      $attachments = NestedArray::mergeDeep($this->attachments, $attachments);
       $this->setAttachments($attachments);

What is the reason we can use NestedArray::mergeDeep instead of the implementation of the renderer.

Wim Leers’s picture

Status: Needs review » Needs work
mitrpaka’s picture

NestedArray::mergeDeep does exactly what static method of mergeAttachments did.

rendered service is not valid when called out from the global services container. If implementation of the renderer is to be used then (I guess) service should be injected somehow via setter methods.

Please advise / help which way to go from here?

  public function addCommand(CommandInterface $command, $prepend = FALSE) {
    if ($prepend) {
      array_unshift($this->commands, $command->render());
    }
    else {
      $this->commands[] = $command->render();
    }
    if ($command instanceof CommandWithAttachedAssetsInterface) {
      $assets = $command->getAttachedAssets();
      $attachments = [
        'library' => $assets->getLibraries(),
        'drupalSettings' => $assets->getSettings(),
      ];
      $attachments = NestedArray::mergeDeep($this->attachments, $attachments);
      $this->setAttachments($attachments);
    }

    return $this;
  }
Wim Leers’s picture

@mitrpaka: in cases like this, we usually do something like:

function getRenderer() {
  return \Drupal::service('renderer');
}

And then: $this->getRenderer()->mergeAttachments().

mitrpaka’s picture

Did try that already but unit test cases fails due to missing (global services) container.

"\Drupal::$container is not initialized yet. \Drupal::setContainer() must be called with a real container."

Wim Leers’s picture

@mitrpaka: see e.g. \Drupal\Tests\Core\Session\AnonymousUserSessionTest for an example of how to deal with that.

rpayanm’s picture

Status: Needs work » Needs review
FileSize
2.06 KB
6.56 KB

I can do something like this?

Status: Needs review » Needs work

The last submitted patch, 10: 2460695-10.patch, failed testing.

Wim Leers’s picture

#10: that only works for plugins, because plugin instances are created using the static ::create() method, but Response objects are created using new (Ajax)Response().

rpayanm’s picture

Status: Needs work » Needs review
FileSize
2.04 KB
5.85 KB

Then let me try with #7.

Status: Needs review » Needs work

The last submitted patch, 13: 2460695-13.patch, failed testing.

Wim Leers’s picture

Now something like #9 is still necessary to fix the last few failures.

mitrpaka’s picture

Status: Needs work » Needs review
FileSize
12.18 KB
6.18 KB

First trial to fix the last few failures.

For some reason if container created using RendererInterface (instead of Renderer), e.g.

    $this->renderer = $this->getMock('Drupal\Core\Render\RendererInterface');
    $container = new ContainerBuilder();
    $container->set('renderer', $this->renderer);
    \Drupal::setContainer($container);

returned error (even though arguments were exactly the same in mergeAttachments())
Argument 1 passed to Drupal\Core\Ajax\AjaxResponse::setAttachments() must be of the type array, null given

Wim Leers’s picture

Thanks! Getting close! :)

Did you try

$this->getMockBuilder('Drupal\Core\Render\Renderer')
  ->disableOriginalConstructor()
  ->setMethods(NULL)
  ->getMock()

?

AFAICT that should allow you to get a mocked renderer with the ::mergeAttachments() method exactly the same, without the need to mock all dependencies of the renderer :)

(See https://phpunit.de/manual/3.7/en/test-doubles.html.)

mitrpaka’s picture

FileSize
9.2 KB
5.46 KB

@Wim Leers: Thank You! Was not aware of that (until now ;-).

Updated patch, as in #17.

Wim Leers’s picture

Status: Needs review » Needs work

It takes a while to get the hang of PHPUnit :) Just giving the advice I wish somebody had given me long ago! :)


This looks great! Almost ready. Only nitpicks this time :)

  1. +++ b/core/lib/Drupal/Core/Ajax/AjaxResponse.php
    @@ -228,4 +229,14 @@ protected function ajaxRender(Request $request) {
    +   * @return \Drupal\Core\Render\Renderer
    

    We always typehint on the interface.

  2. +++ b/core/lib/Drupal/Core/Ajax/AjaxResponse.php
    @@ -228,4 +229,14 @@ protected function ajaxRender(Request $request) {
    +   *   Service that turns a render array into a HTML string.
    

    Just say "The renderer service." — that's enough :)

  3. +++ b/core/tests/Drupal/Tests/Core/Controller/AjaxRendererTest.php
    @@ -25,6 +26,13 @@ class AjaxRendererTest extends UnitTestCase {
       /**
    +   * The tested renderer.
    +   *
    +   * @var \Drupal\Core\Render\Renderer
    +   */
    

    This should be:

      /**
       * The renderer.
       *
       * @var \Drupal\Core\Render\RendererInterface|\PHPUnit_Framework_MockObject_MockObject
       */
    

    (Just like in ViewAjaxControllerTest.)

mitrpaka’s picture

Status: Needs work » Needs review
FileSize
9.21 KB
1.03 KB

Thanks for the help and good advices once again!

Updated patch, changes as in #19.

Wim Leers’s picture

Status: Needs review » Reviewed & tested by the community

Perfect, thank you! :)

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

This issue addresses a major bug and is allowed per https://www.drupal.org/core/beta-changes. Committed a0078c5 and pushed to 8.0.x. Thanks!

I considered asking for a CR but I don't usages of these methods with be widespread in custom or contrib yet - so I don't think it is worth the noise.

  • alexpott committed a0078c5 on 8.0.x
    Issue #2460695 by mitrpaka, rpayanm: No methods on RendererInterface...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.