Model hooks (#945)

* Model hooks

* composer fix-style

* Fix test for ModelHooks

Co-authored-by: laravel-ide-helper <[email protected]>
This commit is contained in:
wimski
2021-03-29 20:49:09 +02:00
committed by GitHub
co-authored by laravel-ide-helper
parent b8d5fc3663
commit f0c138c4fd
8 changed files with 218 additions and 4 deletions
+3
View File
@@ -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)
+43
View File
@@ -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
+15
View File
@@ -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
+30 -4
View File
@@ -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);
}
}
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Barryvdh\LaravelIdeHelper\Contracts;
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
use Illuminate\Database\Eloquent\Model;
interface ModelHookInterface
{
public function run(ModelsCommand $command, Model $model): void;
}
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks\Hooks;
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
use Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface;
use Illuminate\Database\Eloquent\Model;
class CustomProperty implements ModelHookInterface
{
public function run(ModelsCommand $command, Model $model): void
{
$command->setProperty('custom', 'string', true, false);
}
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks\Models;
use Illuminate\Database\Eloquent\Model;
class Simple extends Model
{
}
@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks;
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\AbstractModelsCommand;
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks\Hooks\CustomProperty;
use Illuminate\Filesystem\Filesystem;
use Mockery;
class Test extends AbstractModelsCommand
{
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/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'
<?php
declare(strict_types=1);
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks\Models;
use Illuminate\Database\Eloquent\Model;
/**
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks\Models\Simple
*
* @property int $id
* @property-read string $custom
* @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
{
}
PHP;
$this->assertSame($expectedContent, $actualContent);
}
}