diff --git a/CHANGELOG.md b/CHANGELOG.md index dcd8828..b562403 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file. ### 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 custom casts that using `Castable` [#1287 / binotaliu](https://github.com/barryvdh/laravel-ide-helper/pull/1287) ### Fixed - Fix recursively searching for `HasFactory` and `Macroable` traits [\#1216 / daniel-de-wit](https://github.com/barryvdh/laravel-ide-helper/pull/1216) diff --git a/src/Console/ModelsCommand.php b/src/Console/ModelsCommand.php index 578991c..b498b4f 100644 --- a/src/Console/ModelsCommand.php +++ b/src/Console/ModelsCommand.php @@ -20,6 +20,7 @@ use Composer\Autoload\ClassMapGenerator; use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Types\Type; use Illuminate\Console\Command; +use Illuminate\Contracts\Database\Eloquent\Castable; use Illuminate\Contracts\Database\Eloquent\CastsAttributes; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\Factory; @@ -366,6 +367,9 @@ class ModelsCommand extends Command } elseif (Str::startsWith($type, 'encrypted:')) { $type = Str::after($type, ':'); } + + $params = []; + switch ($type) { case 'encrypted': $realType = 'mixed'; @@ -412,6 +416,9 @@ class ModelsCommand extends Command $type = strtok($type, ':'); $realType = class_exists($type) ? ('\\' . $type) : 'mixed'; $this->setProperty($name, null, true, true); + + $params = strtok(':'); + $params = $params ? explode(',', $params) : []; break; } @@ -419,6 +426,7 @@ class ModelsCommand extends Command continue; } + $realType = $this->checkForCastableCasts($realType, $params); $realType = $this->checkForCustomLaravelCasts($realType); $realType = $this->getTypeOverride($realType); $this->properties[$name]['type'] = $this->getTypeInModel($model, $realType); @@ -1141,9 +1149,9 @@ class ModelsCommand extends Command * * @return null|string */ - protected function getReturnTypeFromDocBlock(\ReflectionMethod $reflection) + protected function getReturnTypeFromDocBlock(\ReflectionMethod $reflection, \Reflector $reflectorForContext = null) { - $phpDocContext = (new ContextFactory())->createFromReflector($reflection); + $phpDocContext = (new ContextFactory())->createFromReflector($reflectorForContext ?? $reflection); $context = new Context( $phpDocContext->getNamespace(), $phpDocContext->getNamespaceAliases() @@ -1260,6 +1268,32 @@ class ModelsCommand extends Command return $keyword; } + protected function checkForCastableCasts(string $type, array $params = []): string + { + if (!class_exists($type) || !interface_exists(Castable::class)) { + return $type; + } + + $reflection = new \ReflectionClass($type); + + if (!$reflection->implementsInterface(Castable::class)) { + return $type; + } + + $cast = call_user_func([$type, 'castUsing'], $params); + + if (is_string($cast) && !is_object($cast)) { + return $cast; + } + + $castReflection = new ReflectionObject($cast); + + $methodReflection = $castReflection->getMethod('get'); + + return $this->getReturnTypeFromReflection($methodReflection) ?? + $this->getReturnTypeFromDocBlock($methodReflection, $reflection); + } + /** * @param string $type * @return string|null diff --git a/tests/Console/ModelsCommand/LaravelCustomCasts/Casts/CastableReturnsAnonymousCaster.php b/tests/Console/ModelsCommand/LaravelCustomCasts/Casts/CastableReturnsAnonymousCaster.php new file mode 100644 index 0000000..268e357 --- /dev/null +++ b/tests/Console/ModelsCommand/LaravelCustomCasts/Casts/CastableReturnsAnonymousCaster.php @@ -0,0 +1,33 @@ + CustomCasterWithParam::class . ':param', 'casted_property_with_static_return_docblock' => SelfCastingCasterWithStaticDocblockReturn::class, 'casted_property_with_this_return_docblock' => SelfCastingCasterWithThisDocblockReturn::class, + 'casted_property_with_castable' => CastableReturnsCustomCaster::class, + 'casted_property_with_anonymous_cast' => CastableReturnsAnonymousCaster::class, 'extended_casted_property_with_static_return_docblock' => ExtendedSelfCastingCasterWithStaticDocblockReturn::class, 'extended_casted_property_with_this_return_docblock' => ExtendedSelfCastingCasterWithThisDocblockReturn::class, 'casted_property_with_static_return_docblock_and_param' => SelfCastingCasterWithStaticDocblockReturn::class . ':param', diff --git a/tests/Console/ModelsCommand/LaravelCustomCasts/__snapshots__/Test__test__1.php b/tests/Console/ModelsCommand/LaravelCustomCasts/__snapshots__/Test__test__1.php index 2bc8716..2454d8f 100644 --- a/tests/Console/ModelsCommand/LaravelCustomCasts/__snapshots__/Test__test__1.php +++ b/tests/Console/ModelsCommand/LaravelCustomCasts/__snapshots__/Test__test__1.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Models; +use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CastableReturnsAnonymousCaster; +use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CastableReturnsCustomCaster; use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CustomCasterWithDocblockReturn; use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CustomCasterWithDocblockReturnFqn; use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CustomCasterWithNullablePrimitiveReturn; @@ -34,6 +36,8 @@ use Illuminate\Database\Eloquent\Model; * @property ExtendedSelfCastingCasterWithStaticDocblockReturn $extended_casted_property_with_static_return_docblock * @property ExtendedSelfCastingCasterWithThisDocblockReturn $extended_casted_property_with_this_return_docblock * @property SelfCastingCasterWithStaticDocblockReturn $casted_property_with_static_return_docblock_and_param + * @property \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CastedProperty $casted_property_with_castable + * @property \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CastedProperty $casted_property_with_anonymous_cast * @property \Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\LaravelCustomCasts\Casts\CastedProperty $cast_without_property * @method static \Illuminate\Database\Eloquent\Builder|CustomCast newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|CustomCast newQuery() @@ -66,6 +70,8 @@ class CustomCast extends Model 'casted_property_with_param' => CustomCasterWithParam::class . ':param', 'casted_property_with_static_return_docblock' => SelfCastingCasterWithStaticDocblockReturn::class, 'casted_property_with_this_return_docblock' => SelfCastingCasterWithThisDocblockReturn::class, + 'casted_property_with_castable' => CastableReturnsCustomCaster::class, + 'casted_property_with_anonymous_cast' => CastableReturnsAnonymousCaster::class, 'extended_casted_property_with_static_return_docblock' => ExtendedSelfCastingCasterWithStaticDocblockReturn::class, 'extended_casted_property_with_this_return_docblock' => ExtendedSelfCastingCasterWithThisDocblockReturn::class, 'casted_property_with_static_return_docblock_and_param' => SelfCastingCasterWithStaticDocblockReturn::class . ':param',