diff --git a/src/Guesser/StubAsGuesser/StubAsBackedEnumGuesser.php b/src/Guesser/StubAsGuesser/StubAsBackedEnumGuesser.php index 71d3d67..5f45906 100644 --- a/src/Guesser/StubAsGuesser/StubAsBackedEnumGuesser.php +++ b/src/Guesser/StubAsGuesser/StubAsBackedEnumGuesser.php @@ -6,6 +6,7 @@ use Ingenerator\StubObjects\Attribute\StubAs; use Ingenerator\StubObjects\Attribute\StubAs\StubAsBackedEnum; use Ingenerator\StubObjects\Guesser\StubAsGuesser; +use Ingenerator\StubObjects\ReflectionUtils; use Ingenerator\StubObjects\StubbingContext; use ReflectionProperty; @@ -13,16 +14,12 @@ class StubAsBackedEnumGuesser implements StubAsGuesser { public function guessCaster(ReflectionProperty $property, StubbingContext $context): false|StubAs { - if ( ! $property->getType()) { + if (ReflectionUtils::isBuiltinType($property)) { return FALSE; } - if ($property->getType()->isBuiltin()) { - return FALSE; - } - - $type = $property->getType()->getName(); - if (is_a($type, BackedEnum::class, TRUE)) { + $type = ReflectionUtils::getTypeNameIfAvailable($property); + if ($type && is_a($type, BackedEnum::class, TRUE)) { return new StubAsBackedEnum($type); } diff --git a/src/Guesser/StubAsGuesser/StubAsCollectionGuesser.php b/src/Guesser/StubAsGuesser/StubAsCollectionGuesser.php index b4a768b..efde234 100644 --- a/src/Guesser/StubAsGuesser/StubAsCollectionGuesser.php +++ b/src/Guesser/StubAsGuesser/StubAsCollectionGuesser.php @@ -6,6 +6,7 @@ use Ingenerator\StubObjects\Attribute\StubAs; use Ingenerator\StubObjects\Attribute\StubAs\StubAsCollection; use Ingenerator\StubObjects\Guesser\StubAsGuesser; +use Ingenerator\StubObjects\ReflectionUtils; use Ingenerator\StubObjects\StubbingContext; use ReflectionProperty; @@ -13,8 +14,7 @@ class StubAsCollectionGuesser implements StubAsGuesser { public function guessCaster(ReflectionProperty $property, StubbingContext $context): false|StubAs { - // @todo: test with untyped props - if ($property->getType()?->getName() === Collection::class) { + if (ReflectionUtils::getTypeNameIfAvailable($property) === Collection::class) { $collected_class = $this->parseItemClass($property); return new StubAsCollection($property->getType()->getName(), $collected_class); diff --git a/src/Guesser/StubAsGuesser/StubAsDateTimeGuesser.php b/src/Guesser/StubAsGuesser/StubAsDateTimeGuesser.php index 04edcbc..8400546 100644 --- a/src/Guesser/StubAsGuesser/StubAsDateTimeGuesser.php +++ b/src/Guesser/StubAsGuesser/StubAsDateTimeGuesser.php @@ -6,6 +6,7 @@ use Ingenerator\StubObjects\Attribute\StubAs; use Ingenerator\StubObjects\Attribute\StubAs\StubAsDateTime; use Ingenerator\StubObjects\Guesser\StubAsGuesser; +use Ingenerator\StubObjects\ReflectionUtils; use Ingenerator\StubObjects\StubbingContext; use ReflectionProperty; @@ -13,8 +14,7 @@ class StubAsDateTimeGuesser implements StubAsGuesser { public function guessCaster(ReflectionProperty $property, StubbingContext $context): false|StubAs { - // @todo test with untyped props - if ($property->getType()?->getName() === DateTimeImmutable::class) { + if (ReflectionUtils::getTypeNameIfAvailable($property) === DateTimeImmutable::class) { return new StubAsDateTime(); } diff --git a/src/Guesser/StubAsGuesser/StubAsStubObjectGuesser.php b/src/Guesser/StubAsGuesser/StubAsStubObjectGuesser.php index ef58a8c..3ae8572 100644 --- a/src/Guesser/StubAsGuesser/StubAsStubObjectGuesser.php +++ b/src/Guesser/StubAsGuesser/StubAsStubObjectGuesser.php @@ -5,6 +5,7 @@ use Ingenerator\StubObjects\Attribute\StubAs; use Ingenerator\StubObjects\Attribute\StubAs\StubAsStubObject; use Ingenerator\StubObjects\Guesser\StubAsGuesser; +use Ingenerator\StubObjects\ReflectionUtils; use Ingenerator\StubObjects\StubbingContext; use ReflectionProperty; @@ -12,12 +13,11 @@ class StubAsStubObjectGuesser implements StubAsGuesser { public function guessCaster(ReflectionProperty $property, StubbingContext $context): false|StubAs { - // @todo: test with untyped props - if ($property->getType()?->isBuiltin()) { + if (ReflectionUtils::isBuiltinType($property)) { return FALSE; } - $class = $property->getType()?->getName(); + $class = ReflectionUtils::getTypeNameIfAvailable($property); if ($class && $context->isStubbable($class)) { return new StubAsStubObject($class); } diff --git a/src/Guesser/StubDefaultGuesser/StubDefualtDateTimeGuesser.php b/src/Guesser/StubDefaultGuesser/StubDefaultDateTimeGuesser.php similarity index 79% rename from src/Guesser/StubDefaultGuesser/StubDefualtDateTimeGuesser.php rename to src/Guesser/StubDefaultGuesser/StubDefaultDateTimeGuesser.php index 684a0c3..44fedaf 100644 --- a/src/Guesser/StubDefaultGuesser/StubDefualtDateTimeGuesser.php +++ b/src/Guesser/StubDefaultGuesser/StubDefaultDateTimeGuesser.php @@ -6,14 +6,15 @@ use Ingenerator\StubObjects\Attribute\StubDefault; use Ingenerator\StubObjects\Attribute\StubDefault\StubDefaultValue; use Ingenerator\StubObjects\Guesser\StubDefaultGuesser; +use Ingenerator\StubObjects\ReflectionUtils; use Ingenerator\StubObjects\StubbingContext; use ReflectionProperty; -class StubDefualtDateTimeGuesser implements StubDefaultGuesser +class StubDefaultDateTimeGuesser implements StubDefaultGuesser { public function guessProvider(ReflectionProperty $property, StubbingContext $context): false|StubDefault { - if ($property->getType()->getName() === DateTimeImmutable::class) { + if (ReflectionUtils::getTypeNameIfAvailable($property) === DateTimeImmutable::class) { // Note that we're guessing this in string form, converting back to a DateTime happens at the // point of hydrating values because we anyway need to do it there for values that came in overrides return new StubDefaultValue('now'); diff --git a/src/Guesser/StubDefaultGuesser/StubDefaultStringGuesser.php b/src/Guesser/StubDefaultGuesser/StubDefaultStringGuesser.php index d7c8a0d..3608b44 100644 --- a/src/Guesser/StubDefaultGuesser/StubDefaultStringGuesser.php +++ b/src/Guesser/StubDefaultGuesser/StubDefaultStringGuesser.php @@ -5,6 +5,7 @@ use Ingenerator\StubObjects\Attribute\StubDefault; use Ingenerator\StubObjects\Attribute\StubDefault\StubDefaultRandomString; use Ingenerator\StubObjects\Guesser\StubDefaultGuesser; +use Ingenerator\StubObjects\ReflectionUtils; use Ingenerator\StubObjects\StubbingContext; use Random\Randomizer; use ReflectionProperty; @@ -19,7 +20,7 @@ public function __construct( public function guessProvider(ReflectionProperty $property, StubbingContext $context): false|StubDefault { - if ($property->getType()->getName() === 'string') { + if (ReflectionUtils::getTypeNameIfAvailable($property) === 'string') { // @todo: guess email addresses for props with `email` in the name // @todo: guess URLs for props with `url` in the name return new StubDefaultRandomString( diff --git a/src/Guesser/StubDefaultGuesser/StubDefaultStubbableObjectGuesser.php b/src/Guesser/StubDefaultGuesser/StubDefaultStubbableObjectGuesser.php index 6fe4330..fe30c95 100644 --- a/src/Guesser/StubDefaultGuesser/StubDefaultStubbableObjectGuesser.php +++ b/src/Guesser/StubDefaultGuesser/StubDefaultStubbableObjectGuesser.php @@ -6,6 +6,7 @@ use Ingenerator\StubObjects\Attribute\StubDefault; use Ingenerator\StubObjects\Attribute\StubDefault\StubDefaultValue; use Ingenerator\StubObjects\Guesser\StubDefaultGuesser; +use Ingenerator\StubObjects\ReflectionUtils; use Ingenerator\StubObjects\StubbingContext; use ReflectionProperty; @@ -13,7 +14,7 @@ class StubDefaultStubbableObjectGuesser implements StubDefaultGuesser { public function guessProvider(ReflectionProperty $property, StubbingContext $context): false|StubDefault { - if ($property->getType()->isBuiltin()) { + if (ReflectionUtils::isBuiltinType($property)) { return FALSE; } @@ -29,18 +30,19 @@ public function guessProvider(ReflectionProperty $property, StubbingContext $con private function isStubbable(StubbingContext $context, ReflectionProperty $property): bool { - $type = $property->getType(); - if ($type->isBuiltin()) { + $name = ReflectionUtils::getTypeNameIfAvailable($property); + + if ($name === FALSE) { return FALSE; } - if ($type->getName() === Collection::class) { + if ($name === Collection::class) { // Treat collections as a special case, we'll recursively attempt to cast arrays to collections return TRUE; } // Otherwise it depends if they've configured recursion - return $context->isStubbable($property->getType()->getName()); + return $context->isStubbable($name); } } diff --git a/src/ReflectionUtils.php b/src/ReflectionUtils.php new file mode 100644 index 0000000..540b46a --- /dev/null +++ b/src/ReflectionUtils.php @@ -0,0 +1,27 @@ +getType(); + + return ($type instanceof ReflectionNamedType) && $type->isBuiltin(); + } + + public static function getTypeNameIfAvailable(ReflectionProperty $property): false|string + { + $type = $property->getType(); + if ($type instanceof \ReflectionNamedType) { + return $type->getName(); + } + + return FALSE; + } +} diff --git a/src/StandardConfig.php b/src/StandardConfig.php index 76b5f94..39859fe 100644 --- a/src/StandardConfig.php +++ b/src/StandardConfig.php @@ -8,10 +8,10 @@ use Ingenerator\StubObjects\Guesser\StubAsGuesser\StubAsDateTimeGuesser; use Ingenerator\StubObjects\Guesser\StubAsGuesser\StubAsStubObjectGuesser; use Ingenerator\StubObjects\Guesser\StubDefaultGuesser; +use Ingenerator\StubObjects\Guesser\StubDefaultGuesser\StubDefaultDateTimeGuesser; use Ingenerator\StubObjects\Guesser\StubDefaultGuesser\StubDefaultNullGuesser; use Ingenerator\StubObjects\Guesser\StubDefaultGuesser\StubDefaultStringGuesser; use Ingenerator\StubObjects\Guesser\StubDefaultGuesser\StubDefaultStubbableObjectGuesser; -use Ingenerator\StubObjects\Guesser\StubDefaultGuesser\StubDefualtDateTimeGuesser; class StandardConfig { @@ -36,7 +36,7 @@ public static function loadDefaultValueGuessers(): array { return [ new StubDefaultNullGuesser(), - new StubDefualtDateTimeGuesser(), + new StubDefaultDateTimeGuesser(), new StubDefaultStringGuesser(), new StubDefaultStubbableObjectGuesser(), ]; diff --git a/test/integration/HandlesUnsupportedTypesTest.php b/test/integration/HandlesUnsupportedTypesTest.php new file mode 100644 index 0000000..d308478 --- /dev/null +++ b/test/integration/HandlesUnsupportedTypesTest.php @@ -0,0 +1,46 @@ + 'default']])] + #[TestWith([['something' => 'whatever'], ['something' => 'whatever']])] + public function test_it_can_stub_objects_with_union_types(array $data, array $expect) + { + $class = new class { + #[StubDefaultValue('default')] + public bool|string $something; + }; + + $result = $this->newSubject()->stub($class::class, $data); + + $this->assertSame($expect, (array) $result); + } + + #[TestWith([[], ['something' => null]])] + #[TestWith([['something' => 'whatever'], ['something' => 'whatever']])] + public function test_it_can_stub_objects_with_untyped_properties(array $data, array $expect) + { + $class = new class { + // Note: this will be auto-detected as able to be stubbed with `null`, which is technically correct. + public $something; + }; + + $result = $this->newSubject()->stub($class::class, $data); + + $this->assertSame($expect, (array) $result); + } + + + private function newSubject(array $stubbable_class_patterns = ['*']): StubObjects + { + return new StubObjects(...get_defined_vars()); + } +} diff --git a/test/unit/Guesser/StubDefault/StubDefaultDateTimeGuesserTest.php b/test/unit/Guesser/StubDefault/StubDefaultDateTimeGuesserTest.php index 251e426..e05c96a 100644 --- a/test/unit/Guesser/StubDefault/StubDefaultDateTimeGuesserTest.php +++ b/test/unit/Guesser/StubDefault/StubDefaultDateTimeGuesserTest.php @@ -5,7 +5,7 @@ use DateTimeImmutable; use Ingenerator\StubObjects\Attribute\StubDefault\StubDefaultValue; -use Ingenerator\StubObjects\Guesser\StubDefaultGuesser\StubDefualtDateTimeGuesser; +use Ingenerator\StubObjects\Guesser\StubDefaultGuesser\StubDefaultDateTimeGuesser; class StubDefaultDateTimeGuesserTest extends BaseStubDefaultGuesserTestCase { @@ -22,7 +22,7 @@ public function test_it_guesses_current_time() 'string' => FALSE, ], $class::class, - new StubDefualtDateTimeGuesser, + new StubDefaultDateTimeGuesser, ); } }