Skip to content

Commit

Permalink
Expect::from() removed support for phpDoc annotations (BC break)
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Oct 6, 2024
1 parent 1cca5ea commit c909d49
Show file tree
Hide file tree
Showing 7 changed files with 39 additions and 254 deletions.
22 changes: 3 additions & 19 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -486,12 +486,9 @@ You can generate structure schema from the class. Example:
```php
class Config
{
/** @var string */
public $name;
/** @var string|null */
public $password;
/** @var bool */
public $admin = false;
public string $name;
public ?string $password;
public bool $admin = false;
}

$schema = Expect::from(new Config);
Expand All @@ -505,19 +502,6 @@ $normalized = $processor->process($schema, $data);
// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false}
```

If you are using PHP 7.4 or higher, you can use native types:

```php
class Config
{
public string $name;
public ?string $password;
public bool $admin = false;
}

$schema = Expect::from(new Config);
```

Anonymous classes are also supported:

```php
Expand Down
5 changes: 1 addition & 4 deletions src/Schema/Expect.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,11 @@ public static function from(object $object, array $items = []): Structure
foreach ($props as $prop) {
$item = &$items[$prop->getName()];
if (!$item) {
$type = Helpers::getPropertyType($prop) ?? 'mixed';
$item = new Type($type);
$item = new Type((string) (Nette\Utils\Type::fromReflection($prop) ?? 'mixed'));
if ($prop instanceof \ReflectionProperty ? $prop->isInitialized($object) : $prop->isOptional()) {
$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)) {
$item->required();
} else {
$item->default($def);
}
Expand Down
36 changes: 0 additions & 36 deletions src/Schema/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
namespace Nette\Schema;

use Nette;
use Nette\Utils\Reflection;


/**
Expand Down Expand Up @@ -55,41 +54,6 @@ public static function merge(mixed $value, mixed $base): mixed
}


public static function getPropertyType(\ReflectionProperty|\ReflectionParameter $prop): ?string
{
if ($type = Nette\Utils\Type::fromReflection($prop)) {
return (string) $type;
} elseif (
($prop instanceof \ReflectionProperty)
&& ($type = preg_replace('#\s.*#', '', (string) self::parseAnnotation($prop, 'var')))
) {
$class = Reflection::getPropertyDeclaringClass($prop);
return preg_replace_callback('#[\w\\\\]+#', fn($m) => Reflection::expandClassName($m[0], $class), $type);
}

return null;
}


/**
* Returns an annotation value.
* @param \ReflectionProperty $ref
*/
public static function parseAnnotation(\Reflector $ref, string $name): ?string
{
if (!Reflection::areCommentsAvailable()) {
throw new Nette\InvalidStateException('You have to enable phpDoc comments in opcode cache.');
}

$re = '#[\s*]@' . preg_quote($name, '#') . '(?=\s|$)(?:[ \t]+([^@\s]\S*))?#';
if ($ref->getDocComment() && preg_match($re, trim($ref->getDocComment(), '/*'), $m)) {
return $m[1] ?? '';
}

return null;
}


public static function formatValue(mixed $value): string
{
if ($value instanceof DynamicParameter) {
Expand Down
57 changes: 0 additions & 57 deletions tests/Schema/Expect.from.php80.phpt

This file was deleted.

76 changes: 35 additions & 41 deletions tests/Schema/Expect.from.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -22,76 +22,70 @@ Assert::with(Structure::class, function () {

Assert::with(Structure::class, function () {
$schema = Expect::from($obj = new class {
/** @var string */
public $dsn = 'mysql';

/** @var string|null */
public $user;

/** @var ?string */
public $password;

/** @var string[] */
public $options = [1];

/** @var bool */
public $debugger = true;
public $mixed;

/** @var array|null */
public $arr;

/** @var string */
public $required;
public string $dsn = 'mysql';
public ?string $user;
public ?string $password = null;
public array|int $options = [];
public bool $debugger = true;
public mixed $mixed;
public array $arr = [1];
});

Assert::type(Structure::class, $schema);
Assert::equal([
'dsn' => Expect::string('mysql'),
'user' => Expect::type('string|null'),
'user' => Expect::type('?string')->required(),
'password' => Expect::type('?string'),
'options' => Expect::type('string[]')->default([1]),
'options' => Expect::type('array|int')->default([]),
'debugger' => Expect::bool(true),
'mixed' => Expect::mixed(),
'arr' => Expect::type('array|null')->default(null),
'required' => Expect::type('string')->required(),
'mixed' => Expect::mixed()->required(),
'arr' => Expect::type('array')->default([1]),
], $schema->items);
Assert::type($obj, (new Processor)->process($schema, ['required' => '']));
Assert::type($obj, (new Processor)->process($schema, ['user' => '', 'mixed' => '']));
});


Assert::exception(function () {
Expect::from(new class {
/** @var Unknown */
public $unknown;
Assert::with(Structure::class, function () { // constructor injection
$schema = Expect::from($obj = new class ('') {
public function __construct(
public ?string $user,
public ?string $password = null,
) {
}
});
}, Nette\NotImplementedException::class, 'Anonymous classes are not supported.');

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']),
);
});


Assert::with(Structure::class, function () { // overwritten item
$schema = Expect::from(new class {
/** @var string */
public $dsn = 'mysql';
public string $dsn = 'mysql';

/** @var string|null */
public $user;
public ?string $user;
}, ['dsn' => Expect::int(123)]);

Assert::equal([
'dsn' => Expect::int(123),
'user' => Expect::type('string|null'),
'user' => Expect::type('?string')->required(),
], $schema->items);
});


Assert::with(Structure::class, function () { // nested object
$obj = new class {
/** @var object */
public $inner;
public object $inner;
};
$obj->inner = new class {
/** @var string */
public $name;
public string $name;
};

$schema = Expect::from($obj);
Expand Down
37 changes: 0 additions & 37 deletions tests/Schema/Helpers.getPropertyType.phpt

This file was deleted.

60 changes: 0 additions & 60 deletions tests/Schema/Helpers.parseAnnotation().phpt

This file was deleted.

0 comments on commit c909d49

Please sign in to comment.