Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Moving from PHP Annotations to Attributes? Use this helpful Rector script! #1047

Open
BusterNeece opened this issue Dec 28, 2021 · 27 comments
Open

Comments

@BusterNeece
Copy link
Contributor

BusterNeece commented Dec 28, 2021

This isn't a bug report, but rather a helpful tool that might save folks moving from annotations to attributes a lot of time.

Rector has a tool that can automatically migrate annotations to attributes, so long as you provide the class mapping for it.

Here's a sample script I set up that (crudely) does the migration from annotations to attributes:

https://gist.github.com/BusterNeece/230f1bf619740b0564b88a69978de82c

It's not perfect, but it gets you to a really good starting point and takes care of a task that is extremely boring to do otherwise.

Edit: See below for a link to a repo that does this in an improved manner!

@fuale
Copy link

fuale commented Feb 18, 2022

It does not handle nested attributes for now

@fuale
Copy link

fuale commented Feb 19, 2022

I figured out how to handle nested attributes

/**
 * @OA\Get(
 *     path="/departments",
 *     summary="summary",
 *     tags={"sit"},
- *     @OA\Response(
+ *     responses={@OA\Response(
 *         response=200,
 *         description="desc",
- *         @OA\JsonContent(
+ *         content=@OA\JsonContent(
 *              type="array",
 *              @OA\Items(ref="#/components/schemas/refff")
 *         ),
- *     )
+ *    )}
 * )
 */

@LVoogd
Copy link

LVoogd commented Jun 14, 2022

Thanks for the script @SlvrEagle23! However the ContainerConfigurator is deprecated and does not work that well with the new rector versions anymore.

I have refactored the script to work with the newer rector versions https://gist.github.com/LVoogd/34078de7144663db5ab2067977516130

Beware of the sets loaded on line 54, if you don't want to update other annotations please disable these.

@momala454
Copy link
Contributor

Thanks for the script @SlvrEagle23! However the ContainerConfigurator is deprecated and does not work that well with the new rector versions anymore.

I have refactored the script to work with the newer rector versions https://gist.github.com/LVoogd/34078de7144663db5ab2067977516130

Beware of the sets loaded on line 54, if you don't want to update other annotations please disable these.

i'm not able to make your script work, it does nothing when running rector. It shows 100%, then Rector is done, but nothing changed

@LVoogd
Copy link

LVoogd commented Jun 28, 2022

i'm not able to make your script work, it does nothing when running rector. It shows 100%, then Rector is done, but nothing changed

Hi @momala454,

Make sure you use the latest version of Rector (I used 0.13.5).

If that does not work maybe your notation is a bit different and therefore the script does not pick it up correctly. I did recall having to manually update some schema entities, presumably because of some special case this script does not understand. As @SlvrEagle23 stated, its a crude script and should be perceived as a good starting point. You may need to make alterations to make it work for your specific case.

Good luck!

@momala454
Copy link
Contributor

momala454 commented Jun 28, 2022

