Skip to content

Commit f2a7f9d

Browse files
authored
Improve performance for supporting Laravel 8.77+ cast attributes (#1292)
#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 ```
1 parent 29dd57e commit f2a7f9d

File tree

3 files changed

+41
-1
lines changed

3 files changed

+41
-1
lines changed

src/Console/ModelsCommand.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,7 @@ public function getPropertiesFromMethods($model)
562562
sort($methods);
563563
foreach ($methods as $method) {
564564
$reflection = new \ReflectionMethod($model, $method);
565-
$type = $this->getReturnType($reflection);
565+
$type = $this->getReturnTypeFromReflection($reflection);
566566
$isAttribute = is_a($type, '\Illuminate\Database\Eloquent\Casts\Attribute', true);
567567
if (
568568
Str::startsWith($method, 'get') && Str::endsWith(

tests/Console/ModelsCommand/Attributes/Models/Simple.php

+20
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,24 @@ function (?string $name): ?string {
2020
}
2121
);
2222
}
23+
24+
/**
25+
* ide-helper does not recognize this method being an Attribute
26+
* because the method has no actual return type;
27+
* phpdoc is ignored here deliberately due to performance reasons and also
28+
* isn't supported by Laravel itself.
29+
*
30+
* @return \Illuminate\Database\Eloquent\Casts\Attribute
31+
*/
32+
public function notAnAttribute()
33+
{
34+
return new Attribute(
35+
function (?string $value): ?string {
36+
return $value;
37+
},
38+
function (?string $value): ?string {
39+
return $value;
40+
}
41+
);
42+
}
2343
}

tests/Console/ModelsCommand/Attributes/__snapshots__/Test__test__1.php

+20
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,24 @@ function (?string $name): ?string {
3131
}
3232
);
3333
}
34+
35+
/**
36+
* ide-helper does not recognize this method being an Attribute
37+
* because the method has no actual return type;
38+
* phpdoc is ignored here deliberately due to performance reasons and also
39+
* isn't supported by Laravel itself.
40+
*
41+
* @return \Illuminate\Database\Eloquent\Casts\Attribute
42+
*/
43+
public function notAnAttribute()
44+
{
45+
return new Attribute(
46+
function (?string $value): ?string {
47+
return $value;
48+
},
49+
function (?string $value): ?string {
50+
return $value;
51+
}
52+
);
53+
}
3454
}

0 commit comments

Comments
 (0)