Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Model hooks #945

Merged
merged 3 commits into from
Mar 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions config/ide-helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,21 @@

],

/*
|--------------------------------------------------------------------------
| 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
Expand Down
34 changes: 30 additions & 4 deletions src/Console/ModelsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -279,6 +280,9 @@ protected function generateDocs($loadModels, $ignore = '')
$this->getSoftDeleteMethods($model);
$this->getCollectionMethods($model);
$this->getFactoryMethods($model);

$this->runModelHooks($model);

$output .= $this->createPhpDocs($name);
$ignore[] = $name;
$this->nullableColumns = [];
Expand Down Expand Up @@ -336,7 +340,7 @@ protected function loadModels()
*
* @param \Illuminate\Database\Eloquent\Model $model
*/
protected function castPropertiesType($model)
public function castPropertiesType($model)
{
$casts = $model->getCasts();
foreach ($casts as $name => $type) {
Expand Down Expand Up @@ -412,7 +416,7 @@ protected function getTypeOverride($type)
*
* @param \Illuminate\Database\Eloquent\Model $model
*/
protected function getPropertiesFromTable($model)
public function getPropertiesFromTable($model)
{
$table = $model->getConnection()->getTablePrefix() . $model->getTable();
$schema = $model->getConnection()->getDoctrineSchemaManager();
Expand Down Expand Up @@ -509,7 +513,7 @@ protected function getPropertiesFromTable($model)
/**
* @param \Illuminate\Database\Eloquent\Model $model
*/
protected function getPropertiesFromMethods($model)
public function getPropertiesFromMethods($model)
{
$methods = get_class_methods($model);
if ($methods) {
Expand Down Expand Up @@ -721,7 +725,7 @@ protected function isRelationNullable(string $relation, Relation $relationObj):
* @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] = [];
Expand Down Expand Up @@ -1351,4 +1355,26 @@ protected function getReflectionNamedType(ReflectionNamedType $paramType): strin

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 changes: 11 additions & 0 deletions src/Contracts/ModelHookInterface.php
Original file line number Diff line number Diff line change
@@ -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;
}
17 changes: 17 additions & 0 deletions tests/Console/ModelsCommand/ModelHooks/Hooks/CustomProperty.php
Original file line number Diff line number Diff line change
@@ -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);
}
}
11 changes: 11 additions & 0 deletions tests/Console/ModelsCommand/ModelHooks/Models/Simple.php
Original file line number Diff line number Diff line change
@@ -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
{
}
88 changes: 88 additions & 0 deletions tests/Console/ModelsCommand/ModelHooks/Test.php
Original file line number Diff line number Diff line change
@@ -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);
}
}