zircote / swagger-php

A php swagger annotation and parsing library
http://zircote.github.io/swagger-php/
Apache License 2.0
5.06k stars 930 forks source link

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

Open BusterNeece opened 2 years ago

BusterNeece commented 2 years ago

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 commented 2 years ago

It does not handle nested attributes for now

fuale commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

@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 commented 2 years ago

@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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

@LVoogd i finally was able to make it work, however it doesn't detect usage that doesnt contains the parameter name. Like mentioned here : https://github.com/zircote/swagger-php/issues/1047#issuecomment-1045965367

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 commented 1 year ago

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 commented 1 year ago

@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 commented 1 year ago

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

wenbinye commented 1 year ago

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 commented 1 year ago

Thank you @momala454 You saved me countless hours !

I used a modified version of (https://github.com/zircote/swagger-php/issues/1047#issuecomment-1174858414):

<?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 commented 1 year ago

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 commented 1 year ago

For me it works great

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

@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 commented 1 year ago

@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 commented 1 year ago

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

LVoogd commented 1 year ago

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 commented 1 year ago

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

image
DerManoMann commented 1 year ago

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

esorinas commented 6 months ago

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 commented 6 months ago

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 commented 6 months ago

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

a-kompaneytsev commented 5 months ago

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.