Skip to content

Commit f12d933

Browse files
pindab0terbarryvdh
andauthored
Add support for non type-hinted attribute accessors with no backed property (#1411)
* Read Attribute type from parameter * Update CHANGELOG.md * Update ModelsCommand.php --------- Co-authored-by: Barry vd. Heuvel <[email protected]> Co-authored-by: Barry vd. Heuvel <[email protected]>
1 parent 4751420 commit f12d933

File tree

4 files changed

+194
-29
lines changed

4 files changed

+194
-29
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file.
1515
- Add support for enum default arguments using enum cases. [#1464 / d8vjork](https://github.com/barryvdh/laravel-ide-helper/pull/1464)
1616
- Add support for real-time facades in the helper file. [#1455 / filipac](https://github.com/barryvdh/laravel-ide-helper/pull/1455)
1717
- Add support for relations with composite keys. [#1479 / calebdw](https://github.com/barryvdh/laravel-ide-helper/pull/1479)
18+
- Add support for attribute accessors with no backing field or type hinting [#1411 / pindab0ter](https://github.com/barryvdh/laravel-ide-helper/pull/1411).
1819

1920
2024-02-05, 2.14.0
2021
------------------
@@ -24,12 +25,12 @@ All notable changes to this project will be documented in this file.
2425
- Refactor resolving of null information for custom casted attribute types [#1330 / wimski](https://github.com/barryvdh/laravel-ide-helper/pull/1330)
2526

2627
### Fixed
27-
- Add support for attribute accessors marked as protected. [#1339 / pindab0ter](https://github.com/barryvdh/laravel-ide-helper/pull/1339)
2828
- Catch exceptions when loading aliases [#1465 / dongm2ez](https://github.com/barryvdh/laravel-ide-helper/pull/1465)
2929

3030
### Added
3131
- Add support for nikic/php-parser 5 (next to 4) [#1502 / mfn](https://github.com/barryvdh/laravel-ide-helper/pull/1502)
3232
- Add support for `immutable_date:*` and `immutable_datetime:*` casts. [#1380 / thekonz](https://github.com/barryvdh/laravel-ide-helper/pull/1380)
33+
- Add support for attribute accessors marked as protected. [#1339 / pindab0ter](https://github.com/barryvdh/laravel-ide-helper/pull/1339)
3334

3435
2023-02-04, 2.13.0
3536
------------------

src/Console/ModelsCommand.php

+34-26
Original file line numberDiff line numberDiff line change
@@ -620,10 +620,7 @@ public function getPropertiesFromMethods($model)
620620
// methods that resemble mutators but aren't.
621621
$reflections = array_filter($reflections, function (\ReflectionMethod $methodReflection) {
622622
return !$methodReflection->isPrivate() && !(
623-
in_array(
624-
\Illuminate\Database\Eloquent\Concerns\HasAttributes::class,
625-
$methodReflection->getDeclaringClass()->getTraitNames()
626-
) && (
623+
$methodReflection->getDeclaringClass()->getName() === \Illuminate\Database\Eloquent\Model::class && (
627624
$methodReflection->getName() === 'setClassCastableAttribute' ||
628625
$methodReflection->getName() === 'setEnumCastableAttribute'
629626
)
@@ -649,18 +646,15 @@ public function getPropertiesFromMethods($model)
649646
$this->setProperty($name, $type, true, null, $comment);
650647
}
651648
} elseif ($isAttribute) {
652-
$name = Str::snake($method);
653-
$types = $this->getAttributeReturnType($model, $reflection);
654-
$comment = $this->getCommentFromDocBlock($reflection);
655-
656-
if ($types->has('get')) {
657-
$type = $this->getTypeInModel($model, $types['get']);
658-
$this->setProperty($name, $type, true, null, $comment);
659-
}
660-
661-
if ($types->has('set')) {
662-
$this->setProperty($name, null, null, true, $comment);
663-
}
649+
$types = $this->getAttributeTypes($model, $reflection);
650+
$type = $this->getTypeInModel($model, $types->get('get') ?: $types->get('set')) ?: null;
651+
$this->setProperty(
652+
Str::snake($method),
653+
$type,
654+
$types->has('get'),
655+
$types->has('set'),
656+
$this->getCommentFromDocBlock($reflection)
657+
);
664658
} elseif (
665659
Str::startsWith($method, 'set') && Str::endsWith(
666660
$method,
@@ -1192,21 +1186,36 @@ protected function hasCamelCaseModelProperties()
11921186
return $this->laravel['config']->get('ide-helper.model_camel_case_properties', false);
11931187
}
11941188

1195-
protected function getAttributeReturnType(Model $model, \ReflectionMethod $reflectionMethod): Collection
1189+
/**
1190+
* @psalm-suppress NoValue
1191+
*/
1192+
protected function getAttributeTypes(Model $model, \ReflectionMethod $reflectionMethod): Collection
11961193
{
11971194
// Private/protected ReflectionMethods require setAccessible prior to PHP 8.1
11981195
$reflectionMethod->setAccessible(true);
11991196

12001197
/** @var Attribute $attribute */
12011198
$attribute = $reflectionMethod->invoke($model);
12021199

1203-
return collect([
1204-
'get' => $attribute->get ? optional(new \ReflectionFunction($attribute->get))->getReturnType() : null,
1205-
'set' => $attribute->set ? optional(new \ReflectionFunction($attribute->set))->getReturnType() : null,
1206-
])
1207-
->filter()
1200+
$methods = new Collection();
1201+
1202+
if ($attribute->get) {
1203+
$methods['get'] = optional(new \ReflectionFunction($attribute->get))->getReturnType();
1204+
}
1205+
if ($attribute->set) {
1206+
$function = optional(new \ReflectionFunction($attribute->set));
1207+
if ($function->getNumberOfParameters() === 0) {
1208+
$methods['set'] = null;
1209+
} else {
1210+
$methods['set'] = $function->getParameters()[0]->getType();
1211+
}
1212+
}
1213+
1214+
return $methods
12081215
->map(function ($type) {
1209-
if ($type instanceof \ReflectionUnionType) {
1216+
if ($type === null) {
1217+
$types = collect([]);
1218+
} elseif ($type instanceof \ReflectionUnionType) {
12101219
$types = collect($type->getTypes())
12111220
/** @var ReflectionType $reflectionType */
12121221
->map(function ($reflectionType) {
@@ -1217,7 +1226,7 @@ protected function getAttributeReturnType(Model $model, \ReflectionMethod $refle
12171226
$types = collect($this->extractReflectionTypes($type));
12181227
}
12191228

1220-
if ($type->allowsNull()) {
1229+
if ($type && $type->allowsNull()) {
12211230
$types->push('null');
12221231
}
12231232

@@ -1467,8 +1476,7 @@ protected function getClassNameInDestinationFile(object $model, string $classNam
14671476
{
14681477
$reflection = $model instanceof ReflectionClass
14691478
? $model
1470-
: new ReflectionObject($model)
1471-
;
1479+
: new ReflectionObject($model);
14721480

14731481
$className = trim($className, '\\');
14741482
$writingToExternalFile = !$this->write || $this->write_mixin;

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

+75-1
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,92 @@
99

1010
class Simple extends Model
1111
{
12+
// With a backed property
1213
protected function name(): Attribute
1314
{
1415
return new Attribute(
1516
function (?string $name): ?string {
1617
return $name;
1718
},
1819
function (?string $name): ?string {
19-
return $name === null ? null : ucfirst($name);
20+
return $name;
21+
}
22+
);
23+
}
24+
25+
// Without backed properties
26+
27+
protected function typeHintedGetAndSet(): Attribute
28+
{
29+
return new Attribute(
30+
function (): ?string {
31+
return $this->name;
32+
},
33+
function (?string $name) {
34+
$this->name = $name;
35+
}
36+
);
37+
}
38+
39+
protected function divergingTypeHintedGetAndSet(): Attribute
40+
{
41+
return new Attribute(
42+
function (): int {
43+
return strlen($this->name);
44+
},
45+
function (?string $name) {
46+
$this->name = $name;
47+
}
48+
);
49+
}
50+
51+
protected function typeHintedGet(): Attribute
52+
{
53+
return Attribute::get(function (): ?string {
54+
return $this->name;
55+
});
56+
}
57+
58+
protected function typeHintedSet(): Attribute
59+
{
60+
return Attribute::set(function (?string $name) {
61+
$this->name = $name;
62+
});
63+
}
64+
65+
protected function nonTypeHintedGetAndSet(): Attribute
66+
{
67+
return new Attribute(
68+
function () {
69+
return $this->name;
70+
},
71+
function ($name) {
72+
$this->name = $name;
2073
}
2174
);
2275
}
2376

77+
protected function nonTypeHintedGet(): Attribute
78+
{
79+
return Attribute::get(function () {
80+
return $this->name;
81+
});
82+
}
83+
84+
protected function nonTypeHintedSet(): Attribute
85+
{
86+
return Attribute::set(function ($name) {
87+
$this->name = $name;
88+
});
89+
}
90+
91+
protected function parameterlessSet(): Attribute
92+
{
93+
return Attribute::set(function () {
94+
$this->name = null;
95+
});
96+
}
97+
2498
/**
2599
* ide-helper does not recognize this method being an Attribute
26100
* because the method has no actual return type;

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

+83-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,15 @@
1111
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Attributes\Models\Simple
1212
*
1313
* @property integer $id
14+
* @property int $diverging_type_hinted_get_and_set
1415
* @property string|null $name
16+
* @property-read mixed $non_type_hinted_get
17+
* @property mixed $non_type_hinted_get_and_set
18+
* @property-write mixed $non_type_hinted_set
19+
* @property-write mixed $parameterless_set
20+
* @property-read string|null $type_hinted_get
21+
* @property string|null $type_hinted_get_and_set
22+
* @property-write string|null $type_hinted_set
1523
* @method static \Illuminate\Database\Eloquent\Builder|Simple newModelQuery()
1624
* @method static \Illuminate\Database\Eloquent\Builder|Simple newQuery()
1725
* @method static \Illuminate\Database\Eloquent\Builder|Simple query()
@@ -20,18 +28,92 @@
2028
*/
2129
class Simple extends Model
2230
{
31+
// With a backed property
2332
protected function name(): Attribute
2433
{
2534
return new Attribute(
2635
function (?string $name): ?string {
2736
return $name;
2837
},
2938
function (?string $name): ?string {
30-
return $name === null ? null : ucfirst($name);
39+
return $name;
40+
}
41+
);
42+
}
43+
44+
// Without backed properties
45+
46+
protected function typeHintedGetAndSet(): Attribute
47+
{
48+
return new Attribute(
49+
function (): ?string {
50+
return $this->name;
51+
},
52+
function (?string $name) {
53+
$this->name = $name;
54+
}
55+
);
56+
}
57+
58+
protected function divergingTypeHintedGetAndSet(): Attribute
59+
{
60+
return new Attribute(
61+
function (): int {
62+
return strlen($this->name);
63+
},
64+
function (?string $name) {
65+
$this->name = $name;
66+
}
67+
);
68+
}
69+
70+
protected function typeHintedGet(): Attribute
71+
{
72+
return Attribute::get(function (): ?string {
73+
return $this->name;
74+
});
75+
}
76+
77+
protected function typeHintedSet(): Attribute
78+
{
79+
return Attribute::set(function (?string $name) {
80+
$this->name = $name;
81+
});
82+
}
83+
84+
protected function nonTypeHintedGetAndSet(): Attribute
85+
{
86+
return new Attribute(
87+
function () {
88+
return $this->name;
89+
},
90+
function ($name) {
91+
$this->name = $name;
3192
}
3293
);
3394
}
3495

96+
protected function nonTypeHintedGet(): Attribute
97+
{
98+
return Attribute::get(function () {
99+
return $this->name;
100+
});
101+
}
102+
103+
protected function nonTypeHintedSet(): Attribute
104+
{
105+
return Attribute::set(function ($name) {
106+
$this->name = $name;
107+
});
108+
}
109+
110+
protected function parameterlessSet(): Attribute
111+
{
112+
return Attribute::set(function () {
113+
$this->name = null;
114+
});
115+
}
116+
35117
/**
36118
* ide-helper does not recognize this method being an Attribute
37119
* because the method has no actual return type;

0 commit comments

Comments
 (0)