From 7bc77e361885330a93042e101e8883700a05ea4c Mon Sep 17 00:00:00 2001 From: Hans van Luttikhuizen-Ross Date: Wed, 6 Apr 2022 11:29:53 +0200 Subject: [PATCH 1/3] Read Attribute type from parameter --- CHANGELOG.md | 6 +- src/Console/ModelsCommand.php | 57 +++++++------ .../Attributes/Models/Simple.php | 76 ++++++++++++++++- .../__snapshots__/Test__test__1.php | 84 ++++++++++++++++++- 4 files changed, 191 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6a715a1a..2f08e096e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,10 @@ All notable changes to this project will be documented in this file. [Next release](https://github.com/barryvdh/laravel-ide-helper/compare/v2.13.0...master) -------------- - -### Fixed -- Add support for attribute accessors marked as protected. [#1339 / pindab0ter](https://github.com/barryvdh/laravel-ide-helper/pull/1339) - ### Added - Add support for `immutable_date:*` and `immutable_datetime:*` casts. [#1380 / thekonz](https://github.com/barryvdh/laravel-ide-helper/pull/1380) +- Add support for attribute accessors marked as protected. [#1339 / pindab0ter](https://github.com/barryvdh/laravel-ide-helper/pull/1339) +- Add support for attribute accessors with no backing field or type hinting [#1315 / pindab0ter](https://github.com/barryvdh/laravel-ide-helper/pull/1338). ### Changed - Refactor resolving of null information for custom casted attribute types [#1330 / wimski](https://github.com/barryvdh/laravel-ide-helper/pull/1330) diff --git a/src/Console/ModelsCommand.php b/src/Console/ModelsCommand.php index 9f9931f0c..7b35964f7 100644 --- a/src/Console/ModelsCommand.php +++ b/src/Console/ModelsCommand.php @@ -612,10 +612,7 @@ public function getPropertiesFromMethods($model) // methods that resemble mutators but aren't. $reflections = array_filter($reflections, function (\ReflectionMethod $methodReflection) { return !$methodReflection->isPrivate() && !( - in_array( - \Illuminate\Database\Eloquent\Concerns\HasAttributes::class, - $methodReflection->getDeclaringClass()->getTraitNames() - ) && ( + $methodReflection->getDeclaringClass()->getName() === \Illuminate\Database\Eloquent\Model::class && ( $methodReflection->getName() === 'setClassCastableAttribute' || $methodReflection->getName() === 'setEnumCastableAttribute' ) @@ -641,18 +638,15 @@ public function getPropertiesFromMethods($model) $this->setProperty($name, $type, true, null, $comment); } } elseif ($isAttribute) { - $name = Str::snake($method); - $types = $this->getAttributeReturnType($model, $reflection); - $comment = $this->getCommentFromDocBlock($reflection); - - if ($types->has('get')) { - $type = $this->getTypeInModel($model, $types['get']); - $this->setProperty($name, $type, true, null, $comment); - } - - if ($types->has('set')) { - $this->setProperty($name, null, null, true, $comment); - } + $types = $this->getAttributeTypes($model, $reflection); + $type = $this->getTypeInModel($model, $types->get('get') ?: $types->get('set')) ?: null; + $this->setProperty( + Str::snake($method), + $type, + $types->has('get'), + $types->has('set'), + $this->getCommentFromDocBlock($reflection) + ); } elseif ( Str::startsWith($method, 'set') && Str::endsWith( $method, @@ -1172,7 +1166,7 @@ protected function hasCamelCaseModelProperties() return $this->laravel['config']->get('ide-helper.model_camel_case_properties', false); } - protected function getAttributeReturnType(Model $model, \ReflectionMethod $reflectionMethod): Collection + protected function getAttributeTypes(Model $model, \ReflectionMethod $reflectionMethod): Collection { // Private/protected ReflectionMethods require setAccessible prior to PHP 8.1 $reflectionMethod->setAccessible(true); @@ -1180,13 +1174,25 @@ protected function getAttributeReturnType(Model $model, \ReflectionMethod $refle /** @var Attribute $attribute */ $attribute = $reflectionMethod->invoke($model); - return collect([ - 'get' => $attribute->get ? optional(new \ReflectionFunction($attribute->get))->getReturnType() : null, - 'set' => $attribute->set ? optional(new \ReflectionFunction($attribute->set))->getReturnType() : null, - ]) - ->filter() + $methods = new Collection(); + + if ($attribute->get) { + $methods['get'] = optional(new \ReflectionFunction($attribute->get))->getReturnType(); + } + if ($attribute->set) { + $function = optional(new \ReflectionFunction($attribute->set)); + if ($function->getNumberOfParameters() === 0) { + $methods['set'] = null; + } else { + $methods['set'] = $function->getParameters()[0]->getType(); + } + } + + return $methods ->map(function ($type) { - if ($type instanceof \ReflectionUnionType) { + if ($type === null) { + $types = collect([]); + } elseif ($type instanceof \ReflectionUnionType) { $types = collect($type->getTypes()) /** @var ReflectionType $reflectionType */ ->map(function ($reflectionType) { @@ -1197,7 +1203,7 @@ protected function getAttributeReturnType(Model $model, \ReflectionMethod $refle $types = collect($this->extractReflectionTypes($type)); } - if ($type->allowsNull()) { + if ($type && $type->allowsNull()) { $types->push('null'); } @@ -1451,8 +1457,7 @@ protected function getClassNameInDestinationFile(object $model, string $classNam { $reflection = $model instanceof ReflectionClass ? $model - : new ReflectionObject($model) - ; + : new ReflectionObject($model); $className = trim($className, '\\'); $writingToExternalFile = !$this->write || $this->write_mixin; diff --git a/tests/Console/ModelsCommand/Attributes/Models/Simple.php b/tests/Console/ModelsCommand/Attributes/Models/Simple.php index f64861b44..d0b54c38c 100644 --- a/tests/Console/ModelsCommand/Attributes/Models/Simple.php +++ b/tests/Console/ModelsCommand/Attributes/Models/Simple.php @@ -9,6 +9,7 @@ class Simple extends Model { + // With a backed property protected function name(): Attribute { return new Attribute( @@ -16,11 +17,84 @@ function (?string $name): ?string { return $name; }, function (?string $name): ?string { - return $name === null ? null : ucfirst($name); + return $name; + } + ); + } + + // Without backed properties + + protected function typeHintedGetAndSet(): Attribute + { + return new Attribute( + function (): ?string { + return $this->name; + }, + function (?string $name) { + $this->name = $name; + } + ); + } + + protected function divergingTypeHintedGetAndSet(): Attribute + { + return new Attribute( + function (): int { + return strlen($this->name); + }, + function (?string $name) { + $this->name = $name; + } + ); + } + + protected function typeHintedGet(): Attribute + { + return Attribute::get(function (): ?string { + return $this->name; + }); + } + + protected function typeHintedSet(): Attribute + { + return Attribute::set(function (?string $name) { + $this->name = $name; + }); + } + + protected function nonTypeHintedGetAndSet(): Attribute + { + return new Attribute( + function () { + return $this->name; + }, + function ($name) { + $this->name = $name; } ); } + protected function nonTypeHintedGet(): Attribute + { + return Attribute::get(function () { + return $this->name; + }); + } + + protected function nonTypeHintedSet(): Attribute + { + return Attribute::set(function ($name) { + $this->name = $name; + }); + } + + protected function parameterlessSet(): Attribute + { + return Attribute::set(function () { + $this->name = null; + }); + } + /** * ide-helper does not recognize this method being an Attribute * because the method has no actual return type; diff --git a/tests/Console/ModelsCommand/Attributes/__snapshots__/Test__test__1.php b/tests/Console/ModelsCommand/Attributes/__snapshots__/Test__test__1.php index df6863950..fea3cc70e 100644 --- a/tests/Console/ModelsCommand/Attributes/__snapshots__/Test__test__1.php +++ b/tests/Console/ModelsCommand/Attributes/__snapshots__/Test__test__1.php @@ -11,7 +11,15 @@ * Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Attributes\Models\Simple * * @property integer $id + * @property int $diverging_type_hinted_get_and_set * @property string|null $name + * @property-read mixed $non_type_hinted_get + * @property mixed $non_type_hinted_get_and_set + * @property-write mixed $non_type_hinted_set + * @property-write mixed $parameterless_set + * @property-read string|null $type_hinted_get + * @property string|null $type_hinted_get_and_set + * @property-write string|null $type_hinted_set * @method static \Illuminate\Database\Eloquent\Builder|Simple newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Simple newQuery() * @method static \Illuminate\Database\Eloquent\Builder|Simple query() @@ -20,6 +28,7 @@ */ class Simple extends Model { + // With a backed property protected function name(): Attribute { return new Attribute( @@ -27,11 +36,84 @@ function (?string $name): ?string { return $name; }, function (?string $name): ?string { - return $name === null ? null : ucfirst($name); + return $name; + } + ); + } + + // Without backed properties + + protected function typeHintedGetAndSet(): Attribute + { + return new Attribute( + function (): ?string { + return $this->name; + }, + function (?string $name) { + $this->name = $name; + } + ); + } + + protected function divergingTypeHintedGetAndSet(): Attribute + { + return new Attribute( + function (): int { + return strlen($this->name); + }, + function (?string $name) { + $this->name = $name; + } + ); + } + + protected function typeHintedGet(): Attribute + { + return Attribute::get(function (): ?string { + return $this->name; + }); + } + + protected function typeHintedSet(): Attribute + { + return Attribute::set(function (?string $name) { + $this->name = $name; + }); + } + + protected function nonTypeHintedGetAndSet(): Attribute + { + return new Attribute( + function () { + return $this->name; + }, + function ($name) { + $this->name = $name; } ); } + protected function nonTypeHintedGet(): Attribute + { + return Attribute::get(function () { + return $this->name; + }); + } + + protected function nonTypeHintedSet(): Attribute + { + return Attribute::set(function ($name) { + $this->name = $name; + }); + } + + protected function parameterlessSet(): Attribute + { + return Attribute::set(function () { + $this->name = null; + }); + } + /** * ide-helper does not recognize this method being an Attribute * because the method has no actual return type; From b0e7df321b6633422a48516531486a171737c785 Mon Sep 17 00:00:00 2001 From: "Barry vd. Heuvel" Date: Thu, 8 Feb 2024 09:25:22 +0100 Subject: [PATCH 2/3] Update CHANGELOG.md --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ab53cb64..ca3bb91e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,6 @@ All notable changes to this project will be documented in this file. - Add support for enum default arguments using enum cases. [#1464 / d8vjork](https://github.com/barryvdh/laravel-ide-helper/pull/1464) - Add support for real-time facades in the helper file. [#1455 / filipac](https://github.com/barryvdh/laravel-ide-helper/pull/1455) - Add support for relations with composite keys. [#1479 / calebdw](https://github.com/barryvdh/laravel-ide-helper/pull/1479) -- Add support for attribute accessors marked as protected. [#1339 / pindab0ter](https://github.com/barryvdh/laravel-ide-helper/pull/1339) - Add support for attribute accessors with no backing field or type hinting [#1411 / pindab0ter](https://github.com/barryvdh/laravel-ide-helper/pull/1411). 2024-02-05, 2.14.0 @@ -26,12 +25,12 @@ All notable changes to this project will be documented in this file. - Refactor resolving of null information for custom casted attribute types [#1330 / wimski](https://github.com/barryvdh/laravel-ide-helper/pull/1330) ### Fixed -- Add support for attribute accessors marked as protected. [#1339 / pindab0ter](https://github.com/barryvdh/laravel-ide-helper/pull/1339) - Catch exceptions when loading aliases [#1465 / dongm2ez](https://github.com/barryvdh/laravel-ide-helper/pull/1465) ### Added - Add support for nikic/php-parser 5 (next to 4) [#1502 / mfn](https://github.com/barryvdh/laravel-ide-helper/pull/1502) - Add support for `immutable_date:*` and `immutable_datetime:*` casts. [#1380 / thekonz](https://github.com/barryvdh/laravel-ide-helper/pull/1380) +- Add support for attribute accessors marked as protected. [#1339 / pindab0ter](https://github.com/barryvdh/laravel-ide-helper/pull/1339) 2023-02-04, 2.13.0 ------------------ From 11be0e05e8528937441fdf269dc5495916063d7b Mon Sep 17 00:00:00 2001 From: "Barry vd. Heuvel" Date: Thu, 8 Feb 2024 09:36:35 +0100 Subject: [PATCH 3/3] Update ModelsCommand.php --- src/Console/ModelsCommand.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Console/ModelsCommand.php b/src/Console/ModelsCommand.php index 7913effb3..98991cdd7 100644 --- a/src/Console/ModelsCommand.php +++ b/src/Console/ModelsCommand.php @@ -1186,6 +1186,9 @@ protected function hasCamelCaseModelProperties() return $this->laravel['config']->get('ide-helper.model_camel_case_properties', false); } + /** + * @psalm-suppress NoValue + */ protected function getAttributeTypes(Model $model, \ReflectionMethod $reflectionMethod): Collection { // Private/protected ReflectionMethods require setAccessible prior to PHP 8.1