Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit294b185

Browse files
committed
[TypeInfo] AddArrayShapeType
1 parentb28e597 commit294b185

File tree

8 files changed

+243
-3
lines changed

8 files changed

+243
-3
lines changed

‎src/Symfony/Component/TypeInfo/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
* Deprecate constructing a`CollectionType` instance as a list that is not an array
1010
* Deprecate the third`$asList` argument of`TypeFactoryTrait::iterable()`, use`TypeFactoryTrait::list()` instead
1111
* Add type alias support in`TypeContext` and`StringTypeResolver`
12+
* Add`ArrayShapeType` to represent the exact shape of an array
1213

1314
7.2
1415
---
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespaceSymfony\Component\TypeInfo\Tests\Type;
13+
14+
usePHPUnit\Framework\TestCase;
15+
useSymfony\Component\TypeInfo\Type;
16+
useSymfony\Component\TypeInfo\Type\ArrayShapeType;
17+
18+
class ArrayShapeTypeTestextends TestCase
19+
{
20+
publicfunctiontestGetCollectionKeyType()
21+
{
22+
$type =newArrayShapeType([
23+
1 => ['type' => Type::bool(),'optional' =>false],
24+
]);
25+
$this->assertEquals(Type::int(),$type->getCollectionKeyType());
26+
27+
$type =newArrayShapeType([
28+
'foo' => ['type' => Type::bool(),'optional' =>false],
29+
]);
30+
$this->assertEquals(Type::string(),$type->getCollectionKeyType());
31+
32+
$type =newArrayShapeType([
33+
1 => ['type' => Type::bool(),'optional' =>false],
34+
'foo' => ['type' => Type::bool(),'optional' =>false],
35+
]);
36+
$this->assertEquals(Type::union(Type::int(), Type::string()),$type->getCollectionKeyType());
37+
}
38+
39+
publicfunctiontestGetCollectionValueType()
40+
{
41+
$type =newArrayShapeType([
42+
1 => ['type' => Type::bool(),'optional' =>false],
43+
]);
44+
$this->assertEquals(Type::bool(),$type->getCollectionValueType());
45+
46+
$type =newArrayShapeType([
47+
'foo' => ['type' => Type::bool(),'optional' =>false],
48+
'bar' => ['type' => Type::int(),'optional' =>false],
49+
]);
50+
$this->assertEquals(Type::union(Type::int(), Type::bool()),$type->getCollectionValueType());
51+
52+
$type =newArrayShapeType([
53+
'foo' => ['type' => Type::bool(),'optional' =>false],
54+
'bar' => ['type' => Type::nullable(Type::string()),'optional' =>false],
55+
]);
56+
$this->assertEquals(Type::nullable(Type::union(Type::bool(), Type::string())),$type->getCollectionValueType());
57+
58+
$type =newArrayShapeType([
59+
'foo' => ['type' => Type::true(),'optional' =>false],
60+
'bar' => ['type' => Type::false(),'optional' =>false],
61+
]);
62+
$this->assertEquals(Type::bool(),$type->getCollectionValueType());
63+
}
64+
65+
publicfunctiontestAccepts()
66+
{
67+
$type =newArrayShapeType([
68+
'foo' => ['type' => Type::bool(),'optional' =>false],
69+
'bar' => ['type' => Type::string(),'optional' =>true],
70+
]);
71+
72+
$this->assertFalse($type->accepts('string'));
73+
$this->assertFalse($type->accepts([]));
74+
$this->assertFalse($type->accepts(['foo' =>'string']));
75+
$this->assertFalse($type->accepts(['foo' =>true,'other' =>'string']));
76+
77+
$this->assertTrue($type->accepts(['foo' =>true]));
78+
$this->assertTrue($type->accepts(['foo' =>true,'bar' =>'string']));
79+
}
80+
81+
publicfunctiontestToString()
82+
{
83+
$type =newArrayShapeType([1 => ['type' => Type::bool(),'optional' =>false]]);
84+
$this->assertSame('array{1: bool}', (string)$type);
85+
86+
$type =newArrayShapeType([
87+
2 => ['type' => Type::int(),'optional' =>true],
88+
1 => ['type' => Type::bool(),'optional' =>false],
89+
]);
90+
$this->assertSame('array{1: bool, 2?: int}', (string)$type);
91+
92+
$type =newArrayShapeType([
93+
'foo' => ['type' => Type::bool(),'optional' =>false],
94+
'bar' => ['type' => Type::string(),'optional' =>true],
95+
]);
96+
$this->assertSame("array{'bar'?: string, 'foo': bool}", (string)$type);
97+
}
98+
}

