Repository: barryvdh/laravel-ide-helper (php)

Issue:
Latest commit causes massive slowdown on writing to models
### Versions:
- ide-helper Version: last commit #29dd57e1e27540cefea914500fc6cca3ee33dbed
- Laravel Version: 8.77.1
- PHP Version: 8.0

### Description:

Using the absolute latest commit of this package this evening when wanting to test out the new support for attributes in 8.77 (https://github.com/barryvdh/laravel-ide-helper/pull/1289), I noticed that the generation/writting to the models was SO much slower than it ever has been in the past.

I checked out multiple commits and the only one to show this issue was the one commited today (#29dd57e1e27540cefea914500fc6cca3ee33dbed)


I took a video to show the difference. Normally I'd speed up the video but in this case it makes more sense to let it run in real time. This is what I did.

* I installed the SECOND most recent commit (#6244a931a00ee313a7081a86d217fbb8e48c7aad) in my project.
* As part of my `composer.json` `post-update-cmd` is the following command

```
        "post-update-cmd": [
             <snip>
            "@php artisan ide-helper:models --smart-reset --phpstorm-noinspections --write",
             <snip>
        ],
```

Just after that gets installed you can see it writting new docblocks to all my models. It takes less than a second and whizzes through.

* Then I install the latest commit (#29dd57e1e27540cefea914500fc6cca3ee33dbed).
* Watch how it takes nearly 30secs to go through all the models.

Something in the latest commit with the new attribute support is causing a lareg slow down but I don't know where or how to debug why it's taking so much time.

I hope the video helps explain it better! It's about a minute long. 

Thank you!

https://youtu.be/c8elNXVuEiA

Pull request:
Improve performance for supporting Laravel 8.77+ cast attributes
## Summary
#1289 calls `getReturnType` for every method on every model, which in
turn calls `getReturnTypeFromDocBlock` which has this code:
```php
$phpDocContext = (new ContextFactory())->createFromReflector($reflection);
```
Extracting the docblock is super slow, always has been. Now that we do
this for every method, this adds up a lot.

Performance on a private commercial project _before_ #1289 was introduced:
```
$ time ./artisan ide-helper:models --write --reset >/dev/null

real	0m2.857s
user	0m1.835s
sys	0m0.129s
```
After #1289 :
```
$ time ./artisan ide-helper:models --write --reset >/dev/null

real	0m54.147s
user	0m47.132s
sys	0m1.047s
```

However, in this case we **do not need** the phpdoc fallback (which is
legitimate and by design for many other cases), because also the Laravel
implementation only works by inspecting the _actual type_, see
https://github.com/laravel/framework/blob/e0c2620b57be6416820ea7ca8e46fd2f71d2fe35/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php#L570-L575
```php
$returnType = (new ReflectionMethod($this, $method))->getReturnType();

return static::$attributeMutatorCache[get_class($this)][$key] = $returnType &&
            $returnType instanceof ReflectionNamedType &&
            $returnType->getName() === Attribute::class &&
            is_callable($this->{$method}()->get);
```

This side-stepping the phpdoc parsing a) still works correctly and b)
brings us back to the previous performance characteristics:
```
time ./artisan ide-helper:models --write --reset >/dev/null

real	0m2.987s
user	0m1.915s
sys	0m0.120s
```

Fixes #1291

## Type of change

<!-- Please delete options that are not relevant. -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
- [x] Misc. change (internal, infrastructure, maintenance, etc.)

### Checklist
- [x] Existing tests have been adapted and/or new tests have been added
- [ ] Add a CHANGELOG.md entry
- [ ] Update the README.md
- [x] Code style has been fixed via `composer fix-style`


Patch:
diff --git a/src/Console/ModelsCommand.php b/src/Console/ModelsCommand.php
index d7ad3f0..578991c 100644
--- a/src/Console/ModelsCommand.php
+++ b/src/Console/ModelsCommand.php
@@ -562,7 +562,7 @@ class ModelsCommand extends Command
             sort($methods);
             foreach ($methods as $method) {
                 $reflection = new \ReflectionMethod($model, $method);
-                $type = $this->getReturnType($reflection);
+                $type = $this->getReturnTypeFromReflection($reflection);
                 $isAttribute = is_a($type, '\Illuminate\Database\Eloquent\Casts\Attribute', true);
                 if (
                     Str::startsWith($method, 'get') && Str::endsWith(


Test patch:
diff --git a/tests/Console/ModelsCommand/Attributes/Models/Simple.php b/tests/Console/ModelsCommand/Attributes/Models/Simple.php
index 6457000..0d1eff8 100644
--- a/tests/Console/ModelsCommand/Attributes/Models/Simple.php
+++ b/tests/Console/ModelsCommand/Attributes/Models/Simple.php
@@ -20,4 +20,24 @@ class Simple extends Model
             }
         );
     }
+
+    /**
+     * ide-helper does not recognize this method being an Attribute
+     * because the method has no actual return type;
+     * phpdoc is ignored here deliberately due to performance reasons and also
+     * isn't supported by Laravel itself.
+     *
+     * @return \Illuminate\Database\Eloquent\Casts\Attribute
+     */
+    public function notAnAttribute()
+    {
+        return new Attribute(
+            function (?string $value): ?string {
+                return $value;
+            },
+            function (?string $value): ?string {
+                return $value;
+            }
+        );
+    }
 }
diff --git a/tests/Console/ModelsCommand/Attributes/__snapshots__/Test__test__1.php b/tests/Console/ModelsCommand/Attributes/__snapshots__/Test__test__1.php
index 7e8fcd4..eabeffc 100644
--- a/tests/Console/ModelsCommand/Attributes/__snapshots__/Test__test__1.php
+++ b/tests/Console/ModelsCommand/Attributes/__snapshots__/Test__test__1.php
@@ -31,4 +31,24 @@ class Simple extends Model
             }
         );
     }
+
+    /**
+     * ide-helper does not recognize this method being an Attribute
+     * because the method has no actual return type;
+     * phpdoc is ignored here deliberately due to performance reasons and also
+     * isn't supported by Laravel itself.
+     *
+     * @return \Illuminate\Database\Eloquent\Casts\Attribute
+     */
+    public function notAnAttribute()
+    {
+        return new Attribute(
+            function (?string $value): ?string {
+                return $value;
+            },
+            function (?string $value): ?string {
+                return $value;
+            }
+        );
+    }
 }