Skip to content

Add AddSymfonyConstraintValidatorTypeDeclarationsRector (Symfony 8 / Drupal 12 validator type declarations)#407

Merged
bbrala merged 5 commits into
palantirnet:mainfrom
bbrala:feature/symfony-constraint-validator-type-declarations
Jul 7, 2026
Merged

Add AddSymfonyConstraintValidatorTypeDeclarationsRector (Symfony 8 / Drupal 12 validator type declarations)#407
bbrala merged 5 commits into
palantirnet:mainfrom
bbrala:feature/symfony-constraint-validator-type-declarations

Conversation

@bbrala

@bbrala bbrala commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds AddSymfonyConstraintValidatorTypeDeclarationsRector, a new rule that gives Symfony ConstraintValidatorInterface implementers their Drupal 12 / Symfony 8 type declarations:

  • validate() — adds mixed to param 0 (when untyped) and a : void return (when absent)
  • initialize() — adds a : void return (when absent)

It matches any class that is a Symfony\Component\Validator\ConstraintValidatorInterface — covering extends ConstraintValidator, direct implements, and the legacy extends Constraint implements ConstraintValidatorInterface self-validating pattern.

Surfaced by redirect's automated Drupal 12 work (project/redirect!200), which made exactly these edits to UniqueHashValidator and SourceLinkTypeConstraint. Affects many contrib modules and wasn't handled by any automated tool yet.

Issue: #3600790

Backward compatibility

No version gate / DeprecationHelper — the change is backward compatible on every supported Drupal version. A child class may declare a return/param type its interface does not (Drupal core itself ships validate(mixed $value, Constraint $constraint): void while running on a Symfony 7.4 interface without the native : void); adding mixed is contravariant-widening (and Symfony 7 already declares mixed $value on the interface). Validators return nothing by contract, so : void is a runtime no-op.

Deliberately not matching Constraint on its own: its methods (validatedBy(), getTargets(), etc.) have been typed since Symfony 7.4, so any untyped override already fatals on Drupal 11 — there are no real targets there.

Placement

Registered in the Drupal 11 floor set (^11.0, so every D11 install picks it up and modules can adopt the types while still supporting D11) and a new Drupal 12 set (^12.0, covering installs already on 12 that same-major matching excludes from the D11 sets). Same rule in both; idempotent, so double coverage is harmless. Adds the first Drupal12 scaffolding (Drupal12SetList, config/drupal-12/, DrupalSetProvider wiring).

Testing

  • 5 rector fixtures: extends ConstraintValidator; legacy extends Constraint implements ConstraintValidatorInterface; already-typed (idempotency no-op); a non-validator class (type-guard negative); and a Constraint-only class with an untyped validate() (proves the guard does not fire on Constraint alone).
  • Symfony validator test stubs added under stubs/ (classmap-autoloaded) so isObjectType resolves.
  • Full suite green (700+ tests), PHPStan clean, php-cs-fixer clean.

@bbrala

bbrala commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator Author

Live test results

Ran AddSymfonyConstraintValidatorTypeDeclarationsRector against 8 real Drupal-11-compatible contrib modules (found by searching contrib for extends ConstraintValidator and implements ConstraintValidatorInterface). 8 files transformed, every one correct, zero false positives, no non-validator class touched.

Ground-truth match — for the redirect module (the source of this issue), the rector produced output identical to the hand-written MR !200:

File Pattern Transformation
SourceLinkTypeConstraint extends Constraint implements ConstraintValidatorInterface initialize(ExecutionContextInterface $context): void + validate(mixed $value, Constraint $constraint): void
UniqueHashValidator extends ConstraintValidator validate(mixed $redirect, Constraint $constraint): void

Other modules (all correct):

  • file_linkinitialize(): void + validate(mixed $link, …): void
  • colorapi, telephone_advanced, allow_only_one, signature_padvalidate(mixed …, …): void
  • limited_field_widgets — already had : void, only mixed added (idempotency path)
  • prevent_absolute_internal_links — added both mixed and : void

Both class shapes (extends ConstraintValidator and the legacy self-validating extends Constraint implements ConstraintValidatorInterface) were exercised, and both the mixed-only and mixed + : void paths fired correctly. All module files were reset after the run.

(No PHPStan deprecation-coverage entry applies here — this rule is matched by interface implementation and tightens signatures, not a deprecated-symbol rewrite, so it emits no PHPStan deprecation message.)

@bbrala bbrala merged commit a620544 into palantirnet:main Jul 7, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant