Skip to content

Commit f0c138c

Browse files
wimskilaravel-ide-helper
andauthored
Model hooks (#945)
* Model hooks * composer fix-style * Fix test for ModelHooks Co-authored-by: laravel-ide-helper <[email protected]>
1 parent b8d5fc3 commit f0c138c

File tree

8 files changed

+218
-4
lines changed

8 files changed

+218
-4
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
44

55
[Next release](https://github.com/barryvdh/laravel-ide-helper/compare/v2.9.2...master)
66
--------------
7+
### Added
8+
- 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)
9+
710
### Fixed
811
- Running tests triggering post_migrate hooks [\#1193 / netpok](https://github.com/barryvdh/laravel-ide-helper/pull/1193)
912
- Array_merge error when config is cached prior to package install [\#1184 / netpok](https://github.com/barryvdh/laravel-ide-helper/pull/1184)

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ Generation is done based on the files in your project, so they are always up-to-
1414
- [Usage](#usage)
1515
- [Automatic PHPDoc generation for Laravel Facades](#automatic-phpdoc-generation-for-laravel-facades)
1616
- [Automatic PHPDocs for models](#automatic-phpdocs-for-models)
17+
- [Model Directories](#model-directories)
18+
- [Ignore Models](#ignore-models)
19+
- [Model Hooks](#model-hooks)
1720
- [Automatic PHPDocs generation for Laravel Fluent methods](#automatic-phpdocs-generation-for-laravel-fluent-methods)
1821
- [Auto-completion for factory builders](#auto-completion-for-factory-builders)
1922
- [PhpStorm Meta for Container instances](#phpstorm-meta-for-container-instances)
@@ -175,6 +178,8 @@ With the `--write-mixin (-M)` option
175178
*/
176179
```
177180

181+
#### Model Directories
182+
178183
By default, models in `app/models` are scanned. The optional argument tells what models to use (also outside app/models).
179184

180185
```bash
@@ -189,6 +194,8 @@ php artisan ide-helper:models --dir="path/to/models" --dir="app/src/Model"
189194

190195
You can publish the config file (`php artisan vendor:publish`) and set the default directories.
191196

197+
#### Ignore Models
198+
192199
Models can be ignored using the `--ignore (-I)` option
193200

194201
```bash
@@ -270,6 +277,42 @@ For those special cases, you can map them via the config `custom_db_types`. Exam
270277
],
271278
```
272279

280+
#### Model Hooks
281+
282+
If you need additional information on your model from sources that are not handled by default, you can hook in to the
283+
generation process with model hooks to add extra information on the fly.
284+
Simply create a class that implements `ModelHookInterface` and add it to the `model_hooks` array in the config:
285+
286+
```php
287+
'model_hooks' => [
288+
MyCustomHook::class,
289+
],
290+
```
291+
292+
The `run` method will be called during generation for every model and receives the current running `ModelsCommand` and the current `Model`, e.g.:
293+
294+
```php
295+
class MyCustomHook implements ModelHookInterface
296+
{
297+
public function run(ModelsCommand $command, Model $model): void
298+
{
299+
if (! $model instanceof MyModel) {
300+
return;
301+
}
302+
303+
$command->setProperty('custom', 'string', true, false, 'My custom property');
304+
}
305+
}
306+
```
307+
308+
```php
309+
/**
310+
* MyModel
311+
*
312+
* @property integer $id
313+
* @property-read string $custom
314+
```
315+
273316
### Automatic PHPDocs generation for Laravel Fluent methods
274317

275318
If you need PHPDocs support for Fluent methods in migration, for example

config/ide-helper.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,21 @@
144144

145145
],
146146

147+
/*
148+
|--------------------------------------------------------------------------
149+
| Models hooks
150+
|--------------------------------------------------------------------------
151+
|
152+
| Define which hook classes you want to run for models to add custom information
153+
|
154+
| Hooks should implement Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface.
155+
|
156+
*/
157+
158+
'model_hooks' => [
159+
// App\Support\IdeHelper\MyModelHook::class
160+
],
161+
147162
/*
148163
|--------------------------------------------------------------------------
149164
| Extra classes

src/Console/ModelsCommand.php

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Barryvdh\LaravelIdeHelper\Console;
1313

14+
use Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface;
1415
use Barryvdh\Reflection\DocBlock;
1516
use Barryvdh\Reflection\DocBlock\Context;
1617
use Barryvdh\Reflection\DocBlock\Serializer as DocBlockSerializer;
@@ -279,6 +280,9 @@ protected function generateDocs($loadModels, $ignore = '')
279280
$this->getSoftDeleteMethods($model);
280281
$this->getCollectionMethods($model);
281282
$this->getFactoryMethods($model);
283+
284+
$this->runModelHooks($model);
285+
282286
$output .= $this->createPhpDocs($name);
283287
$ignore[] = $name;
284288
$this->nullableColumns = [];
@@ -336,7 +340,7 @@ protected function loadModels()
336340
*
337341
* @param \Illuminate\Database\Eloquent\Model $model
338342
*/
339-
protected function castPropertiesType($model)
343+
public function castPropertiesType($model)
340344
{
341345
$casts = $model->getCasts();
342346
foreach ($casts as $name => $type) {
@@ -412,7 +416,7 @@ protected function getTypeOverride($type)
412416
*
413417
* @param \Illuminate\Database\Eloquent\Model $model
414418
*/
415-
protected function getPropertiesFromTable($model)
419+
public function getPropertiesFromTable($model)
416420
{
417421
$table = $model->getConnection()->getTablePrefix() . $model->getTable();
418422
$schema = $model->getConnection()->getDoctrineSchemaManager();
@@ -509,7 +513,7 @@ protected function getPropertiesFromTable($model)
509513
/**
510514
* @param \Illuminate\Database\Eloquent\Model $model
511515
*/
512-
protected function getPropertiesFromMethods($model)
516+
public function getPropertiesFromMethods($model)
513517
{
514518
$methods = get_class_methods($model);
515519
if ($methods) {
@@ -721,7 +725,7 @@ protected function isRelationNullable(string $relation, Relation $relationObj):
721725
* @param string|null $comment
722726
* @param bool $nullable
723727
*/
724-
protected function setProperty($name, $type = null, $read = null, $write = null, $comment = '', $nullable = false)
728+
public function setProperty($name, $type = null, $read = null, $write = null, $comment = '', $nullable = false)
725729
{
726730
if (!isset($this->properties[$name])) {
727731
$this->properties[$name] = [];
@@ -1351,4 +1355,26 @@ protected function getReflectionNamedType(ReflectionNamedType $paramType): strin
13511355

13521356
return $parameterName;
13531357
}
1358+
1359+
/**
1360+
* @param \Illuminate\Database\Eloquent\Model $model
1361+
* @throws \Illuminate\Contracts\Container\BindingResolutionException
1362+
* @throws \RuntimeException
1363+
*/
1364+
protected function runModelHooks($model): void
1365+
{
1366+
$hooks = $this->laravel['config']->get('ide-helper.model_hooks', []);
1367+
1368+
foreach ($hooks as $hook) {
1369+
$hookInstance = $this->laravel->make($hook);
1370+
1371+
if (!$hookInstance instanceof ModelHookInterface) {
1372+
throw new \RuntimeException(
1373+
'Your IDE helper model hook must implement Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface'
1374+
);
1375+
}
1376+
1377+
$hookInstance->run($this, $model);
1378+
}
1379+
}
13541380
}

src/Contracts/ModelHookInterface.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Barryvdh\LaravelIdeHelper\Contracts;
4+
5+
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
6+
use Illuminate\Database\Eloquent\Model;
7+
8+
interface ModelHookInterface
9+
{
10+
public function run(ModelsCommand $command, Model $model): void;
11+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks\Hooks;
6+
7+
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
8+
use Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface;
9+
use Illuminate\Database\Eloquent\Model;
10+
11+
class CustomProperty implements ModelHookInterface
12+
{
13+
public function run(ModelsCommand $command, Model $model): void
14+
{
15+
$command->setProperty('custom', 'string', true, false);
16+
}
17+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks\Models;
6+
7+
use Illuminate\Database\Eloquent\Model;
8+
9+
class Simple extends Model
10+
{
11+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks;
6+
7+
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
8+
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\AbstractModelsCommand;
9+
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks\Hooks\CustomProperty;
10+
use Illuminate\Filesystem\Filesystem;
11+
use Mockery;
12+
13+
class Test extends AbstractModelsCommand
14+
{
15+
protected function getEnvironmentSetUp($app)
16+
{
17+
parent::getEnvironmentSetUp($app);
18+
19+
$app['config']->set('ide-helper', [
20+
'model_locations' => [
21+
// This is calculated from the base_path() which points to
22+
// vendor/orchestra/testbench-core/laravel
23+
'/../../../../tests/Console/ModelsCommand/ModelHooks/Models',
24+
],
25+
'model_hooks' => [
26+
CustomProperty::class,
27+
],
28+
]);
29+
}
30+
31+
public function test(): void
32+
{
33+
$actualContent = null;
34+
35+
$mockFilesystem = Mockery::mock(Filesystem::class);
36+
$mockFilesystem
37+
->shouldReceive('get')
38+
->andReturn(file_get_contents(__DIR__ . '/Models/Simple.php'))
39+
->once();
40+
$mockFilesystem
41+
->shouldReceive('put')
42+
->with(
43+
Mockery::any(),
44+
Mockery::capture($actualContent)
45+
)
46+
->andReturn(1) // Simulate we wrote _something_ to the file
47+
->once();
48+
49+
$this->instance(Filesystem::class, $mockFilesystem);
50+
51+
$command = $this->app->make(ModelsCommand::class);
52+
53+
$tester = $this->runCommand($command, [
54+
'--write' => true,
55+
]);
56+
57+
$this->assertSame(0, $tester->getStatusCode());
58+
$this->assertStringContainsString('Written new phpDocBlock to', $tester->getDisplay());
59+
60+
$expectedContent = <<<'PHP'
61+
<?php
62+
63+
declare(strict_types=1);
64+
65+
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks\Models;
66+
67+
use Illuminate\Database\Eloquent\Model;
68+
69+
/**
70+
* Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\ModelHooks\Models\Simple
71+
*
72+
* @property int $id
73+
* @property-read string $custom
74+
* @method static \Illuminate\Database\Eloquent\Builder|Simple newModelQuery()
75+
* @method static \Illuminate\Database\Eloquent\Builder|Simple newQuery()
76+
* @method static \Illuminate\Database\Eloquent\Builder|Simple query()
77+
* @method static \Illuminate\Database\Eloquent\Builder|Simple whereId($value)
78+
* @mixin \Eloquent
79+
*/
80+
class Simple extends Model
81+
{
82+
}
83+
84+
PHP;
85+
86+
$this->assertSame($expectedContent, $actualContent);
87+
}
88+
}

0 commit comments

Comments
 (0)