Skip to content

Commit db53e8e

Browse files
Add support for dedicated eloquent builders and type/doc parameter support. (#1089)
* Add support for generating helpers for external eloquent builders * Extract external builder methods generator to its own method and add an option to toggle this feature * Check if we are using the default name of the eloquent builder * Add tests with snapshots for external eloquent builder feature * Refactor codegs * Allow for type hinting * Update test * Run cs fix * Update readme * Use getName for parameter instead of relying on __toString() * Do not use str_contains and use built in php method
1 parent b3a79fe commit db53e8e

File tree

11 files changed

+631
-2
lines changed

11 files changed

+631
-2
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,13 @@ You may use the [`::withCount`](https://laravel.com/docs/master/eloquent-relatio
216216

217217
By default, these attributes are generated in the phpdoc. You can turn them off by setting the config `write_model_relation_count_properties` to `false`.
218218

219+
#### Dedicated Eloquent Builder methods
220+
221+
A new method to the eloquent models was added called `newEloquentBuilder` [Reference](https://timacdonald.me/dedicated-eloquent-model-query-builders/) where we can
222+
add support for creating a new dedicated class instead of using local scopes in the model itself.
223+
224+
If for some reason it's undesired to have them generated (one for each column), you can disable this via config `write_model_external_builder_methods` and setting it to `false`.
225+
219226
#### Unsupported or custom database types
220227

221228
Common column types (e.g. varchar, integer) are correctly mapped to PHP types (`string`, `int`).

config/ide-helper.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,17 @@
6262

6363
'write_model_magic_where' => true,
6464

65+
/*
66+
|--------------------------------------------------------------------------
67+
| Write Model External Eloquent Builder methods
68+
|--------------------------------------------------------------------------
69+
|
70+
| Set to false to disable write external eloquent builder methods
71+
|
72+
*/
73+
74+
'write_model_external_builder_methods' => true,
75+
6576
/*
6677
|--------------------------------------------------------------------------
6778
| Write Model relation count properties

src/Console/ModelsCommand.php

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ class ModelsCommand extends Command
9191
protected $reset;
9292
protected $keep_text;
9393
protected $phpstorm_noinspections;
94+
protected $write_model_external_builder_methods;
9495
/**
9596
* @var bool[string]
9697
*/
@@ -135,6 +136,7 @@ public function handle()
135136
$this->keep_text = $this->reset = true;
136137
}
137138
$this->write_model_magic_where = $this->laravel['config']->get('ide-helper.write_model_magic_where', true);
139+
$this->write_model_external_builder_methods = $this->laravel['config']->get('ide-helper.write_model_external_builder_methods', true);
138140
$this->write_model_relation_count_properties =
139141
$this->laravel['config']->get('ide-helper.write_model_relation_count_properties', true);
140142

@@ -543,7 +545,7 @@ protected function getPropertiesFromMethods($model)
543545
array_shift($args);
544546
$builder = $this->getClassNameInDestinationFile(
545547
$reflection->getDeclaringClass(),
546-
\Illuminate\Database\Eloquent\Builder::class
548+
get_class($model->newModelQuery())
547549
);
548550
$modelName = $this->getClassNameInDestinationFile(
549551
$reflection->getDeclaringClass(),
@@ -558,6 +560,10 @@ protected function getPropertiesFromMethods($model)
558560
$method,
559561
$builder . '|' . $this->getClassNameInDestinationFile($model, get_class($model))
560562
);
563+
564+
if ($this->write_model_external_builder_methods) {
565+
$this->writeModelExternalBuilderMethods($builder, $model);
566+
}
561567
} elseif (
562568
!method_exists('Illuminate\Database\Eloquent\Model', $method)
563569
&& !Str::startsWith($method, 'get')
@@ -887,9 +893,11 @@ protected function createPhpDocs($class)
887893
* Get the parameters and format them correctly
888894
*
889895
* @param $method
896+
* @param bool $withTypeHint
890897
* @return array
898+
* @throws \ReflectionException
891899
*/
892-
public function getParameters($method)
900+
public function getParameters($method, bool $withTypeHint = false)
893901
{
894902
//Loop through the default values for parameters, and make the correct output string
895903
$paramsWithDefault = [];
@@ -920,8 +928,14 @@ public function getParameters($method)
920928
} else {
921929
$default = "'" . trim($default) . "'";
922930
}
931+
923932
$paramStr .= " = $default";
924933
}
934+
935+
if ($withTypeHint && $paramType = $this->getParamType($method, $param)) {
936+
$paramStr = $paramType . ' ' . $paramStr;
937+
}
938+
925939
$paramsWithDefault[] = $paramStr;
926940
}
927941
return $paramsWithDefault;
@@ -1146,4 +1160,98 @@ protected function getUsedClassNames(ReflectionClass $reflection): array
11461160

