Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for protected Attribute accessors #1339

Merged
merged 12 commits into from
Feb 20, 2023
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
--------------


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

### Added
- Add support for `immutable_date:*` and `immutable_datetime:*` casts. [#1380 / thekonz](https://github.com/barryvdh/laravel-ide-helper/pull/1380)

Expand All @@ -14,7 +18,7 @@ All notable changes to this project will be documented in this file.
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)
- 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)
Expand Down
56 changes: 36 additions & 20 deletions src/Console/ModelsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -605,13 +605,27 @@ public function getPropertiesFromTable($model)
*/
public function getPropertiesFromMethods($model)
{
$methods = get_class_methods($model);
if ($methods) {
sort($methods);
foreach ($methods as $method) {
$reflection = new \ReflectionMethod($model, $method);
$reflectionClass = new ReflectionClass($model);
$reflections = $reflectionClass->getMethods();
if ($reflections) {
// Filter out private methods because they can't be used to generate magic properties and HasAttributes'
// 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);
$isAttribute = is_a($type, '\Illuminate\Database\Eloquent\Casts\Attribute', true);
$method = $reflection->getName();
if (
Str::startsWith($method, 'get') && Str::endsWith(
$method,
Expand All @@ -628,16 +642,15 @@ public function getPropertiesFromMethods($model)
}
} elseif ($isAttribute) {
$name = Str::snake($method);
$types = $this->getAttributeReturnType($model, $method);
$types = $this->getAttributeReturnType($model, $reflection);
$comment = $this->getCommentFromDocBlock($reflection);

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

if ($types->has('set')) {
$comment = $this->getCommentFromDocBlock($reflection);
$this->setProperty($name, null, null, true, $comment);
}
} elseif (
Expand Down Expand Up @@ -713,20 +726,20 @@ public function getPropertiesFromMethods($model)
$search = '$this->' . $relation . '(';
if (stripos($code, $search) || ltrim($impl, '\\') === ltrim((string)$type, '\\')) {
//Resolve the relation's model to a Relation object.
$methodReflection = new \ReflectionMethod($model, $method);
if ($methodReflection->getNumberOfParameters()) {
if ($reflection->getNumberOfParameters()) {
continue;
}

$comment = $this->getCommentFromDocBlock($reflection);
// Adding constraints requires reading model properties which
// can cause errors. Since we don't need constraints we can
// 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 {
return $model->$method();
$methodName = $reflection->getName();
return $model->$methodName();
} 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;
}
Expand Down Expand Up @@ -1159,10 +1172,13 @@ protected function hasCamelCaseModelProperties()
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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chose not to wrap this in an if-statement checking for PHP_VERSION_ID since according to the docs from PHP 8.1 onward this method call is a noop.


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

return collect([
'get' => $attribute->get ? optional(new \ReflectionFunction($attribute->get))->getReturnType() : null,
Expand All @@ -1171,7 +1187,7 @@ protected function getAttributeReturnType(Model $model, string $method): Collect
->filter()
->map(function ($type) {
if ($type instanceof \ReflectionUnionType) {
$types =collect($type->getTypes())
$types = collect($type->getTypes())
/** @var ReflectionType $reflectionType */
->map(function ($reflectionType) {
return collect($this->extractReflectionTypes($reflectionType));
Expand Down Expand Up @@ -1259,7 +1275,7 @@ protected function getReturnTypeFromReflection(\ReflectionMethod $reflection): ?
$type = implode('|', $types);

if ($returnType->allowsNull()) {
$type .='|null';
$type .= '|null';
}

return $type;
Expand Down Expand Up @@ -1501,10 +1517,10 @@ protected function getParamType(\ReflectionMethod $method, \ReflectionParameter
$type = implode('|', $types);

if ($paramType->allowsNull()) {
if (count($types)==1) {
if (count($types) == 1) {
$type = '?' . $type;
} else {
$type .='|null';
$type .= '|null';
}
}

Expand Down Expand Up @@ -1581,7 +1597,7 @@ protected function extractReflectionTypes(ReflectionType $reflection_type)
} else {
$types = [];
foreach ($reflection_type->getTypes() as $named_type) {
if ($named_type->getName()==='null') {
if ($named_type->getName() === 'null') {
continue;
}

Expand Down
4 changes: 2 additions & 2 deletions tests/Console/ModelsCommand/Attributes/Models/Simple.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class Simple extends Model
{
public function name(): Attribute
protected function name(): Attribute
{
return new Attribute(
function (?string $name): ?string {
Expand All @@ -29,7 +29,7 @@ function (?string $name): ?string {
*
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
public function notAnAttribute()
protected function notAnAttribute()
{
return new Attribute(
function (?string $value): ?string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
*/
class Simple extends Model
{
public function name(): Attribute
protected function name(): Attribute
{
return new Attribute(
function (?string $name): ?string {
Expand All @@ -40,7 +40,7 @@ function (?string $name): ?string {
*
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
public function notAnAttribute()
protected function notAnAttribute()
{
return new Attribute(
function (?string $value): ?string {
Expand Down
4 changes: 4 additions & 0 deletions tests/Console/ModelsCommand/Getter/Models/Simple.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,8 @@ public function getAttributeReturnsNullableCallableAttribute(): ?callable
public function getAttributeReturnsVoidAttribute(): void
{
}

private function getInvalidAccessModifierAttribute()
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,8 @@ public function getAttributeReturnsNullableCallableAttribute(): ?callable
public function getAttributeReturnsVoidAttribute(): void
{
}

private function getInvalidAccessModifierAttribute()
{
}
}