h
This commit is contained in:
parent
3116d650da
commit
412652b9dc
453 changed files with 39152 additions and 1629 deletions
39
vendor/nette/schema/composer.json
vendored
Normal file
39
vendor/nette/schema/composer.json
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"name": "nette/schema",
|
||||
"description": "📐 Nette Schema: validating data structures against a given Schema.",
|
||||
"keywords": ["nette", "config"],
|
||||
"homepage": "https://nette.org",
|
||||
"license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
"homepage": "https://davidgrudl.com"
|
||||
},
|
||||
{
|
||||
"name": "Nette Community",
|
||||
"homepage": "https://nette.org/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "8.1 - 8.4",
|
||||
"nette/utils": "^4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.5.2",
|
||||
"tracy/tracy": "^2.8",
|
||||
"phpstan/phpstan-nette": "^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["src/"]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"scripts": {
|
||||
"phpstan": "phpstan analyse",
|
||||
"tester": "tester tests -s"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.3-dev"
|
||||
}
|
||||
}
|
||||
}
|
60
vendor/nette/schema/license.md
vendored
Normal file
60
vendor/nette/schema/license.md
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
Licenses
|
||||
========
|
||||
|
||||
Good news! You may use Nette Framework under the terms of either
|
||||
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
|
||||
|
||||
The BSD License is recommended for most projects. It is easy to understand and it
|
||||
places almost no restrictions on what you can do with the framework. If the GPL
|
||||
fits better to your project, you can use the framework under this license.
|
||||
|
||||
You don't have to notify anyone which license you are using. You can freely
|
||||
use Nette Framework in commercial projects as long as the copyright header
|
||||
remains intact.
|
||||
|
||||
Please be advised that the name "Nette Framework" is a protected trademark and its
|
||||
usage has some limitations. So please do not use word "Nette" in the name of your
|
||||
project or top-level domain, and choose a name that stands on its own merits.
|
||||
If your stuff is good, it will not take long to establish a reputation for yourselves.
|
||||
|
||||
|
||||
New BSD License
|
||||
---------------
|
||||
|
||||
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of "Nette Framework" nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as is" and
|
||||
any express or implied warranties, including, but not limited to, the implied
|
||||
warranties of merchantability and fitness for a particular purpose are
|
||||
disclaimed. In no event shall the copyright owner or contributors be liable for
|
||||
any direct, indirect, incidental, special, exemplary, or consequential damages
|
||||
(including, but not limited to, procurement of substitute goods or services;
|
||||
loss of use, data, or profits; or business interruption) however caused and on
|
||||
any theory of liability, whether in contract, strict liability, or tort
|
||||
(including negligence or otherwise) arising in any way out of the use of this
|
||||
software, even if advised of the possibility of such damage.
|
||||
|
||||
|
||||
GNU General Public License
|
||||
--------------------------
|
||||
|
||||
GPL licenses are very very long, so instead of including them here we offer
|
||||
you URLs with full text:
|
||||
|
||||
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
|
||||
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
|
537
vendor/nette/schema/readme.md
vendored
Normal file
537
vendor/nette/schema/readme.md
vendored
Normal file
|
@ -0,0 +1,537 @@
|
|||
Nette Schema
|
||||
************
|
||||
|
||||
[](https://packagist.org/packages/nette/schema)
|
||||
[](https://github.com/nette/schema/actions)
|
||||
[](https://coveralls.io/github/nette/schema?branch=master)
|
||||
[](https://github.com/nette/schema/releases)
|
||||
[](https://github.com/nette/schema/blob/master/license.md)
|
||||
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
A practical library for validation and normalization of data structures against a given schema with a smart & easy-to-understand API.
|
||||
|
||||
Documentation can be found on the [website](https://doc.nette.org/schema).
|
||||
|
||||
Installation:
|
||||
|
||||
```shell
|
||||
composer require nette/schema
|
||||
```
|
||||
|
||||
It requires PHP version 8.1 and supports PHP up to 8.4.
|
||||
|
||||
|
||||
[Support Me](https://github.com/sponsors/dg)
|
||||
--------------------------------------------
|
||||
|
||||
Do you like Nette Schema? Are you looking forward to the new features?
|
||||
|
||||
[](https://github.com/sponsors/dg)
|
||||
|
||||
Thank you!
|
||||
|
||||
|
||||
Basic Usage
|
||||
-----------
|
||||
|
||||
In variable `$schema` we have a validation schema (what exactly this means and how to create it we will say later) and in variable `$data` we have a data structure that we want to validate and normalize. This can be, for example, data sent by the user through an API, configuration file, etc.
|
||||
|
||||
The task is handled by the [Nette\Schema\Processor](https://api.nette.org/schema/master/Nette/Schema/Processor.html) class, which processes the input and either returns normalized data or throws an [Nette\Schema\ValidationException](https://api.nette.org/schema/master/Nette/Schema/ValidationException.html) exception on error.
|
||||
|
||||
```php
|
||||
$processor = new Nette\Schema\Processor;
|
||||
|
||||
try {
|
||||
$normalized = $processor->process($schema, $data);
|
||||
} catch (Nette\Schema\ValidationException $e) {
|
||||
echo 'Data is invalid: ' . $e->getMessage();
|
||||
}
|
||||
```
|
||||
|
||||
Method `$e->getMessages()` returns array of all message strings and `$e->getMessageObjects()` return all messages as [Nette\Schema\Message](https://api.nette.org/schema/master/Nette/Schema/Message.html) objects.
|
||||
|
||||
|
||||
Defining Schema
|
||||
---------------
|
||||
|
||||
And now let's create a schema. The class [Nette\Schema\Expect](https://api.nette.org/schema/master/Nette/Schema/Expect.html) is used to define it, we actually define expectations of what the data should look like. Let's say that the input data must be a structure (e.g. an array) containing elements `processRefund` of type bool and `refundAmount` of type int.
|
||||
|
||||
```php
|
||||
use Nette\Schema\Expect;
|
||||
|
||||
$schema = Expect::structure([
|
||||
'processRefund' => Expect::bool(),
|
||||
'refundAmount' => Expect::int(),
|
||||
]);
|
||||
```
|
||||
|
||||
We believe that the schema definition looks clear, even if you see it for the very first time.
|
||||
|
||||
Lets send the following data for validation:
|
||||
|
||||
```php
|
||||
$data = [
|
||||
'processRefund' => true,
|
||||
'refundAmount' => 17,
|
||||
];
|
||||
|
||||
$normalized = $processor->process($schema, $data); // OK, it passes
|
||||
```
|
||||
|
||||
The output, i.e. the value `$normalized`, is the object `stdClass`. If we want the output to be an array, we add a cast to schema `Expect::structure([...])->castTo('array')`.
|
||||
|
||||
All elements of the structure are optional and have a default value `null`. Example:
|
||||
|
||||
```php
|
||||
$data = [
|
||||
'refundAmount' => 17,
|
||||
];
|
||||
|
||||
$normalized = $processor->process($schema, $data); // OK, it passes
|
||||
// $normalized = {'processRefund' => null, 'refundAmount' => 17}
|
||||
```
|
||||
|
||||
The fact that the default value is `null` does not mean that it would be accepted in the input data `'processRefund' => null`. No, the input must be boolean, i.e. only `true` or `false`. We would have to explicitly allow `null` via `Expect::bool()->nullable()`.
|
||||
|
||||
An item can be made mandatory using `Expect::bool()->required()`. We change the default value to `false` using `Expect::bool()->default(false)` or shortly using `Expect::bool(false)`.
|
||||
|
||||
And what if we wanted to accept `1` and `0` besides booleans? Then we list the allowed values, which we will also normalize to boolean:
|
||||
|
||||
```php
|
||||
$schema = Expect::structure([
|
||||
'processRefund' => Expect::anyOf(true, false, 1, 0)->castTo('bool'),
|
||||
'refundAmount' => Expect::int(),
|
||||
]);
|
||||
|
||||
$normalized = $processor->process($schema, $data);
|
||||
is_bool($normalized->processRefund); // true
|
||||
```
|
||||
|
||||
Now you know the basics of how the schema is defined and how the individual elements of the structure behave. We will now show what all the other elements can be used in defining a schema.
|
||||
|
||||
|
||||
Data Types: type()
|
||||
------------------
|
||||
|
||||
All standard PHP data types can be listed in the schema:
|
||||
|
||||
```php
|
||||
Expect::string($default = null)
|
||||
Expect::int($default = null)
|
||||
Expect::float($default = null)
|
||||
Expect::bool($default = null)
|
||||
Expect::null()
|
||||
Expect::array($default = [])
|
||||
```
|
||||
|
||||
And then all types [supported by the Validators](https://doc.nette.org/validators#toc-validation-rules) via `Expect::type('scalar')` or abbreviated `Expect::scalar()`. Also class or interface names are accepted, e.g. `Expect::type('AddressEntity')`.
|
||||
|
||||
You can also use union notation:
|
||||
|
||||
```php
|
||||
Expect::type('bool|string|array')
|
||||
```
|
||||
|
||||
The default value is always `null` except for `array` and `list`, where it is an empty array. (A list is an array indexed in ascending order of numeric keys from zero, that is, a non-associative array).
|
||||
|
||||
|
||||
Array of Values: arrayOf() listOf()
|
||||
-----------------------------------
|
||||
|
||||
The array is too general structure, it is more useful to specify exactly what elements it can contain. For example, an array whose elements can only be strings:
|
||||
|
||||
```php
|
||||
$schema = Expect::arrayOf('string');
|
||||
|
||||
$processor->process($schema, ['hello', 'world']); // OK
|
||||
$processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK
|
||||
$processor->process($schema, ['key' => 123]); // ERROR: 123 is not a string
|
||||
```
|
||||
|
||||
The second parameter can be used to specify keys (since version 1.2):
|
||||
|
||||
```php
|
||||
$schema = Expect::arrayOf('string', 'int');
|
||||
|
||||
$processor->process($schema, ['hello', 'world']); // OK
|
||||
$processor->process($schema, ['a' => 'hello']); // ERROR: 'a' is not int
|
||||
```
|
||||
|
||||
The list is an indexed array:
|
||||
|
||||
```php
|
||||
$schema = Expect::listOf('string');
|
||||
|
||||
$processor->process($schema, ['a', 'b']); // OK
|
||||
$processor->process($schema, ['a', 123]); // ERROR: 123 is not a string
|
||||
$processor->process($schema, ['key' => 'a']); // ERROR: is not a list
|
||||
$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: is not a list
|
||||
```
|
||||
|
||||
The parameter can also be a schema, so we can write:
|
||||
|
||||
```php
|
||||
Expect::arrayOf(Expect::bool())
|
||||
```
|
||||
|
||||
The default value is an empty array. If you specify a default value, it will be merged with the passed data. This can be disabled using `mergeDefaults(false)`.
|
||||
|
||||
|
||||
Enumeration: anyOf()
|
||||
--------------------
|
||||
|
||||
`anyOf()` is a set of values or schemas that a value can be. Here's how to write an array of elements that can be either `'a'`, `true`, or `null`:
|
||||
|
||||
```php
|
||||
$schema = Expect::listOf(
|
||||
Expect::anyOf('a', true, null),
|
||||
);
|
||||
|
||||
$processor->process($schema, ['a', true, null, 'a']); // OK
|
||||
$processor->process($schema, ['a', false]); // ERROR: false does not belong there
|
||||
```
|
||||
|
||||
The enumeration elements can also be schemas:
|
||||
|
||||
```php
|
||||
$schema = Expect::listOf(
|
||||
Expect::anyOf(Expect::string(), true, null),
|
||||
);
|
||||
|
||||
$processor->process($schema, ['foo', true, null, 'bar']); // OK
|
||||
$processor->process($schema, [123]); // ERROR
|
||||
```
|
||||
|
||||
The `anyOf()` method accepts variants as individual parameters, not as array. To pass it an array of values, use the unpacking operator `anyOf(...$variants)`.
|
||||
|
||||
The default value is `null`. Use the `firstIsDefault()` method to make the first element the default:
|
||||
|
||||
```php
|
||||
// default is 'hello'
|
||||
Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault();
|
||||
```
|
||||
|
||||
|
||||
Structures
|
||||
----------
|
||||
|
||||
Structures are objects with defined keys. Each of these key => value pairs is referred to as a "property":
|
||||
|
||||
Structures accept arrays and objects and return objects `stdClass` (unless you change it with `castTo('array')`, etc.).
|
||||
|
||||
By default, all properties are optional and have a default value of `null`. You can define mandatory properties using `required()`:
|
||||
|
||||
```php
|
||||
$schema = Expect::structure([
|
||||
'required' => Expect::string()->required(),
|
||||
'optional' => Expect::string(), // the default value is null
|
||||
]);
|
||||
|
||||
$processor->process($schema, ['optional' => '']);
|
||||
// ERROR: option 'required' is missing
|
||||
|
||||
$processor->process($schema, ['required' => 'foo']);
|
||||
// OK, returns {'required' => 'foo', 'optional' => null}
|
||||
```
|
||||
|
||||
If you do not want to output properties with only a default value, use `skipDefaults()`:
|
||||
|
||||
```php
|
||||
$schema = Expect::structure([
|
||||
'required' => Expect::string()->required(),
|
||||
'optional' => Expect::string(),
|
||||
])->skipDefaults();
|
||||
|
||||
$processor->process($schema, ['required' => 'foo']);
|
||||
// OK, returns {'required' => 'foo'}
|
||||
```
|
||||
|
||||
Although `null` is the default value of the `optional` property, it is not allowed in the input data (the value must be a string). Properties accepting `null` are defined using `nullable()`:
|
||||
|
||||
```php
|
||||
$schema = Expect::structure([
|
||||
'optional' => Expect::string(),
|
||||
'nullable' => Expect::string()->nullable(),
|
||||
]);
|
||||
|
||||
$processor->process($schema, ['optional' => null]);
|
||||
// ERROR: 'optional' expects to be string, null given.
|
||||
|
||||
$processor->process($schema, ['nullable' => null]);
|
||||
// OK, returns {'optional' => null, 'nullable' => null}
|
||||
```
|
||||
|
||||
By default, there can be no extra items in the input data:
|
||||
|
||||
```php
|
||||
$schema = Expect::structure([
|
||||
'key' => Expect::string(),
|
||||
]);
|
||||
|
||||
$processor->process($schema, ['additional' => 1]);
|
||||
// ERROR: Unexpected item 'additional'
|
||||
```
|
||||
|
||||
Which we can change with `otherItems()`. As a parameter, we will specify the schema for each extra element:
|
||||
|
||||
```php
|
||||
$schema = Expect::structure([
|
||||
'key' => Expect::string(),
|
||||
])->otherItems(Expect::int());
|
||||
|
||||
$processor->process($schema, ['additional' => 1]); // OK
|
||||
$processor->process($schema, ['additional' => true]); // ERROR
|
||||
```
|
||||
|
||||
|
||||
Deprecations
|
||||
------------
|
||||
|
||||
You can deprecate property using the `deprecated([string $message])` method. Deprecation notices are returned by `$processor->getWarnings()`:
|
||||
|
||||
```php
|
||||
$schema = Expect::structure([
|
||||
'old' => Expect::int()->deprecated('The item %path% is deprecated'),
|
||||
]);
|
||||
|
||||
$processor->process($schema, ['old' => 1]); // OK
|
||||
$processor->getWarnings(); // ["The item 'old' is deprecated"]
|
||||
```
|
||||
|
||||
|
||||
Ranges: min() max()
|
||||
-------------------
|
||||
|
||||
Use `min()` and `max()` to limit the number of elements for arrays:
|
||||
|
||||
```php
|
||||
// array, at least 10 items, maximum 20 items
|
||||
Expect::array()->min(10)->max(20);
|
||||
```
|
||||
|
||||
For strings, limit their length:
|
||||
|
||||
```php
|
||||
// string, at least 10 characters long, maximum 20 characters
|
||||
Expect::string()->min(10)->max(20);
|
||||
```
|
||||
|
||||
For numbers, limit their value:
|
||||
|
||||
```php
|
||||
// integer, between 10 and 20 inclusive
|
||||
Expect::int()->min(10)->max(20);
|
||||
```
|
||||
|
||||
Of course, it is possible to mention only `min()`, or only `max()`:
|
||||
|
||||
```php
|
||||
// string, maximum 20 characters
|
||||
Expect::string()->max(20);
|
||||
```
|
||||
|
||||
|
||||
Regular Expressions: pattern()
|
||||
------------------------------
|
||||
|
||||
Using `pattern()`, you can specify a regular expression which the **whole** input string must match (i.e. as if it were wrapped in characters `^` a `$`):
|
||||
|
||||
```php
|
||||
// just 9 digits
|
||||
Expect::string()->pattern('\d{9}');
|
||||
```
|
||||
|
||||
|
||||
Custom Assertions: assert()
|
||||
---------------------------
|
||||
|
||||
You can add any other restrictions using `assert(callable $fn)`.
|
||||
|
||||
```php
|
||||
$countIsEven = fn($v) => count($v) % 2 === 0;
|
||||
|
||||
$schema = Expect::arrayOf('string')
|
||||
->assert($countIsEven); // the count must be even
|
||||
|
||||
$processor->process($schema, ['a', 'b']); // OK
|
||||
$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 is not even
|
||||
```
|
||||
|
||||
Or
|
||||
|
||||
```php
|
||||
Expect::string()->assert('is_file'); // the file must exist
|
||||
```
|
||||
|
||||
You can add your own description for each assertion. It will be part of the error message.
|
||||
|
||||
```php
|
||||
$schema = Expect::arrayOf('string')
|
||||
->assert($countIsEven, 'Even items in array');
|
||||
|
||||
$processor->process($schema, ['a', 'b', 'c']);
|
||||
// Failed assertion "Even items in array" for item with value array.
|
||||
```
|
||||
|
||||
The method can be called repeatedly to add multiple constraints. It can be intermixed with calls to `transform()` and `castTo()`.
|
||||
|
||||
|
||||
Transformation: transform()
|
||||
---------------------------
|
||||
|
||||
Successfully validated data can be modified using a custom function:
|
||||
|
||||
```php
|
||||
// conversion to uppercase:
|
||||
Expect::string()->transform(fn(string $s) => strtoupper($s));
|
||||
```
|
||||
|
||||
The method can be called repeatedly to add multiple transformations. It can be intermixed with calls to `assert()` and `castTo()`. The operations will be executed in the order in which they are declared:
|
||||
|
||||
```php
|
||||
Expect::type('string|int')
|
||||
->castTo('string')
|
||||
->assert('ctype_lower', 'All characters must be lowercased')
|
||||
->transform(fn(string $s) => strtoupper($s)); // conversion to uppercase
|
||||
```
|
||||
|
||||
The `transform()` method can both transform and validate the value simultaneously. This is often simpler and less redundant than chaining `transform()` and `assert()`. For this purpose, the function receives a [Nette\Schema\Context](https://api.nette.org/schema/master/Nette/Schema/Context.html) object with an `addError()` method, which can be used to add information about validation issues:
|
||||
|
||||
```php
|
||||
Expect::string()
|
||||
->transform(function (string $s, Nette\Schema\Context $context) {
|
||||
if (!ctype_lower($s)) {
|
||||
$context->addError('All characters must be lowercased', 'my.case.error');
|
||||
return null;
|
||||
}
|
||||
|
||||
return strtoupper($s);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
Casting: castTo()
|
||||
-----------------
|
||||
|
||||
Successfully validated data can be cast:
|
||||
|
||||
```php
|
||||
Expect::scalar()->castTo('string');
|
||||
```
|
||||
|
||||
In addition to native PHP types, you can also cast to classes. It distinguishes whether it is a simple class without a constructor or a class with a constructor. If the class has no constructor, an instance of it is created and all elements of the structure are written to its properties:
|
||||
|
||||
```php
|
||||
class Info
|
||||
{
|
||||
public bool $processRefund;
|
||||
public int $refundAmount;
|
||||
}
|
||||
|
||||
Expect::structure([
|
||||
'processRefund' => Expect::bool(),
|
||||
'refundAmount' => Expect::int(),
|
||||
])->castTo(Info::class);
|
||||
|
||||
// creates '$obj = new Info' and writes to $obj->processRefund and $obj->refundAmount
|
||||
```
|
||||
|
||||
If the class has a constructor, the elements of the structure are passed as named parameters to the constructor:
|
||||
|
||||
```php
|
||||
class Info
|
||||
{
|
||||
public function __construct(
|
||||
public bool $processRefund,
|
||||
public int $refundAmount,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
// creates $obj = new Info(processRefund: ..., refundAmount: ...)
|
||||
```
|
||||
|
||||
Casting combined with a scalar parameter creates an object and passes the value as the sole parameter to the constructor:
|
||||
|
||||
```php
|
||||
Expect::string()->castTo(DateTime::class);
|
||||
// creates new DateTime(...)
|
||||
```
|
||||
|
||||
|
||||
Normalization: before()
|
||||
-----------------------
|
||||
|
||||
Prior to the validation itself, the data can be normalized using the method `before()`. As an example, let's have an element that must be an array of strings (eg `['a', 'b', 'c']`), but receives input in the form of a string `a b c`:
|
||||
|
||||
```php
|
||||
$explode = fn($v) => explode(' ', $v);
|
||||
|
||||
$schema = Expect::arrayOf('string')
|
||||
->before($explode);
|
||||
|
||||
$normalized = $processor->process($schema, 'a b c');
|
||||
// OK, returns ['a', 'b', 'c']
|
||||
```
|
||||
|
||||
|
||||
Mapping to Objects: from()
|
||||
--------------------------
|
||||
|
||||
You can generate structure schema from the class. Example:
|
||||
|
||||
```php
|
||||
class Config
|
||||
{
|
||||
/** @var string */
|
||||
public $name;
|
||||
/** @var string|null */
|
||||
public $password;
|
||||
/** @var bool */
|
||||
public $admin = false;
|
||||
}
|
||||
|
||||
$schema = Expect::from(new Config);
|
||||
|
||||
$data = [
|
||||
'name' => 'jeff',
|
||||
];
|
||||
|
||||
$normalized = $processor->process($schema, $data);
|
||||
// $normalized instanceof Config
|
||||
// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false}
|
||||
```
|
||||
|
||||
If you are using PHP 7.4 or higher, you can use native types:
|
||||
|
||||
```php
|
||||
class Config
|
||||
{
|
||||
public string $name;
|
||||
public ?string $password;
|
||||
public bool $admin = false;
|
||||
}
|
||||
|
||||
$schema = Expect::from(new Config);
|
||||
```
|
||||
|
||||
Anonymous classes are also supported:
|
||||
|
||||
```php
|
||||
$schema = Expect::from(new class {
|
||||
public string $name;
|
||||
public ?string $password;
|
||||
public bool $admin = false;
|
||||
});
|
||||
```
|
||||
|
||||
Because the information obtained from the class definition may not be sufficient, you can add a custom schema for the elements with the second parameter:
|
||||
|
||||
```php
|
||||
$schema = Expect::from(new Config, [
|
||||
'name' => Expect::string()->pattern('\w:.*'),
|
||||
]);
|
||||
```
|
51
vendor/nette/schema/src/Schema/Context.php
vendored
Normal file
51
vendor/nette/schema/src/Schema/Context.php
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?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;
|
||||
|
||||
|
||||
final class Context
|
||||
{
|
||||
public bool $skipDefaults = false;
|
||||
|
||||
/** @var string[] */
|
||||
public array $path = [];
|
||||
|
||||
public bool $isKey = false;
|
||||
|
||||
/** @var Message[] */
|
||||
public array $errors = [];
|
||||
|
||||
/** @var Message[] */
|
||||
public array $warnings = [];
|
||||
|
||||
/** @var array[] */
|
||||
public array $dynamics = [];
|
||||
|
||||
|
||||
public function addError(string $message, string $code, array $variables = []): Message
|
||||
{
|
||||
$variables['isKey'] = $this->isKey;
|
||||
return $this->errors[] = new Message($message, $code, $this->path, $variables);
|
||||
}
|
||||
|
||||
|
||||
public function addWarning(string $message, string $code, array $variables = []): Message
|
||||
{
|
||||
return $this->warnings[] = new Message($message, $code, $this->path, $variables);
|
||||
}
|
||||
|
||||
|
||||
/** @return \Closure(): bool */
|
||||
public function createChecker(): \Closure
|
||||
{
|
||||
$count = count($this->errors);
|
||||
return fn(): bool => $count === count($this->errors);
|
||||
}
|
||||
}
|
15
vendor/nette/schema/src/Schema/DynamicParameter.php
vendored
Normal file
15
vendor/nette/schema/src/Schema/DynamicParameter.php
vendored
Normal 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
|
||||
{
|
||||
}
|
147
vendor/nette/schema/src/Schema/Elements/AnyOf.php
vendored
Normal file
147
vendor/nette/schema/src/Schema/Elements/AnyOf.php
vendored
Normal file
|
@ -0,0 +1,147 @@
|
|||
<?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;
|
||||
|
||||
private array $set;
|
||||
|
||||
|
||||
public function __construct(mixed ...$set)
|
||||
{
|
||||
if (!$set) {
|
||||
throw new Nette\InvalidStateException('The enumeration must not be empty.');
|
||||
}
|
||||
|
||||
$this->set = $set;
|
||||
}
|
||||
|
||||
|
||||
public function firstIsDefault(): self
|
||||
{
|
||||
$this->default = $this->set[0];
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
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(mixed $value, Context $context): mixed
|
||||
{
|
||||
return $this->doNormalize($value, $context);
|
||||
}
|
||||
|
||||
|
||||
public function merge(mixed $value, mixed $base): mixed
|
||||
{
|
||||
if (is_array($value) && isset($value[Helpers::PreventMerging])) {
|
||||
unset($value[Helpers::PreventMerging]);
|
||||
return $value;
|
||||
}
|
||||
|
||||
return Helpers::merge($value, $base);
|
||||
}
|
||||
|
||||
|
||||
public function complete(mixed $value, Context $context): mixed
|
||||
{
|
||||
$isOk = $context->createChecker();
|
||||
$value = $this->findAlternative($value, $context);
|
||||
$isOk() && $value = $this->doTransform($value, $context);
|
||||
return $isOk() ? $value : null;
|
||||
}
|
||||
|
||||
|
||||
private function findAlternative(mixed $value, Context $context): mixed
|
||||
{
|
||||
$expecteds = $innerErrors = [];
|
||||
foreach ($this->set as $item) {
|
||||
if ($item instanceof Schema) {
|
||||
$dolly = new Context;
|
||||
$dolly->path = $context->path;
|
||||
$res = $item->complete($item->normalize($value, $dolly), $dolly);
|
||||
if (!$dolly->errors) {
|
||||
$context->warnings = array_merge($context->warnings, $dolly->warnings);
|
||||
return $res;
|
||||
}
|
||||
|
||||
foreach ($dolly->errors as $error) {
|
||||
if ($error->path !== $context->path || empty($error->variables['expected'])) {
|
||||
$innerErrors[] = $error;
|
||||
} else {
|
||||
$expecteds[] = $error->variables['expected'];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($item === $value) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$expecteds[] = Nette\Schema\Helpers::formatValue($item);
|
||||
}
|
||||
}
|
||||
|
||||
if ($innerErrors) {
|
||||
$context->errors = array_merge($context->errors, $innerErrors);
|
||||
} else {
|
||||
$context->addError(
|
||||
'The %label% %path% expects to be %expected%, %value% given.',
|
||||
Nette\Schema\Message::TypeMismatch,
|
||||
[
|
||||
'value' => $value,
|
||||
'expected' => implode('|', array_unique($expecteds)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public function completeDefault(Context $context): mixed
|
||||
{
|
||||
if ($this->required) {
|
||||
$context->addError(
|
||||
'The mandatory item %path% is missing.',
|
||||
Nette\Schema\Message::MissingItem,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->default instanceof Schema) {
|
||||
return $this->default->completeDefault($context);
|
||||
}
|
||||
|
||||
return $this->default;
|
||||
}
|
||||
}
|
162
vendor/nette/schema/src/Schema/Elements/Base.php
vendored
Normal file
162
vendor/nette/schema/src/Schema/Elements/Base.php
vendored
Normal file
|
@ -0,0 +1,162 @@
|
|||
<?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;
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait Base
|
||||
{
|
||||
private bool $required = false;
|
||||
private mixed $default = null;
|
||||
|
||||
/** @var ?callable */
|
||||
private $before;
|
||||
|
||||
/** @var callable[] */
|
||||
private array $transforms = [];
|
||||
private ?string $deprecated = null;
|
||||
|
||||
|
||||
public function default(mixed $value): self
|
||||
{
|
||||
$this->default = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function required(bool $state = true): self
|
||||
{
|
||||
$this->required = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function before(callable $handler): self
|
||||
{
|
||||
$this->before = $handler;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function castTo(string $type): self
|
||||
{
|
||||
return $this->transform(Helpers::getCastStrategy($type));
|
||||
}
|
||||
|
||||
|
||||
public function transform(callable $handler): self
|
||||
{
|
||||
$this->transforms[] = $handler;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function assert(callable $handler, ?string $description = null): self
|
||||
{
|
||||
$expected = $description ?: (is_string($handler) ? "$handler()" : '#' . count($this->transforms));
|
||||
return $this->transform(function ($value, Context $context) use ($handler, $description, $expected) {
|
||||
if ($handler($value)) {
|
||||
return $value;
|
||||
}
|
||||
$context->addError(
|
||||
'Failed assertion ' . ($description ? "'%assertion%'" : '%assertion%') . ' for %label% %path% with value %value%.',
|
||||
Nette\Schema\Message::FailedAssertion,
|
||||
['value' => $value, 'assertion' => $expected],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/** Marks as deprecated */
|
||||
public function deprecated(string $message = 'The item %path% is deprecated.'): self
|
||||
{
|
||||
$this->deprecated = $message;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function completeDefault(Context $context): mixed
|
||||
{
|
||||
if ($this->required) {
|
||||
$context->addError(
|
||||
'The mandatory item %path% is missing.',
|
||||
Nette\Schema\Message::MissingItem,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->default;
|
||||
}
|
||||
|
||||
|
||||
public function doNormalize(mixed $value, Context $context): mixed
|
||||
{
|
||||
if ($this->before) {
|
||||
$value = ($this->before)($value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
private function doDeprecation(Context $context): void
|
||||
{
|
||||
if ($this->deprecated !== null) {
|
||||
$context->addWarning(
|
||||
$this->deprecated,
|
||||
Nette\Schema\Message::Deprecated,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function doTransform(mixed $value, Context $context): mixed
|
||||
{
|
||||
$isOk = $context->createChecker();
|
||||
foreach ($this->transforms as $handler) {
|
||||
$value = $handler($value, $context);
|
||||
if (!$isOk()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated use Nette\Schema\Validators::validateType() */
|
||||
private function doValidate(mixed $value, string $expected, Context $context): bool
|
||||
{
|
||||
$isOk = $context->createChecker();
|
||||
Helpers::validateType($value, $expected, $context);
|
||||
return $isOk();
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated use Nette\Schema\Validators::validateRange() */
|
||||
private static function doValidateRange(mixed $value, array $range, Context $context, string $types = ''): bool
|
||||
{
|
||||
$isOk = $context->createChecker();
|
||||
Helpers::validateRange($value, $range, $context, $types);
|
||||
return $isOk();
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated use doTransform() */
|
||||
private function doFinalize(mixed $value, Context $context): mixed
|
||||
{
|
||||
return $this->doTransform($value, $context);
|
||||
}
|
||||
}
|
210
vendor/nette/schema/src/Schema/Elements/Structure.php
vendored
Normal file
210
vendor/nette/schema/src/Schema/Elements/Structure.php
vendored
Normal file
|
@ -0,0 +1,210 @@
|
|||
<?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;
|
||||
|
||||
/** @var Schema[] */
|
||||
private array $items;
|
||||
|
||||
/** for array|list */
|
||||
private ?Schema $otherItems = null;
|
||||
|
||||
/** @var array{?int, ?int} */
|
||||
private array $range = [null, null];
|
||||
private bool $skipDefaults = false;
|
||||
|
||||
|
||||
/**
|
||||
* @param Schema[] $shape
|
||||
*/
|
||||
public function __construct(array $shape)
|
||||
{
|
||||
(function (Schema ...$items) {})(...array_values($shape));
|
||||
$this->items = $shape;
|
||||
$this->castTo('object');
|
||||
$this->required = true;
|
||||
}
|
||||
|
||||
|
||||
public function default(mixed $value): self
|
||||
{
|
||||
throw new Nette\InvalidStateException('Structure cannot have default value.');
|
||||
}
|
||||
|
||||
|
||||
public function min(?int $min): self
|
||||
{
|
||||
$this->range[0] = $min;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function max(?int $max): self
|
||||
{
|
||||
$this->range[1] = $max;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function otherItems(string|Schema $type = 'mixed'): self
|
||||
{
|
||||
$this->otherItems = $type instanceof Schema ? $type : new Type($type);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function skipDefaults(bool $state = true): self
|
||||
{
|
||||
$this->skipDefaults = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function extend(array|self $shape): self
|
||||
{
|
||||
$shape = $shape instanceof self ? $shape->items : $shape;
|
||||
return new self(array_merge($this->items, $shape));
|
||||
}
|
||||
|
||||
|
||||
public function getShape(): array
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
|
||||
/********************* processing ****************d*g**/
|
||||
|
||||
|
||||
public function normalize(mixed $value, Context $context): mixed
|
||||
{
|
||||
if ($prevent = (is_array($value) && isset($value[Helpers::PreventMerging]))) {
|
||||
unset($value[Helpers::PreventMerging]);
|
||||
}
|
||||
|
||||
$value = $this->doNormalize($value, $context);
|
||||
if (is_object($value)) {
|
||||
$value = (array) $value;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if ($prevent) {
|
||||
$value[Helpers::PreventMerging] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
public function merge(mixed $value, mixed $base): mixed
|
||||
{
|
||||
if (is_array($value) && isset($value[Helpers::PreventMerging])) {
|
||||
unset($value[Helpers::PreventMerging]);
|
||||
$base = null;
|
||||
}
|
||||
|
||||
if (is_array($value) && is_array($base)) {
|
||||
$index = $this->otherItems === null ? null : 0;
|
||||
foreach ($value as $key => $val) {
|
||||
if ($key === $index) {
|
||||
$base[] = $val;
|
||||
$index++;
|
||||
} else {
|
||||
$base[$key] = array_key_exists($key, $base) && ($itemSchema = $this->items[$key] ?? $this->otherItems)
|
||||
? $itemSchema->merge($val, $base[$key])
|
||||
: $val;
|
||||
}
|
||||
}
|
||||
|
||||
return $base;
|
||||
}
|
||||
|
||||
return $value ?? $base;
|
||||
}
|
||||
|
||||
|
||||
public function complete(mixed $value, Context $context): mixed
|
||||
{
|
||||
if ($value === null) {
|
||||
$value = []; // is unable to distinguish null from array in NEON
|
||||
}
|
||||
|
||||
$this->doDeprecation($context);
|
||||
|
||||
$isOk = $context->createChecker();
|
||||
Helpers::validateType($value, 'array', $context);
|
||||
$isOk() && Helpers::validateRange($value, $this->range, $context);
|
||||
$isOk() && $this->validateItems($value, $context);
|
||||
$isOk() && $value = $this->doTransform($value, $context);
|
||||
return $isOk() ? $value : null;
|
||||
}
|
||||
|
||||
|
||||
private function validateItems(array &$value, Context $context): void
|
||||
{
|
||||
$items = $this->items;
|
||||
if ($extraKeys = array_keys(array_diff_key($value, $items))) {
|
||||
if ($this->otherItems) {
|
||||
$items += array_fill_keys($extraKeys, $this->otherItems);
|
||||
} else {
|
||||
$keys = array_map('strval', array_keys($items));
|
||||
foreach ($extraKeys as $key) {
|
||||
$hint = Nette\Utils\Helpers::getSuggestion($keys, (string) $key);
|
||||
$context->addError(
|
||||
'Unexpected item %path%' . ($hint ? ", did you mean '%hint%'?" : '.'),
|
||||
Nette\Schema\Message::UnexpectedItem,
|
||||
['hint' => $hint],
|
||||
)->path[] = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 && !$this->skipDefaults) {
|
||||
$value[$itemKey] = $default;
|
||||
}
|
||||
}
|
||||
|
||||
array_pop($context->path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function completeDefault(Context $context): mixed
|
||||
{
|
||||
return $this->required
|
||||
? $this->complete([], $context)
|
||||
: null;
|
||||
}
|
||||
}
|
208
vendor/nette/schema/src/Schema/Elements/Type.php
vendored
Normal file
208
vendor/nette/schema/src/Schema/Elements/Type.php
vendored
Normal file
|
@ -0,0 +1,208 @@
|
|||
<?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\Schema\Context;
|
||||
use Nette\Schema\DynamicParameter;
|
||||
use Nette\Schema\Helpers;
|
||||
use Nette\Schema\Schema;
|
||||
|
||||
|
||||
final class Type implements Schema
|
||||
{
|
||||
use Base;
|
||||
|
||||
private string $type;
|
||||
private ?Schema $itemsValue = null;
|
||||
private ?Schema $itemsKey = null;
|
||||
|
||||
/** @var array{?float, ?float} */
|
||||
private array $range = [null, null];
|
||||
private ?string $pattern = null;
|
||||
private bool $merge = true;
|
||||
|
||||
|
||||
public function __construct(string $type)
|
||||
{
|
||||
$defaults = ['list' => [], 'array' => []];
|
||||
$this->type = $type;
|
||||
$this->default = strpos($type, '[]') ? [] : $defaults[$type] ?? null;
|
||||
}
|
||||
|
||||
|
||||
public function nullable(): self
|
||||
{
|
||||
$this->type = 'null|' . $this->type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function mergeDefaults(bool $state = true): self
|
||||
{
|
||||
$this->merge = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function dynamic(): self
|
||||
{
|
||||
$this->type = DynamicParameter::class . '|' . $this->type;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @internal use arrayOf() or listOf()
|
||||
*/
|
||||
public function items(string|Schema $valueType = 'mixed', string|Schema|null $keyType = null): self
|
||||
{
|
||||
$this->itemsValue = $valueType instanceof Schema
|
||||
? $valueType
|
||||
: new self($valueType);
|
||||
$this->itemsKey = $keyType instanceof Schema || $keyType === null
|
||||
? $keyType
|
||||
: new self($keyType);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function pattern(?string $pattern): self
|
||||
{
|
||||
$this->pattern = $pattern;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/********************* processing ****************d*g**/
|
||||
|
||||
|
||||
public function normalize(mixed $value, Context $context): mixed
|
||||
{
|
||||
if ($prevent = (is_array($value) && isset($value[Helpers::PreventMerging]))) {
|
||||
unset($value[Helpers::PreventMerging]);
|
||||
}
|
||||
|
||||
$value = $this->doNormalize($value, $context);
|
||||
if (is_array($value) && $this->itemsValue) {
|
||||
$res = [];
|
||||
foreach ($value as $key => $val) {
|
||||
$context->path[] = $key;
|
||||
$context->isKey = true;
|
||||
$key = $this->itemsKey
|
||||
? $this->itemsKey->normalize($key, $context)
|
||||
: $key;
|
||||
$context->isKey = false;
|
||||
$res[$key] = $this->itemsValue->normalize($val, $context);
|
||||
array_pop($context->path);
|
||||
}
|
||||
|
||||
$value = $res;
|
||||
}
|
||||
|
||||
if ($prevent && is_array($value)) {
|
||||
$value[Helpers::PreventMerging] = true;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
public function merge(mixed $value, mixed $base): mixed
|
||||
{
|
||||
if (is_array($value) && isset($value[Helpers::PreventMerging])) {
|
||||
unset($value[Helpers::PreventMerging]);
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_array($value) && is_array($base) && $this->itemsValue) {
|
||||
$index = 0;
|
||||
foreach ($value as $key => $val) {
|
||||
if ($key === $index) {
|
||||
$base[] = $val;
|
||||
$index++;
|
||||
} else {
|
||||
$base[$key] = array_key_exists($key, $base)
|
||||
? $this->itemsValue->merge($val, $base[$key])
|
||||
: $val;
|
||||
}
|
||||
}
|
||||
|
||||
return $base;
|
||||
}
|
||||
|
||||
return Helpers::merge($value, $base);
|
||||
}
|
||||
|
||||
|
||||
public function complete(mixed $value, Context $context): mixed
|
||||
{
|
||||
$merge = $this->merge;
|
||||
if (is_array($value) && isset($value[Helpers::PreventMerging])) {
|
||||
unset($value[Helpers::PreventMerging]);
|
||||
$merge = false;
|
||||
}
|
||||
|
||||
if ($value === null && is_array($this->default)) {
|
||||
$value = []; // is unable to distinguish null from array in NEON
|
||||
}
|
||||
|
||||
$this->doDeprecation($context);
|
||||
|
||||
$isOk = $context->createChecker();
|
||||
Helpers::validateType($value, $this->type, $context);
|
||||
$isOk() && Helpers::validateRange($value, $this->range, $context, $this->type);
|
||||
$isOk() && $value !== null && $this->pattern !== null && Helpers::validatePattern($value, $this->pattern, $context);
|
||||
$isOk() && is_array($value) && $this->validateItems($value, $context);
|
||||
$isOk() && $merge && $value = Helpers::merge($value, $this->default);
|
||||
$isOk() && $value = $this->doTransform($value, $context);
|
||||
if (!$isOk()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($value instanceof DynamicParameter) {
|
||||
$expected = $this->type . ($this->range === [null, null] ? '' : ':' . implode('..', $this->range));
|
||||
$context->dynamics[] = [$value, str_replace(DynamicParameter::class . '|', '', $expected), $context->path];
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
private function validateItems(array &$value, Context $context): void
|
||||
{
|
||||
if (!$this->itemsValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
$res = [];
|
||||
foreach ($value as $key => $val) {
|
||||
$context->path[] = $key;
|
||||
$context->isKey = true;
|
||||
$key = $this->itemsKey ? $this->itemsKey->complete($key, $context) : $key;
|
||||
$context->isKey = false;
|
||||
$res[$key] = $this->itemsValue->complete($val, $context);
|
||||
array_pop($context->path);
|
||||
}
|
||||
$value = $res;
|
||||
}
|
||||
}
|
118
vendor/nette/schema/src/Schema/Expect.php
vendored
Normal file
118
vendor/nette/schema/src/Schema/Expect.php
vendored
Normal file
|
@ -0,0 +1,118 @@
|
|||
<?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 list($default = [])
|
||||
* @method static Type mixed($default = null)
|
||||
* @method static Type email($default = null)
|
||||
* @method static Type unicode($default = null)
|
||||
*/
|
||||
final class Expect
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
public static function anyOf(mixed ...$set): AnyOf
|
||||
{
|
||||
return new AnyOf(...$set);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Schema[] $shape
|
||||
*/
|
||||
public static function structure(array $shape): Structure
|
||||
{
|
||||
return new Structure($shape);
|
||||
}
|
||||
|
||||
|
||||
public static function from(object $object, array $items = []): Structure
|
||||
{
|
||||
$ro = new \ReflectionObject($object);
|
||||
$props = $ro->hasMethod('__construct')
|
||||
? $ro->getMethod('__construct')->getParameters()
|
||||
: $ro->getProperties();
|
||||
|
||||
foreach ($props as $prop) {
|
||||
$item = &$items[$prop->getName()];
|
||||
if (!$item) {
|
||||
$type = Helpers::getPropertyType($prop) ?? 'mixed';
|
||||
$item = new Type($type);
|
||||
if ($prop instanceof \ReflectionProperty ? $prop->isInitialized($object) : $prop->isOptional()) {
|
||||
$def = ($prop instanceof \ReflectionProperty ? $prop->getValue($object) : $prop->getDefaultValue());
|
||||
if (is_object($def)) {
|
||||
$item = static::from($def);
|
||||
} elseif ($def === null && !Nette\Utils\Validators::is(null, $type)) {
|
||||
$item->required();
|
||||
} else {
|
||||
$item->default($def);
|
||||
}
|
||||
} else {
|
||||
$item->required();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (new Structure($items))->castTo($ro->getName());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed[] $shape
|
||||
*/
|
||||
public static function array(?array $shape = []): Structure|Type
|
||||
{
|
||||
return Nette\Utils\Arrays::first($shape ?? []) instanceof Schema
|
||||
? (new Structure($shape))->castTo('array')
|
||||
: (new Type('array'))->default($shape);
|
||||
}
|
||||
|
||||
|
||||
public static function arrayOf(string|Schema $valueType, string|Schema|null $keyType = null): Type
|
||||
{
|
||||
return (new Type('array'))->items($valueType, $keyType);
|
||||
}
|
||||
|
||||
|
||||
public static function listOf(string|Schema $type): Type
|
||||
{
|
||||
return (new Type('list'))->items($type);
|
||||
}
|
||||
}
|
183
vendor/nette/schema/src/Schema/Helpers.php
vendored
Normal file
183
vendor/nette/schema/src/Schema/Helpers.php
vendored
Normal file
|
@ -0,0 +1,183 @@
|
|||
<?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 PreventMerging = '_prevent_merging';
|
||||
|
||||
|
||||
/**
|
||||
* Merges dataset. Left has higher priority than right one.
|
||||
*/
|
||||
public static function merge(mixed $value, mixed $base): mixed
|
||||
{
|
||||
if (is_array($value) && isset($value[self::PreventMerging])) {
|
||||
unset($value[self::PreventMerging]);
|
||||
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|\ReflectionParameter $prop): ?string
|
||||
{
|
||||
if ($type = Nette\Utils\Type::fromReflection($prop)) {
|
||||
return (string) $type;
|
||||
} elseif (
|
||||
($prop instanceof \ReflectionProperty)
|
||||
&& ($type = preg_replace('#\s.*#', '', (string) self::parseAnnotation($prop, 'var')))
|
||||
) {
|
||||
$class = Reflection::getPropertyDeclaringClass($prop);
|
||||
return preg_replace_callback('#[\w\\\\]+#', fn($m) => Reflection::expandClassName($m[0], $class), $type);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an annotation value.
|
||||
* @param \ReflectionProperty $ref
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
public static function formatValue(mixed $value): string
|
||||
{
|
||||
if ($value instanceof DynamicParameter) {
|
||||
return 'dynamic';
|
||||
} elseif (is_object($value)) {
|
||||
return 'object ' . $value::class;
|
||||
} elseif (is_string($value)) {
|
||||
return "'" . Nette\Utils\Strings::truncate($value, 15, '...') . "'";
|
||||
} elseif (is_scalar($value)) {
|
||||
return var_export($value, return: true);
|
||||
} else {
|
||||
return get_debug_type($value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function validateType(mixed $value, string $expected, Context $context): void
|
||||
{
|
||||
if (!Nette\Utils\Validators::is($value, $expected)) {
|
||||
$expected = str_replace(DynamicParameter::class . '|', '', $expected);
|
||||
$expected = str_replace(['|', ':'], [' or ', ' in range '], $expected);
|
||||
$context->addError(
|
||||
'The %label% %path% expects to be %expected%, %value% given.',
|
||||
Message::TypeMismatch,
|
||||
['value' => $value, 'expected' => $expected],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function validateRange(mixed $value, array $range, Context $context, string $types = ''): void
|
||||
{
|
||||
if (is_array($value) || is_string($value)) {
|
||||
[$length, $label] = is_array($value)
|
||||
? [count($value), 'items']
|
||||
: (in_array('unicode', explode('|', $types), true)
|
||||
? [Nette\Utils\Strings::length($value), 'characters']
|
||||
: [strlen($value), 'bytes']);
|
||||
|
||||
if (!self::isInRange($length, $range)) {
|
||||
$context->addError(
|
||||
"The length of %label% %path% expects to be in range %expected%, %length% $label given.",
|
||||
Message::LengthOutOfRange,
|
||||
['value' => $value, 'length' => $length, 'expected' => implode('..', $range)],
|
||||
);
|
||||
}
|
||||
} elseif ((is_int($value) || is_float($value)) && !self::isInRange($value, $range)) {
|
||||
$context->addError(
|
||||
'The %label% %path% expects to be in range %expected%, %value% given.',
|
||||
Message::ValueOutOfRange,
|
||||
['value' => $value, 'expected' => implode('..', $range)],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function isInRange(mixed $value, array $range): bool
|
||||
{
|
||||
return ($range[0] === null || $value >= $range[0])
|
||||
&& ($range[1] === null || $value <= $range[1]);
|
||||
}
|
||||
|
||||
|
||||
public static function validatePattern(string $value, string $pattern, Context $context): void
|
||||
{
|
||||
if (!preg_match("\x01^(?:$pattern)$\x01Du", $value)) {
|
||||
$context->addError(
|
||||
"The %label% %path% expects to match pattern '%pattern%', %value% given.",
|
||||
Message::PatternMismatch,
|
||||
['value' => $value, 'pattern' => $pattern],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function getCastStrategy(string $type): \Closure
|
||||
{
|
||||
if (Nette\Utils\Reflection::isBuiltinType($type)) {
|
||||
return static function ($value) use ($type) {
|
||||
settype($value, $type);
|
||||
return $value;
|
||||
};
|
||||
} elseif (method_exists($type, '__construct')) {
|
||||
return static fn($value) => is_array($value) || $value instanceof \stdClass
|
||||
? new $type(...(array) $value)
|
||||
: new $type($value);
|
||||
} else {
|
||||
return static fn($value) => Nette\Utils\Arrays::toObject((array) $value, new $type);
|
||||
}
|
||||
}
|
||||
}
|
98
vendor/nette/schema/src/Schema/Message.php
vendored
Normal file
98
vendor/nette/schema/src/Schema/Message.php
vendored
Normal file
|
@ -0,0 +1,98 @@
|
|||
<?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;
|
||||
|
||||
|
||||
final class Message
|
||||
{
|
||||
/** variables: {value: mixed, expected: string} */
|
||||
public const TypeMismatch = 'schema.typeMismatch';
|
||||
|
||||
/** variables: {value: mixed, expected: string} */
|
||||
public const ValueOutOfRange = 'schema.valueOutOfRange';
|
||||
|
||||
/** variables: {value: mixed, length: int, expected: string} */
|
||||
public const LengthOutOfRange = 'schema.lengthOutOfRange';
|
||||
|
||||
/** variables: {value: string, pattern: string} */
|
||||
public const PatternMismatch = 'schema.patternMismatch';
|
||||
|
||||
/** variables: {value: mixed, assertion: string} */
|
||||
public const FailedAssertion = 'schema.failedAssertion';
|
||||
|
||||
/** no variables */
|
||||
public const MissingItem = 'schema.missingItem';
|
||||
|
||||
/** variables: {hint: string} */
|
||||
public const UnexpectedItem = 'schema.unexpectedItem';
|
||||
|
||||
/** no variables */
|
||||
public const Deprecated = 'schema.deprecated';
|
||||
|
||||
/** @deprecated use Message::TypeMismatch */
|
||||
public const TYPE_MISMATCH = self::TypeMismatch;
|
||||
|
||||
/** @deprecated use Message::ValueOutOfRange */
|
||||
public const VALUE_OUT_OF_RANGE = self::ValueOutOfRange;
|
||||
|
||||
/** @deprecated use Message::LengthOutOfRange */
|
||||
public const LENGTH_OUT_OF_RANGE = self::LengthOutOfRange;
|
||||
|
||||
/** @deprecated use Message::PatternMismatch */
|
||||
public const PATTERN_MISMATCH = self::PatternMismatch;
|
||||
|
||||
/** @deprecated use Message::FailedAssertion */
|
||||
public const FAILED_ASSERTION = self::FailedAssertion;
|
||||
|
||||
/** @deprecated use Message::MissingItem */
|
||||
public const MISSING_ITEM = self::MissingItem;
|
||||
|
||||
/** @deprecated use Message::UnexpectedItem */
|
||||
public const UNEXPECTED_ITEM = self::UnexpectedItem;
|
||||
|
||||
/** @deprecated use Message::Deprecated */
|
||||
public const DEPRECATED = self::Deprecated;
|
||||
|
||||
public string $message;
|
||||
public string $code;
|
||||
|
||||
/** @var string[] */
|
||||
public array $path;
|
||||
|
||||
/** @var string[] */
|
||||
public array $variables;
|
||||
|
||||
|
||||
public function __construct(string $message, string $code, array $path, array $variables = [])
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->code = $code;
|
||||
$this->path = $path;
|
||||
$this->variables = $variables;
|
||||
}
|
||||
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
$vars = $this->variables;
|
||||
$vars['label'] = empty($vars['isKey']) ? 'item' : 'key of item';
|
||||
$vars['path'] = $this->path
|
||||
? "'" . implode("\u{a0}›\u{a0}", $this->path) . "'"
|
||||
: null;
|
||||
$vars['value'] = Helpers::formatValue($vars['value'] ?? null);
|
||||
|
||||
return preg_replace_callback('~( ?)%(\w+)%~', function ($m) use ($vars) {
|
||||
[, $space, $key] = $m;
|
||||
return $vars[$key] === null ? '' : $space . $vars[$key];
|
||||
}, $this->message) ?? throw new Nette\InvalidStateException(preg_last_error_msg());
|
||||
}
|
||||
}
|
96
vendor/nette/schema/src/Schema/Processor.php
vendored
Normal file
96
vendor/nette/schema/src/Schema/Processor.php
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
<?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
|
||||
{
|
||||
public array $onNewContext = [];
|
||||
private Context $context;
|
||||
private bool $skipDefaults = false;
|
||||
|
||||
|
||||
public function skipDefaults(bool $value = true): void
|
||||
{
|
||||
$this->skipDefaults = $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes and validates data. Result is a clean completed data.
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function process(Schema $schema, mixed $data): mixed
|
||||
{
|
||||
$this->createContext();
|
||||
$data = $schema->normalize($data, $this->context);
|
||||
$this->throwsErrors();
|
||||
$data = $schema->complete($data, $this->context);
|
||||
$this->throwsErrors();
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes and validates and merges multiple data. Result is a clean completed data.
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function processMultiple(Schema $schema, array $dataset): mixed
|
||||
{
|
||||
$this->createContext();
|
||||
$flatten = null;
|
||||
$first = true;
|
||||
foreach ($dataset as $data) {
|
||||
$data = $schema->normalize($data, $this->context);
|
||||
$this->throwsErrors();
|
||||
$flatten = $first ? $data : $schema->merge($data, $flatten);
|
||||
$first = false;
|
||||
}
|
||||
|
||||
$data = $schema->complete($flatten, $this->context);
|
||||
$this->throwsErrors();
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getWarnings(): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($this->context->warnings as $message) {
|
||||
$res[] = $message->toString();
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
private function throwsErrors(): void
|
||||
{
|
||||
if ($this->context->errors) {
|
||||
throw new ValidationException(null, $this->context->errors);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function createContext(): void
|
||||
{
|
||||
$this->context = new Context;
|
||||
$this->context->skipDefaults = $this->skipDefaults;
|
||||
Nette\Utils\Arrays::invoke($this->onNewContext, $this->context);
|
||||
}
|
||||
}
|
37
vendor/nette/schema/src/Schema/Schema.php
vendored
Normal file
37
vendor/nette/schema/src/Schema/Schema.php
vendored
Normal 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(mixed $value, Context $context);
|
||||
|
||||
/**
|
||||
* Merging.
|
||||
* @return mixed
|
||||
*/
|
||||
function merge(mixed $value, mixed $base);
|
||||
|
||||
/**
|
||||
* Validation and finalization.
|
||||
* @return mixed
|
||||
*/
|
||||
function complete(mixed $value, Context $context);
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
function completeDefault(Context $context);
|
||||
}
|
55
vendor/nette/schema/src/Schema/ValidationException.php
vendored
Normal file
55
vendor/nette/schema/src/Schema/ValidationException.php
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?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 Message[] */
|
||||
private array $messages;
|
||||
|
||||
|
||||
/**
|
||||
* @param Message[] $messages
|
||||
*/
|
||||
public function __construct(?string $message, array $messages = [])
|
||||
{
|
||||
parent::__construct($message ?: $messages[0]->toString());
|
||||
$this->messages = $messages;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getMessages(): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($this->messages as $message) {
|
||||
$res[] = $message->toString();
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Message[]
|
||||
*/
|
||||
public function getMessageObjects(): array
|
||||
{
|
||||
return $this->messages;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue