Skip to content

Commit

Permalink
Expect::from() analyzes the constructor if it exists [Closes #59]
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Oct 31, 2023
1 parent 6b8ec6c commit 22f7675
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 8 deletions.
15 changes: 11 additions & 4 deletions src/Schema/Expect.php
Expand Up @@ -75,15 +75,22 @@ public static function structure(array $items): Structure
public static function from($object, array $items = []): Structure
{
$ro = new \ReflectionObject($object);
foreach ($ro->getProperties() as $prop) {
$type = Helpers::getPropertyType($prop) ?? 'mixed';
$props = $ro->hasMethod('__construct')
? $ro->getMethod('__construct')->getParameters()
: $ro->getProperties();

foreach ($props as $prop) {
$item = &$items[$prop->getName()];
if (!$item) {
$type = Helpers::getPropertyType($prop) ?? 'mixed';
$item = new Type($type);
if (PHP_VERSION_ID >= 70400 && !$prop->isInitialized($object)) {
if ($prop instanceof \ReflectionProperty
? PHP_VERSION_ID >= 70400 && !$prop->isInitialized($object)
: !$prop->isOptional()
) {
$item->required();
} else {
$def = $prop->getValue($object);
$def = ($prop instanceof \ReflectionProperty ? $prop->getValue($object) : $prop->getDefaultValue());
if (is_object($def)) {
$item = static::from($def);
} elseif ($def === null && !Nette\Utils\Validators::is(null, $type)) {
Expand Down
11 changes: 7 additions & 4 deletions src/Schema/Helpers.php
Expand Up @@ -57,13 +57,16 @@ public static function merge($value, $base)
}


public static function getPropertyType(\ReflectionProperty $prop): ?string
public static function getPropertyType($prop): ?string
{
if (!class_exists(Nette\Utils\Type::class)) {
throw new Nette\NotSupportedException('Expect::from() requires nette/utils 3.x');
} elseif ($type = Nette\Utils\Type::fromReflection($prop)) {
return (string) $type;
} elseif ($type = preg_replace('#\s.*#', '', (string) self::parseAnnotation($prop, 'var'))) {
} elseif (
($prop instanceof \ReflectionProperty)
&& ($type = preg_replace('#\s.*#', '', (string) self::parseAnnotation($prop, 'var')))
) {
$class = Reflection::getPropertyDeclaringClass($prop);
return preg_replace_callback('#[\w\\\\]+#', function ($m) use ($class) {
return Reflection::expandClassName($m[0], $class);
Expand Down Expand Up @@ -183,8 +186,8 @@ public static function getCastStrategy(string $type): \Closure
if (PHP_VERSION_ID < 80000 && is_array($value)) {
throw new Nette\NotSupportedException("Creating $type objects is supported since PHP 8.0");
}
return is_array($value)
? new $type(...$value)
return is_array($value) || $value instanceof \stdClass
? new $type(...(array) $value)
: new $type($value);
};
} else {
Expand Down
21 changes: 21 additions & 0 deletions tests/Schema/Expect.from.php80.phpt
Expand Up @@ -38,3 +38,24 @@ Assert::with(Structure::class, function () {
], $schema->items);
Assert::type($obj, (new Processor)->process($schema, ['user' => '', 'mixed' => '']));
});


Assert::with(Structure::class, function () { // constructor injection
$schema = Expect::from($obj = new class ('') {
public function __construct(
public ?string $user,
public ?string $password = null,
) {
}
});

Assert::type(Structure::class, $schema);
Assert::equal([
'user' => Expect::type('?string')->required(),
'password' => Expect::type('?string'),
], $schema->items);
Assert::equal(
new $obj('foo', 'bar'),
(new Processor)->process($schema, ['user' => 'foo', 'password' => 'bar']),
);
});

0 comments on commit 22f7675

Please sign in to comment.