Skip to content

Commit a63568b

Browse files
calebdwWulfheart
andcommitted
fix: property type for uuid and ulid primary keys
Co-authored-by: Alex Wulf <alex.f.wulf@gmail.com>
1 parent 3bd58e1 commit a63568b

12 files changed

+148
-61
lines changed

extension.neon

+5-3
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,8 @@ services:
271271
class: Larastan\Larastan\ReturnTypes\Helpers\ValidatorExtension
272272
tags:
273273
- phpstan.broker.dynamicFunctionReturnTypeExtension
274-
275-
-
274+
275+
-
276276
class: Larastan\Larastan\ReturnTypes\Helpers\LiteralExtension
277277
tags:
278278
- phpstan.broker.dynamicFunctionReturnTypeExtension
@@ -408,13 +408,15 @@ services:
408408
arguments:
409409
active: %checkModelProperties%
410410

411+
-
412+
class: Larastan\Larastan\Support\ModelHelper
413+
411414
-
412415
class: Larastan\Larastan\Properties\MigrationHelper
413416
arguments:
414417
databaseMigrationPath: %databaseMigrationsPath%
415418
disableMigrationScan: %disableMigrationScan%
416419
parser: @currentPhpVersionSimpleDirectParser
417-
reflectionProvider: @reflectionProvider
418420

419421
-
420422
class: Larastan\Larastan\Properties\SquashedMigrationHelper

src/Properties/MigrationHelper.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
namespace Larastan\Larastan\Properties;
66

7+
use Larastan\Larastan\Support\ModelHelper;
78
use PHPStan\File\FileHelper;
89
use PHPStan\Parser\Parser;
910
use PHPStan\Parser\ParserErrorsException;
10-
use PHPStan\Reflection\ReflectionProvider;
1111
use RecursiveDirectoryIterator;
1212
use RecursiveIteratorIterator;
1313
use RegexIterator;
@@ -27,7 +27,7 @@ public function __construct(
2727
private array $databaseMigrationPath,
2828
private FileHelper $fileHelper,
2929
private bool $disableMigrationScan,
30-
private ReflectionProvider $reflectionProvider,
30+
private ModelHelper $modelHelper,
3131
) {
3232
}
3333

@@ -46,7 +46,7 @@ public function initializeTables(array $tables = []): array
4646
$this->databaseMigrationPath = [database_path('migrations')];
4747
}
4848

49-
$schemaAggregator = new SchemaAggregator($this->reflectionProvider, $tables);
49+
$schemaAggregator = new SchemaAggregator($this->modelHelper, $tables);
5050
$filesArray = $this->getMigrationFiles();
5151

