no message
This commit is contained in:
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Context
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var bool */
|
||||
public $skipDefaults = false;
|
||||
|
||||
/** @var string[] */
|
||||
public $path = [];
|
||||
|
||||
/** @var \stdClass[] */
|
||||
public $errors = [];
|
||||
|
||||
/** @var array[] */
|
||||
public $dynamics = [];
|
||||
|
||||
|
||||
public function addError($message, $hint = null)
|
||||
{
|
||||
$this->errors[] = (object) ['message' => $message, 'path' => $this->path, 'hint' => $hint];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema;
|
||||
|
||||
|
||||
interface DynamicParameter
|
||||
{
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema\Elements;
|
||||
|
||||
use Nette;
|
||||
use Nette\Schema\Context;
|
||||
use Nette\Schema\Helpers;
|
||||
use Nette\Schema\Schema;
|
||||
|
||||
|
||||
final class AnyOf implements Schema
|
||||
{
|
||||
use Base;
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var array */
|
||||
private $set;
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed|Schema ...$set
|
||||
*/
|
||||
public function __construct(...$set)
|
||||
{
|
||||
$this->set = $set;
|
||||
}
|
||||
|
||||
|
||||
public function nullable(): self
|
||||
{
|
||||
$this->set[] = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function dynamic(): self
|
||||
{
|
||||
$this->set[] = new Type(Nette\Schema\DynamicParameter::class);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/********************* processing ****************d*g**/
|
||||
|
||||
|
||||
public function normalize($value, Context $context)
|
||||
{
|
||||
return $this->doNormalize($value, $context);
|
||||
}
|
||||
|
||||
|
||||
public function merge($value, $base)
|
||||
{
|
||||
if (is_array($value) && isset($value[Helpers::PREVENT_MERGING])) {
|
||||
unset($value[Helpers::PREVENT_MERGING]);
|
||||
return $value;
|
||||
}
|
||||
return Helpers::merge($value, $base);
|
||||
}
|
||||
|
||||
|
||||
public function complete($value, Nette\Schema\Context $context)
|
||||
{
|
||||
$hints = $innerErrors = [];
|
||||
foreach ($this->set as $item) {
|
||||
if ($item instanceof Schema) {
|
||||
$dolly = new Context;
|
||||
$dolly->path = $context->path;
|
||||
$res = $item->complete($value, $dolly);
|
||||
if (!$dolly->errors) {
|
||||
return $this->doFinalize($res, $context);
|
||||
}
|
||||
foreach ($dolly->errors as $error) {
|
||||
if ($error->path !== $context->path || !$error->hint) {
|
||||
$innerErrors[] = $error;
|
||||
} else {
|
||||
$hints[] = $error->hint;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($item === $value) {
|
||||
return $this->doFinalize($value, $context);
|
||||
}
|
||||
$hints[] = static::formatValue($item);
|
||||
}
|
||||
}
|
||||
|
||||
if ($innerErrors) {
|
||||
$context->errors = array_merge($context->errors, $innerErrors);
|
||||
} else {
|
||||
$hints = implode('|', array_unique($hints));
|
||||
$context->addError("The option %path% expects to be $hints, " . static::formatValue($value) . ' given.');
|
||||
}
|
||||
}
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema\Elements;
|
||||
|
||||
use Nette;
|
||||
use Nette\Schema\Context;
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait Base
|
||||
{
|
||||
/** @var bool */
|
||||
private $required = false;
|
||||
|
||||
/** @var mixed */
|
||||
private $default;
|
||||
|
||||
/** @var callable|null */
|
||||
private $before;
|
||||
|
||||
/** @var callable[] */
|
||||
private $asserts = [];
|
||||
|
||||
/** @var string|null */
|
||||
private $castTo;
|
||||
|
||||
|
||||
public function default($value): self
|
||||
{
|
||||
$this->default = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function required(): self
|
||||
{
|
||||
$this->required = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function before(callable $handler): self
|
||||
{
|
||||
$this->before = $handler;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function castTo(string $type): self
|
||||
{
|
||||
$this->castTo = $type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function assert(callable $handler): self
|
||||
{
|
||||
$this->asserts[] = $handler;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function completeDefault(Context $context)
|
||||
{
|
||||
if ($this->required) {
|
||||
$context->addError('The mandatory option %path% is missing.');
|
||||
return null;
|
||||
}
|
||||
return $this->default;
|
||||
}
|
||||
|
||||
|
||||
public function doNormalize($value, Context $context)
|
||||
{
|
||||
if ($this->before) {
|
||||
$value = ($this->before)($value);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
private function doValidate($value, string $expected, Context $context): bool
|
||||
{
|
||||
try {
|
||||
Nette\Utils\Validators::assert($value, $expected, 'option %path%');
|
||||
return true;
|
||||
} catch (Nette\Utils\AssertionException $e) {
|
||||
$context->addError($e->getMessage(), $expected);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function doFinalize($value, Context $context)
|
||||
{
|
||||
if ($this->castTo) {
|
||||
if (Nette\Utils\Reflection::isBuiltinType($this->castTo)) {
|
||||
settype($value, $this->castTo);
|
||||
} else {
|
||||
$value = Nette\Utils\Arrays::toObject($value, new $this->castTo);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->asserts as $i => $assert) {
|
||||
if (!$assert($value)) {
|
||||
$expected = is_string($assert) ? "$assert()" : "#$i";
|
||||
$context->addError("Failed assertion $expected for option %path% with value " . static::formatValue($value) . '.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
private static function formatValue($value): string
|
||||
{
|
||||
if (is_string($value)) {
|
||||
return "'$value'";
|
||||
} elseif (is_bool($value)) {
|
||||
return $value ? 'true' : 'false';
|
||||
} elseif (is_scalar($value)) {
|
||||
return (string) $value;
|
||||
} else {
|
||||
return strtolower(gettype($value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema\Elements;
|
||||
|
||||
use Nette;
|
||||
use Nette\Schema\Context;
|
||||
use Nette\Schema\Helpers;
|
||||
use Nette\Schema\Schema;
|
||||
|
||||
|
||||
final class Structure implements Schema
|
||||
{
|
||||
use Base;
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var Schema[] */
|
||||
private $items;
|
||||
|
||||
/** @var Schema|null for array|list */
|
||||
private $otherItems;
|
||||
|
||||
/** @var array */
|
||||
private $range = [null, null];
|
||||
|
||||
|
||||
/**
|
||||
* @param Schema[] $items
|
||||
*/
|
||||
public function __construct(array $items)
|
||||
{
|
||||
(function (Schema ...$items) {})(...array_values($items));
|
||||
$this->items = $items;
|
||||
$this->castTo = 'object';
|
||||
}
|
||||
|
||||
|
||||
public function default($value): self
|
||||
{
|
||||
throw new Nette\InvalidStateException('Structure cannot have default value.');
|
||||
}
|
||||
|
||||
|
||||
public function min(?float $min): self
|
||||
{
|
||||
$this->range[0] = $min;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function max(?float $max): self
|
||||
{
|
||||
$this->range[1] = $max;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string|Schema $type
|
||||
*/
|
||||
public function otherItems($type = 'mixed'): self
|
||||
{
|
||||
$this->otherItems = $type instanceof Schema ? $type : new Type($type);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/********************* processing ****************d*g**/
|
||||
|
||||
|
||||
public function normalize($value, Context $context)
|
||||
{
|
||||
$value = $this->doNormalize($value, $context);
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $key => $val) {
|
||||
$itemSchema = $this->items[$key] ?? $this->otherItems;
|
||||
if ($itemSchema) {
|
||||
$context->path[] = $key;
|
||||
$value[$key] = $itemSchema->normalize($val, $context);
|
||||
array_pop($context->path);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
public function merge($value, $base)
|
||||
{
|
||||
if (is_array($value) && isset($value[Helpers::PREVENT_MERGING])) {
|
||||
unset($value[Helpers::PREVENT_MERGING]);
|
||||
$base = null;
|
||||
}
|
||||
|
||||
if (is_array($value) && is_array($base)) {
|
||||
$index = 0;
|
||||
foreach ($value as $key => $val) {
|
||||
if ($key === $index) {
|
||||
$base[] = $val;
|
||||
$index++;
|
||||
} elseif (array_key_exists($key, $base)) {
|
||||
$itemSchema = $this->items[$key] ?? $this->otherItems;
|
||||
$base[$key] = $itemSchema
|
||||
? $itemSchema->merge($val, $base[$key])
|
||||
: Helpers::merge($val, $base[$key]);
|
||||
} else {
|
||||
$base[$key] = $val;
|
||||
}
|
||||
}
|
||||
return $base;
|
||||
}
|
||||
|
||||
return Helpers::merge($value, $base);
|
||||
}
|
||||
|
||||
|
||||
public function complete($value, Nette\Schema\Context $context)
|
||||
{
|
||||
if ($value === null) {
|
||||
$value = []; // is unable to distinguish null from array in NEON
|
||||
} elseif (is_object($value)) {
|
||||
$value = (array) $value;
|
||||
}
|
||||
|
||||
$expected = 'array' . ($this->range === [null, null] ? '' : ':' . implode('..', $this->range));
|
||||
if (!$this->doValidate($value, $expected, $context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$errCount = count($context->errors);
|
||||
$items = $this->items;
|
||||
if ($extraKeys = array_keys(array_diff_key($value, $items))) {
|
||||
if ($this->otherItems) {
|
||||
$items += array_fill_keys($extraKeys, $this->otherItems);
|
||||
} else {
|
||||
$hint = Nette\Utils\ObjectHelpers::getSuggestion(array_map('strval', array_keys($items)), (string) $extraKeys[0]);
|
||||
$s = implode("', '", array_map(function ($key) use ($context) {
|
||||
return implode(' › ', array_merge($context->path, [$key]));
|
||||
}, $hint ? [$extraKeys[0]] : $extraKeys));
|
||||
$context->addError("Unexpected option '$s'" . ($hint ? ", did you mean '$hint'?" : '.'));
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($items as $itemKey => $itemVal) {
|
||||
$context->path[] = $itemKey;
|
||||
if (array_key_exists($itemKey, $value)) {
|
||||
$value[$itemKey] = $itemVal->complete($value[$itemKey], $context);
|
||||
} else {
|
||||
$default = $itemVal->completeDefault($context); // checks required item
|
||||
if (!$context->skipDefaults) {
|
||||
$value[$itemKey] = $default;
|
||||
}
|
||||
}
|
||||
array_pop($context->path);
|
||||
}
|
||||
|
||||
if (count($context->errors) > $errCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $this->doFinalize($value, $context);
|
||||
}
|
||||
|
||||
|
||||
public function completeDefault(Context $context)
|
||||
{
|
||||
return $this->complete([], $context);
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema\Elements;
|
||||
|
||||
use Nette;
|
||||
use Nette\Schema\Context;
|
||||
use Nette\Schema\DynamicParameter;
|
||||
use Nette\Schema\Helpers;
|
||||
use Nette\Schema\Schema;
|
||||
|
||||
|
||||
final class Type implements Schema
|
||||
{
|
||||
use Base;
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var string */
|
||||
private $type;
|
||||
|
||||
/** @var Schema|null for arrays */
|
||||
private $items;
|
||||
|
||||
/** @var array */
|
||||
private $range = [null, null];
|
||||
|
||||
|
||||
public function __construct(string $type)
|
||||
{
|
||||
static $defaults = ['list' => [], 'array' => []];
|
||||
$this->type = $type;
|
||||
$this->default = strpos($type, '[]') ? [] : $defaults[$type] ?? null;
|
||||
}
|
||||
|
||||
|
||||
public function nullable(): self
|
||||
{
|
||||
$this->type .= '|null';
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function dynamic(): self
|
||||
{
|
||||
$this->type .= '|' . DynamicParameter::class;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function min(?float $min): self
|
||||
{
|
||||
$this->range[0] = $min;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function max(?float $max): self
|
||||
{
|
||||
$this->range[1] = $max;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string|Schema $type
|
||||
*/
|
||||
public function items($type = 'mixed'): self
|
||||
{
|
||||
$this->items = $type instanceof Schema ? $type : new self($type);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/********************* processing ****************d*g**/
|
||||
|
||||
|
||||
public function normalize($value, Context $context)
|
||||
{
|
||||
$value = $this->doNormalize($value, $context);
|
||||
if (is_array($value) && $this->items) {
|
||||
foreach ($value as $key => $val) {
|
||||
$context->path[] = $key;
|
||||
$value[$key] = $this->items->normalize($val, $context);
|
||||
array_pop($context->path);
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
public function merge($value, $base)
|
||||
{
|
||||
if (is_array($value) && isset($value[Helpers::PREVENT_MERGING])) {
|
||||
unset($value[Helpers::PREVENT_MERGING]);
|
||||
return $value;
|
||||
}
|
||||
if (is_array($value) && is_array($base) && $this->items) {
|
||||
$index = 0;
|
||||
foreach ($value as $key => $val) {
|
||||
if ($key === $index) {
|
||||
$base[] = $val;
|
||||
$index++;
|
||||
} else {
|
||||
$base[$key] = array_key_exists($key, $base)
|
||||
? $this->items->merge($val, $base[$key])
|
||||
: $val;
|
||||
}
|
||||
}
|
||||
return $base;
|
||||
}
|
||||
|
||||
return Helpers::merge($value, $base);
|
||||
}
|
||||
|
||||
|
||||
public function complete($value, Context $context)
|
||||
{
|
||||
if ($value === null && is_array($this->default)) {
|
||||
$value = []; // is unable to distinguish null from array in NEON
|
||||
}
|
||||
|
||||
$expected = $this->type . ($this->range === [null, null] ? '' : ':' . implode('..', $this->range));
|
||||
if (!$this->doValidate($value, $expected, $context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($value instanceof DynamicParameter) {
|
||||
$context->dynamics[] = [$value, str_replace('|' . DynamicParameter::class, '', $expected)];
|
||||
}
|
||||
|
||||
if ($this->items) {
|
||||
$errCount = count($context->errors);
|
||||
foreach ($value as $key => $val) {
|
||||
$context->path[] = $key;
|
||||
$value[$key] = $this->items->complete($val, $context);
|
||||
array_pop($context->path);
|
||||
}
|
||||
if (count($context->errors) > $errCount) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$value = Helpers::merge($value, $this->default);
|
||||
return $this->doFinalize($value, $context);
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema;
|
||||
|
||||
use Nette;
|
||||
use Nette\Schema\Elements\AnyOf;
|
||||
use Nette\Schema\Elements\Structure;
|
||||
use Nette\Schema\Elements\Type;
|
||||
|
||||
|
||||
/**
|
||||
* Schema generator.
|
||||
*
|
||||
* @method static Type scalar($default = null)
|
||||
* @method static Type string($default = null)
|
||||
* @method static Type int($default = null)
|
||||
* @method static Type float($default = null)
|
||||
* @method static Type bool($default = null)
|
||||
* @method static Type null()
|
||||
* @method static Type array()
|
||||
* @method static Type list()
|
||||
* @method static Type mixed()
|
||||
* @method static Type email($default = null)
|
||||
*/
|
||||
final class Expect
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
public static function __callStatic(string $name, array $args): Type
|
||||
{
|
||||
$type = new Type($name);
|
||||
if ($args) {
|
||||
$type->default($args[0]);
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
|
||||
|
||||
public static function type(string $type): Type
|
||||
{
|
||||
return new Type($type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed|Schema ...$set
|
||||
*/
|
||||
public static function anyOf(...$set): AnyOf
|
||||
{
|
||||
return new AnyOf(...$set);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Schema[] $items
|
||||
*/
|
||||
public static function structure(array $items): Structure
|
||||
{
|
||||
return new Structure($items);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param object $object
|
||||
*/
|
||||
public static function from($object, array $items = []): Structure
|
||||
{
|
||||
$ro = new \ReflectionObject($object);
|
||||
foreach ($ro->getProperties() as $prop) {
|
||||
$type = Helpers::getPropertyType($prop) ?? 'mixed';
|
||||
$item = &$items[$prop->getName()];
|
||||
if (!$item) {
|
||||
$item = new Type($type);
|
||||
if (PHP_VERSION_ID >= 70400 && !$prop->isInitialized($object)) {
|
||||
$item->required();
|
||||
} else {
|
||||
$def = $prop->getValue($object);
|
||||
if (is_object($def)) {
|
||||
$item = static::from($def);
|
||||
} elseif ($def === null && !Nette\Utils\Validators::is(null, $type)) {
|
||||
$item->required();
|
||||
} else {
|
||||
$item->default($def);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (new Structure($items))->castTo($ro->getName());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string|Schema $type
|
||||
*/
|
||||
public static function arrayOf($type): Type
|
||||
{
|
||||
return (new Type('array'))->items($type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string|Schema $type
|
||||
*/
|
||||
public static function listOf($type): Type
|
||||
{
|
||||
return (new Type('list'))->items($type);
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema;
|
||||
|
||||
use Nette;
|
||||
use Nette\Utils\Reflection;
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Helpers
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
public const PREVENT_MERGING = '_prevent_merging';
|
||||
|
||||
|
||||
/**
|
||||
* Merges dataset. Left has higher priority than right one.
|
||||
* @return array|string
|
||||
*/
|
||||
public static function merge($value, $base)
|
||||
{
|
||||
if (is_array($value) && isset($value[self::PREVENT_MERGING])) {
|
||||
unset($value[self::PREVENT_MERGING]);
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_array($value) && is_array($base)) {
|
||||
$index = 0;
|
||||
foreach ($value as $key => $val) {
|
||||
if ($key === $index) {
|
||||
$base[] = $val;
|
||||
$index++;
|
||||
} else {
|
||||
$base[$key] = static::merge($val, $base[$key] ?? null);
|
||||
}
|
||||
}
|
||||
return $base;
|
||||
|
||||
} elseif ($value === null && is_array($base)) {
|
||||
return $base;
|
||||
|
||||
} else {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function getPropertyType(\ReflectionProperty $prop): ?string
|
||||
{
|
||||
if ($type = Reflection::getPropertyType($prop)) {
|
||||
return ($prop->getType()->allowsNull() ? '?' : '') . $type;
|
||||
} elseif ($type = preg_replace('#\s.*#', '', (string) self::parseAnnotation($prop, 'var'))) {
|
||||
$class = Reflection::getPropertyDeclaringClass($prop);
|
||||
return preg_replace_callback('#[\w\\\\]+#', function ($m) use ($class) {
|
||||
return Reflection::expandClassName($m[0], $class);
|
||||
}, $type);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an annotation value.
|
||||
*/
|
||||
public static function parseAnnotation(\Reflector $ref, string $name): ?string
|
||||
{
|
||||
if (!Reflection::areCommentsAvailable()) {
|
||||
throw new Nette\InvalidStateException('You have to enable phpDoc comments in opcode cache.');
|
||||
}
|
||||
$re = '#[\s*]@' . preg_quote($name, '#') . '(?=\s|$)(?:[ \t]+([^@\s]\S*))?#';
|
||||
if ($ref->getDocComment() && preg_match($re, trim($ref->getDocComment(), '/*'), $m)) {
|
||||
return $m[1] ?? '';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Schema validator.
|
||||
*/
|
||||
final class Processor
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var array */
|
||||
public $onNewContext = [];
|
||||
|
||||
/** @var bool */
|
||||
private $skipDefaults;
|
||||
|
||||
|
||||
public function skipDefaults(bool $value = true)
|
||||
{
|
||||
$this->skipDefaults = $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes and validates data. Result is a clean completed data.
|
||||
* @return mixed
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function process(Schema $schema, $data)
|
||||
{
|
||||
$context = $this->createContext();
|
||||
$data = $schema->normalize($data, $context);
|
||||
$this->throwsErrors($context);
|
||||
$data = $schema->complete($data, $context);
|
||||
$this->throwsErrors($context);
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes and validates and merges multiple data. Result is a clean completed data.
|
||||
* @return mixed
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function processMultiple(Schema $schema, array $dataset)
|
||||
{
|
||||
$context = $this->createContext();
|
||||
$flatten = null;
|
||||
$first = true;
|
||||
foreach ($dataset as $data) {
|
||||
$data = $schema->normalize($data, $context);
|
||||
$this->throwsErrors($context);
|
||||
$flatten = $first ? $data : $schema->merge($data, $flatten);
|
||||
$first = false;
|
||||
}
|
||||
$data = $schema->complete($flatten, $context);
|
||||
$this->throwsErrors($context);
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
private function throwsErrors(Context $context): void
|
||||
{
|
||||
$messages = [];
|
||||
foreach ($context->errors as $error) {
|
||||
$pathStr = " '" . implode(' › ', $error->path) . "'";
|
||||
$messages[] = str_replace(' %path%', $error->path ? $pathStr : '', $error->message);
|
||||
}
|
||||
if ($messages) {
|
||||
throw new ValidationException($messages[0], $messages);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function createContext(): Context
|
||||
{
|
||||
$context = new Context;
|
||||
$context->skipDefaults = $this->skipDefaults;
|
||||
$this->onNewContext($context);
|
||||
return $context;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema;
|
||||
|
||||
|
||||
interface Schema
|
||||
{
|
||||
/**
|
||||
* Normalization.
|
||||
* @return mixed
|
||||
*/
|
||||
function normalize($value, Context $context);
|
||||
|
||||
/**
|
||||
* Merging.
|
||||
* @return mixed
|
||||
*/
|
||||
function merge($value, $base);
|
||||
|
||||
/**
|
||||
* Validation and finalization.
|
||||
* @return mixed
|
||||
*/
|
||||
function complete($value, Context $context);
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
function completeDefault(Context $context);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Validation error.
|
||||
*/
|
||||
class ValidationException extends Nette\InvalidStateException
|
||||
{
|
||||
/** @var array */
|
||||
private $messages;
|
||||
|
||||
|
||||
public function __construct(string $message, array $messages = [])
|
||||
{
|
||||
parent::__construct($message);
|
||||
$this->messages = $messages ?: [$message];
|
||||
}
|
||||
|
||||
|
||||
public function getMessages(): array
|
||||
{
|
||||
return $this->messages;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user