Support non string callbacks with lazy builders

Created on 10 June 2019, over 5 years ago
Updated 14 September 2023, over 1 year ago

Problem/Motivation

Lazy builder callbacks only support strings, which allows you to use a global function or a service in service_id:method format
But PHP considers [$class, $method] to be a valid callback too

Steps to reproduce

using [$object, $methodname] as a lazy builder callback and notice it generates an error during placeholdering

Proposed resolution

Support array based callbacks

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Original report by joachim

I get this error:

>htmlspecialchars() expects parameter 1 to be string, array

This is because PlaceholderGenerator::createPlaceholder() assumes the callback is a string:

    $callback = $placeholder_render_array['#lazy_builder'][0];
    $arguments = UrlHelper::buildQuery($placeholder_render_array['#lazy_builder'][1]);
    $token = Crypt::hashBase64(serialize($placeholder_render_array));
    $placeholder_markup = '<drupal-render-placeholder callback="' . Html::escape($callback) . '" arguments="' . Html::escape($arguments) . '" token="' . Html::escape($token) . '"></drupal-render-placeholder>';

Everything else about lazy builders works ok with the callable in [$object, $methodname] format.

πŸ“Œ Task
Status

Needs work

Version

11.0 πŸ”₯

Component
RenderΒ  β†’

Last updated about 8 hours ago

Created by

πŸ‡¬πŸ‡§United Kingdom joachim

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • last update over 1 year ago
    30,150 pass
  • πŸ‡ΊπŸ‡ΈUnited States mfb San Francisco

    I found at least three places where we need some tweaks to support non-string callbacks.

    (This is just a proof of concept, feel free to bikeshed :)

  • πŸ‡¬πŸ‡§United Kingdom joachim

    Reading this issue again -- which I filed! -- I am wondering how I thought this would work with the way LB callbacks are stored in cached HTML...

    But anyway, if it works, it works :D

    About the patch -- I think that instead of repeating that code, we should add a method to the new callback resolver service, something like toString().

  • πŸ‡ΊπŸ‡ΈUnited States mfb San Francisco

    I'm thinking we should make clear that the string it generates is just for debugging purposes, something like CallableResolver::getDebugStringFromDefinition()?

  • πŸ‡¬πŸ‡§United Kingdom joachim

    It wouldn't be for debugging in this use case though -- it's to go in a lazy builder placeholder in the render cache.

  • πŸ‡ΊπŸ‡ΈUnited States mfb San Francisco

    No, these strings are only there for debugging purposes. The callable gets cached as serialized PHP.

  • last update over 1 year ago
    Custom Commands Failed
  • πŸ‡ΊπŸ‡ΈUnited States mfb San Francisco

    Here's a new method to provide this functionality (my initial thinking was it could live in PlaceholderGenerator, but it is also related to CallableResolver so, either way works for me)

  • last update over 1 year ago
    30,150 pass
  • πŸ‡ΊπŸ‡ΈUnited States mfb San Francisco

    Fix typos

  • Status changed to Needs work over 1 year ago
  • πŸ‡¬πŸ‡§United Kingdom joachim
    +++ b/core/lib/Drupal/Core/Render/PlaceholderGenerator.php
    @@ -113,7 +114,7 @@ public function createPlaceholder(array $element) {
    +    $callback = CallableResolver::getDebugStringFromDefinition($placeholder_render_array['#lazy_builder'][0]);
    

    The callable resolver should be obtained as a service from the container.

    > The callable gets cached as serialized PHP.

    Where's the code that does that? I can't see that in PlaceholderGenerator. Does it happen earlier than that?

  • πŸ‡ΊπŸ‡ΈUnited States mfb San Francisco

    Where's the code that does that? I can't see that in PlaceholderGenerator. Does it happen earlier than that?

    The render cache stores the whole array in the cache. And the cache backend (e.g. database backend) calls serialize() on that array. Thus a #lazy_builder callback can be an object method call, as long as the object can be serialized. And it cannot be a closure, which cannot be serialized.

  • πŸ‡ΊπŸ‡ΈUnited States mfb San Francisco

    The callable resolver should be obtained as a service from the container.

    Is there some reason this is necessary? I don't think this service is overridable - there is no interface for it. Also the service doesn't need to be instantiated - only a static method is called on it. But we could obtain the service if there's a reason to.

Production build 0.71.5 2024