Support for Nested Doctrine Annotation to Flat Attributes in Rector 0.14

This feature is available since Rector 0.14.

We added support for annotation to attribute upgrade in Rector 0.12. Since then, PHP 8.1 has come with nested attributes. Rector supports these, e.g., for Symfony validator.

Yet, Doctrine already took a path of its own and unwrapped nested annotations to flat attributes to be exclusively open to PHP 8.0 users. Next Rector comes with support for these too.

One of the annotations that got unwrapped is Doctrine\ORM\Mapping\Table, where indexes and uniqueConstraints have their own attributes:

use Doctrine\ORM\Mapping as ORM;

#[ORM\Index(name: 'index_key')]
#[ORM\UniqueConstraint(name: 'unique_key')]

Adding support for this attribute is pretty straightforward, as every unique annotation class has its unique attribute class.

Then Doctrine came with the next-level challenge. Unwrap array of JoinColumn annotations, once to JoinColumn attribute, and once to InverseJoinColumns attribute. Based on parent key.

    use Doctrine\ORM\Mapping as ORM;

     * @ORM\JoinTable(name="join_table_name",
     *     joinColumns={
     *          @ORM\JoinColumn(name="target_id"),
     *     },
     *     inverseJoinColumns={
     *          @ORM\JoinColumn(name="another_id")
     *     }
     * )
    private $collection;

To handle this specific situation, we added brand new NestedAnnotationToAttributeRector rule to cover.

The case above would be handled by such configuration:

use Rector\Config\RectorConfig;
use Rector\Php80\Rector\Property\NestedAnnotationToAttributeRector;
use Rector\Php80\ValueObject\NestedAnnotationToAttribute;

return static function (RectorConfig $rectorConfig): void {
    $rectorConfig->ruleWithConfiguration(NestedAnnotationToAttributeRector::class, [
        new NestedAnnotationToAttribute('Doctrine\ORM\Mapping\JoinTable', [
            'joinColumns' => 'Doctrine\ORM\Mapping\JoinColumn',
            'inverseJoinColumns' => 'Doctrine\ORM\Mapping\InverseJoinColumn',

This rule will intelligently split the annotations:

    use Doctrine\ORM\Mapping as ORM;

    #[ORM\JoinTable(name: 'join_table_name')]
    #[ORM\JoinColumn(name: 'target_id')]
    #[ORM\InverseJoinColumn(name: 'another_id')]
    private $collection;

Doctrine Support OnBoard

Do you use the Doctrine upgrade set?

use Rector\Config\RectorConfig;
use Rector\Doctrine\Set\DoctrineSetList;

return static function (RectorConfig $rectorConfig): void {

We've got you covered. You don't need to fill the particular annotations because the set is already extended.

Just upgrade to Rector 0.14.1, and you're good to go.

Happy coding!