10000 [TypeInfo] Add `TypeFactoryTrait::fromValue` method · symfony/symfony@7b09bed · GitHub
[go: up one dir, main page]

Skip to content

Commit 7b09bed

Browse files
committed
[TypeInfo] Add TypeFactoryTrait::fromValue method
1 parent 18d08ff commit 7b09bed

File tree

3 files changed

+153
-0
lines changed

3 files changed

+153
-0
lines changed

src/Symfony/Component/TypeInfo/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Add `Type::accepts()` method
8+
* Add `TypeFactoryTrait::fromValue()` method
89

910
7.2
1011
---

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

+57
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,61 @@ public function testCreateNullable()
206206
Type::nullable(Type::union(Type::int(), Type::string(), Type::null())),
207207
);
208208
}
209+
210+
/**
211+
* @dataProvider createFromValueProvider
212+
*/
213+
public function testCreateFromValue(Type $expected, mixed $value)
214+
{
215+
$this->assertEquals($expected, Type::fromValue($value));
216+
}
217+
218+
/**
219+
* @return iterable<array{0: Type, 1: mixed}>
220+
*/
221+
public static function createFromValueProvider(): iterable
222+
{
223+
// builtin
224+
yield [Type::null(), null];
225+
yield [Type::true(), true];
226+
yield [Type::false(), false];
227+
yield [Type::int(), 1];
228+
yield [Type::float(), 1.1];
229+
yield [Type::string(), 'string'];
230+
yield [Type::callable(), strtoupper(...)];
231+
yield [Type::resource(), fopen('php://temp', 'r')];
232+
233+
// object
234+
yield [Type::object(\DateTimeImmutable::class), new \DateTimeImmutable()];
235+
yield [Type::object(), new \stdClass()];
236+
237+
// collection
238+
$arrayAccess = new class() implements \ArrayAccess {
239+
public function offsetExists(mixed $offset): bool
240+
{
241+
return true;
242+
}
243+
244+
public function offsetGet(mixed $offset): mixed
245+
{
246+
return null;
247+
}
248+
249+
public function offsetSet(mixed $offset, mixed $value): void
250+
{
251+
}
252+
253+
public function offsetUnset(mixed $offset): void
254+
{
255+
}
256+
};
257+
258+
yield [Type::list(Type::int()), [1, 2, 3]];
259+
yield [Type::dict(Type::bool()), ['a' => true, 'b' => false]];
260+
yield [Type::array(Type::string()), [1 => 'foo', 'bar' => 'baz']];
261+
yield [Type::array(Type::nullable(Type::bool()), Type::int()), [1 => true, 2 => null, 3 => false]];
262+
yield [Type::collection(Type::object(\ArrayIterator::class), Type::mixed(), Type::union(Type::int(), Type::string())), new \ArrayIterator()];
263+
yield [Type::collection(Type::object(\Generator::class), Type::string(), Type::int()), (fn (): iterable => yield 'string')()];
264+
yield [Type::collection(Type::object($arrayAccess::class)), $arrayAccess];
265+
}
209266
}

src/Symfony/Component/TypeInfo/TypeFactoryTrait.php

+95
Original file line numberDiff line numberDiff line change
@@ -340,4 +340,99 @@ public static function nullable(Type $type): Type
340340

341341
return new NullableType($type);
342342
}
343+
344+
public static function fromValue(mixed $value): Type
345+
{
346+
$type = match ($value) {
347+
null => self::null(),
348+
true => self::true(),
349+
false => self::false(),
350+
default => null,
351+
};
352+
353+
if (null !== $type) {
354+
return $type;
355+
}
356+
357+
if (\is_callable($value)) {
358+
return Type::callable();
359+
}
360+
361+
if (\is_resource($value)) {
362+
return Type::resource();
363+
}
364+
365+
$type = match (get_debug_type($value)) {
366+
TypeIdentifier::INT->value => self::int(),
367+
TypeIdentifier::FLOAT->value => self::float(),
368+
TypeIdentifier::STRING->value => self::string(),
369+
default => null,
370+
};
371+
372+
if (null !== $type) {
373+
return $type;
374+
}
375+
376+
$type = match (true) {
377+
\is_object($value) => \stdClass::class === $value::class ? self::object() : self::object($value::class),
378+
\is_array($value) => self::builtin(TypeIdentifier::ARRAY),
379+
default => null,
380+
};
381+
382+
if (null === $type) {
383+
return Type::mixed();
384+
}
385+
386+
if (is_iterable($value)) {
387+
/** @var list<BuiltinType<TypeIdentifier::INT>|BuiltinType<TypeIdentifier::STRING>> $keyTypes */
388+
$keyTypes = [];
389+
390+
/** @var list<Type|null> $valueTypes */
391+
$valueTypes = [];
392+
393+
$i = 0;
394+
395+
foreach ($value as $k => $v) {
396+
$keyTypes[] = self::fromValue($k);
397+
$keyTypes = array_unique($keyTypes);
398+
399+
$valueTypes[] = self::fromValue($v);
400+
$valueTypes = array_unique($valueTypes);
401+
}
402+
403+
if ([] !== $keyTypes) {
404+
$keyTypes = array_values($keyTypes);
405+
$keyType = \count($keyTypes) > 1 ? self::union(...$keyTypes) : $keyTypes[0];
406+
407+
$valueType = null;
408+
foreach ($valueTypes as &$v) {
409+
if ($v->isIdentifiedBy(TypeIdentifier::MIXED)) {
410+
$valueType = Type::mixed();
411+
412+
break;
413+
}
414+
415+
if ($v->isIdentifiedBy(TypeIdentifier::TRUE, TypeIdentifier::FALSE)) {
416+
$v = Type::bool();
417+
}
418+
}
419+
420+
if (!$valueType) {
421+
$valueTypes = array_values(array_unique($valueTypes));
422+
$valueType = \count($valueTypes) > 1 ? self::union(...$valueTypes) : $valueTypes[0];
423+
}
424+
} else {
425+
$keyType = Type::union(Type::int(), Type::string());
426+
$valueType = Type::mixed();
427+
}
428+
429+
return self::collection($type, $valueType, $keyType, \is_array($value) && \array_is_list($value));
430+
}
431+
432+
if ($value instanceof \ArrayAccess) {
433+
return self::collection($type);
434+
}
435+
436+
return $type;
437+
}
343438
}

0 commit comments

Comments
 (0)
0