Add support for protected Attribute accessors (#1339)

* Add support for protected Attribute accessors

* Fix formatting

* Prevent accessors methods marked as private from being added

* Exclude specific accessors based on trait instead of class

* Add changelog entry

* Add clarifying comment

* Fix accessor attributes not working on PHP < 8.1

* Reintroduce method variable to reduce PR clutter

* Change variable name to reduce PR clutter

* Update CHANGELOG.md

---------

Co-authored-by: Barry vd. Heuvel <[email protected]>
Co-authored-by: Barry vd. Heuvel <[email protected]>
This commit is contained in:
Hans van Luttikhuizen-Ross
2023-02-20 09:44:01 +01:00
committed by GitHub
co-authored by Barry vd. Heuvel Barry vd. Heuvel
parent aa1aa66ee6
commit 39885645b9
6 changed files with 53 additions and 25 deletions
+5 -1
View File
@@ -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) [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 ### Added
- Add support for `immutable_date:*` and `immutable_datetime:*` casts. [#1380 / thekonz](https://github.com/barryvdh/laravel-ide-helper/pull/1380) - 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 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) - 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) - 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) - Fix issue where \Eloquent is not included when using write_mixin [#1352 / Jefemy](https://github.com/barryvdh/laravel-ide-helper/pull/1352)
+31 -15
View File
@@ -605,13 +605,27 @@ class ModelsCommand extends Command
*/ */
public function getPropertiesFromMethods($model) public function getPropertiesFromMethods($model)
{ {
$methods = get_class_methods($model); $reflectionClass = new ReflectionClass($model);
if ($methods) { $reflections = $reflectionClass->getMethods();
sort($methods); if ($reflections) {
foreach ($methods as $method) { // Filter out private methods because they can't be used to generate magic properties and HasAttributes'
$reflection = new \ReflectionMethod($model, $method); // 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); $type = $this->getReturnTypeFromReflection($reflection);
$isAttribute = is_a($type, '\Illuminate\Database\Eloquent\Casts\Attribute', true); $isAttribute = is_a($type, '\Illuminate\Database\Eloquent\Casts\Attribute', true);
$method = $reflection->getName();
if ( if (
Str::startsWith($method, 'get') && Str::endsWith( Str::startsWith($method, 'get') && Str::endsWith(
$method, $method,
@@ -628,16 +642,15 @@ class ModelsCommand extends Command
} }
} elseif ($isAttribute) { } elseif ($isAttribute) {
$name = Str::snake($method); $name = Str::snake($method);
$types = $this->getAttributeReturnType($model, $method); $types = $this->getAttributeReturnType($model, $reflection);
$comment = $this->getCommentFromDocBlock($reflection);
if ($types->has('get')) { if ($types->has('get')) {
$type = $this->getTypeInModel($model, $types['get']); $type = $this->getTypeInModel($model, $types['get']);
$comment = $this->getCommentFromDocBlock($reflection);
$this->setProperty($name, $type, true, null, $comment); $this->setProperty($name, $type, true, null, $comment);
} }
if ($types->has('set')) { if ($types->has('set')) {
$comment = $this->getCommentFromDocBlock($reflection);
$this->setProperty($name, null, null, true, $comment); $this->setProperty($name, null, null, true, $comment);
} }
} elseif ( } elseif (
@@ -713,8 +726,7 @@ class ModelsCommand extends Command
$search = '$this->' . $relation . '('; $search = '$this->' . $relation . '(';
if (stripos($code, $search) || ltrim($impl, '\\') === ltrim((string)$type, '\\')) { if (stripos($code, $search) || ltrim($impl, '\\') === ltrim((string)$type, '\\')) {
//Resolve the relation's model to a Relation object. //Resolve the relation's model to a Relation object.
$methodReflection = new \ReflectionMethod($model, $method); if ($reflection->getNumberOfParameters()) {
if ($methodReflection->getNumberOfParameters()) {
continue; continue;
} }
@@ -722,11 +734,12 @@ class ModelsCommand extends Command
// Adding constraints requires reading model properties which // Adding constraints requires reading model properties which
// can cause errors. Since we don't need constraints we can // can cause errors. Since we don't need constraints we can
// disable them when we fetch the relation to avoid errors. // 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 { try {
return $model->$method(); $methodName = $reflection->getName();
return $model->$methodName();
} catch (Throwable $e) { } 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; return null;
} }
@@ -1159,10 +1172,13 @@ class ModelsCommand extends Command
return $this->laravel['config']->get('ide-helper.model_camel_case_properties', false); 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 */ /** @var Attribute $attribute */
$attribute = $model->{$method}(); $attribute = $reflectionMethod->invoke($model);
return collect([ return collect([
'get' => $attribute->get ? optional(new \ReflectionFunction($attribute->get))->getReturnType() : null, 'get' => $attribute->get ? optional(new \ReflectionFunction($attribute->get))->getReturnType() : null,
@@ -9,7 +9,7 @@ use Illuminate\Database\Eloquent\Model;
class Simple extends Model class Simple extends Model
{ {
public function name(): Attribute protected function name(): Attribute
{ {
return new Attribute( return new Attribute(
function (?string $name): ?string { function (?string $name): ?string {
@@ -29,7 +29,7 @@ class Simple extends Model
* *
* @return \Illuminate\Database\Eloquent\Casts\Attribute * @return \Illuminate\Database\Eloquent\Casts\Attribute
*/ */
public function notAnAttribute() protected function notAnAttribute()
{ {
return new Attribute( return new Attribute(
function (?string $value): ?string { function (?string $value): ?string {
@@ -20,7 +20,7 @@ use Illuminate\Database\Eloquent\Model;
*/ */
class Simple extends Model class Simple extends Model
{ {
public function name(): Attribute protected function name(): Attribute
{ {
return new Attribute( return new Attribute(
function (?string $name): ?string { function (?string $name): ?string {
@@ -40,7 +40,7 @@ class Simple extends Model
* *
* @return \Illuminate\Database\Eloquent\Casts\Attribute * @return \Illuminate\Database\Eloquent\Casts\Attribute
*/ */
public function notAnAttribute() protected function notAnAttribute()
{ {
return new Attribute( return new Attribute(
function (?string $value): ?string { function (?string $value): ?string {
@@ -103,4 +103,8 @@ class Simple extends Model
public function getAttributeReturnsVoidAttribute(): void public function getAttributeReturnsVoidAttribute(): void
{ {
} }
private function getInvalidAccessModifierAttribute()
{
}
} }
@@ -133,4 +133,8 @@ class Simple extends Model
public function getAttributeReturnsVoidAttribute(): void public function getAttributeReturnsVoidAttribute(): void
{ {
} }
private function getInvalidAccessModifierAttribute()
{
}
} }