diff --git a/src/Console/ModelsCommand.php b/src/Console/ModelsCommand.php index b08b237..7ebe99c 100644 --- a/src/Console/ModelsCommand.php +++ b/src/Console/ModelsCommand.php @@ -16,7 +16,7 @@ use Barryvdh\Reflection\DocBlock\Serializer as DocBlockSerializer; use Barryvdh\Reflection\DocBlock\Tag; use Composer\Autoload\ClassMapGenerator; use Illuminate\Console\Command; -use Illuminate\Database\Eloquent\Collection; +use Illuminate\Contracts\Database\Eloquent\CastsAttributes; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Str; @@ -322,6 +322,8 @@ class ModelsCommand extends Command if (!isset($this->properties[$name])) { continue; } else { + $realType = $this->checkForCustomLaravelCasts($realType); + $this->properties[$name]['type'] = $this->getTypeOverride($realType); if (isset($this->nullableColumns[$name])) { @@ -942,4 +944,32 @@ class ModelsCommand extends Command return $keyword; } + + /** + * @param string $type + * @return string|null + * @throws \ReflectionException + */ + protected function checkForCustomLaravelCasts(string $type): ?string + { + if (!class_exists($type)) { + return $type; + } + + $reflection = new \ReflectionClass($type); + + if (!$reflection->implementsInterface(CastsAttributes::class)) { + return $type; + } + + $methodReflection = new \ReflectionMethod($type, 'get'); + + $type = $this->getReturnTypeFromReflection($methodReflection); + + if ($type === null) { + $type = $this->getReturnTypeFromDocBlock($methodReflection); + } + + return $type; + } } diff --git a/tests/Console/ModelsCommand/LaravelCustomCasts/Casts/CastedProperty.php b/tests/Console/ModelsCommand/LaravelCustomCasts/Casts/CastedProperty.php new file mode 100644 index 0000000..e0dd9fb --- /dev/null +++ b/tests/Console/ModelsCommand/LaravelCustomCasts/Casts/CastedProperty.php @@ -0,0 +1,8 @@ + CustomCasterWithReturnType::class, + 'casted_property_with_return_docblock' => CustomCasterWithDocblockReturn::class, + 'casted_property_with_return_primitive' => CustomCasterWithPrimitiveReturn::class, + 'casted_property_with_return_primitive_docblock' => CustomCasterWithPrimitiveDocblockReturn::class, + 'casted_property_with_return_nullable_primitive' => CustomCasterWithNullablePrimitiveReturn::class, + 'casted_property_without_return' => CustomCasterWithoutReturnType::class, + ]; +} diff --git a/tests/Console/ModelsCommand/LaravelCustomCasts/Test.php b/tests/Console/ModelsCommand/LaravelCustomCasts/Test.php new file mode 100644 index 0000000..1ebacf9 --- /dev/null +++ b/tests/Console/ModelsCommand/LaravelCustomCasts/Test.php @@ -0,0 +1,113 @@ +markTestSkipped('This test requires Laravel 7.0 or higher'); + } + } + + protected function getEnvironmentSetUp($app) + { + parent::getEnvironmentSetUp($app); + + $app['config']->set('ide-helper', [ + 'model_locations' => [ + // This is calculated from the base_path() which points to + // vendor/orchestra/testbench-core/laravel + '/../../../../tests/Console/ModelsCommand/LaravelCustomCasts/Models', + ], + ]); + } + + public function test_it_parses_casted_properties_correctly(): void + { + $actualContent = null; + $mockFilesystem = Mockery::mock(Filesystem::class); + $mockFilesystem + ->shouldReceive('get') + ->andReturn(file_get_contents(__DIR__.'/Models/CustomCast.php')) + ->once(); + $mockFilesystem + ->shouldReceive('put') + ->with( + Mockery::any(), + Mockery::capture($actualContent) + ) + ->andReturn(1) // Simulate we wrote _something_ to the file + ->once(); + + $this->instance(Filesystem::class, $mockFilesystem); + + $command = $this->app->make(ModelsCommand::class); + + $tester = $this->runCommand($command, [ + '--write' => true, + ]); + + $this->assertSame(0, $tester->getStatusCode()); + $this->assertEmpty($tester->getDisplay()); + + $expectedContent = <<<'PHP' + CustomCasterWithReturnType::class, + 'casted_property_with_return_docblock' => CustomCasterWithDocblockReturn::class, + 'casted_property_with_return_primitive' => CustomCasterWithPrimitiveReturn::class, + 'casted_property_with_return_primitive_docblock' => CustomCasterWithPrimitiveDocblockReturn::class, + 'casted_property_with_return_nullable_primitive' => CustomCasterWithNullablePrimitiveReturn::class, + 'casted_property_without_return' => CustomCasterWithoutReturnType::class, + ]; +} + +PHP; + + $this->assertSame($expectedContent, $actualContent); + } +} diff --git a/tests/Console/ModelsCommand/migrations/____custom_casts_table.php b/tests/Console/ModelsCommand/migrations/____custom_casts_table.php new file mode 100644 index 0000000..173b765 --- /dev/null +++ b/tests/Console/ModelsCommand/migrations/____custom_casts_table.php @@ -0,0 +1,20 @@ +string('casted_property_with_return_type'); + $table->string('casted_property_with_return_docblock'); + $table->string('casted_property_with_return_primitive'); + $table->string('casted_property_with_return_primitive_docblock'); + $table->string('casted_property_with_return_nullable_primitive'); + $table->string('casted_property_without_return'); + }); + } +}