5252
if (empty($filesArray)) {

src/Properties/ModelCastHelper.php

+4-10
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
use Illuminate\Database\Eloquent\Casts\AsEncryptedArrayObject;
1616
use Illuminate\Database\Eloquent\Casts\AsEncryptedCollection;
1717
use Illuminate\Database\Eloquent\Casts\AsStringable;
18-
use Illuminate\Database\Eloquent\Model;
1918
use Illuminate\Support\Arr;
2019
use Illuminate\Support\Carbon as IlluminateCarbon;
2120
use Illuminate\Support\Collection;
2221
use Illuminate\Support\Facades\Date;
2322
use Illuminate\Support\Stringable as IlluminateStringable;
23+
use Larastan\Larastan\Support\ModelHelper;
2424
use PHPStan\Analyser\OutOfClassScope;
2525
use PHPStan\Reflection\ClassReflection;
2626
use PHPStan\Reflection\MissingMethodFromReflectionException;
@@ -40,7 +40,6 @@
4040
use PHPStan\Type\StringType;
4141
use PHPStan\Type\Type;
4242
use PHPStan\Type\TypeCombinator;
43-
use ReflectionException;
4443
use stdClass;
4544
use Stringable;
4645

@@ -59,6 +58,7 @@ class ModelCastHelper
5958

6059
public function __construct(
6160
protected ReflectionProvider $reflectionProvider,
61+
protected ModelHelper $modelHelper,
6262
) {
6363
}
6464

@@ -237,14 +237,8 @@ public function getCastForProperty(ClassReflection $modelClassReflection, string
237237
*/
238238
private function getModelCasts(ClassReflection $modelClassReflection): array
239239
{
240-
try {
241-
/** @var Model $modelInstance */
242-
$modelInstance = $modelClassReflection->getNativeReflection()->newInstanceWithoutConstructor();
243-
} catch (ReflectionException) {
244-
throw new ShouldNotHappenException();
245-
}
246-
247-
$modelCasts = $modelInstance->getCasts();
240+
$modelInstance = $this->modelHelper->getModelInstance($modelClassReflection);
241+
$modelCasts = $modelInstance->getCasts();
248242

249243
$castsMethodReturnType = $modelClassReflection->getMethod(
250244
'casts',

src/Properties/ModelPropertyHelper.php

+4-14
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,15 @@
88
use Illuminate\Database\Eloquent\Model;
99
use Illuminate\Support\Str;
1010
use Larastan\Larastan\Reflection\ReflectionHelper;
11+
use Larastan\Larastan\Support\ModelHelper;
1112
use PHPStan\PhpDoc\TypeStringResolver;
1213
use PHPStan\Reflection\ClassReflection;
13-
use PHPStan\ShouldNotHappenException;
1414
use PHPStan\Type\Constant\ConstantStringType;
1515
use PHPStan\Type\Generic\GenericObjectType;
1616
use PHPStan\Type\MixedType;
1717
use PHPStan\Type\ObjectType;
1818
use PHPStan\Type\StringType;
1919
use PHPStan\Type\TypeCombinator;
20-
use ReflectionException;
2120

2221
use function array_key_exists;
2322
use function array_map;
@@ -36,6 +35,7 @@ public function __construct(
3635
private MigrationHelper $migrationHelper,
3736
private SquashedMigrationHelper $squashedMigrationHelper,
3837
private ModelCastHelper $modelCastHelper,
38+
private ModelHelper $modelHelper,
3939
) {
4040
}
4141

@@ -68,12 +68,7 @@ public function hasDatabaseProperty(ClassReflection|string $classReflectionOrTab
6868
return false;
6969
}
7070

71-
try {
72-
/** @var Model $modelInstance */
73-
$modelInstance = $classReflectionOrTable->getNativeReflection()->newInstanceWithoutConstructor();
74-
} catch (ReflectionException) {
75-
return false;
76-
}
71+
$modelInstance = $this->modelHelper->getModelInstance($classReflectionOrTable);
7772

7873
if ($propertyName === $modelInstance->getKeyName()) {
7974
return true;
@@ -90,12 +85,7 @@ public function hasDatabaseProperty(ClassReflection|string $classReflectionOrTab
9085

9186
public function getDatabaseProperty(ClassReflection $classReflection, string $propertyName): ModelProperty
9287
{
93-
try {
94-
/** @var Model $modelInstance */
95-
$modelInstance = $classReflection->getNativeReflection()->newInstanceWithoutConstructor();
96-
} catch (ReflectionException) {
97-
throw new ShouldNotHappenException();
98-
}
88+
$modelInstance = $this->modelHelper->getModelInstance($classReflection);
9989

10090
$tableName = $modelInstance->getTable();
10191

src/Properties/SchemaAggregator.php

+11-13
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@
77
use Exception;
88
use Illuminate\Database\Eloquent\Model;
99
use Illuminate\Support\Str;
10+
use Larastan\Larastan\Support\ModelHelper;
1011
use PhpParser;
1112
use PhpParser\NodeFinder;
12-
use PHPStan\Reflection\ReflectionProvider;
1313
use PHPStan\Type\ObjectType;
14-
use ReflectionException;
1514

1615
use function array_key_exists;
1716
use function array_merge;
17+
use function assert;
1818
use function class_basename;
1919
use function count;
20+
use function is_a;
2021
use function is_string;
2122
use function property_exists;
2223
use function strtolower;
@@ -25,7 +26,7 @@
2526
final class SchemaAggregator
2627
{
2728
/** @param array<string, SchemaTable> $tables */
28-
public function __construct(private ReflectionProvider $reflectionProvider, public array $tables = [])
29+
public function __construct(private ModelHelper $modelHelper, public array $tables = [])
2930
{
3031
}
3132

@@ -128,7 +129,8 @@ private function alterTable(PhpParser\Node\Expr\StaticCall|PhpParser\Node\Expr\M
128129
! isset($call->args[1])
129130
|| ! $call->getArgs()[1]->value instanceof PhpParser\Node\Expr\Closure
130131
|| count($call->getArgs()[1]->value->params) < 1
131-
|| ($call->getArgs()[1]->value->params[0]->type instanceof PhpParser\Node\Name
132+
|| (
133+
$call->getArgs()[1]->value->params[0]->type instanceof PhpParser\Node\Name
132134
&& ! (new ObjectType('Illuminate\Database\Schema\Blueprint'))->isSuperTypeOf(new ObjectType($call->getArgs()[1]->value->params[0]->type->toCodeString()))->yes()
133135
)
134136
) {
@@ -213,6 +215,8 @@ private function processColumnUpdates(string $tableName, string $argName, array
213215
continue;
214216
}
215217

218+
assert(is_a($modelClass, Model::class, true));
219+
216220
$columnName = Str::snake(class_basename($modelClass)) . '_id';
217221
if ($secondArg instanceof PhpParser\Node\Scalar\String_) {
218222
$columnName = $secondArg->value;
@@ -381,17 +385,11 @@ private function renameTable(string $oldTableName, string $newTableName): void
381385
$this->tables[$newTableName] = $table;
382386
}
383387

388+
/** @param class-string<Model> $modelClass */
384389
private function getModelReferenceType(string $modelClass): string|null
385390
{
386-
$classReflection = $this->reflectionProvider->getClass($modelClass);
387-
try {
388-
/** @var Model $modelInstance */
389-
$modelInstance = $classReflection->getNativeReflection()->newInstanceWithoutConstructor();
390-
} catch (ReflectionException) {
391-
return null;
392-
}
393-
394-
$tableName = $modelInstance->getTable();
391+
$modelInstance = $this->modelHelper->getModelInstance($modelClass);
392+
$tableName = $modelInstance->getTable();
395393

396394
if (! array_key_exists($tableName, $this->tables)) {
397395
return null;

src/Support/ModelHelper.php

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Larastan\Larastan\Support;
6+
7+
use Illuminate\Database\Eloquent\Model;
8+
use PHPStan\Reflection\ClassReflection;
9+
use PHPStan\Reflection\ReflectionProvider;
10+
use Throwable;
11+
12+
use function is_string;
13+
14+
/** @internal */
15+
final class ModelHelper
16+
{
17+
public function __construct(private ReflectionProvider $reflectionProvider)
18+
{
19+
}
20+
21+
/** @param ClassReflection|class-string<Model> $model */
22+
public function getModelInstance(ClassReflection|string $model): Model
23+
{
24+
if (is_string($model)) {
25+
$model = $this->reflectionProvider->getClass($model);
26+
}
27+
28+
try {
29+
/** @var Model $modelInstance */
30+
$modelInstance = $model->getNativeReflection()->newInstance();
31+
} catch (Throwable) {
32+
/** @var Model $modelInstance */
33+
$modelInstance = $model->getNativeReflection()->newInstanceWithoutConstructor();
34+
}
35+
36+
return $modelInstance;
37+
}
38+
}

tests/Type/GeneralTypeTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public static function dataFileAsserts(): iterable
2727
yield from self::gatherAssertTypes(__DIR__ . '/data/bug-1985.php');
2828
yield from self::gatherAssertTypes(__DIR__ . '/data/bug-2073.php');
2929
yield from self::gatherAssertTypes(__DIR__ . '/data/bug-2111.php');
30+
yield from self::gatherAssertTypes(__DIR__ . '/data/bug-2188.php');
3031
yield from self::gatherAssertTypes(__DIR__ . '/data/conditionable.php');
3132
yield from self::gatherAssertTypes(__DIR__ . '/data/container-array-access.php');
3233
yield from self::gatherAssertTypes(__DIR__ . '/data/container-make.php');

tests/Type/data/bug-2188.php

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
use App\UlidModel;
4+
use App\UuidModel;
5+
6+
use function PHPStan\Testing\assertType;
7+
8+
function test(UlidModel $ulid, UuidModel $uuid): void
9+
{
10+
assertType('string', $ulid->id);
11+
assertType('string', $uuid->id);
12+
}

0 commit comments

Comments
 (0)