Skip to content

Commit c829e53

Browse files
committedFeb 24, 2025··
Fix invalid interface implementation by dynamic return type
The object storage offsetGet() is not compatible with the ArrayAccess interface but does some TYPO3 sepcific stuff. We can't set the correct return type via a stub as phpstan will complain that the return type is not compatible with the interface. To work around this we have to implement a dynamic return type extension again.
1 parent 8a361cf commit c829e53

File tree

6 files changed

+67
-8
lines changed

6 files changed

+67
-8
lines changed
 

‎extension.neon

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ services:
88
class: SaschaEgerer\PhpstanTypo3\Type\ValidatorResolverDynamicReturnTypeExtension
99
tags:
1010
- phpstan.broker.dynamicMethodReturnTypeExtension
11+
-
12+
class: SaschaEgerer\PhpstanTypo3\Type\ObjectStorageDynamicReturnTypeExtension
13+
tags:
14+
- phpstan.broker.dynamicMethodReturnTypeExtension
1115
-
1216
class: SaschaEgerer\PhpstanTypo3\Type\ContextDynamicReturnTypeExtension
1317
arguments:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SaschaEgerer\PhpstanTypo3\Type;
4+
5+
use PhpParser\Node\Arg;
6+
use PhpParser\Node\Expr\MethodCall;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Reflection\MethodReflection;
9+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
10+
use PHPStan\Type\IntegerType;
11+
use PHPStan\Type\MixedType;
12+
use PHPStan\Type\StringType;
13+
use PHPStan\Type\Type;
14+
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
15+
16+
class ObjectStorageDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
17+
{
18+
19+
public function getClass(): string
20+
{
21+
return ObjectStorage::class;
22+
}
23+
24+
public function isMethodSupported(
25+
MethodReflection $methodReflection
26+
): bool
27+
{
28+
return $methodReflection->getName() === 'offsetGet';
29+
}
30+
31+
public function getTypeFromMethodCall(
32+
MethodReflection $methodReflection,
33+
MethodCall $methodCall,
34+
Scope $scope
35+
): ?Type
36+
{
37+
$firstArgument = $methodCall->args[0] ?? null;
38+
39+
if (!$firstArgument instanceof Arg) {
40+
return null;
41+
}
42+
43+
$argumentType = $scope->getType($firstArgument->value);
44+
45+
if ((new StringType())->isSuperTypeOf($argumentType)->yes()) {
46+
return null;
47+
}
48+
if ((new IntegerType())->isSuperTypeOf($argumentType)->yes()) {
49+
return null;
50+
}
51+
return new MixedType();
52+
}
53+
54+
}

‎stubs/ObjectStorage.stub

+1-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
namespace TYPO3\CMS\Extbase\Persistence;
33

44
/**
5-
* @template TEntity
5+
* @template TEntity of object
66
* @implements \ArrayAccess<string, TEntity>
77
* @implements \Iterator<string, TEntity>
88
* @phpstan-type ObjectStorageInternal array{obj: TEntity, inf: mixed}
@@ -31,12 +31,6 @@ class ObjectStorage implements \Iterator, \ArrayAccess
3131
*/
3232
public function offsetUnset($value);
3333

34-
/**
35-
* @param TEntity|int|string $value
36-
* @return ($value is int ? TEntity|null : mixed)
37-
*/
38-
public function offsetGet($value);
39-
4034
/**
4135
* This is different from the SplObjectStorage as the key in this implementation is the object hash (string).
4236
* @phpstan-ignore-next-line See https://forge.typo3.org/issues/98146

‎stubs/QueryResultInterface.stub

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
namespace TYPO3\CMS\Extbase\Persistence;
33

44
/**
5-
* @template TKey
5+
* @template TKey of int
66
* @template TValue of object
77
* @extends \Iterator<TKey, TValue>
88
* @extends \ArrayAccess<TKey, TValue>

‎tests/Unit/Type/QueryResultToArrayDynamicReturnTypeExtension/data/query-result-to-array.php

+6
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,12 @@ public function showAction(): void
135135
'list<SaschaEgerer\PhpstanTypo3\Tests\Unit\Type\QueryResultToArrayDynamicReturnTypeExtension\FrontendUserGroup>',
136136
$myObjects
137137
);
138+
139+
$key = $queryResult->key();
140+
assertType(
141+
'int',
142+
$key
143+
);
138144
}
139145

140146
}

‎tests/Unit/Type/data/object-storage-stub-files.php

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public function foo(): void
2929
}
3030

3131
assertType(self::class . '|null', $objectStorage->offsetGet(0));
32+
assertType(self::class . '|null', $objectStorage->current());
3233

3334
// We ignore errors in the next line as this will produce an
3435
// "Offset 0 does not exist on TYPO3\CMS\Extbase\Persistence\ObjectStorage<ObjectStorage\My\Test\Extension\Domain\Model\MyModel>

0 commit comments

Comments
 (0)
Please sign in to comment.