diff --git a/src/InputHandler.php b/src/InputHandler.php index 385ef81..c32c843 100644 --- a/src/InputHandler.php +++ b/src/InputHandler.php @@ -36,9 +36,9 @@ public function __construct(TypeHandler $typeHandler = null) $this->root->setTypeHandler($this->typeHandler); } - public function add(string $key, string $type, array $options = []): BaseNode + public function add(string $key, string $type, array $options = [], InputHandler $handler = null): BaseNode { - return $this->root->add($key, $type, $options); + return $this->root->add($key, $type, $options, $handler); } public function remove(string $key): void diff --git a/src/Node/BaseNode.php b/src/Node/BaseNode.php index bde6b6c..07413ad 100644 --- a/src/Node/BaseNode.php +++ b/src/Node/BaseNode.php @@ -44,9 +44,6 @@ class BaseNode */ protected $required = true; - /** - * @var mixed - */ protected $default; /** @@ -64,44 +61,60 @@ class BaseNode */ protected $allowNull = false; - public function setConstraints(array $constraints): void + public function setConstraints(array $constraints): self { $this->constraints = $constraints; + + return $this; } - public function addConstraint(ConstraintInterface $constraint): void + public function addConstraint(ConstraintInterface $constraint): self { $this->constraints[] = $constraint; + + return $this; } - public function addConstraints(array $constraints): void + public function addConstraints(array $constraints): self { $this->constraints = array_merge($this->constraints, $constraints); + + return $this; } - public function setTransformer(TransformerInterface $transformer): void + public function setTransformer(TransformerInterface $transformer): self { $this->transformer = $transformer; + + return $this; } - public function setInstantiator(InstantiatorInterface $instantiator): void + public function setInstantiator(InstantiatorInterface $instantiator): self { $this->instantiator = $instantiator; + + return $this; } - public function setTypeHandler(TypeHandler $typeHandler): void + public function setTypeHandler(TypeHandler $typeHandler): self { $this->typeHandler = $typeHandler; + + return $this; } - public function setType(string $type): void + public function setType(string $type): self { $this->type = $type; + + return $this; } - public function setTypeAlias(string $typeAlias): void + public function setTypeAlias(string $typeAlias): self { $this->typeAlias = $typeAlias; + + return $this; } public function getTypeAlias(): string @@ -109,19 +122,25 @@ public function getTypeAlias(): string return $this->typeAlias; } - public function setRequired(bool $required): void + public function setRequired(bool $required): self { $this->required = $required; + + return $this; } - public function setDefault($default): void + public function setDefault($default): self { $this->default = $default; + + return $this; } - public function setAllowNull(bool $allowNull): void + public function setAllowNull(bool $allowNull): self { $this->allowNull = $allowNull; + + return $this; } public function getDefault() @@ -134,17 +153,16 @@ public function hasDefault(): bool return (bool) $this->default; } - public function add(string $key, string $type, array $options = []): BaseNode + public function add(string $key, string $type, array $options = [], InputHandler $handler = null): BaseNode { $child = $this->typeHandler->getType($type); - if (isset($options['handler'])) { - /** @var InputHandler $handler */ - $handler = $options['handler']; - $handler->setRootType($type); - $handler->define(); + if (isset($handler)) { + $child = $child->setHandler($handler, $type); + } - $child = $handler->getRoot(); + if (isset($options['handler']) && !isset($handler)) { + $child = $child->setHandler($options['handler'], $type); } if (isset($options['required'])) { @@ -262,4 +280,12 @@ protected function checkConstraints(string $field, $value): void } } } + + private function setHandler(InputHandler $handler, string $type): self + { + $handler->setRootType($type); + $handler->define(); + + return $handler->getRoot(); + } } diff --git a/tests/InputHandlerTest.php b/tests/InputHandlerTest.php index f6bb43a..8c1f1fb 100644 --- a/tests/InputHandlerTest.php +++ b/tests/InputHandlerTest.php @@ -4,7 +4,10 @@ namespace Linio\Component\Input; +use Linio\Component\Input\Constraint\Email; +use Linio\Component\Input\Constraint\Enum; use Linio\Component\Input\Constraint\Range; +use Linio\Component\Input\Constraint\StringSize; use Linio\Component\Input\Instantiator\InstantiatorInterface; use Linio\Component\Input\Instantiator\PropertyInstantiator; use PHPUnit\Framework\TestCase; @@ -99,6 +102,16 @@ public function define(): void } } +class TestRecursiveInputHandlerExplicit extends InputHandler +{ + public function define(): void + { + $this->add('title', 'string'); + $this->add('size', 'int'); + $this->add('child', \stdClass::class, ['instantiator' => new PropertyInstantiator()], new TestInputHandler()); + } +} + class TestNullableInputHandler extends InputHandler { public function define(): void @@ -121,6 +134,31 @@ public function define(): void } } +class TestInputHandlerCascade extends InputHandler +{ + public function define(): void + { + $this->add('name', 'string') + ->setRequired(true) + ->addConstraint(new StringSize(1, 80)); + + $this->add('age', 'int') + ->setRequired(true) + ->addConstraint(new Range(1, 99)); + + $this->add('gender', 'string') + ->setRequired(true) + ->addConstraint(new Enum(['male', 'female', 'other'])); + + $this->add('birthday', 'datetime') + ->setRequired(false); + + $this->add('email', 'string') + ->setRequired(false) + ->addConstraint(new Email()); + } +} + class InputHandlerTest extends TestCase { public function testIsHandlingBasicInput(): void @@ -464,6 +502,102 @@ public function testIsHandlingInputWithRecursiveHandler(): void $this->assertEquals([$fanA, $fanB, $fanC], $child->fans); } + public function testIsHandlingInputWithRecursiveHandlerExplicit(): void + { + $input = [ + 'title' => 'Barfoo', + 'size' => 20, + 'child' => [ + 'title' => 'Foobar', + 'size' => 35, + 'dimensions' => [11, 22, 33], + 'date' => '2015-01-01 22:50', + 'metadata' => [ + 'foo' => 'bar', + ], + 'simple' => [ + 'date' => '2015-01-01 22:50', + ], + 'user' => [ + 'name' => false, + 'age' => '28', + ], + 'author' => [ + 'name' => 'Barfoo', + 'age' => 28, + 'related' => [ + 'name' => 'Barfoo', + 'age' => 28, + ], + ], + 'fans' => [ + [ + 'name' => 'A', + 'age' => 18, + 'birthday' => '2000-01-01', + ], + [ + 'name' => 'B', + 'age' => 28, + 'birthday' => '2000-01-02', + ], + [ + 'name' => 'C', + 'age' => 38, + 'birthday' => '2000-01-03', + ], + ], + ], + ]; + + $inputHandler = new TestRecursiveInputHandlerExplicit(); + $inputHandler->bind($input); + $this->assertTrue($inputHandler->isValid()); + + // Basic fields + $this->assertEquals('Barfoo', $inputHandler->getData('title')); + $this->assertEquals(20, $inputHandler->getData('size')); + /** @var \stdClass $child */ + $child = $inputHandler->getData('child'); + + // Scalar collection + $this->assertEquals([11, 22, 33], $child->dimensions); + + // Transformer + $this->assertEquals(new \DateTime('2015-01-01 22:50'), $child->date); + + // Mixed array + $this->assertEquals(['foo' => 'bar'], $child->metadata); + + // Typed array + $this->assertEquals(['title' => 'Barfoo', 'size' => 15, 'date' => new \DateTime('2015-01-01 22:50')], $child->simple); + + // Object and nested object + $related = new TestUser(); + $related->setName('Barfoo'); + $related->setAge(28); + $author = new TestUser(); + $author->setName('Barfoo'); + $author->setAge(28); + $author->setRelated($related); + $this->assertEquals($author, $child->author); + + // Object collection + $fanA = new TestUser(); + $fanA->setName('A'); + $fanA->setAge(18); + $fanA->setBirthday(new \DateTime('2000-01-01')); + $fanB = new TestUser(); + $fanB->setName('B'); + $fanB->setAge(28); + $fanB->setBirthday(new \DateTime('2000-01-02')); + $fanC = new TestUser(); + $fanC->setName('C'); + $fanC->setAge(38); + $fanC->setBirthday(new \DateTime('2000-01-03')); + $this->assertEquals([$fanA, $fanB, $fanC], $child->fans); + } + public function testOverride(): void { $input = [ @@ -539,6 +673,21 @@ public function testIsHandlingInputWithNullValues(): void $this->assertNull($data); } + + public function testInputHandlerOnCascade(): void + { + $input = [ + 'name' => 'A', + 'age' => 18, + 'gender' => 'male', + 'birthday' => '2000-01-01', + ]; + + $inputHandler = new TestInputHandlerCascade(); + $inputHandler->bind($input); + + $this->assertTrue($inputHandler->isValid()); + } } class TestConstraintOverrideType extends InputHandler