[Drupal] ResourceResponseで作成したレスポンスにURLを入れるとエラーが出る場合の対処方


Custom REST Resources

カスタムのRESTリソースプラグインを作成するときは、こんな感じで\Drupal\rest\ResourceResponseオブジェクトを返す必要があるのですが、

  /**
   * Responds to entity GET requests.
   * @return \Drupal\rest\ResourceResponse
   */
  public function get() {
    $response = ['message' => 'Hello, this is a rest service'];
    return new ResourceResponse($response);
  }

この配列の中に\Drupal\Core\Urlオブジェクトから作ったURLを入れると、

  public function get() {
    $response = [
        'message' => 'Hello, this is a rest service'
        'url' => Url::fromUri('internal:/test')->toString(), // '/test'という文字列
    ];
    return new ResourceResponse($response);
  }

以下のようなエラーになります。

サイトに予期せぬエラーが起こりました。しばらくたってから再度お試しください。<br><br><em class="placeholder">LogicException</em>: The controller result claims to be providing relevant cache metadata, but leaked metadata was detected. Please ensure you are not rendering content too early. Returned object class: Drupal\rest\ResourceResponse. in <em class="placeholder">Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber-&gt;wrapControllerExecutionInRenderContext()</em> (line <em class="placeholder">154</em> of <em class="placeholder">core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php</em>). <pre class="backtrace">Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber-&gt;Drupal\Core\EventSubscriber\{closure}() (Line: 158)
Symfony\Component\HttpKernel\HttpKernel-&gt;handleRaw() (Line: 80)
Symfony\Component\HttpKernel\HttpKernel-&gt;handle() (Line: 58)
Drupal\Core\StackMiddleware\Session-&gt;handle() (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle-&gt;handle() (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache-&gt;pass() (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache-&gt;handle() (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware-&gt;handle() (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware-&gt;handle() (Line: 23)
Stack\StackedHttpKernel-&gt;handle() (Line: 708)
Drupal\Core\DrupalKernel-&gt;handle() (Line: 19)
</pre>

ちなみに、URLっぽい文字列に反応してるのかと思い、Urlから作らずにただの文字列をそのまま入れたらエラーにならなかった。

    public function get() {
    $response = [
        'message' => 'Hello, this is a rest service',
        'url' => '/test',
    ];
    return new ResourceResponse($response);
  }

とにかくプロセス中にUrl::fromUri()->toString()が走るとだめっぽい。

この質問見ると、コントローラーでも同じ事象が起きるらしい。
https://drupal.stackexchange.com/a/225963/90523

public function demo() {

  $build = ['#markup' => 'early rendering'];
  $markup = \Drupal::service('renderer')->render($build);

  $build = [
    '#cache' => [
      'contexts' => ['ip'],
    ],
  ];
  return (new CacheableJsonResponse(['test'], 200))->addCacheableDependency(CacheableMetadata::createFromRenderArray($build));
}

レスポンスの問題というより、

Url::fromUri('internal:/test')->toString()

$markup = \Drupal::service('renderer')->render($build);

に対してエラーが起きてるのか。。。?

同じような質問のコメント欄。
https://drupal.stackexchange.com/questions/187086/trustedresponseredirect-failing-how-to-prevent-cache-metadata/187094#187094

To make Matthew's comment abundantly clear, Url::toString() seems to count as a render apparently, so if you are using this in your controller anywhere and do not sent TRUE to gather the cacheable meta data you will hit this trap.

つまり、Url::fromUri('internal:/test')->toString()で実はレンダリング処理が走っていると。

よくわかんないけど以下のように書き換えたら治った。

    public function get() {
    $response = [
        'message' => 'Hello, this is a rest service',
        'url' => Url::fromUri('internal:/test')
            // Drupal\Core\GeneratedUrlオブジェクトにする
            ->toString(TRUE)
            // /testの文字列取得
            ->getGeneratedUrl(), 
    ];
    return new ResourceResponse($response);
  }