mirror of
https://github.com/barryvdh/laravel-ide-helper.git
synced 2026-08-19 18:43:12 +00:00
Support Laravel 8.77 Attributes (#1289)
* ✨ Set properties from model functions returning an Attribute * ✅ Add test for model Attributes * 🎨 Fix code style * 🔖 Update changelog * ✅ Update test to not require php 8 * 🐛 Fix PHP 7.3 incompatibility * ✅ Update tests to only run when Illuminate Attribute exists
This commit is contained in:
@@ -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)
|
[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
|
### 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)
|
- 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)
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ use Doctrine\DBAL\Exception as DBALException;
|
|||||||
use Doctrine\DBAL\Types\Type;
|
use Doctrine\DBAL\Types\Type;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
@@ -35,6 +36,7 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
|
|||||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||||
use Illuminate\Filesystem\Filesystem;
|
use Illuminate\Filesystem\Filesystem;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use phpDocumentor\Reflection\Types\ContextFactory;
|
use phpDocumentor\Reflection\Types\ContextFactory;
|
||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
@@ -559,6 +561,9 @@ class ModelsCommand extends Command
|
|||||||
if ($methods) {
|
if ($methods) {
|
||||||
sort($methods);
|
sort($methods);
|
||||||
foreach ($methods as $method) {
|
foreach ($methods as $method) {
|
||||||
|
$reflection = new \ReflectionMethod($model, $method);
|
||||||
|
$type = $this->getReturnType($reflection);
|
||||||
|
$isAttribute = is_a($type, '\Illuminate\Database\Eloquent\Casts\Attribute', true);
|
||||||
if (
|
if (
|
||||||
Str::startsWith($method, 'get') && Str::endsWith(
|
Str::startsWith($method, 'get') && Str::endsWith(
|
||||||
$method,
|
$method,
|
||||||
@@ -568,12 +573,25 @@ class ModelsCommand extends Command
|
|||||||
//Magic get<name>Attribute
|
//Magic get<name>Attribute
|
||||||
$name = Str::snake(substr($method, 3, -9));
|
$name = Str::snake(substr($method, 3, -9));
|
||||||
if (!empty($name)) {
|
if (!empty($name)) {
|
||||||
$reflection = new \ReflectionMethod($model, $method);
|
|
||||||
$type = $this->getReturnType($reflection);
|
$type = $this->getReturnType($reflection);
|
||||||
$type = $this->getTypeInModel($model, $type);
|
$type = $this->getTypeInModel($model, $type);
|
||||||
$comment = $this->getCommentFromDocBlock($reflection);
|
$comment = $this->getCommentFromDocBlock($reflection);
|
||||||
$this->setProperty($name, $type, true, null, $comment);
|
$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 (
|
} elseif (
|
||||||
Str::startsWith($method, 'set') && Str::endsWith(
|
Str::startsWith($method, 'set') && Str::endsWith(
|
||||||
$method,
|
$method,
|
||||||
@@ -583,7 +601,6 @@ class ModelsCommand extends Command
|
|||||||
//Magic set<name>Attribute
|
//Magic set<name>Attribute
|
||||||
$name = Str::snake(substr($method, 3, -9));
|
$name = Str::snake(substr($method, 3, -9));
|
||||||
if (!empty($name)) {
|
if (!empty($name)) {
|
||||||
$reflection = new \ReflectionMethod($model, $method);
|
|
||||||
$comment = $this->getCommentFromDocBlock($reflection);
|
$comment = $this->getCommentFromDocBlock($reflection);
|
||||||
$this->setProperty($name, null, null, true, $comment);
|
$this->setProperty($name, null, null, true, $comment);
|
||||||
}
|
}
|
||||||
@@ -591,7 +608,6 @@ class ModelsCommand extends Command
|
|||||||
//Magic set<name>Attribute
|
//Magic set<name>Attribute
|
||||||
$name = Str::camel(substr($method, 5));
|
$name = Str::camel(substr($method, 5));
|
||||||
if (!empty($name)) {
|
if (!empty($name)) {
|
||||||
$reflection = new \ReflectionMethod($model, $method);
|
|
||||||
$comment = $this->getCommentFromDocBlock($reflection);
|
$comment = $this->getCommentFromDocBlock($reflection);
|
||||||
$args = $this->getParameters($reflection);
|
$args = $this->getParameters($reflection);
|
||||||
//Remove the first ($query) argument
|
//Remove the first ($query) argument
|
||||||
@@ -622,8 +638,6 @@ class ModelsCommand extends Command
|
|||||||
&& !Str::startsWith($method, 'get')
|
&& !Str::startsWith($method, 'get')
|
||||||
) {
|
) {
|
||||||
//Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php
|
//Use reflection to inspect the code, based on Illuminate/Support/SerializableClosure.php
|
||||||
$reflection = new \ReflectionMethod($model, $method);
|
|
||||||
|
|
||||||
if ($returnType = $reflection->getReturnType()) {
|
if ($returnType = $reflection->getReturnType()) {
|
||||||
$type = $returnType instanceof ReflectionNamedType
|
$type = $returnType instanceof ReflectionNamedType
|
||||||
? $returnType->getName()
|
? $returnType->getName()
|
||||||
@@ -1056,6 +1070,36 @@ class ModelsCommand extends Command
|
|||||||
return $this->laravel['config']->get('ide-helper.model_camel_case_properties', false);
|
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())
|
||||||
|
/** @var ReflectionType $reflectionType */
|
||||||
|
->map(function ($reflectionType) {
|
||||||
|
return 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
|
protected function getReturnType(\ReflectionMethod $reflection): ?string
|
||||||
{
|
{
|
||||||
$type = $this->getReturnTypeFromDocBlock($reflection);
|
$type = $this->getReturnTypeFromDocBlock($reflection);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?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
|
||||||
|
{
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
if (!class_exists('\Illuminate\Database\Eloquent\Casts\Attribute')) {
|
||||||
|
$this->markTestSkipped('This test requires Laravel 8.77 or newer');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user