From 39f2084347fdd066aeb5fa4cb682b40b8886e055 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 27 Aug 2021 20:14:27 +0200 Subject: [PATCH] castTo() allows you to create objects [Closes #44][Closes #47][Closes #58][Closes #46] --- readme.md | 37 +++++++++++++++++++-- src/Schema/Helpers.php | 9 +++++ tests/Schema/Expect.castTo.phpt | 58 ++++++++++++++++++++++++++++----- 3 files changed, 94 insertions(+), 10 deletions(-) diff --git a/readme.md b/readme.md index b156796..1e6714b 100644 --- a/readme.md +++ b/readme.md @@ -422,10 +422,43 @@ Successfully validated data can be cast: Expect::scalar()->castTo('string'); ``` -In addition to native PHP types, you can also cast to classes: +In addition to native PHP types, you can also cast to classes. It distinguishes whether it is a simple class without a constructor or a class with a constructor. If the class has no constructor, an instance of it is created and all elements of the structure are written to its properties: ```php -Expect::scalar()->castTo('AddressEntity'); +class Info +{ + public bool $processRefund; + public int $refundAmount; +} + +Expect::structure([ + 'processRefund' => Expect::bool(), + 'refundAmount' => Expect::int(), +])->castTo(Info::class); + +// creates '$obj = new Info' and writes to $obj->processRefund and $obj->refundAmount +``` + +If the class has a constructor, the elements of the structure are passed as named parameters to the constructor (requires PHP 8): + +```php +class Info +{ + public function __construct( + public bool $processRefund, + public int $refundAmount, + ) { + } +} + +// creates $obj = new Info(processRefund: ..., refundAmount: ...) +``` + +Casting combined with a scalar parameter creates an object and passes the value as the sole parameter to the constructor: + +```php +Expect::string()->castTo(DateTime::class); +// creates new DateTime(...) ``` diff --git a/src/Schema/Helpers.php b/src/Schema/Helpers.php index c0432db..3b65322 100644 --- a/src/Schema/Helpers.php +++ b/src/Schema/Helpers.php @@ -176,6 +176,15 @@ public static function getCastStrategy(string $type): \Closure settype($value, $type); return $value; }; + } elseif (method_exists($type, '__construct')) { + return static function ($value) use ($type) { + 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) + : new $type($value); + }; } else { return static function ($value) use ($type) { $object = new $type; diff --git a/tests/Schema/Expect.castTo.phpt b/tests/Schema/Expect.castTo.phpt index 0d0d5bb..f0a4d03 100644 --- a/tests/Schema/Expect.castTo.phpt +++ b/tests/Schema/Expect.castTo.phpt @@ -10,22 +10,64 @@ use Tester\Assert; require __DIR__ . '/../bootstrap.php'; -test('', function () { +test('built-in', function () { $schema = Expect::int()->castTo('string'); - Assert::same('10', (new Processor)->process($schema, 10)); + + $schema = Expect::string()->castTo('array'); + Assert::same(['foo'], (new Processor)->process($schema, 'foo')); }); -test('', function () { - $schema = Expect::string()->castTo('array'); +test('simple object', function () { + class Foo1 + { + public $a; + public $b; + } - Assert::same(['foo'], (new Processor)->process($schema, 'foo')); + $foo = new Foo1; + $foo->a = 1; + $foo->b = 2; + + $schema = Expect::array()->castTo(Foo1::class); + Assert::equal( + $foo, + (new Processor)->process($schema, ['a' => 1, 'b' => 2]) + ); }); -test('', function () { - $schema = Expect::array()->castTo('stdClass'); +test('object with constructor', function () { + if (PHP_VERSION_ID < 80000) { + return; + } + + class Foo2 + { + private $a; + private $b; + + + public function __construct(int $a, int $b) + { + $this->b = $b; + $this->a = $a; + } + } + + $schema = Expect::array()->castTo(Foo2::class); + Assert::equal( + new Foo2(1, 2), + (new Processor)->process($schema, ['b' => 2, 'a' => 1]) + ); +}); + - Assert::equal((object) ['a' => 1, 'b' => 2], (new Processor)->process($schema, ['a' => 1, 'b' => 2])); +test('DateTime', function () { + $schema = Expect::string()->castTo(DateTime::class); + Assert::equal( + new DateTime('2021-01-01'), + (new Processor)->process($schema, '2021-01-01') + ); });