diff --git a/CHANGELOG.md b/CHANGELOG.md
index f76565b..65b07f4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,55 @@
+VERSION 3.2.0
+-------------
+Release date: 2020-04-02
+
+ - added ValidationBuilder
+
+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
+
+ - use SchemaInterface instead of Schema in StrictValidationFromSchema and ValidationFromSchema
+ - added validator expectKeyToBeObject() and expectKeysToBeObject()
+
+VERSION 3.0.0
+-------------
+Release date: 2020-03-21
+
+ - 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
+ - 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
+-------------
+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
+
+ - added methods expectKeysToBeArray(), expectKeysToBeBoolean() and expectKeysToBeFloat() to StrictArrayValidator
+ - transformed ArrayValidationException to 2 more specific exceptions: InvalidStructureException and InvalidTypeException
+ - [BC] simplified and reordered constructor params of StrictArrayValidator
+
VERSION 1.0.0
-------------
Release date: 2020-02-07
diff --git a/README.md b/README.md
index 6aee4cf..f06b3e5 100644
--- a/README.md
+++ b/README.md
@@ -8,4 +8,172 @@
composer require peak/array-validation
-## Usage
\ No newline at end of file
+## What is this?
+
+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 by allowing only a set of keys
+
+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
+##### 8 Usages
+
+## 1- General validation "à la carte" (stateless)
+
+```php
+$validator = new Validator();
+
+if ($validator->expectExactlyKeys($data, $keys) === true) {
+ // ...
+}
+```
+
+## 2- Validation with fluent interface (stateful)
+
+```php
+$data = [ // data
+ 'tags' => [],
+ 'name' => 'foobar'
+];
+$validation = new Validation($data);
+
+$validation
+ ->expectExactlyKeys(['tags', 'name'])
+ ->expectKeyToBeArray('tags');
+ ->expectKeyToBeString('name');
+
+if ($validation->hasErrors()) {
+ // $lastError = $validation->getLastError();
+ // $errors = $validation->getErrors();
+}
+```
+
+## 3- Strict validation with fluent interface (stateful)
+
+```php
+
+$validation = new StrictValidation($data);
+
+// 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();
+$vDef
+ ->expectOnlyKeys(['title', 'content', 'description'])
+ ->expectAtLeastKeys(['title', 'content'])
+ ->expectKeysToBeString(['title', 'content', 'description']);
+
+$validation = new ValidationFromDefinition($vDef, $data);
+
+if ($validation->hasErrors()) {
+ // $validation->getErrors();
+}
+
+```
+
+## 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, etc.)
+
+```php
+
+$mySchema = [
+ 'title' => [
+ 'type' => 'string',
+ 'required' => true
+ ],
+ 'content' => [
+ 'type' => 'string',
+ 'nullable' => true,
+ ],
+];
+
+$schema = new Schema(new SchemaCompiler(), $mySchema, 'mySchemaName');
+
+$validation = new ValidationFromSchema($schema, $data);
+
+if ($validation->hasErrors()) {
+ // $validation->getErrors();
+}
+```
+
+
+
+## 6- Strict validation using ValidationDefinition
+
+```php
+// all validation definitions are executed at object creation and an exception is thrown if any of tests failed
+new StrictValidationFromDefinition($validationDefinition, $arrayToValidate);
+```
+
+## 7- Strict validation using Schema
+
+```php
+// all validation definitions are executed at object creation and an exception is thrown if any of tests failed
+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
+
+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);
+ public function expectKeyToBeObject(string $key, bool $acceptNull = false);
+ public function expectKeysToBeObject(array $keys, bool $acceptNull = false);
+}
+```
\ No newline at end of file
diff --git a/composer.json b/composer.json
index 0e01e86..7eae421 100644
--- a/composer.json
+++ b/composer.json
@@ -5,15 +5,22 @@
"license": "MIT",
"type": "library",
"require": {
- "php": ">=7.2"
+ "php": ">=7.2",
+ "symfony/polyfill-php73": "^1.14",
+ "ext-json": "*"
},
"require-dev": {
- "phpunit/phpunit": "^8.2",
- "phpstan/phpstan": "^0.11"
+ "phpunit/phpunit": "^9.0",
+ "phpstan/phpstan": "^0.12"
},
"autoload": {
"psr-4": {
"Peak\\ArrayValidation\\": "src/"
}
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Tests\\": "tests/"
+ }
}
-}
\ No newline at end of file
+}
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
diff --git a/phpunit.xml b/phpunit.xml
index 2d23c38..fcfbb9e 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,5 +1,8 @@
@@ -18,5 +21,4 @@
-
\ No newline at end of file
diff --git a/src/AbstractValidation.php b/src/AbstractValidation.php
new file mode 100644
index 0000000..38b5846
--- /dev/null
+++ b/src/AbstractValidation.php
@@ -0,0 +1,69 @@
+
+ */
+ protected $errors = [];
+
+ /**
+ * @var array
+ */
+ protected $errorMessages = [
+ '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',
+ ];
+
+ /**
+ * @param string $type
+ * @param array $context
+ * @return string
+ */
+ protected function getErrorMessage(string $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'))) {
+ $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;
+ }
+}
diff --git a/src/Exception/ArrayValidationException.php b/src/Exception/ArrayValidationException.php
deleted file mode 100644
index d5f7228..0000000
--- a/src/Exception/ArrayValidationException.php
+++ /dev/null
@@ -1,7 +0,0 @@
-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
+ * @return array
+ */
+ public function jsonSerialize()
+ {
+ return $this->schema;
+ }
+}
diff --git a/src/SchemaCompiler.php b/src/SchemaCompiler.php
new file mode 100644
index 0000000..c04cfd1
--- /dev/null
+++ b/src/SchemaCompiler.php
@@ -0,0 +1,81 @@
+ $fieldDefinition) {
+ if (is_string($fieldName) && is_array($fieldDefinition)) {
+ $this->handleFieldDefinition($fieldName, $fieldDefinition, $definition);
+ }
+ }
+
+ // handle "required"
+ $this->handleRequiredFields($schema, $definition);
+
+ return $definition;
+ }
+
+ /**
+ * @param string $fieldName
+ * @param array $fieldDefinition
+ * @param ValidationDefinition $definition
+ * @throws Exception\InvalidStructureException
+ * @throws Exception\InvalidTypeException
+ */
+ private function handleFieldDefinition(string $fieldName, array $fieldDefinition, ValidationDefinition $definition): void
+ {
+ (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): void
+ {
+ $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);
+ }
+ }
+}
diff --git a/src/SchemaCompilerInterface.php b/src/SchemaCompilerInterface.php
new file mode 100644
index 0000000..84e5f93
--- /dev/null
+++ b/src/SchemaCompilerInterface.php
@@ -0,0 +1,16 @@
+ '{dataName}invalid data, expected {expectedType} [{keysExpected}], received [{keysReceived}]',
- 'type' => '{dataName}invalid type for key [{key}], type {expectedType} is expected',
- ];
-
- /**
- * ArrayValidationHelper constructor.
- * @param ArrayValidation $arrayValidation
- * @param array $data
- * @param string|null $dataName
- */
- public function __construct(ArrayValidation $arrayValidation, array $data, string $dataName = null)
- {
- $this->arrayValidation = $arrayValidation;
- $this->data = $data;
- $this->dataName = $dataName;
- }
-
- /**
- * @param array $keysName
- * @return $this
- * @throws ArrayValidationException
- */
- public function expectExactlyKeys(array $keysName)
- {
- if ($this->arrayValidation->expectExactlyKeys($this->data, $keysName) === false) {
- $keysReceived = array_keys($this->data);
- natsort($keysName);
- natsort($keysReceived);
- $message = $this->getErrorMessage('expected', [
- 'expectedType' => 'exactly keys',
- 'keysExpected' => implode(', ', $keysName),
- 'keysReceived' => implode(', ', $keysReceived)
- ]);
- throw new ArrayValidationException($message);
- }
- return $this;
- }
-
- /**
- * @param array $keysName
- * @return $this
- * @throws ArrayValidationException
- */
- public function expectAtLeastKeys(array $keysName)
- {
- if ($this->arrayValidation->expectAtLeastKeys($this->data, $keysName) === false) {
- $keysReceived = array_keys($this->data);
- natsort($keysName);
- natsort($keysReceived);
- $message = $this->getErrorMessage('expected', [
- 'expectedType' => 'at least keys',
- 'keysExpected' => implode(', ', $keysName),
- 'keysReceived' => implode(', ', $keysReceived)
- ]);
- throw new ArrayValidationException($message);
- }
- return $this;
- }
-
- /**
- * @param array $keysName
- * @return $this
- * @throws ArrayValidationException
- */
- public function expectOnlyKeys(array $keysName)
- {
- if ($this->arrayValidation->expectOnlyKeys($this->data, $keysName) === false) {
- $keysReceived = array_keys($this->data);
- natsort($keysName);
- natsort($keysReceived);
- $message = $this->getErrorMessage('expected', [
- 'expectedType' => 'only keys',
- 'keysExpected' => implode(', ', $keysName),
- 'keysReceived' => implode(', ', $keysReceived)
- ]);
- throw new ArrayValidationException($message);
- }
- return $this;
- }
-
- /**
- * @param string $key
- * @param bool $acceptNull
- * @return $this
- * @throws ArrayValidationException
- */
- public function expectKeyToBeArray(string $key, bool $acceptNull = false)
- {
- if (array_key_exists($key, $this->data) && $this->arrayValidation->expectKeyToBeArray($this->data, $key, $acceptNull) === false) {
- $message = $this->getErrorMessage('type', [
- 'key' => $key,
- 'expectedType' => 'array',
- ]);
- throw new ArrayValidationException($message);
- }
- return $this;
- }
-
- /**
- * @param string $key
- * @param bool $acceptNull
- * @return $this
- * @throws ArrayValidationException
- */
- public function expectKeyToBeInteger(string $key, bool $acceptNull = false)
- {
- if (array_key_exists($key, $this->data) && $this->arrayValidation->expectKeyToBeInteger($this->data, $key, $acceptNull) === false) {
- $message = $this->getErrorMessage('type', [
- 'key' => $key,
- 'expectedType' => 'integer',
- ]);
- throw new ArrayValidationException($message);
- }
- return $this;
- }
-
- /**
- * @param string $key
- * @param bool $acceptNull
- * @return $this
- * @throws ArrayValidationException
- */
- public function expectKeyToBeFloat(string $key, bool $acceptNull = false)
- {
- if (array_key_exists($key, $this->data) && $this->arrayValidation->expectKeyToBeFloat($this->data, $key, $acceptNull) === false) {
- $message = $this->getErrorMessage('type', [
- 'key' => $key,
- 'expectedType' => 'float',
- ]);
- throw new ArrayValidationException($message);
- }
- return $this;
- }
-
- /**
- * @param string $key
- * @param bool $acceptNull
- * @return $this
- * @throws ArrayValidationException
- */
- public function expectKeyToBeString(string $key, bool $acceptNull = false)
- {
- if (array_key_exists($key, $this->data) && $this->arrayValidation->expectKeyToBeString($this->data, $key, $acceptNull) === false) {
- $message = $this->getErrorMessage('type', [
- 'key' => $key,
- 'expectedType' => 'string',
- ]);
- throw new ArrayValidationException($message);
- }
- return $this;
- }
-
- /**
- * @param array $keys
- * @param bool $acceptNull
- * @return $this
- * @throws ArrayValidationException
- */
- 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 ArrayValidationException
- */
- public function expectKeysToBeString(array $keys, bool $acceptNull = false)
- {
- foreach ($keys as $key) {
- $this->expectKeyToBeString($key, $acceptNull);
- }
- return $this;
- }
-
- /**
- * @param string $key
- * @param bool $acceptNull
- * @return $this
- * @throws ArrayValidationException
- */
- public function expectKeyToBeBoolean(string $key, bool $acceptNull = false)
- {
- if (array_key_exists($key, $this->data) && $this->arrayValidation->expectKeyToBeBoolean($this->data, $key, $acceptNull) === false) {
- $message = $this->getErrorMessage('type', [
- 'key' => $key,
- 'expectedType' => 'boolean',
- ]);
- throw new ArrayValidationException($message);
- }
- 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/StrictValidation.php b/src/StrictValidation.php
new file mode 100644
index 0000000..98465de
--- /dev/null
+++ b/src/StrictValidation.php
@@ -0,0 +1,220 @@
+checkStructureErrors();
+ }
+
+ /**
+ * @param array $keys
+ * @return $this
+ * @throws InvalidStructureException
+ */
+ public function expectAtLeastKeys(array $keys)
+ {
+ parent::expectAtLeastKeys($keys);
+ return $this->checkStructureErrors();
+ }
+
+ /**
+ * @param array $keys
+ * @return $this
+ * @throws InvalidStructureException
+ */
+ public function expectOnlyKeys(array $keys)
+ {
+ parent::expectOnlyKeys($keys);
+ return $this->checkStructureErrors();
+ }
+
+ /**
+ * @param array $keys
+ * @return $this
+ * @throws InvalidStructureException
+ */
+ public function expectOnlyOneFromKeys(array $keys)
+ {
+ parent::expectOnlyOneFromKeys($keys);
+ return $this->checkStructureErrors();
+ }
+
+ /**
+ * @param int $n
+ * @return $this
+ * @throws InvalidStructureException
+ */
+ public function expectNKeys(int $n)
+ {
+ parent::expectNKeys($n);
+ return $this->checkStructureErrors();
+ }
+
+ /**
+ * @param string $key
+ * @param bool $acceptNull
+ * @return $this
+ * @throws InvalidTypeException
+ */
+ public function expectKeyToBeArray(string $key, bool $acceptNull = false)
+ {
+ parent::expectKeyToBeArray($key, $acceptNull);
+ return $this->checkTypeErrors();
+ }
+
+ /**
+ * @param string $key
+ * @param bool $acceptNull
+ * @return $this
+ * @throws InvalidTypeException
+ */
+ public function expectKeyToBeInteger(string $key, bool $acceptNull = false)
+ {
+ parent::expectKeyToBeInteger($key, $acceptNull);
+ return $this->checkTypeErrors();
+ }
+
+ /**
+ * @param string $key
+ * @param bool $acceptNull
+ * @return $this
+ * @throws InvalidTypeException
+ */
+ public function expectKeyToBeFloat(string $key, bool $acceptNull = false)
+ {
+ parent::expectKeyToBeFloat($key, $acceptNull);
+ return $this->checkTypeErrors();
+ }
+
+ /**
+ * @param string $key
+ * @param bool $acceptNull
+ * @return $this
+ * @throws InvalidTypeException
+ */
+ public function expectKeyToBeString(string $key, bool $acceptNull = false)
+ {
+ parent::expectKeyToBeString($key, $acceptNull);
+ return $this->checkTypeErrors();
+ }
+
+ /**
+ * @param string $key
+ * @param bool $acceptNull
+ * @return $this
+ * @throws InvalidTypeException
+ */
+ 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() ?? '');
+ }
+ return $this;
+ }
+}
diff --git a/src/StrictValidationFromDefinition.php b/src/StrictValidationFromDefinition.php
new file mode 100644
index 0000000..cb41f38
--- /dev/null
+++ b/src/StrictValidationFromDefinition.php
@@ -0,0 +1,36 @@
+validationDefinition = $validationDefinition;
+ parent::__construct($data, $dataName, $arrayValidation);
+
+ (new ValidationDefinitionExecutor())->execute(
+ $validationDefinition,
+ $this
+ );
+ }
+}
diff --git a/src/StrictValidationFromSchema.php b/src/StrictValidationFromSchema.php
new file mode 100644
index 0000000..5e705ce
--- /dev/null
+++ b/src/StrictValidationFromSchema.php
@@ -0,0 +1,31 @@
+getName(), $arrayValidation);
+
+ $validationDefinition = $schema->compile();
+
+ (new ValidationDefinitionExecutor())->execute(
+ $validationDefinition,
+ $this
+ );
+ }
+}
diff --git a/src/Validation.php b/src/Validation.php
new file mode 100644
index 0000000..98b7396
--- /dev/null
+++ b/src/Validation.php
@@ -0,0 +1,331 @@
+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);
+ $this->errors[] = $this->getErrorMessage('expected', [
+ 'expectedType' => 'exactly keys',
+ 'keysExpected' => implode(', ', $keys),
+ 'keysReceived' => implode(', ', $keysReceived)
+ ]);
+ }
+ 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);
+ $this->errors[] = $this->getErrorMessage('expected', [
+ 'expectedType' => 'at least keys',
+ 'keysExpected' => implode(', ', $keys),
+ 'keysReceived' => implode(', ', $keysReceived)
+ ]);
+ }
+ 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);
+ $this->errors[] = $this->getErrorMessage('expected', [
+ 'expectedType' => 'only keys',
+ 'keysExpected' => implode(', ', $keys),
+ 'keysReceived' => implode(', ', $keysReceived)
+ ]);
+ }
+ 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);
+ $this->errors[] = $this->getErrorMessage('expected', [
+ 'expectedType' => 'only one of keys',
+ 'keysExpected' => implode(', ', $keys),
+ 'keysReceived' => implode(', ', $keysReceived)
+ ]);
+ }
+ 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);
+ $this->errors[] = $this->getErrorMessage('expectedN', [
+ 'expectedType' => 'only N keys',
+ 'nExpected' => $n,
+ 'nExpectedPlural' => $n > 1 ? 's' : '',
+ 'nReceivedPlural' => count($keysReceived) > 1 ? 's' : '',
+ 'nReceived' => count($keysReceived)
+ ]);
+ }
+ 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) {
+ $this->errors[] = $this->getErrorMessage('type', [
+ 'key' => $key,
+ 'expectedType' => 'array',
+ ]);
+ }
+ 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) {
+ $this->errors[] = $this->getErrorMessage('type', [
+ 'key' => $key,
+ 'expectedType' => 'integer',
+ ]);
+ }
+ 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) {
+ $this->errors[] = $this->getErrorMessage('type', [
+ 'key' => $key,
+ 'expectedType' => 'float',
+ ]);
+ }
+ 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) {
+ $this->errors[] = $this->getErrorMessage('type', [
+ 'key' => $key,
+ 'expectedType' => 'string',
+ ]);
+ }
+ 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) {
+ $this->errors[] = $this->getErrorMessage('type', [
+ 'key' => $key,
+ 'expectedType' => 'boolean',
+ ]);
+ }
+ 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;
+ }
+
+ /**
+ * @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;
+ }
+
+ /**
+ * @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
+ */
+ protected function getExceptionDataName(): ?string
+ {
+ if (isset($this->dataName)) {
+ return '['. $this->dataName.'] ';
+ }
+ return null;
+ }
+
+ /**
+ * @param string $type
+ * @param array $context
+ * @return 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/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/src/ValidationDefinition.php b/src/ValidationDefinition.php
new file mode 100644
index 0000000..e1018c7
--- /dev/null
+++ b/src/ValidationDefinition.php
@@ -0,0 +1,232 @@
+ [],
+ 'expectOnlyOneFromKeys' => [],
+ 'expectAtLeastKeys' => [],
+ 'expectOnlyKeys' => [],
+ 'expectNKeys' => [],
+ 'expectKeyToBeArray' => [],
+ 'expectKeysToBeArray' => [],
+ 'expectKeyToBeInteger' => [],
+ 'expectKeysToBeInteger' => [],
+ 'expectKeyToBeFloat' => [],
+ 'expectKeysToBeFloat' => [],
+ 'expectKeyToBeString' => [],
+ 'expectKeysToBeString' => [],
+ 'expectKeyToBeBoolean' => [],
+ 'expectKeysToBeBoolean' => [],
+ 'expectKeyToBeObject' => [],
+ 'expectKeysToBeObject' => [],
+ ];
+
+ /**
+ * @return array
+ */
+ public function getValidations(): array
+ {
+ $validations = [];
+ foreach ($this->validations as $name => $args) {
+ if (!empty($args)) {
+ $validations[$name] = $args;
+ }
+ }
+ return $validations;
+ }
+
+ /**
+ * @param array $keys
+ * @return $this
+ */
+ public function expectExactlyKeys(array $keys)
+ {
+ return $this->addValidation(__FUNCTION__, func_get_args());
+ }
+
+ /**
+ * @param array $keys
+ * @return $this
+ */
+ public function expectOnlyOneFromKeys(array $keys)
+ {
+ return $this->addValidation(__FUNCTION__, func_get_args());
+ }
+
+ /**
+ * @param array $keys
+ * @return $this
+ */
+ public function expectAtLeastKeys(array $keys)
+ {
+ return $this->addValidation(__FUNCTION__, func_get_args());
+ }
+
+ /**
+ * @param array $keys
+ * @return $this
+ */
+ public function expectOnlyKeys(array $keys)
+ {
+ return $this->addValidation(__FUNCTION__, func_get_args());
+ }
+
+ /**
+ * @param int $n
+ * @return $this
+ */
+ public function expectNKeys(int $n)
+ {
+ return $this->addValidation(__FUNCTION__, func_get_args());
+ }
+
+ /**
+ * @param string $key
+ * @param bool $acceptNull
+ * @return $this
+ */
+ public function expectKeyToBeArray(string $key, bool $acceptNull = false)
+ {
+ return $this->addValidation(__FUNCTION__, func_get_args());
+ }
+
+ /**
+ * @param array $keys
+ * @param bool $acceptNull
+ * @return $this
+ */
+ public function expectKeysToBeArray(array $keys, bool $acceptNull = false)
+ {
+ return $this->addValidation(__FUNCTION__, func_get_args());
+ }
+
+ /**
+ * @param string $key
+ * @param bool $acceptNull
+ * @return $this
+ */
+ public function expectKeyToBeInteger(string $key, bool $acceptNull = false)
+ {
+ return $this->addValidation(__FUNCTION__, func_get_args());
+ }
+
+ /**
+ * @param array $keys
+ * @param bool $acceptNull
+ * @return $this
+ */
+ public function expectKeysToBeInteger(array $keys, bool $acceptNull = false)
+ {
+ return $this->addValidation(__FUNCTION__, func_get_args());
+ }
+
+ /**
+ * @param string $key
+ * @param bool $acceptNull
+ * @return $this
+ */
+ public function expectKeyToBeFloat(string $key, bool $acceptNull = false)
+ {
+ return $this->addValidation(__FUNCTION__, func_get_args());
+ }
+
+ /**
+ * @param array $keys
+ * @param bool $acceptNull
+ * @return $this
+ */
+ public function expectKeysToBeFloat(array $keys, bool $acceptNull = false)
+ {
+ return $this->addValidation(__FUNCTION__, func_get_args());
+ }
+
+ /**
+ * @param string $key
+ * @param bool $acceptNull
+ * @return $this
+ */
+ public function expectKeyToBeString(string $key, bool $acceptNull = false)
+ {
+ return $this->addValidation(__FUNCTION__, func_get_args());
+ }
+
+ /**
+ * @param array $keys
+ * @param bool $acceptNull
+ * @return $this
+ */
+ public function expectKeysToBeString(array $keys, bool $acceptNull = false)
+ {
+ return $this->addValidation(__FUNCTION__, func_get_args());
+ }
+
+ /**
+ * @param string $key
+ * @param bool $acceptNull
+ * @return $this
+ */
+ public function expectKeyToBeBoolean(string $key, bool $acceptNull = false)
+ {
+ return $this->addValidation(__FUNCTION__, func_get_args());
+ }
+
+ /**
+ * @param array $keys
+ * @param bool $acceptNull
+ * @return $this
+ */
+ public function expectKeysToBeBoolean(array $keys, bool $acceptNull = false)
+ {
+ 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());
+ }
+
+ /**
+ * @inheritDoc
+ * @return array
+ */
+ 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/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
diff --git a/src/ValidationFromDefinition.php b/src/ValidationFromDefinition.php
new file mode 100644
index 0000000..ddc791b
--- /dev/null
+++ b/src/ValidationFromDefinition.php
@@ -0,0 +1,35 @@
+validationDefinition = $validationDefinition;
+ parent::__construct($data, $dataName, $arrayValidation);
+
+ (new ValidationDefinitionExecutor())->execute(
+ $validationDefinition,
+ $this
+ );
+ }
+}
diff --git a/src/ValidationFromSchema.php b/src/ValidationFromSchema.php
new file mode 100644
index 0000000..2330da0
--- /dev/null
+++ b/src/ValidationFromSchema.php
@@ -0,0 +1,30 @@
+getName(), $arrayValidation);
+
+ $validationDefinition = $schema->compile();
+ (new ValidationDefinitionExecutor())->execute(
+ $validationDefinition,
+ $this
+ );
+ }
+}
diff --git a/src/ValidationInterface.php b/src/ValidationInterface.php
new file mode 100644
index 0000000..ed66f1e
--- /dev/null
+++ b/src/ValidationInterface.php
@@ -0,0 +1,26 @@
+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,49 @@ 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 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
@@ -155,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
new file mode 100644
index 0000000..263c02a
--- /dev/null
+++ b/src/ValidatorInterface.php
@@ -0,0 +1,26 @@
+ 'foo',
- 'content' => 'foobar',
- ];
-
- private $data2 = [
- 'title' => 'foo'
- ];
-
- private $data3 = [
- 'title' => null,
- 'views' => 45,
- 'comments' => []
- ];
-
- public function testExpectExactlyKeys()
- {
- $validation = new ArrayValidation();
- $this->assertTrue($validation->expectExactlyKeys($this->data1, ['title', 'content']));
- $this->assertFalse($validation->expectExactlyKeys($this->data1, ['title', 'content', 'extra']));
- $this->assertFalse($validation->expectExactlyKeys($this->data1, ['title']));
- $this->assertFalse($validation->expectExactlyKeys($this->data1, ['extra']));
- $this->assertFalse($validation->expectExactlyKeys($this->data1, []));
- }
-
- public function testExpectOnlyOneFromKeys()
- {
- $validation = new ArrayValidation();
- $this->assertTrue($validation->expectOnlyOneFromKeys($this->data2, ['title', 'extra']));
- $this->assertTrue($validation->expectOnlyOneFromKeys($this->data2, ['title']));
- $this->assertFalse($validation->expectOnlyOneFromKeys($this->data2, ['extra']));
- }
-
- public function testExpectAtLeastKeys()
- {
- $validation = new ArrayValidation();
- $this->assertTrue($validation->expectAtLeastKeys($this->data1, ['title']));
- $this->assertTrue($validation->expectAtLeastKeys($this->data1, ['content']));
- $this->assertTrue($validation->expectAtLeastKeys($this->data1, ['title', 'content']));
- $this->assertFalse($validation->expectAtLeastKeys($this->data1, ['extra']));
- $this->assertFalse($validation->expectAtLeastKeys($this->data1, ['title', 'content', 'extra']));
- }
-
- public function testExpectOnlyKeys()
- {
- $validation = new ArrayValidation();
- $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()
- {
- $validation = new ArrayValidation();
- $this->assertTrue($validation->expectXElement($this->data1, 2));
- $this->assertFalse($validation->expectXElement($this->data1, 1));
- }
-
- public function testIsArray()
- {
- $validation = new ArrayValidation();
- $this->assertTrue($validation->expectKeyToBeArray($this->data3, 'comments', false));
- $this->assertFalse($validation->expectKeyToBeArray($this->data3, 'title', false));
- $this->assertTrue($validation->expectKeyToBeArray($this->data3, 'title', true));
- }
-}
diff --git a/tests/SchemaCompilerTest.php b/tests/SchemaCompilerTest.php
new file mode 100644
index 0000000..3516d62
--- /dev/null
+++ b/tests/SchemaCompilerTest.php
@@ -0,0 +1,72 @@
+ '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..beb4fe1
--- /dev/null
+++ b/tests/SchemaTest.php
@@ -0,0 +1,80 @@
+ [
+ '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);
+ }
+
+ public function testJsonSerialize()
+ {
+ $compiler = new SchemaCompiler();
+ $schema = new Schema($compiler, ['schema'], 'myName');
+ $this->assertTrue(json_encode($schema) === '["schema"]');
+ }
+}
diff --git a/tests/StrictArrayValidatorTest.php b/tests/StrictArrayValidatorTest.php
deleted file mode 100644
index 07b92d8..0000000
--- a/tests/StrictArrayValidatorTest.php
+++ /dev/null
@@ -1,60 +0,0 @@
- 'foo',
- 'content' => 'foobar',
- ];
-
- private $data2 = [
- 'title' => 'foo'
- ];
-
- private $data3 = [
- 'title' => null,
- 'views' => 45,
- 'comments' => []
- ];
-
- /**
- * @throws ArrayValidationException
- */
- public function testNoException()
- {
- $strictValidator = new StrictArrayValidator(new ArrayValidation(), $this->data1);
-
- $strictValidator
- ->expectAtLeastKeys(['title', 'content'])
- ->expectExactlyKeys(['title', 'content'])
- ->expectOnlyKeys(['title', 'content'])
- ->expectKeysToBeString(['title', 'content']);
-
- $this->assertTrue(true);
- }
-
- /**
- * @throws ArrayValidationException
- */
- public function testWithException()
- {
- $this->expectException(ArrayValidationException::class);
-
- $strictValidator = new StrictArrayValidator(new ArrayValidation(), $this->data1, 'my data');
-
- $strictValidator
- ->expectAtLeastKeys(['title', 'content'])
- ->expectExactlyKeys(['title', 'content'])
- ->expectOnlyKeys(['title', 'content'])
- ->expectKeysToBeInteger(['title', 'content']);
-
- $this->assertTrue(true);
- }
-}
\ No newline at end of file
diff --git a/tests/StrictValidationTest.php b/tests/StrictValidationTest.php
new file mode 100644
index 0000000..6287cd3
--- /dev/null
+++ b/tests/StrictValidationTest.php
@@ -0,0 +1,205 @@
+ 'foo',
+ 'content' => 'foobar',
+ ];
+
+ private $data2 = [
+ 'id' => 1,
+ 'title' => 'foo',
+ 'isPrivate' => true,
+ 'tags' => ['tag1', ''],
+ 'money' => 15.55
+ ];
+
+ private $data3 = [
+ 'title' => null,
+ 'views' => 45,
+ 'comments' => []
+ ];
+
+ /**
+ * @throws InvalidStructureException
+ * @throws InvalidTypeException
+ */
+ public function testNoException1()
+ {
+ $strictValidator = new StrictValidation($this->data1);
+
+ $strictValidator
+ ->expectAtLeastKeys(['title', 'content'])
+ ->expectExactlyKeys(['title', 'content'])
+ ->expectOnlyKeys(['title', 'content'])
+ ->expectKeysToBeString(['title', 'content']);
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @throws InvalidStructureException
+ * @throws InvalidTypeException
+ */
+ public function testNoException2()
+ {
+ $strictValidator = new StrictValidation($this->data2);
+
+ $strictValidator
+ ->expectOnlyKeys(['id', 'title', 'isPrivate', 'tags', 'money'])
+ ->expectKeysToBeInteger(['id'])
+ ->expectKeysToBeString(['title'])
+ ->expectKeysToBeBoolean(['isPrivate'])
+ ->expectKeysToBeArray(['tags'])
+ ->expectKeysToBeFloat(['money']);
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @throws InvalidStructureException
+ */
+ public function testOnlyKeysException()
+ {
+ $this->expectException(InvalidStructureException::class);
+ $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 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 StrictValidation(['title' => 1]);
+ $strictValidator->expectKeyToBeString('title');
+ }
+
+ /**
+ * @throws InvalidTypeException
+ */
+ public function testInvalidArrayException()
+ {
+ $this->expectException(InvalidTypeException::class);
+ $strictValidator = new StrictValidation(['tags' => 1]);
+ $strictValidator->expectKeyToBeArray('tags');
+ }
+ /**
+ * @throws InvalidTypeException
+ */
+ public function testInvalidBooleanException()
+ {
+ $this->expectException(InvalidTypeException::class);
+ $strictValidator = new StrictValidation(['isPrivate' => 1]);
+ $strictValidator->expectKeyToBeBoolean('isPrivate');
+ }
+
+ /**
+ * @throws InvalidTypeException
+ */
+ public function testInvalidFloatException()
+ {
+ $this->expectException(InvalidTypeException::class);
+ $strictValidator = new StrictValidation(['money' => 1]);
+ $strictValidator->expectKeyToBeFloat('money');
+ }
+
+ /**
+ * @throws InvalidStructureException
+ * @throws InvalidTypeException
+ */
+ public function testWithInvalidTypeException()
+ {
+ $this->expectException(InvalidTypeException::class);
+
+ $strictValidator = new StrictValidation($this->data1);
+
+ $strictValidator
+ ->expectAtLeastKeys(['title', 'content'])
+ ->expectExactlyKeys(['title', 'content'])
+ ->expectOnlyKeys(['title', 'content'])
+ ->expectKeysToBeInteger(['title', 'content']);
+
+ $this->assertTrue(true);
+ }
+
+ /**
+ * @throws InvalidStructureException
+ * @throws InvalidTypeException
+ */
+ public function testWithInvalidStructureException()
+ {
+ $this->expectException(InvalidStructureException::class);
+
+ $strictValidator = new StrictValidation($this->data3, 'my data');
+
+ $strictValidator
+ ->expectAtLeastKeys(['title', 'content'])
+ ->expectExactlyKeys(['title', 'content'])
+ ->expectOnlyKeys(['title', 'content'])
+ ->expectKeysToBeInteger(['title', 'content']);
+
+ $this->assertTrue(true);
+ }
+}
\ No newline at end of file
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);
+ }
+
+}
diff --git a/tests/ValidationDefinitionTest.php b/tests/ValidationDefinitionTest.php
new file mode 100644
index 0000000..2323bdf
--- /dev/null
+++ b/tests/ValidationDefinitionTest.php
@@ -0,0 +1,147 @@
+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', 'description', 'id', 'number', 'amount', 'fields', 'isPrivate', 'person'])
+ ->expectOnlyOneFromKeys(['title'])
+ ->expectKeysToBeInteger(['id'])
+ ->expectKeyToBeInteger('number')
+ ->expectKeyToBeFloat('amount')
+ ->expectKeysToBeFloat(['amount'])
+ ->expectKeysToBeArray(['fields'])
+ ->expectKeyToBeArray('fields')
+ ->expectKeyToBeBoolean('isPrivate')
+ ->expectKeysToBeBoolean(['isPrivate'])
+ ->expectKeysToBeString(['title', 'content'], true)
+ ->expectKeyToBeString('title', true)
+ ->expectKeyToBeString('content', true)
+ ->expectKeyToBeString('description', false)
+ ->expectKeyToBeObject('person', true)
+ ->expectKeysToBeObject(['person']);
+
+ $validations = $arrayDefinition->getValidations();
+
+ $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]);
+
+ $this->assertTrue(isset($validations['expectKeyToBeObject'][0][0]));
+ $this->assertTrue($validations['expectKeyToBeObject'][0][0] === 'person');
+ $this->assertTrue($validations['expectKeyToBeObject'][0][1]);
+ }
+
+ 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,
+ ]);
+
+ }
+
+ public function testJsonSerialize()
+ {
+ $definition = new ValidationDefinition();
+ $definition->expectOnlyKeys(['title', 'content']);
+ $this->assertTrue(json_encode($definition) === '{"expectOnlyKeys":[[["title","content"]]]}');
+ }
+}
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..6dd1c05
--- /dev/null
+++ b/tests/ValidationTest.php
@@ -0,0 +1,119 @@
+ [], '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'],
+
+ 'expectKeysToBeArray' => [['field1']],
+ 'expectKeysToBeString' => [['field2']],
+ 'expectKeysToBeInteger' => [['field3']],
+ 'expectKeysToBeBoolean' => [['field4']],
+ 'expectKeysToBeFloat' => [['field5']],
+ 'expectKeysToBeObject' => [['field6']],
+ ],
+ false, // has error(s)
+ 0, // number of error(s)
+ [] // error(s) messages
+ ],
+
+ // 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);
+
+ foreach ($validationMethods as $methodName => $methodArgs) {
+ call_user_func_array([$validation, $methodName], $methodArgs);
+ }
+
+ $this->assertTrue($validation->hasErrors() === $hasErrors);
+ $this->assertTrue(count($validation->getErrors()) === $errorsCount);
+ $this->assertTrue($validation->getErrors() === $errorsMsg);
+
+ 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
new file mode 100644
index 0000000..6d79dba
--- /dev/null
+++ b/tests/ValidatorTest.php
@@ -0,0 +1,196 @@
+ 'foo',
+ 'content' => 'foobar',
+ ];
+
+ private $data2 = [
+ 'title' => 'foo'
+ ];
+
+ private $data3 = [
+ 'title' => null,
+ 'views' => 45,
+ 'comments' => []
+ ];
+
+ public function testExpectExactlyKeys()
+ {
+ $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']));
+ $this->assertFalse($validation->expectExactlyKeys($this->data1, ['extra']));
+ $this->assertFalse($validation->expectExactlyKeys($this->data1, []));
+ }
+
+ public function testExpectOnlyOneFromKeys()
+ {
+ $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']));
+ }
+
+ public function testExpectAtLeastKeys()
+ {
+ $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']));
+ $this->assertFalse($validation->expectAtLeastKeys($this->data1, ['extra']));
+ $this->assertFalse($validation->expectAtLeastKeys($this->data1, ['title', 'content', 'extra']));
+ }
+
+ public function testExpectOnlyKeys()
+ {
+ $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 testExpectNElement()
+ {
+ $validation = new Validator();
+ $this->assertTrue($validation->expectNKeys($this->data1, 2));
+ $this->assertFalse($validation->expectNKeys($this->data1, 1));
+ }
+
+ public function testIsArray()
+ {
+ $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));
+ }
+
+ 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));
+ }
+}