You are here

class RenderExampleController in Examples for Developers 3.x

Same name and namespace in other branches
  1. 8 render_example/src/Controller/RenderExampleController.php \Drupal\render_example\Controller\RenderExampleController

Provides module description page and examples of building render arrays.

Controllers that respond to a route should always return their content as a renderable array. See the arrays() method below as an example.

Hierarchy

Expanded class hierarchy of RenderExampleController

Related topics

File

modules/render_example/src/Controller/RenderExampleController.php, line 24

Namespace

Drupal\render_example\Controller
View source
class RenderExampleController extends ControllerBase implements TrustedCallbackInterface {
  use DescriptionTemplateTrait;

  /**
   * Constructs a new BlockController instance.
   *
   * @param \Drupal\Core\Session\AccountInterface $current_user
   *   The current user.
   */
  public function __construct(AccountInterface $current_user) {
    $this->currentUser = $current_user;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static($container
      ->get('current_user'));
  }

  /**
   * {@inheritdoc}
   */
  protected function getModuleName() {
    return 'render_example';
  }

  /**
   * Examples of defining content using renderable arrays.
   *
   * Methods on a controller that are the target of a route should return a
   * renderable array which contains any content to display for that route.
   */
  public function arrays() {

    // The core structure of the Render API is the render array, which is a
    // hierarchical associative array containing data to be rendered and
    // properties describing how the data should be rendered. Whenever a module
    // needs to output content it should do so be defining that content as a
    // renderable array. Below we'll look at some common examples of how render
    // arrays can be used to define content.
    $build = [];

    // CSS and JavaScript libraries can be attached to elements in a renderable
    // array. This way, if the element ends up being rendered and displayed you
    // know for sure the CSS/JavaScript will also be included. But, if for
    // some reason the element isn't ever rendered then Drupal can skip the
    // unnecessary extra files.
    //
    // Learn more about attaching CSS and JavaScript libraries with the
    // #attached property here:
    // https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21theme.api.php/group/theme_render/#sec_attached
    $build['#attached'] = [
      'library' => [
        'render_example/render-example.library',
      ],
    ];

    // Renderable arrays have two kinds of key/value pairs: properties and
    // children. Properties have keys starting with '#' and their values
    // influence how the array will be translated to a string. Children are all
    // elements whose keys do not start with a '#'. Their values should be
    // renderable arrays themselves.
    //
    // This example defines a new element, 'simple', that contains two
    // properties; '#markup' and '#description'. This is the quickest way to
    // output a string of HTML.
    $build['simple'] = [
      '#markup' => '<p>' . $this
        ->t('This page contains examples of various content elements described using render arrays. Read the code and comments in \\Drupal\\render_example\\Controller\\RenderExampleController::arrays() for more information.') . '</p>',
      '#description' => $this
        ->t('Example of using #markup'),
    ];

    // Additional properties can be used to further define the content. In this
    // case '#prefix' and '#suffix' are being used to provide strings to add
    // before, and after, the main content. This is useful because now the tag
    // being used to wrap the block of content can be easily changed without
    // having to worry about the content. Or, the tag can easily be left out
    // during rendering if for example the content is being output as JSON.
    //
    // There is a set of common properties that can be used for all elements in
    // a render array. These are defined by
    // \Drupal\Core\Render\Element\RenderElement. Most elements also have
    // additional element type specific properties.
    //
    // Figuring out what additional properties are available requires first
    // determining what sort of render element you're dealing with. Look for the
    // presence of one of these properties to start:
    // - #markup, or #plain_text: These are the simplest render arrays, and are
    //   used to display simple strings of text. In addition to the common set
    //   of properties available for all elements #markup elements can use the
    //   #allowed_tags property, an array of additional tags to allow when the
    //   HTML string is run through \Drupal\Component\Utility\Xss::filterAdmin()
    //   to strip out possible XSS vectors.
    // - #theme: The presence of #theme indicates that the array contains data
    //   to be themed by a particular theme hook. The available properties will
    //   depend on the specific theme hook. See the example below for more about
    //   determining what properties to use.
    // - #type: The presence of #type indicates that the array contains data and
    //   options for a particular type of "render element" (for example, 'form',
    //   'textfield', 'submit', for HTML form element types; 'table', for a
    //   table with rows, columns, and headers). The additional properties will
    //   depend on the render element type, and are documented on the class that
    //   defines the element type.
    //
    $build['simple_extras'] = [
      '#description' => $this
        ->t('Example of using #prefix and #suffix'),
      // Note the addition of '#type' => 'markup' in this example compared to
      // the one above. Because #markup is such a commonly used element type you
      // can exclude the '#type' => 'markup' line and it will be assumed
      // automatically if the '#markup' property is present.
      '#type' => 'markup',
      '#markup' => '<p>' . $this
        ->t('This one adds a prefix and suffix, which put a blockqoute tag around the item.') . '</p>',
      '#prefix' => '<blockquote>',
      '#suffix' => '</blockquote>',
    ];

    // In addition to #markup, you can also use #plain_text to output, you
    // guessed it, strings of plain text. This indicates that the array contains
    // text which should be escaped before it is displayed.
    $build['simple_text'] = [
      '#plain_text' => '<em>This is escaped</em>',
      '#description' => $this
        ->t('Example of using #plain_text'),
    ];

    // Using the '#theme' property for an element specifies that the array
    // contains data to be themed by a particular theme hook. Essentially using
    // a Twig template to generate the HTML for an element. Modules define theme
    // hooks by implementing hook_theme(), which specifies the input "variables"
    // used to provide data and options; if a hook_theme() implementation
    // specifies variable 'separator', then in a render array, you would provide
    // this data using the '#separator' property.
    //
    // @see hook_theme()
    $build['theme_element'] = [
      // The '#theme' property can be set to any valid theme hook. For more
      // information about theme hooks, and to discover available theme hooks
      // that you can use when creating render arrays see the documentation for
      // hook_theme().
      //
      // Many of the most commonly used theme hooks are defined in
      // drupal_common_theme().
      '#theme' => 'item_list',
      '#title' => $this
        ->t('Example of using #theme'),
      // The #items property is specific to the 'item_list' theme hook, and
      // corresponds to the variable {{ items }} in the item-list.twig.html
      // template file.
      '#items' => [
        $this
          ->t('This is an item in the list'),
        $this
          ->t('This is some more text that we need in the list'),
      ],
    ];

    // Using the '#type' property for an element specifies that the array
    // contains data and options for a particular type of "render element".
    // Render element types can be thought of as prepackaged render arrays that
    // provide default values for a set of properties as well as code that
    // will perform additional processing on the array before it is rendered.
    //
    // As an example take a look at the code in
    // \Drupal\Core\Render\Element\Table::getInfo(). Notice that it is defining
    // values for #theme, and #process? These values will be merged with
    // whatever properties you define in your code.
    //
    // In addition, most render element types have type specific properties. A
    // table for example has #header, and #rows properties. The easiest way to
    // determine what element type specific properties exist is to read the
    // documentation for the class that defines the element type. Don't forget
    // that it will also inherit properties used by any class it is extending.
    //
    // There are two types of render element types:
    // - Generic elements: Generic render element types encapsulate logic for
    //   generating HTML and attaching relevant CSS and JavaScript to the page.
    //   These include things like link, table, and drop button elements.
    // - Form elements: Most of the render element types provided by core
    //   represent the various widgets you might use on a form. Text fields,
    //   password fields, and file upload buttons for example. These elements
    //   are intended to be used in conjunction with a form controller class
    //   and have additional properties such as `#required`, and
    //   `#element_validate`, related to their use as part of a form. For more
    //   on form elements check out the fapi_example module.
    $build['table'] = [
      // The value used for #type is the ID of the plugin that implements the
      // element type you want to use. This can be inferred from the annotation
      // for the element.
      // You can also find a list of element types provided by Drupal core here
      // https://api.drupal.org/api/drupal/elements.
      '#type' => 'table',
      '#caption' => $this
        ->t('Our favorite colors.'),
      '#header' => [
        $this
          ->t('Name'),
        $this
          ->t('Favorite color'),
      ],
      '#rows' => [
        [
          $this
            ->t('Amber'),
          $this
            ->t('teal'),
        ],
        [
          $this
            ->t('Addi'),
          $this
            ->t('green'),
        ],
        [
          $this
            ->t('Blake'),
          $this
            ->t('#063'),
        ],
        [
          $this
            ->t('Enid'),
          $this
            ->t('indigo'),
        ],
        [
          $this
            ->t('Joe'),
          $this
            ->t('green'),
        ],
      ],
      '#description' => $this
        ->t('Example of using #type.'),
    ];

    // Render arrays can be nested any level deep. This allows you to group
    // like things together. A great example of this is the $page array used in
    // conjunction with the page.html.twig template. The top level contains all
    // the regions, each of which contain the blocks placed in that region,
    // which in turn contain their own content. In fact, when this array is
    // ultimately displayed on a page it will be as part of the $page array.
    $build['nested_example'] = [
      '#description' => $this
        ->t('Example of nesting elements'),
      '#markup' => '<p>' . $this
        ->t('Render arrays can contain any number of nested elements. During rendering, the innermost elements are rendered first, and their output is incorporated into the parent element.') . '</p>',
      'nested_child_element' => [
        // An un-ordered list of links.
        // See /core/modules/system/templates/item-list.html.twig.
        '#theme' => 'item_list',
        '#title' => $this
          ->t('Links'),
        '#list_type' => 'ol',
        '#items' => [
          Link::fromTextAndUrl($this
            ->t('Drupal'), Url::fromUri('https://www.drupal.org')),
          Link::fromTextAndUrl($this
            ->t('Not Drupal'), Url::fromUri('https://wordpress.org/')),
        ],
      ],
    ];

    // Example of adding a link using the #link element type.
    $build['nested_example']['another_nested_child'] = [
      // See \Drupal\Core\Render\Element\Link.
      '#type' => 'link',
      '#title' => $this
        ->t('A link to example.com'),
      '#url' => Url::fromUri('https://example.com'),
    ];

    // The #theme_wrappers property can be used to provide an array of theme
    // hooks which provide the envelope or "wrapper" of a set of child elements.
    // The theme function finds its element children (the sub-arrays) already
    // rendered in '#children'.
    $build['theme_wrappers demonstration'] = [
      '#description' => $this
        ->t('Example of using #theme_wrappers'),
      'child1' => [
        '#markup' => $this
          ->t('Markup for child1'),
      ],
      'child2' => [
        '#markup' => $this
          ->t('Markup for child2'),
      ],
      '#theme_wrappers' => [
        'render_example_add_div',
      ],
    ];

    // Use the #access property to control who can see what content. If an
    // element in an render array has its #access property set to FALSE it will
    // be removed from the array before rendering. And thus not visible.
    $build['access_example'] = [
      '#description' => $this
        ->t('Example of using #access to control visibility'),
      '#markup' => $this
        ->t('This text is only visible to authenticated users.'),
      '#access' => $this->currentUser
        ->isAuthenticated(),
    ];

    // Some properties define callbacks, which are callable functions or methods
    // that are triggered at specific points during the rendering pipeline.
    $build['pre_render_and_post_render'] = [
      '#description' => $this
        ->t('Example of using #pre_render and #post_render'),
      '#markup' => '<div style="color:green">' . $this
        ->t('markup for pre_render and post_render example') . '</div>',
      // #pre_render callbacks are triggered early in the rendering process,
      // they get access to the element in the array where the callback is
      // named, and all of its children. They can be used to do things like
      // conditionally alter the value of a property prior to the array being
      // rendered to HTML.
      '#pre_render' => [
        static::class . '::preRenderAddSuffix',
      ],
      // #post_render callbacks are triggered after the array has been rendered
      // and can operate on the rendered HTML. They also have access to the
      // original array for context.
      '#post_render' => [
        static::class . '::postRenderAddPrefix',
      ],
    ];

    // Properties that contain callbacks can also reference methods on a class
    // in addition to functions. See
    // \Drupal\render_example\Controller\RenderExampleController::preRender()
    // @todo: This doesn't work, we need to fix it.
    // https://www.drupal.org/project/examples/issues/2986435
    // $build['#pre_render'] = [static::class, 'preRender'];.
    // Caching is an important part of the Render API, converting an array to a
    // string of HTML can be an expensive process, and therefore whenever
    // possible the Render API will cache the results of rendering an array in
    // order to improve performance.
    //
    // When defining a render array you should use the #cache property to define
    // the cachability of an element.
    $build['cache_demonstration'] = [
      '#description' => $this
        ->t('#cache demonstration'),
      // This string contains information that is specific to the user who is
      // currently viewing the page. We can cache it, and re-use the string any
      // time the same user views the page again. However, if the user changes,
      // or if the user changes their name, we need to expire the cached data
      // and rebuild it so that it is accurate.
      '#markup' => $this
        ->t('Hello @name, welcome to the #cache example.', [
        '@name' => $this->currentUser
          ->getAccountName(),
      ]),
      // The #cache property is used to provide metadata about the element being
      // cached, and the conditions under which it should be expired. This can
      // be time based, or context based. You can read more about caching
      // render arrays here
      // https://www.drupal.org/docs/8/api/render-api/cacheability-of-render-arrays
      '#cache' => [
        // The "current user" is used above, which depends on the request, so
        // we tell Drupal to vary by the 'user' cache context.
        'contexts' => [
          'user',
        ],
      ],
    ];

    // A #lazy_builder callback can be used to build a highly dynamic section of
    // a render array from scratch. This, combined with the use of placeholders,
    // allows the renderer to cache some, but not all, portions of a render
    // array. Without #lazy_builders, if any element in the render tree is
    // uncacheable the whole tree would need to be re-rendered every time.
    //
    // The general rendering flow is as follows:
    // - Check for cached version of output from previous rendering, if it
    //   exists replace any placeholders in the rendered output with their
    //   dynamic content as generated by the #lazy_builder callback, and return
    //   the resulting HTML.
    // - If no cached version exists render the array to HTML, when an element
    //   that can be placeholdered is encountered insert a placeholder, cache
    //   the HTML after rendering for next time, replace the placeholders with
    //   their dynamic content, and return the resulting HTML.
    //
    // This is especially noticeable when used in conjunction with modules like
    // Big Pipe which do rendering of a page in multiple passes vs. the default
    // single flush renderer.
    //
    // See \Drupal\block\BlockViewBuilder::viewMultiple() for an example from
    // core.
    $build['lazy_builder'] = [
      // Set the value of the #lazy_builder property to an array, the first key
      // of the array is the method, service, or function, to call in oder to
      // generate the dynamic data. The second argument is an array of any
      // arguments to pass to the callback. Arguments can be only primitive
      // types (string, bool, int, float, NULL).
      '#lazy_builder' => [
        static::class . '::lazyBuilder',
        [
          $this->currentUser
            ->id(),
          'Y-m-d',
        ],
      ],
      // #lazy_builder callbacks can be used in conjunction with
      // #create_placeholder to tell the renderer that instead of simply calling
      // the #lazy_builder code right away, to instead insert a placeholder and
      // delay execution of the #lazy_builder code until it's needed.
      //
      // This is somewhat analogous to the way Drupal uses the PSR-4 autoloading
      // standard to "lazy" load PHP files that contain the definition of a
      // class only if, and when, that class is used.
      //
      // To force a element to use a placeholder set #create_placeholder to
      // TRUE.
      //
      // Alternatively you could include #cache metadata (see above) and allow
      // the Render API to use that metadata to automatically determine based on
      // the existence of high-cardinality cache contexts in the subtree whether
      // or not the element should use a placeholder.
      '#create_placeholder' => TRUE,
    ];

    // Example of the marquee element type defined by
    // \Drupal\render_example\Element\Marquee.
    $build['marquee'] = [
      '#description' => $this
        ->t('Example custom element type'),
      '#type' => 'markup',
      'marquee_element' => [
        '#type' => 'marquee',
        '#content' => $this
          ->t('Hello world!'),
      ],
    ];
    $output = [];

    // We are going to create a new output render array that pairs each
    // example with a set of helper render arrays. These are used to display
    // the description as a title and the unrendered content alongside the
    // examples.
    foreach (Element::children($build) as $key) {
      if (isset($build[$key])) {
        $output[$key] = [
          '#theme' => 'render_array',
          'description' => [
            '#type' => 'markup',
            '#markup' => $build[$key]['#description'] ?? '',
          ],
          'rendered' => $build[$key],
          'unrendered' => [
            '#type' => 'markup',
            '#markup' => htmlentities(Variable::export($build[$key])),
          ],
        ];
      }
    }
    foreach (Element::properties($build) as $key) {
      $output[$key] = $build[$key];
    }
    return $output;
  }

