no message

This commit is contained in:
BarboraFleg
2019-04-04 18:34:40 +02:00
commit ce48ca4a21
589 changed files with 82184 additions and 0 deletions
+39
View File
@@ -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];
}
}
+15
View File
@@ -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
View File
@@ -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
View File
@@ -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));
}
}
}
+175
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
+35
View File
@@ -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;
}
}