diff --git a/CHANGELOG.md b/CHANGELOG.md index d4b5789..d85e760 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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.9.2...master) -------------- +### Added +- Model hooks for adding custom information from external sources to model classes through the ModelsCommand [\#945 / wimski](https://github.com/barryvdh/laravel-ide-helper/pull/945) + ### Fixed - Running tests triggering post_migrate hooks [\#1193 / netpok](https://github.com/barryvdh/laravel-ide-helper/pull/1193) - Array_merge error when config is cached prior to package install [\#1184 / netpok](https://github.com/barryvdh/laravel-ide-helper/pull/1184) diff --git a/README.md b/README.md index bd2a927..410aed3 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ Generation is done based on the files in your project, so they are always up-to- - [Usage](#usage) - [Automatic PHPDoc generation for Laravel Facades](#automatic-phpdoc-generation-for-laravel-facades) - [Automatic PHPDocs for models](#automatic-phpdocs-for-models) + - [Model Directories](#model-directories) + - [Ignore Models](#ignore-models) + - [Model Hooks](#model-hooks) - [Automatic PHPDocs generation for Laravel Fluent methods](#automatic-phpdocs-generation-for-laravel-fluent-methods) - [Auto-completion for factory builders](#auto-completion-for-factory-builders) - [PhpStorm Meta for Container instances](#phpstorm-meta-for-container-instances) @@ -175,6 +178,8 @@ With the `--write-mixin (-M)` option */ ``` +#### Model Directories + By default, models in `app/models` are scanned. The optional argument tells what models to use (also outside app/models). ```bash @@ -189,6 +194,8 @@ php artisan ide-helper:models --dir="path/to/models" --dir="app/src/Model" You can publish the config file (`php artisan vendor:publish`) and set the default directories. +#### Ignore Models + Models can be ignored using the `--ignore (-I)` option ```bash @@ -270,6 +277,42 @@ For those special cases, you can map them via the config `custom_db_types`. Exam ], ``` +#### Model Hooks + +If you need additional information on your model from sources that are not handled by default, you can hook in to the + generation process with model hooks to add extra information on the fly. + Simply create a class that implements `ModelHookInterface` and add it to the `model_hooks` array in the config: + + ```php +'model_hooks' => [ + MyCustomHook::class, +], +``` + +The `run` method will be called during generation for every model and receives the current running `ModelsCommand` and the current `Model`, e.g.: + +```php +class MyCustomHook implements ModelHookInterface +{ + public function run(ModelsCommand $command, Model $model): void + { + if (! $model instanceof MyModel) { + return; + } + + $command->setProperty('custom', 'string', true, false, 'My custom property'); + } +} +``` + +```php +/** + * MyModel + * + * @property integer $id + * @property-read string $custom +``` + ### Automatic PHPDocs generation for Laravel Fluent methods If you need PHPDocs support for Fluent methods in migration, for example diff --git a/config/ide-helper.php b/config/ide-helper.php index 4ea6c29..4a62954 100644 --- a/config/ide-helper.php +++ b/config/ide-helper.php @@ -144,6 +144,21 @@ return [ ], + /* + |-------------------------------------------------------------------------- + | Models hooks + |-------------------------------------------------------------------------- + | + | Define which hook classes you want to run for models to add custom information + | + | Hooks should implement Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface. + | + */ + + 'model_hooks' => [ + // App\Support\IdeHelper\MyModelHook::class + ], + /* |-------------------------------------------------------------------------- | Extra classes diff --git a/src/Console/ModelsCommand.php b/src/Console/ModelsCommand.php index 4b7acae..4ee4b08 100644 --- a/src/Console/ModelsCommand.php +++ b/src/Console/ModelsCommand.php @@ -11,6 +11,7 @@ namespace Barryvdh\LaravelIdeHelper\Console; +use Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface; use Barryvdh\Reflection\DocBlock; use Barryvdh\Reflection\DocBlock\Context; use Barryvdh\Reflection\DocBlock\Serializer as DocBlockSerializer; @@ -279,6 +280,9 @@ class ModelsCommand extends Command $this->getSoftDeleteMethods($model); $this->getCollectionMethods($model); $this->getFactoryMethods($model); + + $this->runModelHooks($model); + $output .= $this->createPhpDocs($name); $ignore[] = $name; $this->nullableColumns = []; @@ -336,7 +340,7 @@ class ModelsCommand extends Command * * @param \Illuminate\Database\Eloquent\Model $model */ - protected function castPropertiesType($model) + public function castPropertiesType($model) { $casts = $model->getCasts(); foreach ($casts as $name => $type) { @@ -412,7 +416,7 @@ class ModelsCommand extends Command * * @param \Illuminate\Database\Eloquent\Model $model */ - protected function getPropertiesFromTable($model) + public function getPropertiesFromTable($model) { $table = $model->getConnection()->getTablePrefix() . $model->getTable(); $schema = $model->getConnection()->getDoctrineSchemaManager(); @@ -509,7 +513,7 @@ class ModelsCommand extends Command /** * @param \Illuminate\Database\Eloquent\Model $model */ - protected function getPropertiesFromMethods($model) + public function getPropertiesFromMethods($model) { $methods = get_class_methods($model); if ($methods) { @@ -721,7 +725,7 @@ class ModelsCommand extends Command * @param string|null $comment * @param bool $nullable */ - protected function setProperty($name, $type = null, $read = null, $write = null, $comment = '', $nullable = false) + public function setProperty($name, $type = null, $read = null, $write = null, $comment = '', $nullable = false) { if (!isset($this->properties[$name])) { $this->properties[$name] = []; @@ -1351,4 +1355,26 @@ class ModelsCommand extends Command return $parameterName; } + + /** + * @param \Illuminate\Database\Eloquent\Model $model + * @throws \Illuminate\Contracts\Container\BindingResolutionException + * @throws \RuntimeException + */ + protected function runModelHooks($model): void + { + $hooks = $this->laravel['config']->get('ide-helper.model_hooks', []); + + foreach ($hooks as $hook) { + $hookInstance = $this->laravel->make($hook); + + if (!$hookInstance instanceof ModelHookInterface) { + throw new \RuntimeException( + 'Your IDE helper model hook must implement Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface' + ); + } + + $hookInstance->run($this, $model); + } + } } diff --git a/src/Contracts/ModelHookInterface.php b/src/Contracts/ModelHookInterface.php new file mode 100644 index 0000000..5212470 --- /dev/null +++ b/src/Contracts/ModelHookInterface.php @@ -0,0 +1,11 @@ +setProperty('custom', 'string', true, false); + } +} diff --git a/tests/Console/ModelsCommand/ModelHooks/Models/Simple.php b/tests/Console/ModelsCommand/ModelHooks/Models/Simple.php new file mode 100644 index 0000000..1c1ddbb --- /dev/null +++ b/tests/Console/ModelsCommand/ModelHooks/Models/Simple.php @@ -0,0 +1,11 @@ +set('ide-helper', [ + 'model_locations' => [ + // This is calculated from the base_path() which points to + // vendor/orchestra/testbench-core/laravel + '/../../../../tests/Console/ModelsCommand/ModelHooks/Models', + ], + 'model_hooks' => [ + CustomProperty::class, + ], + ]); + } + + public function test(): void + { + $actualContent = null; + + $mockFilesystem = Mockery::mock(Filesystem::class); + $mockFilesystem + ->shouldReceive('get') + ->andReturn(file_get_contents(__DIR__ . '/Models/Simple.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->assertStringContainsString('Written new phpDocBlock to', $tester->getDisplay()); + + $expectedContent = <<<'PHP' +assertSame($expectedContent, $actualContent); + } +}