‎src/Symfony/Component/TypeInfo/Tests/TypeFactoryTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
useSymfony\Component\TypeInfo\Tests\Fixtures\DummyBackedEnum;
1717
useSymfony\Component\TypeInfo\Tests\Fixtures\DummyEnum;
1818
useSymfony\Component\TypeInfo\Type;
19+
useSymfony\Component\TypeInfo\Type\ArrayShapeType;
1920
useSymfony\Component\TypeInfo\Type\BackedEnumType;
2021
useSymfony\Component\TypeInfo\Type\BuiltinType;
2122
useSymfony\Component\TypeInfo\Type\CollectionType;
@@ -205,6 +206,12 @@ public function testCreateNullable()
205206
);
206207
}
207208

209+
publicfunctiontestCreateArrayShape()
210+
{
211+
$this->assertEquals(newArrayShapeType(['foo' => ['type' => Type::bool(),'optional' =>true]]), Type::arrayShape(['foo' => ['type' => Type::bool(),'optional' =>true]]));
212+
$this->assertEquals(newArrayShapeType(['foo' => ['type' => Type::bool(),'optional' =>false]]), Type::arrayShape(['foo' => Type::bool()]));
213+
}
214+
208215
/**
209216
* @dataProvider createFromValueProvider
210217
*/

‎src/Symfony/Component/TypeInfo/Tests/TypeResolver/StringTypeResolverTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ public static function resolveDataProvider(): iterable
7474
yield [Type::list(Type::bool()),'bool[]'];
7575

7676
// array shape
77-
yield [Type::array(),'array{0: true, 1: false}'];
77+
yield [Type::arrayShape(['foo' => Type::true(),1 => Type::false()]),'array{foo: true, 1: false}'];
78+
yield [Type::arrayShape(['foo' => ['type' => Type::bool(),'optional' =>true]]),'array{foo?: bool}'];
7879