  /**
   * A #pre_render callback, expand array to include additional example info.
   *
   * This method is called during the process of rendering the array generated
   * by \Drupal\render_example\Controller\RenderExampleController::arrays().
   *
   * This also demonstrates how a #pre_render callback could be used to expand
   * a relatively simple array into multiple individual renderable elements
   * based on application logic.
   *
   * @param array $element
   *   Pre render methods (and functions) get a single argument that is the
   *   render API array representing the element where the #pre_render property
   *   was defined, and all of it's children.
   *
   * @return array
   *   Pre render methods (and functions) should return the modified render
   *   array.
   */
  public static function preRender(array $element) {

    // For each first level child element lets add some additional helpful
    // output. \Drupal\Core\Render\Element::children() is a utility method that
    // allows you to quickly identify all children of a render array. That is
    // those key/value pairs whose key does not start with a '#'.
    foreach (Element::children($element) as $key) {
      $child = $element[$key];
      unset($element[$key]);
      if (isset($child['#description'])) {
        $element[$key] = [
          // The value from the #description property will be used as a title
          // for this element in the final output.
          'description' => [
            '#markup' => $child['#description'],
          ],
          // Move the original element to 'rendered'. The rendering process is
          // recursive so this will still be located, and rendered to HTML.
          'rendered' => $child,
          // Export the element definition as a string of text so we can display
          // the array that was used to create the rendered output just below
          // the output.
          'unrendered' => [
            '#markup' => htmlentities(Variable::export($child)),
          ],
          '#theme' => 'render_array',
        ];
      }
    }

    // Return our modified version of the original $element.
    return $element;
  }

