Skip to content

Commit 7bc77e3

Browse files
committed
Read Attribute type from parameter
1 parent 3988564 commit 7bc77e3

File tree

4 files changed

+191
-32
lines changed

4 files changed

+191
-32
lines changed

CHANGELOG.md

+2-4
Original file line numberDiff line numberDiff line change
@@ -5,12 +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-
128
### Added
139
- Add support for `immutable_date:*` and `immutable_datetime:*` casts. [#1380 / thekonz](https://github.com/barryvdh/laravel-ide-helper/pull/1380)
10+
- Add support for attribute accessors marked as protected. [#1339 / pindab0ter](https://github.com/barryvdh/laravel-ide-helper/pull/1339)
11+
- Add support for attribute accessors with no backing field or type hinting [#1315 / pindab0ter](https://github.com/barryvdh/laravel-ide-helper/pull/1338).
1412

1513
### Changed
1614
- Refactor resolving of null information for custom casted attribute types [#1330 / wimski](https://github.com/barryvdh/laravel-ide-helper/pull/1330)

src/Console/ModelsCommand.php

+31-26
Original file line numberDiff line numberDiff line change
@@ -612,10 +612,7 @@ public function getPropertiesFromMethods($model)
612612
// methods that resemble mutators but aren't.
613613
$reflections = array_filter($reflections, function (\ReflectionMethod $methodReflection) {
614614
return !$methodReflection->isPrivate() && !(
615-
in_array(
616-
\Illuminate\Database\Eloquent\Concerns\HasAttributes::class,
617-
$methodReflection->getDeclaringClass()->getTraitNames()
618-
) && (
615+
$methodReflection->getDeclaringClass()->getName() === \Illuminate\Database\Eloquent\Model::class && (
619616
$methodReflection->getName() === 'setClassCastableAttribute' ||
620617
$methodReflection->getName() === 'setEnumCastableAttribute'
621618
)
@@ -641,18 +638,15 @@ public function getPropertiesFromMethods($model)
641638
$this->setProperty($name, $type, true, null, $comment);
642639
}
643640
} elseif ($isAttribute) {
644-
$name = Str::snake($method);
645-
$types = $this->getAttributeReturnType($model, $reflection);
646-
$comment = $this->getCommentFromDocBlock($reflection);
647-
648-
if ($types->has('get')) {
649-
$type = $this->getTypeInModel($model, $types['get']);
650-
$this->setProperty($name, $type, true, null, $comment);
651-
}
652-
653-
if ($types->has('set')) {
654-
$this->setProperty($name, null, null, true, $comment);
655-
}
641+
$types = $this->getAttributeTypes($model, $reflection);
642+
$type = $this->getTypeInModel($model, $types->get('get') ?: $types->get('set')) ?: null;
643+
$this->setProperty(
644+
Str::snake($method),
645+
$type,
646+
$types->has('get'),
647+
$types->has('set'),
648+
$this->getCommentFromDocBlock($reflection)
649+
);
656650
} elseif (
657651
Str::startsWith($method, 'set') && Str::endsWith(
658652
$method,
@@ -1172,21 +1166,33 @@ protected function hasCamelCaseModelProperties()
11721166
return $this->laravel['config']->get('ide-helper.model_camel_case_properties', false);
11731167
}
11741168

1175-
protected function getAttributeReturnType(Model $model, \ReflectionMethod $reflectionMethod): Collection
1169+
protected function getAttributeTypes(Model $model, \ReflectionMethod $reflectionMethod): Collection
11761170
{
11771171
// Private/protected ReflectionMethods require setAccessible prior to PHP 8.1
11781172
$reflectionMethod->setAccessible(true);
11791173

11801174
/** @var Attribute $attribute */
11811175
$attribute = $reflectionMethod->invoke($model);
11821176

1183-
return collect([
1184-
'get' => $attribute->get ? optional(new \ReflectionFunction($attribute->get))->getReturnType() : null,
1185-
'set' => $attribute->set ? optional(new \ReflectionFunction($attribute->set))->getReturnType() : null,
1186-
])
1187-
->filter()
1177+
$methods = new Collection();
1178+
1179+
if ($attribute->get) {
1180+
$methods['get'] = optional(new \ReflectionFunction($attribute->get))->getReturnType();
1181+
}
1182+
if ($attribute->set) {
1183+
$function = optional(new \ReflectionFunction($attribute->set));
1184+
if ($function->getNumberOfParameters() === 0) {
1185+
$methods['set'] = null;
1186+
} else {
1187+
$methods['set'] = $function->getParameters()[0]->getType();
1188+
}
1189+
}
1190+
1191+
return $methods
11881192
->map(function ($type) {
1189-
if ($type instanceof \ReflectionUnionType) {
1193+
if ($type === null) {
1194+
$types = collect([]);
1195+
} elseif ($type instanceof \ReflectionUnionType) {
11901196
$types = collect($type->getTypes())
11911197
/** @var ReflectionType $reflectionType */
11921198
->map(function ($reflectionType) {
@@ -1197,7 +1203,7 @@ protected function getAttributeReturnType(Model $model, \ReflectionMethod $refle
11971203
$types = collect($this->extractReflectionTypes($type));
11981204
}
11991205

1200-
if ($type->allowsNull()) {
1206+
if ($type && $type->allowsNull()) {
12011207
$types->push('null');
12021208
}
12031209

@@ -1451,8 +1457,7 @@ protected function getClassNameInDestinationFile(object $model, string $classNam
14511457
{
14521458
$reflection = $model instanceof ReflectionClass
14531459
? $model
1454-
: new ReflectionObject($model)
1455-
;
1460+
: new ReflectionObject($model);
14561461

14571462
$className = trim($className, '\\');
14581463
$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)