diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8d47bd7..65b07f4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,37 @@
+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
diff --git a/README.md b/README.md
index e8d8b51..f06b3e5 100644
--- a/README.md
+++ b/README.md
@@ -8,23 +8,55 @@
composer require peak/array-validation
-## Usage
+## What is 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 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($array, ['key1', 'key2', 'key3']) === true) {
+if ($validator->expectExactlyKeys($data, $keys) === true) {
// ...
}
```
-Advanced validation with fluent interface.
+## 2- Validation with fluent interface (stateful)
```php
+$data = [ // data
+ 'tags' => [],
+ 'name' => 'foobar'
+];
+$validation = new Validation($data);
-$validation = new StrictValidation($array);
+$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
@@ -35,7 +67,113 @@ $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.
+```
+
+## 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/ArrayValidationExceptionInterface.php b/src/Exception/ArrayValidationExceptionInterface.php
new file mode 100644
index 0000000..405b5d7
--- /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
+ * @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 {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,18 +16,8 @@ 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);
- }
- return $this;
+ parent::expectExactlyKeys($keys);
+ return $this->checkStructureErrors();
}
/**
@@ -77,18 +27,8 @@ 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);
- }
- return $this;
+ parent::expectAtLeastKeys($keys);
+ return $this->checkStructureErrors();
}
/**
@@ -98,18 +38,8 @@ 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);
- }
- return $this;
+ parent::expectOnlyKeys($keys);
+ return $this->checkStructureErrors();
}
/**
@@ -119,18 +49,8 @@ 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);
- }
- return $this;
+ parent::expectOnlyOneFromKeys($keys);
+ return $this->checkStructureErrors();
}
/**
@@ -140,17 +60,8 @@ 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);
- }
- return $this;
+ parent::expectNKeys($n);
+ return $this->checkStructureErrors();
}
/**
@@ -161,14 +72,8 @@ 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);
- }
- return $this;
+ parent::expectKeyToBeArray($key, $acceptNull);
+ return $this->checkTypeErrors();
}
/**
@@ -179,14 +84,8 @@ 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);
- }
- return $this;
+ parent::expectKeyToBeInteger($key, $acceptNull);
+ return $this->checkTypeErrors();
}
/**
@@ -197,14 +96,8 @@ 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);
- }
- return $this;
+ parent::expectKeyToBeFloat($key, $acceptNull);
+ return $this->checkTypeErrors();
}
/**
@@ -215,14 +108,8 @@ 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);
- }
- return $this;
+ parent::expectKeyToBeString($key, $acceptNull);
+ return $this->checkTypeErrors();
}
/**
@@ -233,20 +120,14 @@ 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;
+ parent::expectKeyToBeBoolean($key, $acceptNull);
+ return $this->checkTypeErrors();
}
/**
* @param array $keys
* @param bool $acceptNull
- * @return $this
+ * @return $this|Validation
* @throws InvalidTypeException
*/
public function expectKeysToBeString(array $keys, bool $acceptNull = false)
@@ -260,7 +141,7 @@ public function expectKeysToBeString(array $keys, bool $acceptNull = false)
/**
* @param array $keys
* @param bool $acceptNull
- * @return $this
+ * @return $this|Validation
* @throws InvalidTypeException
*/
public function expectKeysToBeInteger(array $keys, bool $acceptNull = false)
@@ -274,7 +155,7 @@ public function expectKeysToBeInteger(array $keys, bool $acceptNull = false)
/**
* @param array $keys
* @param bool $acceptNull
- * @return $this
+ * @return $this|Validation
* @throws InvalidTypeException
*/
public function expectKeysToBeFloat(array $keys, bool $acceptNull = false)
@@ -288,7 +169,7 @@ public function expectKeysToBeFloat(array $keys, bool $acceptNull = false)
/**
* @param array $keys
* @param bool $acceptNull
- * @return $this
+ * @return $this|Validation
* @throws InvalidTypeException
*/
public function expectKeysToBeBoolean(array $keys, bool $acceptNull = false)
@@ -302,7 +183,7 @@ public function expectKeysToBeBoolean(array $keys, bool $acceptNull = false)
/**
* @param array $keys
* @param bool $acceptNull
- * @return $this
+ * @return $this|Validation
* @throws InvalidTypeException
*/
public function expectKeysToBeArray(array $keys, bool $acceptNull = false)
@@ -314,36 +195,26 @@ public function expectKeysToBeArray(array $keys, bool $acceptNull = false)
}
/**
- * @return string|null
+ * @return $this
+ * @throws InvalidStructureException
*/
- private function getExceptionDataName(): ?string
+ protected function checkStructureErrors()
{
- if (isset($this->dataName)) {
- return '['. $this->dataName.'] ';
+ if ($this->hasErrors()) {
+ throw new InvalidStructureException($this->getLastError() ?? '');
}
- return null;
+ return $this;
}
/**
- * @param $type
- * @param array $context
- * @return string
+ * @return $this
+ * @throws InvalidTypeException
*/
- private function getErrorMessage($type, array $context): string
+ protected function checkTypeErrors()
{
- $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;
- }
+ if ($this->hasErrors()) {
+ throw new InvalidTypeException($this->getLastError() ?? '');
}
- return strtr($message, $replace);
+ return $this;
}
}
diff --git a/src/StrictValidationFromDefinition.php b/src/StrictValidationFromDefinition.php
index 80b0897..cb41f38 100644
--- a/src/StrictValidationFromDefinition.php
+++ b/src/StrictValidationFromDefinition.php
@@ -6,30 +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) {
- call_user_func_array([$this, $name], $args);
- }
+ (new ValidationDefinitionExecutor())->execute(
+ $validationDefinition,
+ $this
+ );
}
-}
\ No newline at end of file
+}
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
index 659f872..e1018c7 100644
--- a/src/ValidationDefinition.php
+++ b/src/ValidationDefinition.php
@@ -4,27 +4,31 @@
namespace Peak\ArrayValidation;
-class ValidationDefinition implements ValidationInterface
+use \JsonSerializable;
+
+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' => [],
+ 'expectKeyToBeObject' => [],
+ 'expectKeysToBeObject' => [],
];
/**
@@ -34,100 +38,195 @@ public function getValidations(): array
{
$validations = [];
foreach ($this->validations as $name => $args) {
- if (null !== $args) {
+ if (!empty($args)) {
$validations[$name] = $args;
}
}
return $validations;
}
+ /**
+ * @param array $keys
+ * @return $this
+ */
public function expectExactlyKeys(array $keys)
{
- $this->validations[__FUNCTION__] = func_get_args();
- return $this;
+ return $this->addValidation(__FUNCTION__, func_get_args());
}
+ /**
+ * @param array $keys
+ * @return $this
+ */
public function expectOnlyOneFromKeys(array $keys)
{
- $this->validations[__FUNCTION__] = func_get_args();
- return $this;
+ return $this->addValidation(__FUNCTION__, func_get_args());
}
+ /**
+ * @param array $keys
+ * @return $this
+ */
public function expectAtLeastKeys(array $keys)
{
- $this->validations[__FUNCTION__] = func_get_args();
- return $this;
+ return $this->addValidation(__FUNCTION__, func_get_args());
}
+ /**
+ * @param array $keys
+ * @return $this
+ */
public function expectOnlyKeys(array $keys)
{
- $this->validations[__FUNCTION__] = func_get_args();
- return $this;
+ return $this->addValidation(__FUNCTION__, func_get_args());
}
+ /**
+ * @param int $n
+ * @return $this
+ */
public function expectNKeys(int $n)
{
- $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 expectKeyToBeArray(string $key, bool $acceptNull = false)
{
- $this->validations[__FUNCTION__] = func_get_args();
- return $this;
+ return $this->addValidation(__FUNCTION__, func_get_args());
}
+ /**
+ * @param array $keys
+ * @param bool $acceptNull
+ * @return $this
+ */
public function expectKeysToBeArray(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 expectKeyToBeInteger(string $key, bool $acceptNull = false)
{
- $this->validations[__FUNCTION__] = func_get_args();
- return $this;
+ return $this->addValidation(__FUNCTION__, func_get_args());
}
+ /**
+ * @param array $keys
+ * @param bool $acceptNull
+ * @return $this
+ */
public function expectKeysToBeInteger(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 expectKeyToBeFloat(string $key, bool $acceptNull = false)
{
- $this->validations[__FUNCTION__] = func_get_args();
- return $this;
+ return $this->addValidation(__FUNCTION__, func_get_args());
}
+ /**
+ * @param array $keys
+ * @param bool $acceptNull
+ * @return $this
+ */
public function expectKeysToBeFloat(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 expectKeyToBeString(string $key, bool $acceptNull = false)
{
- $this->validations[__FUNCTION__] = func_get_args();
- return $this;
+ return $this->addValidation(__FUNCTION__, func_get_args());
}
+ /**
+ * @param array $keys
+ * @param bool $acceptNull
+ * @return $this
+ */
public function expectKeysToBeString(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 expectKeyToBeBoolean(string $key, bool $acceptNull = false)
{
- $this->validations[__FUNCTION__] = func_get_args();
- return $this;
+ return $this->addValidation(__FUNCTION__, func_get_args());
}
+ /**
+ * @param array $keys
+ * @param bool $acceptNull
+ * @return $this
+ */
public function expectKeysToBeBoolean(array $keys, bool $acceptNull = false)
{
- $this->validations[__FUNCTION__] = func_get_args();
+ 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
index 20b9374..ed66f1e 100644
--- a/src/ValidationInterface.php
+++ b/src/ValidationInterface.php
@@ -1,5 +1,7 @@
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 6897071..263c02a 100644
--- a/src/ValidatorInterface.php
+++ b/src/ValidatorInterface.php
@@ -1,5 +1,7 @@
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..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/StrictValidationTest.php
similarity index 98%
rename from tests/StrictArrayValidatorTest.php
rename to tests/StrictValidationTest.php
index 6a73018..6287cd3 100644
--- a/tests/StrictArrayValidatorTest.php
+++ b/tests/StrictValidationTest.php
@@ -2,12 +2,14 @@
declare(strict_types=1);
+namespace Tests;
+
use Peak\ArrayValidation\Exception\InvalidStructureException;
use Peak\ArrayValidation\Exception\InvalidTypeException;
use Peak\ArrayValidation\StrictValidation;
use PHPUnit\Framework\TestCase;
-class StrictArrayValidatorTest extends TestCase
+class StrictValidationTest extends TestCase
{
private $data1 = [
'title' => 'foo',
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/ArrayValidationTest.php b/tests/ValidatorTest.php
similarity index 89%
rename from tests/ArrayValidationTest.php
rename to tests/ValidatorTest.php
index 72eff64..6d79dba 100644
--- a/tests/ArrayValidationTest.php
+++ b/tests/ValidatorTest.php
@@ -2,10 +2,14 @@
declare(strict_types=1);
+namespace Tests;
+
+use DateTime;
use \PHPUnit\Framework\TestCase;
use \Peak\ArrayValidation\Validator;
+use stdClass;
-class ArrayValidationTest extends TestCase
+class ValidatorTest extends TestCase
{
private $data1 = [
'title' => 'foo',
@@ -173,4 +177,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));
+ }
}