11471161
return $namespaceAliases;
11481162
}
1163+
1164+
protected function writeModelExternalBuilderMethods(string $builder, Model $model): void
1165+
{
1166+
if (in_array($builder, ['\Illuminate\Database\Eloquent\Builder', 'EloquentBuilder'])) {
1167+
return;
1168+
}
1169+
1170+
$newBuilderMethods = get_class_methods($builder);
1171+
$originalBuilderMethods = get_class_methods('\Illuminate\Database\Eloquent\Builder');
1172+
1173+
// diff the methods between the new builder and original one
1174+
// and create helpers for the ones that are new
1175+
$newMethodsFromNewBuilder = array_diff($newBuilderMethods, $originalBuilderMethods);
1176+
1177+
foreach ($newMethodsFromNewBuilder as $builderMethod) {
1178+
$reflection = new \ReflectionMethod($builder, $builderMethod);
1179+
$args = $this->getParameters($reflection, true);
1180+
1181+
$this->setMethod(
1182+
$builderMethod,
1183+
$builder . '|' . $this->getClassNameInDestinationFile($model, get_class($model)),
1184+
$args
1185+
);
1186+
}
1187+
}
1188+
1189+
protected function getParamType(\ReflectionMethod $method, \ReflectionParameter $parameter): ?string
1190+
{
1191+
if ($paramType = $parameter->getType()) {
1192+
if ($paramType->allowsNull()) {
1193+
return '?' . $paramType->getName();
1194+
}
1195+
1196+
return $paramType->getName();
1197+
}
1198+
1199+
$docComment = $method->getDocComment();
1200+
1201+
if (!$docComment) {
1202+
return null;
1203+
}
1204+
1205+
preg_match(
1206+
'/@param ((?:(?:[\w?|\\\\<>])+(?:\[])?)+)/',
1207+
$docComment ?? '',
1208+
$matches
1209+
);
1210+
$type = $matches[1] ?? null;
1211+
1212+
if (strpos($type, '|') !== false) {
1213+
$types = explode('|', $type);
1214+
1215+
// if we have more than 2 types
1216+
// we return null as we cannot use unions in php yet
1217+
if (count($types) > 2) {
1218+
return null;
1219+
}
1220+
1221+
$hasNull = false;
1222+
1223+
foreach ($types as $currentType) {
1224+
if ($currentType === 'null') {
1225+
$hasNull = true;
1226+
continue;
1227+
}
1228+
1229+
// if we didn't find null assign the current type to the type we want
1230+
$type = $currentType;
1231+
}
1232+
1233+
// if we haven't found null type set
1234+
// we return null as we cannot use unions with different types yet
1235+
if (!$hasNull) {
1236+
return null;
1237+
}
1238+
1239+
$type = '?' . $type;
1240+
}
1241+
1242+
$typesThatAreNotAllowed = [
1243+
'null',
1244+
'mixed',
1245+
'nullable',
1246+
];
1247+
1248+
// we replace the ? with an empty string so we can check the actual type
1249+
if (in_array(str_replace('?', '', $type), $typesThatAreNotAllowed)) {
1250+
return null;
1251+
}
1252+
1253+
// if we have a match on index 1
1254+
// then we have found the type of the variable if not we return null
1255+
return $type;
1256+
}
11491257
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DoesNotGeneratePhpdocWithExternalEloquentBuilder\Builders;
6+
7+
use Illuminate\Database\Eloquent\Builder;
8+
9+
class PostExternalQueryBuilder extends Builder
10+
{
11+
public function isActive(): self
12+
{
13+
return $this;
14+
}
15+
16+
public function isStatus(string $status): self
17+
{
18+
return $this;
19+
}
20+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DoesNotGeneratePhpdocWithExternalEloquentBuilder\Models;
6+
7+
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DoesNotGeneratePhpdocWithExternalEloquentBuilder\Builders\PostExternalQueryBuilder;
8+
use Illuminate\Database\Eloquent\Model;
9+
10+
class Post extends Model
11+
{
12+
public function newEloquentBuilder($query): PostExternalQueryBuilder
13+
{
14+
return new PostExternalQueryBuilder($query);
15+
}
16+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\DoesNotGeneratePhpdocWithExternalEloquentBuilder;
6+
7+
use Barryvdh\LaravelIdeHelper\Console\ModelsCommand;
8+
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\AbstractModelsCommand;
9+
10+
class Test extends AbstractModelsCommand
11+
{
12+
protected function getEnvironmentSetUp($app)
13+
{
14+
parent::getEnvironmentSetUp($app);
15+
16+
$app['config']->set('ide-helper.write_model_external_builder_methods', false);
17+
}
18+
19+
public function test(): void
20+
{
21+
$command = $this->app->make(ModelsCommand::class);
22+
23+
$tester = $this->runCommand($command, [
24+
'--nowrite' => true,
25+
]);
26+
27+
$this->assertSame(0, $tester->getStatusCode());
28+
$this->assertStringContainsString('Model information was written to _ide_helper_models.php', $tester->getDisplay());
29+
$this->assertMatchesMockedSnapshot();
30+
}
31+
}

0 commit comments

Comments
 (0)