Skip to content

Commit 586c8bb

Browse files
authored
Fix active record array access (proget-hq#39)
1 parent d91f567 commit 586c8bb

8 files changed

+70
-35
lines changed

composer.lock

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

phpstan.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ includes:
55
- extension.neon
66

77
parameters:
8-
autoload_files:
8+
scanFiles:
99
- vendor/yiisoft/yii2/Yii.php
1010

1111
yii2:

src/ServiceMap.php

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ final class ServiceMap
1414
private $services = [];
1515

1616
/**
17-
* @var (string[]|object)[]
17+
* @var array<string, string>
1818
*/
1919
private $components = [];
2020

@@ -39,7 +39,7 @@ public function __construct(string $configPath)
3939

4040
foreach ($config['components'] ?? [] as $id => $component) {
4141
if (is_object($component)) {
42-
$this->components[$id] = $component;
42+
$this->components[$id] = \get_class($component);
4343
continue;
4444
}
4545

@@ -48,7 +48,7 @@ public function __construct(string $configPath)
4848
}
4949

5050
if (null !== $class = $component['class'] ?? null) {
51-
$this->components[$id]['class'] = $class;
51+
$this->components[$id] = $class;
5252
}
5353
}
5454
}
@@ -64,12 +64,7 @@ public function getServiceClassFromNode(Node $node): ?string
6464

6565
public function getComponentClassById(string $id): ?string
6666
{
67-
// Special case in which the component is already initialized
68-
if (isset($this->components[$id]) && is_object($this->components[$id])) {
69-
return get_class($this->components[$id]);
70-
}
71-
72-
return $this->components[$id]['class'] ?? null;
67+
return $this->components[$id] ?? null;
7368
}
7469

