From 08b786cd81026f2e40803451a6b978ff959584b8 Mon Sep 17 00:00:00 2001 From: Zachary Lund Date: Wed, 26 Mar 2025 16:23:26 -0500 Subject: [PATCH] Symfony: detect callback constraints --- src/Provider/SymfonyUsageProvider.php | 22 ++++++++++++++++++++++ tests/Rule/data/providers/symfony.php | 19 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/Provider/SymfonyUsageProvider.php b/src/Provider/SymfonyUsageProvider.php index 781c5b03..3c799c1b 100644 --- a/src/Provider/SymfonyUsageProvider.php +++ b/src/Provider/SymfonyUsageProvider.php @@ -326,6 +326,10 @@ protected function shouldMarkAsUsed(ReflectionMethod $method): ?string return 'Route method via #[Route] attribute'; } + if ($this->isMethodWithCallbackConstraintAttribute($method)) { + return 'Callback constraint method via #[Assert\Callback] attribute'; + } + if ($this->isProbablySymfonyListener($method)) { return 'Probable listener method'; } @@ -461,6 +465,23 @@ protected function isMethodWithRouteAttribute(ReflectionMethod $method): bool || $this->hasAttribute($method, 'Symfony\Component\Routing\Annotation\Route', $isInstanceOf); } + protected function isMethodWithCallbackConstraintAttribute(ReflectionMethod $method): bool + { + $attributes = $method->getDeclaringClass()->getAttributes('Symfony\Component\Validator\Constraints\Callback'); + + foreach ($attributes as $attribute) { + $arguments = $attribute->getArguments(); + + $callback = $arguments['callback'] ?? $arguments[0] ?? null; + + if ($callback === $method->getName()) { + return true; + } + } + + return $this->hasAttribute($method, 'Symfony\Component\Validator\Constraints\Callback'); + } + /** * Ideally, we would need to parse DIC xml to know this for sure just like phpstan-symfony does. */ @@ -502,6 +523,7 @@ private function isSymfonyInstalled(): bool || InstalledVersions::isInstalled('symfony/contracts') || InstalledVersions::isInstalled('symfony/console') || InstalledVersions::isInstalled('symfony/http-kernel') + || InstalledVersions::isInstalled('symfony/validator') || InstalledVersions::isInstalled('symfony/dependency-injection'); } diff --git a/tests/Rule/data/providers/symfony.php b/tests/Rule/data/providers/symfony.php index 2739edd2..578984b8 100644 --- a/tests/Rule/data/providers/symfony.php +++ b/tests/Rule/data/providers/symfony.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\Routing\Attribute\Route; use Symfony\Contracts\Service\Attribute\Required; +use Symfony\Component\Validator\Constraints as Assert; class SomeController { @@ -115,3 +116,21 @@ public function create(): self { class Sftp { const RETRY_LIMIT = 3; // used in yaml via !php/const } + +class ModelValidator +{ + public static function validate(): void {} +} + +#[Assert\Callback([ModelValidator::class, 'validate'])] +#[Assert\Callback('validateBar')] +#[Assert\Callback(callback: 'validateBaz')] +class ValidatedModel +{ + #[Assert\Callback] + public function validateFoo(): void {} + + public function validateBar(): void {} + + public function validateBaz(): void {} +}