  /**
   * Example #lazy_builder callback.
   *
   * Demonstrates the use of a #lazy_builder callback to build out a render
   * array that can be substituted into the parent array wherever the cacheable
   * placeholder exists.
   *
   * This method is called during the process of rendering the array generated
   * by \Drupal\render_example\Controller\RenderExampleController::arrays().
   *
   * @param string $date_format
   *   Date format to use with \Drupal\Core\Datetime\DateFormatter::format().
   *
   * @return array
   *   A renderable array with content to replace the #lazy_builder placeholder.
   */
  public static function lazyBuilder($date_format) {
    $build = [
      'lazy_builder_time' => [
        '#markup' => '<p>' . \Drupal::translation()
          ->translate('The current time is @time', [
          '@time' => \Drupal::service('date.formatter')
            ->format(REQUEST_TIME, 'long'),
        ]) . '</p>',
      ],
    ];
    return $build;
  }

  /**
   * Example '#post_render' callback function.
   *
   * Post render callbacks are triggered after an element has been rendered to
   * HTML and can act upon the final rendered string.
   *
   * This function is used as a post render callback in
   * Drupal\render_example\Controller\RenderExampleController::arrays().
   *
   * @param string $markup
   *   The rendered element.
   * @param array $element
   *   The element which was rendered (for reference)
   *
   * @return string
   *   Markup altered as necessary. In this case we add a little postscript.
   *
   * @see \Drupal\render_example\Controller\RenderExampleController::arrays()
   */
  public static function postRenderAddPrefix($markup, array $element) {
    $markup .= '<div style="color:blue">This markup was added after rendering by a #post_render callback.</div>';
    return $markup;
  }

