Skip to content

Commit 71626dd

Browse files
committed
Merge branch 'fix/ulid-or-uuid-primary-key' into 3.x
2 parents b524649 + a63568b commit 71626dd

16 files changed

+147
-59
lines changed

extension.neon

+5-2
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,8 @@ services:
281281
class: Larastan\Larastan\ReturnTypes\Helpers\ValidatorExtension
282282
tags:
283283
- phpstan.broker.dynamicFunctionReturnTypeExtension
284-
285-
-
284+
285+
-
286286
class: Larastan\Larastan\ReturnTypes\Helpers\LiteralExtension
287287
tags:
288288
- phpstan.broker.dynamicFunctionReturnTypeExtension
@@ -418,6 +418,9 @@ services:
418418
arguments:
419419
active: %checkModelProperties%
420420

421+
-
422+
class: Larastan\Larastan\Support\ModelHelper
423+
421424
-
422425
class: Larastan\Larastan\Properties\MigrationHelper
423426
arguments:

src/Properties/MigrationHelper.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Larastan\Larastan\Properties;
66

7+
use Larastan\Larastan\Support\ModelHelper;
78
use Larastan\Larastan\Internal\FileHelper;
89
use PHPStan\Parser\Parser;
910
use PHPStan\Parser\ParserErrorsException;
@@ -21,6 +22,7 @@ public function __construct(
2122
private array $databaseMigrationPath,
2223
private FileHelper $fileHelper,
2324
private bool $disableMigrationScan,
25+
private ModelHelper $modelHelper,
2426
) {
2527
}
2628

@@ -34,7 +36,7 @@ public function parseMigrations(ModelDatabaseHelper &$modelDatabaseHelper): void
3436
$this->databaseMigrationPath = [database_path('migrations')];
3537
}
3638

37-
$schemaAggregator = new SchemaAggregator($modelDatabaseHelper);
39+
$schemaAggregator = new SchemaAggregator($modelDatabaseHelper, $this->modelHelper);
3840
$filesArray = $this->fileHelper->getFiles($this->databaseMigrationPath, '/\.php$/i');
3941

4042
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/ModelDatabaseHelper.php

-21
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,8 @@
66

77
use Illuminate\Database\Eloquent\Model;
88
use Larastan\Larastan\Concerns\HasContainer;
9-
use PHPStan\Reflection\ClassReflection;
10-
use PHPStan\Reflection\ReflectionProvider;
11-
use ReflectionException;
129

1310
use function count;
14-
use function is_string;
1511

