Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file.
- Add support for enum default arguments using enum cases. [#1464 / d8vjork](https://github.com/barryvdh/laravel-ide-helper/pull/1464)
- Add support for real-time facades in the helper file. [#1455 / filipac](https://github.com/barryvdh/laravel-ide-helper/pull/1455)
- Add support for relations with composite keys. [#1479 / calebdw](https://github.com/barryvdh/laravel-ide-helper/pull/1479)
- Add support for attribute accessors with no backing field or type hinting [#1411 / pindab0ter](https://github.com/barryvdh/laravel-ide-helper/pull/1411).

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

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

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

2023-02-04, 2.13.0
------------------
Expand Down
57 changes: 31 additions & 26 deletions src/Console/ModelsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -620,10 +620,7 @@ public function getPropertiesFromMethods($model)
// 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->getDeclaringClass()->getName() === \Illuminate\Database\Eloquent\Model::class && (
$methodReflection->getName() === 'setClassCastableAttribute' ||
$methodReflection->getName() === 'setEnumCastableAttribute'
)
Expand All @@ -649,18 +646,15 @@ public function getPropertiesFromMethods($model)
$this->setProperty($name, $type, true, null, $comment);
}
} elseif ($isAttribute) {
$name = Str::snake($method);
$types = $this->getAttributeReturnType($model, $reflection);
$comment = $this->getCommentFromDocBlock($reflection);

if ($types->has('get')) {
$type = $this->getTypeInModel($model, $types['get']);
$this->setProperty($name, $type, true, null, $comment);
}

if ($types->has('set')) {
$this->setProperty($name, null, null, true, $comment);
}
$types = $this->getAttributeTypes($model, $reflection);
$type = $this->getTypeInModel($model, $types->get('get') ?: $types->get('set')) ?: null;
$this->setProperty(
Str::snake($method),
$type,
$types->has('get'),
$types->has('set'),
$this->getCommentFromDocBlock($reflection)
);
} elseif (
Str::startsWith($method, 'set') && Str::endsWith(
$method,
Expand Down Expand Up @@ -1192,21 +1186,33 @@ protected function hasCamelCaseModelProperties()
return $this->laravel['config']->get('ide-helper.model_camel_case_properties', false);
}

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

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

return collect([
'get' => $attribute->get ? optional(new \ReflectionFunction($attribute->get))->getReturnType() : null,
'set' => $attribute->set ? optional(new \ReflectionFunction($attribute->set))->getReturnType() : null,
])
->filter()
$methods = new Collection();

if ($attribute->get) {
$methods['get'] = optional(new \ReflectionFunction($attribute->get))->getReturnType();
}
if ($attribute->set) {
$function = optional(new \ReflectionFunction($attribute->set));
if ($function->getNumberOfParameters() === 0) {
$methods['set'] = null;
} else {
$methods['set'] = $function->getParameters()[0]->getType();
}
}

return $methods
->map(function ($type) {
if ($type instanceof \ReflectionUnionType) {
if ($type === null) {
$types = collect([]);
} elseif ($type instanceof \ReflectionUnionType) {
$types = collect($type->getTypes())
/** @var ReflectionType $reflectionType */
->map(function ($reflectionType) {
Expand All @@ -1217,7 +1223,7 @@ protected function getAttributeReturnType(Model $model, \ReflectionMethod $refle
$types = collect($this->extractReflectionTypes($type));
}

if ($type->allowsNull()) {
if ($type && $type->allowsNull()) {
$types->push('null');
}

Expand Down Expand Up @@ -1467,8 +1473,7 @@ protected function getClassNameInDestinationFile(object $model, string $classNam
{
$reflection = $model instanceof ReflectionClass
? $model
: new ReflectionObject($model)
;
: new ReflectionObject($model);

$className = trim($className, '\\');
$writingToExternalFile = !$this->write || $this->write_mixin;
Expand Down
76 changes: 75 additions & 1 deletion tests/Console/ModelsCommand/Attributes/Models/Simple.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,92 @@

class Simple extends Model
{
// With a backed property
protected function name(): Attribute
{
return new Attribute(
function (?string $name): ?string {
return $name;
},
function (?string $name): ?string {
return $name === null ? null : ucfirst($name);
return $name;
}
);
}

// Without backed properties

protected function typeHintedGetAndSet(): Attribute
{
return new Attribute(
function (): ?string {
return $this->name;
},
function (?string $name) {
$this->name = $name;
}
);
}

protected function divergingTypeHintedGetAndSet(): Attribute
{
return new Attribute(
function (): int {
return strlen($this->name);
},
function (?string $name) {
$this->name = $name;
}
);
}

protected function typeHintedGet(): Attribute
{
return Attribute::get(function (): ?string {
return $this->name;
});
}

protected function typeHintedSet(): Attribute
{
return Attribute::set(function (?string $name) {
$this->name = $name;
});
}

protected function nonTypeHintedGetAndSet(): Attribute
{
return new Attribute(
function () {
return $this->name;
},
function ($name) {
$this->name = $name;
}
);
}

protected function nonTypeHintedGet(): Attribute
{
return Attribute::get(function () {
return $this->name;
});
}

protected function nonTypeHintedSet(): Attribute
{
return Attribute::set(function ($name) {
$this->name = $name;
});
}

protected function parameterlessSet(): Attribute
{
return Attribute::set(function () {
$this->name = null;
});
}

/**
* ide-helper does not recognize this method being an Attribute
* because the method has no actual return type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Attributes\Models\Simple
*
* @property integer $id
* @property int $diverging_type_hinted_get_and_set
* @property string|null $name
* @property-read mixed $non_type_hinted_get
* @property mixed $non_type_hinted_get_and_set
* @property-write mixed $non_type_hinted_set
* @property-write mixed $parameterless_set
* @property-read string|null $type_hinted_get
* @property string|null $type_hinted_get_and_set
* @property-write string|null $type_hinted_set
* @method static \Illuminate\Database\Eloquent\Builder|Simple newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Simple newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Simple query()
Expand All @@ -20,18 +28,92 @@
*/
class Simple extends Model
{
// With a backed property
protected function name(): Attribute
{
return new Attribute(
function (?string $name): ?string {
return $name;
},
function (?string $name): ?string {
return $name === null ? null : ucfirst($name);
return $name;
}
);
}

// Without backed properties

protected function typeHintedGetAndSet(): Attribute
{
return new Attribute(
function (): ?string {
return $this->name;
},
function (?string $name) {
$this->name = $name;
}
);
}

protected function divergingTypeHintedGetAndSet(): Attribute
{
return new Attribute(
function (): int {
return strlen($this->name);
},
function (?string $name) {
$this->name = $name;
}
);
}

protected function typeHintedGet(): Attribute
{
return Attribute::get(function (): ?string {
return $this->name;
});
}

protected function typeHintedSet(): Attribute
{
return Attribute::set(function (?string $name) {
$this->name = $name;
});
}

protected function nonTypeHintedGetAndSet(): Attribute
{
return new Attribute(
function () {
return $this->name;
},
function ($name) {
$this->name = $name;
}
);
}

protected function nonTypeHintedGet(): Attribute
{
return Attribute::get(function () {
return $this->name;
});
}

protected function nonTypeHintedSet(): Attribute
{
return Attribute::set(function ($name) {
$this->name = $name;
});
}

protected function parameterlessSet(): Attribute
{
return Attribute::set(function () {
$this->name = null;
});
}

/**
* ide-helper does not recognize this method being an Attribute
* because the method has no actual return type;
Expand Down