  /**
   * Example '#pre_render' function.
   *
   * Pre render callbacks are triggered prior to rendering an element to HTML
   * and are given the chance to manipulate the renderable array. Any changes
   * they make will be reflected in the final rendered HTML.
   *
   * We need to wrap suffix in a Markup object.
   * Otherwise, style attribute will be removed by Xss
   * @see \Drupal\Component\Utility\Xss::filter()
   *
   * This function is used as a post render callback in
   * \Drupal\render_example\Controller\RenderExampleController::arrays().
   *
   * @param array $element
   *   The element which will be rendered.
   *
   * @return array
   *   The altered element. In this case we add a #prefix to it.
   *
   * @see \Drupal\render_example\Controller\RenderExampleController::arrays()
   */
  public static function preRenderAddSuffix(array $element) {
    $element['#suffix'] = Markup::create('<div style="color:red">' . t('This #suffix was added by a #pre_render callback.') . '</div>');
    return $element;
  }

  /**
   * {@inheritDoc}
   */
  public static function trustedCallbacks() {
    return [
      'postRenderAddPrefix',
      'preRenderAddSuffix',
      'lazyBuilder',
    ];
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ControllerBase::$configFactory protected property The configuration factory.
ControllerBase::$currentUser protected property The current user service. 1
ControllerBase::$entityFormBuilder protected property The entity form builder.
ControllerBase::$entityTypeManager protected property The entity type manager.
ControllerBase::$formBuilder protected property The form builder. 2
ControllerBase::$keyValue protected property The key-value storage. 1
ControllerBase::$languageManager protected property The language manager. 1
ControllerBase::$moduleHandler protected property The module handler. 2
ControllerBase::$stateService protected property The state service.
ControllerBase::cache protected function Returns the requested cache bin.
ControllerBase::config protected function Retrieves a configuration object.
ControllerBase::container private function Returns the service container.
ControllerBase::currentUser protected function Returns the current user. 1
ControllerBase::entityFormBuilder protected function Retrieves the entity form builder.
ControllerBase::entityTypeManager protected function Retrieves the entity type manager.
ControllerBase::formBuilder protected function Returns the form builder service. 2
ControllerBase::keyValue protected function Returns a key/value storage collection. 1
ControllerBase::languageManager protected function Returns the language manager service. 1
ControllerBase::moduleHandler protected function Returns the module handler. 2
ControllerBase::redirect protected function Returns a redirect response object for the specified route.
ControllerBase::state protected function Returns the state storage service.
DescriptionTemplateTrait::description public function Generate a render array with our templated content.
DescriptionTemplateTrait::getDescriptionTemplatePath protected function Get full path to the template.
DescriptionTemplateTrait::getDescriptionVariables protected function Variables to act as context to the twig template file. 1
LoggerChannelTrait::$loggerFactory protected property The logger channel factory service.
LoggerChannelTrait::getLogger protected function Gets the logger for a specific channel.
LoggerChannelTrait::setLoggerFactory public function Injects the logger channel factory.
MessengerTrait::$messenger protected property The messenger. 27
MessengerTrait::messenger public function Gets the messenger. 27
MessengerTrait::setMessenger public function Sets the messenger.
RedirectDestinationTrait::$redirectDestination protected property The redirect destination service. 1
RedirectDestinationTrait::getDestinationArray protected function Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url.
RedirectDestinationTrait::getRedirectDestination protected function Returns the redirect destination service.
RedirectDestinationTrait::setRedirectDestination public function Sets the redirect destination service.
RenderExampleController::arrays public function Examples of defining content using renderable arrays.
RenderExampleController::create public static function Instantiates a new instance of this class. Overrides ControllerBase::create
RenderExampleController::getModuleName protected function Name of our module. Overrides DescriptionTemplateTrait::getModuleName
RenderExampleController::lazyBuilder public static function Example #lazy_builder callback.
RenderExampleController::postRenderAddPrefix public static function Example '#post_render' callback function.
RenderExampleController::preRender public static function A #pre_render callback, expand array to include additional example info.
RenderExampleController::preRenderAddSuffix public static function Example '#pre_render' function.
RenderExampleController::trustedCallbacks public static function Lists the trusted callbacks provided by the implementing class. Overrides TrustedCallbackInterface::trustedCallbacks
RenderExampleController::__construct public function Constructs a new BlockController instance.
StringTranslationTrait::$stringTranslation protected property The string translation service. 4
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
TrustedCallbackInterface::THROW_EXCEPTION constant Untrusted callbacks throw exceptions.
TrustedCallbackInterface::TRIGGER_SILENCED_DEPRECATION constant Untrusted callbacks trigger silenced E_USER_DEPRECATION errors.
TrustedCallbackInterface::TRIGGER_WARNING constant Untrusted callbacks trigger E_USER_WARNING errors.