308 lines
7.4 KiB
PHP
308 lines
7.4 KiB
PHP
<?php
|
|
|
|
/**
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; under version 2
|
|
* of the License (non-upgradable).
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* Copyright (c) 2019 (original work) Open Assessment Technologies SA;
|
|
*/
|
|
|
|
namespace oat\tao\helpers;
|
|
|
|
class ArrayValidator
|
|
{
|
|
const STR = 'string';
|
|
const INT = 'integer';
|
|
const FLOAT = 'float';
|
|
const BOOL = 'boolean';
|
|
const ARR = 'array';
|
|
const OBJ = 'object';
|
|
|
|
private static $typeCheckFunctions = [
|
|
self::STR => 'is_string',
|
|
self::INT => 'is_int',
|
|
self::FLOAT => 'is_float',
|
|
self::BOOL => 'is_bool',
|
|
self::ARR => 'is_array',
|
|
self::OBJ => 'is_object'
|
|
];
|
|
|
|
/**
|
|
* @var array[]
|
|
*/
|
|
private $rules = [];
|
|
|
|
/**
|
|
* @var bool
|
|
*/
|
|
private $allowExtraKeys = true;
|
|
|
|
/**
|
|
* @var string[]
|
|
*/
|
|
private $missedKeys;
|
|
|
|
/**
|
|
* @var string[]
|
|
*/
|
|
private $typeMismatchKeys;
|
|
|
|
/**
|
|
* @var string[]
|
|
*/
|
|
private $extraKeys;
|
|
|
|
/**
|
|
* @param int|int[]|string|string[] $key
|
|
* @param bool $required
|
|
* @param bool $nullable
|
|
* @return $this
|
|
*/
|
|
public function assertString($key, $required = true, $nullable = false)
|
|
{
|
|
return $this->assertType($key, self::STR, $required, $nullable);
|
|
}
|
|
|
|
/**
|
|
* @param int|int[]|string|string[] $key
|
|
* @param bool $required
|
|
* @param bool $nullable
|
|
* @return $this
|
|
*/
|
|
public function assertInt($key, $required = true, $nullable = false)
|
|
{
|
|
return $this->assertType($key, self::INT, $required, $nullable);
|
|
}
|
|
|
|
/**
|
|
* @param int|int[]|string|string[] $key
|
|
* @param bool $required
|
|
* @param bool $nullable
|
|
* @return $this
|
|
*/
|
|
public function assertFloat($key, $required = true, $nullable = false)
|
|
{
|
|
return $this->assertType($key, self::FLOAT, $required, $nullable);
|
|
}
|
|
|
|
/**
|
|
* @param int|int[]|string|string[] $key
|
|
* @param bool $required
|
|
* @param bool $nullable
|
|
* @return $this
|
|
*/
|
|
public function assertBool($key, $required = true, $nullable = false)
|
|
{
|
|
return $this->assertType($key, self::BOOL, $required, $nullable);
|
|
}
|
|
|
|
/**
|
|
* @param int|int[]|string|string[] $key
|
|
* @param bool $required
|
|
* @param bool $nullable
|
|
* @return $this
|
|
*/
|
|
public function assertArray($key, $required = true, $nullable = false)
|
|
{
|
|
return $this->assertType($key, self::ARR, $required, $nullable);
|
|
}
|
|
|
|
/**
|
|
* @param int|int[]|string|string[] $key
|
|
* @param bool $required
|
|
* @param bool $nullable
|
|
* @return $this
|
|
*/
|
|
public function assertObject($key, $required = true, $nullable = false)
|
|
{
|
|
return $this->assertType($key, self::OBJ, $required, $nullable);
|
|
}
|
|
|
|
public function assertExists($key)
|
|
{
|
|
return $this->assertType($key, null, true, true);
|
|
}
|
|
|
|
/**
|
|
* @param bool $allow
|
|
* @return $this
|
|
*/
|
|
public function allowExtraKeys($allow = true)
|
|
{
|
|
$this->allowExtraKeys = $allow;
|
|
return $this;
|
|
}
|
|
|
|
public function validate($data)
|
|
{
|
|
$this->cleanResults();
|
|
|
|
foreach ($this->rules as $key => $rule) {
|
|
$this->validateKey($data, $key, $rule);
|
|
}
|
|
|
|
if (!$this->allowExtraKeys) {
|
|
$this->extraKeys = array_diff(array_keys($data), array_keys($this->rules));
|
|
}
|
|
|
|
return $this->isValid();
|
|
}
|
|
|
|
public function isValid()
|
|
{
|
|
return count($this->missedKeys) === 0 &&
|
|
count($this->typeMismatchKeys) === 0 &&
|
|
count($this->extraKeys) === 0;
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getMissedKeys()
|
|
{
|
|
return $this->missedKeys;
|
|
}
|
|
|
|
/**
|
|
* Key: mismatch key name
|
|
* Value: error message
|
|
* @return string[]
|
|
*/
|
|
public function getTypeMismatchKeys()
|
|
{
|
|
return $this->typeMismatchKeys;
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getExtraKeys()
|
|
{
|
|
return $this->extraKeys;
|
|
}
|
|
|
|
/**
|
|
* return string
|
|
*/
|
|
public function getErrorMessage()
|
|
{
|
|
$errors = [];
|
|
if (count($this->missedKeys) > 0) {
|
|
$errors[] = 'missed keys: ' . implode(', ', $this->missedKeys);
|
|
}
|
|
foreach ($this->typeMismatchKeys as $key => $msg) {
|
|
$errors[] = $key . ' ' . $msg;
|
|
}
|
|
if (count($this->extraKeys) > 0) {
|
|
$errors[] = 'unexpected keys: ' . implode(', ', $this->extraKeys);
|
|
}
|
|
return count($errors) > 0
|
|
? implode('; ', $errors)
|
|
: null;
|
|
}
|
|
|
|
private function cleanResults()
|
|
{
|
|
$this->missedKeys = $this->typeMismatchKeys = $this->extraKeys = [];
|
|
}
|
|
|
|
/**
|
|
* @param array $data
|
|
* @param string|int $key
|
|
* @param array $rule
|
|
*/
|
|
private function validateKey($data, $key, $rule)
|
|
{
|
|
if (
|
|
!$this->validateKeyExistence($data, $key, $rule) ||
|
|
!$this->validateIfKeyTypeCanBeChecked($data, $key, $rule)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
$type = $rule['type'];
|
|
if (!$this->isType($data[$key], $type)) {
|
|
$this->typeMismatchKeys[$key] = "is not $type";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array $data
|
|
* @param string|int $key
|
|
* @param array $rule
|
|
* @return bool should continue validation
|
|
*/
|
|
private function validateKeyExistence($data, $key, $rule)
|
|
{
|
|
if (!array_key_exists($key, $data)) {
|
|
if ($rule['req']) {
|
|
$this->missedKeys[] = $key;
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param array $data
|
|
* @param string|int $key
|
|
* @param array $rule
|
|
* @return bool should continue validation
|
|
*/
|
|
private function validateIfKeyTypeCanBeChecked($data, $key, $rule)
|
|
{
|
|
if ($rule['type'] === null) {
|
|
return false;
|
|
}
|
|
|
|
if ($data[$key] === null) {
|
|
if (!$rule['nullable']) {
|
|
$this->typeMismatchKeys[$key] = 'is null';
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param int|int[]|string|string[] $keys
|
|
* @param string|null $typeName
|
|
* @param bool $required
|
|
* @param bool $nullable
|
|
* @return $this
|
|
*/
|
|
private function assertType($keys, $typeName, $required, $nullable)
|
|
{
|
|
$keys = (array) $keys;
|
|
foreach ($keys as $key) {
|
|
$this->rules[$key] = ['type' => $typeName, 'req' => $required, 'nullable' => $nullable];
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value
|
|
* @param string $type
|
|
* @return bool
|
|
*/
|
|
private function isType($value, $type)
|
|
{
|
|
if (!isset(self::$typeCheckFunctions[$type])) {
|
|
throw new \InvalidArgumentException('Unsupported type: ' . $type);
|
|
}
|
|
$func = self::$typeCheckFunctions[$type];
|
|
return $func($value);
|
|
}
|
|
}
|