Problem/Motivation

This broke my extension classes. ViewsBulkOperationsActionBase::executeMultiple() had its return type tightened. LLM helped me write the summary of the break.

// 4.4.5
public function executeMultiple(array $objects) { ... }

// 4.4.6+ (introduced in commit "#3589897: General cleanup", carried into 4.4.7)
public function executeMultiple(array $objects): array { ... }

Why it's a hard fatal, not a warning: my_module/src/Plugin/Action/DownloadActionBase.php extends ViewsBulkOperationsActionBase and overrides executeMultiple(array $entities) with no return type. PHP enforces return-type covariance between parent and child at class-declaration time, so as soon as this class is loaded, PHP throws:

Fatal error: Declaration of Drupal\my_module\Plugin\Action\DownloadActionBase::executeMultiple(array $entities)
must be compatible with Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase::executeMultiple(array $objects): array

Steps to reproduce

Proposed resolution

Remaining tasks

User interface changes

API changes

Data model changes

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

heddn created an issue. See original summary.

graber’s picture

Thank you for reporting, we should deprecate missing return type now and add it in the next major to the API.

Here’s a deprecated return type trait pattern:


namespace Drupal\your_module\Traits;

/**
 * Provides a helper to smoothly transition to strict PHP return types.
 */
trait ReturnTypeDeprecationTrait {

  /**
   * Tracks checked classes and methods to prevent performance-heavy reflection loops.
   *
   * @var array<string, array<string, bool>>
   */
  private static array $checkedReturnTypeSignatures = [];

  /**
   * Triggers a deprecation warning if a child class overrides a method without the proper return type.
   *
   * @param string $methodName
   *   The name of the method to inspect.
   * @param string $expectedType
   *   The expected native return type (e.g., 'array', 'string', 'int', or a FQCN).
   * @param string $version
   *   The future version when this type will become strictly enforced.
   */
  protected function deprecateMissingReturnType(string $methodName, string $expectedType, string $version = '2.0'): void {
    $currentClass = static::class;

    // Fast-return if this specific method on this specific class has already been verified.
    if (isset(self::$checkedReturnTypeSignatures[$currentClass][$methodName])) {
      return;
    }

    // Cache the check immediately so reflection runs exactly once per lifecycle.
    self::$checkedReturnTypeSignatures[$currentClass][$methodName] = true;

    try {
      $reflection = new \ReflectionMethod($this, $methodName);
      
      // Determine the base class where the trait is being used.
      $baseClass = $reflection->getPrototype()->getDeclaringClass()->getName();

      // If the method definition belongs to a child class, check its signature.
      if ($reflection->getDeclaringClass()->getName() !== $baseClass) {
        $returnType = $reflection->getReturnType();

        // Trigger deprecation if there is no type hint, or if it doesn't match the expected type.
        if ($returnType === null || $returnType->getName() !== $expectedType) {
          @trigger_error(
            sprintf(
              'Method "%s::%s()" will require a native "%s" return type declaration in v%s. Please update your child class definition to avoid fatal errors.',
              $currentClass,
              $methodName,
              $expectedType,
              $version
            ),
            E_USER_DEPRECATED
          );
        }
      }
    }
    catch (\ReflectionException $e) {
      // Fail silently if the method doesn't exist to prevent unexpected runtime crashes.
    }
  }

}

graber’s picture

Status: Active » Needs review

  • graber committed 36152e8a on 4.4.x
    #3612594 No return type deprecation.
    
graber’s picture

Status: Needs review » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.