7570
/**

src/Type/ActiveQueryDynamicMethodReturnTypeExtension.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use PHPStan\Type\IntegerType;
1616
use PHPStan\Type\MixedType;
1717
use PHPStan\Type\NullType;
18-
use PHPStan\Type\ObjectType;
1918
use PHPStan\Type\StringType;
2019
use PHPStan\Type\ThisType;
2120
use PHPStan\Type\Type;
@@ -61,13 +60,13 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
6160
if ($methodName === 'one') {
6261
return TypeCombinator::union(
6362
new NullType(),
64-
$calledOnType->isAsArray() ? new ArrayType(new StringType(), new MixedType()) : new ObjectType($calledOnType->getModelClass())
63+
$calledOnType->isAsArray() ? new ArrayType(new StringType(), new MixedType()) : new ActiveRecordObjectType($calledOnType->getModelClass())
6564
);
6665
}
6766

6867
return new ArrayType(
6968
new IntegerType(),
70-
$calledOnType->isAsArray() ? new ArrayType(new StringType(), new MixedType()) : new ObjectType($calledOnType->getModelClass())
69+
$calledOnType->isAsArray() ? new ArrayType(new StringType(), new MixedType()) : new ActiveRecordObjectType($calledOnType->getModelClass())
7170
);
7271
}
7372
}

src/Type/ActiveRecordDynamicStaticMethodReturnTypeExtension.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
use PHPStan\Reflection\MethodReflection;
1111
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
1212
use PHPStan\Type\NullType;
13-
use PHPStan\Type\ObjectType;
1413
use PHPStan\Type\Type;
1514
use PHPStan\Type\TypeCombinator;
1615

@@ -36,7 +35,7 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection,
3635
if ($methodName === 'findOne') {
3736
return TypeCombinator::union(
3837
new NullType(),
39-
new ObjectType($name)
38+
new ActiveRecordObjectType($name)
4039
);
4140
}
4241

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Proget\PHPStan\Yii2\Type;
6+
7+
use PHPStan\TrinaryLogic;
8+
use PHPStan\Type\Constant\ConstantStringType;
9+
use PHPStan\Type\ErrorType;
10+
use PHPStan\Type\ObjectType;
11+
use PHPStan\Type\Type;
12+
13+
class ActiveRecordObjectType extends ObjectType
14+
{
15+
public function hasOffsetValueType(Type $offsetType): TrinaryLogic
16+
{
17+
if (!$offsetType instanceof ConstantStringType) {
18+
return TrinaryLogic::createNo();
19+
}
20+
21+
if ($this->isInstanceOf(\ArrayAccess::class)->yes()) {
22+
return TrinaryLogic::createFromBoolean($this->hasProperty($offsetType->getValue())->yes());
23+
}
24+
25+
return parent::hasOffsetValueType($offsetType);
26+
}
27+
28+
public function setOffsetValueType(?Type $offsetType, Type $valueType): Type
29+
{
30+
if ($offsetType instanceof ConstantStringType && $this->hasProperty($offsetType->getValue())->no()) {
31+
return new ErrorType();
32+
}
33+
34+
return $this;
35+
}
36+
}

tests/ServiceMapTest.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,16 @@ public function testItLoadsServicesAndComponents(): void
3939
{
4040
$serviceMap = new ServiceMap(__DIR__.DIRECTORY_SEPARATOR.'assets'.DIRECTORY_SEPARATOR.'yii-config-valid.php');
4141

42-
$this->assertSame(\SplStack::class, $serviceMap->getServiceClassFromNode(new String_('singleton-closure')));
43-
$this->assertSame(\SplObjectStorage::class, $serviceMap->getServiceClassFromNode(new String_('singleton-service')));
44-
$this->assertSame(\SplFileInfo::class, $serviceMap->getServiceClassFromNode(new String_('singleton-nested-service-class')));
42+
self::assertSame(\SplStack::class, $serviceMap->getServiceClassFromNode(new String_('singleton-closure')));
43+
self::assertSame(\SplObjectStorage::class, $serviceMap->getServiceClassFromNode(new String_('singleton-service')));
44+
self::assertSame(\SplFileInfo::class, $serviceMap->getServiceClassFromNode(new String_('singleton-nested-service-class')));
4545

46-
$this->assertSame(\SplStack::class, $serviceMap->getServiceClassFromNode(new String_('closure')));
47-
$this->assertSame(\SplObjectStorage::class, $serviceMap->getServiceClassFromNode(new String_('service')));
48-
$this->assertSame(\SplFileInfo::class, $serviceMap->getServiceClassFromNode(new String_('nested-service-class')));
46+
self::assertSame(\SplStack::class, $serviceMap->getServiceClassFromNode(new String_('closure')));
47+
self::assertSame(\SplObjectStorage::class, $serviceMap->getServiceClassFromNode(new String_('service')));
48+
self::assertSame(\SplFileInfo::class, $serviceMap->getServiceClassFromNode(new String_('nested-service-class')));
4949

50-
$this->assertSame(MyActiveRecord::class, $serviceMap->getComponentClassById('customComponent'));
51-
$this->assertSame(MyActiveRecord::class, $serviceMap->getComponentClassById('customInitializedComponent'));
50+
self::assertSame(MyActiveRecord::class, $serviceMap->getComponentClassById('customComponent'));
51+
self::assertSame(MyActiveRecord::class, $serviceMap->getComponentClassById('customInitializedComponent'));
5252
}
5353

5454
/**

tests/Yii/MyController.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,20 @@ final class MyController extends \yii\web\Controller
88
{
99
public function actionMy(): void
1010
{
11+
$offsetProp = 'flag';
12+
$flag = false;
1113
$record = MyActiveRecord::find()->where(['flag' => \Yii::$app->request->post('flag', true)])->one();
1214
if ($record) {
1315
$record->flag = false;
16+
$flag = $record[$offsetProp];
17+
$record[$offsetProp] = true;
1418
$record->save();
1519
}
1620

1721
$record = MyActiveRecord::findOne(['condition']);
1822
if ($record) {
1923
$flag = $record->flag;
24+
$flag = $record['flag'];
2025
}
2126

2227
$records = MyActiveRecord::find()->asArray()->where(['flag' => \Yii::$app->request->post('flag', true)])->all();
@@ -33,6 +38,7 @@ public function actionMy(): void
3338
foreach ($records as $record) {
3439
$flag = $record->flag;
3540
$flag = $record['flag'];
41+
$record['flag'] = true;
3642
}
3743

3844
\Yii::$app->response->data = \Yii::$app->request->rawBody;

0 commit comments

Comments
 (0)