7980
// object shape
8081
yield [Type::object(),'object{foo: true, bar: false}'];
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespaceSymfony\Component\TypeInfo\Type;
13+
14+
useSymfony\Component\TypeInfo\Type;
15+
useSymfony\Component\TypeInfo\TypeIdentifier;
16+
17+
/**
18+
* Represents the exact shape of an array.
19+
*
20+
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
21+
*
22+
* @extends CollectionType<GenericType<BuiltinType<TypeIdentifier::ARRAY>>>
23+
*/
24+
finalclass ArrayShapeTypeextends CollectionType
25+
{
26+
/**
27+
* @var array<array{type: Type, optional: bool}>
28+
*/
29+
privatereadonlyarray$shape;
30+
31+
/**
32+
* @param array<array{type: Type, optional: bool}> $shape
33+
*/
34+
publicfunction__construct(array$shape)
35+
{
36+
$keyTypes = [];
37+
$valueTypes = [];
38+
39+
foreach ($shapeas$k =>$v) {
40+
$keyTypes[] =self::fromValue($k);
41+
$valueTypes[] =$v['type'];
42+
}
43+
44+
if ($keyTypes) {
45+
$keyTypes =array_values(array_unique($keyTypes));
46+
$keyType =\count($keyTypes) >1 ?self::union(...$keyTypes) :$keyTypes[0];
47+
}else {
48+
$keyType = Type::union(Type::int(), Type::string());
49+
}
50+
51+
$valueType =$valueTypes ? CollectionType::mergeCollectionValueTypes($valueTypes) : Type::mixed();
52+
53+
parent::__construct(self::generic(self::builtin(TypeIdentifier::ARRAY),$keyType,$valueType));
54+
55+
$sortedShape =$shape;
56+
ksort($sortedShape);
57+
58+
$this->shape =$sortedShape;
59+
}
60+
61+
/**
62+
* @return array<array{type: Type, optional: bool}>
63+
*/
64+
publicfunctiongetShape():array
65+
{
66+
return$this->shape;
67+
}
68+
69+
publicfunctionaccepts(mixed$value):bool
70+
{
71+
if (!\is_array($value)) {
72+
returnfalse;
73+
}
74+
75+
foreach ($this->shapeas$key =>$shapeValue) {
76+
if (!($shapeValue['optional'] ??false) && !\array_key_exists($key,$value)) {
77+
returnfalse;
78+
}
79+
}
80+
81+
foreach ($valueas$key =>$itemValue) {
82+
$valueType =$this->shape[$key]['type'] ??false;
83+
if (!$valueType) {
84+
returnfalse;
85+
}
86+
87+
if (!$valueType->accepts($itemValue)) {
88+
returnfalse;
89+
}
90+
}
91+
92+
returntrue;
93+
}
94+
95+
publicfunction__toString():string
96+
{
97+
$items = [];
98+
99+
foreach ($this->shapeas$key =>$value) {
100+
$itemKey =\is_int($key) ? (string)$key :\sprintf("'%s'",$key);
101+
if ($value['optional'] ??false) {
102+
$itemKey =\sprintf('%s?',$itemKey);
103+
}
104+
105+
$items[] =\sprintf('%s: %s',$itemKey,$value['type']);
106+
}
107+
108+
return\sprintf('array{%s}',implode(',',$items));
109+
}
110+
}

‎src/Symfony/Component/TypeInfo/Type/CollectionType.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
*
2626
* @implements WrappingTypeInterface<T>
2727
*/
28-
finalclass CollectionTypeextends Typeimplements WrappingTypeInterface
28+
class CollectionTypeextends Typeimplements WrappingTypeInterface
2929
{
3030
/**
3131
* @param T $type

‎src/Symfony/Component/TypeInfo/TypeFactoryTrait.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespaceSymfony\Component\TypeInfo;
1313

14+
useSymfony\Component\TypeInfo\Type\ArrayShapeType;
1415
useSymfony\Component\TypeInfo\Type\BackedEnumType;
1516
useSymfony\Component\TypeInfo\Type\BuiltinType;
1617
useSymfony\Component\TypeInfo\Type\CollectionType;
@@ -194,6 +195,20 @@ public static function dict(?Type $value = null): CollectionType
194195
returnself::array($value,self::string());
195196
}
196197

198+
/**
199+
* @param array<array{type: Type, optional?: bool}|Type> $shape
200+
*
201+
* @return CollectionType<BuiltinType<TypeIdentifier::ARRAY>>
202+
*/
203+
publicstaticfunctionarrayShape(array$shape):CollectionType
204+
{
205+
returnnewArrayShapeType(array_map(staticfunction (array|Type$item):array {
206+
return$iteminstanceof Type
207+
? ['type' =>$item,'optional' =>false]
208+
: ['type' =>$item['type'],'optional' =>$item['optional'] ??false];
209+
},$shape));
210+
}
211+
197212
/**
198213
* @template T of class-string
199214
*

‎src/Symfony/Component/TypeInfo/TypeResolver/StringTypeResolver.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,15 @@ private function getTypeFromNode(TypeNode $node, ?TypeContext $typeContext): Typ
102102
}
103103

104104
if ($nodeinstanceof ArrayShapeNode) {
105-
return Type::array();
105+
$shape = [];
106+
foreach ($node->itemsas$item) {
107+
$shape[(string)$item->keyName] = [
108+
'type' =>$this->getTypeFromNode($item->valueType,$typeContext),
109+
'optional' =>$item->optional,
110+
];
111+
}
112+
113+
return Type::arrayShape($shape);
106114
}
107115

108116
if ($nodeinstanceof ObjectShapeNode) {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp