Skip to content

Commit b882f79

Browse files
pindab0terbarryvdh
authored andcommitted
Add support for protected Attribute accessors (barryvdh#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 <barry@fruitcake.nl> Co-authored-by: Barry vd. Heuvel <barryvdh@gmail.com>
1 parent 67dc1db commit b882f79

File tree

6 files changed

+53
-25
lines changed

6 files changed

+53
-25
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
55
[Next release](https://github.com/barryvdh/laravel-ide-helper/compare/v2.13.0...master)
66
--------------
77

8+
9+
### Fixed
10+
- Add support for attribute accessors marked as protected. [#1339 / pindab0ter](https://github.com/barryvdh/laravel-ide-helper/pull/1339)
11+
812
### Added
913
- Add support for `immutable_date:*` and `immutable_datetime:*` casts. [#1380 / thekonz](https://github.com/barryvdh/laravel-ide-helper/pull/1380)
1014

@@ -14,7 +18,7 @@ All notable changes to this project will be documented in this file.
1418
2023-02-04, 2.13.0
1519
------------------
1620

17-
### Fixes
21+
### Fixed
1822
- Fix return type of methods provided by `SoftDeletes` [#1345 / KentarouTakeda](https://github.com/barryvdh/laravel-ide-helper/pull/1345)
1923
- Handle PHP 8.1 deprecation warnings when passing `null` to `new \ReflectionClass` [#1351 / mfn](https://github.com/barryvdh/laravel-ide-helper/pull/1351)
2024
- Fix issue where \Eloquent is not included when using write_mixin [#1352 / Jefemy](https://github.com/barryvdh/laravel-ide-helper/pull/1352)

src/Console/ModelsCommand.php

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -605,13 +605,27 @@ public function getPropertiesFromTable($model)
605605
*/
606606
public function getPropertiesFromMethods($model)
607607
{
608-
$methods = get_class_methods($model);
609-
if ($methods) {
610-
sort($methods);
611-
foreach ($methods as $method) {
612-
$reflection = new \ReflectionMethod($model, $method);
608+
$reflectionClass = new ReflectionClass($model);
609+
$reflections = $reflectionClass->getMethods();
610+
if ($reflections) {
611+
// Filter out private methods because they can't be used to generate magic properties and HasAttributes'
612+
// methods that resemble mutators but aren't.
613+
$reflections = array_filter($reflections, function (\ReflectionMethod $methodReflection) {
614+
return !$methodReflection->isPrivate() && !(
615+
in_array(
616+
\Illuminate\Database\Eloquent\Concerns\HasAttributes::class,
617+
$methodReflection->getDeclaringClass()->getTraitNames()
618+
) && (
619+
$methodReflection->getName() === 'setClassCastableAttribute' ||
620+
$methodReflection->getName() === 'setEnumCastableAttribute'
621+
)
622+
);
623+
});
624+
sort($reflections);
625+
foreach ($reflections as $reflection) {
613626
$type = $this->getReturnTypeFromReflection($reflection);
614627
$isAttribute = is_a($type, '\Illuminate\Database\Eloquent\Casts\Attribute', true);
628+
$method = $reflection->getName();
615629
if (
616630
Str::startsWith($method, 'get') && Str::endsWith(
617631
$method,
@@ -628,16 +642,15 @@ public function getPropertiesFromMethods($model)
628642
}
629643
} elseif ($isAttribute) {
630644
$name = Str::snake($method);
631-
$types = $this->getAttributeReturnType($model, $method);
645+
$types = $this->getAttributeReturnType($model, $reflection);
646+
$comment = $this->getCommentFromDocBlock($reflection);
632647

633648
if ($types->has('get')) {
634649
$type = $this->getTypeInModel($model, $types['get']);
635-
$comment = $this->getCommentFromDocBlock($reflection);
636650
$this->setProperty($name, $type, true, null, $comment);
637651
}
638652

639653
if ($types->has('set')) {
640-
$comment = $this->getCommentFromDocBlock($reflection);
641654
$this->setProperty($name, null, null, true, $comment);
642655
}
643656
} elseif (
@@ -713,20 +726,20 @@ public function getPropertiesFromMethods($model)
713726
$search = '$this->' . $relation . '(';
714727
if (stripos($code, $search) || ltrim($impl, '\\') === ltrim((string)$type, '\\')) {
715728
//Resolve the relation's model to a Relation object.
716-
$methodReflection = new \ReflectionMethod($model, $method);
717-
if ($methodReflection->getNumberOfParameters()) {
729+
if ($reflection->getNumberOfParameters()) {
718730
continue;
719731
}
720732

721733
$comment = $this->getCommentFromDocBlock($reflection);
722734
// Adding constraints requires reading model properties which
723735
// can cause errors. Since we don't need constraints we can
724736
// disable them when we fetch the relation to avoid errors.
725-
$relationObj = Relation::noConstraints(function () use ($model, $method) {
737+
$relationObj = Relation::noConstraints(function () use ($model, $reflection) {
726738
try {
727-
return $model->$method();
739+
$methodName = $reflection->getName();
740+
return $model->$methodName();
728741
} catch (Throwable $e) {
729-
$this->warn(sprintf('Error resolving relation model of %s:%s() : %s', get_class($model), $method, $e->getMessage()));
742+
$this->warn(sprintf('Error resolving relation model of %s:%s() : %s', get_class($model), $reflection->getName(), $e->getMessage()));
730743

731744
return null;
732745
}
@@ -1170,10 +1183,13 @@ protected function hasCamelCaseModelProperties()
11701183
return $this->laravel['config']->get('ide-helper.model_camel_case_properties', false);
11711184
}
11721185

1173-
protected function getAttributeReturnType(Model $model, string $method): Collection
1186+
protected function getAttributeReturnType(Model $model, \ReflectionMethod $reflectionMethod): Collection
11741187
{
1188+
// Private/protected ReflectionMethods require setAccessible prior to PHP 8.1
1189+
$reflectionMethod->setAccessible(true);
1190+
11751191
/** @var Attribute $attribute */
1176-
$attribute = $model->{$method}();
1192+
$attribute = $reflectionMethod->invoke($model);
11771193

11781194
return collect([
11791195
'get' => $attribute->get ? optional(new \ReflectionFunction($attribute->get))->getReturnType() : null,
@@ -1182,7 +1198,7 @@ protected function getAttributeReturnType(Model $model, string $method): Collect
11821198
->filter()
11831199
->map(function ($type) {
11841200
if ($type instanceof \ReflectionUnionType) {
1185-
$types =collect($type->getTypes())
1201+
$types = collect($type->getTypes())
11861202
/** @var ReflectionType $reflectionType */
11871203
->map(function ($reflectionType) {
11881204
return collect($this->extractReflectionTypes($reflectionType));
@@ -1270,7 +1286,7 @@ protected function getReturnTypeFromReflection(\ReflectionMethod $reflection): ?
12701286
$type = implode('|', $types);
12711287

12721288
if ($returnType->allowsNull()) {
1273-
$type .='|null';
1289+
$type .= '|null';
12741290
}
12751291

12761292
return $type;
@@ -1512,10 +1528,10 @@ protected function getParamType(\ReflectionMethod $method, \ReflectionParameter
15121528
$type = implode('|', $types);
15131529

15141530
if ($paramType->allowsNull()) {
1515-
if (count($types)==1) {
1531+
if (count($types) == 1) {
15161532
$type = '?' . $type;
15171533
} else {
1518-
$type .='|null';
1534+
$type .= '|null';
15191535
}
15201536
}
15211537

@@ -1592,7 +1608,7 @@ protected function extractReflectionTypes(ReflectionType $reflection_type)
15921608
} else {
15931609
$types = [];
15941610
foreach ($reflection_type->getTypes() as $named_type) {
1595-
if ($named_type->getName()==='null') {
1611+
if ($named_type->getName() === 'null') {
15961612
continue;
15971613
}
15981614

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
class Simple extends Model
1111
{
12-
public function name(): Attribute
12+
protected function name(): Attribute
1313
{
1414
return new Attribute(
1515
function (?string $name): ?string {
@@ -29,7 +29,7 @@ function (?string $name): ?string {
2929
*
3030
* @return \Illuminate\Database\Eloquent\Casts\Attribute
3131
*/
32-
public function notAnAttribute()
32+
protected function notAnAttribute()
3333
{
3434
return new Attribute(
3535
function (?string $value): ?string {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
*/
2121
class Simple extends Model
2222
{
23-
public function name(): Attribute
23+
protected function name(): Attribute
2424
{
2525
return new Attribute(
2626
function (?string $name): ?string {
@@ -40,7 +40,7 @@ function (?string $name): ?string {
4040
*
4141
* @return \Illuminate\Database\Eloquent\Casts\Attribute
4242
*/
43-
public function notAnAttribute()
43+
protected function notAnAttribute()
4444
{
4545
return new Attribute(
4646
function (?string $value): ?string {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,8 @@ public function getAttributeReturnsNullableCallableAttribute(): ?callable
103103
public function getAttributeReturnsVoidAttribute(): void
104104
{
105105
}
106+
107+
private function getInvalidAccessModifierAttribute()
108+
{
109+
}
106110
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,8 @@ public function getAttributeReturnsNullableCallableAttribute(): ?callable
133133
public function getAttributeReturnsVoidAttribute(): void
134134
{
135135
}
136+
137+
private function getInvalidAccessModifierAttribute()
138+
{
139+
}
136140
}

0 commit comments

Comments
 (0)