php

RouteSubscriber.php
<?php

namespace Drupal\scp_base\Routing;

use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;

class RouteSubscriber extends RouteSubscriberBase {

  /**
   * {@inheritdoc}
   */
  protected function alterRoutes(RouteCollection $collection) {
    // Make sure profile pages are not admin pages, in order to load the right
    // translation.
    if ($route = $collection->get('profile.user_page.single')) {
      $route->setOption('_admin_route', FALSE);
    }
  }

}
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;
  }

}
16.07.2026 | Dominik Wille

Controller decorator

If you want to extend a class with static methods, don't use self:: since it will scope the static method to the extended class.

FooElement.php
// $options = self::getPluginOptions($element)
$options = static::getPluginOptions($element)
10.03.2023 | Fabian Alleblas, Dominik Wille

Replace self with static: Just do it, trust me.

ComponentPluginManager.php
    // Check if the component plugin instance was already created.
    if (isset($this->componentPluginCacheEntries[$machine_name])) {
      return $this->componentPluginCacheEntries[$machine_name];
    }

    $definitions = $this->getDefinitions();
    if (empty($definitions)) {
      throw new ComponentNotFoundException('Unable to find any component definition.');
    }
    $instance = $this->createInstance(
      $this->componentNegotiator->negotiate($machine_name, $definitions)
    );

    // Cache the component plugin instance.
    $this->componentPluginCacheEntries[$machine_name] = $instance;
27.01.2023 | Dominik Wille, Peter Gerken, Pascal Crott

How to static cache in symfony services

SampleEntityProvider.php
<?php

namespace Drupal\MY_MODULE;

use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Psr\Log\LoggerInterface;

/**
 * A service that generates a sample entity with populated fields.
 */
class SampleEntityProvider extends ServiceProviderBase {

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The logger service.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected $logger;

  /**
   * Creates a SampleEntityProvider object.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Psr\Log\LoggerInterface $logger
   *   A logger instance.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, LoggerInterface $logger) {
    $this->entityTypeManager = $entity_type_manager;
    $this->logger = $logger;
  }

  /**
   * Gets a sample entity with populated fields.
   *
   * @param string $entity_type_id
   *   The entity type id the samle entity should have.
   * @param string $entity_bundle
   *   The bundle the sample entity should have.
   *
   * @return \Drupal\Core\Entity\EntityInterface|void $entity
   *   The generated sample entity.
   */
  public function getSampleEntity(string $entity_type_id, string $entity_bundle) {
    try {
      $entity_storge = $this->entityTypeManager->getStorage($entity_type_id);
      $bundle_key = $entity_storge->getEntityType()->getKey('bundle');

      $sample_entity = $entity_storge->create([$bundle_key => $entity_bundle]);
      $this->populateFields($sample_entity);

      return $sample_entity;
    } catch (\Exception $e) {
      $this->logger->error($e->getMessage());
    }
  }

  /**
   * Populate the fields on a given entity with sample values.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity to be enriched with sample field values.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function populateFields(EntityInterface $entity) {
    $properties = [
      'entity_type' => $entity->getEntityType()->id(),
      'bundle' => $entity->bundle(),
    ];
    $field_config_storage = \Drupal::entityTypeManager()->getStorage('field_config');
    /** @var \Drupal\field\FieldConfigInterface[] $instances */
    $instances = $field_config_storage->loadByProperties($properties);

    foreach ($instances as $instance) {
      $field_storage = $instance->getFieldStorageDefinition();
      $max = $cardinality = $field_storage->getCardinality();
      if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
        // Just an arbitrary number for 'unlimited'.
        $max = rand(1, 3);
      }
      $field_name = $field_storage->getName();
      $entity->$field_name->generateSampleItems($max);
    }
  }
}

DomainRouteProvider.php
<?php

namespace Drupal\poormans_domain\Routing;

use Drupal\Core\Routing\RouteProvider;
use Symfony\Component\HttpFoundation\Request;

/**
 * Custom router.route_provider service to make it domain context sensitive.
 */
class DomainRouteProvider extends RouteProvider {

  /**
   * {@inheritdoc}
   */
  protected function getRouteCollectionCacheId(Request $request) {
    // Add domain sensitive cache information to the cid.
    $this->addExtraCacheKeyPart('poormans_domain', $request->getHost());
    return parent::getRouteCollectionCacheId($request);
  }

}

