4

PHP v8 supports constructor property promotion, which greatly uses boilerplate code. But basically all Drupal services use a factory pattern with a __construct() and a create() function.

Is the create() still necessary when using constructor property promotion? I've been searching in contrib modules for MR which use the new constructor property, and I've seen both, services with and without create function.

So given this YAML file

services:
  my_module.helper:
    class: Drupal\my_module\Helper
    arguments: ['@request_stack', '@entity_type.manager', '@config.factory', '@current_user', '@logger.factory']

and this service class constructor

  public function __construct(
    protected RequestStack $requestStack,
    protected EntityTypeManagerInterface $entityTypeManager,
    protected ConfigFactoryInterface $configFactory,
    protected AccountInterface $currentUser,
    protected LoggerChannelFactoryInterface $loggerFactory
  ) {}

do I still need a create($container) function (with all the redundant $container->get(...) calls inside it) at all?

If I don't need a create() function, can I somehow pass arguments to the other factories used inside my own service? E.g. loggers and configs are often used in my service, and I don't want to write

$this->configFactory->get('my_module)->get('foo');
$this->loggerFactory->get('my_module)->error('bar');

all the time. Can I somehow tell the logger and config factories to use my module name to write shorter code like

$this->config->get('foo');
$this->logger->error('bar');
6
  • Constructor property promotion is a syntax. It does not change object creation.
    – mona lisa
    Commented Feb 16 at 12:06
  • But there are contrib modules with service classes without create() function. What is the trick to avoid the redundant function create() { $container->get('service') } bloat?
    – Hudri
    Commented Feb 16 at 12:14
  • That’s a different question.
    – mona lisa
    Commented Feb 16 at 12:22
  • 1
    Services don't have a factory method create(). This is a Drupal-specific thing for plugins and controllers. However, Drupal can register a controller as a service, too, and by refactoring plugin code into services and adding auto-wiring you can avoid programming Drupal "with all the redundant code".
    – 4uk4
    Commented Feb 16 at 12:24
  • 1
    Yes, and you can use auto-wiring in Drupal. There is already a new service added to Drupal core 10.1 using it. See Drupal\field_ui\Access\FieldReuseAccessCheck as a perfect example of what is possible now.
    – 4uk4
    Commented Feb 16 at 13:36

1 Answer 1

4

See Drupal\field_ui\Access\FieldReuseAccessCheck:

field_ui.services.yml

services:
  access_check.field_ui.field_reuse:
    class: Drupal\field_ui\Access\FieldReuseAccessCheck
    autowire: true
    tags:
      - { name: access_check, applies_to: _field_ui_field_reuse_access }

/src/Access/FieldReuseAccessCheck.php

<?php

namespace Drupal\field_ui\Access;

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\field\FieldStorageConfigInterface;
use Symfony\Component\Routing\Route;

/**
 * Defines an access check for the reuse existing fields form.
 */
class FieldReuseAccessCheck implements AccessInterface {

  /**
   * Creates a new FieldReuseAccessCheck.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $fieldTypePluginManager
   *   The field type plugin manager.
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entityFieldManager
   *   The entity field manager.
   */
  public function __construct(
    protected EntityTypeManagerInterface $entityTypeManager,
    protected FieldTypePluginManagerInterface $fieldTypePluginManager,
    protected EntityFieldManagerInterface $entityFieldManager,
  ) {}

  /**
   * Checks access to the reuse existing fields form.
   *
   * @param \Symfony\Component\Routing\Route $route
   *   The route to check against.
   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
   *   The parametrized route.
   * @param \Drupal\Core\Session\AccountInterface $account
   *   The currently logged in account.
   * @param string|null $bundle
   *   (optional) The bundle. Different entity types can have different names
   *   for their bundle key, so if not specified on the route via a {bundle}
   *   parameter, the access checker determines the appropriate key name, and
   *   gets the value from the corresponding request attribute. For example, for
   *   nodes, the bundle key is "node_type", so the value would be available via
   *   the {node_type} parameter rather than a {bundle} parameter.
   *
   * @return \Drupal\Core\Access\AccessResultInterface
   *   The access result.
   */
  public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account, string $bundle = NULL): AccessResultInterface {
    $access = AccessResult::neutral();
    if ($entity_type_id = $route->getDefault('entity_type_id')) {
      if (empty($bundle)) {
        $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
        $bundle = $route_match->getRawParameter($entity_type->getBundleEntityType());
      }

      $field_types = $this->fieldTypePluginManager->getDefinitions();
      // Allows access if there are any existing fields and the user
      // correct permissions.
      foreach ($this->entityFieldManager->getFieldStorageDefinitions($entity_type_id) as $field_storage) {
        // Do not include fields with
        // - non-configurable field storages,
        // - locked field storages,
        // - field storages that should not be added via user interface,
        // - field storages that already have a field in the bundle.
        $field_type = $field_storage->getType();
        $access->addCacheableDependency($field_storage);
        if ($field_storage instanceof FieldStorageConfigInterface
          && !$field_storage->isLocked()
          && empty($field_types[$field_type]['no_ui'])
          && !in_array($bundle, $field_storage->getBundles(), TRUE)) {
          $permission = $route->getRequirement('_field_ui_field_reuse_access');
          $access = $access->orIf(AccessResult::allowedIfHasPermission($account, $permission));
        }
      }
      $access->addCacheableDependency($this->entityFieldManager);
    }
    return $access;
  }

}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.