From 8a15782ddae1bd6a787c224dd269644ff09d64db Mon Sep 17 00:00:00 2001 From: Franck Date: Mon, 24 Feb 2020 06:30:18 -0500 Subject: [PATCH 01/46] major updated --- CHANGELOG.md | 10 ++ README.md | 32 ++++- ...rrayValidator.php => StrictValidation.php} | 100 +++++++++---- src/StrictValidationFromDefinition.php | 35 +++++ src/ValidationDefinition.php | 133 ++++++++++++++++++ src/ValidationInterface.php | 22 +++ src/{ArrayValidation.php => Validator.php} | 84 ++++++++++- src/ValidatorInterface.php | 22 +++ tests/ArrayValidationDefinitionTest.php | 49 +++++++ tests/ArrayValidationTest.php | 121 ++++++++++++++-- tests/StrictArrayValidatorTest.php | 62 ++++++-- 11 files changed, 617 insertions(+), 53 deletions(-) rename src/{StrictArrayValidator.php => StrictValidation.php} (69%) create mode 100644 src/StrictValidationFromDefinition.php create mode 100644 src/ValidationDefinition.php create mode 100644 src/ValidationInterface.php rename src/{ArrayValidation.php => Validator.php} (64%) create mode 100644 src/ValidatorInterface.php create mode 100644 tests/ArrayValidationDefinitionTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 629c2c0..8d47bd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +VERSION 2.0.0 +------------- +Release date: 2020-02-24 + + - [BC] renamed method expectNElement() to expectNKeys() in ArrayValidation + - added expectNKeys() and expectOnlyOneFromKeys() to StrictArrayValidator + - added interfaces ArrayValidationInterface and ArrayValidatorInterface + - [BC] renamed all classes and interfaces with better name + - added ValidationDefinition and StrictValidationFromDefinition + VERSION 1.1.0 ------------- Release date: 2020-02-20 diff --git a/README.md b/README.md index 6aee4cf..e8d8b51 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,34 @@ composer require peak/array-validation -## Usage \ No newline at end of file +## Usage + +General validation (stateless) + +```php +$validator = new Validator(); + +if ($validator->expectExactlyKeys($array, ['key1', 'key2', 'key3']) === true) { + // ... +} +``` + +Advanced validation with fluent interface. + +```php + +$validation = new StrictValidation($array); + +// will throw an exception if any of tests below fail +$validation + ->expectOnlyKeys(['id', 'title', 'description', 'isPrivate', 'tags']) + ->expectAtLeastKeys(['id', 'title', 'description']) + ->expectKeyToBeInteger('id') + ->expectKeysToBeString(['title', 'description']) + ->expectKeyToBeBoolean('isPrivate') + ->expectKeyToBeArray('tags'); + +// if we reach this point, it means the array is +// valid according to the validation rules above. + +``` \ No newline at end of file diff --git a/src/StrictArrayValidator.php b/src/StrictValidation.php similarity index 69% rename from src/StrictArrayValidator.php rename to src/StrictValidation.php index 2939123..491f185 100644 --- a/src/StrictArrayValidator.php +++ b/src/StrictValidation.php @@ -7,12 +7,12 @@ use Peak\ArrayValidation\Exception\InvalidStructureException; use Peak\ArrayValidation\Exception\InvalidTypeException; -class StrictArrayValidator +class StrictValidation implements ValidationInterface { /** - * @var ArrayValidation + * @var Validator */ - private $arrayValidation; + private $validator; /** * @var array @@ -28,40 +28,41 @@ class StrictArrayValidator * @var array */ private $messages = [ + 'expectedN' => '{dataName}invalid data, expected {nExpected} element(s), received {nReceived} element(s)', 'expected' => '{dataName}invalid data, expected {expectedType} [{keysExpected}], received [{keysReceived}]', 'type' => '{dataName}invalid type for key [{key}], type {expectedType} is expected', ]; /** - * StrictArrayValidator constructor. + * StrictValidation constructor. * @param array $data * @param string|null $dataName - * @param ArrayValidation|null $arrayValidation + * @param Validator|null $validator */ - public function __construct(array $data, string $dataName = null, ArrayValidation $arrayValidation = null) + public function __construct(array $data, string $dataName = null, Validator $validator = null) { $this->data = $data; $this->dataName = $dataName; - if (!isset($arrayValidation)) { - $arrayValidation = new ArrayValidation(); + if (!isset($validator)) { + $validator = new Validator(); } - $this->arrayValidation = $arrayValidation; + $this->validator = $validator; } /** - * @param array $keysName + * @param array $keys * @return $this * @throws InvalidStructureException */ - public function expectExactlyKeys(array $keysName) + public function expectExactlyKeys(array $keys) { - if ($this->arrayValidation->expectExactlyKeys($this->data, $keysName) === false) { + if ($this->validator->expectExactlyKeys($this->data, $keys) === false) { $keysReceived = array_keys($this->data); - natsort($keysName); + natsort($keys); natsort($keysReceived); $message = $this->getErrorMessage('expected', [ 'expectedType' => 'exactly keys', - 'keysExpected' => implode(', ', $keysName), + 'keysExpected' => implode(', ', $keys), 'keysReceived' => implode(', ', $keysReceived) ]); throw new InvalidStructureException($message); @@ -70,19 +71,19 @@ public function expectExactlyKeys(array $keysName) } /** - * @param array $keysName + * @param array $keys * @return $this * @throws InvalidStructureException */ - public function expectAtLeastKeys(array $keysName) + public function expectAtLeastKeys(array $keys) { - if ($this->arrayValidation->expectAtLeastKeys($this->data, $keysName) === false) { + if ($this->validator->expectAtLeastKeys($this->data, $keys) === false) { $keysReceived = array_keys($this->data); - natsort($keysName); + natsort($keys); natsort($keysReceived); $message = $this->getErrorMessage('expected', [ 'expectedType' => 'at least keys', - 'keysExpected' => implode(', ', $keysName), + 'keysExpected' => implode(', ', $keys), 'keysReceived' => implode(', ', $keysReceived) ]); throw new InvalidStructureException($message); @@ -91,19 +92,40 @@ public function expectAtLeastKeys(array $keysName) } /** - * @param array $keysName + * @param array $keys * @return $this * @throws InvalidStructureException */ - public function expectOnlyKeys(array $keysName) + public function expectOnlyKeys(array $keys) { - if ($this->arrayValidation->expectOnlyKeys($this->data, $keysName) === false) { + if ($this->validator->expectOnlyKeys($this->data, $keys) === false) { $keysReceived = array_keys($this->data); - natsort($keysName); + natsort($keys); natsort($keysReceived); $message = $this->getErrorMessage('expected', [ 'expectedType' => 'only keys', - 'keysExpected' => implode(', ', $keysName), + 'keysExpected' => implode(', ', $keys), + 'keysReceived' => implode(', ', $keysReceived) + ]); + throw new InvalidStructureException($message); + } + return $this; + } + + /** + * @param array $keys + * @return $this + * @throws InvalidStructureException + */ + public function expectOnlyOneFromKeys(array $keys) + { + if ($this->validator->expectOnlyOneFromKeys($this->data, $keys) === false) { + $keysReceived = array_keys($this->data); + natsort($keys); + natsort($keysReceived); + $message = $this->getErrorMessage('expected', [ + 'expectedType' => 'only one of keys', + 'keysExpected' => implode(', ', $keys), 'keysReceived' => implode(', ', $keysReceived) ]); throw new InvalidStructureException($message); @@ -111,6 +133,26 @@ public function expectOnlyKeys(array $keysName) return $this; } + /** + * @param int $n + * @return $this + * @throws InvalidStructureException + */ + public function expectNKeys(int $n) + { + if ($this->validator->expectNKeys($this->data, $n) === false) { + $keysReceived = array_keys($this->data); + natsort($keysReceived); + $message = $this->getErrorMessage('expectedN', [ + 'expectedType' => 'only N keys', + 'nExpected' => $n, + 'nReceived' => count($keysReceived) + ]); + throw new InvalidStructureException($message); + } + return $this; + } + /** * @param string $key * @param bool $acceptNull @@ -119,7 +161,7 @@ public function expectOnlyKeys(array $keysName) */ public function expectKeyToBeArray(string $key, bool $acceptNull = false) { - if (array_key_exists($key, $this->data) && $this->arrayValidation->expectKeyToBeArray($this->data, $key, $acceptNull) === false) { + if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeArray($this->data, $key, $acceptNull) === false) { $message = $this->getErrorMessage('type', [ 'key' => $key, 'expectedType' => 'array', @@ -137,7 +179,7 @@ public function expectKeyToBeArray(string $key, bool $acceptNull = false) */ public function expectKeyToBeInteger(string $key, bool $acceptNull = false) { - if (array_key_exists($key, $this->data) && $this->arrayValidation->expectKeyToBeInteger($this->data, $key, $acceptNull) === false) { + if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeInteger($this->data, $key, $acceptNull) === false) { $message = $this->getErrorMessage('type', [ 'key' => $key, 'expectedType' => 'integer', @@ -155,7 +197,7 @@ public function expectKeyToBeInteger(string $key, bool $acceptNull = false) */ public function expectKeyToBeFloat(string $key, bool $acceptNull = false) { - if (array_key_exists($key, $this->data) && $this->arrayValidation->expectKeyToBeFloat($this->data, $key, $acceptNull) === false) { + if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeFloat($this->data, $key, $acceptNull) === false) { $message = $this->getErrorMessage('type', [ 'key' => $key, 'expectedType' => 'float', @@ -173,7 +215,7 @@ public function expectKeyToBeFloat(string $key, bool $acceptNull = false) */ public function expectKeyToBeString(string $key, bool $acceptNull = false) { - if (array_key_exists($key, $this->data) && $this->arrayValidation->expectKeyToBeString($this->data, $key, $acceptNull) === false) { + if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeString($this->data, $key, $acceptNull) === false) { $message = $this->getErrorMessage('type', [ 'key' => $key, 'expectedType' => 'string', @@ -191,7 +233,7 @@ public function expectKeyToBeString(string $key, bool $acceptNull = false) */ public function expectKeyToBeBoolean(string $key, bool $acceptNull = false) { - if (array_key_exists($key, $this->data) && $this->arrayValidation->expectKeyToBeBoolean($this->data, $key, $acceptNull) === false) { + if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeBoolean($this->data, $key, $acceptNull) === false) { $message = $this->getErrorMessage('type', [ 'key' => $key, 'expectedType' => 'boolean', diff --git a/src/StrictValidationFromDefinition.php b/src/StrictValidationFromDefinition.php new file mode 100644 index 0000000..80b0897 --- /dev/null +++ b/src/StrictValidationFromDefinition.php @@ -0,0 +1,35 @@ +arrayValidationDefinition = $arrayValidationDefinition; + parent::__construct($data, $dataName, $arrayValidation); + + $validations = $arrayValidationDefinition->getValidations(); + foreach ($validations as $name => $args) { + call_user_func_array([$this, $name], $args); + } + } +} \ No newline at end of file diff --git a/src/ValidationDefinition.php b/src/ValidationDefinition.php new file mode 100644 index 0000000..659f872 --- /dev/null +++ b/src/ValidationDefinition.php @@ -0,0 +1,133 @@ + null, + 'expectOnlyOneFromKeys' => null, + 'expectAtLeastKeys' => null, + 'expectOnlyKeys' => null, + 'expectNKeys' => null, + 'expectKeyToBeArray' => null, + 'expectKeysToBeArray' => null, + 'expectKeyToBeInteger' => null, + 'expectKeysToBeInteger' => null, + 'expectKeyToBeFloat' => null, + 'expectKeysToBeFloat' => null, + 'expectKeyToBeString' => null, + 'expectKeysToBeString' => null, + 'expectKeyToBeBoolean' => null, + 'expectKeysToBeBoolean' => null, + ]; + + /** + * @return array + */ + public function getValidations(): array + { + $validations = []; + foreach ($this->validations as $name => $args) { + if (null !== $args) { + $validations[$name] = $args; + } + } + return $validations; + } + + public function expectExactlyKeys(array $keys) + { + $this->validations[__FUNCTION__] = func_get_args(); + return $this; + } + + public function expectOnlyOneFromKeys(array $keys) + { + $this->validations[__FUNCTION__] = func_get_args(); + return $this; + } + + public function expectAtLeastKeys(array $keys) + { + $this->validations[__FUNCTION__] = func_get_args(); + return $this; + } + + public function expectOnlyKeys(array $keys) + { + $this->validations[__FUNCTION__] = func_get_args(); + return $this; + } + + public function expectNKeys(int $n) + { + $this->validations[__FUNCTION__] = func_get_args(); + return $this; + } + + public function expectKeyToBeArray(string $key, bool $acceptNull = false) + { + $this->validations[__FUNCTION__] = func_get_args(); + return $this; + } + + public function expectKeysToBeArray(array $keys, bool $acceptNull = false) + { + $this->validations[__FUNCTION__] = func_get_args(); + return $this; + } + + public function expectKeyToBeInteger(string $key, bool $acceptNull = false) + { + $this->validations[__FUNCTION__] = func_get_args(); + return $this; + } + + public function expectKeysToBeInteger(array $keys, bool $acceptNull = false) + { + $this->validations[__FUNCTION__] = func_get_args(); + return $this; + } + + public function expectKeyToBeFloat(string $key, bool $acceptNull = false) + { + $this->validations[__FUNCTION__] = func_get_args(); + return $this; + } + + public function expectKeysToBeFloat(array $keys, bool $acceptNull = false) + { + $this->validations[__FUNCTION__] = func_get_args(); + return $this; + } + + public function expectKeyToBeString(string $key, bool $acceptNull = false) + { + $this->validations[__FUNCTION__] = func_get_args(); + return $this; + } + + public function expectKeysToBeString(array $keys, bool $acceptNull = false) + { + $this->validations[__FUNCTION__] = func_get_args(); + return $this; + } + + public function expectKeyToBeBoolean(string $key, bool $acceptNull = false) + { + $this->validations[__FUNCTION__] = func_get_args(); + return $this; + } + + public function expectKeysToBeBoolean(array $keys, bool $acceptNull = false) + { + $this->validations[__FUNCTION__] = func_get_args(); + return $this; + } +} diff --git a/src/ValidationInterface.php b/src/ValidationInterface.php new file mode 100644 index 0000000..20b9374 --- /dev/null +++ b/src/ValidationInterface.php @@ -0,0 +1,22 @@ +internalTypeValidation('is_array', $array, $key, $acceptNull); } + /** + * @param array $array + * @param array $keys + * @param bool $acceptNull + * @return bool + */ + public function expectKeysToBeArray(array $array, array $keys, bool $acceptNull = false): bool + { + foreach ($keys as $key) { + if ($this->expectKeyToBeArray($array, $key, $acceptNull) === false) { + return false; + } + } + return true; + } + /** * @param array $array * @param string $key @@ -107,6 +123,22 @@ public function expectKeyToBeInteger(array $array, string $key, bool $acceptNull return $this->internalTypeValidation('is_integer', $array, $key, $acceptNull); } + /** + * @param array $array + * @param array $keys + * @param bool $acceptNull + * @return bool + */ + public function expectKeysToBeInteger(array $array, array $keys, bool $acceptNull = false): bool + { + foreach ($keys as $key) { + if ($this->expectKeyToBeInteger($array, $key, $acceptNull) === false) { + return false; + } + } + return true; + } + /** * @param array $array * @param string $key @@ -118,6 +150,22 @@ public function expectKeyToBeString(array $array, string $key, bool $acceptNull return $this->internalTypeValidation('is_string', $array, $key, $acceptNull); } + /** + * @param array $array + * @param array $keys + * @param bool $acceptNull + * @return bool + */ + public function expectKeysToBeString(array $array, array $keys, bool $acceptNull = false): bool + { + foreach ($keys as $key) { + if ($this->expectKeyToBeString($array, $key, $acceptNull) === false) { + return false; + } + } + return true; + } + /** * @param array $array * @param string $key @@ -129,6 +177,22 @@ public function expectKeyToBeFloat(array $array, string $key, bool $acceptNull = return $this->internalTypeValidation('is_float', $array, $key, $acceptNull); } + /** + * @param array $array + * @param array $keys + * @param bool $acceptNull + * @return bool + */ + public function expectKeysToBeFloat(array $array, array $keys, bool $acceptNull = false): bool + { + foreach ($keys as $key) { + if ($this->expectKeyToBeFloat($array, $key, $acceptNull) === false) { + return false; + } + } + return true; + } + /** * @param array $array * @param string $key @@ -140,6 +204,22 @@ public function expectKeyToBeBoolean(array $array, string $key, bool $acceptNull return $this->internalTypeValidation('is_bool', $array, $key, $acceptNull); } + /** + * @param array $array + * @param array $keys + * @param bool $acceptNull + * @return bool + */ + public function expectKeysToBeBoolean(array $array, array $keys, bool $acceptNull = false): bool + { + foreach ($keys as $key) { + if ($this->expectKeyToBeBoolean($array, $key, $acceptNull) === false) { + return false; + } + } + return true; + } + /** * @param string $method * @param array $array diff --git a/src/ValidatorInterface.php b/src/ValidatorInterface.php new file mode 100644 index 0000000..6897071 --- /dev/null +++ b/src/ValidatorInterface.php @@ -0,0 +1,22 @@ +expectAtLeastKeys(['title', 'content']) + ->expectExactlyKeys(['title', 'content']) + ->expectOnlyKeys(['title', 'content']) + ->expectKeysToBeString(['title', 'content'], true); + + $validations = $arrayDefinition->getValidations(); + $this->assertTrue(is_array($validations)); + $this->assertTrue(count($validations) === 4); + } + + public function testStrictArrayValidatorFromDefinition() + { + $arrayDefinition = new ValidationDefinition(); + $arrayDefinition + ->expectAtLeastKeys(['title', 'content']) + ->expectExactlyKeys(['title', 'content']) + ->expectOnlyKeys(['title', 'content']) + ->expectKeysToBeString(['title', 'content'], true); + + $strictValidator = new StrictValidationFromDefinition($arrayDefinition, [ + 'title' => '', + 'content' => '', + ]); + + $this->assertTrue(true); + + $this->expectException(InvalidTypeException::class); + $strictValidator = new StrictValidationFromDefinition($arrayDefinition, [ + 'title' => '', + 'content' => 1, + ]); + } +} diff --git a/tests/ArrayValidationTest.php b/tests/ArrayValidationTest.php index e295b5f..72eff64 100644 --- a/tests/ArrayValidationTest.php +++ b/tests/ArrayValidationTest.php @@ -3,7 +3,7 @@ declare(strict_types=1); use \PHPUnit\Framework\TestCase; -use \Peak\ArrayValidation\ArrayValidation; +use \Peak\ArrayValidation\Validator; class ArrayValidationTest extends TestCase { @@ -24,7 +24,7 @@ class ArrayValidationTest extends TestCase public function testExpectExactlyKeys() { - $validation = new ArrayValidation(); + $validation = new Validator(); $this->assertTrue($validation->expectExactlyKeys($this->data1, ['title', 'content'])); $this->assertFalse($validation->expectExactlyKeys($this->data1, ['title', 'content', 'extra'])); $this->assertFalse($validation->expectExactlyKeys($this->data1, ['title'])); @@ -34,7 +34,7 @@ public function testExpectExactlyKeys() public function testExpectOnlyOneFromKeys() { - $validation = new ArrayValidation(); + $validation = new Validator(); $this->assertTrue($validation->expectOnlyOneFromKeys($this->data2, ['title', 'extra'])); $this->assertTrue($validation->expectOnlyOneFromKeys($this->data2, ['title'])); $this->assertFalse($validation->expectOnlyOneFromKeys($this->data2, ['extra'])); @@ -42,7 +42,7 @@ public function testExpectOnlyOneFromKeys() public function testExpectAtLeastKeys() { - $validation = new ArrayValidation(); + $validation = new Validator(); $this->assertTrue($validation->expectAtLeastKeys($this->data1, ['title'])); $this->assertTrue($validation->expectAtLeastKeys($this->data1, ['content'])); $this->assertTrue($validation->expectAtLeastKeys($this->data1, ['title', 'content'])); @@ -52,24 +52,125 @@ public function testExpectAtLeastKeys() public function testExpectOnlyKeys() { - $validation = new ArrayValidation(); + $validation = new Validator(); $this->assertTrue($validation->expectOnlyKeys($this->data1, ['title', 'content'])); $this->assertTrue($validation->expectOnlyKeys($this->data1, ['title', 'content', 'extra'])); $this->assertFalse($validation->expectOnlyKeys($this->data1, ['title'])); } - public function testExpectXElement() + public function testExpectNElement() { - $validation = new ArrayValidation(); - $this->assertTrue($validation->expectXElement($this->data1, 2)); - $this->assertFalse($validation->expectXElement($this->data1, 1)); + $validation = new Validator(); + $this->assertTrue($validation->expectNKeys($this->data1, 2)); + $this->assertFalse($validation->expectNKeys($this->data1, 1)); } public function testIsArray() { - $validation = new ArrayValidation(); + $validation = new Validator(); $this->assertTrue($validation->expectKeyToBeArray($this->data3, 'comments', false)); $this->assertFalse($validation->expectKeyToBeArray($this->data3, 'title', false)); $this->assertTrue($validation->expectKeyToBeArray($this->data3, 'title', true)); + + $data = [ + 'item1' => 45, + 'item2' => [], + 'item3' => [], + 'item4' => null, + ]; + $this->assertTrue($validation->expectKeysToBeArray($data, ['item2'], false)); + $this->assertTrue($validation->expectKeysToBeArray($data, ['item2', 'item3'], false)); + $this->assertFalse($validation->expectKeysToBeArray($data, ['item2', 'item3', 'item4'], false)); + $this->assertTrue($validation->expectKeysToBeArray($data, ['item2', 'item3', 'item4'], true)); + } + + public function testIsInt() + { + $data = [ + 'item1' => 45, + 'item2' => 36, + 'item3' => [], + 'item4' => null, + 'item5' => '', + ]; + $validation = new Validator(); + $this->assertTrue($validation->expectKeyToBeInteger($data, 'item1', false)); + $this->assertFalse($validation->expectKeyToBeInteger($data, 'item3', false)); + $this->assertFalse($validation->expectKeyToBeInteger($data, 'item4', false)); + $this->assertTrue($validation->expectKeyToBeInteger($data, 'item4', true)); + + + $this->assertTrue($validation->expectKeysToBeInteger($data, ['item1', 'item2'], false)); + $this->assertFalse($validation->expectKeysToBeInteger($data, ['item1', 'item2', 'item4'], false)); + $this->assertTrue($validation->expectKeysToBeInteger($data, ['item1', 'item2', 'item4'], true)); + $this->assertFalse($validation->expectKeysToBeInteger($data, ['item1', 'item2', 'item3', 'item4'], true)); + } + + public function testIsFloat() + { + $data = [ + 'item1' => 45.1, + 'item2' => 36.5, + 'item3' => [], + 'item4' => null, + 'item5' => 99, + ]; + $validation = new Validator(); + $this->assertTrue($validation->expectKeyToBeFloat($data, 'item1', false)); + $this->assertFalse($validation->expectKeyToBeFloat($data, 'item3', false)); + $this->assertFalse($validation->expectKeyToBeFloat($data, 'item4', false)); + $this->assertTrue($validation->expectKeyToBeFloat($data, 'item4', true)); + + + $this->assertTrue($validation->expectKeysToBeFloat($data, ['item1', 'item2'], false)); + $this->assertFalse($validation->expectKeysToBeFloat($data, ['item1', 'item2', 'item4'], false)); + $this->assertTrue($validation->expectKeysToBeFloat($data, ['item1', 'item2', 'item4'], true)); + $this->assertFalse($validation->expectKeysToBeFloat($data, ['item1', 'item2', 'item3', 'item4'], true)); + $this->assertFalse($validation->expectKeysToBeFloat($data, ['item1', 'item2', 'item5'], false)); + } + + public function testIsBool() + { + $data = [ + 'item1' => true, + 'item2' => false, + 'item3' => [], + 'item4' => null, + 'item5' => 1, + ]; + $validation = new Validator(); + $this->assertTrue($validation->expectKeyToBeBoolean($data, 'item1', false)); + $this->assertFalse($validation->expectKeyToBeBoolean($data, 'item3', false)); + $this->assertFalse($validation->expectKeyToBeBoolean($data, 'item5', false)); + $this->assertFalse($validation->expectKeyToBeBoolean($data, 'item4', false)); + $this->assertTrue($validation->expectKeyToBeBoolean($data, 'item4', true)); + + + $this->assertTrue($validation->expectKeysToBeBoolean($data, ['item1', 'item2'], false)); + $this->assertFalse($validation->expectKeysToBeBoolean($data, ['item1', 'item2', 'item4'], false)); + $this->assertTrue($validation->expectKeysToBeBoolean($data, ['item1', 'item2', 'item4'], true)); + $this->assertFalse($validation->expectKeysToBeBoolean($data, ['item1', 'item2', 'item3', 'item4'], true)); + } + + public function testIsString() + { + $data = [ + 'item1' => 'string', + 'item2' => 'string', + 'item3' => [], + 'item4' => null, + 'item5' => 2, + ]; + $validation = new Validator(); + $this->assertTrue($validation->expectKeyToBeString($data, 'item1', false)); + $this->assertFalse($validation->expectKeyToBeString($data, 'item3', false)); + $this->assertFalse($validation->expectKeyToBeString($data, 'item4', false)); + $this->assertTrue($validation->expectKeyToBeString($data, 'item4', true)); + + + $this->assertTrue($validation->expectKeysToBeString($data, ['item1', 'item2'], false)); + $this->assertFalse($validation->expectKeysToBeString($data, ['item1', 'item2', 'item4'], false)); + $this->assertTrue($validation->expectKeysToBeString($data, ['item1', 'item2', 'item4'], true)); + $this->assertFalse($validation->expectKeysToBeString($data, ['item1', 'item2', 'item3', 'item4'], true)); } } diff --git a/tests/StrictArrayValidatorTest.php b/tests/StrictArrayValidatorTest.php index 539b412..6a73018 100644 --- a/tests/StrictArrayValidatorTest.php +++ b/tests/StrictArrayValidatorTest.php @@ -4,7 +4,7 @@ use Peak\ArrayValidation\Exception\InvalidStructureException; use Peak\ArrayValidation\Exception\InvalidTypeException; -use Peak\ArrayValidation\StrictArrayValidator; +use Peak\ArrayValidation\StrictValidation; use PHPUnit\Framework\TestCase; class StrictArrayValidatorTest extends TestCase @@ -34,7 +34,7 @@ class StrictArrayValidatorTest extends TestCase */ public function testNoException1() { - $strictValidator = new StrictArrayValidator($this->data1); + $strictValidator = new StrictValidation($this->data1); $strictValidator ->expectAtLeastKeys(['title', 'content']) @@ -51,7 +51,7 @@ public function testNoException1() */ public function testNoException2() { - $strictValidator = new StrictArrayValidator($this->data2); + $strictValidator = new StrictValidation($this->data2); $strictValidator ->expectOnlyKeys(['id', 'title', 'isPrivate', 'tags', 'money']) @@ -70,27 +70,67 @@ public function testNoException2() public function testOnlyKeysException() { $this->expectException(InvalidStructureException::class); - $strictValidator = new StrictArrayValidator(['title' => 1]); + $strictValidator = new StrictValidation(['title' => 1]); $strictValidator->expectOnlyKeys(['name']); } + /** + * @throws InvalidStructureException + */ + public function testNKeysException() + { + $this->expectException(InvalidStructureException::class); + $strictValidator = new StrictValidation(['title' => 1, 'description' => 'foo']); + $strictValidator->expectNKeys(1); + } + + /** + * @throws InvalidStructureException + */ + public function testNKeysNoException() + { + $strictValidator = new StrictValidation(['title' => 1]); + $strictValidator->expectNKeys(1); + $this->assertTrue(true); + } + + /** + * @throws InvalidStructureException + */ + public function testOnlyOneFromKeysException() + { + $this->expectException(InvalidStructureException::class); + $strictValidator = new StrictValidation(['title' => 'x', 'description' => 'foo']); + $strictValidator->expectOnlyOneFromKeys(['title', 'description']); + } + /** * @throws InvalidStructureException */ public function testExactlyKeysException() { $this->expectException(InvalidStructureException::class); - $strictValidator = new StrictArrayValidator(['title' => 1]); + $strictValidator = new StrictValidation(['title' => 1]); $strictValidator->expectExactlyKeys(['name']); } + /** + * @throws InvalidStructureException + */ + public function testOnlyOneFromKeysNoException() + { + $strictValidator = new StrictValidation(['title' => 1]); + $strictValidator->expectOnlyOneFromKeys(['name', 'title']); + $this->assertTrue(true); + } + /** * @throws InvalidTypeException */ public function testInvalidStringException() { $this->expectException(InvalidTypeException::class); - $strictValidator = new StrictArrayValidator(['title' => 1]); + $strictValidator = new StrictValidation(['title' => 1]); $strictValidator->expectKeyToBeString('title'); } @@ -100,7 +140,7 @@ public function testInvalidStringException() public function testInvalidArrayException() { $this->expectException(InvalidTypeException::class); - $strictValidator = new StrictArrayValidator(['tags' => 1]); + $strictValidator = new StrictValidation(['tags' => 1]); $strictValidator->expectKeyToBeArray('tags'); } /** @@ -109,7 +149,7 @@ public function testInvalidArrayException() public function testInvalidBooleanException() { $this->expectException(InvalidTypeException::class); - $strictValidator = new StrictArrayValidator(['isPrivate' => 1]); + $strictValidator = new StrictValidation(['isPrivate' => 1]); $strictValidator->expectKeyToBeBoolean('isPrivate'); } @@ -119,7 +159,7 @@ public function testInvalidBooleanException() public function testInvalidFloatException() { $this->expectException(InvalidTypeException::class); - $strictValidator = new StrictArrayValidator(['money' => 1]); + $strictValidator = new StrictValidation(['money' => 1]); $strictValidator->expectKeyToBeFloat('money'); } @@ -131,7 +171,7 @@ public function testWithInvalidTypeException() { $this->expectException(InvalidTypeException::class); - $strictValidator = new StrictArrayValidator($this->data1); + $strictValidator = new StrictValidation($this->data1); $strictValidator ->expectAtLeastKeys(['title', 'content']) @@ -150,7 +190,7 @@ public function testWithInvalidStructureException() { $this->expectException(InvalidStructureException::class); - $strictValidator = new StrictArrayValidator($this->data3, 'my data'); + $strictValidator = new StrictValidation($this->data3, 'my data'); $strictValidator ->expectAtLeastKeys(['title', 'content']) From b16da3aa237d19806b14823405f621d11f41ca1b Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 00:48:33 -0400 Subject: [PATCH 02/46] major updated (v3) --- CHANGELOG.md | 12 + README.md | 131 +++++++- composer.json | 5 +- phpunit.xml | 3 + src/AbstractValidation.php | 70 ++++ .../ArrayValidationExceptionInterface.php | 9 + src/Exception/InvalidStructureException.php | 2 +- src/Exception/InvalidTypeException.php | 2 +- src/Schema.php | 67 ++++ src/SchemaCompiler.php | 81 +++++ src/SchemaCompilerInterface.php | 16 + src/SchemaInterface.php | 20 ++ src/StrictValidation.php | 255 ++------------ src/StrictValidationFromSchema.php | 29 ++ src/Validation.php | 310 ++++++++++++++++++ src/ValidationDefinition.php | 12 +- src/ValidationFromDefinition.php | 35 ++ src/ValidationFromSchema.php | 29 ++ tests/ArrayValidationDefinitionTest.php | 49 --- tests/SchemaCompilerTest.php | 73 +++++ tests/SchemaTest.php | 73 +++++ ...datorTest.php => StrictValidationTest.php} | 2 +- tests/ValidationDefinitionTest.php | 118 +++++++ tests/ValidationFromDefinitionTest.php | 37 +++ tests/ValidationFromSchemaTest.php | 48 +++ tests/ValidationTest.php | 36 ++ ...ayValidationTest.php => ValidatorTest.php} | 2 +- 27 files changed, 1240 insertions(+), 286 deletions(-) create mode 100644 src/AbstractValidation.php create mode 100644 src/Exception/ArrayValidationExceptionInterface.php create mode 100644 src/Schema.php create mode 100644 src/SchemaCompiler.php create mode 100644 src/SchemaCompilerInterface.php create mode 100644 src/SchemaInterface.php create mode 100644 src/StrictValidationFromSchema.php create mode 100644 src/Validation.php create mode 100644 src/ValidationFromDefinition.php create mode 100644 src/ValidationFromSchema.php delete mode 100644 tests/ArrayValidationDefinitionTest.php create mode 100644 tests/SchemaCompilerTest.php create mode 100644 tests/SchemaTest.php rename tests/{StrictArrayValidatorTest.php => StrictValidationTest.php} (99%) create mode 100644 tests/ValidationDefinitionTest.php create mode 100644 tests/ValidationFromDefinitionTest.php create mode 100644 tests/ValidationFromSchemaTest.php create mode 100644 tests/ValidationTest.php rename tests/{ArrayValidationTest.php => ValidatorTest.php} (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d47bd7..6cc5867 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +VERSION 3.0.0 +------------- +Release date: ? + + - introducing schema which add a new way to define your validations structure + - added Schema, SchemaInterface, SchemaCompiler and SchemaCompilerInterface + - added ArrayValidationExceptionInterface to help catching any validation exceptions + - added StrictValidationFromSchema + - rewrited StrictValidation and added AbstractValidation + - added Validation, ValidationFromDefinition and ValidationFromSchema + which act almost like as Strict* classes but without exceptions + VERSION 2.0.0 ------------- Release date: 2020-02-24 diff --git a/README.md b/README.md index e8d8b51..aa6b610 100644 --- a/README.md +++ b/README.md @@ -8,23 +8,104 @@ composer require peak/array-validation -## Usage +## What it this? -General validation (stateless) +This component help you to validate array structure by: + +- validating the type of any key values +- ensuring a data structure with expected keys requirements +- preventing structure pollution buy allowing only a set of keys + +This is specially useful when dealing with json data request, before using the data, you must validate his content so +you afterward check the value of those keys with your business logic without worrying about the type or presence of any key value. + +# How to use +##### 7 Usages + +## 1- General validation (stateless) ```php $validator = new Validator(); -if ($validator->expectExactlyKeys($array, ['key1', 'key2', 'key3']) === true) { +if ($validator->expectExactlyKeys($array, $keys) === true) { // ... } ``` -Advanced validation with fluent interface. +## 2- Validation with fluent interface (stateful) + +```php +$data = [ // data + 'tags' => [], + 'name' => 'foobar' +]; +$validation = new Validation($data); + +$validation + ->expectKeyToBeArray('tags'); + ->expectKeyToBeString('name'); + +if ($validation->hasErrors()) { + // die($validation->getLastError()) +} +``` + +## 3- Create a ValidationDefinition for later usage + +```php +$vDef = new ValidationDefinition(); +$vDef + ->expectAtLeastKeys(['title', 'content']) + ->expectExactlyKeys(['title', 'content']) + ->expectOnlyKeys(['title', 'content']) + ->expectKeysToBeString(['title', 'content'], true); + +$validationsArray = $arrayDefinition->getValidations(); + +$validation = new ValidationFromDefinition($vDef, $data); + +if ($validation->hasErrors()) { + // $validation->getErrors(); +} + +``` + +## 4- Create a validation Schema for later usage + +Schema is just another way to write validation definitions. This format is ideal when you want to store schemas in file (ex: json, php array file, yml, ...) ```php -$validation = new StrictValidation($array); +$schemaArray = [ + 'title' => [ + 'type' => 'string', + 'required' => true + ], + 'content' => [ + 'type' => 'string', + 'nullable' => true, + ], +]; + +$schema = new Schema( + new SchemaCompiler(), + $schemaArray, + 'mySchemaName' +); + +$validation = new ValidationFromSchema($schema, $data); + +if ($validation->hasErrors()) { + // $validation->getErrors(); +} + +``` + +## 5- Strict validation with fluent interface. + +```php + +$validation = new StrictValidation($arrayToValidate); // will throw an exception if any of tests below fail $validation @@ -35,7 +116,45 @@ $validation ->expectKeyToBeBoolean('isPrivate') ->expectKeyToBeArray('tags'); -// if we reach this point, it means the array is +// if we reach this point, it means the array structure is // valid according to the validation rules above. +``` + +## 6- Strict validation using Schema + +```php +// all validation definitions are executed and an exception is thrown if any of tests failed +new StrictValidationFromDefiniton($validationDefinition, $arrayToValidate); +``` + +## 7- Strict validation using Schema + +```php +// all validation definitions are executed and an exception is thrown if any of tests failed +new StrictValidationFromSchema($schema, $arrayToValidate); +``` + + +# Validation methods +```php + +interface ValidationInterface +{ + public function expectExactlyKeys(array $keys); + public function expectOnlyOneFromKeys( array $keys); + public function expectAtLeastKeys(array $keys); + public function expectOnlyKeys(array $keys); + public function expectNKeys(int $n); + public function expectKeyToBeArray(string $key, bool $acceptNull = false); + public function expectKeysToBeArray(array $keys, bool $acceptNull = false); + public function expectKeyToBeInteger(string $key, bool $acceptNull = false); + public function expectKeysToBeInteger(array $keys, bool $acceptNull = false); + public function expectKeyToBeFloat(string $key, bool $acceptNull = false); + public function expectKeysToBeFloat(array $keys, bool $acceptNull = false); + public function expectKeyToBeString(string $key, bool $acceptNull = false); + public function expectKeysToBeString(array $keys, bool $acceptNull = false); + public function expectKeyToBeBoolean(string $key, bool $acceptNull = false); + public function expectKeysToBeBoolean(array $keys, bool $acceptNull = false); +} ``` \ No newline at end of file diff --git a/composer.json b/composer.json index 0e01e86..cfa9ac6 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,8 @@ "license": "MIT", "type": "library", "require": { - "php": ">=7.2" + "php": ">=7.2", + "symfony/polyfill-php73": "^1.14" }, "require-dev": { "phpunit/phpunit": "^8.2", @@ -16,4 +17,4 @@ "Peak\\ArrayValidation\\": "src/" } } -} \ No newline at end of file +} diff --git a/phpunit.xml b/phpunit.xml index 2d23c38..0d2c7ea 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,5 +1,8 @@ diff --git a/src/AbstractValidation.php b/src/AbstractValidation.php new file mode 100644 index 0000000..3d72668 --- /dev/null +++ b/src/AbstractValidation.php @@ -0,0 +1,70 @@ + '{dataName}invalid data, expected {nExpected} element(s), received {nReceived} element(s)', + 'expected' => '{dataName}invalid data, expected {expectedType} [{keysExpected}], received [{keysReceived}]', + 'type' => '{dataName}invalid type for key [{key}], type {expectedType} is expected', + ]; + + /** + * @param $type + * @param array $context + * @return string + */ + protected function getErrorMessage($type, array $context): string + { + $message = $this->errorMessages[$type]; + $replace = []; + + foreach ($context as $key => $val) { + // check that the value can be casted to string + if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) { + if (isset($fn)) { + $val = $fn($val); + } + $replace['{' . $key . '}'] = $val; + } + } + return strtr($message, $replace); + } + + /** + * @return string|null + */ + public function getLastError(): ?string + { + $lastKey = array_key_last($this->errors); + if ($lastKey === null) { + return $lastKey; + } + return $this->errors[$lastKey]; + } + + /** + * @return bool + */ + public function hasErrors(): bool + { + return !empty($this->errors); + } + + /** + * @return array + */ + public function getErrors(): array + { + return $this->errors; + } +} \ No newline at end of file diff --git a/src/Exception/ArrayValidationExceptionInterface.php b/src/Exception/ArrayValidationExceptionInterface.php new file mode 100644 index 0000000..7793e2a --- /dev/null +++ b/src/Exception/ArrayValidationExceptionInterface.php @@ -0,0 +1,9 @@ +compiler = $compiler; + $this->schema = $schema; + $this->schemaName = $schemaName; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->schemaName; + } + + /** + * @return ValidationDefinition + * @throws Exception\InvalidStructureException + * @throws Exception\InvalidTypeException + */ + public function compile(): ValidationDefinition + { + return $this->compiler->compileSchema($this->schema); + } + + /** + * @inheritDoc + */ + public function jsonSerialize() + { + return $this->schema; + } +} diff --git a/src/SchemaCompiler.php b/src/SchemaCompiler.php new file mode 100644 index 0000000..208259c --- /dev/null +++ b/src/SchemaCompiler.php @@ -0,0 +1,81 @@ + $fieldDefinition) { + if (is_array($fieldDefinition)) { + $this->handleFieldDefinition($fieldName, $fieldDefinition, $definition); + } + } + + // handle "required" + $this->handleRequiredFields($schema, $definition); + + return $definition; + } + + /** + * @param $fieldName + * @param array $fieldDefinition + * @param ValidationDefinition $definition + * @throws Exception\InvalidStructureException + * @throws Exception\InvalidTypeException + */ + private function handleFieldDefinition($fieldName, array $fieldDefinition, ValidationDefinition $definition) + { + (new StrictValidation($fieldDefinition, 'compile.schema.field.' . $fieldName)) + ->expectOnlyKeys([ + 'comment', 'type', 'nullable', 'required', 'default', 'values' + ]) + ->expectKeysToBeBoolean([ + 'nullable', 'required' + ]); + + // handle "type" and "nullable" + if (isset($fieldDefinition['type'])) { + $method = 'expectKeyToBe' . ucfirst($fieldDefinition['type']); + $definition->$method($fieldName, $fieldDefinition['nullable'] ?? false); + } + } + + /** + * @param array $schema + * @param ValidationDefinition $definition + */ + private function handleRequiredFields(array $schema, ValidationDefinition $definition) + { + $definition->expectOnlyKeys(array_keys($schema)); + $atLeastKeys = []; + $fieldCount = 0; + $requiredFieldCount = 0; + foreach ($schema as $field => $fieldDef) { + if (is_array($fieldDef)) { + ++$fieldCount; + if (isset($fieldDef['required']) && $fieldDef['required'] === true) { + $atLeastKeys[] = $field; + ++$requiredFieldCount; + } + } + } + + if ($requiredFieldCount == $fieldCount) { + $definition->expectExactlyKeys($atLeastKeys); + } else { + $definition->expectAtLeastKeys($atLeastKeys); + } + } +} \ No newline at end of file diff --git a/src/SchemaCompilerInterface.php b/src/SchemaCompilerInterface.php new file mode 100644 index 0000000..f23e711 --- /dev/null +++ b/src/SchemaCompilerInterface.php @@ -0,0 +1,16 @@ + '{dataName}invalid data, expected {nExpected} element(s), received {nReceived} element(s)', - 'expected' => '{dataName}invalid data, expected {expectedType} [{keysExpected}], received [{keysReceived}]', - 'type' => '{dataName}invalid type for key [{key}], type {expectedType} is expected', - ]; - - /** - * StrictValidation constructor. - * @param array $data - * @param string|null $dataName - * @param Validator|null $validator - */ - public function __construct(array $data, string $dataName = null, Validator $validator = null) - { - $this->data = $data; - $this->dataName = $dataName; - if (!isset($validator)) { - $validator = new Validator(); - } - $this->validator = $validator; - } - /** * @param array $keys * @return $this @@ -56,16 +16,9 @@ public function __construct(array $data, string $dataName = null, Validator $val */ public function expectExactlyKeys(array $keys) { - if ($this->validator->expectExactlyKeys($this->data, $keys) === false) { - $keysReceived = array_keys($this->data); - natsort($keys); - natsort($keysReceived); - $message = $this->getErrorMessage('expected', [ - 'expectedType' => 'exactly keys', - 'keysExpected' => implode(', ', $keys), - 'keysReceived' => implode(', ', $keysReceived) - ]); - throw new InvalidStructureException($message); + parent::expectExactlyKeys($keys); + if ($this->hasErrors()) { + throw new InvalidStructureException($this->getLastError()); } return $this; } @@ -77,16 +30,9 @@ public function expectExactlyKeys(array $keys) */ public function expectAtLeastKeys(array $keys) { - if ($this->validator->expectAtLeastKeys($this->data, $keys) === false) { - $keysReceived = array_keys($this->data); - natsort($keys); - natsort($keysReceived); - $message = $this->getErrorMessage('expected', [ - 'expectedType' => 'at least keys', - 'keysExpected' => implode(', ', $keys), - 'keysReceived' => implode(', ', $keysReceived) - ]); - throw new InvalidStructureException($message); + parent::expectAtLeastKeys($keys); + if ($this->hasErrors()) { + throw new InvalidStructureException($this->getLastError()); } return $this; } @@ -98,16 +44,9 @@ public function expectAtLeastKeys(array $keys) */ public function expectOnlyKeys(array $keys) { - if ($this->validator->expectOnlyKeys($this->data, $keys) === false) { - $keysReceived = array_keys($this->data); - natsort($keys); - natsort($keysReceived); - $message = $this->getErrorMessage('expected', [ - 'expectedType' => 'only keys', - 'keysExpected' => implode(', ', $keys), - 'keysReceived' => implode(', ', $keysReceived) - ]); - throw new InvalidStructureException($message); + parent::expectOnlyKeys($keys); + if ($this->hasErrors()) { + throw new InvalidStructureException($this->getLastError()); } return $this; } @@ -119,16 +58,9 @@ public function expectOnlyKeys(array $keys) */ public function expectOnlyOneFromKeys(array $keys) { - if ($this->validator->expectOnlyOneFromKeys($this->data, $keys) === false) { - $keysReceived = array_keys($this->data); - natsort($keys); - natsort($keysReceived); - $message = $this->getErrorMessage('expected', [ - 'expectedType' => 'only one of keys', - 'keysExpected' => implode(', ', $keys), - 'keysReceived' => implode(', ', $keysReceived) - ]); - throw new InvalidStructureException($message); + parent::expectOnlyOneFromKeys($keys); + if ($this->hasErrors()) { + throw new InvalidStructureException($this->getLastError()); } return $this; } @@ -140,15 +72,9 @@ public function expectOnlyOneFromKeys(array $keys) */ public function expectNKeys(int $n) { - if ($this->validator->expectNKeys($this->data, $n) === false) { - $keysReceived = array_keys($this->data); - natsort($keysReceived); - $message = $this->getErrorMessage('expectedN', [ - 'expectedType' => 'only N keys', - 'nExpected' => $n, - 'nReceived' => count($keysReceived) - ]); - throw new InvalidStructureException($message); + parent::expectNKeys($n); + if ($this->hasErrors()) { + throw new InvalidStructureException($this->getLastError()); } return $this; } @@ -161,12 +87,9 @@ public function expectNKeys(int $n) */ public function expectKeyToBeArray(string $key, bool $acceptNull = false) { - if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeArray($this->data, $key, $acceptNull) === false) { - $message = $this->getErrorMessage('type', [ - 'key' => $key, - 'expectedType' => 'array', - ]); - throw new InvalidTypeException($message); + parent::expectKeyToBeArray($key, $acceptNull); + if ($this->hasErrors()) { + throw new InvalidTypeException($this->getLastError()); } return $this; } @@ -179,12 +102,9 @@ public function expectKeyToBeArray(string $key, bool $acceptNull = false) */ public function expectKeyToBeInteger(string $key, bool $acceptNull = false) { - if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeInteger($this->data, $key, $acceptNull) === false) { - $message = $this->getErrorMessage('type', [ - 'key' => $key, - 'expectedType' => 'integer', - ]); - throw new InvalidTypeException($message); + parent::expectKeyToBeInteger($key, $acceptNull); + if ($this->hasErrors()) { + throw new InvalidTypeException($this->getLastError()); } return $this; } @@ -197,12 +117,9 @@ public function expectKeyToBeInteger(string $key, bool $acceptNull = false) */ public function expectKeyToBeFloat(string $key, bool $acceptNull = false) { - if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeFloat($this->data, $key, $acceptNull) === false) { - $message = $this->getErrorMessage('type', [ - 'key' => $key, - 'expectedType' => 'float', - ]); - throw new InvalidTypeException($message); + parent::expectKeyToBeFloat($key, $acceptNull); + if ($this->hasErrors()) { + throw new InvalidTypeException($this->getLastError()); } return $this; } @@ -215,12 +132,9 @@ public function expectKeyToBeFloat(string $key, bool $acceptNull = false) */ public function expectKeyToBeString(string $key, bool $acceptNull = false) { - if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeString($this->data, $key, $acceptNull) === false) { - $message = $this->getErrorMessage('type', [ - 'key' => $key, - 'expectedType' => 'string', - ]); - throw new InvalidTypeException($message); + parent::expectKeyToBeString($key, $acceptNull); + if ($this->hasErrors()) { + throw new InvalidTypeException($this->getLastError()); } return $this; } @@ -233,117 +147,10 @@ public function expectKeyToBeString(string $key, bool $acceptNull = false) */ public function expectKeyToBeBoolean(string $key, bool $acceptNull = false) { - if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeBoolean($this->data, $key, $acceptNull) === false) { - $message = $this->getErrorMessage('type', [ - 'key' => $key, - 'expectedType' => 'boolean', - ]); - throw new InvalidTypeException($message); - } - return $this; - } - - /** - * @param array $keys - * @param bool $acceptNull - * @return $this - * @throws InvalidTypeException - */ - public function expectKeysToBeString(array $keys, bool $acceptNull = false) - { - foreach ($keys as $key) { - $this->expectKeyToBeString($key, $acceptNull); - } - return $this; - } - - /** - * @param array $keys - * @param bool $acceptNull - * @return $this - * @throws InvalidTypeException - */ - public function expectKeysToBeInteger(array $keys, bool $acceptNull = false) - { - foreach ($keys as $key) { - $this->expectKeyToBeInteger($key, $acceptNull); - } - return $this; - } - - /** - * @param array $keys - * @param bool $acceptNull - * @return $this - * @throws InvalidTypeException - */ - public function expectKeysToBeFloat(array $keys, bool $acceptNull = false) - { - foreach ($keys as $key) { - $this->expectKeyToBeFloat($key, $acceptNull); - } - return $this; - } - - /** - * @param array $keys - * @param bool $acceptNull - * @return $this - * @throws InvalidTypeException - */ - public function expectKeysToBeBoolean(array $keys, bool $acceptNull = false) - { - foreach ($keys as $key) { - $this->expectKeyToBeBoolean($key, $acceptNull); + parent::expectKeyToBeBoolean($key, $acceptNull); + if ($this->hasErrors()) { + throw new InvalidTypeException($this->getLastError()); } return $this; } - - /** - * @param array $keys - * @param bool $acceptNull - * @return $this - * @throws InvalidTypeException - */ - public function expectKeysToBeArray(array $keys, bool $acceptNull = false) - { - foreach ($keys as $key) { - $this->expectKeyToBeArray($key, $acceptNull); - } - return $this; - } - - /** - * @return string|null - */ - private function getExceptionDataName(): ?string - { - if (isset($this->dataName)) { - return '['. $this->dataName.'] '; - } - return null; - } - - /** - * @param $type - * @param array $context - * @return string - */ - private function getErrorMessage($type, array $context): string - { - $message = $this->messages[$type]; - $context = array_merge(['dataName' => $this->getExceptionDataName()], $context); - $replace = []; - - foreach ($context as $key => $val) { - // check that the value can be casted to string - if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) { - if (isset($fn)) { - $val = $fn($val); - } - $replace['{' . $key . '}'] = $val; - } - } - return strtr($message, $replace); - } } diff --git a/src/StrictValidationFromSchema.php b/src/StrictValidationFromSchema.php new file mode 100644 index 0000000..a5f088c --- /dev/null +++ b/src/StrictValidationFromSchema.php @@ -0,0 +1,29 @@ +getName(), $arrayValidation); + + $validationDefinition = $schema->compile(); + foreach ($validationDefinition->getValidations() as $name => $args) { + call_user_func_array([$this, $name], $args); + } + } +} \ No newline at end of file diff --git a/src/Validation.php b/src/Validation.php new file mode 100644 index 0000000..2d99cfa --- /dev/null +++ b/src/Validation.php @@ -0,0 +1,310 @@ +data = $data; + $this->dataName = $dataName; + if (!isset($validator)) { + $validator = new Validator(); + } + $this->validator = $validator; + } + + /** + * @param array $keys + * @return $this + */ + public function expectExactlyKeys(array $keys) + { + if ($this->validator->expectExactlyKeys($this->data, $keys) === false) { + $keysReceived = array_keys($this->data); + natsort($keys); + natsort($keysReceived); + $message = $this->getErrorMessage('expected', [ + 'expectedType' => 'exactly keys', + 'keysExpected' => implode(', ', $keys), + 'keysReceived' => implode(', ', $keysReceived) + ]); + $this->errors[] = $message; + } + return $this; + } + + /** + * @param array $keys + * @return $this + */ + public function expectAtLeastKeys(array $keys) + { + if ($this->validator->expectAtLeastKeys($this->data, $keys) === false) { + $keysReceived = array_keys($this->data); + natsort($keys); + natsort($keysReceived); + $message = $this->getErrorMessage('expected', [ + 'expectedType' => 'at least keys', + 'keysExpected' => implode(', ', $keys), + 'keysReceived' => implode(', ', $keysReceived) + ]); + $this->errors[] = $message; + } + return $this; + } + + /** + * @param array $keys + * @return $this + */ + public function expectOnlyKeys(array $keys) + { + if ($this->validator->expectOnlyKeys($this->data, $keys) === false) { + $keysReceived = array_keys($this->data); + natsort($keys); + natsort($keysReceived); + $message = $this->getErrorMessage('expected', [ + 'expectedType' => 'only keys', + 'keysExpected' => implode(', ', $keys), + 'keysReceived' => implode(', ', $keysReceived) + ]); + $this->errors[] = $message; + } + return $this; + } + + /** + * @param array $keys + * @return $this + */ + public function expectOnlyOneFromKeys(array $keys) + { + if ($this->validator->expectOnlyOneFromKeys($this->data, $keys) === false) { + $keysReceived = array_keys($this->data); + natsort($keys); + natsort($keysReceived); + $message = $this->getErrorMessage('expected', [ + 'expectedType' => 'only one of keys', + 'keysExpected' => implode(', ', $keys), + 'keysReceived' => implode(', ', $keysReceived) + ]); + $this->errors[] = $message; + } + return $this; + } + + /** + * @param int $n + * @return $this + */ + public function expectNKeys(int $n) + { + if ($this->validator->expectNKeys($this->data, $n) === false) { + $keysReceived = array_keys($this->data); + natsort($keysReceived); + $message = $this->getErrorMessage('expectedN', [ + 'expectedType' => 'only N keys', + 'nExpected' => $n, + 'nReceived' => count($keysReceived) + ]); + $this->errors[] = $message; + } + return $this; + } + + /** + * @param string $key + * @param bool $acceptNull + * @return $this + */ + public function expectKeyToBeArray(string $key, bool $acceptNull = false) + { + if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeArray($this->data, $key, $acceptNull) === false) { + $message = $this->getErrorMessage('type', [ + 'key' => $key, + 'expectedType' => 'array', + ]); + $this->errors[] = $message; + } + return $this; + } + + /** + * @param string $key + * @param bool $acceptNull + * @return $this + */ + public function expectKeyToBeInteger(string $key, bool $acceptNull = false) + { + if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeInteger($this->data, $key, $acceptNull) === false) { + $message = $this->getErrorMessage('type', [ + 'key' => $key, + 'expectedType' => 'integer', + ]); + $this->errors[] = $message; + } + return $this; + } + + /** + * @param string $key + * @param bool $acceptNull + * @return $this + */ + public function expectKeyToBeFloat(string $key, bool $acceptNull = false) + { + if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeFloat($this->data, $key, $acceptNull) === false) { + $message = $this->getErrorMessage('type', [ + 'key' => $key, + 'expectedType' => 'float', + ]); + $this->errors[] = $message; + } + return $this; + } + + /** + * @param string $key + * @param bool $acceptNull + * @return $this + */ + public function expectKeyToBeString(string $key, bool $acceptNull = false) + { + if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeString($this->data, $key, $acceptNull) === false) { + $message = $this->getErrorMessage('type', [ + 'key' => $key, + 'expectedType' => 'string', + ]); + $this->errors[] = $message; + } + return $this; + } + + /** + * @param string $key + * @param bool $acceptNull + * @return $this + */ + public function expectKeyToBeBoolean(string $key, bool $acceptNull = false) + { + if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeBoolean($this->data, $key, $acceptNull) === false) { + $message = $this->getErrorMessage('type', [ + 'key' => $key, + 'expectedType' => 'boolean', + ]); + $this->errors[] = $message; + } + return $this; + } + + /** + * @param array $keys + * @param bool $acceptNull + * @return $this + */ + public function expectKeysToBeString(array $keys, bool $acceptNull = false) + { + foreach ($keys as $key) { + $this->expectKeyToBeString($key, $acceptNull); + } + return $this; + } + + /** + * @param array $keys + * @param bool $acceptNull + * @return $this + */ + public function expectKeysToBeInteger(array $keys, bool $acceptNull = false) + { + foreach ($keys as $key) { + $this->expectKeyToBeInteger($key, $acceptNull); + } + return $this; + } + + /** + * @param array $keys + * @param bool $acceptNull + * @return $this + */ + public function expectKeysToBeFloat(array $keys, bool $acceptNull = false) + { + foreach ($keys as $key) { + $this->expectKeyToBeFloat($key, $acceptNull); + } + return $this; + } + + /** + * @param array $keys + * @param bool $acceptNull + * @return $this + */ + public function expectKeysToBeBoolean(array $keys, bool $acceptNull = false) + { + foreach ($keys as $key) { + $this->expectKeyToBeBoolean($key, $acceptNull); + } + return $this; + } + + /** + * @param array $keys + * @param bool $acceptNull + * @return $this + */ + public function expectKeysToBeArray(array $keys, bool $acceptNull = false) + { + foreach ($keys as $key) { + $this->expectKeyToBeArray($key, $acceptNull); + } + return $this; + } + + /** + * @return string|null + */ + protected function getExceptionDataName(): ?string + { + if (isset($this->dataName)) { + return '['. $this->dataName.'] '; + } + return null; + } + + /** + * @param $type + * @param array $context + * @return string + */ + protected function getErrorMessage($type, array $context): string + { + $context = array_merge(['dataName' => $this->getExceptionDataName()], $context); + return parent::getErrorMessage($type, $context); + } +} diff --git a/src/ValidationDefinition.php b/src/ValidationDefinition.php index 659f872..5ba422e 100644 --- a/src/ValidationDefinition.php +++ b/src/ValidationDefinition.php @@ -4,7 +4,9 @@ namespace Peak\ArrayValidation; -class ValidationDefinition implements ValidationInterface +use \JsonSerializable; + +class ValidationDefinition implements ValidationInterface, JsonSerializable { /** * @var array @@ -130,4 +132,12 @@ public function expectKeysToBeBoolean(array $keys, bool $acceptNull = false) $this->validations[__FUNCTION__] = func_get_args(); return $this; } + + /** + * @inheritDoc + */ + public function jsonSerialize() + { + return $this->getValidations(); + } } diff --git a/src/ValidationFromDefinition.php b/src/ValidationFromDefinition.php new file mode 100644 index 0000000..0d4f967 --- /dev/null +++ b/src/ValidationFromDefinition.php @@ -0,0 +1,35 @@ +arrayValidationDefinition = $arrayValidationDefinition; + parent::__construct($data, $dataName, $arrayValidation); + + $validations = $arrayValidationDefinition->getValidations(); + foreach ($validations as $name => $args) { + call_user_func_array([$this, $name], $args); + } + } +} \ No newline at end of file diff --git a/src/ValidationFromSchema.php b/src/ValidationFromSchema.php new file mode 100644 index 0000000..3b5a8f5 --- /dev/null +++ b/src/ValidationFromSchema.php @@ -0,0 +1,29 @@ +getName(), $arrayValidation); + + $validationDefinition = $schema->compile(); + foreach ($validationDefinition->getValidations() as $name => $args) { + call_user_func_array([$this, $name], $args); + } + } +} \ No newline at end of file diff --git a/tests/ArrayValidationDefinitionTest.php b/tests/ArrayValidationDefinitionTest.php deleted file mode 100644 index 0412de2..0000000 --- a/tests/ArrayValidationDefinitionTest.php +++ /dev/null @@ -1,49 +0,0 @@ -expectAtLeastKeys(['title', 'content']) - ->expectExactlyKeys(['title', 'content']) - ->expectOnlyKeys(['title', 'content']) - ->expectKeysToBeString(['title', 'content'], true); - - $validations = $arrayDefinition->getValidations(); - $this->assertTrue(is_array($validations)); - $this->assertTrue(count($validations) === 4); - } - - public function testStrictArrayValidatorFromDefinition() - { - $arrayDefinition = new ValidationDefinition(); - $arrayDefinition - ->expectAtLeastKeys(['title', 'content']) - ->expectExactlyKeys(['title', 'content']) - ->expectOnlyKeys(['title', 'content']) - ->expectKeysToBeString(['title', 'content'], true); - - $strictValidator = new StrictValidationFromDefinition($arrayDefinition, [ - 'title' => '', - 'content' => '', - ]); - - $this->assertTrue(true); - - $this->expectException(InvalidTypeException::class); - $strictValidator = new StrictValidationFromDefinition($arrayDefinition, [ - 'title' => '', - 'content' => 1, - ]); - } -} diff --git a/tests/SchemaCompilerTest.php b/tests/SchemaCompilerTest.php new file mode 100644 index 0000000..3d00b12 --- /dev/null +++ b/tests/SchemaCompilerTest.php @@ -0,0 +1,73 @@ + 'category.schema', + 'field1' => [ + 'type' => 'array', + 'required' => true + ], + 'field2' => [ + 'type' => 'string', + 'required' => true + ], + ], + [ // data + 'field1' => [], + 'field2' => 'strong' + ] + ], + [ + [ // schema + 'name' => 'post.schema', + 'field1' => [ + 'type' => 'array', + 'required' => true + ], + 'field2' => [ + 'type' => 'string', + 'required' => false, + 'nullable' => true, + ], + ], + [ // data + 'field1' => [], + 'field2' => null + ] + ], + ]; + } + + /** + * @dataProvider dataProvider + * @param $schema + * @param $data + * @throws InvalidStructureException + * @throws InvalidTypeException + */ + public function testExpectExactlyKeys($schema, $data) + { + $def = new SchemaCompiler(); + $validationDefinition = $def->compileSchema($schema); + $this->assertInstanceOf(ValidationInterface::class, $validationDefinition); + new StrictValidationFromDefinition($validationDefinition, $data); + } +} diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php new file mode 100644 index 0000000..1edc875 --- /dev/null +++ b/tests/SchemaTest.php @@ -0,0 +1,73 @@ + [ + 'type' => 'array', + 'required' => true + ], + 'field2' => [ + 'type' => 'string', + 'required' => true + ], + ], + [ // data + 'field1' => [], + 'field2' => 'strong' + ] + ], + [ + [ // schema + 'field1' => [ + 'type' => 'array', + 'required' => true + ], + 'field2' => [ + 'type' => 'string', + 'required' => false, + 'nullable' => true, + ], + ], + [ // data + 'field1' => [], + 'field2' => null + ] + ], + ]; + } + + /** + * @dataProvider dataProvider + * @param $schema + * @param $data + * @throws InvalidStructureException + * @throws InvalidTypeException + */ + public function testExpectExactlyKeys($schema, $data) + { + $compiler = new SchemaCompiler(); + $schema = new Schema($compiler, $schema, 'myName'); + $validationDefinition = $schema->compile(); + $this->assertInstanceOf(ValidationInterface::class, $validationDefinition); + $this->assertTrue($schema->getName() === 'myName'); + new StrictValidationFromDefinition($validationDefinition, $data); + } +} diff --git a/tests/StrictArrayValidatorTest.php b/tests/StrictValidationTest.php similarity index 99% rename from tests/StrictArrayValidatorTest.php rename to tests/StrictValidationTest.php index 6a73018..70d83a2 100644 --- a/tests/StrictArrayValidatorTest.php +++ b/tests/StrictValidationTest.php @@ -7,7 +7,7 @@ use Peak\ArrayValidation\StrictValidation; use PHPUnit\Framework\TestCase; -class StrictArrayValidatorTest extends TestCase +class StrictValidationTest extends TestCase { private $data1 = [ 'title' => 'foo', diff --git a/tests/ValidationDefinitionTest.php b/tests/ValidationDefinitionTest.php new file mode 100644 index 0000000..90bb053 --- /dev/null +++ b/tests/ValidationDefinitionTest.php @@ -0,0 +1,118 @@ +expectAtLeastKeys(['title', 'content']) + ->expectExactlyKeys(['title', 'content']) + ->expectOnlyKeys(['title', 'content']) + ->expectKeysToBeString(['title', 'content'], true); + + $validations = $arrayDefinition->getValidations(); + $this->assertTrue(is_array($validations)); + $this->assertTrue(count($validations) === 4); + } + + public function testGetDefinition2() + { + $arrayDefinition = new ValidationDefinition(); + + $arrayDefinition + ->expectOnlyKeys(['title', 'content', 'id', 'number', 'amount', 'fields', 'isPrivate']) + ->expectKeysToBeInteger(['id']) + ->expectKeyToBeInteger('number') + ->expectKeyToBeFloat('amount') + ->expectKeysToBeFloat(['amount']) + ->expectKeysToBeArray(['fields']) + ->expectKeyToBeArray('fields') + ->expectKeyToBeBoolean('isPrivate') + ->expectKeysToBeBoolean(['isPrivate']) + ->expectKeysToBeString(['title', 'content'], true); + + $validations = $arrayDefinition->getValidations(); + $this->assertTrue(is_array($validations)); + } + + public function testStrictArrayValidatorFromDefinition() + { + $arrayDefinition = new ValidationDefinition(); + $arrayDefinition + ->expectNKeys(2) + ->expectAtLeastKeys(['title', 'content']) + ->expectExactlyKeys(['title', 'content']) + ->expectOnlyKeys(['title', 'content']) + ->expectKeysToBeString(['title', 'content'], true); + + $strictValidator = new StrictValidationFromDefinition($arrayDefinition, [ + 'title' => '', + 'content' => '', + ]); + + $this->assertTrue(true); + + $this->expectException(InvalidTypeException::class); + $strictValidator = new StrictValidationFromDefinition($arrayDefinition, [ + 'title' => '', + 'content' => 1, + ]); + } + + /** + * @throws InvalidTypeException + * @throws InvalidStructureException + */ + public function testStrictArrayValidatorFromSchema() + { + $schemaArray = [ + 'title' => [ + 'type' => 'string', + 'required' => true + ], + 'content' => [ + 'type' => 'string', + 'nullable' => true, + ], + ]; + + $schema = new Schema( + new SchemaCompiler(), + $schemaArray, + 'mySchemaName' + ); + + $arrayToValidate = [ + 'title' => '', + 'content' => null, + ]; + + // will throw an exception if any of tests failed, + // this should pass + new StrictValidationFromSchema($schema, $arrayToValidate); + $this->assertTrue(true); + + // this should not pass + $this->expectException(InvalidTypeException::class); + new StrictValidationFromSchema($schema, [ + 'title' => '', + 'content' => 1, + ]); + + } +} diff --git a/tests/ValidationFromDefinitionTest.php b/tests/ValidationFromDefinitionTest.php new file mode 100644 index 0000000..408b2a2 --- /dev/null +++ b/tests/ValidationFromDefinitionTest.php @@ -0,0 +1,37 @@ +expectAtLeastKeys(['title', 'content']) + ->expectExactlyKeys(['title', 'content']) + ->expectOnlyKeys(['title', 'content']) + ->expectKeysToBeString(['title', 'content'], true); + + $data = [ + 'item1' => 45.1, + 'item2' => 36.5, + 'item3' => [], + 'item4' => null, + 'item5' => 99, + ]; + + $validation = new ValidationFromDefinition($validationDefinition, $data); + + $this->assertTrue($validation->hasErrors()); + $this->assertTrue(count($validation->getErrors()) === 3); + } + +} diff --git a/tests/ValidationFromSchemaTest.php b/tests/ValidationFromSchemaTest.php new file mode 100644 index 0000000..790f0c8 --- /dev/null +++ b/tests/ValidationFromSchemaTest.php @@ -0,0 +1,48 @@ + [ + 'type' => 'string', + 'required' => true + ], + 'content' => [ + 'type' => 'string', + 'nullable' => true, + ], + ]; + + $schema = new Schema( + new SchemaCompiler(), + $schemaArray, + 'mySchemaName' + ); + + $arrayToValidate = [ + 'title' => '', + 'content' => null, + ]; + + // this should not pass + $validation = new ValidationFromSchema($schema, [ + 'title' => '', + 'content' => 1, + ]); + + $this->assertTrue($validation->hasErrors()); + $this->assertTrue(count($validation->getErrors()) == 1); + } + +} diff --git a/tests/ValidationTest.php b/tests/ValidationTest.php new file mode 100644 index 0000000..ab9a3c5 --- /dev/null +++ b/tests/ValidationTest.php @@ -0,0 +1,36 @@ + [], + 'field2' => 'strong' + ]; + $validation = new Validation($data); + + $this->assertFalse($validation->hasErrors()); + + $validation->expectKeyToBeArray('field1'); + $validation->expectKeyToBeString('field2'); + + $this->assertFalse($validation->hasErrors()); + $this->assertTrue($validation->getLastError() === null); + + $validation->expectKeyToBeString('field1'); + + $this->assertTrue($validation->hasErrors()); + $this->assertTrue($validation->getLastError() === 'invalid type for key [field1], type string is expected'); + $this->assertTrue(is_array($validation->getErrors())); + $this->assertTrue(count($validation->getErrors()) == 1); + } +} diff --git a/tests/ArrayValidationTest.php b/tests/ValidatorTest.php similarity index 99% rename from tests/ArrayValidationTest.php rename to tests/ValidatorTest.php index 72eff64..c882877 100644 --- a/tests/ArrayValidationTest.php +++ b/tests/ValidatorTest.php @@ -5,7 +5,7 @@ use \PHPUnit\Framework\TestCase; use \Peak\ArrayValidation\Validator; -class ArrayValidationTest extends TestCase +class ValidatorTest extends TestCase { private $data1 = [ 'title' => 'foo', From a20fbcb725524af492d87c48fb006d2383a51e1e Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 00:50:36 -0400 Subject: [PATCH 03/46] updated --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa6b610..aa9854d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ composer require peak/array-validation -## What it this? +## What is this? This component help you to validate array structure by: From b93cd37dde84a9bdbb2fa6dde0d52260f1a9c5ab Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 00:51:40 -0400 Subject: [PATCH 04/46] updated --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa9854d..f7a57f2 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This component help you to validate array structure by: - validating the type of any key values - ensuring a data structure with expected keys requirements -- preventing structure pollution buy allowing only a set of keys +- preventing structure pollution by allowing only a set of keys This is specially useful when dealing with json data request, before using the data, you must validate his content so you afterward check the value of those keys with your business logic without worrying about the type or presence of any key value. From 9af93b7683a9f97c0b7ef1103ae61fec82f623eb Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 01:41:05 -0400 Subject: [PATCH 05/46] updated --- src/AbstractValidation.php | 2 ++ src/ValidationInterface.php | 2 ++ src/ValidatorInterface.php | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/AbstractValidation.php b/src/AbstractValidation.php index 3d72668..b215234 100644 --- a/src/AbstractValidation.php +++ b/src/AbstractValidation.php @@ -1,5 +1,7 @@ Date: Sat, 21 Mar 2020 01:43:33 -0400 Subject: [PATCH 06/46] updated --- CHANGELOG.md | 2 +- composer.json | 2 +- phpunit.xml | 1 - tests/SchemaCompilerTest.php | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cc5867..641cb6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Release date: ? - added Schema, SchemaInterface, SchemaCompiler and SchemaCompilerInterface - added ArrayValidationExceptionInterface to help catching any validation exceptions - added StrictValidationFromSchema - - rewrited StrictValidation and added AbstractValidation + - rewritten StrictValidation and added AbstractValidation - added Validation, ValidationFromDefinition and ValidationFromSchema which act almost like as Strict* classes but without exceptions diff --git a/composer.json b/composer.json index cfa9ac6..9ebe5e3 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "symfony/polyfill-php73": "^1.14" }, "require-dev": { - "phpunit/phpunit": "^8.2", + "phpunit/phpunit": "^9.0", "phpstan/phpstan": "^0.11" }, "autoload": { diff --git a/phpunit.xml b/phpunit.xml index 0d2c7ea..fcfbb9e 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -21,5 +21,4 @@ - \ No newline at end of file diff --git a/tests/SchemaCompilerTest.php b/tests/SchemaCompilerTest.php index 3d00b12..3516d62 100644 --- a/tests/SchemaCompilerTest.php +++ b/tests/SchemaCompilerTest.php @@ -11,7 +11,6 @@ use Peak\ArrayValidation\ValidationInterface; use \PHPUnit\Framework\TestCase; - class SchemaCompilerTest extends TestCase { From e5f3744853db5957d9aa1569a9b4232403efa556 Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 10:18:25 -0400 Subject: [PATCH 07/46] updated docblock and removed unused var --- src/AbstractValidation.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/AbstractValidation.php b/src/AbstractValidation.php index b215234..e82e7a2 100644 --- a/src/AbstractValidation.php +++ b/src/AbstractValidation.php @@ -7,12 +7,12 @@ abstract class AbstractValidation { /** - * @var array + * @var array */ protected $errors = []; /** - * @var array + * @var array */ protected $errorMessages = [ 'expectedN' => '{dataName}invalid data, expected {nExpected} element(s), received {nReceived} element(s)', @@ -21,11 +21,11 @@ abstract class AbstractValidation ]; /** - * @param $type + * @param string $type * @param array $context * @return string */ - protected function getErrorMessage($type, array $context): string + protected function getErrorMessage(string $type, array $context): string { $message = $this->errorMessages[$type]; $replace = []; @@ -33,9 +33,6 @@ protected function getErrorMessage($type, array $context): string foreach ($context as $key => $val) { // check that the value can be casted to string if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) { - if (isset($fn)) { - $val = $fn($val); - } $replace['{' . $key . '}'] = $val; } } From d7241e27327703e0196a9613ca688ac74b741335 Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 10:18:34 -0400 Subject: [PATCH 08/46] added --- phpstan.neon | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 phpstan.neon diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..afcd42b --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: max + checkMissingIterableValueType: false + paths: + - src From eba709b7f4a3006bb0d9695ce3644251b36a6856 Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 10:18:50 -0400 Subject: [PATCH 09/46] updated phpstan --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9ebe5e3..c734826 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ }, "require-dev": { "phpunit/phpunit": "^9.0", - "phpstan/phpstan": "^0.11" + "phpstan/phpstan": "^0.12" }, "autoload": { "psr-4": { From 44fe38d9d512e7a86cf77ceea2c29457ff6abe32 Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 10:19:11 -0400 Subject: [PATCH 10/46] updated docblock --- src/Schema.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Schema.php b/src/Schema.php index d4b63a1..6f75630 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -27,7 +27,7 @@ class Schema implements SchemaInterface, JsonSerializable * Schema constructor. * @param SchemaCompilerInterface $compiler * @param array $schema - * @param string|null $schemaName + * @param string $schemaName */ public function __construct( SchemaCompilerInterface $compiler, @@ -59,6 +59,7 @@ public function compile(): ValidationDefinition /** * @inheritDoc + * @return array */ public function jsonSerialize() { From 3d359af4896b86b0fa556a100c4371ff4b2e7334 Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 10:20:11 -0400 Subject: [PATCH 11/46] updated docblock and added additional check in compileSchema() --- src/SchemaCompiler.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SchemaCompiler.php b/src/SchemaCompiler.php index 208259c..7580440 100644 --- a/src/SchemaCompiler.php +++ b/src/SchemaCompiler.php @@ -17,7 +17,7 @@ public function compileSchema(array $schema): ValidationDefinition $definition = new ValidationDefinition(); foreach ($schema as $fieldName => $fieldDefinition) { - if (is_array($fieldDefinition)) { + if (is_string($fieldName) && is_array($fieldDefinition)) { $this->handleFieldDefinition($fieldName, $fieldDefinition, $definition); } } @@ -29,13 +29,13 @@ public function compileSchema(array $schema): ValidationDefinition } /** - * @param $fieldName + * @param string $fieldName * @param array $fieldDefinition * @param ValidationDefinition $definition * @throws Exception\InvalidStructureException * @throws Exception\InvalidTypeException */ - private function handleFieldDefinition($fieldName, array $fieldDefinition, ValidationDefinition $definition) + private function handleFieldDefinition(string $fieldName, array $fieldDefinition, ValidationDefinition $definition): void { (new StrictValidation($fieldDefinition, 'compile.schema.field.' . $fieldName)) ->expectOnlyKeys([ @@ -56,7 +56,7 @@ private function handleFieldDefinition($fieldName, array $fieldDefinition, Valid * @param array $schema * @param ValidationDefinition $definition */ - private function handleRequiredFields(array $schema, ValidationDefinition $definition) + private function handleRequiredFields(array $schema, ValidationDefinition $definition): void { $definition->expectOnlyKeys(array_keys($schema)); $atLeastKeys = []; From ec9f4136f5e921f25199dc380ba090ab0f6d2dbe Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 10:21:14 -0400 Subject: [PATCH 12/46] refactored the way exceptions are thrown --- src/StrictValidation.php | 138 ++++++++++++++++++++++++++++----------- 1 file changed, 101 insertions(+), 37 deletions(-) diff --git a/src/StrictValidation.php b/src/StrictValidation.php index 006ac8b..98465de 100644 --- a/src/StrictValidation.php +++ b/src/StrictValidation.php @@ -17,10 +17,7 @@ class StrictValidation extends Validation public function expectExactlyKeys(array $keys) { parent::expectExactlyKeys($keys); - if ($this->hasErrors()) { - throw new InvalidStructureException($this->getLastError()); - } - return $this; + return $this->checkStructureErrors(); } /** @@ -31,10 +28,7 @@ public function expectExactlyKeys(array $keys) public function expectAtLeastKeys(array $keys) { parent::expectAtLeastKeys($keys); - if ($this->hasErrors()) { - throw new InvalidStructureException($this->getLastError()); - } - return $this; + return $this->checkStructureErrors(); } /** @@ -45,10 +39,7 @@ public function expectAtLeastKeys(array $keys) public function expectOnlyKeys(array $keys) { parent::expectOnlyKeys($keys); - if ($this->hasErrors()) { - throw new InvalidStructureException($this->getLastError()); - } - return $this; + return $this->checkStructureErrors(); } /** @@ -59,10 +50,7 @@ public function expectOnlyKeys(array $keys) public function expectOnlyOneFromKeys(array $keys) { parent::expectOnlyOneFromKeys($keys); - if ($this->hasErrors()) { - throw new InvalidStructureException($this->getLastError()); - } - return $this; + return $this->checkStructureErrors(); } /** @@ -73,10 +61,7 @@ public function expectOnlyOneFromKeys(array $keys) public function expectNKeys(int $n) { parent::expectNKeys($n); - if ($this->hasErrors()) { - throw new InvalidStructureException($this->getLastError()); - } - return $this; + return $this->checkStructureErrors(); } /** @@ -88,10 +73,7 @@ public function expectNKeys(int $n) public function expectKeyToBeArray(string $key, bool $acceptNull = false) { parent::expectKeyToBeArray($key, $acceptNull); - if ($this->hasErrors()) { - throw new InvalidTypeException($this->getLastError()); - } - return $this; + return $this->checkTypeErrors(); } /** @@ -103,10 +85,7 @@ public function expectKeyToBeArray(string $key, bool $acceptNull = false) public function expectKeyToBeInteger(string $key, bool $acceptNull = false) { parent::expectKeyToBeInteger($key, $acceptNull); - if ($this->hasErrors()) { - throw new InvalidTypeException($this->getLastError()); - } - return $this; + return $this->checkTypeErrors(); } /** @@ -118,10 +97,7 @@ public function expectKeyToBeInteger(string $key, bool $acceptNull = false) public function expectKeyToBeFloat(string $key, bool $acceptNull = false) { parent::expectKeyToBeFloat($key, $acceptNull); - if ($this->hasErrors()) { - throw new InvalidTypeException($this->getLastError()); - } - return $this; + return $this->checkTypeErrors(); } /** @@ -133,10 +109,7 @@ public function expectKeyToBeFloat(string $key, bool $acceptNull = false) public function expectKeyToBeString(string $key, bool $acceptNull = false) { parent::expectKeyToBeString($key, $acceptNull); - if ($this->hasErrors()) { - throw new InvalidTypeException($this->getLastError()); - } - return $this; + return $this->checkTypeErrors(); } /** @@ -148,8 +121,99 @@ public function expectKeyToBeString(string $key, bool $acceptNull = false) public function expectKeyToBeBoolean(string $key, bool $acceptNull = false) { parent::expectKeyToBeBoolean($key, $acceptNull); + return $this->checkTypeErrors(); + } + + /** + * @param array $keys + * @param bool $acceptNull + * @return $this|Validation + * @throws InvalidTypeException + */ + public function expectKeysToBeString(array $keys, bool $acceptNull = false) + { + foreach ($keys as $key) { + $this->expectKeyToBeString($key, $acceptNull); + } + return $this; + } + + /** + * @param array $keys + * @param bool $acceptNull + * @return $this|Validation + * @throws InvalidTypeException + */ + public function expectKeysToBeInteger(array $keys, bool $acceptNull = false) + { + foreach ($keys as $key) { + $this->expectKeyToBeInteger($key, $acceptNull); + } + return $this; + } + + /** + * @param array $keys + * @param bool $acceptNull + * @return $this|Validation + * @throws InvalidTypeException + */ + public function expectKeysToBeFloat(array $keys, bool $acceptNull = false) + { + foreach ($keys as $key) { + $this->expectKeyToBeFloat($key, $acceptNull); + } + return $this; + } + + /** + * @param array $keys + * @param bool $acceptNull + * @return $this|Validation + * @throws InvalidTypeException + */ + public function expectKeysToBeBoolean(array $keys, bool $acceptNull = false) + { + foreach ($keys as $key) { + $this->expectKeyToBeBoolean($key, $acceptNull); + } + return $this; + } + + /** + * @param array $keys + * @param bool $acceptNull + * @return $this|Validation + * @throws InvalidTypeException + */ + public function expectKeysToBeArray(array $keys, bool $acceptNull = false) + { + foreach ($keys as $key) { + $this->expectKeyToBeArray($key, $acceptNull); + } + return $this; + } + + /** + * @return $this + * @throws InvalidStructureException + */ + protected function checkStructureErrors() + { + if ($this->hasErrors()) { + throw new InvalidStructureException($this->getLastError() ?? ''); + } + return $this; + } + + /** + * @return $this + * @throws InvalidTypeException + */ + protected function checkTypeErrors() + { if ($this->hasErrors()) { - throw new InvalidTypeException($this->getLastError()); + throw new InvalidTypeException($this->getLastError() ?? ''); } return $this; } From 5c3400e5df97ed74509607a7ffa03ebc4bff77cc Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 10:22:04 -0400 Subject: [PATCH 13/46] added a check to ensure method is callable --- src/StrictValidationFromDefinition.php | 5 ++++- src/StrictValidationFromSchema.php | 5 ++++- src/ValidationFromDefinition.php | 5 ++++- src/ValidationFromSchema.php | 5 ++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/StrictValidationFromDefinition.php b/src/StrictValidationFromDefinition.php index 80b0897..7863ca2 100644 --- a/src/StrictValidationFromDefinition.php +++ b/src/StrictValidationFromDefinition.php @@ -29,7 +29,10 @@ public function __construct( $validations = $arrayValidationDefinition->getValidations(); foreach ($validations as $name => $args) { - call_user_func_array([$this, $name], $args); + $callable = [$this, $name]; + if (is_callable($callable)) { + call_user_func_array($callable, $args); + } } } } \ No newline at end of file diff --git a/src/StrictValidationFromSchema.php b/src/StrictValidationFromSchema.php index a5f088c..ea1100c 100644 --- a/src/StrictValidationFromSchema.php +++ b/src/StrictValidationFromSchema.php @@ -23,7 +23,10 @@ public function __construct( $validationDefinition = $schema->compile(); foreach ($validationDefinition->getValidations() as $name => $args) { - call_user_func_array([$this, $name], $args); + $callable = [$this, $name]; + if (is_callable($callable)) { + call_user_func_array($callable, $args); + } } } } \ No newline at end of file diff --git a/src/ValidationFromDefinition.php b/src/ValidationFromDefinition.php index 0d4f967..972c14c 100644 --- a/src/ValidationFromDefinition.php +++ b/src/ValidationFromDefinition.php @@ -29,7 +29,10 @@ public function __construct( $validations = $arrayValidationDefinition->getValidations(); foreach ($validations as $name => $args) { - call_user_func_array([$this, $name], $args); + $callable = [$this, $name]; + if (is_callable($callable)) { + call_user_func_array($callable, $args); + } } } } \ No newline at end of file diff --git a/src/ValidationFromSchema.php b/src/ValidationFromSchema.php index 3b5a8f5..3e2673e 100644 --- a/src/ValidationFromSchema.php +++ b/src/ValidationFromSchema.php @@ -23,7 +23,10 @@ public function __construct( $validationDefinition = $schema->compile(); foreach ($validationDefinition->getValidations() as $name => $args) { - call_user_func_array([$this, $name], $args); + $callable = [$this, $name]; + if (is_callable($callable)) { + call_user_func_array($callable, $args); + } } } } \ No newline at end of file From d6bf529451dbf78e1e9ad17356e3cda8cde085d1 Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 10:22:30 -0400 Subject: [PATCH 14/46] updated docblock --- src/Validation.php | 4 +- src/ValidationDefinition.php | 71 ++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/Validation.php b/src/Validation.php index 2d99cfa..357e61d 100644 --- a/src/Validation.php +++ b/src/Validation.php @@ -298,11 +298,11 @@ protected function getExceptionDataName(): ?string } /** - * @param $type + * @param string $type * @param array $context * @return string */ - protected function getErrorMessage($type, array $context): string + protected function getErrorMessage(string $type, array $context): string { $context = array_merge(['dataName' => $this->getExceptionDataName()], $context); return parent::getErrorMessage($type, $context); diff --git a/src/ValidationDefinition.php b/src/ValidationDefinition.php index 5ba422e..bff8079 100644 --- a/src/ValidationDefinition.php +++ b/src/ValidationDefinition.php @@ -43,90 +43,160 @@ public function getValidations(): array return $validations; } + /** + * @param array $keys + * @return $this + */ public function expectExactlyKeys(array $keys) { $this->validations[__FUNCTION__] = func_get_args(); return $this; } + /** + * @param array $keys + * @return $this + */ public function expectOnlyOneFromKeys(array $keys) { $this->validations[__FUNCTION__] = func_get_args(); return $this; } + /** + * @param array $keys + * @return $this + */ public function expectAtLeastKeys(array $keys) { $this->validations[__FUNCTION__] = func_get_args(); return $this; } + /** + * @param array $keys + * @return $this + */ public function expectOnlyKeys(array $keys) { $this->validations[__FUNCTION__] = func_get_args(); return $this; } + /** + * @param int $n + * @return $this + */ public function expectNKeys(int $n) { $this->validations[__FUNCTION__] = func_get_args(); return $this; } + /** + * @param string $key + * @param bool $acceptNull + * @return $this + */ public function expectKeyToBeArray(string $key, bool $acceptNull = false) { $this->validations[__FUNCTION__] = func_get_args(); return $this; } + /** + * @param array $keys + * @param bool $acceptNull + * @return $this + */ public function expectKeysToBeArray(array $keys, bool $acceptNull = false) { $this->validations[__FUNCTION__] = func_get_args(); return $this; } + /** + * @param string $key + * @param bool $acceptNull + * @return $this + */ public function expectKeyToBeInteger(string $key, bool $acceptNull = false) { $this->validations[__FUNCTION__] = func_get_args(); return $this; } + /** + * @param array $keys + * @param bool $acceptNull + * @return $this + */ public function expectKeysToBeInteger(array $keys, bool $acceptNull = false) { $this->validations[__FUNCTION__] = func_get_args(); return $this; } + /** + * @param string $key + * @param bool $acceptNull + * @return $this + */ public function expectKeyToBeFloat(string $key, bool $acceptNull = false) { $this->validations[__FUNCTION__] = func_get_args(); return $this; } + /** + * @param array $keys + * @param bool $acceptNull + * @return $this + */ public function expectKeysToBeFloat(array $keys, bool $acceptNull = false) { $this->validations[__FUNCTION__] = func_get_args(); return $this; } + /** + * @param string $key + * @param bool $acceptNull + * @return $this + */ public function expectKeyToBeString(string $key, bool $acceptNull = false) { $this->validations[__FUNCTION__] = func_get_args(); return $this; } + /** + * @param array $keys + * @param bool $acceptNull + * @return $this + */ public function expectKeysToBeString(array $keys, bool $acceptNull = false) { $this->validations[__FUNCTION__] = func_get_args(); return $this; } + /** + * @param string $key + * @param bool $acceptNull + * @return $this + */ public function expectKeyToBeBoolean(string $key, bool $acceptNull = false) { $this->validations[__FUNCTION__] = func_get_args(); return $this; } + /** + * @param array $keys + * @param bool $acceptNull + * @return $this + */ public function expectKeysToBeBoolean(array $keys, bool $acceptNull = false) { $this->validations[__FUNCTION__] = func_get_args(); @@ -135,6 +205,7 @@ public function expectKeysToBeBoolean(array $keys, bool $acceptNull = false) /** * @inheritDoc + * @return array */ public function jsonSerialize() { From 1a235db4bff860831b40e75cac47d82a24e707f8 Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 10:23:34 -0400 Subject: [PATCH 15/46] added strict types --- src/Exception/InvalidStructureException.php | 2 ++ src/Exception/InvalidTypeException.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Exception/InvalidStructureException.php b/src/Exception/InvalidStructureException.php index fad9a37..a9aaaa2 100644 --- a/src/Exception/InvalidStructureException.php +++ b/src/Exception/InvalidStructureException.php @@ -1,5 +1,7 @@ Date: Sat, 21 Mar 2020 10:24:14 -0400 Subject: [PATCH 16/46] updated for psr-2 --- src/SchemaCompilerInterface.php | 2 +- src/SchemaInterface.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SchemaCompilerInterface.php b/src/SchemaCompilerInterface.php index f23e711..84e5f93 100644 --- a/src/SchemaCompilerInterface.php +++ b/src/SchemaCompilerInterface.php @@ -13,4 +13,4 @@ interface SchemaCompilerInterface * @throws Exception\InvalidTypeException */ public function compileSchema(array $schema): ValidationDefinition; -} \ No newline at end of file +} diff --git a/src/SchemaInterface.php b/src/SchemaInterface.php index f41c604..b7fd3bc 100644 --- a/src/SchemaInterface.php +++ b/src/SchemaInterface.php @@ -17,4 +17,4 @@ public function getName(): string; * @throws Exception\InvalidTypeException */ public function compile(): ValidationDefinition; -} \ No newline at end of file +} From 6d172bb46964c6121c745ce76663604306fbc7f7 Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 10:25:19 -0400 Subject: [PATCH 17/46] updated for psr-2 --- src/AbstractValidation.php | 2 +- src/Exception/ArrayValidationExceptionInterface.php | 2 +- src/Exception/InvalidStructureException.php | 2 +- src/Exception/InvalidTypeException.php | 2 +- src/SchemaCompiler.php | 2 +- src/StrictValidationFromDefinition.php | 2 +- src/StrictValidationFromSchema.php | 2 +- src/ValidationFromDefinition.php | 2 +- src/ValidationFromSchema.php | 2 +- src/ValidationInterface.php | 2 +- src/ValidatorInterface.php | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/AbstractValidation.php b/src/AbstractValidation.php index e82e7a2..e160591 100644 --- a/src/AbstractValidation.php +++ b/src/AbstractValidation.php @@ -66,4 +66,4 @@ public function getErrors(): array { return $this->errors; } -} \ No newline at end of file +} diff --git a/src/Exception/ArrayValidationExceptionInterface.php b/src/Exception/ArrayValidationExceptionInterface.php index 7793e2a..405b5d7 100644 --- a/src/Exception/ArrayValidationExceptionInterface.php +++ b/src/Exception/ArrayValidationExceptionInterface.php @@ -6,4 +6,4 @@ interface ArrayValidationExceptionInterface { -} \ No newline at end of file +} diff --git a/src/Exception/InvalidStructureException.php b/src/Exception/InvalidStructureException.php index a9aaaa2..0a17da9 100644 --- a/src/Exception/InvalidStructureException.php +++ b/src/Exception/InvalidStructureException.php @@ -8,4 +8,4 @@ class InvalidStructureException extends Exception implements ArrayValidationExceptionInterface { -} \ No newline at end of file +} diff --git a/src/Exception/InvalidTypeException.php b/src/Exception/InvalidTypeException.php index 9cf2add..18af368 100644 --- a/src/Exception/InvalidTypeException.php +++ b/src/Exception/InvalidTypeException.php @@ -8,4 +8,4 @@ class InvalidTypeException extends Exception implements ArrayValidationExceptionInterface { -} \ No newline at end of file +} diff --git a/src/SchemaCompiler.php b/src/SchemaCompiler.php index 7580440..c04cfd1 100644 --- a/src/SchemaCompiler.php +++ b/src/SchemaCompiler.php @@ -78,4 +78,4 @@ private function handleRequiredFields(array $schema, ValidationDefinition $defin $definition->expectAtLeastKeys($atLeastKeys); } } -} \ No newline at end of file +} diff --git a/src/StrictValidationFromDefinition.php b/src/StrictValidationFromDefinition.php index 7863ca2..9199a9e 100644 --- a/src/StrictValidationFromDefinition.php +++ b/src/StrictValidationFromDefinition.php @@ -35,4 +35,4 @@ public function __construct( } } } -} \ No newline at end of file +} diff --git a/src/StrictValidationFromSchema.php b/src/StrictValidationFromSchema.php index ea1100c..7eb6c0b 100644 --- a/src/StrictValidationFromSchema.php +++ b/src/StrictValidationFromSchema.php @@ -29,4 +29,4 @@ public function __construct( } } } -} \ No newline at end of file +} diff --git a/src/ValidationFromDefinition.php b/src/ValidationFromDefinition.php index 972c14c..453f3f8 100644 --- a/src/ValidationFromDefinition.php +++ b/src/ValidationFromDefinition.php @@ -35,4 +35,4 @@ public function __construct( } } } -} \ No newline at end of file +} diff --git a/src/ValidationFromSchema.php b/src/ValidationFromSchema.php index 3e2673e..70f3055 100644 --- a/src/ValidationFromSchema.php +++ b/src/ValidationFromSchema.php @@ -29,4 +29,4 @@ public function __construct( } } } -} \ No newline at end of file +} diff --git a/src/ValidationInterface.php b/src/ValidationInterface.php index 68a8174..5ad54b1 100644 --- a/src/ValidationInterface.php +++ b/src/ValidationInterface.php @@ -21,4 +21,4 @@ public function expectKeyToBeString(string $key, bool $acceptNull = false); public function expectKeysToBeString(array $keys, bool $acceptNull = false); public function expectKeyToBeBoolean(string $key, bool $acceptNull = false); public function expectKeysToBeBoolean(array $keys, bool $acceptNull = false); -} \ No newline at end of file +} diff --git a/src/ValidatorInterface.php b/src/ValidatorInterface.php index e422e95..ced81c9 100644 --- a/src/ValidatorInterface.php +++ b/src/ValidatorInterface.php @@ -21,4 +21,4 @@ public function expectKeyToBeFloat(array $array, string $key, bool $acceptNull = public function expectKeysToBeFloat(array $array, array $keys, bool $acceptNull = false): bool; public function expectKeyToBeBoolean(array $array, string $key, bool $acceptNull = false): bool; public function expectKeysToBeBoolean(array $array, array $keys, bool $acceptNull = false): bool; -} \ No newline at end of file +} From 3568d2a4fdfbba27de11229c17571ae4dd9dc4b2 Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 10:31:28 -0400 Subject: [PATCH 18/46] updated --- README.md | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index f7a57f2..7691e99 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,12 @@ This component help you to validate array structure by: - preventing structure pollution by allowing only a set of keys This is specially useful when dealing with json data request, before using the data, you must validate his content so -you afterward check the value of those keys with your business logic without worrying about the type or presence of any key value. +you can afterward check the value of those keys with your business logic without worrying about the type or presence of any key value. # How to use ##### 7 Usages -## 1- General validation (stateless) +## 1- General validation "à la carte" (stateless) ```php $validator = new Validator(); @@ -60,8 +60,6 @@ $vDef ->expectOnlyKeys(['title', 'content']) ->expectKeysToBeString(['title', 'content'], true); -$validationsArray = $arrayDefinition->getValidations(); - $validation = new ValidationFromDefinition($vDef, $data); if ($validation->hasErrors()) { @@ -76,7 +74,7 @@ Schema is just another way to write validation definitions. This format is ideal ```php -$schemaArray = [ +$mySchema = [ 'title' => [ 'type' => 'string', 'required' => true @@ -87,18 +85,13 @@ $schemaArray = [ ], ]; -$schema = new Schema( - new SchemaCompiler(), - $schemaArray, - 'mySchemaName' -); +$schema = new Schema(new SchemaCompiler(), $mySchema, 'mySchemaName'); $validation = new ValidationFromSchema($schema, $data); if ($validation->hasErrors()) { // $validation->getErrors(); } - ``` ## 5- Strict validation with fluent interface. @@ -124,14 +117,14 @@ $validation ## 6- Strict validation using Schema ```php -// all validation definitions are executed and an exception is thrown if any of tests failed +// all validation definitions are executed at object creation and an exception is thrown if any of tests failed new StrictValidationFromDefiniton($validationDefinition, $arrayToValidate); ``` ## 7- Strict validation using Schema ```php -// all validation definitions are executed and an exception is thrown if any of tests failed +// all validation definitions are executed at object creation and an exception is thrown if any of tests failed new StrictValidationFromSchema($schema, $arrayToValidate); ``` From d22a049454496be0d177e2d5fd2657118f316671 Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 17:20:06 -0400 Subject: [PATCH 19/46] updated --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 641cb6e..2ef511c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ VERSION 3.0.0 ------------- -Release date: ? +Release date: 2020-03-21 - introducing schema which add a new way to define your validations structure - added Schema, SchemaInterface, SchemaCompiler and SchemaCompilerInterface From e3d3902ce1283bc80cb63fdbfa4ab460a597bef3 Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 20:40:41 -0400 Subject: [PATCH 20/46] refactored with ValidationDefinitionExecutor --- src/StrictValidationFromDefinition.php | 20 +++++++++----------- src/StrictValidationFromSchema.php | 11 +++++------ src/ValidationFromDefinition.php | 19 ++++++++----------- src/ValidationFromSchema.php | 10 ++++------ 4 files changed, 26 insertions(+), 34 deletions(-) diff --git a/src/StrictValidationFromDefinition.php b/src/StrictValidationFromDefinition.php index 9199a9e..cb41f38 100644 --- a/src/StrictValidationFromDefinition.php +++ b/src/StrictValidationFromDefinition.php @@ -6,33 +6,31 @@ class StrictValidationFromDefinition extends StrictValidation { + /** * @var ValidationDefinition */ - private $arrayValidationDefinition; + private $validationDefinition; /** * StrictArrayValidatorFromDefinition constructor. - * @param ValidationDefinition $arrayValidationDefinition + * @param ValidationDefinition $validationDefinition * @param array $data * @param string|null $dataName * @param Validator|null $arrayValidation */ public function __construct( - ValidationDefinition $arrayValidationDefinition, + ValidationDefinition $validationDefinition, array $data, string $dataName = null, Validator $arrayValidation = null ) { - $this->arrayValidationDefinition = $arrayValidationDefinition; + $this->validationDefinition = $validationDefinition; parent::__construct($data, $dataName, $arrayValidation); - $validations = $arrayValidationDefinition->getValidations(); - foreach ($validations as $name => $args) { - $callable = [$this, $name]; - if (is_callable($callable)) { - call_user_func_array($callable, $args); - } - } + (new ValidationDefinitionExecutor())->execute( + $validationDefinition, + $this + ); } } diff --git a/src/StrictValidationFromSchema.php b/src/StrictValidationFromSchema.php index 7eb6c0b..89b56ab 100644 --- a/src/StrictValidationFromSchema.php +++ b/src/StrictValidationFromSchema.php @@ -22,11 +22,10 @@ public function __construct( parent::__construct($data, $schema->getName(), $arrayValidation); $validationDefinition = $schema->compile(); - foreach ($validationDefinition->getValidations() as $name => $args) { - $callable = [$this, $name]; - if (is_callable($callable)) { - call_user_func_array($callable, $args); - } - } + + (new ValidationDefinitionExecutor())->execute( + $validationDefinition, + $this + ); } } diff --git a/src/ValidationFromDefinition.php b/src/ValidationFromDefinition.php index 453f3f8..ddc791b 100644 --- a/src/ValidationFromDefinition.php +++ b/src/ValidationFromDefinition.php @@ -9,30 +9,27 @@ class ValidationFromDefinition extends Validation /** * @var ValidationDefinition */ - private $arrayValidationDefinition; + private $validationDefinition; /** * ValidationFromDefinition constructor. - * @param ValidationDefinition $arrayValidationDefinition + * @param ValidationDefinition $validationDefinition * @param array $data * @param string|null $dataName * @param Validator|null $arrayValidation */ public function __construct( - ValidationDefinition $arrayValidationDefinition, + ValidationDefinition $validationDefinition, array $data, string $dataName = null, Validator $arrayValidation = null ) { - $this->arrayValidationDefinition = $arrayValidationDefinition; + $this->validationDefinition = $validationDefinition; parent::__construct($data, $dataName, $arrayValidation); - $validations = $arrayValidationDefinition->getValidations(); - foreach ($validations as $name => $args) { - $callable = [$this, $name]; - if (is_callable($callable)) { - call_user_func_array($callable, $args); - } - } + (new ValidationDefinitionExecutor())->execute( + $validationDefinition, + $this + ); } } diff --git a/src/ValidationFromSchema.php b/src/ValidationFromSchema.php index 70f3055..2185fc2 100644 --- a/src/ValidationFromSchema.php +++ b/src/ValidationFromSchema.php @@ -22,11 +22,9 @@ public function __construct( parent::__construct($data, $schema->getName(), $arrayValidation); $validationDefinition = $schema->compile(); - foreach ($validationDefinition->getValidations() as $name => $args) { - $callable = [$this, $name]; - if (is_callable($callable)) { - call_user_func_array($callable, $args); - } - } + (new ValidationDefinitionExecutor())->execute( + $validationDefinition, + $this + ); } } From 52ab285e06277443c6a47eee7c91959e4ba10a12 Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 20:40:53 -0400 Subject: [PATCH 21/46] added --- src/ValidationDefinitionExecutor.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/ValidationDefinitionExecutor.php diff --git a/src/ValidationDefinitionExecutor.php b/src/ValidationDefinitionExecutor.php new file mode 100644 index 0000000..0ff1a57 --- /dev/null +++ b/src/ValidationDefinitionExecutor.php @@ -0,0 +1,23 @@ +getValidations(); + foreach ($validations as $name => $multipleArgs) { + foreach ($multipleArgs as $args) { + $callable = [$validation, $name]; + if (is_callable($callable)) { + call_user_func_array($callable, $args); + } + } + } + } +} \ No newline at end of file From 25c196c1799d7591b8f25bb4ea30d1b347786260 Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 20:42:31 -0400 Subject: [PATCH 22/46] fixed bug with ValidationDefinition where same method validation were not stacked but overwritten --- src/ValidationDefinition.php | 62 ++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/ValidationDefinition.php b/src/ValidationDefinition.php index bff8079..3a5716f 100644 --- a/src/ValidationDefinition.php +++ b/src/ValidationDefinition.php @@ -12,21 +12,21 @@ class ValidationDefinition implements ValidationInterface, JsonSerializable * @var array */ private $validations = [ - 'expectExactlyKeys' => null, - 'expectOnlyOneFromKeys' => null, - 'expectAtLeastKeys' => null, - 'expectOnlyKeys' => null, - 'expectNKeys' => null, - 'expectKeyToBeArray' => null, - 'expectKeysToBeArray' => null, - 'expectKeyToBeInteger' => null, - 'expectKeysToBeInteger' => null, - 'expectKeyToBeFloat' => null, - 'expectKeysToBeFloat' => null, - 'expectKeyToBeString' => null, - 'expectKeysToBeString' => null, - 'expectKeyToBeBoolean' => null, - 'expectKeysToBeBoolean' => null, + 'expectExactlyKeys' => [], + 'expectOnlyOneFromKeys' => [], + 'expectAtLeastKeys' => [], + 'expectOnlyKeys' => [], + 'expectNKeys' => [], + 'expectKeyToBeArray' => [], + 'expectKeysToBeArray' => [], + 'expectKeyToBeInteger' => [], + 'expectKeysToBeInteger' => [], + 'expectKeyToBeFloat' => [], + 'expectKeysToBeFloat' => [], + 'expectKeyToBeString' => [], + 'expectKeysToBeString' => [], + 'expectKeyToBeBoolean' => [], + 'expectKeysToBeBoolean' => [], ]; /** @@ -36,7 +36,7 @@ public function getValidations(): array { $validations = []; foreach ($this->validations as $name => $args) { - if (null !== $args) { + if (!empty($args)) { $validations[$name] = $args; } } @@ -49,7 +49,7 @@ public function getValidations(): array */ public function expectExactlyKeys(array $keys) { - $this->validations[__FUNCTION__] = func_get_args(); + $this->validations[__FUNCTION__][] = func_get_args(); return $this; } @@ -59,7 +59,7 @@ public function expectExactlyKeys(array $keys) */ public function expectOnlyOneFromKeys(array $keys) { - $this->validations[__FUNCTION__] = func_get_args(); + $this->validations[__FUNCTION__][] = func_get_args(); return $this; } @@ -69,7 +69,7 @@ public function expectOnlyOneFromKeys(array $keys) */ public function expectAtLeastKeys(array $keys) { - $this->validations[__FUNCTION__] = func_get_args(); + $this->validations[__FUNCTION__][] = func_get_args(); return $this; } @@ -79,7 +79,7 @@ public function expectAtLeastKeys(array $keys) */ public function expectOnlyKeys(array $keys) { - $this->validations[__FUNCTION__] = func_get_args(); + $this->validations[__FUNCTION__][] = func_get_args(); return $this; } @@ -89,7 +89,7 @@ public function expectOnlyKeys(array $keys) */ public function expectNKeys(int $n) { - $this->validations[__FUNCTION__] = func_get_args(); + $this->validations[__FUNCTION__][] = func_get_args(); return $this; } @@ -100,7 +100,7 @@ public function expectNKeys(int $n) */ public function expectKeyToBeArray(string $key, bool $acceptNull = false) { - $this->validations[__FUNCTION__] = func_get_args(); + $this->validations[__FUNCTION__][] = func_get_args(); return $this; } @@ -111,7 +111,7 @@ public function expectKeyToBeArray(string $key, bool $acceptNull = false) */ public function expectKeysToBeArray(array $keys, bool $acceptNull = false) { - $this->validations[__FUNCTION__] = func_get_args(); + $this->validations[__FUNCTION__][] = func_get_args(); return $this; } @@ -122,7 +122,7 @@ public function expectKeysToBeArray(array $keys, bool $acceptNull = false) */ public function expectKeyToBeInteger(string $key, bool $acceptNull = false) { - $this->validations[__FUNCTION__] = func_get_args(); + $this->validations[__FUNCTION__][] = func_get_args(); return $this; } @@ -133,7 +133,7 @@ public function expectKeyToBeInteger(string $key, bool $acceptNull = false) */ public function expectKeysToBeInteger(array $keys, bool $acceptNull = false) { - $this->validations[__FUNCTION__] = func_get_args(); + $this->validations[__FUNCTION__][] = func_get_args(); return $this; } @@ -144,7 +144,7 @@ public function expectKeysToBeInteger(array $keys, bool $acceptNull = false) */ public function expectKeyToBeFloat(string $key, bool $acceptNull = false) { - $this->validations[__FUNCTION__] = func_get_args(); + $this->validations[__FUNCTION__][] = func_get_args(); return $this; } @@ -155,7 +155,7 @@ public function expectKeyToBeFloat(string $key, bool $acceptNull = false) */ public function expectKeysToBeFloat(array $keys, bool $acceptNull = false) { - $this->validations[__FUNCTION__] = func_get_args(); + $this->validations[__FUNCTION__][] = func_get_args(); return $this; } @@ -166,7 +166,7 @@ public function expectKeysToBeFloat(array $keys, bool $acceptNull = false) */ public function expectKeyToBeString(string $key, bool $acceptNull = false) { - $this->validations[__FUNCTION__] = func_get_args(); + $this->validations[__FUNCTION__][] = func_get_args(); return $this; } @@ -177,7 +177,7 @@ public function expectKeyToBeString(string $key, bool $acceptNull = false) */ public function expectKeysToBeString(array $keys, bool $acceptNull = false) { - $this->validations[__FUNCTION__] = func_get_args(); + $this->validations[__FUNCTION__][] = func_get_args(); return $this; } @@ -188,7 +188,7 @@ public function expectKeysToBeString(array $keys, bool $acceptNull = false) */ public function expectKeyToBeBoolean(string $key, bool $acceptNull = false) { - $this->validations[__FUNCTION__] = func_get_args(); + $this->validations[__FUNCTION__][] = func_get_args(); return $this; } @@ -199,7 +199,7 @@ public function expectKeyToBeBoolean(string $key, bool $acceptNull = false) */ public function expectKeysToBeBoolean(array $keys, bool $acceptNull = false) { - $this->validations[__FUNCTION__] = func_get_args(); + $this->validations[__FUNCTION__][] = func_get_args(); return $this; } From f004fdf293b2980169683926dea3f873da1d821c Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 20:42:45 -0400 Subject: [PATCH 23/46] updated --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ef511c..5fac70f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ Release date: 2020-03-21 - rewritten StrictValidation and added AbstractValidation - added Validation, ValidationFromDefinition and ValidationFromSchema which act almost like as Strict* classes but without exceptions + - fixed bug with ValidationDefinition where same method validation were not stacked but overwritten + - added ValidationDefinitionExecutor VERSION 2.0.0 ------------- From 09cb8bfc45e38706bf7f8fb8a408ea386e186813 Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 20:51:28 -0400 Subject: [PATCH 24/46] updated --- tests/ValidationDefinitionTest.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/tests/ValidationDefinitionTest.php b/tests/ValidationDefinitionTest.php index 90bb053..033fd41 100644 --- a/tests/ValidationDefinitionTest.php +++ b/tests/ValidationDefinitionTest.php @@ -35,7 +35,7 @@ public function testGetDefinition2() $arrayDefinition = new ValidationDefinition(); $arrayDefinition - ->expectOnlyKeys(['title', 'content', 'id', 'number', 'amount', 'fields', 'isPrivate']) + ->expectOnlyKeys(['title', 'content', 'description', 'id', 'number', 'amount', 'fields', 'isPrivate']) ->expectKeysToBeInteger(['id']) ->expectKeyToBeInteger('number') ->expectKeyToBeFloat('amount') @@ -44,10 +44,25 @@ public function testGetDefinition2() ->expectKeyToBeArray('fields') ->expectKeyToBeBoolean('isPrivate') ->expectKeysToBeBoolean(['isPrivate']) - ->expectKeysToBeString(['title', 'content'], true); + ->expectKeysToBeString(['title', 'content'], true) + ->expectKeyToBeString('title', true) + ->expectKeyToBeString('content', true) + ->expectKeyToBeString('description', false); $validations = $arrayDefinition->getValidations(); +// print_r($validations); $this->assertTrue(is_array($validations)); + $this->assertTrue(isset($validations['expectKeyToBeString'][0][0])); + $this->assertTrue($validations['expectKeyToBeString'][0][0] === 'title'); + $this->assertTrue($validations['expectKeyToBeString'][0][1]); + + $this->assertTrue(isset($validations['expectKeyToBeString'][1][0])); + $this->assertTrue($validations['expectKeyToBeString'][1][0] === 'content'); + $this->assertTrue($validations['expectKeyToBeString'][1][1]); + + $this->assertTrue(isset($validations['expectKeyToBeString'][2][0])); + $this->assertTrue($validations['expectKeyToBeString'][2][0] === 'description'); + $this->assertFalse($validations['expectKeyToBeString'][2][1]); } public function testStrictArrayValidatorFromDefinition() From 1e1e24ee69acba051fdf4d95187bea5c79a7ef30 Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 22:15:20 -0400 Subject: [PATCH 25/46] changed Schema to SchemaInterface --- src/StrictValidationFromSchema.php | 4 ++-- src/ValidationFromSchema.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/StrictValidationFromSchema.php b/src/StrictValidationFromSchema.php index 89b56ab..5e705ce 100644 --- a/src/StrictValidationFromSchema.php +++ b/src/StrictValidationFromSchema.php @@ -8,14 +8,14 @@ class StrictValidationFromSchema extends StrictValidation { /** * StrictValidationFromSchema constructor. - * @param Schema $schema + * @param SchemaInterface $schema * @param array $data * @param Validator|null $arrayValidation * @throws Exception\InvalidStructureException * @throws Exception\InvalidTypeException */ public function __construct( - Schema $schema, + SchemaInterface $schema, array $data, Validator $arrayValidation = null ) { diff --git a/src/ValidationFromSchema.php b/src/ValidationFromSchema.php index 2185fc2..2330da0 100644 --- a/src/ValidationFromSchema.php +++ b/src/ValidationFromSchema.php @@ -8,14 +8,14 @@ class ValidationFromSchema extends Validation { /** * ValidationFromSchema constructor. - * @param Schema $schema + * @param SchemaInterface $schema * @param array $data * @param Validator|null $arrayValidation * @throws Exception\InvalidStructureException * @throws Exception\InvalidTypeException */ public function __construct( - Schema $schema, + SchemaInterface $schema, array $data, Validator $arrayValidation = null ) { From f9ea6bc1827de46077f2389a4404786a55d15f44 Mon Sep 17 00:00:00 2001 From: Franck Date: Sat, 21 Mar 2020 22:15:29 -0400 Subject: [PATCH 26/46] updated --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fac70f..1f8bb89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +VERSION 3.1.0 +------------- +Release date: ? + + - use SchemaInterface instead of Schema in StrictValidationFromSchema and ValidationFromSchema + VERSION 3.0.0 ------------- Release date: 2020-03-21 From 2e65bb90f67e18615c495720b7af8ac2ac09a5a0 Mon Sep 17 00:00:00 2001 From: Franck Date: Wed, 25 Mar 2020 13:23:57 -0400 Subject: [PATCH 27/46] updated --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7691e99..8c23a77 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ $validation ``` -## 6- Strict validation using Schema +## 6- Strict validation using ValidationDefinition ```php // all validation definitions are executed at object creation and an exception is thrown if any of tests failed From dc7eee0b7f95c61c3c7eb4b9e4ac24e3f39d6296 Mon Sep 17 00:00:00 2001 From: Franck Date: Wed, 25 Mar 2020 13:24:16 -0400 Subject: [PATCH 28/46] updated --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c23a77..9e8bfb8 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ $validation ```php // all validation definitions are executed at object creation and an exception is thrown if any of tests failed -new StrictValidationFromDefiniton($validationDefinition, $arrayToValidate); +new StrictValidationFromDefinition($validationDefinition, $arrayToValidate); ``` ## 7- Strict validation using Schema From 09f90c35d577af0457d9a5d42aff3c7c75588272 Mon Sep 17 00:00:00 2001 From: Franck Date: Thu, 26 Mar 2020 07:32:00 -0400 Subject: [PATCH 29/46] updated --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9e8bfb8..aa44bbb 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ $data = [ // data $validation = new Validation($data); $validation + ->expectExactlyKeys(['tags', 'name']) ->expectKeyToBeArray('tags'); ->expectKeyToBeString('name'); @@ -55,10 +56,9 @@ if ($validation->hasErrors()) { ```php $vDef = new ValidationDefinition(); $vDef + ->expectOnlyKeys(['title', 'content', 'description']) ->expectAtLeastKeys(['title', 'content']) - ->expectExactlyKeys(['title', 'content']) - ->expectOnlyKeys(['title', 'content']) - ->expectKeysToBeString(['title', 'content'], true); + ->expectKeysToBeString(['title', 'content', 'description']); $validation = new ValidationFromDefinition($vDef, $data); From 5839c3a4080d213c791905a4220889f38faac180 Mon Sep 17 00:00:00 2001 From: Franck Date: Thu, 26 Mar 2020 07:34:28 -0400 Subject: [PATCH 30/46] updated --- README.md | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index aa44bbb..0e4e5cd 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,27 @@ if ($validation->hasErrors()) { } ``` -## 3- Create a ValidationDefinition for later usage +## 3- Strict validation with fluent interface (stateful) + +```php + +$validation = new StrictValidation($arrayToValidate); + +// will throw an exception if any of tests below fail +$validation + ->expectOnlyKeys(['id', 'title', 'description', 'isPrivate', 'tags']) + ->expectAtLeastKeys(['id', 'title', 'description']) + ->expectKeyToBeInteger('id') + ->expectKeysToBeString(['title', 'description']) + ->expectKeyToBeBoolean('isPrivate') + ->expectKeyToBeArray('tags'); + +// if we reach this point, it means the array structure is +// valid according to the validation rules above. + +``` + +## 4- Create a ValidationDefinition for later usage ```php $vDef = new ValidationDefinition(); @@ -68,7 +88,7 @@ if ($validation->hasErrors()) { ``` -## 4- Create a validation Schema for later usage +## 5- Create a validation Schema for later usage Schema is just another way to write validation definitions. This format is ideal when you want to store schemas in file (ex: json, php array file, yml, ...) @@ -94,25 +114,7 @@ if ($validation->hasErrors()) { } ``` -## 5- Strict validation with fluent interface. - -```php - -$validation = new StrictValidation($arrayToValidate); - -// will throw an exception if any of tests below fail -$validation - ->expectOnlyKeys(['id', 'title', 'description', 'isPrivate', 'tags']) - ->expectAtLeastKeys(['id', 'title', 'description']) - ->expectKeyToBeInteger('id') - ->expectKeysToBeString(['title', 'description']) - ->expectKeyToBeBoolean('isPrivate') - ->expectKeyToBeArray('tags'); - -// if we reach this point, it means the array structure is -// valid according to the validation rules above. -``` ## 6- Strict validation using ValidationDefinition From e33b937dfa3c8af19ec0c7df42f87160e5e82293 Mon Sep 17 00:00:00 2001 From: Franck Date: Thu, 26 Mar 2020 12:20:17 -0400 Subject: [PATCH 31/46] updated error message --- src/AbstractValidation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AbstractValidation.php b/src/AbstractValidation.php index e160591..38b5846 100644 --- a/src/AbstractValidation.php +++ b/src/AbstractValidation.php @@ -15,7 +15,7 @@ abstract class AbstractValidation * @var array */ protected $errorMessages = [ - 'expectedN' => '{dataName}invalid data, expected {nExpected} element(s), received {nReceived} element(s)', + 'expectedN' => '{dataName}invalid data, expected {nExpected} element{nExpectedPlural}, received {nReceived} element{nReceivedPlural}', 'expected' => '{dataName}invalid data, expected {expectedType} [{keysExpected}], received [{keysReceived}]', 'type' => '{dataName}invalid type for key [{key}], type {expectedType} is expected', ]; From 27879ddd2a4b40f61a7e432f04c15c75036bf95a Mon Sep 17 00:00:00 2001 From: Franck Date: Thu, 26 Mar 2020 12:21:30 -0400 Subject: [PATCH 32/46] added expectKeyToBeObject() and expectKeysToBeObject() --- src/Validation.php | 61 +++++++++++++++++++--------- src/ValidationDefinition.php | 78 ++++++++++++++++++++++-------------- src/ValidationInterface.php | 2 + src/Validator.php | 30 ++++++++++++++ src/ValidatorInterface.php | 2 + 5 files changed, 123 insertions(+), 50 deletions(-) diff --git a/src/Validation.php b/src/Validation.php index 357e61d..98b7396 100644 --- a/src/Validation.php +++ b/src/Validation.php @@ -47,12 +47,11 @@ public function expectExactlyKeys(array $keys) $keysReceived = array_keys($this->data); natsort($keys); natsort($keysReceived); - $message = $this->getErrorMessage('expected', [ + $this->errors[] = $this->getErrorMessage('expected', [ 'expectedType' => 'exactly keys', 'keysExpected' => implode(', ', $keys), 'keysReceived' => implode(', ', $keysReceived) ]); - $this->errors[] = $message; } return $this; } @@ -67,12 +66,11 @@ public function expectAtLeastKeys(array $keys) $keysReceived = array_keys($this->data); natsort($keys); natsort($keysReceived); - $message = $this->getErrorMessage('expected', [ + $this->errors[] = $this->getErrorMessage('expected', [ 'expectedType' => 'at least keys', 'keysExpected' => implode(', ', $keys), 'keysReceived' => implode(', ', $keysReceived) ]); - $this->errors[] = $message; } return $this; } @@ -87,12 +85,11 @@ public function expectOnlyKeys(array $keys) $keysReceived = array_keys($this->data); natsort($keys); natsort($keysReceived); - $message = $this->getErrorMessage('expected', [ + $this->errors[] = $this->getErrorMessage('expected', [ 'expectedType' => 'only keys', 'keysExpected' => implode(', ', $keys), 'keysReceived' => implode(', ', $keysReceived) ]); - $this->errors[] = $message; } return $this; } @@ -107,12 +104,11 @@ public function expectOnlyOneFromKeys(array $keys) $keysReceived = array_keys($this->data); natsort($keys); natsort($keysReceived); - $message = $this->getErrorMessage('expected', [ + $this->errors[] = $this->getErrorMessage('expected', [ 'expectedType' => 'only one of keys', 'keysExpected' => implode(', ', $keys), 'keysReceived' => implode(', ', $keysReceived) ]); - $this->errors[] = $message; } return $this; } @@ -126,12 +122,13 @@ public function expectNKeys(int $n) if ($this->validator->expectNKeys($this->data, $n) === false) { $keysReceived = array_keys($this->data); natsort($keysReceived); - $message = $this->getErrorMessage('expectedN', [ + $this->errors[] = $this->getErrorMessage('expectedN', [ 'expectedType' => 'only N keys', 'nExpected' => $n, + 'nExpectedPlural' => $n > 1 ? 's' : '', + 'nReceivedPlural' => count($keysReceived) > 1 ? 's' : '', 'nReceived' => count($keysReceived) ]); - $this->errors[] = $message; } return $this; } @@ -144,11 +141,10 @@ public function expectNKeys(int $n) public function expectKeyToBeArray(string $key, bool $acceptNull = false) { if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeArray($this->data, $key, $acceptNull) === false) { - $message = $this->getErrorMessage('type', [ + $this->errors[] = $this->getErrorMessage('type', [ 'key' => $key, 'expectedType' => 'array', ]); - $this->errors[] = $message; } return $this; } @@ -161,11 +157,10 @@ public function expectKeyToBeArray(string $key, bool $acceptNull = false) public function expectKeyToBeInteger(string $key, bool $acceptNull = false) { if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeInteger($this->data, $key, $acceptNull) === false) { - $message = $this->getErrorMessage('type', [ + $this->errors[] = $this->getErrorMessage('type', [ 'key' => $key, 'expectedType' => 'integer', ]); - $this->errors[] = $message; } return $this; } @@ -178,11 +173,10 @@ public function expectKeyToBeInteger(string $key, bool $acceptNull = false) public function expectKeyToBeFloat(string $key, bool $acceptNull = false) { if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeFloat($this->data, $key, $acceptNull) === false) { - $message = $this->getErrorMessage('type', [ + $this->errors[] = $this->getErrorMessage('type', [ 'key' => $key, 'expectedType' => 'float', ]); - $this->errors[] = $message; } return $this; } @@ -195,11 +189,10 @@ public function expectKeyToBeFloat(string $key, bool $acceptNull = false) public function expectKeyToBeString(string $key, bool $acceptNull = false) { if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeString($this->data, $key, $acceptNull) === false) { - $message = $this->getErrorMessage('type', [ + $this->errors[] = $this->getErrorMessage('type', [ 'key' => $key, 'expectedType' => 'string', ]); - $this->errors[] = $message; } return $this; } @@ -212,11 +205,26 @@ public function expectKeyToBeString(string $key, bool $acceptNull = false) public function expectKeyToBeBoolean(string $key, bool $acceptNull = false) { if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeBoolean($this->data, $key, $acceptNull) === false) { - $message = $this->getErrorMessage('type', [ + $this->errors[] = $this->getErrorMessage('type', [ 'key' => $key, 'expectedType' => 'boolean', ]); - $this->errors[] = $message; + } + return $this; + } + + /** + * @param string $key + * @param bool $acceptNull + * @return $this + */ + public function expectKeyToBeObject(string $key, bool $acceptNull = false) + { + if (array_key_exists($key, $this->data) && $this->validator->expectKeyToBeObject($this->data, $key, $acceptNull) === false) { + $this->errors[] = $this->getErrorMessage('type', [ + 'key' => $key, + 'expectedType' => 'object', + ]); } return $this; } @@ -286,6 +294,19 @@ public function expectKeysToBeArray(array $keys, bool $acceptNull = false) return $this; } + /** + * @param array $keys + * @param bool $acceptNull + * @return $this + */ + public function expectKeysToBeObject(array $keys, bool $acceptNull = false) + { + foreach ($keys as $key) { + $this->expectKeyToBeObject($key, $acceptNull); + } + return $this; + } + /** * @return string|null */ diff --git a/src/ValidationDefinition.php b/src/ValidationDefinition.php index 3a5716f..e1018c7 100644 --- a/src/ValidationDefinition.php +++ b/src/ValidationDefinition.php @@ -27,6 +27,8 @@ class ValidationDefinition implements ValidationInterface, JsonSerializable 'expectKeysToBeString' => [], 'expectKeyToBeBoolean' => [], 'expectKeysToBeBoolean' => [], + 'expectKeyToBeObject' => [], + 'expectKeysToBeObject' => [], ]; /** @@ -49,8 +51,7 @@ public function getValidations(): array */ public function expectExactlyKeys(array $keys) { - $this->validations[__FUNCTION__][] = func_get_args(); - return $this; + return $this->addValidation(__FUNCTION__, func_get_args()); } /** @@ -59,8 +60,7 @@ public function expectExactlyKeys(array $keys) */ public function expectOnlyOneFromKeys(array $keys) { - $this->validations[__FUNCTION__][] = func_get_args(); - return $this; + return $this->addValidation(__FUNCTION__, func_get_args()); } /** @@ -69,8 +69,7 @@ public function expectOnlyOneFromKeys(array $keys) */ public function expectAtLeastKeys(array $keys) { - $this->validations[__FUNCTION__][] = func_get_args(); - return $this; + return $this->addValidation(__FUNCTION__, func_get_args()); } /** @@ -79,8 +78,7 @@ public function expectAtLeastKeys(array $keys) */ public function expectOnlyKeys(array $keys) { - $this->validations[__FUNCTION__][] = func_get_args(); - return $this; + return $this->addValidation(__FUNCTION__, func_get_args()); } /** @@ -89,8 +87,7 @@ public function expectOnlyKeys(array $keys) */ public function expectNKeys(int $n) { - $this->validations[__FUNCTION__][] = func_get_args(); - return $this; + return $this->addValidation(__FUNCTION__, func_get_args()); } /** @@ -100,8 +97,7 @@ public function expectNKeys(int $n) */ public function expectKeyToBeArray(string $key, bool $acceptNull = false) { - $this->validations[__FUNCTION__][] = func_get_args(); - return $this; + return $this->addValidation(__FUNCTION__, func_get_args()); } /** @@ -111,8 +107,7 @@ public function expectKeyToBeArray(string $key, bool $acceptNull = false) */ public function expectKeysToBeArray(array $keys, bool $acceptNull = false) { - $this->validations[__FUNCTION__][] = func_get_args(); - return $this; + return $this->addValidation(__FUNCTION__, func_get_args()); } /** @@ -122,8 +117,7 @@ public function expectKeysToBeArray(array $keys, bool $acceptNull = false) */ public function expectKeyToBeInteger(string $key, bool $acceptNull = false) { - $this->validations[__FUNCTION__][] = func_get_args(); - return $this; + return $this->addValidation(__FUNCTION__, func_get_args()); } /** @@ -133,8 +127,7 @@ public function expectKeyToBeInteger(string $key, bool $acceptNull = false) */ public function expectKeysToBeInteger(array $keys, bool $acceptNull = false) { - $this->validations[__FUNCTION__][] = func_get_args(); - return $this; + return $this->addValidation(__FUNCTION__, func_get_args()); } /** @@ -144,8 +137,7 @@ public function expectKeysToBeInteger(array $keys, bool $acceptNull = false) */ public function expectKeyToBeFloat(string $key, bool $acceptNull = false) { - $this->validations[__FUNCTION__][] = func_get_args(); - return $this; + return $this->addValidation(__FUNCTION__, func_get_args()); } /** @@ -155,8 +147,7 @@ public function expectKeyToBeFloat(string $key, bool $acceptNull = false) */ public function expectKeysToBeFloat(array $keys, bool $acceptNull = false) { - $this->validations[__FUNCTION__][] = func_get_args(); - return $this; + return $this->addValidation(__FUNCTION__, func_get_args()); } /** @@ -166,8 +157,7 @@ public function expectKeysToBeFloat(array $keys, bool $acceptNull = false) */ public function expectKeyToBeString(string $key, bool $acceptNull = false) { - $this->validations[__FUNCTION__][] = func_get_args(); - return $this; + return $this->addValidation(__FUNCTION__, func_get_args()); } /** @@ -177,8 +167,7 @@ public function expectKeyToBeString(string $key, bool $acceptNull = false) */ public function expectKeysToBeString(array $keys, bool $acceptNull = false) { - $this->validations[__FUNCTION__][] = func_get_args(); - return $this; + return $this->addValidation(__FUNCTION__, func_get_args()); } /** @@ -188,8 +177,7 @@ public function expectKeysToBeString(array $keys, bool $acceptNull = false) */ public function expectKeyToBeBoolean(string $key, bool $acceptNull = false) { - $this->validations[__FUNCTION__][] = func_get_args(); - return $this; + return $this->addValidation(__FUNCTION__, func_get_args()); } /** @@ -199,8 +187,27 @@ public function expectKeyToBeBoolean(string $key, bool $acceptNull = false) */ public function expectKeysToBeBoolean(array $keys, bool $acceptNull = false) { - $this->validations[__FUNCTION__][] = func_get_args(); - return $this; + return $this->addValidation(__FUNCTION__, func_get_args()); + } + + /** + * @param string $key + * @param bool $acceptNull + * @return $this + */ + public function expectKeyToBeObject(string $key, bool $acceptNull = false) + { + return $this->addValidation(__FUNCTION__, func_get_args()); + } + + /** + * @param array $keys + * @param bool $acceptNull + * @return $this + */ + public function expectKeysToBeObject(array $keys, bool $acceptNull = false) + { + return $this->addValidation(__FUNCTION__, func_get_args()); } /** @@ -211,4 +218,15 @@ public function jsonSerialize() { return $this->getValidations(); } + + /** + * @param string $validationName + * @param array $validationArgs + * @return $this + */ + protected function addValidation(string $validationName, array $validationArgs) + { + $this->validations[$validationName][] = $validationArgs; + return $this; + } } diff --git a/src/ValidationInterface.php b/src/ValidationInterface.php index 5ad54b1..ed66f1e 100644 --- a/src/ValidationInterface.php +++ b/src/ValidationInterface.php @@ -21,4 +21,6 @@ public function expectKeyToBeString(string $key, bool $acceptNull = false); public function expectKeysToBeString(array $keys, bool $acceptNull = false); public function expectKeyToBeBoolean(string $key, bool $acceptNull = false); public function expectKeysToBeBoolean(array $keys, bool $acceptNull = false); + public function expectKeyToBeObject(string $key, bool $acceptNull = false); + public function expectKeysToBeObject(array $keys, bool $acceptNull = false); } diff --git a/src/Validator.php b/src/Validator.php index 65d9f48..27011c0 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -220,6 +220,33 @@ public function expectKeysToBeBoolean(array $array, array $keys, bool $acceptNul return true; } + /** + * @param array $array + * @param string $key + * @param bool $acceptNull + * @return bool + */ + public function expectKeyToBeObject(array $array, string $key, bool $acceptNull = false): bool + { + return $this->internalTypeValidation('is_object', $array, $key, $acceptNull); + } + + /** + * @param array $array + * @param array $keys + * @param bool $acceptNull + * @return bool + */ + public function expectKeysToBeObject(array $array, array $keys, bool $acceptNull = false): bool + { + foreach ($keys as $key) { + if ($this->expectKeyToBeObject($array, $key, $acceptNull) === false) { + return false; + } + } + return true; + } + /** * @param string $method * @param array $array @@ -235,4 +262,7 @@ private function internalTypeValidation(string $method, array $array, string $ke ($acceptNull && ($array[$key] !== null && !$method($array[$key]))) ); } + + + } diff --git a/src/ValidatorInterface.php b/src/ValidatorInterface.php index ced81c9..263c02a 100644 --- a/src/ValidatorInterface.php +++ b/src/ValidatorInterface.php @@ -21,4 +21,6 @@ public function expectKeyToBeFloat(array $array, string $key, bool $acceptNull = public function expectKeysToBeFloat(array $array, array $keys, bool $acceptNull = false): bool; public function expectKeyToBeBoolean(array $array, string $key, bool $acceptNull = false): bool; public function expectKeysToBeBoolean(array $array, array $keys, bool $acceptNull = false): bool; + public function expectKeyToBeObject(array $array, string $key, bool $acceptNull = false): bool; + public function expectKeysToBeObject(array $array, array $keys, bool $acceptNull = false): bool; } From e4f80f2bf0bee085d411bd99d32ddd0d06f0b9d7 Mon Sep 17 00:00:00 2001 From: Franck Date: Thu, 26 Mar 2020 12:21:39 -0400 Subject: [PATCH 33/46] updated --- CHANGELOG.md | 1 + tests/SchemaTest.php | 7 ++ tests/ValidationDefinitionTest.php | 20 ++++- tests/ValidationTest.php | 114 +++++++++++++++++++++++++---- tests/ValidatorTest.php | 16 ++++ 5 files changed, 139 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f8bb89..9c61ec6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ VERSION 3.1.0 Release date: ? - use SchemaInterface instead of Schema in StrictValidationFromSchema and ValidationFromSchema + - added validator expectKeyToBeObject() and expectKeysToBeObject() VERSION 3.0.0 ------------- diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php index 1edc875..beb4fe1 100644 --- a/tests/SchemaTest.php +++ b/tests/SchemaTest.php @@ -70,4 +70,11 @@ public function testExpectExactlyKeys($schema, $data) $this->assertTrue($schema->getName() === 'myName'); new StrictValidationFromDefinition($validationDefinition, $data); } + + public function testJsonSerialize() + { + $compiler = new SchemaCompiler(); + $schema = new Schema($compiler, ['schema'], 'myName'); + $this->assertTrue(json_encode($schema) === '["schema"]'); + } } diff --git a/tests/ValidationDefinitionTest.php b/tests/ValidationDefinitionTest.php index 033fd41..2323bdf 100644 --- a/tests/ValidationDefinitionTest.php +++ b/tests/ValidationDefinitionTest.php @@ -35,7 +35,8 @@ public function testGetDefinition2() $arrayDefinition = new ValidationDefinition(); $arrayDefinition - ->expectOnlyKeys(['title', 'content', 'description', 'id', 'number', 'amount', 'fields', 'isPrivate']) + ->expectOnlyKeys(['title', 'content', 'description', 'id', 'number', 'amount', 'fields', 'isPrivate', 'person']) + ->expectOnlyOneFromKeys(['title']) ->expectKeysToBeInteger(['id']) ->expectKeyToBeInteger('number') ->expectKeyToBeFloat('amount') @@ -47,10 +48,12 @@ public function testGetDefinition2() ->expectKeysToBeString(['title', 'content'], true) ->expectKeyToBeString('title', true) ->expectKeyToBeString('content', true) - ->expectKeyToBeString('description', false); + ->expectKeyToBeString('description', false) + ->expectKeyToBeObject('person', true) + ->expectKeysToBeObject(['person']); $validations = $arrayDefinition->getValidations(); -// print_r($validations); + $this->assertTrue(is_array($validations)); $this->assertTrue(isset($validations['expectKeyToBeString'][0][0])); $this->assertTrue($validations['expectKeyToBeString'][0][0] === 'title'); @@ -63,6 +66,10 @@ public function testGetDefinition2() $this->assertTrue(isset($validations['expectKeyToBeString'][2][0])); $this->assertTrue($validations['expectKeyToBeString'][2][0] === 'description'); $this->assertFalse($validations['expectKeyToBeString'][2][1]); + + $this->assertTrue(isset($validations['expectKeyToBeObject'][0][0])); + $this->assertTrue($validations['expectKeyToBeObject'][0][0] === 'person'); + $this->assertTrue($validations['expectKeyToBeObject'][0][1]); } public function testStrictArrayValidatorFromDefinition() @@ -130,4 +137,11 @@ public function testStrictArrayValidatorFromSchema() ]); } + + public function testJsonSerialize() + { + $definition = new ValidationDefinition(); + $definition->expectOnlyKeys(['title', 'content']); + $this->assertTrue(json_encode($definition) === '{"expectOnlyKeys":[[["title","content"]]]}'); + } } diff --git a/tests/ValidationTest.php b/tests/ValidationTest.php index ab9a3c5..59d5956 100644 --- a/tests/ValidationTest.php +++ b/tests/ValidationTest.php @@ -10,27 +10,109 @@ class ValidationTest extends TestCase { - public function testValidationPass() + public function dataProvider() { - $data = [ // data - 'field1' => [], - 'field2' => 'strong' - ]; - $validation = new Validation($data); + return [ + [ + + ['field1' => [], 'field2' => 'text', 'field3' => 1, 'field4'=> true, 'field5' => 1.2, 'field6' => new \StdClass()], // data + null, // dataName + [ // validation rules + 'expectOnlyKeys' => [ ['field1', 'field2', 'field3', 'field4', 'field5', 'field6'] ], + 'expectExactlyKeys' => [ ['field1', 'field2', 'field3', 'field4', 'field5', 'field6'] ], + 'expectKeyToBeArray' => ['field1'], + 'expectKeyToBeString' => ['field2'], + 'expectKeyToBeInteger' => ['field3'], + 'expectKeyToBeBoolean' => ['field4'], + 'expectKeyToBeFloat' => ['field5'], + 'expectKeyToBeObject' => ['field6'], - $this->assertFalse($validation->hasErrors()); + 'expectKeysToBeArray' => [['field1']], + 'expectKeysToBeString' => [['field2']], + 'expectKeysToBeInteger' => [['field3']], + 'expectKeysToBeBoolean' => [['field4']], + 'expectKeysToBeFloat' => [['field5']], + 'expectKeysToBeObject' => [['field6']], + ], + false, // has error(s) + 0, // number of error(s) + [] // error(s) messages + ], - $validation->expectKeyToBeArray('field1'); - $validation->expectKeyToBeString('field2'); + // this one will test types errors + [ + ['field6' => [], 'field5' => 'text', 'field4' => 1, 'field3'=> true, 'field2' => 1.2, 'field1' => new \StdClass()], // data + 'myValidation', // dataName + [ // validation rules + 'expectKeyToBeArray' => ['field1'], + 'expectKeyToBeString' => ['field2'], + 'expectKeyToBeInteger' => ['field3'], + 'expectKeyToBeBoolean' => ['field4'], + 'expectKeyToBeFloat' => ['field5'], + 'expectKeyToBeObject' => ['field6'], + ], + true, // has error(s) + 6, // number of error(s) + [ + '[myValidation] invalid type for key [field1], type array is expected', + '[myValidation] invalid type for key [field2], type string is expected', + '[myValidation] invalid type for key [field3], type integer is expected', + '[myValidation] invalid type for key [field4], type boolean is expected', + '[myValidation] invalid type for key [field5], type float is expected', + '[myValidation] invalid type for key [field6], type object is expected', + ] // error(s) messages + ], + + //this one will tests structure errors + [ + ['field6' => 'im here!', 'field7' => 'foo'], // data + null, // dataName + [ // validation rules + 'expectOnlyKeys' => [ ['field1', 'field2', 'field3', 'field4', 'field5', 'field6'] ], + 'expectExactlyKeys' => [ ['field1', 'field2', 'field3', 'field4', 'field5', 'field6'] ], + 'expectAtLeastKeys' => [ ['field1'] ], + 'expectOnlyOneFromKeys' => [ ['field1'] ], + 'expectNKeys' => [1] + ], + true, // has error(s) + 5, // number of error(s) + [ + 'invalid data, expected only keys [field1, field2, field3, field4, field5, field6], received [field6, field7]', + 'invalid data, expected exactly keys [field1, field2, field3, field4, field5, field6], received [field6, field7]', + 'invalid data, expected at least keys [field1], received [field6, field7]', + 'invalid data, expected only one of keys [field1], received [field6, field7]', + 'invalid data, expected 1 element, received 2 elements' + ] // error(s) messages + ], + ]; + } + + /** + * @dataProvider dataProvider + * @param array $data + * @param string|null $dataName + * @param array $validationMethods + * @param bool $hasErrors + * @param int $errorsCount + * @param array $errorsMsg + */ + public function testScenarios(array $data, ?string $dataName, array $validationMethods, bool $hasErrors, int $errorsCount, array $errorsMsg) + { + $validation = new Validation($data, $dataName); - $this->assertFalse($validation->hasErrors()); - $this->assertTrue($validation->getLastError() === null); + foreach ($validationMethods as $methodName => $methodArgs) { + call_user_func_array([$validation, $methodName], $methodArgs); + } - $validation->expectKeyToBeString('field1'); + $this->assertTrue($validation->hasErrors() === $hasErrors); + $this->assertTrue(count($validation->getErrors()) === $errorsCount); + $this->assertTrue($validation->getErrors() === $errorsMsg); - $this->assertTrue($validation->hasErrors()); - $this->assertTrue($validation->getLastError() === 'invalid type for key [field1], type string is expected'); - $this->assertTrue(is_array($validation->getErrors())); - $this->assertTrue(count($validation->getErrors()) == 1); + if ($errorsCount > 0) { + $errors = $validation->getErrors(); + $this->assertTrue($validation->getLastError() === $errors[array_key_last($errors)]); + } else { + $this->assertTrue($validation->getLastError() === null); + } } } diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php index c882877..9c41921 100644 --- a/tests/ValidatorTest.php +++ b/tests/ValidatorTest.php @@ -173,4 +173,20 @@ public function testIsString() $this->assertTrue($validation->expectKeysToBeString($data, ['item1', 'item2', 'item4'], true)); $this->assertFalse($validation->expectKeysToBeString($data, ['item1', 'item2', 'item3', 'item4'], true)); } + + public function testIsObject() + { + $data = [ + 'item1' => new stdClass(), + 'item2' => new DateTime(), + 'item3' => null, + ]; + $validation = new Validator(); + $this->assertTrue($validation->expectKeyToBeObject($data, 'item1', false)); + $this->assertFalse($validation->expectKeyToBeObject($data, 'item3', false)); + $this->assertTrue($validation->expectKeyToBeObject($data, 'item3', true)); + $this->assertTrue($validation->expectKeysToBeObject($data, ['item1', 'item2'], false)); + $this->assertFalse($validation->expectKeysToBeObject($data, ['item1', 'item2', 'item3'], false)); + $this->assertTrue($validation->expectKeysToBeObject($data, ['item1', 'item2', 'item3'], true)); + } } From 5256271e31e7bc52006c0b2bc84f076e8e91b50d Mon Sep 17 00:00:00 2001 From: Franck Date: Thu, 26 Mar 2020 14:13:39 -0400 Subject: [PATCH 34/46] updated --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c61ec6..4f13169 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ VERSION 3.1.0 ------------- -Release date: ? +Release date: 2020-03-26 - use SchemaInterface instead of Schema in StrictValidationFromSchema and ValidationFromSchema - added validator expectKeyToBeObject() and expectKeysToBeObject() From 24b14154e3586cc7f2d5f51e8d05f0f3f0c2bac6 Mon Sep 17 00:00:00 2001 From: Franck Date: Fri, 27 Mar 2020 11:45:56 -0400 Subject: [PATCH 35/46] updated --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0e4e5cd..05c3249 100644 --- a/README.md +++ b/README.md @@ -151,5 +151,7 @@ interface ValidationInterface public function expectKeysToBeString(array $keys, bool $acceptNull = false); public function expectKeyToBeBoolean(string $key, bool $acceptNull = false); public function expectKeysToBeBoolean(array $keys, bool $acceptNull = false); + public function expectKeyToBeObject(string $key, bool $acceptNull = false); + public function expectKeysToBeObject(array $keys, bool $acceptNull = false); } ``` \ No newline at end of file From 6612599f6fa358ba7a5fb100320c279c9ee79770 Mon Sep 17 00:00:00 2001 From: Franck Date: Fri, 27 Mar 2020 11:56:01 -0400 Subject: [PATCH 36/46] updated --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 05c3249..78d492e 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ you can afterward check the value of those keys with your business logic without ```php $validator = new Validator(); -if ($validator->expectExactlyKeys($array, $keys) === true) { +if ($validator->expectExactlyKeys($data, $keys) === true) { // ... } ``` @@ -47,7 +47,8 @@ $validation ->expectKeyToBeString('name'); if ($validation->hasErrors()) { - // die($validation->getLastError()) + // $lastError = $validation->getLastError(); + // $errors = $validation->getErrors(); } ``` From 88052d62c5aed21d3b259318b9283f879bcb42fb Mon Sep 17 00:00:00 2001 From: Franck Date: Fri, 27 Mar 2020 12:56:28 -0400 Subject: [PATCH 37/46] updated --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 78d492e..1232d3d 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ if ($validation->hasErrors()) { ```php -$validation = new StrictValidation($arrayToValidate); +$validation = new StrictValidation($data); // will throw an exception if any of tests below fail $validation From 3a040764f9128bda924e9767cb73a2d834d16f3c Mon Sep 17 00:00:00 2001 From: Franck Date: Fri, 27 Mar 2020 12:57:25 -0400 Subject: [PATCH 38/46] updated --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1232d3d..e6ae589 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ if ($validation->hasErrors()) { ## 5- Create a validation Schema for later usage -Schema is just another way to write validation definitions. This format is ideal when you want to store schemas in file (ex: json, php array file, yml, ...) +Schema is just another way to write validation definitions. This format is ideal when you want to store schemas in file (ex: json, php array file, yml, etc.) ```php From d158af55ebc59cd97cb8aa817f9b08cc0d2766a6 Mon Sep 17 00:00:00 2001 From: Franck Date: Fri, 27 Mar 2020 12:58:44 -0400 Subject: [PATCH 39/46] updated --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e6ae589..cf01ece 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This component help you to validate array structure by: - ensuring a data structure with expected keys requirements - preventing structure pollution by allowing only a set of keys -This is specially useful when dealing with json data request, before using the data, you must validate his content so +This is especially useful when dealing with json data request, before using the data, you must validate his content so you can afterward check the value of those keys with your business logic without worrying about the type or presence of any key value. # How to use From 5cab8a8749f2699771e1eb5f9784203f4427f1bb Mon Sep 17 00:00:00 2001 From: Matias Ibargoyen Date: Fri, 27 Mar 2020 22:15:24 +0000 Subject: [PATCH 40/46] added missing extension "ext-json" to composer.json --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c734826..ca57287 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,8 @@ "type": "library", "require": { "php": ">=7.2", - "symfony/polyfill-php73": "^1.14" + "symfony/polyfill-php73": "^1.14", + "ext-json": "*" }, "require-dev": { "phpunit/phpunit": "^9.0", From b4dc6f309997b752a21bc7e6653c0b5435ced67c Mon Sep 17 00:00:00 2001 From: Matias Ibargoyen Date: Fri, 27 Mar 2020 22:16:45 +0000 Subject: [PATCH 41/46] added "autoload-dev" to composer.json --- composer.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/composer.json b/composer.json index ca57287..7eae421 100644 --- a/composer.json +++ b/composer.json @@ -17,5 +17,10 @@ "psr-4": { "Peak\\ArrayValidation\\": "src/" } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } } } From 3da255ea538b1677186f8cb9e05064a6b146d579 Mon Sep 17 00:00:00 2001 From: Matias Ibargoyen Date: Fri, 27 Mar 2020 22:17:45 +0000 Subject: [PATCH 42/46] added 2 missing namespaces, and also use statements --- tests/StrictValidationTest.php | 2 ++ tests/ValidationTest.php | 5 +++-- tests/ValidatorTest.php | 4 ++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/StrictValidationTest.php b/tests/StrictValidationTest.php index 70d83a2..6287cd3 100644 --- a/tests/StrictValidationTest.php +++ b/tests/StrictValidationTest.php @@ -2,6 +2,8 @@ declare(strict_types=1); +namespace Tests; + use Peak\ArrayValidation\Exception\InvalidStructureException; use Peak\ArrayValidation\Exception\InvalidTypeException; use Peak\ArrayValidation\StrictValidation; diff --git a/tests/ValidationTest.php b/tests/ValidationTest.php index 59d5956..6dd1c05 100644 --- a/tests/ValidationTest.php +++ b/tests/ValidationTest.php @@ -6,6 +6,7 @@ use Peak\ArrayValidation\Validation; use \PHPUnit\Framework\TestCase; +use StdClass; class ValidationTest extends TestCase { @@ -15,7 +16,7 @@ public function dataProvider() return [ [ - ['field1' => [], 'field2' => 'text', 'field3' => 1, 'field4'=> true, 'field5' => 1.2, 'field6' => new \StdClass()], // data + ['field1' => [], 'field2' => 'text', 'field3' => 1, 'field4'=> true, 'field5' => 1.2, 'field6' => new StdClass()], // data null, // dataName [ // validation rules 'expectOnlyKeys' => [ ['field1', 'field2', 'field3', 'field4', 'field5', 'field6'] ], @@ -41,7 +42,7 @@ public function dataProvider() // this one will test types errors [ - ['field6' => [], 'field5' => 'text', 'field4' => 1, 'field3'=> true, 'field2' => 1.2, 'field1' => new \StdClass()], // data + ['field6' => [], 'field5' => 'text', 'field4' => 1, 'field3'=> true, 'field2' => 1.2, 'field1' => new StdClass()], // data 'myValidation', // dataName [ // validation rules 'expectKeyToBeArray' => ['field1'], diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php index 9c41921..6d79dba 100644 --- a/tests/ValidatorTest.php +++ b/tests/ValidatorTest.php @@ -2,8 +2,12 @@ declare(strict_types=1); +namespace Tests; + +use DateTime; use \PHPUnit\Framework\TestCase; use \Peak\ArrayValidation\Validator; +use stdClass; class ValidatorTest extends TestCase { From 68a289eb0b661b347b3bd950e0d5990f1dbc8fb7 Mon Sep 17 00:00:00 2001 From: Franck Date: Fri, 27 Mar 2020 19:28:41 -0400 Subject: [PATCH 43/46] updated --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f13169..9cf72ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +VERSION 3.1.1 +------------- +Release date: 2020-03-27 + + - added "autoload-dev" to composer.json (thank you @ordago) + - added missing extension "ext-json" to composer.json (thank you @ordago) + VERSION 3.1.0 ------------- Release date: 2020-03-26 From ec02b520d7cadadfaae9e19449a42f868c2d2934 Mon Sep 17 00:00:00 2001 From: Franck Date: Thu, 2 Apr 2020 12:33:44 -0400 Subject: [PATCH 44/46] added --- src/ValidationBuilder.php | 56 +++++++++++++++++++++++++ tests/ValidationBuilderTest.php | 72 +++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 src/ValidationBuilder.php create mode 100644 tests/ValidationBuilderTest.php diff --git a/src/ValidationBuilder.php b/src/ValidationBuilder.php new file mode 100644 index 0000000..0b77d75 --- /dev/null +++ b/src/ValidationBuilder.php @@ -0,0 +1,56 @@ +errors = $validation->getErrors(); + $this->lastError = $validation->getLastError(); + return !$validation->hasErrors(); + } + + /** + * @param array $data + * @throw InvalidStructureException + * @throw InvalidTypeException + */ + public function strictValidate(array $data): void + { + new StrictValidationFromDefinition($this, $data); + } + + /** + * @return array + */ + public function getErrors(): array + { + return $this->errors; + } + + /** + * @return string|null + */ + public function getLastError(): ?string + { + return $this->lastError; + } +} diff --git a/tests/ValidationBuilderTest.php b/tests/ValidationBuilderTest.php new file mode 100644 index 0000000..abcbdf9 --- /dev/null +++ b/tests/ValidationBuilderTest.php @@ -0,0 +1,72 @@ +expectNKeys(2) + ->expectAtLeastKeys(['title', 'content']) + ->expectExactlyKeys(['title', 'content']) + ->expectOnlyKeys(['title', 'content']) + ->expectKeysToBeString(['title', 'content'], true); + + $data = [ + 'title' => 'test', + 'content' => 'test', + ]; + + $validationPass = $validation->validate($data); + $this->assertTrue($validationPass); + $this->assertTrue($validation->getErrors() === []); + $this->assertTrue($validation->getLastError() === null); + } + + public function testStrictValidate() + { + $validation = new ValidationBuilder(); + $validation + ->expectNKeys(2) + ->expectAtLeastKeys(['title', 'content']) + ->expectExactlyKeys(['title', 'content']) + ->expectOnlyKeys(['title', 'content']) + ->expectKeysToBeString(['title', 'content'], true); + + $data = [ + 'title' => 'test', + 'content' => 'test', + ]; + + $validation->strictValidate($data); + $this->assertTrue(true); + } + + public function testStrictValidateException() + { + $validation = new ValidationBuilder(); + $validation + ->expectNKeys(2) + ->expectAtLeastKeys(['title', 'content']) + ->expectExactlyKeys(['title', 'content']) + ->expectOnlyKeys(['title', 'content']) + ->expectKeysToBeString(['title', 'content'], true); + + $data = [ + 'title' => 1, + 'content' => 'test', + ]; + + $this->expectException(ArrayValidationExceptionInterface::class); + $validation->strictValidate($data); + } + +} From 7584822297a0b9809f187823ad5a0aef16546526 Mon Sep 17 00:00:00 2001 From: Franck Date: Thu, 2 Apr 2020 12:33:51 -0400 Subject: [PATCH 45/46] updated --- CHANGELOG.md | 6 ++++++ README.md | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cf72ed..65b07f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +VERSION 3.2.0 +------------- +Release date: 2020-04-02 + + - added ValidationBuilder + VERSION 3.1.1 ------------- Release date: 2020-03-27 diff --git a/README.md b/README.md index cf01ece..42ac3f7 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,28 @@ new StrictValidationFromDefinition($validationDefinition, $arrayToValidate); new StrictValidationFromSchema($schema, $arrayToValidate); ``` +## 8- Validation and Strict Validation with ValidationBuilder + + +```php +$validation = new ValidationBuilder(); +$validation + ->expectOnlyKeys(['title', 'content', 'description']) + ->expectAtLeastKeys(['title', 'content']) + ->expectKeysToBeString(['title', 'content', 'description']); + +if ($validation->validate($data) === false) { + // $validation->getErrors(); + // $validation->getLastError(); +} +``` + +and with strict validation: + +```php +// will throw an exception if any of tests fail +$validation->strictValidate($data); +``` # Validation methods ```php From 8ee7ea80474183d551c5c355b643304deea770ec Mon Sep 17 00:00:00 2001 From: Franck Date: Thu, 2 Apr 2020 12:35:13 -0400 Subject: [PATCH 46/46] updated --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 42ac3f7..f06b3e5 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ This is especially useful when dealing with json data request, before using the you can afterward check the value of those keys with your business logic without worrying about the type or presence of any key value. # How to use -##### 7 Usages +##### 8 Usages ## 1- General validation "à la carte" (stateless) @@ -133,7 +133,6 @@ new StrictValidationFromSchema($schema, $arrayToValidate); ## 8- Validation and Strict Validation with ValidationBuilder - ```php $validation = new ValidationBuilder(); $validation