1612
final class ModelDatabaseHelper
1713
{
@@ -23,7 +19,6 @@ final class ModelDatabaseHelper
2319
private string $defaultConnection;
2420

2521
public function __construct(
26-
private ReflectionProvider $reflectionProvider,
2722
private SquashedMigrationHelper $squashedMigrationHelper,
2823
private MigrationHelper $migrationHelper,
2924
) {
@@ -98,22 +93,6 @@ public function dropConnection(string $connectionName): void
9893
unset($this->connections[$connectionName]);
9994
}
10095

101-
public function getModelInstance(ClassReflection|string $class): Model|null
102-
{
103-
if (is_string($class)) {
104-
$class = $this->reflectionProvider->getClass($class);
105-
}
106-
107-
try {
108-
/** @var Model $modelInstance */
109-
$modelInstance = $class->getNativeReflection()->newInstanceWithoutConstructor();
110-
} catch (ReflectionException) {
111-
return null;
112-
}
113-
114-
return $modelInstance;
115-
}
116-
11796
public function ensureInitialized(): void
11897
{
11998
if (count($this->connections) !== 0) {

src/Properties/ModelPropertyHelper.php

+4-11
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
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;
@@ -29,6 +29,7 @@ public function __construct(
2929
private TypeStringResolver $stringResolver,
3030
private ModelDatabaseHelper $modelDatabaseHelper,
3131
private ModelCastHelper $modelCastHelper,
32+
private ModelHelper $modelHelper,
3233
) {
3334
}
3435

@@ -49,11 +50,7 @@ public function hasDatabaseProperty(ClassReflection $classReflection, string $pr
4950
return false;
5051
}
5152

52-
$modelInstance = $this->modelDatabaseHelper->getModelInstance($classReflection);
53-
54-
if ($modelInstance === null) {
55-
return false;
56-
}
53+
$modelInstance = $this->modelHelper->getModelInstance($classReflection);
5754

5855
if ($propertyName === $modelInstance->getKeyName()) {
5956
return true;
@@ -64,11 +61,7 @@ public function hasDatabaseProperty(ClassReflection $classReflection, string $pr
6461

6562
public function getDatabaseProperty(ClassReflection $classReflection, string $propertyName): ModelProperty
6663
{
67-
$modelInstance = $this->modelDatabaseHelper->getModelInstance($classReflection);
68-
69-
if ($modelInstance === null) {
70-
throw new ShouldNotHappenException();
71-
}
64+
$modelInstance = $this->modelHelper->getModelInstance($classReflection);
7265

7366
if (
7467
$propertyName === $modelInstance->getKeyName()

src/Properties/SchemaAggregator.php

+7-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
namespace Larastan\Larastan\Properties;
66

77
use Exception;
8+
use Illuminate\Database\Eloquent\Model;
89
use Illuminate\Support\Str;
10+
use Larastan\Larastan\Support\ModelHelper;
911
use PhpParser;
1012
use PhpParser\NodeFinder;
1113
use PHPStan\Type\ObjectType;
@@ -29,6 +31,7 @@ final class SchemaAggregator
2931

3032
public function __construct(
3133
private ModelDatabaseHelper $modelDatabaseHelper,
34+
private ModelHelper $modelHelper,
3235
) {
3336
}
3437

@@ -162,7 +165,8 @@ private function alterTable(PhpParser\Node\Expr\StaticCall|PhpParser\Node\Expr\M
162165
! isset($call->args[1])
163166
|| ! $call->getArgs()[1]->value instanceof PhpParser\Node\Expr\Closure
164167
|| count($call->getArgs()[1]->value->params) < 1
165-
|| ($call->getArgs()[1]->value->params[0]->type instanceof PhpParser\Node\Name
168+
|| (
169+
$call->getArgs()[1]->value->params[0]->type instanceof PhpParser\Node\Name
166170
&& ! (new ObjectType('Illuminate\Database\Schema\Blueprint'))->isSuperTypeOf(new ObjectType($call->getArgs()[1]->value->params[0]->type->toCodeString()))->yes()
167171
)
168172
) {
@@ -252,11 +256,8 @@ private function processColumnUpdates(string $tableName, string $argName, array
252256
$columnName = $secondArg->value;
253257
}
254258

255-
$model = $this->modelDatabaseHelper->getModelInstance($modelClass);
256-
257-
if ($model === null) {
258-
throw new Exception('Model not found: ' . $modelClass);
259-
}
259+
/** @var class-string<Model> $modelClass */
260+
$model = $this->modelHelper->getModelInstance($modelClass);
260261

261262
$type = $this->modelDatabaseHelper->hasModelColumn($model, $model->getKeyName())
262263
? $this->modelDatabaseHelper->getModelColumn($model, $model->getKeyName())->readableType

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+
}

src/Types/ModelProperty/GenericModelPropertyType.php

+14-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
namespace Larastan\Larastan\Types\ModelProperty;
66

7+
use Illuminate\Database\Eloquent\Model;
78
use Illuminate\Support\Str;
9+
use Larastan\Larastan\Support\ModelHelper;
810
use Larastan\Larastan\Properties\ModelDatabaseHelper;
911
use PHPStan\Type\AcceptsResult;
1012
use PHPStan\Type\CompoundType;
@@ -30,7 +32,11 @@
3032

3133
class GenericModelPropertyType extends StringType
3234
{
33-
public function __construct(private Type $type, private ModelDatabaseHelper $modelDatabaseHelper)
35+
public function __construct(
36+
private Type $type,
37+
private ModelDatabaseHelper $modelDatabaseHelper,
38+
private ModelHelper $modelHelper,
39+
)
3440
{
3541
parent::__construct();
3642
}
@@ -76,12 +82,14 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult
7682
if (str_contains($givenString, '.')) {
7783
$tableName = Str::beforeLast($givenString, '.');
7884
$propertyName = Str::afterLast($givenString, '.');
85+
/** @var class-string<Model> $modelClass */
86+
$modelClass = $genericType->getObjectClassNames()[0];
7987

8088
// Assume the connection is the same as the generic model's connection
8189
// or fallback to the default connection
82-
$connection = $this->modelDatabaseHelper
83-
->getModelInstance($genericType->getObjectClassNames()[0])
84-
?->getConnectionName()
90+
$connection = $this->modelHelper
91+
->getModelInstance($modelClass)
92+
->getConnectionName()
8593
?? $this->modelDatabaseHelper->getDefaultConnection();
8694

8795
if (! isset($this->modelDatabaseHelper->connections[$connection]->tables[$tableName]->columns[$propertyName])) {
@@ -144,7 +152,7 @@ public function traverse(callable $cb): Type
144152
return $this;
145153
}
146154

147-
return new self($newType, $this->modelDatabaseHelper);
155+
return new self($newType, $this->modelDatabaseHelper, $this->modelHelper);
148156
}
149157

150158
public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
@@ -189,6 +197,6 @@ public function getReferencedTemplateTypes(TemplateTypeVariance $positionVarianc
189197
/** @param mixed[] $properties */
190198
public static function __set_state(array $properties): Type
191199
{
192-
return new self($properties['type'], $properties['modelDatabaseHelper']);
200+
return new self($properties['type'], $properties['modelDatabaseHelper'], $properties['modelHelper']);
193201
}
194202
}

src/Types/ModelProperty/ModelPropertyTypeNodeResolverExtension.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Illuminate\Database\Eloquent\Model;
88
use Larastan\Larastan\Properties\ModelDatabaseHelper;
9+
use Larastan\Larastan\Support\ModelHelper;
910
use PHPStan\Analyser\NameScope;
1011
use PHPStan\PhpDoc\TypeNodeResolver;
1112
use PHPStan\PhpDoc\TypeNodeResolverExtension;
@@ -28,6 +29,7 @@ public function __construct(
2829
protected TypeNodeResolver $baseResolver,
2930
protected bool $active,
3031
private ModelDatabaseHelper $modelDatabaseHelper,
32+
private ModelHelper $modelHelper,
3133
) {
3234
}
3335

@@ -59,6 +61,6 @@ public function resolve(TypeNode $typeNode, NameScope $nameScope): Type|null
5961
return new ErrorType();
6062
}
6163

62-
return new GenericModelPropertyType($genericType, $this->modelDatabaseHelper);
64+
return new GenericModelPropertyType($genericType, $this->modelDatabaseHelper, $this->modelHelper);
6365
}
6466
}

tests/Type/GeneralTypeTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public static function dataFileAsserts(): iterable
3131
yield from self::gatherAssertTypes(__DIR__ . '/data/bug-2057.php');
3232
yield from self::gatherAssertTypes(__DIR__ . '/data/bug-2073.php');
3333
yield from self::gatherAssertTypes(__DIR__ . '/data/bug-2111.php');
34+
yield from self::gatherAssertTypes(__DIR__ . '/data/bug-2188.php');
3435
yield from self::gatherAssertTypes(__DIR__ . '/data/conditionable.php');
3536
yield from self::gatherAssertTypes(__DIR__ . '/data/container-array-access.php');
3637
yield from self::gatherAssertTypes(__DIR__ . '/data/container-make.php');

tests/Type/GenericModelPropertyTypeTest.php

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use App\Account;
88
use App\User;
99
use Larastan\Larastan\Properties\ModelDatabaseHelper;
10+
use Larastan\Larastan\Support\ModelHelper;
1011
use Larastan\Larastan\Types\ModelProperty\GenericModelPropertyType;
1112
use PHPStan\Testing\PHPStanTestCase;
1213
use PHPStan\Type\Constant\ConstantStringType;
@@ -188,6 +189,7 @@ private static function genericPropertyType(string $class): GenericModelProperty
188189
return new GenericModelPropertyType(
189190
new ObjectType($class),
190191
self::getContainer()->getByType(ModelDatabaseHelper::class),
192+
new ModelHelper(self::createReflectionProvider()),
191193
);
192194
}
193195
}

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)