diff --git a/CHANGELOG.md b/CHANGELOG.md index f305c1928..a6a715a1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +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) @@ -14,7 +18,7 @@ All notable changes to this project will be documented in this file. 2023-02-04, 2.13.0 ------------------ -### Fixes +### Fixed - Fix return type of methods provided by `SoftDeletes` [#1345 / KentarouTakeda](https://github.com/barryvdh/laravel-ide-helper/pull/1345) - Handle PHP 8.1 deprecation warnings when passing `null` to `new \ReflectionClass` [#1351 / mfn](https://github.com/barryvdh/laravel-ide-helper/pull/1351) - Fix issue where \Eloquent is not included when using write_mixin [#1352 / Jefemy](https://github.com/barryvdh/laravel-ide-helper/pull/1352) diff --git a/src/Console/ModelsCommand.php b/src/Console/ModelsCommand.php index 308a11f04..9f9931f0c 100644 --- a/src/Console/ModelsCommand.php +++ b/src/Console/ModelsCommand.php @@ -605,13 +605,27 @@ public function getPropertiesFromTable($model) */ public function getPropertiesFromMethods($model) { - $methods = get_class_methods($model); - if ($methods) { - sort($methods); - foreach ($methods as $method) { - $reflection = new \ReflectionMethod($model, $method); + $reflectionClass = new ReflectionClass($model); + $reflections = $reflectionClass->getMethods(); + if ($reflections) { + // Filter out private methods because they can't be used to generate magic properties and HasAttributes' + // 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->getName() === 'setClassCastableAttribute' || + $methodReflection->getName() === 'setEnumCastableAttribute' + ) + ); + }); + sort($reflections); + foreach ($reflections as $reflection) { $type = $this->getReturnTypeFromReflection($reflection); $isAttribute = is_a($type, '\Illuminate\Database\Eloquent\Casts\Attribute', true); + $method = $reflection->getName(); if ( Str::startsWith($method, 'get') && Str::endsWith( $method, @@ -628,16 +642,15 @@ public function getPropertiesFromMethods($model) } } elseif ($isAttribute) { $name = Str::snake($method); - $types = $this->getAttributeReturnType($model, $method); + $types = $this->getAttributeReturnType($model, $reflection); + $comment = $this->getCommentFromDocBlock($reflection); if ($types->has('get')) { $type = $this->getTypeInModel($model, $types['get']); - $comment = $this->getCommentFromDocBlock($reflection); $this->setProperty($name, $type, true, null, $comment); } if ($types->has('set')) { - $comment = $this->getCommentFromDocBlock($reflection); $this->setProperty($name, null, null, true, $comment); } } elseif ( @@ -713,8 +726,7 @@ public function getPropertiesFromMethods($model) $search = '$this->' . $relation . '('; if (stripos($code, $search) || ltrim($impl, '\\') === ltrim((string)$type, '\\')) { //Resolve the relation's model to a Relation object. - $methodReflection = new \ReflectionMethod($model, $method); - if ($methodReflection->getNumberOfParameters()) { + if ($reflection->getNumberOfParameters()) { continue; } @@ -722,11 +734,12 @@ public function getPropertiesFromMethods($model) // Adding constraints requires reading model properties which // can cause errors. Since we don't need constraints we can // disable them when we fetch the relation to avoid errors. - $relationObj = Relation::noConstraints(function () use ($model, $method) { + $relationObj = Relation::noConstraints(function () use ($model, $reflection) { try { - return $model->$method(); + $methodName = $reflection->getName(); + return $model->$methodName(); } catch (Throwable $e) { - $this->warn(sprintf('Error resolving relation model of %s:%s() : %s', get_class($model), $method, $e->getMessage())); + $this->warn(sprintf('Error resolving relation model of %s:%s() : %s', get_class($model), $reflection->getName(), $e->getMessage())); return null; } @@ -1159,10 +1172,13 @@ protected function hasCamelCaseModelProperties() return $this->laravel['config']->get('ide-helper.model_camel_case_properties', false); } - protected function getAttributeReturnType(Model $model, string $method): Collection + protected function getAttributeReturnType(Model $model, \ReflectionMethod $reflectionMethod): Collection { + // Private/protected ReflectionMethods require setAccessible prior to PHP 8.1 + $reflectionMethod->setAccessible(true); + /** @var Attribute $attribute */ - $attribute = $model->{$method}(); + $attribute = $reflectionMethod->invoke($model); return collect([ 'get' => $attribute->get ? optional(new \ReflectionFunction($attribute->get))->getReturnType() : null, @@ -1171,7 +1187,7 @@ protected function getAttributeReturnType(Model $model, string $method): Collect ->filter() ->map(function ($type) { if ($type instanceof \ReflectionUnionType) { - $types =collect($type->getTypes()) + $types = collect($type->getTypes()) /** @var ReflectionType $reflectionType */ ->map(function ($reflectionType) { return collect($this->extractReflectionTypes($reflectionType)); @@ -1259,7 +1275,7 @@ protected function getReturnTypeFromReflection(\ReflectionMethod $reflection): ? $type = implode('|', $types); if ($returnType->allowsNull()) { - $type .='|null'; + $type .= '|null'; } return $type; @@ -1501,10 +1517,10 @@ protected function getParamType(\ReflectionMethod $method, \ReflectionParameter $type = implode('|', $types); if ($paramType->allowsNull()) { - if (count($types)==1) { + if (count($types) == 1) { $type = '?' . $type; } else { - $type .='|null'; + $type .= '|null'; } } @@ -1581,7 +1597,7 @@ protected function extractReflectionTypes(ReflectionType $reflection_type) } else { $types = []; foreach ($reflection_type->getTypes() as $named_type) { - if ($named_type->getName()==='null') { + if ($named_type->getName() === 'null') { continue; } diff --git a/tests/Console/ModelsCommand/Attributes/Models/Simple.php b/tests/Console/ModelsCommand/Attributes/Models/Simple.php index 0d1eff89e..f64861b44 100644 --- a/tests/Console/ModelsCommand/Attributes/Models/Simple.php +++ b/tests/Console/ModelsCommand/Attributes/Models/Simple.php @@ -9,7 +9,7 @@ class Simple extends Model { - public function name(): Attribute + protected function name(): Attribute { return new Attribute( function (?string $name): ?string { @@ -29,7 +29,7 @@ function (?string $name): ?string { * * @return \Illuminate\Database\Eloquent\Casts\Attribute */ - public function notAnAttribute() + protected function notAnAttribute() { return new Attribute( function (?string $value): ?string { diff --git a/tests/Console/ModelsCommand/Attributes/__snapshots__/Test__test__1.php b/tests/Console/ModelsCommand/Attributes/__snapshots__/Test__test__1.php index eabeffcbb..df6863950 100644 --- a/tests/Console/ModelsCommand/Attributes/__snapshots__/Test__test__1.php +++ b/tests/Console/ModelsCommand/Attributes/__snapshots__/Test__test__1.php @@ -20,7 +20,7 @@ */ class Simple extends Model { - public function name(): Attribute + protected function name(): Attribute { return new Attribute( function (?string $name): ?string { @@ -40,7 +40,7 @@ function (?string $name): ?string { * * @return \Illuminate\Database\Eloquent\Casts\Attribute */ - public function notAnAttribute() + protected function notAnAttribute() { return new Attribute( function (?string $value): ?string { diff --git a/tests/Console/ModelsCommand/Getter/Models/Simple.php b/tests/Console/ModelsCommand/Getter/Models/Simple.php index dba42eb12..639ecfc5f 100644 --- a/tests/Console/ModelsCommand/Getter/Models/Simple.php +++ b/tests/Console/ModelsCommand/Getter/Models/Simple.php @@ -103,4 +103,8 @@ public function getAttributeReturnsNullableCallableAttribute(): ?callable public function getAttributeReturnsVoidAttribute(): void { } + + private function getInvalidAccessModifierAttribute() + { + } } diff --git a/tests/Console/ModelsCommand/Getter/__snapshots__/Test__test__1.php b/tests/Console/ModelsCommand/Getter/__snapshots__/Test__test__1.php index ab67d5444..94b0a3eb3 100644 --- a/tests/Console/ModelsCommand/Getter/__snapshots__/Test__test__1.php +++ b/tests/Console/ModelsCommand/Getter/__snapshots__/Test__test__1.php @@ -133,4 +133,8 @@ public function getAttributeReturnsNullableCallableAttribute(): ?callable public function getAttributeReturnsVoidAttribute(): void { } + + private function getInvalidAccessModifierAttribute() + { + } }