@LVoogd i'm on 0.13.6.
Thanks for the response and script.
Do i need to run it on the whole project folder (including vendor folder and src folder, or do I run it on src folder ?
running on whole project including vendor is super slow and generate a timeout

edit: this dummy annoation is not detected/replaced

/**
     * @OA\Get(
     * path="/hello",
     * responses={@OA\Response(response= 200, description="")}
     * )
     */

@momala454
Copy link
Contributor

@LVoogd even trying to fix only rector.php doesn't work, it does nothing

<?php

use Rector\Doctrine\Set\DoctrineSetList;
use Rector\Symfony\Set\SymfonySetList;
use Rector\Symfony\Set\SensiolabsSetList;
use Rector\Nette\Set\NetteSetList;
use Rector\Config\RectorConfig;
use Rector\Php80\Rector\Class_\AnnotationToAttributeRector;
use Rector\Php80\ValueObject\AnnotationToAttribute;

/**
 * @OpenApi\Annotations\Get(
 * path="/hello"
 * )
 */

return function (RectorConfig $rectorConfig): void {
    $rectorConfig->ruleWithConfiguration(AnnotationToAttributeRector::class, [
        new AnnotationToAttribute('OpenApi\\Annotations\\AdditionalProperties', 'OpenApi\\Attributes\\AdditionalProperties'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Attachable', 'OpenApi\\Attributes\\Attachable'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Components', 'OpenApi\\Attributes\\Components'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Contact', 'OpenApi\\Attributes\\Contact'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Delete', 'OpenApi\\Attributes\\Delete'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Discriminator', 'OpenApi\\Attributes\\Discriminator'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Examples', 'OpenApi\\Attributes\\Examples'),
        new AnnotationToAttribute('OpenApi\\Annotations\\ExternalDocumentation', 'OpenApi\\Attributes\\ExternalDocumentation'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Flow', 'OpenApi\\Attributes\\Flow'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Get', 'OpenApi\\Attributes\\Get'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Head', 'OpenApi\\Attributes\\Head'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Header', 'OpenApi\\Attributes\\Header'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Info', 'OpenApi\\Attributes\\Info'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Items', 'OpenApi\\Attributes\\Items'),
        new AnnotationToAttribute('OpenApi\\Annotations\\JsonContent', 'OpenApi\\Attributes\\JsonContent'),
        new AnnotationToAttribute('OpenApi\\Annotations\\License', 'OpenApi\\Attributes\\License'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Link', 'OpenApi\\Attributes\\Link'),
        new AnnotationToAttribute('OpenApi\\Annotations\\MediaType', 'OpenApi\\Attributes\\MediaType'),
        new AnnotationToAttribute('OpenApi\\Annotations\\OpenApi', 'OpenApi\\Attributes\\OpenApi'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Operation', 'OpenApi\\Attributes\\Operation'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Options', 'OpenApi\\Attributes\\Options'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Parameter', 'OpenApi\\Attributes\\Parameter'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Patch', 'OpenApi\\Attributes\\Patch'),
        new AnnotationToAttribute('OpenApi\\Annotations\\PatchItem', 'OpenApi\\Attributes\\PatchItem'),
        new AnnotationToAttribute('OpenApi\\Annotations\\PathParameter', 'OpenApi\\Attributes\\PathParameter'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Post', 'OpenApi\\Attributes\\Post'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Property', 'OpenApi\\Attributes\\Property'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Put', 'OpenApi\\Attributes\\Put'),
        new AnnotationToAttribute('OpenApi\\Annotations\\RequestBody', 'OpenApi\\Attributes\\RequestBody'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Response', 'OpenApi\\Attributes\\Response'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Schema', 'OpenApi\\Attributes\\Schema'),
        new AnnotationToAttribute('OpenApi\\Annotations\\SecurityScheme', 'OpenApi\\Attributes\\SecurityScheme'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Server', 'OpenApi\\Attributes\\Server'),
        new AnnotationToAttribute('OpenApi\\Annotations\\ServerVariable', 'OpenApi\\Attributes\\ServerVariable'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Tag', 'OpenApi\\Attributes\\Tag'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Trace', 'OpenApi\\Attributes\\Trace'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Xml', 'OpenApi\\Attributes\\Xml'),
        new AnnotationToAttribute('OpenApi\\Annotations\\XmlContent', 'OpenApi\\Attributes\\XmlContent'),
    ]);
};
php vendor/bin/rector process rector.php --dry-run --config rector.php -vvv
rector.php                                                                                                                        
 [OK] Rector is done!                                                                                                   
                                                                                                                        

@LVoogd
Copy link

LVoogd commented Jul 5, 2022

Hi @momala454

I have tested your example as follows

# test.php

<?php

class test {
    /**
     * @OpenApi\Annotations\Get(
     * path="/hello"
     * )
     */
    public function get()
    {

    }
}

output:

php vendor/bin/rector process test.php --dry-run --config rector.php -vvv

test.php
[file] test.php
[rule] Rector\Php80\Rector\Class_\AnnotationToAttributeRector

[file] test.php
[rule] Rector\Php80\Rector\Class_\AnnotationToAttributeRector

[file] test.php
[rule] Rector\Php80\Rector\Class_\AnnotationToAttributeRector


1 file with changes
===================

1) test.php:1

    ---------- begin diff ----------
@@ @@


 class test {
-    /**
-     * @OpenApi\Annotations\Get(
-     * path="/hello"
-     * )
-     */
+    #[\OpenApi\Attributes\Get(path: '/hello')]
     public function get()
     {
-
     }
 }
    ----------- end diff -----------

Applied rules:
 * AnnotationToAttributeRector (https://wiki.php.net/rfc/attributes_v2)


                                                                                                                        
 [OK] 1 file would have changed (dry-run) by Rector                                                                     

I believe this to be the desired outcome.

@momala454
Copy link
Contributor

momala454 commented Jul 5, 2022

using your test.php file it gives me the same result as before for me, no changes at all.

What contains your rector.php ?

 php vendor/bin/rector process test.php --dry-run --config rector.php -vvv

                                                                                                                        
 [OK] Rector is done!                                                                                                   
php vendor/bin/rector --version
Rector 0.13.6

@LVoogd which is your openapi library ? i'm using "zircote/swagger-php"

@momala454
Copy link
Contributor

momala454 commented Sep 2, 2022

@LVoogd i finally was able to make it work, however it doesn't detect usage that doesnt contains the parameter name. Like mentioned here : #1047 (comment)

on the "before" of the diff

It also put everything in the same line, rendering it unreadable. Is there a parameter to put one parameter per line ?

Example

<?php

class test {
    /**
     * @OpenApi\Annotations\Post(
     * path="/hello",
     * @OA\RequestBody(
     *          @OA\MediaType(
     *              mediaType="application/json",
     *              @OA\Schema(
     *                  @OA\Property(
     *                      property="name",
     *                      type="string",
     *                      description="name",
     *                      example="My name"
     *                  ),
     *                  required={"name"}
     *              )
     *          )
     *      ),
     * )
     */
    public function post()
    {

    }
}

is converted to

<?php

class test {
    #[\OpenApi\Attributes\Post(path: '/hello', new OA\RequestBody(new OA\MediaType(mediaType: 'application/json', new OA\Schema(new OA\Property(property: 'name', type: 'string', description: 'name', example: 'My name'), required: ['name']))))]
    public function post()
    {
    }
}

but it doesn't work because "Cannot use positional argument after named argument" the "new OA\RequestBody" is not preceded by "requestBody:"
Is there any automated way to replace this ? It's not the only example, i've also "schema", "mediatype" and "responses" not using named parameters.

like two @OA\Response() one after the other, so i can't do like a search/replace

@kevinpapst
Copy link

Thanks @LVoogd any everyone else, these rules saved me a huge amount of time 👍

Some minor issues, which could easily be solved by adding some named parameters (those were missing in tiny percent of all migrated attributes). But all in all, this was copy & paste and done 🎉

@reksc
Copy link

reksc commented Mar 15, 2023

@momala454 I'm having the exact same issue. Have you came up with a solution to address the named parameters? Is it even possible?

@momala454
Copy link
Contributor

@reksc my "solution" was a whole lot of manual replacement

@wenbinye
Copy link

wenbinye commented Apr 24, 2023

I wrote a rector rule to convert annotations to attributes:
https://github.com/wenbinye/openapi-rector

The only problem is that rector cannot indent code pretty.

@williamdes
Copy link

Thank you @momala454
You saved me countless hours !

I used a modified version of (#1047 (comment)):

<?php

use Rector\Config\RectorConfig;
use Rector\Php80\Rector\Class_\AnnotationToAttributeRector;
use Rector\Php80\ValueObject\AnnotationToAttribute;

/**
 * @OA\Get(
 * path="/hello"
 * )
 */

return function (RectorConfig $rectorConfig): void {
    $rectorConfig->ruleWithConfiguration(AnnotationToAttributeRector::class, [
        new AnnotationToAttribute('OA\\AdditionalProperties', 'OpenApi\\Attributes\\AdditionalProperties'),
        new AnnotationToAttribute('OA\\Attachable', 'OpenApi\\Attributes\\Attachable'),
        new AnnotationToAttribute('OA\\Components', 'OpenApi\\Attributes\\Components'),
        new AnnotationToAttribute('OA\\Contact', 'OpenApi\\Attributes\\Contact'),
        new AnnotationToAttribute('OA\\Delete', 'OpenApi\\Attributes\\Delete'),
        new AnnotationToAttribute('OA\\Discriminator', 'OpenApi\\Attributes\\Discriminator'),
        new AnnotationToAttribute('OA\\Examples', 'OpenApi\\Attributes\\Examples'),
        new AnnotationToAttribute('OA\\ExternalDocumentation', 'OpenApi\\Attributes\\ExternalDocumentation'),
        new AnnotationToAttribute('OA\\Flow', 'OpenApi\\Attributes\\Flow'),
        new AnnotationToAttribute('OA\\Get', 'OpenApi\\Attributes\\Get'),
        new AnnotationToAttribute('OA\\Head', 'OpenApi\\Attributes\\Head'),
        new AnnotationToAttribute('OA\\Header', 'OpenApi\\Attributes\\Header'),
        new AnnotationToAttribute('OA\\Info', 'OpenApi\\Attributes\\Info'),
        new AnnotationToAttribute('OA\\Items', 'OpenApi\\Attributes\\Items'),
        new AnnotationToAttribute('OA\\JsonContent', 'OpenApi\\Attributes\\JsonContent'),
        new AnnotationToAttribute('OA\\License', 'OpenApi\\Attributes\\License'),
        new AnnotationToAttribute('OA\\Link', 'OpenApi\\Attributes\\Link'),
        new AnnotationToAttribute('OA\\MediaType', 'OpenApi\\Attributes\\MediaType'),
        new AnnotationToAttribute('OA\\OpenApi', 'OpenApi\\Attributes\\OpenApi'),
        new AnnotationToAttribute('OA\\Operation', 'OpenApi\\Attributes\\Operation'),
        new AnnotationToAttribute('OA\\Options', 'OpenApi\\Attributes\\Options'),
        new AnnotationToAttribute('OA\\Parameter', 'OpenApi\\Attributes\\Parameter'),
        new AnnotationToAttribute('OA\\Patch', 'OpenApi\\Attributes\\Patch'),
        new AnnotationToAttribute('OA\\PatchItem', 'OpenApi\\Attributes\\PatchItem'),
        new AnnotationToAttribute('OA\\PathParameter', 'OpenApi\\Attributes\\PathParameter'),
        new AnnotationToAttribute('OA\\Post', 'OpenApi\\Attributes\\Post'),
        new AnnotationToAttribute('OA\\Property', 'OpenApi\\Attributes\\Property'),
        new AnnotationToAttribute('OA\\Put', 'OpenApi\\Attributes\\Put'),
        new AnnotationToAttribute('OA\\RequestBody', 'OpenApi\\Attributes\\RequestBody'),
        new AnnotationToAttribute('OA\\Response', 'OpenApi\\Attributes\\Response'),
        new AnnotationToAttribute('OA\\Schema', 'OpenApi\\Attributes\\Schema'),
        new AnnotationToAttribute('OA\\SecurityScheme', 'OpenApi\\Attributes\\SecurityScheme'),
        new AnnotationToAttribute('OA\\Server', 'OpenApi\\Attributes\\Server'),
        new AnnotationToAttribute('OA\\ServerVariable', 'OpenApi\\Attributes\\ServerVariable'),
        new AnnotationToAttribute('OA\\Tag', 'OpenApi\\Attributes\\Tag'),
        new AnnotationToAttribute('OA\\Trace', 'OpenApi\\Attributes\\Trace'),
        new AnnotationToAttribute('OA\\Xml', 'OpenApi\\Attributes\\Xml'),
        new AnnotationToAttribute('OA\\XmlContent', 'OpenApi\\Attributes\\XmlContent'),
    ]);
};

Because my annotations where written with the short syntax.

Then I used quite a lot of vscode regex replace feature on all files, since some where quite screwed up after the migration. A lot of syntax fixed later the spec was back and running.

I wrote a rector rule to convert annotations to attributes:
https://github.com/wenbinye/openapi-rector

It does not work at all, no files where changed. Seems like it was not designed for Laravel or more diverse projects.

@sylbru
Copy link

sylbru commented Jun 19, 2023

I figured out how to handle nested attributes

/**
 * @OA\Get(
 *     path="/departments",
 *     summary="summary",
 *     tags={"sit"},
- *     @OA\Response(
+ *     responses={@OA\Response(
 *         response=200,
 *         description="desc",
- *         @OA\JsonContent(
+ *         content=@OA\JsonContent(
 *              type="array",
 *              @OA\Items(ref="#/components/schemas/refff")
 *         ),
- *     )
+ *    )}
 * )
 */

Note: while the @OA\Response change is working for the Rector script provided here and for regular swagger-php usage (before trying any migration), the @OA\JsonContent change doesn’t seem to be accepted by swagger-php. So if you want to incrementally adapt your annotations before doing the migration, you’ll have to skip the OA\JsonContent ones, and possibly more.

@williamdes
Copy link

For me it works great

#[\OpenApi\Attributes\Response(response: 200, description: 'OK', content: new \OpenApi\Attributes\JsonContent(ref: '#/components/schemas/FooBarBaz'))]

@sylbru
Copy link

sylbru commented Jun 19, 2023

@williamdes I know the migration works ok. But I wanted to delay the migration and just incrementally commit and deploy compatible changes to prepare for the migration in a simple and safe way. The JsonContent change isn't compatible so I can't apply this technique.

@williamdes
Copy link

@williamdes I know the migration works ok. But I wanted to delay the migration and just incrementally commit and deploy compatible changes to prepare for the migration in a simple and safe way. The JsonContent change isn't compatible so I can't apply this technique.

Oh okay, I get it now
Indeed adding "responses" and stuff would have made the migration smoother

@digitaltim-de
Copy link

And i will move from attributes back to annotations, because its clear to read in Editor

@LVoogd
Copy link

LVoogd commented Aug 18, 2023

And i will move from attributes back to annotations, because its clear to read in Editor

If it’s for your personal projects, sure go ahead. If you’re working on code with other contributes or for a client/company I would advise against it.

Since a lot of projects haven’t switched to annotations we’re still quite accustomed to them. However, these projects will become fewer over time and there will be a time that new developers have never used annotations. I can also see packages dropping support for annotations in the future.

Bottom line, using annotations now can be costly in the future. You might be forced into using attributes by a package or you need to teach fresher developers this “legacy thing” that you think looks beter in the IDE.

@digitaltim-de
Copy link

Yea, i stay actually on Attributes, but look this. Looks not so fine, what can we do here?

image

@DerManoMann
Copy link
Collaborator

You can nest attributes but then you have to use new 🤷

@esorinas
Copy link

esorinas commented Feb 17, 2024

Hello! Thanks for providing some light to this issue, it's going to take so many hours to manually migrate from annotations to attributes. I seem to have problems using the snippets you guys provided.

I tried running rector (v1.0.1 released just a couple days ago) adapting rector.php with the configured rules you suggest. It works fine for most annotations, however the @OA\Response annotation with a @OA\JsonContent seems to create an issue, and I get '__remove_array__' in the place of the model.

For instance, the following annotation

     * @OA\Response(
     *     response=200,
     *     description="Returns all books",
     *     @OA\JsonContent(ref=@Model(type=BookListOutputModel::class))
     * )

becomes

#[OA\Response(response: 200, description: 'Returns all books', '__remove_array__')]

Here is what my rector.php looks like:

<?php

declare(strict_types=1);

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

use Rector\Php80\Rector\Class_\AnnotationToAttributeRector;
use Rector\Php80\ValueObject\AnnotationToAttribute;

return RectorConfig::configure()
    ->withPaths([
        __DIR__ . '/config',
        __DIR__ . '/public',
        __DIR__ . '/src',
    ])
    ->withConfiguredRule(AnnotationToAttributeRector::class, [
        new AnnotationToAttribute('OpenApi\\Annotations\\AdditionalProperties', 'OpenApi\\Attributes\\AdditionalProperties'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Attachable', 'OpenApi\\Attributes\\Attachable'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Components', 'OpenApi\\Attributes\\Components'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Contact', 'OpenApi\\Attributes\\Contact'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Delete', 'OpenApi\\Attributes\\Delete'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Discriminator', 'OpenApi\\Attributes\\Discriminator'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Examples', 'OpenApi\\Attributes\\Examples'),
        new AnnotationToAttribute('OpenApi\\Annotations\\ExternalDocumentation', 'OpenApi\\Attributes\\ExternalDocumentation'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Flow', 'OpenApi\\Attributes\\Flow'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Get', 'OpenApi\\Attributes\\Get'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Head', 'OpenApi\\Attributes\\Head'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Header', 'OpenApi\\Attributes\\Header'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Info', 'OpenApi\\Attributes\\Info'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Items', 'OpenApi\\Attributes\\Items'),
        new AnnotationToAttribute('OpenApi\\Annotations\\JsonContent', 'OpenApi\\Attributes\\JsonContent'),
        new AnnotationToAttribute('OpenApi\\Annotations\\License', 'OpenApi\\Attributes\\License'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Link', 'OpenApi\\Attributes\\Link'),
        new AnnotationToAttribute('OpenApi\\Annotations\\MediaType', 'OpenApi\\Attributes\\MediaType'),
        new AnnotationToAttribute('OpenApi\\Annotations\\OpenApi', 'OpenApi\\Attributes\\OpenApi'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Operation', 'OpenApi\\Attributes\\Operation'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Options', 'OpenApi\\Attributes\\Options'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Parameter', 'OpenApi\\Attributes\\Parameter'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Patch', 'OpenApi\\Attributes\\Patch'),
        new AnnotationToAttribute('OpenApi\\Annotations\\PatchItem', 'OpenApi\\Attributes\\PatchItem'),
        new AnnotationToAttribute('OpenApi\\Annotations\\PathParameter', 'OpenApi\\Attributes\\PathParameter'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Post', 'OpenApi\\Attributes\\Post'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Property', 'OpenApi\\Attributes\\Property'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Put', 'OpenApi\\Attributes\\Put'),
        new AnnotationToAttribute('OpenApi\\Annotations\\RequestBody', 'OpenApi\\Attributes\\RequestBody'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Response', 'OpenApi\\Attributes\\Response'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Schema', 'OpenApi\\Attributes\\Schema'),
        new AnnotationToAttribute('OpenApi\\Annotations\\SecurityScheme', 'OpenApi\\Attributes\\SecurityScheme'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Server', 'OpenApi\\Attributes\\Server'),
        new AnnotationToAttribute('OpenApi\\Annotations\\ServerVariable', 'OpenApi\\Attributes\\ServerVariable'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Tag', 'OpenApi\\Attributes\\Tag'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Trace', 'OpenApi\\Attributes\\Trace'),
        new AnnotationToAttribute('OpenApi\\Annotations\\Xml', 'OpenApi\\Attributes\\Xml'),
        new AnnotationToAttribute('OpenApi\\Annotations\\XmlContent', 'OpenApi\\Attributes\\XmlContent'),
    ])
    ->withSets([
        DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES,
        SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES,
    ]);

Any idea on how can I fix this? Is there a better tool or new version of the snippet that can help?

Thank you!

@williamdes
Copy link

Maybe ask rector support directly or Stackoverflow?
Let us know

I had to use vscode regexes, it was not easy to fix the result

@esorinas
Copy link

Thanks @williamdes, I've posted this as a question on Stackoverflow and will come back here if I get any useful insights.

@a-kompaneytsev
Copy link

Perhaps someone will find it useful. A bit of dirty code. If anyone is interested, I can put it all together into a rule.

To be able to translate such code

    /**
     * @OA\Schema(
     *     path="/api/data.json",
     *     @OA\Property(
     *         response="200",
     *         description="The data"
     *     )
     * )
     */

To

    #[OA\Schema(path: '/api/data.json')]
    #[OA\Property(response: '200', description: 'The data')]

Using a rule in Rector that will look something like this

    $rectorConfig->ruleWithConfiguration(NestedAnnotationToAttributeRector::class, [
        new NestedAnnotationToAttribute('OA\Schema', [
            new AnnotationToAttribute('OA\Property', 'OA\Property'),
        ]),
     ]

You'll need to patch a couple of files on the machine where you'll be running Rector.

Attention

Before proceeding, ensure that the files you're patching are of the same version. Usually, in local projects, versions are fixed and, as a result, not up-to-date with the latest versions of Rector and its rules. It's better to check the differences to see what will change for you.

In the file NestedAnnotationToAttribute

Replace on line
if ($attributeClass instanceof AnnotationPropertyToAttributeClass) {

to
if ($attributeClass instanceof AnnotationPropertyToAttributeClass || $attributeClass instanceof AnnotationToAttribute) {

After this line add

            if ($annotationPropertyToAttributeClass instanceof AnnotationToAttribute) {
                continue;
            }

Replace the contents of the file PhpNestedAttributeGroupFactory with this code:

<?php

declare(strict_types=1);

namespace Rector\Php80\ValueObject;

use Rector\Php80\Contract\ValueObject\AnnotationToAttributeInterface;
use Rector\Validation\RectorAssert;

final class NestedAnnotationToAttribute implements AnnotationToAttributeInterface
{
    /**
     * @var AnnotationPropertyToAttributeClass[]|AnnotationToAttribute[]
     */
    private array $annotationPropertiesToAttributeClasses = [];

    /**
     * @param array<string, string>|string[]|AnnotationPropertyToAttributeClass[]|AnnotationToAttribute[] $annotationPropertiesToAttributeClasses
     */
    public function __construct(
        private readonly string $tag,
        array $annotationPropertiesToAttributeClasses,
        private readonly bool $removeOriginal = false,
    ) {
        RectorAssert::className($tag);

        // back compatibility for raw scalar values
        foreach ($annotationPropertiesToAttributeClasses as $annotationProperty => $attributeClass) {
            if ($attributeClass instanceof AnnotationPropertyToAttributeClass || $attributeClass instanceof AnnotationToAttribute) {
                $this->annotationPropertiesToAttributeClasses[] = $attributeClass;
            } else {
                $this->annotationPropertiesToAttributeClasses[] = new AnnotationPropertyToAttributeClass(
                    $attributeClass,
                    $annotationProperty,
                );
            }
        }
    }

    public function getTag(): string
    {
        return $this->tag;
    }

    /**
     * @return AnnotationPropertyToAttributeClass[]|AnnotationToAttribute[]
     */
    public function getAnnotationPropertiesToAttributeClasses(): array
    {
        return $this->annotationPropertiesToAttributeClasses;
    }

    public function getAttributeClass(): string
    {
        return $this->tag;
    }

    public function shouldRemoveOriginal(): bool
    {
        return $this->removeOriginal;
    }

    public function hasExplicitParameters(): bool
    {
        foreach ($this->annotationPropertiesToAttributeClasses as $annotationPropertyToAttributeClass) {
            if ($annotationPropertyToAttributeClass instanceof AnnotationToAttribute) {
                continue;
            }

            if (is_string($annotationPropertyToAttributeClass->getAnnotationProperty())) {
                return true;
            }
        }

        return false;
    }
}

Why this solution is bad

What's needed here is nesting, specifically the ability to automatically wrap @OA\Property( in properties: []. But I regret the time lost and the fact that I discovered this requirement late. Therefore, it would be great if my efforts could save time for someone else.

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

No branches or pull requests