nerdfisch: DevBits

Kleine, aber feine Code-Snippets, nützliche Tweaks und elegante Lösungsansätze aus dem Entwickler-Alltag

30.07.2026 | Lothar Ferreira Neumann

Merge multidimensional arrays in Drupal

nested_array_merge_deep.php
use Drupal\Component\Utility\NestedArray;

// Parent array with default settings.
$parent_array = [
    'config' => [
        'theme' => 'dark',
        'layout' => [
            'width' => 800,
            'height' => 600,
        ],
    ],
];

// Child array adds more nested data but doesn't override existing values.
$child_array = [
    'config' => [
        'layout' => [
            'depth' => 300, // New nested key
        ],
        'features' => [
            'animations' => true, // New section
        ],
    ],
];

// Merge both arrays.
$merged_array = NestedArray::mergeDeep($parent_array, $child_array);
nested_array_result.php
[
    'config' => [
        // Unchanged (not overridden)
        'theme' => 'dark', 
        'layout' => [
            // Keeps parent value
            'width' => 800, 
            // Keeps parent value  
            'height' => 600,
            // New nested key added
            'depth' => 300,   
        ],
        'features' => [
            // New section added
            'animations' => true, 
        ],
    ],
]
php
array manipulation
arrays
drupal
30.07.2026 | Michael Ebert

Formatting Dates programmatically with the Drupal date formatter service

Example for a single datetime field

my_single_datetime_field.php
use Drupal\Core\Datetime\DrupalDateTime;

/** @var Drupal\Core\Datetime\DateFormatterInterface $date_formatter */
$date_formatter = \Drupal::service('date.formatter');

// Get DateTime object from stored date from a DateTimeFieldItemList field.
$my_date = $node->field_my_date->date;

// Get unix timestamp.
$my_date_timestamp = $my_date->getTimestamp();

// For formatting your DateTime object all other arguments are optional:
// See DateFormatterInterface for built-in options, or use machine name of a date format in config.
$type = 'medium';
// Custom PHP date format if $type="custom".
$format = '';

$formatted = $date_formatter->format($my_date_timestamp, $type, $format, $timezone = NULL, $langcode = NULL);

 

Example for a daterange field

my_datetime_range_field.php
use Drupal\Core\Datetime\DrupalDateTime;

/** @var Drupal\Core\Datetime\DateFormatterInterface $date_formatter */
$date_formatter = \Drupal::service('date.formatter');

// Get DateTime objects from stored date from a DateTimeFieldItemList field.
$start_date = $node->field_my_date->start_date;
$end_date = $node->field_my_date->end_date;

// For formatting your DateTime object all other arguments are optional:
// See DateFormatterInterface for built-in options, or use machine name of a date format in config.
$type = 'medium';
// Custom PHP date format if $type="custom".
$format = '';

$start_date_formatted = $date_formatter->format($start_date, $type, $format, $timezone = NULL, $langcode = NULL);
$end_date_formatted = $date_formatter->format($end_date, $type, $format, $timezone = NULL, $langcode = NULL);
php
time and date
services
16.07.2026 | Dominik Wille

Controller decorator

RouteSubscriber.php
<?php

namespace Drupal\layout_builder_collapse_categories\Routing;

use Drupal\Core\Routing\RoutingEvents;
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;
use Drupal\layout_builder_collapse_categories\Controller\ChooseBlockController;

/**
 * Override the controller for layout_builder.move_block.
 */
class RouteSubscriber extends RouteSubscriberBase {

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    $events[RoutingEvents::ALTER] = ['onAlterRoutes', -1000];
    return $events;
  }

  /**
   * {@inheritdoc}
   */
  public function alterRoutes(RouteCollection $collection) {
    // Add a decorator for the choose_block conroller
    if ($route = $collection->get('layout_builder.choose_block')) {
      $defaults = $route->getDefaults();
      $route->setOption('original_controller', substr($defaults['_controller'], 0, strpos($defaults['_controller'], '::')));
      $defaults['_controller'] = ChooseBlockController::class . '::build';
      $route->setDefaults($defaults);
    }

  }
}
ChooseBlockController.php
<?php

namespace Drupal\layout_builder_collapse_categories\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\layout_builder\SectionStorageInterface;
use Drupal\Core\Controller\ControllerResolverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

class ChooseBlockController extends ControllerBase {


  /**
   * The original ChooseBlockController
   *
   * @var \Drupal\layout_builder\Controller\ChooseBlockController $originalController
   */
  protected $originalController;

  /**
   * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
   */
  public function __construct(ControllerResolverInterface $controller_resolver, RouteMatchInterface $route_match) {
    $original_controller_class = $route_match->getRouteObject()->getOption('original_controller');
    $this->originalController = $controller_resolver->getControllerFromDefinition($original_controller_class);
  }

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

  public function build(SectionStorageInterface $section_storage, $delta, $region) {
    $build = $this->originalController->build($section_storage, $delta, $region);

    foreach ($build['block_categories'] as &$category) {
      if (isset($category['#open'])) {
        $category['#open'] = FALSE;
      }
      
    }

    return $build;
  }

}
php
routing
routing
controller
decorators
route
16.07.2026 | Lothar Ferreira Neumann

How to translate the Maxlength Countdown message label globally

A Drupal backend screenshot showing the "Count down message" field

Leave this field empty to allow for translation via Drupal interface translation.


Example configuration created by enabling Maxlength:

core.entity_form_display.node.article_not_translatable.default.yml
# ...
content:
  field_copyright:
    type: text_textarea
    weight: 3
    region: content
    settings:
      rows: 2
      placeholder: ''
    third_party_settings:
      allowed_formats:
        hide_help: '1'
        hide_guidelines: '1'
      maxlength:
        maxlength_js: null
        maxlength_js_label: 'Content limited to @limit characters, remaining: <strong>@remaining</strong>'
        maxlength_js_enforce: false
# ...

However, the module already contains fallback logic for this label in maxlength.module: 

maxlength.module
$maxlength_js_label = !empty($thirdPartySettings['maxlength']['maxlength_js_label']) ? $thirdPartySettings['maxlength']['maxlength_js_label'] : t('Content limited to @limit characters, remaining: <strong>@remaining</strong>');
$maxlength_js = $thirdPartySettings['maxlength']['maxlength_js'];

Working configuration: empty the label

core.entity_form_display.node.article_translatable.default.yml
# ...
content:
  field_copyright:
    type: text_textarea
    weight: 3
    region: content
    settings:
      rows: 2
      placeholder: ''
    third_party_settings:
      allowed_formats:
        hide_help: '1'
        hide_guidelines: '1'
      maxlength:
        maxlength_js: null
        maxlength_js_label: ''
        maxlength_js_enforce: false
# ...

With the label empty, the module falls back to its internal t() string, which can then be translated normally via the interface translation system.

yml
module
maxlength
translation
09.07.2026 | Michael Ebert

Install comment_notify module to have an easy solution to subscribe/unsubscribe to nodes like a forum topic.

Installation with composer:

composer_require.sh
composer require 'drupal/comment_notify'

Enable with drush:

drush_pm_install.sh
drush pm:install comment_notify

Configure it under ../admin/config/people/comment_notify.
Select the Bundle on that you want to enable it. Chose "All Comments" under availible subscription modes and set it as default state. 
 

Comment Notify Configuration
sh
composer
notification