poormans_domain.services.yml
services:
  poormans_domain.route_provider:
    class: Drupal\poormans_domain\Routing\DomainRouteProvider
    decorates: router.route_provider
    decoration_priority: 10
    arguments: ['@database', '@state', '@path.current', '@cache.data', '@path_processor_manager', '@cache_tags.invalidator', 'router', '@language_manager']

MetaCspSubscriber.php
/**
 * Alter CSP policy for meta.
 */
class MetaCspSubscriber implements EventSubscriberInterface {

  /**
   * @var \Drupal\meta_atipixel\MetaAtiCode
   */
  protected $metaAtiCode;

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    $events[CspEvents::POLICY_ALTER] = ['onCspPolicyAlter'];
    return $events;
  }

  /**
   * MetaCspSubscriber constructor.
   *
   * @param\Drupal\meta_atipixel\MetaAtiCode $meta_ati_code
   *   The ati code service.
   */
  public function __construct(MetaAtiCode $meta_ati_code) {
    $this->metaAtiCode = $meta_ati_code;
  }

  /**
   * Add hashed and nonces if we are not on unsafe-inline anyway.
   *
   * @param \Drupal\csp\Event\PolicyAlterEvent $alterEvent
   *   The Policy Alter event.
   */
  public function onCspPolicyAlter(PolicyAlterEvent $alterEvent) {
    $policy = $alterEvent->getPolicy();
    $nonce = $this->metaAtiCode->getNonce();

    if (!in_array("'unsafe-inline'", $policy->getDirective('script-src'))) {
      $additions = [
        // scripts in html.html.twig
        "'sha256-u9HSg2Pp8ssH/rPdIjXPA2C4w25lvarx61zTFRKUrmM='",
        "'sha256-MpSPVM+F08E973/LoTypmKAw/3M85wXWX6kWfoTFytc='",
        "'sha256-LP15v8l8fhV/n/S6XgVVfXn7GJIVJ3hoL8UIw4BYxsE='",
        // onClicks in page.html.twig
        "'sha256-UvdkyVIodw5ho+TLRpWSJlSwRybZxclpp+OSCf7w67I='",
        "'sha256-GGza1Y9dREBYst5ReIQ6hgev0+2sE9tMsbcJ29qs4w4='",
        "'sha256-O5E6+QtFv/CBsWdAYZvTiTYiHXAA7iTst1rBecCv6dI='",
        "'sha256-hIoyxe70kL8INnzt1lpp3M9bfkqfZmnY0SXJrA5LKoA='",
        "'sha256-IVlAoFKs/eP8PtbJup9f6kWE26ZguV+Cmqs6+98t3EE='",
        "'sha256-JmPJi7h6GEv+mUBBxVrImXEvDVkqcCK/xncob2D/3JA='",
        // Ati pixel nonce
        "'nonce-$nonce'"
      ];
      $policy->fallbackAwareAppendIfEnabled('script-src', $additions);
    }
  }

}
10.08.2022 | Dominik Wille

MetaCspSubscriber.php

Easy clean up cron functionality using DB Statements. Should be used with custom tables, which brought their own schema. See: https://mariadb.com/kb/en/date-and-time-units/

 

example.php
/**
 * Implements hook_cron().
 */
 function your_module_cron() {
     \Drupal::database()
        ->query("DELETE FROM example_custom_table WHERE created < UNIX_TIMESTAMP(NOW() - INTERVAL 6 MONTH)")
        ->execute();
 }
 
29.06.2022 | Nikolas Kopp

Cleanup Cron

Related issue: https://www.drupal.org/project/drupal/issues/2408549

SMTPConfigForm.php
  /**
   * Check if config variable is overridden by the settings.php.
   *
   * @param string $name
   *   SMTP settings key.
   *
   * @return bool
   *   Boolean.
   */
  protected function isOverridden($name) {
    $original = $this->configFactory->getEditable('smtp.settings')->get($name);
    $current = $this->configFactory->get('smtp.settings')->get($name);
    return $original != $current;
  }
10.08.2022 | Dominik Wille

SMTPConfigForm.php