Skip to content

Support Laravel 8.77 Attributes #1289

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

Merged
merged 7 commits into from
Jan 3, 2022
Merged
Show file tree
Hide file tree
Changes from 5 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: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.

[Next release](https://github.com/barryvdh/laravel-ide-helper/compare/v2.10.0...master)
--------------
### Added
- Add support for Laravel 8.77 Attributes [\#1289 / SimonJnsson](https://github.com/barryvdh/laravel-ide-helper/pull/1289)

### Added
- Add support for cast types `decimal:*`, `encrypted:*`, `immutable_date`, `immutable_datetime`, `custom_datetime`, and `immutable_custom_datetime` [#1262 / miken32](https://github.com/barryvdh/laravel-ide-helper/pull/1262)

Expand Down
51 changes: 46 additions & 5 deletions src/Console/ModelsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Doctrine\DBAL\Types\Type;
use Illuminate\Console\Command;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
Expand All @@ -35,6 +36,7 @@
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use phpDocumentor\Reflection\Types\ContextFactory;
use ReflectionClass;
Expand Down Expand Up @@ -559,6 +561,9 @@ public function getPropertiesFromMethods($model)
if ($methods) {
sort($methods);
foreach ($methods as $method) {
$reflection = new \ReflectionMethod($model, $method);
$type = $this->getReturnType($reflection);
$isAttribute = is_a($type, '\Illuminate\Database\Eloquent\Casts\Attribute', true);
if (
Str::startsWith($method, 'get') && Str::endsWith(
$method,
Expand All @@ -568,12 +573,25 @@ public function getPropertiesFromMethods($model)
//Magic get<name>Attribute
$name = Str::snake(substr($method, 3, -9));
if (!empty($name)) {
$reflection = new \ReflectionMethod($model, $method);
$type = $this->getReturnType($reflection);
$type = $this->getTypeInModel($model, $type);
$comment = $this->getCommentFromDocBlock($reflection);
$this->setProperty($name, $type, true, null, $comment);
}
} elseif ($isAttribute) {
$name = Str::snake($method);
$types = $this->getAttributeReturnType($model, $method);

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 (
Str::startsWith($method, 'set') && Str::endsWith(
$method,
Expand All @@ -583,15 +601,13 @@ public function getPropertiesFromMethods($model)
//Magic set<name>Attribute
$name = Str::snake(substr($method, 3, -9));
if (!empty($name)) {
$reflection = new \ReflectionMethod($model, $method);
$comment = $this->getCommentFromDocBlock($reflection);
$this->setProperty($name, null, null, true, $comment);
}
} elseif (Str::startsWith($method, 'scope') && $method !== 'scopeQuery') {
//Magic set<name>Attribute
$name = Str::camel(substr($method, 5));
if (!empty($name)) {
$reflection = new \ReflectionMethod($model, $method);
$comment = $this->getCommentFromDocBlock($reflection);
$args = $this->getParameters($reflection);
//Remove the first ($query) argument
Expand Down Expand Up @@ -622,8 +638,6 @@ public function getPropertiesFromMethods($model)
&& !Str::startsWith($method, 'get')
) {
//Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php
$reflection = new \ReflectionMethod($model, $method);

if ($returnType = $reflection->getReturnType()) {
$type = $returnType instanceof ReflectionNamedType
? $returnType->getName()
Expand Down Expand Up @@ -1056,6 +1070,33 @@ protected function hasCamelCaseModelProperties()
return $this->laravel['config']->get('ide-helper.model_camel_case_properties', false);
}

protected function getAttributeReturnType(Model $model, string $method): Collection
{
/** @var Attribute $attribute */
$attribute = $model->{$method}();

return collect([
'get' => $attribute->get ? optional(new \ReflectionFunction($attribute->get))->getReturnType() : null,
'set' => $attribute->set ? optional(new \ReflectionFunction($attribute->set))->getReturnType() : null,
])
->filter()
->map(function ($type) {
if ($type instanceof \ReflectionUnionType) {
$types =collect($type->getTypes())
->map(fn (ReflectionType $reflectionType) => collect($this->extractReflectionTypes($reflectionType)))
->flatten();
} else {
$types = collect($this->extractReflectionTypes($type));
}

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

return $types->join('|');
});
}

protected function getReturnType(\ReflectionMethod $reflection): ?string
{
$type = $this->getReturnTypeFromDocBlock($reflection);
Expand Down
23 changes: 23 additions & 0 deletions tests/Console/ModelsCommand/Attributes/Models/Simple.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Attributes\Models;

use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;

class Simple extends Model
{
public function name(): Attribute
{
return new Attribute(
function (?string $name): ?string {
return $name;
},
function (?string $name): ?string {
return $name === null ? null : ucfirst($name);
}
);
}
}
24 changes: 24 additions & 0 deletions tests/Console/ModelsCommand/Attributes/Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Attributes;

use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\AbstractModelsCommand;

class Test extends AbstractModelsCommand
{
public function test(): void
{
$command = $this->app->make(ModelsCommand::class);

$tester = $this->runCommand($command, [
'--write' => true,
]);

$this->assertSame(0, $tester->getStatusCode());
$this->assertStringContainsString('Written new phpDocBlock to', $tester->getDisplay());
$this->assertMatchesMockedSnapshot();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Attributes\Models;

use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;

/**
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Attributes\Models\Simple
*
* @property integer $id
* @property string|null $name
* @method static \Illuminate\Database\Eloquent\Builder|Simple newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Simple newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Simple query()
* @method static \Illuminate\Database\Eloquent\Builder|Simple whereId($value)
* @mixin \Eloquent
*/
class Simple extends Model
{
public function name(): Attribute
{
return new Attribute(
function (?string $name): ?string {
return $name;
},
function (?string $name): ?string {
return $name === null ? null : ucfirst($name);
}
);
}
}