diff --git a/CHANGELOG.md b/CHANGELOG.md index 74f44f03..7b44e65e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,19 @@ release-by-release. ## [Unreleased] +### Fixed + +- **`RemoveAliasManagerCacheMethodCallsRector`** — no longer removes calls to + `AliasManager::setCacheKey()` / `writeCache()` unconditionally, which broke + backward compatibility on Drupal < 11.3. These methods only became no-ops in + 11.3.0 (when the path alias preload cache was replaced by a Fiber-based + bulk-lookup strategy); before 11.3.0 they performed real caching work. The + rector now extends `AbstractDrupalCoreRector` and, when BC support is enabled, + wraps the call in a `DeprecationHelper::backwardsCompatibleCall()` with a + no-op current callable so the caching still runs on Drupal < 11.3 and is + skipped on 11.3+. When BC support is disabled the call is removed as before. + Reported by Berdir ([#3600789](https://git.drupalcode.org/project/rector/-/work_items/3600789)). + ### Added - **`AddSymfonyConstraintValidatorTypeDeclarationsRector`** — adds the Symfony 8 / Drupal 12 type declarations (`mixed $value` and `: void` on `validate()`, `: void` on `initialize()`) to `Symfony\Component\Validator\ConstraintValidatorInterface` implementers. Backward compatible on all supported Drupal versions, so no version gate. [#3600790] diff --git a/config/drupal-11/drupal-11.3-deprecations.php b/config/drupal-11/drupal-11.3-deprecations.php index 04771132..253a6451 100644 --- a/config/drupal-11/drupal-11.3-deprecations.php +++ b/config/drupal-11/drupal-11.3-deprecations.php @@ -243,8 +243,12 @@ // https://www.drupal.org/node/3496369 // https://www.drupal.org/node/3532412 (change record) // AliasManager::setCacheKey() and AliasManager::writeCache() deprecated in drupal:11.3.0, - // removed in drupal:13.0.0 with no replacement (they are no-ops). - $rectorConfig->rule(RemoveAliasManagerCacheMethodCallsRector::class); + // removed in drupal:13.0.0 with no replacement. They only became no-ops in + // 11.3.0; before that they performed real caching work, so the call is + // wrapped in a backwards-compatible no-op when BC support is enabled. + $rectorConfig->ruleWithConfiguration(RemoveAliasManagerCacheMethodCallsRector::class, [ + new DrupalIntroducedVersionConfiguration('11.3.0'), + ]); // https://www.drupal.org/node/3525388 // https://www.drupal.org/node/3525389 (change record) diff --git a/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector.php b/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector.php index d37c87ee..7ac6fd6f 100644 --- a/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector.php +++ b/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector.php @@ -4,27 +4,37 @@ namespace DrupalRector\Drupal11\Rector\Deprecation; +use DrupalRector\Contract\VersionedConfigurationInterface; +use DrupalRector\Rector\AbstractDrupalCoreRector; +use DrupalRector\Rector\ValueObject\DrupalIntroducedVersionConfiguration; use PhpParser\Node; +use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\MethodCall; +use PhpParser\Node\Name; use PhpParser\Node\Stmt\Expression; use PhpParser\NodeVisitor; use PHPStan\Type\ObjectType; -use Rector\Rector\AbstractRector; -use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; +use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** * Removes calls to AliasManager::setCacheKey() and AliasManager::writeCache(). * * Both methods are deprecated in drupal:11.3.0 and removed in drupal:13.0.0 - * with no replacement. They became no-ops when the path alias preload cache - * was replaced by a Fiber-based bulk-lookup strategy, so callers can simply - * drop the call. + * with no replacement. They only became no-ops in 11.3.0, when the path alias + * preload cache was replaced by a Fiber-based bulk-lookup strategy; before + * 11.3.0 they performed real caching work. Removing the call outright is + * therefore only safe when the code no longer supports Drupal < 11.3. + * + * When backwards compatibility is enabled the call is wrapped in a + * DeprecationHelper::backwardsCompatibleCall() with a no-op current callable, + * so the caching still runs on Drupal < 11.3 and is skipped on 11.3+. When + * backwards compatibility is disabled the call is removed. * * @see https://www.drupal.org/node/3496369 * @see https://www.drupal.org/node/3532412 */ -class RemoveAliasManagerCacheMethodCallsRector extends AbstractRector +final class RemoveAliasManagerCacheMethodCallsRector extends AbstractDrupalCoreRector { public const PHPSTAN_MESSAGES = [ 'Call to deprecated method setCacheKey() of class Drupal\path_alias\AliasManager. Deprecated in drupal:11.3.0 and is removed from drupal:13.0.0. There is no replacement.', @@ -33,14 +43,31 @@ class RemoveAliasManagerCacheMethodCallsRector extends AbstractRector private const TARGET_METHODS = ['setCacheKey', 'writeCache']; + /** + * @var array|DrupalIntroducedVersionConfiguration[] + */ + protected array $configuration = []; + + public function configure(array $configuration): void + { + foreach ($configuration as $value) { + if (!$value instanceof DrupalIntroducedVersionConfiguration) { + throw new \InvalidArgumentException(sprintf('Each configuration item must be an instance of "%s"', DrupalIntroducedVersionConfiguration::class)); + } + } + + parent::configure($configuration); + } + public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( 'Remove calls to AliasManager::setCacheKey() and AliasManager::writeCache(), deprecated in drupal:11.3.0 and removed in drupal:13.0.0 with no replacement.', [ - new CodeSample( + new ConfiguredCodeSample( '$this->aliasManager->setCacheKey($path);', - '' + '', + [new DrupalIntroducedVersionConfiguration('11.3.0')] ), ] ); @@ -52,7 +79,7 @@ public function getNodeTypes(): array return [Expression::class]; } - public function refactor(Node $node): ?int + public function refactorWithConfiguration(Node $node, VersionedConfigurationInterface $configuration): int|Node|null { assert($node instanceof Expression); if (!$node->expr instanceof MethodCall) { @@ -71,6 +98,17 @@ public function refactor(Node $node): ?int return null; } + // The shared createBcCallOnExpr() helper cannot be reached through the + // parent's Expr → Expr path here: the matched node is a whole statement + // and the "current" (11.3+) behaviour is to do nothing, which has no + // replacement expression. Build the wrapper ourselves, using a no-op + // current callable and the original call as the deprecated callable. + if ($this->supportBackwardsCompatibility($configuration)) { + $noOp = new ConstFetch(new Name('null')); + + return new Expression($this->createBcCallOnExpr($methodCall, $noOp, $configuration)); + } + return NodeVisitor::REMOVE_NODE; } } diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/BackwardsCompatibilityRemoveAliasManagerCacheMethodCallsRectorTest.php b/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/BackwardsCompatibilityRemoveAliasManagerCacheMethodCallsRectorTest.php new file mode 100644 index 00000000..f556c465 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/BackwardsCompatibilityRemoveAliasManagerCacheMethodCallsRectorTest.php @@ -0,0 +1,34 @@ +make(DrupalRectorSettings::class) + ->enableBackwardCompatibility() + ->setMinimumCoreVersionSupported('10.1.0'); + $this->doTestFile($filePath); + } + + public static function provideData(): \Iterator + { + return self::yieldFilesFromDirectory(__DIR__.'/fixture-bc'); + } + + public function provideConfigFilePath(): string + { + return __DIR__.'/config/configured_rule_bc.php'; + } +} diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/RemoveAliasManagerCacheMethodCallsRectorTest.php b/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/RemoveAliasManagerCacheMethodCallsRectorTest.php index b40784cb..de704f0b 100644 --- a/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/RemoveAliasManagerCacheMethodCallsRectorTest.php +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/RemoveAliasManagerCacheMethodCallsRectorTest.php @@ -4,6 +4,7 @@ namespace DrupalRector\Tests\Drupal11\Rector\Deprecation\RemoveAliasManagerCacheMethodCallsRector; +use DrupalRector\Services\DrupalRectorSettings; use DrupalRector\Tests\AbstractDrupalRectorTestCase; class RemoveAliasManagerCacheMethodCallsRectorTest extends AbstractDrupalRectorTestCase @@ -11,6 +12,8 @@ class RemoveAliasManagerCacheMethodCallsRectorTest extends AbstractDrupalRectorT #[\PHPUnit\Framework\Attributes\DataProvider('provideData')] public function test(string $filePath): void { + // Backward compatibility disabled: the deprecated call is removed. + static::getContainer()->make(DrupalRectorSettings::class)->disableBackwardCompatibility(); $this->doTestFile($filePath); } @@ -19,6 +22,20 @@ public static function provideData(): \Iterator return self::yieldFilesFromDirectory(__DIR__.'/fixture'); } + #[\PHPUnit\Framework\Attributes\DataProvider('provideDataBelowVersion')] + public function testBelowVersion(string $filePath): void + { + // Target Drupal is below the 11.3.0 deprecation: the rector must not + // fire, regardless of the backward-compatibility setting. + static::getContainer()->make(DrupalRectorSettings::class)->setDrupalVersion('11.2.0'); + $this->doTestFile($filePath); + } + + public static function provideDataBelowVersion(): \Iterator + { + return self::yieldFilesFromDirectory(__DIR__.'/fixture-below-version'); + } + public function provideConfigFilePath(): string { return __DIR__.'/config/configured_rule.php'; diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/config/configured_rule.php b/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/config/configured_rule.php index 51c14642..6bbbe25a 100644 --- a/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/config/configured_rule.php +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/config/configured_rule.php @@ -3,9 +3,12 @@ declare(strict_types=1); use DrupalRector\Drupal11\Rector\Deprecation\RemoveAliasManagerCacheMethodCallsRector; +use DrupalRector\Rector\ValueObject\DrupalIntroducedVersionConfiguration; use DrupalRector\Tests\Rector\Deprecation\DeprecationBase; use Rector\Config\RectorConfig; return static function (RectorConfig $rectorConfig): void { - DeprecationBase::addClass(RemoveAliasManagerCacheMethodCallsRector::class, $rectorConfig, false); + DeprecationBase::addClass(RemoveAliasManagerCacheMethodCallsRector::class, $rectorConfig, false, [ + new DrupalIntroducedVersionConfiguration('11.3.0'), + ]); }; diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/config/configured_rule_bc.php b/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/config/configured_rule_bc.php new file mode 100644 index 00000000..6bbbe25a --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/config/configured_rule_bc.php @@ -0,0 +1,14 @@ +setCacheKey('/some/path'); + $aliasManager->writeCache(); + \Drupal::messenger()->addStatus('done'); +} +?> +----- + null, fn() => $aliasManager->setCacheKey('/some/path')); + \Drupal\Component\Utility\DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.3.0', fn() => null, fn() => $aliasManager->writeCache()); + \Drupal::messenger()->addStatus('done'); +} +?> diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/fixture-bc/concrete_class.php.inc b/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/fixture-bc/concrete_class.php.inc new file mode 100644 index 00000000..2ab8552d --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/fixture-bc/concrete_class.php.inc @@ -0,0 +1,19 @@ +setCacheKey('/some/path'); + $aliasManager->writeCache(); + \Drupal::messenger()->addStatus('done'); +} +?> +----- + null, fn() => $aliasManager->setCacheKey('/some/path')); + \Drupal\Component\Utility\DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.3.0', fn() => null, fn() => $aliasManager->writeCache()); + \Drupal::messenger()->addStatus('done'); +} +?> diff --git a/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/fixture-below-version/no_change_below_version.php.inc b/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/fixture-below-version/no_change_below_version.php.inc new file mode 100644 index 00000000..be3e60e5 --- /dev/null +++ b/tests/src/Drupal11/Rector/Deprecation/RemoveAliasManagerCacheMethodCallsRector/fixture-below-version/no_change_below_version.php.inc @@ -0,0 +1,10 @@ +setCacheKey('/some/path'); + $aliasManager->writeCache(); +} +?>