chore: 添加缺失的静态资源文件
添加多个前端界面所需的图片、字体、音频等静态资源文件,包括: - 管理员后台的图标、背景图、组件图片 - 移动端页面的商品图片、用户中心图片、活动图标 - 第三方库的示例图片和字体文件 - 浏览器兼容性配置文件 - 图标字体文件(eot、ttf、woff、woff2格式) 这些资源是前端界面正常显示所必需的,之前可能因版本控制忽略或遗漏而缺失。
This commit is contained in:
43
vendor/nette/php-generator/composer.json
vendored
Normal file
43
vendor/nette/php-generator/composer.json
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "nette/php-generator",
|
||||
"description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.1 features.",
|
||||
"keywords": ["nette", "php", "code", "scaffolding"],
|
||||
"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": ">=7.2 <8.3",
|
||||
"nette/utils": "^3.1.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.4",
|
||||
"nikic/php-parser": "^4.13",
|
||||
"tracy/tracy": "^2.8",
|
||||
"phpstan/phpstan": "^0.12"
|
||||
},
|
||||
"suggest": {
|
||||
"nikic/php-parser": "to use ClassType::withBodiesFrom() & GlobalFunction::withBodyFrom()"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["src/"]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"scripts": {
|
||||
"phpstan": "phpstan analyse",
|
||||
"tester": "tester tests -s"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.6-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
60
vendor/nette/php-generator/license.md
vendored
Normal file
60
vendor/nette/php-generator/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)
|
||||
14
vendor/nette/php-generator/ncs.php
vendored
Normal file
14
vendor/nette/php-generator/ncs.php
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Rules for Nette Coding Standard
|
||||
* https://github.com/nette/coding-standard
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
// constant NULL, FALSE in src/PhpGenerator/Type.php
|
||||
'constant_case' => false,
|
||||
'lowercase_static_reference' => false,
|
||||
];
|
||||
779
vendor/nette/php-generator/readme.md
vendored
Normal file
779
vendor/nette/php-generator/readme.md
vendored
Normal file
@@ -0,0 +1,779 @@
|
||||
Nette PHP Generator
|
||||
===================
|
||||
|
||||
[](https://packagist.org/packages/nette/php-generator)
|
||||
[](https://github.com/nette/php-generator/actions)
|
||||
[](https://coveralls.io/github/nette/php-generator?branch=master)
|
||||
[](https://github.com/nette/php-generator/releases)
|
||||
[](https://github.com/nette/php-generator/blob/master/license.md)
|
||||
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Do you need to generate PHP code of classes, functions, namespaces, etc.? This library with a friendly API will help you.
|
||||
|
||||
Documentation can be found on the [website](https://doc.nette.org/php-generator).
|
||||
|
||||
|
||||
[Support Me](https://github.com/sponsors/dg)
|
||||
--------------------------------------------
|
||||
|
||||
Do you like PHP Generator? Are you looking forward to the new features?
|
||||
|
||||
[](https://github.com/sponsors/dg)
|
||||
|
||||
Thank you!
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
```shell
|
||||
composer require nette/php-generator
|
||||
```
|
||||
|
||||
- PhpGenerator 3.6 is compatible with PHP 7.2 to 8.1
|
||||
- PhpGenerator 3.2 – 3.5 is compatible with PHP 7.1 to 8.0
|
||||
- PhpGenerator 3.1 is compatible with PHP 7.1 to 7.3
|
||||
- PhpGenerator 3.0 is compatible with PHP 7.0 to 7.3
|
||||
- PhpGenerator 2.6 is compatible with PHP 5.6 to 7.3
|
||||
|
||||
|
||||
|
||||
Classes
|
||||
-------
|
||||
|
||||
Let's start with a straightforward example of generating class using [ClassType](https://api.nette.org/php-generator/Nette/PhpGenerator/ClassType.html):
|
||||
|
||||
```php
|
||||
$class = new Nette\PhpGenerator\ClassType('Demo');
|
||||
|
||||
$class
|
||||
->setFinal()
|
||||
->setExtends(ParentClass::class)
|
||||
->addImplement(Countable::class)
|
||||
->addComment("Description of class.\nSecond line\n")
|
||||
->addComment('@property-read Nette\Forms\Form $form');
|
||||
|
||||
// to generate PHP code simply cast to string or use echo:
|
||||
echo $class;
|
||||
```
|
||||
|
||||
It will render this result:
|
||||
|
||||
```php
|
||||
/**
|
||||
* Description of class.
|
||||
* Second line
|
||||
*
|
||||
* @property-read Nette\Forms\Form $form
|
||||
*/
|
||||
final class Demo extends ParentClass implements Countable
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
We can also use a printer to generate the code, which, unlike `echo $class`, we will be able to further configure:
|
||||
|
||||
```php
|
||||
$printer = new Nette\PhpGenerator\Printer;
|
||||
echo $printer->printClass($class);
|
||||
```
|
||||
|
||||
We can add constants ([Constant](https://api.nette.org/php-generator/Nette/PhpGenerator/Constant.html)) and properties ([Property](https://api.nette.org/php-generator/Nette/PhpGenerator/Property.html)):
|
||||
|
||||
```php
|
||||
$class->addConstant('ID', 123)
|
||||
->setProtected() // constant visiblity
|
||||
->setFinal();
|
||||
|
||||
$class->addProperty('items', [1, 2, 3])
|
||||
->setPrivate() // or setVisibility('private')
|
||||
->setStatic()
|
||||
->addComment('@var int[]');
|
||||
|
||||
$class->addProperty('list')
|
||||
->setType('array')
|
||||
->setNullable()
|
||||
->setInitialized(); // prints '= null'
|
||||
```
|
||||
|
||||
It generates:
|
||||
|
||||
```php
|
||||
final protected const ID = 123;
|
||||
|
||||
/** @var int[] */
|
||||
private static $items = [1, 2, 3];
|
||||
|
||||
public ?array $list = null;
|
||||
```
|
||||
|
||||
And we can add methods with parameters:
|
||||
|
||||
```php
|
||||
$method = $class->addMethod('count')
|
||||
->addComment('Count it.')
|
||||
->addComment('@return int')
|
||||
->setFinal()
|
||||
->setProtected()
|
||||
->setReturnType('int') // method return type
|
||||
->setReturnNullable() // nullable return type
|
||||
->setBody('return count($items ?: $this->items);');
|
||||
|
||||
$method->addParameter('items', []) // $items = []
|
||||
->setReference() // &$items = []
|
||||
->setType('array'); // array &$items = []
|
||||
```
|
||||
|
||||
It results in:
|
||||
|
||||
```php
|
||||
/**
|
||||
* Count it.
|
||||
* @return int
|
||||
*/
|
||||
final protected function count(array &$items = []): ?int
|
||||
{
|
||||
return count($items ?: $this->items);
|
||||
}
|
||||
```
|
||||
|
||||
Promoted parameters introduced by PHP 8.0 can be passed to the constructor:
|
||||
|
||||
```php
|
||||
$method = $class->addMethod('__construct');
|
||||
$method->addPromotedParameter('name');
|
||||
$method->addPromotedParameter('args', [])
|
||||
->setPrivate();
|
||||
```
|
||||
|
||||
It results in:
|
||||
|
||||
```php
|
||||
public function __construct(
|
||||
public $name,
|
||||
private $args = [],
|
||||
) {
|
||||
}
|
||||
```
|
||||
|
||||
Readonly properties introduced by PHP 8.1 can be marked via `setReadOnly()`.
|
||||
|
||||
------
|
||||
|
||||
If the added property, constant, method or parameter already exist, it will be overwritten.
|
||||
|
||||
Members can be removed using `removeProperty()`, `removeConstant()`, `removeMethod()` or `removeParameter()`.
|
||||
|
||||
You can also add existing `Method`, `Property` or `Constant` objects to the class:
|
||||
|
||||
```php
|
||||
$method = new Nette\PhpGenerator\Method('getHandle');
|
||||
$property = new Nette\PhpGenerator\Property('handle');
|
||||
$const = new Nette\PhpGenerator\Constant('ROLE');
|
||||
|
||||
$class = (new Nette\PhpGenerator\ClassType('Demo'))
|
||||
->addMember($method)
|
||||
->addMember($property)
|
||||
->addMember($const);
|
||||
```
|
||||
|
||||
You can clone existing methods, properties and constants with a different name using `cloneWithName()`:
|
||||
|
||||
```php
|
||||
$methodCount = $class->getMethod('count');
|
||||
$methodRecount = $methodCount->cloneWithName('recount');
|
||||
$class->addMember($methodRecount);
|
||||
```
|
||||
|
||||
Types
|
||||
-----
|
||||
|
||||
Each type or union/intersection type can be passed as a string, you can also use predefined constants for native types:
|
||||
|
||||
```php
|
||||
use Nette\PhpGenerator\Type;
|
||||
|
||||
$member->setType('array'); // or Type::ARRAY;
|
||||
$member->setType('array|string'); // or Type::union('array', 'string')
|
||||
$member->setType('Foo&Bar'); // or Type::intersection(Foo::class, Bar::class)
|
||||
$member->setType(null); // removes type
|
||||
```
|
||||
|
||||
The same applies to the method `setReturnType()`.
|
||||
|
||||
|
||||
Tabs versus Spaces
|
||||
------------------
|
||||
|
||||
The generated code uses tabs for indentation. If you want to have the output compatible with PSR-2 or PSR-12, use `PsrPrinter`:
|
||||
|
||||
```php
|
||||
$class = new Nette\PhpGenerator\ClassType('Demo');
|
||||
// ...
|
||||
|
||||
$printer = new Nette\PhpGenerator\PsrPrinter;
|
||||
echo $printer->printClass($class); // 4 spaces indentation
|
||||
```
|
||||
|
||||
|
||||
Interface or Trait
|
||||
------------------
|
||||
|
||||
You can create interfaces and traits:
|
||||
|
||||
```php
|
||||
$interface = Nette\PhpGenerator\ClassType::interface('MyInterface');
|
||||
$trait = Nette\PhpGenerator\ClassType::trait('MyTrait');
|
||||
// in a similar way $class = Nette\PhpGenerator\ClassType::class('MyClass');
|
||||
```
|
||||
|
||||
Enums
|
||||
-----
|
||||
|
||||
You can easily create the enums that PHP 8.1 brings:
|
||||
|
||||
```php
|
||||
$enum = Nette\PhpGenerator\ClassType::enum('Suit');
|
||||
$enum->addCase('Clubs');
|
||||
$enum->addCase('Diamonds');
|
||||
$enum->addCase('Hearts');
|
||||
$enum->addCase('Spades');
|
||||
|
||||
echo $enum;
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```php
|
||||
enum Suit
|
||||
{
|
||||
case Clubs;
|
||||
case Diamonds;
|
||||
case Hearts;
|
||||
case Spades;
|
||||
}
|
||||
```
|
||||
|
||||
You can also define scalar equivalents for cases to create a backed enum:
|
||||
|
||||
```php
|
||||
$enum->addCase('Clubs', '♣');
|
||||
$enum->addCase('Diamonds', '♦');
|
||||
```
|
||||
|
||||
It is possible to add a comment or [attributes](#attributes) to each case using `addComment()` or `addAttribute()`.
|
||||
|
||||
|
||||
Literals
|
||||
--------
|
||||
|
||||
With `Literal` you can pass arbitrary PHP code to, for example, default property or parameter values etc:
|
||||
|
||||
```php
|
||||
use Nette\PhpGenerator\Literal;
|
||||
|
||||
$class = new Nette\PhpGenerator\ClassType('Demo');
|
||||
|
||||
$class->addProperty('foo', new Literal('Iterator::SELF_FIRST'));
|
||||
|
||||
$class->addMethod('bar')
|
||||
->addParameter('id', new Literal('1 + 2'));
|
||||
|
||||
echo $class;
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```php
|
||||
class Demo
|
||||
{
|
||||
public $foo = Iterator::SELF_FIRST;
|
||||
|
||||
public function bar($id = 1 + 2)
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also pass parameters to `Literal` and have it formatted into valid PHP code using [special placeholders](#method-and-function-body-generator):
|
||||
|
||||
```php
|
||||
new Literal('substr(?, ?)', [$a, $b]);
|
||||
// generates, for example: substr('hello', 5);
|
||||
```
|
||||
|
||||
|
||||
|
||||
Using Traits
|
||||
------------
|
||||
|
||||
```php
|
||||
$class = new Nette\PhpGenerator\ClassType('Demo');
|
||||
$class->addTrait('SmartObject');
|
||||
$class->addTrait('MyTrait', true)
|
||||
->addResolution('sayHello as protected')
|
||||
->addComment('@use MyTrait<Foo>');
|
||||
echo $class;
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```php
|
||||
class Demo
|
||||
{
|
||||
use SmartObject;
|
||||
/** @use MyTrait<Foo> */
|
||||
use MyTrait {
|
||||
sayHello as protected;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Anonymous Class
|
||||
---------------
|
||||
|
||||
Give `null` as the name and you have an anonymous class:
|
||||
|
||||
```php
|
||||
$class = new Nette\PhpGenerator\ClassType(null);
|
||||
$class->addMethod('__construct')
|
||||
->addParameter('foo');
|
||||
|
||||
echo '$obj = new class ($val) ' . $class . ';';
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```php
|
||||
$obj = new class ($val) {
|
||||
|
||||
public function __construct($foo)
|
||||
{
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Global Function
|
||||
---------------
|
||||
|
||||
Code of functions will generate class [GlobalFunction](https://api.nette.org/php-generator/Nette/PhpGenerator/GlobalFunction.html):
|
||||
|
||||
```php
|
||||
$function = new Nette\PhpGenerator\GlobalFunction('foo');
|
||||
$function->setBody('return $a + $b;');
|
||||
$function->addParameter('a');
|
||||
$function->addParameter('b');
|
||||
echo $function;
|
||||
|
||||
// or use PsrPrinter for output compatible with PSR-2 / PSR-12
|
||||
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```php
|
||||
function foo($a, $b)
|
||||
{
|
||||
return $a + $b;
|
||||
}
|
||||
```
|
||||
|
||||
Closure
|
||||
-------
|
||||
|
||||
Code of closures will generate class [Closure](https://api.nette.org/php-generator/Nette/PhpGenerator/Closure.html):
|
||||
|
||||
```php
|
||||
$closure = new Nette\PhpGenerator\Closure;
|
||||
$closure->setBody('return $a + $b;');
|
||||
$closure->addParameter('a');
|
||||
$closure->addParameter('b');
|
||||
$closure->addUse('c')
|
||||
->setReference();
|
||||
echo $closure;
|
||||
|
||||
// or use PsrPrinter for output compatible with PSR-2 / PSR-12
|
||||
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```php
|
||||
function ($a, $b) use (&$c) {
|
||||
return $a + $b;
|
||||
}
|
||||
```
|
||||
|
||||
Arrow Function
|
||||
--------------
|
||||
|
||||
You can also print closure as arrow function using printer:
|
||||
|
||||
```php
|
||||
$closure = new Nette\PhpGenerator\Closure;
|
||||
$closure->setBody('$a + $b');
|
||||
$closure->addParameter('a');
|
||||
$closure->addParameter('b');
|
||||
|
||||
// or use PsrPrinter for output compatible with PSR-2 / PSR-12
|
||||
echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure);
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```php
|
||||
fn($a, $b) => $a + $b
|
||||
```
|
||||
|
||||
Attributes
|
||||
----------
|
||||
|
||||
You can add PHP 8 attributes to all classes, methods, properties, constants, enum cases, functions, closures and parameters.
|
||||
|
||||
```php
|
||||
$class = new Nette\PhpGenerator\ClassType('Demo');
|
||||
$class->addAttribute('Deprecated');
|
||||
|
||||
$class->addProperty('list')
|
||||
->addAttribute('WithArguments', [1, 2]);
|
||||
|
||||
$method = $class->addMethod('count')
|
||||
->addAttribute('Foo\Cached', ['mode' => true]);
|
||||
|
||||
$method->addParameter('items')
|
||||
->addAttribute('Bar');
|
||||
|
||||
echo $class;
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```php
|
||||
#[Deprecated]
|
||||
class Demo
|
||||
{
|
||||
#[WithArguments(1, 2)]
|
||||
public $list;
|
||||
|
||||
|
||||
#[Foo\Cached(mode: true)]
|
||||
public function count(#[Bar] $items)
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Method and Function Body Generator
|
||||
----------------------------------
|
||||
|
||||
The body can be passed to the `setBody()` method at once or sequentially (line by line) by repeatedly calling `addBody()`:
|
||||
|
||||
```php
|
||||
$function = new Nette\PhpGenerator\GlobalFunction('foo');
|
||||
$function->addBody('$a = rand(10, 20);');
|
||||
$function->addBody('return $a;');
|
||||
echo $function;
|
||||
```
|
||||
|
||||
Result
|
||||
|
||||
```php
|
||||
function foo()
|
||||
{
|
||||
$a = rand(10, 20);
|
||||
return $a;
|
||||
}
|
||||
```
|
||||
|
||||
You can use special placeholders for handy way to inject variables.
|
||||
|
||||
Simple placeholders `?`
|
||||
|
||||
```php
|
||||
$str = 'any string';
|
||||
$num = 3;
|
||||
$function = new Nette\PhpGenerator\GlobalFunction('foo');
|
||||
$function->addBody('return substr(?, ?);', [$str, $num]);
|
||||
echo $function;
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```php
|
||||
function foo()
|
||||
{
|
||||
return substr('any string', 3);
|
||||
}
|
||||
```
|
||||
|
||||
Variadic placeholder `...?`
|
||||
|
||||
```php
|
||||
$items = [1, 2, 3];
|
||||
$function = new Nette\PhpGenerator\GlobalFunction('foo');
|
||||
$function->setBody('myfunc(...?);', [$items]);
|
||||
echo $function;
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```php
|
||||
function foo()
|
||||
{
|
||||
myfunc(1, 2, 3);
|
||||
}
|
||||
```
|
||||
|
||||
You can also use PHP 8 named parameters using placeholder `...?:`
|
||||
|
||||
```php
|
||||
$items = ['foo' => 1, 'bar' => true];
|
||||
$function->setBody('myfunc(...?:);', [$items]);
|
||||
|
||||
// myfunc(foo: 1, bar: true);
|
||||
```
|
||||
|
||||
Escape placeholder using slash `\?`
|
||||
|
||||
```php
|
||||
$num = 3;
|
||||
$function = new Nette\PhpGenerator\GlobalFunction('foo');
|
||||
$function->addParameter('a');
|
||||
$function->addBody('return $a \? 10 : ?;', [$num]);
|
||||
echo $function;
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```php
|
||||
function foo($a)
|
||||
{
|
||||
return $a ? 10 : 3;
|
||||
}
|
||||
```
|
||||
|
||||
Namespace
|
||||
---------
|
||||
|
||||
Classes, traits, interfaces and enums (hereinafter classes) can be grouped into namespaces ([PhpNamespace](https://api.nette.org/php-generator/Nette/PhpGenerator/PhpNamespace.html)):
|
||||
|
||||
```php
|
||||
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
|
||||
|
||||
// create new classes in the namespace
|
||||
$class = $namespace->addClass('Task');
|
||||
$interface = $namespace->addInterface('Countable');
|
||||
$trait = $namespace->addTrait('NameAware');
|
||||
|
||||
// or insert an existing class into the namespace
|
||||
$class = new Nette\PhpGenerator\ClassType('Task');
|
||||
$namespace->add($class);
|
||||
```
|
||||
|
||||
If the class already exists, it will be overwritten.
|
||||
|
||||
You can define use-statements:
|
||||
|
||||
```php
|
||||
// use Http\Request;
|
||||
$namespace->addUse(Http\Request::class);
|
||||
// use Http\Request as HttpReq;
|
||||
$namespace->addUse(Http\Request::class, 'HttpReq');
|
||||
// use function iter\range;
|
||||
$namespace->addUseFunction('iter\range');
|
||||
```
|
||||
|
||||
To simplify a fully qualified class, function or constant name according to the defined aliases, use the `simplifyName` method:
|
||||
|
||||
```php
|
||||
echo $namespace->simplifyName('Foo\Bar'); // 'Bar', because 'Foo' is current namespace
|
||||
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', because of the defined use-statement
|
||||
```
|
||||
|
||||
Conversely, you can convert a simplified class, function or constant name to a fully qualified one using the `resolveName` method:
|
||||
|
||||
```php
|
||||
echo $namespace->resolveName('Bar'); // 'Foo\Bar'
|
||||
echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'
|
||||
```
|
||||
|
||||
Class Names Resolving
|
||||
---------------------
|
||||
|
||||
**When the class is part of the namespace, it is rendered slightly differently**: all types (ie. type hints, return types, parent class name,
|
||||
implemented interfaces, used traits and attributes) are automatically *resolved* (unless you turn it off, see below).
|
||||
It means that you have to **use full class names** in definitions and they will be replaced
|
||||
with aliases (according to the use-statements) or fully qualified names in the resulting code:
|
||||
|
||||
```php
|
||||
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
|
||||
$namespace->addUse('Bar\AliasedClass');
|
||||
|
||||
$class = $namespace->addClass('Demo');
|
||||
$class->addImplement('Foo\A') // it will simplify to A
|
||||
->addTrait('Bar\AliasedClass'); // it will simplify to AliasedClass
|
||||
|
||||
$method = $class->addMethod('method');
|
||||
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // in comments simplify manually
|
||||
$method->addParameter('arg')
|
||||
->setType('Bar\OtherClass'); // it will resolve to \Bar\OtherClass
|
||||
|
||||
echo $namespace;
|
||||
|
||||
// or use PsrPrinter for output compatible with PSR-2 / PSR-12
|
||||
// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```php
|
||||
namespace Foo;
|
||||
|
||||
use Bar\AliasedClass;
|
||||
|
||||
class Demo implements A
|
||||
{
|
||||
use AliasedClass;
|
||||
|
||||
/**
|
||||
* @return D
|
||||
*/
|
||||
public function method(\Bar\OtherClass $arg)
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Auto-resolving can be turned off this way:
|
||||
|
||||
```php
|
||||
$printer = new Nette\PhpGenerator\Printer; // or PsrPrinter
|
||||
$printer->setTypeResolving(false);
|
||||
echo $printer->printNamespace($namespace);
|
||||
```
|
||||
|
||||
PHP Files
|
||||
---------
|
||||
|
||||
Classes, functions and namespaces can be grouped into PHP files represented by the class [PhpFile](https://api.nette.org/php-generator/Nette/PhpGenerator/PhpFile.html):
|
||||
|
||||
```php
|
||||
$file = new Nette\PhpGenerator\PhpFile;
|
||||
$file->addComment('This file is auto-generated.');
|
||||
$file->setStrictTypes(); // adds declare(strict_types=1)
|
||||
|
||||
$class = $file->addClass('Foo\A');
|
||||
$function = $file->addFunction('Foo\foo');
|
||||
|
||||
// or
|
||||
// $namespace = $file->addNamespace('Foo');
|
||||
// $class = $namespace->addClass('A');
|
||||
// $function = $namespace->addFunction('foo');
|
||||
|
||||
echo $file;
|
||||
|
||||
// or use PsrPrinter for output compatible with PSR-2 / PSR-12
|
||||
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is auto-generated.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Foo;
|
||||
|
||||
class A
|
||||
{
|
||||
}
|
||||
|
||||
function foo()
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
Generate using Reflection
|
||||
-------------------------
|
||||
|
||||
Another common use case is to create class or function based on existing one:
|
||||
|
||||
```php
|
||||
$class = Nette\PhpGenerator\ClassType::from(PDO::class);
|
||||
|
||||
$function = Nette\PhpGenerator\GlobalFunction::from('trim');
|
||||
|
||||
$closure = Nette\PhpGenerator\Closure::from(
|
||||
function (stdClass $a, $b = null) {}
|
||||
);
|
||||
```
|
||||
|
||||
Function and method bodies are empty by default. If you want to load them as well, use this way
|
||||
(it requires `nikic/php-parser` to be installed):
|
||||
|
||||
```php
|
||||
$class = Nette\PhpGenerator\ClassType::withBodiesFrom(MyClass::class);
|
||||
|
||||
$function = Nette\PhpGenerator\GlobalFunction::withBodyFrom('dump');
|
||||
```
|
||||
|
||||
Load class from file
|
||||
--------------------
|
||||
|
||||
You can also load classes directly from a PHP file that is not already loaded or string of PHP code:
|
||||
|
||||
```php
|
||||
$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX
|
||||
<?php
|
||||
|
||||
class Demo
|
||||
{
|
||||
public $foo;
|
||||
}
|
||||
XX);
|
||||
```
|
||||
|
||||
Loading the entire PHP file, which may contain multiple classes or even multiple namespaces:
|
||||
|
||||
```php
|
||||
$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));
|
||||
```
|
||||
|
||||
This requires `nikic/php-parser` to be installed.
|
||||
|
||||
|
||||
Variables Dumper
|
||||
----------------
|
||||
|
||||
The Dumper returns a parsable PHP string representation of a variable. Provides better and clearer output that native functon `var_export()`.
|
||||
|
||||
```php
|
||||
$dumper = new Nette\PhpGenerator\Dumper;
|
||||
|
||||
$var = ['a', 'b', 123];
|
||||
|
||||
echo $dumper->dump($var); // prints ['a', 'b', 123]
|
||||
```
|
||||
|
||||
Custom Printer
|
||||
--------------
|
||||
|
||||
Need to customize printer behavior? Create your own by inheriting the `Printer` class. You can reconfigure these variables:
|
||||
|
||||
```php
|
||||
class MyPrinter extends Nette\PhpGenerator\Printer
|
||||
{
|
||||
protected $indentation = "\t";
|
||||
protected $linesBetweenProperties = 0;
|
||||
protected $linesBetweenMethods = 1;
|
||||
protected $returnTypeColon = ': ';
|
||||
}
|
||||
```
|
||||
50
vendor/nette/php-generator/src/PhpGenerator/Attribute.php
vendored
Normal file
50
vendor/nette/php-generator/src/PhpGenerator/Attribute.php
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* PHP Attribute.
|
||||
*/
|
||||
final class Attribute
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var string */
|
||||
private $name;
|
||||
|
||||
/** @var array */
|
||||
private $args;
|
||||
|
||||
|
||||
public function __construct(string $name, array $args)
|
||||
{
|
||||
if (!Helpers::isNamespaceIdentifier($name)) {
|
||||
throw new Nette\InvalidArgumentException("Value '$name' is not valid attribute name.");
|
||||
}
|
||||
|
||||
$this->name = $name;
|
||||
$this->args = $args;
|
||||
}
|
||||
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
|
||||
public function getArguments(): array
|
||||
{
|
||||
return $this->args;
|
||||
}
|
||||
}
|
||||
676
vendor/nette/php-generator/src/PhpGenerator/ClassType.php
vendored
Normal file
676
vendor/nette/php-generator/src/PhpGenerator/ClassType.php
vendored
Normal file
@@ -0,0 +1,676 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Class/Interface/Trait/Enum description.
|
||||
*
|
||||
* @property Method[] $methods
|
||||
* @property Property[] $properties
|
||||
*/
|
||||
final class ClassType
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
use Traits\CommentAware;
|
||||
use Traits\AttributeAware;
|
||||
|
||||
public const
|
||||
TYPE_CLASS = 'class',
|
||||
TYPE_INTERFACE = 'interface',
|
||||
TYPE_TRAIT = 'trait',
|
||||
TYPE_ENUM = 'enum';
|
||||
|
||||
public const
|
||||
VisibilityPublic = 'public',
|
||||
VisibilityProtected = 'protected',
|
||||
VisibilityPrivate = 'private';
|
||||
|
||||
public const
|
||||
VISIBILITY_PUBLIC = self::VisibilityPublic,
|
||||
VISIBILITY_PROTECTED = self::VisibilityProtected,
|
||||
VISIBILITY_PRIVATE = self::VisibilityPrivate;
|
||||
|
||||
/** @var PhpNamespace|null */
|
||||
private $namespace;
|
||||
|
||||
/** @var string|null */
|
||||
private $name;
|
||||
|
||||
/** @var string class|interface|trait */
|
||||
private $type = self::TYPE_CLASS;
|
||||
|
||||
/** @var bool */
|
||||
private $final = false;
|
||||
|
||||
/** @var bool */
|
||||
private $abstract = false;
|
||||
|
||||
/** @var string|string[] */
|
||||
private $extends = [];
|
||||
|
||||
/** @var string[] */
|
||||
private $implements = [];
|
||||
|
||||
/** @var TraitUse[] */
|
||||
private $traits = [];
|
||||
|
||||
/** @var Constant[] name => Constant */
|
||||
private $consts = [];
|
||||
|
||||
/** @var Property[] name => Property */
|
||||
private $properties = [];
|
||||
|
||||
/** @var Method[] name => Method */
|
||||
private $methods = [];
|
||||
|
||||
/** @var EnumCase[] name => EnumCase */
|
||||
private $cases = [];
|
||||
|
||||
|
||||
public static function class(?string $name): self
|
||||
{
|
||||
return new self($name);
|
||||
}
|
||||
|
||||
|
||||
public static function interface(string $name): self
|
||||
{
|
||||
return (new self($name))->setType(self::TYPE_INTERFACE);
|
||||
}
|
||||
|
||||
|
||||
public static function trait(string $name): self
|
||||
{
|
||||
return (new self($name))->setType(self::TYPE_TRAIT);
|
||||
}
|
||||
|
||||
|
||||
public static function enum(string $name): self
|
||||
{
|
||||
return (new self($name))->setType(self::TYPE_ENUM);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string|object $class
|
||||
*/
|
||||
public static function from($class, bool $withBodies = false, bool $materializeTraits = true): self
|
||||
{
|
||||
return (new Factory)
|
||||
->fromClassReflection(new \ReflectionClass($class), $withBodies, $materializeTraits);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string|object $class
|
||||
*/
|
||||
public static function withBodiesFrom($class): self
|
||||
{
|
||||
return (new Factory)
|
||||
->fromClassReflection(new \ReflectionClass($class), true);
|
||||
}
|
||||
|
||||
|
||||
public static function fromCode(string $code): self
|
||||
{
|
||||
return (new Factory)
|
||||
->fromClassCode($code);
|
||||
}
|
||||
|
||||
|
||||
public function __construct(?string $name = null, ?PhpNamespace $namespace = null)
|
||||
{
|
||||
$this->setName($name);
|
||||
$this->namespace = $namespace;
|
||||
}
|
||||
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
try {
|
||||
return (new Printer)->printClass($this, $this->namespace);
|
||||
} catch (\Throwable $e) {
|
||||
if (PHP_VERSION_ID >= 70400) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated an object can be in multiple namespaces */
|
||||
public function getNamespace(): ?PhpNamespace
|
||||
{
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setName(?string $name): self
|
||||
{
|
||||
if ($name !== null && (!Helpers::isIdentifier($name) || isset(Helpers::Keywords[strtolower($name)]))) {
|
||||
throw new Nette\InvalidArgumentException("Value '$name' is not valid class name.");
|
||||
}
|
||||
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated */
|
||||
public function setClass(): self
|
||||
{
|
||||
$this->type = self::TYPE_CLASS;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isClass(): bool
|
||||
{
|
||||
return $this->type === self::TYPE_CLASS;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setInterface(): self
|
||||
{
|
||||
$this->type = self::TYPE_INTERFACE;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isInterface(): bool
|
||||
{
|
||||
return $this->type === self::TYPE_INTERFACE;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setTrait(): self
|
||||
{
|
||||
$this->type = self::TYPE_TRAIT;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isTrait(): bool
|
||||
{
|
||||
return $this->type === self::TYPE_TRAIT;
|
||||
}
|
||||
|
||||
|
||||
public function isEnum(): bool
|
||||
{
|
||||
return $this->type === self::TYPE_ENUM;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setType(string $type): self
|
||||
{
|
||||
if (!in_array($type, [self::TYPE_CLASS, self::TYPE_INTERFACE, self::TYPE_TRAIT, self::TYPE_ENUM], true)) {
|
||||
throw new Nette\InvalidArgumentException('Argument must be class|interface|trait|enum.');
|
||||
}
|
||||
|
||||
$this->type = $type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setFinal(bool $state = true): self
|
||||
{
|
||||
$this->final = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isFinal(): bool
|
||||
{
|
||||
return $this->final;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setAbstract(bool $state = true): self
|
||||
{
|
||||
$this->abstract = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isAbstract(): bool
|
||||
{
|
||||
return $this->abstract;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string|string[] $names
|
||||
* @return static
|
||||
*/
|
||||
public function setExtends($names): self
|
||||
{
|
||||
if (!is_string($names) && !is_array($names)) {
|
||||
throw new Nette\InvalidArgumentException('Argument must be string or string[].');
|
||||
}
|
||||
|
||||
$this->validateNames((array) $names);
|
||||
$this->extends = $names;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/** @return string|string[] */
|
||||
public function getExtends()
|
||||
{
|
||||
return $this->extends;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function addExtend(string $name): self
|
||||
{
|
||||
$this->validateNames([$name]);
|
||||
$this->extends = (array) $this->extends;
|
||||
$this->extends[] = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string[] $names
|
||||
* @return static
|
||||
*/
|
||||
public function setImplements(array $names): self
|
||||
{
|
||||
$this->validateNames($names);
|
||||
$this->implements = $names;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/** @return string[] */
|
||||
public function getImplements(): array
|
||||
{
|
||||
return $this->implements;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function addImplement(string $name): self
|
||||
{
|
||||
$this->validateNames([$name]);
|
||||
$this->implements[] = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function removeImplement(string $name): self
|
||||
{
|
||||
$this->implements = array_diff($this->implements, [$name]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string[]|TraitUse[] $traits
|
||||
* @return static
|
||||
*/
|
||||
public function setTraits(array $traits): self
|
||||
{
|
||||
$this->traits = [];
|
||||
foreach ($traits as $trait) {
|
||||
if (!$trait instanceof TraitUse) {
|
||||
$trait = new TraitUse($trait);
|
||||
}
|
||||
|
||||
$this->traits[$trait->getName()] = $trait;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/** @return string[] */
|
||||
public function getTraits(): array
|
||||
{
|
||||
return array_keys($this->traits);
|
||||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
public function getTraitResolutions(): array
|
||||
{
|
||||
return $this->traits;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array|bool $resolutions
|
||||
* @return static|TraitUse
|
||||
*/
|
||||
public function addTrait(string $name, $resolutions = [])
|
||||
{
|
||||
$this->traits[$name] = $trait = new TraitUse($name);
|
||||
if ($resolutions === true) {
|
||||
return $trait;
|
||||
}
|
||||
|
||||
array_map(function ($item) use ($trait) {
|
||||
$trait->addResolution($item);
|
||||
}, $resolutions);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function removeTrait(string $name): self
|
||||
{
|
||||
unset($this->traits[$name]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Method|Property|Constant|EnumCase|TraitUse $member
|
||||
* @return static
|
||||
*/
|
||||
public function addMember($member): self
|
||||
{
|
||||
if ($member instanceof Method) {
|
||||
if ($this->isInterface()) {
|
||||
$member->setBody(null);
|
||||
}
|
||||
|
||||
$this->methods[strtolower($member->getName())] = $member;
|
||||
|
||||
} elseif ($member instanceof Property) {
|
||||
$this->properties[$member->getName()] = $member;
|
||||
|
||||
} elseif ($member instanceof Constant) {
|
||||
$this->consts[$member->getName()] = $member;
|
||||
|
||||
} elseif ($member instanceof EnumCase) {
|
||||
$this->cases[$member->getName()] = $member;
|
||||
|
||||
} elseif ($member instanceof TraitUse) {
|
||||
$this->traits[$member->getName()] = $member;
|
||||
|
||||
} else {
|
||||
throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant|EnumCase|TraitUse.');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Constant[]|mixed[] $consts
|
||||
* @return static
|
||||
*/
|
||||
public function setConstants(array $consts): self
|
||||
{
|
||||
$this->consts = [];
|
||||
foreach ($consts as $k => $const) {
|
||||
if (!$const instanceof Constant) {
|
||||
$const = (new Constant($k))->setValue($const)->setPublic();
|
||||
}
|
||||
|
||||
$this->consts[$const->getName()] = $const;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/** @return Constant[] */
|
||||
public function getConstants(): array
|
||||
{
|
||||
return $this->consts;
|
||||
}
|
||||
|
||||
|
||||
public function addConstant(string $name, $value): Constant
|
||||
{
|
||||
return $this->consts[$name] = (new Constant($name))
|
||||
->setValue($value)
|
||||
->setPublic();
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function removeConstant(string $name): self
|
||||
{
|
||||
unset($this->consts[$name]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets cases to enum
|
||||
* @param EnumCase[] $cases
|
||||
* @return static
|
||||
*/
|
||||
public function setCases(array $cases): self
|
||||
{
|
||||
(function (EnumCase ...$cases) {})(...array_values($cases));
|
||||
$this->cases = [];
|
||||
foreach ($cases as $case) {
|
||||
$this->cases[$case->getName()] = $case;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/** @return EnumCase[] */
|
||||
public function getCases(): array
|
||||
{
|
||||
return $this->cases;
|
||||
}
|
||||
|
||||
|
||||
/** Adds case to enum */
|
||||
public function addCase(string $name, $value = null): EnumCase
|
||||
{
|
||||
return $this->cases[$name] = (new EnumCase($name))
|
||||
->setValue($value);
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function removeCase(string $name): self
|
||||
{
|
||||
unset($this->cases[$name]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Property[] $props
|
||||
* @return static
|
||||
*/
|
||||
public function setProperties(array $props): self
|
||||
{
|
||||
(function (Property ...$props) {})(...array_values($props));
|
||||
$this->properties = [];
|
||||
foreach ($props as $v) {
|
||||
$this->properties[$v->getName()] = $v;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/** @return Property[] */
|
||||
public function getProperties(): array
|
||||
{
|
||||
return $this->properties;
|
||||
}
|
||||
|
||||
|
||||
public function getProperty(string $name): Property
|
||||
{
|
||||
if (!isset($this->properties[$name])) {
|
||||
throw new Nette\InvalidArgumentException("Property '$name' not found.");
|
||||
}
|
||||
|
||||
return $this->properties[$name];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $name without $
|
||||
*/
|
||||
public function addProperty(string $name, $value = null): Property
|
||||
{
|
||||
return $this->properties[$name] = func_num_args() > 1
|
||||
? (new Property($name))->setValue($value)
|
||||
: new Property($name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $name without $
|
||||
* @return static
|
||||
*/
|
||||
public function removeProperty(string $name): self
|
||||
{
|
||||
unset($this->properties[$name]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function hasProperty(string $name): bool
|
||||
{
|
||||
return isset($this->properties[$name]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Method[] $methods
|
||||
* @return static
|
||||
*/
|
||||
public function setMethods(array $methods): self
|
||||
{
|
||||
(function (Method ...$methods) {})(...array_values($methods));
|
||||
$this->methods = [];
|
||||
foreach ($methods as $m) {
|
||||
$this->methods[strtolower($m->getName())] = $m;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/** @return Method[] */
|
||||
public function getMethods(): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($this->methods as $m) {
|
||||
$res[$m->getName()] = $m;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
public function getMethod(string $name): Method
|
||||
{
|
||||
$m = $this->methods[strtolower($name)] ?? null;
|
||||
if (!$m) {
|
||||
throw new Nette\InvalidArgumentException("Method '$name' not found.");
|
||||
}
|
||||
|
||||
return $m;
|
||||
}
|
||||
|
||||
|
||||
public function addMethod(string $name): Method
|
||||
{
|
||||
$method = new Method($name);
|
||||
if ($this->isInterface()) {
|
||||
$method->setBody(null);
|
||||
} else {
|
||||
$method->setPublic();
|
||||
}
|
||||
|
||||
return $this->methods[strtolower($name)] = $method;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function removeMethod(string $name): self
|
||||
{
|
||||
unset($this->methods[strtolower($name)]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function hasMethod(string $name): bool
|
||||
{
|
||||
return isset($this->methods[strtolower($name)]);
|
||||
}
|
||||
|
||||
|
||||
/** @throws Nette\InvalidStateException */
|
||||
public function validate(): void
|
||||
{
|
||||
if ($this->isEnum() && ($this->abstract || $this->final || $this->extends || $this->properties)) {
|
||||
throw new Nette\InvalidStateException("Enum '$this->name' cannot be abstract or final or extends class or have properties.");
|
||||
|
||||
} elseif (!$this->name && ($this->abstract || $this->final)) {
|
||||
throw new Nette\InvalidStateException('Anonymous class cannot be abstract or final.');
|
||||
|
||||
} elseif ($this->abstract && $this->final) {
|
||||
throw new Nette\InvalidStateException("Class '$this->name' cannot be abstract and final at the same time.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function validateNames(array $names): void
|
||||
{
|
||||
foreach ($names as $name) {
|
||||
if (!Helpers::isNamespaceIdentifier($name, true)) {
|
||||
throw new Nette\InvalidArgumentException("Value '$name' is not valid class name.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$clone = function ($item) { return clone $item; };
|
||||
$this->traits = array_map($clone, $this->traits);
|
||||
$this->cases = array_map($clone, $this->cases);
|
||||
$this->consts = array_map($clone, $this->consts);
|
||||
$this->properties = array_map($clone, $this->properties);
|
||||
$this->methods = array_map($clone, $this->methods);
|
||||
}
|
||||
}
|
||||
73
vendor/nette/php-generator/src/PhpGenerator/Closure.php
vendored
Normal file
73
vendor/nette/php-generator/src/PhpGenerator/Closure.php
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Closure.
|
||||
*
|
||||
* @property string $body
|
||||
*/
|
||||
final class Closure
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
use Traits\FunctionLike;
|
||||
use Traits\AttributeAware;
|
||||
|
||||
/** @var Parameter[] */
|
||||
private $uses = [];
|
||||
|
||||
|
||||
public static function from(\Closure $closure): self
|
||||
{
|
||||
return (new Factory)->fromFunctionReflection(new \ReflectionFunction($closure));
|
||||
}
|
||||
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
try {
|
||||
return (new Printer)->printClosure($this);
|
||||
} catch (\Throwable $e) {
|
||||
if (PHP_VERSION_ID >= 70400) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Parameter[] $uses
|
||||
* @return static
|
||||
*/
|
||||
public function setUses(array $uses): self
|
||||
{
|
||||
(function (Parameter ...$uses) {})(...$uses);
|
||||
$this->uses = $uses;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function getUses(): array
|
||||
{
|
||||
return $this->uses;
|
||||
}
|
||||
|
||||
|
||||
public function addUse(string $name): Parameter
|
||||
{
|
||||
return $this->uses[] = new Parameter($name);
|
||||
}
|
||||
}
|
||||
59
vendor/nette/php-generator/src/PhpGenerator/Constant.php
vendored
Normal file
59
vendor/nette/php-generator/src/PhpGenerator/Constant.php
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Class constant.
|
||||
*/
|
||||
final class Constant
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
use Traits\NameAware;
|
||||
use Traits\VisibilityAware;
|
||||
use Traits\CommentAware;
|
||||
use Traits\AttributeAware;
|
||||
|
||||
/** @var mixed */
|
||||
private $value;
|
||||
|
||||
/** @var bool */
|
||||
private $final = false;
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setValue($val): self
|
||||
{
|
||||
$this->value = $val;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setFinal(bool $state = true): self
|
||||
{
|
||||
$this->final = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isFinal(): bool
|
||||
{
|
||||
return $this->final;
|
||||
}
|
||||
}
|
||||
277
vendor/nette/php-generator/src/PhpGenerator/Dumper.php
vendored
Normal file
277
vendor/nette/php-generator/src/PhpGenerator/Dumper.php
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* PHP code generator utils.
|
||||
*/
|
||||
final class Dumper
|
||||
{
|
||||
private const IndentLength = 4;
|
||||
|
||||
/** @var int */
|
||||
public $maxDepth = 50;
|
||||
|
||||
/** @var int */
|
||||
public $wrapLength = 120;
|
||||
|
||||
/** @var string */
|
||||
public $indentation = "\t";
|
||||
|
||||
|
||||
/**
|
||||
* Returns a PHP representation of a variable.
|
||||
*/
|
||||
public function dump($var, int $column = 0): string
|
||||
{
|
||||
return $this->dumpVar($var, [], 0, $column);
|
||||
}
|
||||
|
||||
|
||||
private function dumpVar(&$var, array $parents = [], int $level = 0, int $column = 0): string
|
||||
{
|
||||
if ($var === null) {
|
||||
return 'null';
|
||||
|
||||
} elseif (is_string($var)) {
|
||||
return $this->dumpString($var);
|
||||
|
||||
} elseif (is_array($var)) {
|
||||
return $this->dumpArray($var, $parents, $level, $column);
|
||||
|
||||
} elseif ($var instanceof Literal) {
|
||||
return $this->dumpLiteral($var, $level);
|
||||
|
||||
} elseif (is_object($var)) {
|
||||
return $this->dumpObject($var, $parents, $level);
|
||||
|
||||
} elseif (is_resource($var)) {
|
||||
throw new Nette\InvalidArgumentException('Cannot dump resource.');
|
||||
|
||||
} else {
|
||||
return var_export($var, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function dumpString(string $s): string
|
||||
{
|
||||
static $special = [
|
||||
"\r" => '\r',
|
||||
"\n" => '\n',
|
||||
"\t" => '\t',
|
||||
"\e" => '\e',
|
||||
'\\' => '\\\\',
|
||||
];
|
||||
|
||||
$utf8 = preg_match('##u', $s);
|
||||
$escaped = preg_replace_callback(
|
||||
$utf8 ? '#[\p{C}\\\\]#u' : '#[\x00-\x1F\x7F-\xFF\\\\]#',
|
||||
function ($m) use ($special) {
|
||||
return $special[$m[0]] ?? (strlen($m[0]) === 1
|
||||
? '\x' . str_pad(strtoupper(dechex(ord($m[0]))), 2, '0', STR_PAD_LEFT) . ''
|
||||
: '\u{' . strtoupper(ltrim(dechex(self::utf8Ord($m[0])), '0')) . '}');
|
||||
},
|
||||
$s
|
||||
);
|
||||
return $s === str_replace('\\\\', '\\', $escaped)
|
||||
? "'" . preg_replace('#\'|\\\\(?=[\'\\\\]|$)#D', '\\\\$0', $s) . "'"
|
||||
: '"' . addcslashes($escaped, '"$') . '"';
|
||||
}
|
||||
|
||||
|
||||
private static function utf8Ord(string $c): int
|
||||
{
|
||||
$ord0 = ord($c[0]);
|
||||
if ($ord0 < 0x80) {
|
||||
return $ord0;
|
||||
} elseif ($ord0 < 0xE0) {
|
||||
return ($ord0 << 6) + ord($c[1]) - 0x3080;
|
||||
} elseif ($ord0 < 0xF0) {
|
||||
return ($ord0 << 12) + (ord($c[1]) << 6) + ord($c[2]) - 0xE2080;
|
||||
} else {
|
||||
return ($ord0 << 18) + (ord($c[1]) << 12) + (ord($c[2]) << 6) + ord($c[3]) - 0x3C82080;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function dumpArray(array &$var, array $parents, int $level, int $column): string
|
||||
{
|
||||
if (empty($var)) {
|
||||
return '[]';
|
||||
|
||||
} elseif ($level > $this->maxDepth || in_array($var, $parents, true)) {
|
||||
throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.');
|
||||
}
|
||||
|
||||
$space = str_repeat($this->indentation, $level);
|
||||
$outInline = '';
|
||||
$outWrapped = "\n$space";
|
||||
$parents[] = $var;
|
||||
$counter = 0;
|
||||
$hideKeys = is_int(($tmp = array_keys($var))[0]) && $tmp === range($tmp[0], $tmp[0] + count($var) - 1);
|
||||
|
||||
foreach ($var as $k => &$v) {
|
||||
$keyPart = $hideKeys && $k === $counter
|
||||
? ''
|
||||
: $this->dumpVar($k) . ' => ';
|
||||
$counter = is_int($k) ? max($k + 1, $counter) : $counter;
|
||||
$outInline .= ($outInline === '' ? '' : ', ') . $keyPart;
|
||||
$outInline .= $this->dumpVar($v, $parents, 0, $column + strlen($outInline));
|
||||
$outWrapped .= $this->indentation
|
||||
. $keyPart
|
||||
. $this->dumpVar($v, $parents, $level + 1, strlen($keyPart))
|
||||
. ",\n$space";
|
||||
}
|
||||
|
||||
array_pop($parents);
|
||||
$wrap = strpos($outInline, "\n") !== false || $level * self::IndentLength + $column + strlen($outInline) + 3 > $this->wrapLength; // 3 = [],
|
||||
return '[' . ($wrap ? $outWrapped : $outInline) . ']';
|
||||
}
|
||||
|
||||
|
||||
private function dumpObject($var, array $parents, int $level): string
|
||||
{
|
||||
if ($var instanceof \Serializable) {
|
||||
return 'unserialize(' . $this->dumpString(serialize($var)) . ')';
|
||||
|
||||
} elseif ($var instanceof \UnitEnum) {
|
||||
return '\\' . get_class($var) . '::' . $var->name;
|
||||
|
||||
} elseif ($var instanceof \Closure) {
|
||||
$inner = Nette\Utils\Callback::unwrap($var);
|
||||
if (Nette\Utils\Callback::isStatic($inner)) {
|
||||
return PHP_VERSION_ID < 80100
|
||||
? '\Closure::fromCallable(' . $this->dump($inner) . ')'
|
||||
: implode('::', (array) $inner) . '(...)';
|
||||
}
|
||||
|
||||
throw new Nette\InvalidArgumentException('Cannot dump closure.');
|
||||
}
|
||||
|
||||
$class = get_class($var);
|
||||
if ((new \ReflectionObject($var))->isAnonymous()) {
|
||||
throw new Nette\InvalidArgumentException('Cannot dump anonymous class.');
|
||||
|
||||
} elseif (in_array($class, [\DateTime::class, \DateTimeImmutable::class], true)) {
|
||||
return $this->format("new \\$class(?, new \\DateTimeZone(?))", $var->format('Y-m-d H:i:s.u'), $var->getTimeZone()->getName());
|
||||
}
|
||||
|
||||
$arr = (array) $var;
|
||||
$space = str_repeat($this->indentation, $level);
|
||||
|
||||
if ($level > $this->maxDepth || in_array($var, $parents, true)) {
|
||||
throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.');
|
||||
}
|
||||
|
||||
$out = "\n";
|
||||
$parents[] = $var;
|
||||
if (method_exists($var, '__sleep')) {
|
||||
foreach ($var->__sleep() as $v) {
|
||||
$props[$v] = $props["\x00*\x00$v"] = $props["\x00$class\x00$v"] = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($arr as $k => &$v) {
|
||||
if (!isset($props) || isset($props[$k])) {
|
||||
$out .= $space . $this->indentation
|
||||
. ($keyPart = $this->dumpVar($k) . ' => ')
|
||||
. $this->dumpVar($v, $parents, $level + 1, strlen($keyPart))
|
||||
. ",\n";
|
||||
}
|
||||
}
|
||||
|
||||
array_pop($parents);
|
||||
$out .= $space;
|
||||
return $class === \stdClass::class
|
||||
? "(object) [$out]"
|
||||
: '\\' . self::class . "::createObject('$class', [$out])";
|
||||
}
|
||||
|
||||
|
||||
private function dumpLiteral(Literal $var, int $level): string
|
||||
{
|
||||
$s = $var->formatWith($this);
|
||||
$s = Nette\Utils\Strings::indent(trim($s), $level, $this->indentation);
|
||||
return ltrim($s, $this->indentation);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates PHP statement. Supports placeholders: ? \? $? ->? ::? ...? ...?: ?*
|
||||
*/
|
||||
public function format(string $statement, ...$args): string
|
||||
{
|
||||
$tokens = preg_split('#(\.\.\.\?:?|\$\?|->\?|::\?|\\\\\?|\?\*|\?(?!\w))#', $statement, -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||
$res = '';
|
||||
foreach ($tokens as $n => $token) {
|
||||
if ($n % 2 === 0) {
|
||||
$res .= $token;
|
||||
} elseif ($token === '\?') {
|
||||
$res .= '?';
|
||||
} elseif (!$args) {
|
||||
throw new Nette\InvalidArgumentException('Insufficient number of arguments.');
|
||||
} elseif ($token === '?') {
|
||||
$res .= $this->dump(array_shift($args), strlen($res) - strrpos($res, "\n"));
|
||||
} elseif ($token === '...?' || $token === '...?:' || $token === '?*') {
|
||||
$arg = array_shift($args);
|
||||
if (!is_array($arg)) {
|
||||
throw new Nette\InvalidArgumentException('Argument must be an array.');
|
||||
}
|
||||
|
||||
$res .= $this->dumpArguments($arg, strlen($res) - strrpos($res, "\n"), $token === '...?:');
|
||||
|
||||
} else { // $ -> ::
|
||||
$arg = array_shift($args);
|
||||
if ($arg instanceof Literal || !Helpers::isIdentifier($arg)) {
|
||||
$arg = '{' . $this->dumpVar($arg) . '}';
|
||||
}
|
||||
|
||||
$res .= substr($token, 0, -1) . $arg;
|
||||
}
|
||||
}
|
||||
|
||||
if ($args) {
|
||||
throw new Nette\InvalidArgumentException('Insufficient number of placeholders.');
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
private function dumpArguments(array &$var, int $column, bool $named): string
|
||||
{
|
||||
$outInline = $outWrapped = '';
|
||||
|
||||
foreach ($var as $k => &$v) {
|
||||
$k = !$named || is_int($k) ? '' : $k . ': ';
|
||||
$outInline .= $outInline === '' ? '' : ', ';
|
||||
$outInline .= $k . $this->dumpVar($v, [$var], 0, $column + strlen($outInline));
|
||||
$outWrapped .= ($outWrapped === '' ? '' : ',') . "\n"
|
||||
. $this->indentation . $k . $this->dumpVar($v, [$var], 1);
|
||||
}
|
||||
|
||||
return count($var) > 1 && (strpos($outInline, "\n") !== false || $column + strlen($outInline) > $this->wrapLength)
|
||||
? $outWrapped . "\n"
|
||||
: $outInline;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function createObject(string $class, array $props): object
|
||||
{
|
||||
return unserialize('O' . substr(serialize($class), 1, -1) . substr(serialize($props), 1));
|
||||
}
|
||||
}
|
||||
41
vendor/nette/php-generator/src/PhpGenerator/EnumCase.php
vendored
Normal file
41
vendor/nette/php-generator/src/PhpGenerator/EnumCase.php
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Enum case.
|
||||
*/
|
||||
final class EnumCase
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
use Traits\NameAware;
|
||||
use Traits\CommentAware;
|
||||
use Traits\AttributeAware;
|
||||
|
||||
/** @var string|int|null */
|
||||
private $value;
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setValue($val): self
|
||||
{
|
||||
$this->value = $val;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
444
vendor/nette/php-generator/src/PhpGenerator/Extractor.php
vendored
Normal file
444
vendor/nette/php-generator/src/PhpGenerator/Extractor.php
vendored
Normal file
@@ -0,0 +1,444 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
use Nette;
|
||||
use PhpParser;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeFinder;
|
||||
use PhpParser\ParserFactory;
|
||||
|
||||
|
||||
/**
|
||||
* Extracts information from PHP code.
|
||||
* @internal
|
||||
*/
|
||||
final class Extractor
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
private $code;
|
||||
private $statements;
|
||||
private $printer;
|
||||
|
||||
|
||||
public function __construct(string $code)
|
||||
{
|
||||
if (!class_exists(ParserFactory::class)) {
|
||||
throw new Nette\NotSupportedException("PHP-Parser is required to load method bodies, install package 'nikic/php-parser' 4.7 or newer.");
|
||||
}
|
||||
|
||||
$this->printer = new PhpParser\PrettyPrinter\Standard;
|
||||
$this->parseCode($code);
|
||||
}
|
||||
|
||||
|
||||
private function parseCode(string $code): void
|
||||
{
|
||||
if (substr($code, 0, 5) !== '<?php') {
|
||||
throw new Nette\InvalidStateException('The input string is not a PHP code.');
|
||||
}
|
||||
|
||||
$this->code = str_replace("\r\n", "\n", $code);
|
||||
$lexer = new PhpParser\Lexer\Emulative(['usedAttributes' => ['startFilePos', 'endFilePos', 'comments']]);
|
||||
$parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7, $lexer);
|
||||
$stmts = $parser->parse($this->code);
|
||||
|
||||
$traverser = new PhpParser\NodeTraverser;
|
||||
$traverser->addVisitor(new PhpParser\NodeVisitor\ParentConnectingVisitor);
|
||||
$traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver(null, ['preserveOriginalNames' => true]));
|
||||
$this->statements = $traverser->traverse($stmts);
|
||||
}
|
||||
|
||||
|
||||
public function extractMethodBodies(string $className): array
|
||||
{
|
||||
$nodeFinder = new NodeFinder;
|
||||
$classNode = $nodeFinder->findFirst($this->statements, function (Node $node) use ($className) {
|
||||
return $node instanceof Node\Stmt\ClassLike && $node->namespacedName->toString() === $className;
|
||||
});
|
||||
|
||||
$res = [];
|
||||
foreach ($nodeFinder->findInstanceOf($classNode, Node\Stmt\ClassMethod::class) as $methodNode) {
|
||||
/** @var Node\Stmt\ClassMethod $methodNode */
|
||||
if ($methodNode->stmts) {
|
||||
$res[$methodNode->name->toString()] = $this->getReformattedContents($methodNode->stmts, 2);
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
public function extractFunctionBody(string $name): ?string
|
||||
{
|
||||
/** @var Node\Stmt\Function_ $functionNode */
|
||||
$functionNode = (new NodeFinder)->findFirst($this->statements, function (Node $node) use ($name) {
|
||||
return $node instanceof Node\Stmt\Function_ && $node->namespacedName->toString() === $name;
|
||||
});
|
||||
|
||||
return $this->getReformattedContents($functionNode->stmts, 1);
|
||||
}
|
||||
|
||||
|
||||
/** @param Node[] $statements */
|
||||
private function getReformattedContents(array $statements, int $level): string
|
||||
{
|
||||
$body = $this->getNodeContents(...$statements);
|
||||
$body = $this->performReplacements($body, $this->prepareReplacements($statements));
|
||||
return Helpers::unindent($body, $level);
|
||||
}
|
||||
|
||||
|
||||
private function prepareReplacements(array $statements): array
|
||||
{
|
||||
$start = $this->getNodeStartPos($statements[0]);
|
||||
$replacements = [];
|
||||
(new NodeFinder)->find($statements, function (Node $node) use (&$replacements, $start) {
|
||||
if ($node instanceof Node\Name\FullyQualified) {
|
||||
if ($node->getAttribute('originalName') instanceof Node\Name) {
|
||||
$of = $node->getAttribute('parent') instanceof Node\Expr\ConstFetch
|
||||
? PhpNamespace::NameConstant
|
||||
: ($node->getAttribute('parent') instanceof Node\Expr\FuncCall ? PhpNamespace::NameFunction : PhpNamespace::NameNormal);
|
||||
$replacements[] = [
|
||||
$node->getStartFilePos() - $start,
|
||||
$node->getEndFilePos() - $start,
|
||||
Helpers::tagName($node->toCodeString(), $of),
|
||||
];
|
||||
}
|
||||
} elseif ($node instanceof Node\Scalar\String_ || $node instanceof Node\Scalar\EncapsedStringPart) {
|
||||
// multi-line strings => singleline
|
||||
$token = $this->getNodeContents($node);
|
||||
if (strpos($token, "\n") !== false) {
|
||||
$quote = $node instanceof Node\Scalar\String_ ? '"' : '';
|
||||
$replacements[] = [
|
||||
$node->getStartFilePos() - $start,
|
||||
$node->getEndFilePos() - $start,
|
||||
$quote . addcslashes($node->value, "\x00..\x1F") . $quote,
|
||||
];
|
||||
}
|
||||
} elseif ($node instanceof Node\Scalar\Encapsed) {
|
||||
// HEREDOC => "string"
|
||||
if ($node->getAttribute('kind') === Node\Scalar\String_::KIND_HEREDOC) {
|
||||
$replacements[] = [
|
||||
$node->getStartFilePos() - $start,
|
||||
$node->parts[0]->getStartFilePos() - $start - 1,
|
||||
'"',
|
||||
];
|
||||
$replacements[] = [
|
||||
end($node->parts)->getEndFilePos() - $start + 1,
|
||||
$node->getEndFilePos() - $start,
|
||||
'"',
|
||||
];
|
||||
}
|
||||
}
|
||||
});
|
||||
return $replacements;
|
||||
}
|
||||
|
||||
|
||||
private function performReplacements(string $s, array $replacements): string
|
||||
{
|
||||
usort($replacements, function ($a, $b) { // sort by position in file
|
||||
return $b[0] <=> $a[0];
|
||||
});
|
||||
|
||||
foreach ($replacements as [$start, $end, $replacement]) {
|
||||
$s = substr_replace($s, $replacement, $start, $end - $start + 1);
|
||||
}
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
public function extractAll(): PhpFile
|
||||
{
|
||||
$phpFile = new PhpFile;
|
||||
$namespace = '';
|
||||
$visitor = new class extends PhpParser\NodeVisitorAbstract {
|
||||
public $callback;
|
||||
|
||||
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
return ($this->callback)($node);
|
||||
}
|
||||
};
|
||||
|
||||
$visitor->callback = function (Node $node) use (&$class, &$namespace, $phpFile) {
|
||||
if ($node instanceof Node\Stmt\DeclareDeclare && $node->key->name === 'strict_types') {
|
||||
$phpFile->setStrictTypes((bool) $node->value->value);
|
||||
} elseif ($node instanceof Node\Stmt\Namespace_) {
|
||||
$namespace = $node->name ? $node->name->toString() : '';
|
||||
} elseif ($node instanceof Node\Stmt\Use_) {
|
||||
$this->addUseToNamespace($node, $phpFile->addNamespace($namespace));
|
||||
} elseif ($node instanceof Node\Stmt\Class_) {
|
||||
if (!$node->name) {
|
||||
return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN;
|
||||
}
|
||||
|
||||
$class = $this->addClassToFile($phpFile, $node);
|
||||
} elseif ($node instanceof Node\Stmt\Interface_) {
|
||||
$class = $this->addInterfaceToFile($phpFile, $node);
|
||||
} elseif ($node instanceof Node\Stmt\Trait_) {
|
||||
$class = $this->addTraitToFile($phpFile, $node);
|
||||
} elseif ($node instanceof Node\Stmt\Enum_) {
|
||||
$class = $this->addEnumToFile($phpFile, $node);
|
||||
} elseif ($node instanceof Node\Stmt\Function_) {
|
||||
$this->addFunctionToFile($phpFile, $node);
|
||||
} elseif ($node instanceof Node\Stmt\TraitUse) {
|
||||
$this->addTraitToClass($class, $node);
|
||||
} elseif ($node instanceof Node\Stmt\Property) {
|
||||
$this->addPropertyToClass($class, $node);
|
||||
} elseif ($node instanceof Node\Stmt\ClassMethod) {
|
||||
$this->addMethodToClass($class, $node);
|
||||
} elseif ($node instanceof Node\Stmt\ClassConst) {
|
||||
$this->addConstantToClass($class, $node);
|
||||
} elseif ($node instanceof Node\Stmt\EnumCase) {
|
||||
$this->addEnumCaseToClass($class, $node);
|
||||
}
|
||||
|
||||
if ($node instanceof Node\FunctionLike) {
|
||||
return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN;
|
||||
}
|
||||
};
|
||||
|
||||
if ($this->statements) {
|
||||
$this->addCommentAndAttributes($phpFile, $this->statements[0]);
|
||||
}
|
||||
|
||||
$traverser = new PhpParser\NodeTraverser;
|
||||
$traverser->addVisitor($visitor);
|
||||
$traverser->traverse($this->statements);
|
||||
return $phpFile;
|
||||
}
|
||||
|
||||
|
||||
private function addUseToNamespace(Node\Stmt\Use_ $node, PhpNamespace $namespace): void
|
||||
{
|
||||
$of = [
|
||||
$node::TYPE_NORMAL => PhpNamespace::NameNormal,
|
||||
$node::TYPE_FUNCTION => PhpNamespace::NameFunction,
|
||||
$node::TYPE_CONSTANT => PhpNamespace::NameConstant,
|
||||
][$node->type];
|
||||
foreach ($node->uses as $use) {
|
||||
$namespace->addUse($use->name->toString(), $use->alias ? $use->alias->toString() : null, $of);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function addClassToFile(PhpFile $phpFile, Node\Stmt\Class_ $node): ClassType
|
||||
{
|
||||
$class = $phpFile->addClass($node->namespacedName->toString());
|
||||
if ($node->extends) {
|
||||
$class->setExtends($node->extends->toString());
|
||||
}
|
||||
|
||||
foreach ($node->implements as $item) {
|
||||
$class->addImplement($item->toString());
|
||||
}
|
||||
|
||||
$class->setFinal($node->isFinal());
|
||||
$class->setAbstract($node->isAbstract());
|
||||
$this->addCommentAndAttributes($class, $node);
|
||||
return $class;
|
||||
}
|
||||
|
||||
|
||||
private function addInterfaceToFile(PhpFile $phpFile, Node\Stmt\Interface_ $node): ClassType
|
||||
{
|
||||
$class = $phpFile->addInterface($node->namespacedName->toString());
|
||||
foreach ($node->extends as $item) {
|
||||
$class->addExtend($item->toString());
|
||||
}
|
||||
|
||||
$this->addCommentAndAttributes($class, $node);
|
||||
return $class;
|
||||
}
|
||||
|
||||
|
||||
private function addTraitToFile(PhpFile $phpFile, Node\Stmt\Trait_ $node): ClassType
|
||||
{
|
||||
$class = $phpFile->addTrait($node->namespacedName->toString());
|
||||
$this->addCommentAndAttributes($class, $node);
|
||||
return $class;
|
||||
}
|
||||
|
||||
|
||||
private function addEnumToFile(PhpFile $phpFile, Node\Stmt\Enum_ $node): ClassType
|
||||
{
|
||||
$class = $phpFile->addEnum($node->namespacedName->toString());
|
||||
foreach ($node->implements as $item) {
|
||||
$class->addImplement($item->toString());
|
||||
}
|
||||
|
||||
$this->addCommentAndAttributes($class, $node);
|
||||
return $class;
|
||||
}
|
||||
|
||||
|
||||
private function addFunctionToFile(PhpFile $phpFile, Node\Stmt\Function_ $node): void
|
||||
{
|
||||
$function = $phpFile->addFunction($node->namespacedName->toString());
|
||||
$this->setupFunction($function, $node);
|
||||
}
|
||||
|
||||
|
||||
private function addTraitToClass(ClassType $class, Node\Stmt\TraitUse $node): void
|
||||
{
|
||||
foreach ($node->traits as $item) {
|
||||
$trait = $class->addTrait($item->toString(), true);
|
||||
}
|
||||
|
||||
foreach ($node->adaptations as $item) {
|
||||
$trait->addResolution(trim($this->toPhp($item), ';'));
|
||||
}
|
||||
|
||||
$this->addCommentAndAttributes($trait, $node);
|
||||
}
|
||||
|
||||
|
||||
private function addPropertyToClass(ClassType $class, Node\Stmt\Property $node): void
|
||||
{
|
||||
foreach ($node->props as $item) {
|
||||
$prop = $class->addProperty($item->name->toString());
|
||||
$prop->setStatic($node->isStatic());
|
||||
$prop->setVisibility($this->toVisibility($node->flags));
|
||||
$prop->setType($node->type ? $this->toPhp($node->type) : null);
|
||||
if ($item->default) {
|
||||
$prop->setValue(new Literal($this->getReformattedContents([$item->default], 1)));
|
||||
}
|
||||
|
||||
$prop->setReadOnly(method_exists($node, 'isReadonly') && $node->isReadonly());
|
||||
$this->addCommentAndAttributes($prop, $node);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function addMethodToClass(ClassType $class, Node\Stmt\ClassMethod $node): void
|
||||
{
|
||||
$method = $class->addMethod($node->name->toString());
|
||||
$method->setAbstract($node->isAbstract());
|
||||
$method->setFinal($node->isFinal());
|
||||
$method->setStatic($node->isStatic());
|
||||
$method->setVisibility($this->toVisibility($node->flags));
|
||||
$this->setupFunction($method, $node);
|
||||
}
|
||||
|
||||
|
||||
private function addConstantToClass(ClassType $class, Node\Stmt\ClassConst $node): void
|
||||
{
|
||||
foreach ($node->consts as $item) {
|
||||
$value = $this->getReformattedContents([$item->value], 1);
|
||||
$const = $class->addConstant($item->name->toString(), new Literal($value));
|
||||
$const->setVisibility($this->toVisibility($node->flags));
|
||||
$const->setFinal(method_exists($node, 'isFinal') && $node->isFinal());
|
||||
$this->addCommentAndAttributes($const, $node);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function addEnumCaseToClass(ClassType $class, Node\Stmt\EnumCase $node)
|
||||
{
|
||||
$case = $class->addCase($node->name->toString(), $node->expr ? $node->expr->value : null);
|
||||
$this->addCommentAndAttributes($case, $node);
|
||||
}
|
||||
|
||||
|
||||
private function addCommentAndAttributes($element, Node $node): void
|
||||
{
|
||||
if ($node->getDocComment()) {
|
||||
$comment = $node->getDocComment()->getReformattedText();
|
||||
$comment = Helpers::unformatDocComment($comment);
|
||||
$element->setComment($comment);
|
||||
$node->setDocComment(new PhpParser\Comment\Doc(''));
|
||||
}
|
||||
|
||||
foreach ($node->attrGroups ?? [] as $group) {
|
||||
foreach ($group->attrs as $attribute) {
|
||||
$args = [];
|
||||
foreach ($attribute->args as $arg) {
|
||||
$value = new Literal($this->getReformattedContents([$arg->value], 0));
|
||||
if ($arg->name) {
|
||||
$args[$arg->name->toString()] = $value;
|
||||
} else {
|
||||
$args[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$element->addAttribute($attribute->name->toString(), $args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param GlobalFunction|Method $function
|
||||
*/
|
||||
private function setupFunction($function, Node\FunctionLike $node): void
|
||||
{
|
||||
$function->setReturnReference($node->returnsByRef());
|
||||
$function->setReturnType($node->getReturnType() ? $this->toPhp($node->getReturnType()) : null);
|
||||
foreach ($node->getParams() as $item) {
|
||||
$visibility = $this->toVisibility($item->flags);
|
||||
$isReadonly = (bool) ($item->flags & Node\Stmt\Class_::MODIFIER_READONLY);
|
||||
$param = $visibility
|
||||
? ($function->addPromotedParameter($item->var->name))->setVisibility($visibility)->setReadonly($isReadonly)
|
||||
: $function->addParameter($item->var->name);
|
||||
$param->setType($item->type ? $this->toPhp($item->type) : null);
|
||||
$param->setReference($item->byRef);
|
||||
$function->setVariadic($item->variadic);
|
||||
if ($item->default) {
|
||||
$param->setDefaultValue(new Literal($this->getReformattedContents([$item->default], 2)));
|
||||
}
|
||||
|
||||
$this->addCommentAndAttributes($param, $item);
|
||||
}
|
||||
|
||||
$this->addCommentAndAttributes($function, $node);
|
||||
if ($node->getStmts()) {
|
||||
$function->setBody($this->getReformattedContents($node->getStmts(), 2));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function toVisibility(int $flags): ?string
|
||||
{
|
||||
if ($flags & Node\Stmt\Class_::MODIFIER_PUBLIC) {
|
||||
return ClassType::VisibilityPublic;
|
||||
} elseif ($flags & Node\Stmt\Class_::MODIFIER_PROTECTED) {
|
||||
return ClassType::VisibilityProtected;
|
||||
} elseif ($flags & Node\Stmt\Class_::MODIFIER_PRIVATE) {
|
||||
return ClassType::VisibilityPrivate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private function toPhp($value): string
|
||||
{
|
||||
return $this->printer->prettyPrint([$value]);
|
||||
}
|
||||
|
||||
|
||||
private function getNodeContents(Node ...$nodes): string
|
||||
{
|
||||
$start = $this->getNodeStartPos($nodes[0]);
|
||||
return substr($this->code, $start, end($nodes)->getEndFilePos() - $start + 1);
|
||||
}
|
||||
|
||||
|
||||
private function getNodeStartPos(Node $node): int
|
||||
{
|
||||
return ($comments = $node->getComments())
|
||||
? $comments[0]->getStartFilePos()
|
||||
: $node->getStartFilePos();
|
||||
}
|
||||
}
|
||||
368
vendor/nette/php-generator/src/PhpGenerator/Factory.php
vendored
Normal file
368
vendor/nette/php-generator/src/PhpGenerator/Factory.php
vendored
Normal file
@@ -0,0 +1,368 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
use Nette;
|
||||
use Nette\Utils\Reflection;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a representation based on reflection.
|
||||
*/
|
||||
final class Factory
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
private $bodyCache = [];
|
||||
private $extractorCache = [];
|
||||
|
||||
|
||||
public function fromClassReflection(
|
||||
\ReflectionClass $from,
|
||||
bool $withBodies = false,
|
||||
bool $materializeTraits = true
|
||||
): ClassType {
|
||||
if ($withBodies && $from->isAnonymous()) {
|
||||
throw new Nette\NotSupportedException('The $withBodies parameter cannot be used for anonymous functions.');
|
||||
}
|
||||
|
||||
$class = $from->isAnonymous()
|
||||
? new ClassType
|
||||
: new ClassType($from->getShortName(), new PhpNamespace($from->getNamespaceName()));
|
||||
|
||||
if (PHP_VERSION_ID >= 80100 && $from->isEnum()) {
|
||||
$class->setType($class::TYPE_ENUM);
|
||||
$from = new \ReflectionEnum($from->getName());
|
||||
$enumIface = $from->isBacked() ? \BackedEnum::class : \UnitEnum::class;
|
||||
} else {
|
||||
$class->setType($from->isInterface() ? $class::TYPE_INTERFACE : ($from->isTrait() ? $class::TYPE_TRAIT : $class::TYPE_CLASS));
|
||||
$class->setFinal($from->isFinal() && $class->isClass());
|
||||
$class->setAbstract($from->isAbstract() && $class->isClass());
|
||||
$enumIface = null;
|
||||
}
|
||||
|
||||
$ifaces = $from->getInterfaceNames();
|
||||
foreach ($ifaces as $iface) {
|
||||
$ifaces = array_filter($ifaces, function (string $item) use ($iface): bool {
|
||||
return !is_subclass_of($iface, $item);
|
||||
});
|
||||
}
|
||||
|
||||
if ($from->isInterface()) {
|
||||
$class->setExtends($ifaces);
|
||||
} else {
|
||||
$ifaces = array_diff($ifaces, [$enumIface]);
|
||||
$class->setImplements($ifaces);
|
||||
}
|
||||
|
||||
$class->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
|
||||
$class->setAttributes($this->getAttributes($from));
|
||||
if ($from->getParentClass()) {
|
||||
$class->setExtends($from->getParentClass()->name);
|
||||
$class->setImplements(array_diff($class->getImplements(), $from->getParentClass()->getInterfaceNames()));
|
||||
}
|
||||
|
||||
$props = [];
|
||||
foreach ($from->getProperties() as $prop) {
|
||||
$declaringClass = $materializeTraits
|
||||
? $prop->getDeclaringClass()
|
||||
: Reflection::getPropertyDeclaringClass($prop);
|
||||
|
||||
if ($prop->isDefault()
|
||||
&& $declaringClass->name === $from->name
|
||||
&& (PHP_VERSION_ID < 80000 || !$prop->isPromoted())
|
||||
&& !$class->isEnum()
|
||||
) {
|
||||
$props[] = $this->fromPropertyReflection($prop);
|
||||
}
|
||||
}
|
||||
|
||||
$class->setProperties($props);
|
||||
|
||||
$methods = $resolutions = [];
|
||||
foreach ($from->getMethods() as $method) {
|
||||
$realMethod = Reflection::getMethodDeclaringMethod($method);
|
||||
$declaringClass = ($materializeTraits ? $method : $realMethod)->getDeclaringClass();
|
||||
|
||||
if (
|
||||
$declaringClass->name === $from->name
|
||||
&& (!$enumIface || !method_exists($enumIface, $method->name))
|
||||
) {
|
||||
$methods[] = $m = $this->fromMethodReflection($method);
|
||||
if ($withBodies) {
|
||||
$realMethodClass = $realMethod->getDeclaringClass();
|
||||
$bodies = &$this->bodyCache[$realMethodClass->name];
|
||||
$bodies = $bodies ?? $this->getExtractor($realMethodClass)->extractMethodBodies($realMethodClass->name);
|
||||
if (isset($bodies[$realMethod->name])) {
|
||||
$m->setBody($bodies[$realMethod->name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$modifier = $realMethod->getModifiers() !== $method->getModifiers()
|
||||
? ' ' . $this->getVisibility($method)
|
||||
: null;
|
||||
$alias = $realMethod->name !== $method->name ? ' ' . $method->name : '';
|
||||
if ($modifier || $alias) {
|
||||
$resolutions[] = $realMethod->name . ' as' . $modifier . $alias;
|
||||
}
|
||||
}
|
||||
|
||||
$class->setMethods($methods);
|
||||
|
||||
if (!$materializeTraits) {
|
||||
foreach ($from->getTraitNames() as $trait) {
|
||||
$class->addTrait($trait, $resolutions);
|
||||
$resolutions = [];
|
||||
}
|
||||
}
|
||||
|
||||
$consts = $cases = [];
|
||||
foreach ($from->getReflectionConstants() as $const) {
|
||||
if ($class->isEnum() && $from->hasCase($const->name)) {
|
||||
$cases[] = $this->fromCaseReflection($const);
|
||||
} elseif ($const->getDeclaringClass()->name === $from->name) {
|
||||
$consts[] = $this->fromConstantReflection($const);
|
||||
}
|
||||
}
|
||||
|
||||
$class->setConstants($consts);
|
||||
$class->setCases($cases);
|
||||
|
||||
return $class;
|
||||
}
|
||||
|
||||
|
||||
public function fromMethodReflection(\ReflectionMethod $from): Method
|
||||
{
|
||||
$method = new Method($from->name);
|
||||
$method->setParameters(array_map([$this, 'fromParameterReflection'], $from->getParameters()));
|
||||
$method->setStatic($from->isStatic());
|
||||
$isInterface = $from->getDeclaringClass()->isInterface();
|
||||
$method->setVisibility($isInterface ? null : $this->getVisibility($from));
|
||||
$method->setFinal($from->isFinal());
|
||||
$method->setAbstract($from->isAbstract() && !$isInterface);
|
||||
$method->setBody($from->isAbstract() ? null : '');
|
||||
$method->setReturnReference($from->returnsReference());
|
||||
$method->setVariadic($from->isVariadic());
|
||||
$method->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
|
||||
$method->setAttributes($this->getAttributes($from));
|
||||
if ($from->getReturnType() instanceof \ReflectionNamedType) {
|
||||
$method->setReturnType($from->getReturnType()->getName());
|
||||
$method->setReturnNullable($from->getReturnType()->allowsNull());
|
||||
} elseif (
|
||||
$from->getReturnType() instanceof \ReflectionUnionType
|
||||
|| $from->getReturnType() instanceof \ReflectionIntersectionType
|
||||
) {
|
||||
$method->setReturnType((string) $from->getReturnType());
|
||||
}
|
||||
|
||||
return $method;
|
||||
}
|
||||
|
||||
|
||||
/** @return GlobalFunction|Closure */
|
||||
public function fromFunctionReflection(\ReflectionFunction $from, bool $withBody = false)
|
||||
{
|
||||
$function = $from->isClosure() ? new Closure : new GlobalFunction($from->name);
|
||||
$function->setParameters(array_map([$this, 'fromParameterReflection'], $from->getParameters()));
|
||||
$function->setReturnReference($from->returnsReference());
|
||||
$function->setVariadic($from->isVariadic());
|
||||
if (!$from->isClosure()) {
|
||||
$function->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
|
||||
}
|
||||
|
||||
$function->setAttributes($this->getAttributes($from));
|
||||
if ($from->getReturnType() instanceof \ReflectionNamedType) {
|
||||
$function->setReturnType($from->getReturnType()->getName());
|
||||
$function->setReturnNullable($from->getReturnType()->allowsNull());
|
||||
} elseif (
|
||||
$from->getReturnType() instanceof \ReflectionUnionType
|
||||
|| $from->getReturnType() instanceof \ReflectionIntersectionType
|
||||
) {
|
||||
$function->setReturnType((string) $from->getReturnType());
|
||||
}
|
||||
|
||||
if ($withBody) {
|
||||
if ($from->isClosure()) {
|
||||
throw new Nette\NotSupportedException('The $withBody parameter cannot be used for closures.');
|
||||
}
|
||||
|
||||
$function->setBody($this->getExtractor($from)->extractFunctionBody($from->name));
|
||||
}
|
||||
|
||||
return $function;
|
||||
}
|
||||
|
||||
|
||||
/** @return Method|GlobalFunction|Closure */
|
||||
public function fromCallable(callable $from)
|
||||
{
|
||||
$ref = Nette\Utils\Callback::toReflection($from);
|
||||
return $ref instanceof \ReflectionMethod
|
||||
? $this->fromMethodReflection($ref)
|
||||
: $this->fromFunctionReflection($ref);
|
||||
}
|
||||
|
||||
|
||||
public function fromParameterReflection(\ReflectionParameter $from): Parameter
|
||||
{
|
||||
$param = PHP_VERSION_ID >= 80000 && $from->isPromoted()
|
||||
? new PromotedParameter($from->name)
|
||||
: new Parameter($from->name);
|
||||
$param->setReference($from->isPassedByReference());
|
||||
if ($from->getType() instanceof \ReflectionNamedType) {
|
||||
$param->setType($from->getType()->getName());
|
||||
$param->setNullable($from->getType()->allowsNull());
|
||||
} elseif (
|
||||
$from->getType() instanceof \ReflectionUnionType
|
||||
|| $from->getType() instanceof \ReflectionIntersectionType
|
||||
) {
|
||||
$param->setType((string) $from->getType());
|
||||
}
|
||||
|
||||
if ($from->isDefaultValueAvailable()) {
|
||||
if ($from->isDefaultValueConstant()) {
|
||||
$parts = explode('::', $from->getDefaultValueConstantName());
|
||||
if (count($parts) > 1) {
|
||||
$parts[0] = Helpers::tagName($parts[0]);
|
||||
}
|
||||
|
||||
$param->setDefaultValue(new Literal(implode('::', $parts)));
|
||||
} elseif (is_object($from->getDefaultValue())) {
|
||||
$param->setDefaultValue($this->fromObject($from->getDefaultValue()));
|
||||
} else {
|
||||
$param->setDefaultValue($from->getDefaultValue());
|
||||
}
|
||||
|
||||
$param->setNullable($param->isNullable() && $param->getDefaultValue() !== null);
|
||||
}
|
||||
|
||||
$param->setAttributes($this->getAttributes($from));
|
||||
return $param;
|
||||
}
|
||||
|
||||
|
||||
public function fromConstantReflection(\ReflectionClassConstant $from): Constant
|
||||
{
|
||||
$const = new Constant($from->name);
|
||||
$const->setValue($from->getValue());
|
||||
$const->setVisibility($this->getVisibility($from));
|
||||
$const->setFinal(PHP_VERSION_ID >= 80100 ? $from->isFinal() : false);
|
||||
$const->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
|
||||
$const->setAttributes($this->getAttributes($from));
|
||||
return $const;
|
||||
}
|
||||
|
||||
|
||||
public function fromCaseReflection(\ReflectionClassConstant $from): EnumCase
|
||||
{
|
||||
$const = new EnumCase($from->name);
|
||||
$const->setValue($from->getValue()->value ?? null);
|
||||
$const->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
|
||||
$const->setAttributes($this->getAttributes($from));
|
||||
return $const;
|
||||
}
|
||||
|
||||
|
||||
public function fromPropertyReflection(\ReflectionProperty $from): Property
|
||||
{
|
||||
$defaults = $from->getDeclaringClass()->getDefaultProperties();
|
||||
$prop = new Property($from->name);
|
||||
$prop->setValue($defaults[$prop->getName()] ?? null);
|
||||
$prop->setStatic($from->isStatic());
|
||||
$prop->setVisibility($this->getVisibility($from));
|
||||
if (PHP_VERSION_ID >= 70400) {
|
||||
if ($from->getType() instanceof \ReflectionNamedType) {
|
||||
$prop->setType($from->getType()->getName());
|
||||
$prop->setNullable($from->getType()->allowsNull());
|
||||
} elseif (
|
||||
$from->getType() instanceof \ReflectionUnionType
|
||||
|| $from->getType() instanceof \ReflectionIntersectionType
|
||||
) {
|
||||
$prop->setType((string) $from->getType());
|
||||
}
|
||||
|
||||
$prop->setInitialized($from->hasType() && array_key_exists($prop->getName(), $defaults));
|
||||
$prop->setReadOnly(PHP_VERSION_ID >= 80100 ? $from->isReadOnly() : false);
|
||||
} else {
|
||||
$prop->setInitialized(false);
|
||||
}
|
||||
|
||||
$prop->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
|
||||
$prop->setAttributes($this->getAttributes($from));
|
||||
return $prop;
|
||||
}
|
||||
|
||||
|
||||
public function fromObject(object $obj): Literal
|
||||
{
|
||||
return new Literal('new \\' . get_class($obj) . '(/* unknown */)');
|
||||
}
|
||||
|
||||
|
||||
public function fromClassCode(string $code): ClassType
|
||||
{
|
||||
$classes = $this->fromCode($code)->getClasses();
|
||||
if (!$classes) {
|
||||
throw new Nette\InvalidStateException('The code does not contain any class.');
|
||||
}
|
||||
|
||||
return reset($classes);
|
||||
}
|
||||
|
||||
|
||||
public function fromCode(string $code): PhpFile
|
||||
{
|
||||
$reader = new Extractor($code);
|
||||
return $reader->extractAll();
|
||||
}
|
||||
|
||||
|
||||
private function getAttributes($from): array
|
||||
{
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(function ($attr) {
|
||||
$args = $attr->getArguments();
|
||||
foreach ($args as &$arg) {
|
||||
if (is_object($arg)) {
|
||||
$arg = $this->fromObject($arg);
|
||||
}
|
||||
}
|
||||
|
||||
return new Attribute($attr->getName(), $args);
|
||||
}, $from->getAttributes());
|
||||
}
|
||||
|
||||
|
||||
private function getVisibility($from): string
|
||||
{
|
||||
return $from->isPrivate()
|
||||
? ClassType::VisibilityPrivate
|
||||
: ($from->isProtected() ? ClassType::VisibilityProtected : ClassType::VisibilityPublic);
|
||||
}
|
||||
|
||||
|
||||
private function getExtractor($from): Extractor
|
||||
{
|
||||
$file = $from->getFileName();
|
||||
$cache = &$this->extractorCache[$file];
|
||||
if ($cache !== null) {
|
||||
return $cache;
|
||||
} elseif (!$file) {
|
||||
throw new Nette\InvalidStateException("Source code of $from->name not found.");
|
||||
}
|
||||
|
||||
return new Extractor(file_get_contents($file));
|
||||
}
|
||||
}
|
||||
53
vendor/nette/php-generator/src/PhpGenerator/GlobalFunction.php
vendored
Normal file
53
vendor/nette/php-generator/src/PhpGenerator/GlobalFunction.php
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Global function.
|
||||
*
|
||||
* @property string $body
|
||||
*/
|
||||
final class GlobalFunction
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
use Traits\FunctionLike;
|
||||
use Traits\NameAware;
|
||||
use Traits\CommentAware;
|
||||
use Traits\AttributeAware;
|
||||
|
||||
public static function from(string $function, bool $withBody = false): self
|
||||
{
|
||||
return (new Factory)->fromFunctionReflection(new \ReflectionFunction($function), $withBody);
|
||||
}
|
||||
|
||||
|
||||
public static function withBodyFrom(string $function): self
|
||||
{
|
||||
return (new Factory)->fromFunctionReflection(new \ReflectionFunction($function), true);
|
||||
}
|
||||
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
try {
|
||||
return (new Printer)->printFunction($this);
|
||||
} catch (\Throwable $e) {
|
||||
if (PHP_VERSION_ID >= 70400) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
184
vendor/nette/php-generator/src/PhpGenerator/Helpers.php
vendored
Normal file
184
vendor/nette/php-generator/src/PhpGenerator/Helpers.php
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Helpers
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
public const ReIdentifier = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*';
|
||||
|
||||
public const Keywords = [
|
||||
// built-in types
|
||||
'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1,
|
||||
'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1, 'mixed' => 1, 'false' => 1,
|
||||
'never' => 1,
|
||||
|
||||
// class keywords
|
||||
'self' => 1, 'parent' => 1, 'static' => 1,
|
||||
|
||||
// PHP keywords
|
||||
'include' => 1, 'include_once' => 1, 'eval' => 1, 'require' => 1, 'require_once' => 1, 'or' => 1, 'xor' => 1,
|
||||
'and' => 1, 'instanceof' => 1, 'new' => 1, 'clone' => 1, 'exit' => 1, 'if' => 1, 'elseif' => 1, 'else' => 1,
|
||||
'endif' => 1, 'echo' => 1, 'do' => 1, 'while' => 1, 'endwhile' => 1, 'for' => 1, 'endfor' => 1, 'foreach' => 1,
|
||||
'endforeach' => 1, 'declare' => 1, 'enddeclare' => 1, 'as' => 1, 'try' => 1, 'catch' => 1, 'finally' => 1,
|
||||
'throw' => 1, 'use' => 1, 'insteadof' => 1, 'global' => 1, 'var' => 1, 'unset' => 1, 'isset' => 1, 'empty' => 1,
|
||||
'continue' => 1, 'goto' => 1, 'function' => 1, 'const' => 1, 'return' => 1, 'print' => 1, 'yield' => 1, 'list' => 1,
|
||||
'switch' => 1, 'endswitch' => 1, 'case' => 1, 'default' => 1, 'break' => 1,
|
||||
'extends' => 1, 'implements' => 1, 'namespace' => 1, 'trait' => 1, 'interface' => 1, 'class' => 1, '__CLASS__' => 1,
|
||||
'__TRAIT__' => 1, '__FUNCTION__' => 1, '__METHOD__' => 1, '__LINE__' => 1, '__FILE__' => 1, '__DIR__' => 1,
|
||||
'__NAMESPACE__' => 1, 'fn' => 1, 'match' => 1, 'enum' => 1, 'abstract' => 1, 'final' => 1,
|
||||
'private' => 1, 'protected' => 1, 'public' => 1, 'readonly' => 1,
|
||||
|
||||
// additional reserved class names
|
||||
'true' => 1,
|
||||
];
|
||||
|
||||
/** @deprecated */
|
||||
public const
|
||||
PHP_IDENT = self::ReIdentifier,
|
||||
KEYWORDS = self::Keywords;
|
||||
|
||||
|
||||
/** @deprecated use (new Nette\PhpGenerator\Dumper)->dump() */
|
||||
public static function dump($var): string
|
||||
{
|
||||
return (new Dumper)->dump($var);
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated use (new Nette\PhpGenerator\Dumper)->format() */
|
||||
public static function format(string $statement, ...$args): string
|
||||
{
|
||||
return (new Dumper)->format($statement, ...$args);
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated use (new Nette\PhpGenerator\Dumper)->format() */
|
||||
public static function formatArgs(string $statement, array $args): string
|
||||
{
|
||||
return (new Dumper)->format($statement, ...$args);
|
||||
}
|
||||
|
||||
|
||||
public static function formatDocComment(string $content): string
|
||||
{
|
||||
$s = trim($content);
|
||||
$s = str_replace('*/', '* /', $s);
|
||||
if ($s === '') {
|
||||
return '';
|
||||
} elseif (strpos($content, "\n") === false) {
|
||||
return "/** $s */\n";
|
||||
} else {
|
||||
return str_replace("\n", "\n * ", "/**\n$s") . "\n */\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function tagName(string $name, string $of = PhpNamespace::NameNormal): string
|
||||
{
|
||||
return isset(self::Keywords[strtolower($name)])
|
||||
? $name
|
||||
: "/*($of*/$name";
|
||||
}
|
||||
|
||||
|
||||
public static function simplifyTaggedNames(string $code, ?PhpNamespace $namespace): string
|
||||
{
|
||||
return preg_replace_callback('~/\*\(([ncf])\*/([\w\x7f-\xff\\\\]++)~', function ($m) use ($namespace) {
|
||||
[, $of, $name] = $m;
|
||||
return $namespace
|
||||
? $namespace->simplifyType($name, $of)
|
||||
: $name;
|
||||
}, $code);
|
||||
}
|
||||
|
||||
|
||||
public static function unformatDocComment(string $comment): string
|
||||
{
|
||||
return preg_replace('#^\s*\* ?#m', '', trim(trim(trim($comment), '/*')));
|
||||
}
|
||||
|
||||
|
||||
public static function unindent(string $s, int $level = 1): string
|
||||
{
|
||||
return $level
|
||||
? preg_replace('#^(\t| {4}){1,' . $level . '}#m', '', $s)
|
||||
: $s;
|
||||
}
|
||||
|
||||
|
||||
public static function isIdentifier($value): bool
|
||||
{
|
||||
return is_string($value) && preg_match('#^' . self::ReIdentifier . '$#D', $value);
|
||||
}
|
||||
|
||||
|
||||
public static function isNamespaceIdentifier($value, bool $allowLeadingSlash = false): bool
|
||||
{
|
||||
$re = '#^' . ($allowLeadingSlash ? '\\\\?' : '') . self::ReIdentifier . '(\\\\' . self::ReIdentifier . ')*$#D';
|
||||
return is_string($value) && preg_match($re, $value);
|
||||
}
|
||||
|
||||
|
||||
public static function extractNamespace(string $name): string
|
||||
{
|
||||
return ($pos = strrpos($name, '\\')) ? substr($name, 0, $pos) : '';
|
||||
}
|
||||
|
||||
|
||||
public static function extractShortName(string $name): string
|
||||
{
|
||||
return ($pos = strrpos($name, '\\')) === false
|
||||
? $name
|
||||
: substr($name, $pos + 1);
|
||||
}
|
||||
|
||||
|
||||
public static function tabsToSpaces(string $s, int $count = 4): string
|
||||
{
|
||||
return str_replace("\t", str_repeat(' ', $count), $s);
|
||||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
public static function createObject(string $class, array $props): object
|
||||
{
|
||||
return Dumper::createObject($class, $props);
|
||||
}
|
||||
|
||||
|
||||
public static function validateType(?string $type, bool &$nullable): ?string
|
||||
{
|
||||
if ($type === '' || $type === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!preg_match('#(?:
|
||||
\?[\w\\\\]+|
|
||||
[\w\\\\]+ (?: (&[\w\\\\]+)* | (\|[\w\\\\]+)* )
|
||||
)()$#xAD', $type)) {
|
||||
throw new Nette\InvalidArgumentException("Value '$type' is not valid type.");
|
||||
}
|
||||
|
||||
if ($type[0] === '?') {
|
||||
$nullable = true;
|
||||
return substr($type, 1);
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
45
vendor/nette/php-generator/src/PhpGenerator/Literal.php
vendored
Normal file
45
vendor/nette/php-generator/src/PhpGenerator/Literal.php
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
|
||||
/**
|
||||
* PHP literal value.
|
||||
*/
|
||||
class Literal
|
||||
{
|
||||
/** @var string */
|
||||
private $value;
|
||||
|
||||
/** @var ?array */
|
||||
private $args;
|
||||
|
||||
|
||||
public function __construct(string $value, ?array $args = null)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->args = $args;
|
||||
}
|
||||
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->formatWith(new Dumper);
|
||||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
public function formatWith(Dumper $dumper): string
|
||||
{
|
||||
return $this->args === null
|
||||
? $this->value
|
||||
: $dumper->format($this->value, ...$this->args);
|
||||
}
|
||||
}
|
||||
145
vendor/nette/php-generator/src/PhpGenerator/Method.php
vendored
Normal file
145
vendor/nette/php-generator/src/PhpGenerator/Method.php
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Class method.
|
||||
*
|
||||
* @property string|null $body
|
||||
*/
|
||||
final class Method
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
use Traits\FunctionLike;
|
||||
use Traits\NameAware;
|
||||
use Traits\VisibilityAware;
|
||||
use Traits\CommentAware;
|
||||
use Traits\AttributeAware;
|
||||
|
||||
/** @var string|null */
|
||||
private $body = '';
|
||||
|
||||
/** @var bool */
|
||||
private $static = false;
|
||||
|
||||
/** @var bool */
|
||||
private $final = false;
|
||||
|
||||
/** @var bool */
|
||||
private $abstract = false;
|
||||
|
||||
|
||||
/**
|
||||
* @param string|array $method
|
||||
*/
|
||||
public static function from($method): self
|
||||
{
|
||||
return (new Factory)->fromMethodReflection(Nette\Utils\Callback::toReflection($method));
|
||||
}
|
||||
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
try {
|
||||
return (new Printer)->printMethod($this);
|
||||
} catch (\Throwable $e) {
|
||||
if (PHP_VERSION_ID >= 70400) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setBody(?string $code, ?array $args = null): self
|
||||
{
|
||||
$this->body = $args === null || $code === null
|
||||
? $code
|
||||
: (new Dumper)->format($code, ...$args);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function getBody(): ?string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setStatic(bool $state = true): self
|
||||
{
|
||||
$this->static = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isStatic(): bool
|
||||
{
|
||||
return $this->static;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setFinal(bool $state = true): self
|
||||
{
|
||||
$this->final = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isFinal(): bool
|
||||
{
|
||||
return $this->final;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setAbstract(bool $state = true): self
|
||||
{
|
||||
$this->abstract = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isAbstract(): bool
|
||||
{
|
||||
return $this->abstract;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $name without $
|
||||
*/
|
||||
public function addPromotedParameter(string $name, $defaultValue = null): PromotedParameter
|
||||
{
|
||||
$param = new PromotedParameter($name);
|
||||
if (func_num_args() > 1) {
|
||||
$param->setDefaultValue($defaultValue);
|
||||
}
|
||||
|
||||
return $this->parameters[$name] = $param;
|
||||
}
|
||||
|
||||
|
||||
/** @throws Nette\InvalidStateException */
|
||||
public function validate(): void
|
||||
{
|
||||
if ($this->abstract && ($this->final || $this->visibility === ClassType::VisibilityPrivate)) {
|
||||
throw new Nette\InvalidStateException("Method $this->name() cannot be abstract and final or private at the same time.");
|
||||
}
|
||||
}
|
||||
}
|
||||
140
vendor/nette/php-generator/src/PhpGenerator/Parameter.php
vendored
Normal file
140
vendor/nette/php-generator/src/PhpGenerator/Parameter.php
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
use Nette;
|
||||
use Nette\Utils\Type;
|
||||
|
||||
|
||||
/**
|
||||
* Function/Method parameter description.
|
||||
*
|
||||
* @property mixed $defaultValue
|
||||
*/
|
||||
class Parameter
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
use Traits\NameAware;
|
||||
use Traits\AttributeAware;
|
||||
|
||||
/** @var bool */
|
||||
private $reference = false;
|
||||
|
||||
/** @var string|null */
|
||||
private $type;
|
||||
|
||||
/** @var bool */
|
||||
private $nullable = false;
|
||||
|
||||
/** @var bool */
|
||||
private $hasDefaultValue = false;
|
||||
|
||||
/** @var mixed */
|
||||
private $defaultValue;
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setReference(bool $state = true): self
|
||||
{
|
||||
$this->reference = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isReference(): bool
|
||||
{
|
||||
return $this->reference;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setType(?string $type): self
|
||||
{
|
||||
$this->type = Helpers::validateType($type, $this->nullable);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Type|string|null
|
||||
*/
|
||||
public function getType(bool $asObject = false)
|
||||
{
|
||||
return $asObject && $this->type
|
||||
? Type::fromString($this->type)
|
||||
: $this->type;
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated use setType() */
|
||||
public function setTypeHint(?string $type): self
|
||||
{
|
||||
return $this->setType($type);
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated use getType() */
|
||||
public function getTypeHint(): ?string
|
||||
{
|
||||
return $this->getType();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated just use setDefaultValue()
|
||||
* @return static
|
||||
*/
|
||||
public function setOptional(bool $state = true): self
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated, use setDefaultValue()', E_USER_DEPRECATED);
|
||||
$this->hasDefaultValue = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setNullable(bool $state = true): self
|
||||
{
|
||||
$this->nullable = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isNullable(): bool
|
||||
{
|
||||
return $this->nullable;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setDefaultValue($val): self
|
||||
{
|
||||
$this->defaultValue = $val;
|
||||
$this->hasDefaultValue = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function getDefaultValue()
|
||||
{
|
||||
return $this->defaultValue;
|
||||
}
|
||||
|
||||
|
||||
public function hasDefaultValue(): bool
|
||||
{
|
||||
return $this->hasDefaultValue;
|
||||
}
|
||||
|
||||
|
||||
public function validate(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
184
vendor/nette/php-generator/src/PhpGenerator/PhpFile.php
vendored
Normal file
184
vendor/nette/php-generator/src/PhpGenerator/PhpFile.php
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Instance of PHP file.
|
||||
*
|
||||
* Generates:
|
||||
* - opening tag (<?php)
|
||||
* - doc comments
|
||||
* - one or more namespaces
|
||||
*/
|
||||
final class PhpFile
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
use Traits\CommentAware;
|
||||
|
||||
/** @var PhpNamespace[] */
|
||||
private $namespaces = [];
|
||||
|
||||
/** @var bool */
|
||||
private $strictTypes = false;
|
||||
|
||||
|
||||
public static function fromCode(string $code): self
|
||||
{
|
||||
return (new Factory)->fromCode($code);
|
||||
}
|
||||
|
||||
|
||||
public function addClass(string $name): ClassType
|
||||
{
|
||||
return $this
|
||||
->addNamespace(Helpers::extractNamespace($name))
|
||||
->addClass(Helpers::extractShortName($name));
|
||||
}
|
||||
|
||||
|
||||
public function addInterface(string $name): ClassType
|
||||
{
|
||||
return $this
|
||||
->addNamespace(Helpers::extractNamespace($name))
|
||||
->addInterface(Helpers::extractShortName($name));
|
||||
}
|
||||
|
||||
|
||||
public function addTrait(string $name): ClassType
|
||||
{
|
||||
return $this
|
||||
->addNamespace(Helpers::extractNamespace($name))
|
||||
->addTrait(Helpers::extractShortName($name));
|
||||
}
|
||||
|
||||
|
||||
public function addEnum(string $name): ClassType
|
||||
{
|
||||
return $this
|
||||
->addNamespace(Helpers::extractNamespace($name))
|
||||
->addEnum(Helpers::extractShortName($name));
|
||||
}
|
||||
|
||||
|
||||
/** @param string|PhpNamespace $namespace */
|
||||
public function addNamespace($namespace): PhpNamespace
|
||||
{
|
||||
if ($namespace instanceof PhpNamespace) {
|
||||
$res = $this->namespaces[$namespace->getName()] = $namespace;
|
||||
|
||||
} elseif (is_string($namespace)) {
|
||||
$res = $this->namespaces[$namespace] = $this->namespaces[$namespace] ?? new PhpNamespace($namespace);
|
||||
|
||||
} else {
|
||||
throw new Nette\InvalidArgumentException('Argument must be string|PhpNamespace.');
|
||||
}
|
||||
|
||||
foreach ($this->namespaces as $namespace) {
|
||||
$namespace->setBracketedSyntax(count($this->namespaces) > 1 && isset($this->namespaces['']));
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
public function addFunction(string $name): GlobalFunction
|
||||
{
|
||||
return $this
|
||||
->addNamespace(Helpers::extractNamespace($name))
|
||||
->addFunction(Helpers::extractShortName($name));
|
||||
}
|
||||
|
||||
|
||||
/** @return PhpNamespace[] */
|
||||
public function getNamespaces(): array
|
||||
{
|
||||
return $this->namespaces;
|
||||
}
|
||||
|
||||
|
||||
/** @return ClassType[] */
|
||||
public function getClasses(): array
|
||||
{
|
||||
$classes = [];
|
||||
foreach ($this->namespaces as $n => $namespace) {
|
||||
$n .= $n ? '\\' : '';
|
||||
foreach ($namespace->getClasses() as $c => $class) {
|
||||
$classes[$n . $c] = $class;
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
|
||||
/** @return GlobalFunction[] */
|
||||
public function getFunctions(): array
|
||||
{
|
||||
$functions = [];
|
||||
foreach ($this->namespaces as $n => $namespace) {
|
||||
$n .= $n ? '\\' : '';
|
||||
foreach ($namespace->getFunctions() as $f => $function) {
|
||||
$functions[$n . $f] = $function;
|
||||
}
|
||||
}
|
||||
|
||||
return $functions;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function addUse(string $name, ?string $alias = null, string $of = PhpNamespace::NameNormal): self
|
||||
{
|
||||
$this->addNamespace('')->addUse($name, $alias, $of);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds declare(strict_types=1) to output.
|
||||
* @return static
|
||||
*/
|
||||
public function setStrictTypes(bool $on = true): self
|
||||
{
|
||||
$this->strictTypes = $on;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function hasStrictTypes(): bool
|
||||
{
|
||||
return $this->strictTypes;
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated use hasStrictTypes() */
|
||||
public function getStrictTypes(): bool
|
||||
{
|
||||
return $this->strictTypes;
|
||||
}
|
||||
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
try {
|
||||
return (new Printer)->printFile($this);
|
||||
} catch (\Throwable $e) {
|
||||
if (PHP_VERSION_ID >= 70400) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
15
vendor/nette/php-generator/src/PhpGenerator/PhpLiteral.php
vendored
Normal file
15
vendor/nette/php-generator/src/PhpGenerator/PhpLiteral.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\PhpGenerator;
|
||||
|
||||
|
||||
class PhpLiteral extends Literal
|
||||
{
|
||||
}
|
||||
367
vendor/nette/php-generator/src/PhpGenerator/PhpNamespace.php
vendored
Normal file
367
vendor/nette/php-generator/src/PhpGenerator/PhpNamespace.php
vendored
Normal file
@@ -0,0 +1,367 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
use Nette;
|
||||
use Nette\InvalidStateException;
|
||||
|
||||
|
||||
/**
|
||||
* Namespaced part of a PHP file.
|
||||
*
|
||||
* Generates:
|
||||
* - namespace statement
|
||||
* - variable amount of use statements
|
||||
* - one or more class declarations
|
||||
*/
|
||||
final class PhpNamespace
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
public const
|
||||
NameNormal = 'n',
|
||||
NameFunction = 'f',
|
||||
NameConstant = 'c';
|
||||
|
||||
public const
|
||||
NAME_NORMAL = self::NameNormal,
|
||||
NAME_FUNCTION = self::NameFunction,
|
||||
NAME_CONSTANT = self::NameConstant;
|
||||
|
||||
/** @var string */
|
||||
private $name;
|
||||
|
||||
/** @var bool */
|
||||
private $bracketedSyntax = false;
|
||||
|
||||
/** @var string[][] */
|
||||
private $aliases = [
|
||||
self::NameNormal => [],
|
||||
self::NameFunction => [],
|
||||
self::NameConstant => [],
|
||||
];
|
||||
|
||||
/** @var ClassType[] */
|
||||
private $classes = [];
|
||||
|
||||
/** @var GlobalFunction[] */
|
||||
private $functions = [];
|
||||
|
||||
|
||||
public function __construct(string $name)
|
||||
{
|
||||
if ($name !== '' && !Helpers::isNamespaceIdentifier($name)) {
|
||||
throw new Nette\InvalidArgumentException("Value '$name' is not valid name.");
|
||||
}
|
||||
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
* @internal
|
||||
*/
|
||||
public function setBracketedSyntax(bool $state = true): self
|
||||
{
|
||||
$this->bracketedSyntax = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function hasBracketedSyntax(): bool
|
||||
{
|
||||
return $this->bracketedSyntax;
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated use hasBracketedSyntax() */
|
||||
public function getBracketedSyntax(): bool
|
||||
{
|
||||
return $this->bracketedSyntax;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @throws InvalidStateException
|
||||
* @return static
|
||||
*/
|
||||
public function addUse(string $name, ?string $alias = null, string $of = self::NameNormal): self
|
||||
{
|
||||
if (
|
||||
!Helpers::isNamespaceIdentifier($name, true)
|
||||
|| (Helpers::isIdentifier($name) && isset(Helpers::Keywords[strtolower($name)]))
|
||||
) {
|
||||
throw new Nette\InvalidArgumentException("Value '$name' is not valid class/function/constant name.");
|
||||
|
||||
} elseif ($alias && (!Helpers::isIdentifier($alias) || isset(Helpers::Keywords[strtolower($alias)]))) {
|
||||
throw new Nette\InvalidArgumentException("Value '$alias' is not valid alias.");
|
||||
}
|
||||
|
||||
$name = ltrim($name, '\\');
|
||||
$aliases = array_change_key_case($this->aliases[$of]);
|
||||
$used = [self::NameNormal => $this->classes, self::NameFunction => $this->functions, self::NameConstant => []][$of];
|
||||
|
||||
if ($alias === null) {
|
||||
$base = Helpers::extractShortName($name);
|
||||
$counter = null;
|
||||
do {
|
||||
$alias = $base . $counter;
|
||||
$lower = strtolower($alias);
|
||||
$counter++;
|
||||
} while ((isset($aliases[$lower]) && strcasecmp($aliases[$lower], $name) !== 0) || isset($used[$lower]));
|
||||
} else {
|
||||
$lower = strtolower($alias);
|
||||
if (isset($aliases[$lower]) && strcasecmp($aliases[$lower], $name) !== 0) {
|
||||
throw new InvalidStateException(
|
||||
"Alias '$alias' used already for '{$aliases[$lower]}', cannot use for '$name'."
|
||||
);
|
||||
} elseif (isset($used[$lower])) {
|
||||
throw new Nette\InvalidStateException("Name '$alias' used already for '$this->name\\{$used[$lower]->getName()}'.");
|
||||
}
|
||||
}
|
||||
|
||||
$this->aliases[$of][$alias] = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function removeUse(string $name, string $of = self::NameNormal): void
|
||||
{
|
||||
foreach ($this->aliases[$of] as $alias => $item) {
|
||||
if (strcasecmp($item, $name) === 0) {
|
||||
unset($this->aliases[$of][$alias]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function addUseFunction(string $name, ?string $alias = null): self
|
||||
{
|
||||
return $this->addUse($name, $alias, self::NameFunction);
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function addUseConstant(string $name, ?string $alias = null): self
|
||||
{
|
||||
return $this->addUse($name, $alias, self::NameConstant);
|
||||
}
|
||||
|
||||
|
||||
/** @return string[] */
|
||||
public function getUses(string $of = self::NameNormal): array
|
||||
{
|
||||
asort($this->aliases[$of]);
|
||||
return array_filter(
|
||||
$this->aliases[$of],
|
||||
function ($name, $alias) { return strcasecmp(($this->name ? $this->name . '\\' : '') . $alias, $name); },
|
||||
ARRAY_FILTER_USE_BOTH
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated use simplifyName() */
|
||||
public function unresolveName(string $name): string
|
||||
{
|
||||
return $this->simplifyName($name);
|
||||
}
|
||||
|
||||
|
||||
public function resolveName(string $name, string $of = self::NameNormal): string
|
||||
{
|
||||
if (isset(Helpers::Keywords[strtolower($name)]) || $name === '') {
|
||||
return $name;
|
||||
} elseif ($name[0] === '\\') {
|
||||
return substr($name, 1);
|
||||
}
|
||||
|
||||
$aliases = array_change_key_case($this->aliases[$of]);
|
||||
if ($of !== self::NameNormal) {
|
||||
return $aliases[strtolower($name)]
|
||||
?? $this->resolveName(Helpers::extractNamespace($name) . '\\') . Helpers::extractShortName($name);
|
||||
}
|
||||
|
||||
$parts = explode('\\', $name, 2);
|
||||
return ($res = $aliases[strtolower($parts[0])] ?? null)
|
||||
? $res . (isset($parts[1]) ? '\\' . $parts[1] : '')
|
||||
: $this->name . ($this->name ? '\\' : '') . $name;
|
||||
}
|
||||
|
||||
|
||||
public function simplifyType(string $type, string $of = self::NameNormal): string
|
||||
{
|
||||
return preg_replace_callback('~[\w\x7f-\xff\\\\]+~', function ($m) use ($of) { return $this->simplifyName($m[0], $of); }, $type);
|
||||
}
|
||||
|
||||
|
||||
public function simplifyName(string $name, string $of = self::NameNormal): string
|
||||
{
|
||||
if (isset(Helpers::Keywords[strtolower($name)]) || $name === '') {
|
||||
return $name;
|
||||
}
|
||||
|
||||
$name = ltrim($name, '\\');
|
||||
|
||||
if ($of !== self::NameNormal) {
|
||||
foreach ($this->aliases[$of] as $alias => $original) {
|
||||
if (strcasecmp($original, $name) === 0) {
|
||||
return $alias;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->simplifyName(Helpers::extractNamespace($name) . '\\') . Helpers::extractShortName($name);
|
||||
}
|
||||
|
||||
$shortest = null;
|
||||
$relative = self::startsWith($name, $this->name . '\\')
|
||||
? substr($name, strlen($this->name) + 1)
|
||||
: null;
|
||||
|
||||
foreach ($this->aliases[$of] as $alias => $original) {
|
||||
if ($relative && self::startsWith($relative . '\\', $alias . '\\')) {
|
||||
$relative = null;
|
||||
}
|
||||
|
||||
if (self::startsWith($name . '\\', $original . '\\')) {
|
||||
$short = $alias . substr($name, strlen($original));
|
||||
if (!isset($shortest) || strlen($shortest) > strlen($short)) {
|
||||
$shortest = $short;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($shortest, $relative) && strlen($shortest) < strlen($relative)) {
|
||||
return $shortest;
|
||||
}
|
||||
|
||||
return $relative ?? $shortest ?? ($this->name ? '\\' : '') . $name;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function add(ClassType $class): self
|
||||
{
|
||||
$name = $class->getName();
|
||||
if ($name === null) {
|
||||
throw new Nette\InvalidArgumentException('Class does not have a name.');
|
||||
}
|
||||
|
||||
$lower = strtolower($name);
|
||||
if ($orig = array_change_key_case($this->aliases[self::NameNormal])[$lower] ?? null) {
|
||||
throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig.");
|
||||
}
|
||||
|
||||
$this->classes[$lower] = $class;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function addClass(string $name): ClassType
|
||||
{
|
||||
$this->add($class = new ClassType($name, $this));
|
||||
return $class;
|
||||
}
|
||||
|
||||
|
||||
public function addInterface(string $name): ClassType
|
||||
{
|
||||
return $this->addClass($name)->setType(ClassType::TYPE_INTERFACE);
|
||||
}
|
||||
|
||||
|
||||
public function addTrait(string $name): ClassType
|
||||
{
|
||||
return $this->addClass($name)->setType(ClassType::TYPE_TRAIT);
|
||||
}
|
||||
|
||||
|
||||
public function addEnum(string $name): ClassType
|
||||
{
|
||||
return $this->addClass($name)->setType(ClassType::TYPE_ENUM);
|
||||
}
|
||||
|
||||
|
||||
public function removeClass(string $name): self
|
||||
{
|
||||
unset($this->classes[strtolower($name)]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function addFunction(string $name): GlobalFunction
|
||||
{
|
||||
$lower = strtolower($name);
|
||||
if ($orig = array_change_key_case($this->aliases[self::NameFunction])[$lower] ?? null) {
|
||||
throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig.");
|
||||
}
|
||||
|
||||
return $this->functions[$lower] = new GlobalFunction($name);
|
||||
}
|
||||
|
||||
|
||||
public function removeFunction(string $name): self
|
||||
{
|
||||
unset($this->functions[strtolower($name)]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/** @return ClassType[] */
|
||||
public function getClasses(): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($this->classes as $class) {
|
||||
$res[$class->getName()] = $class;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/** @return GlobalFunction[] */
|
||||
public function getFunctions(): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($this->functions as $fn) {
|
||||
$res[$fn->getName()] = $fn;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
private static function startsWith(string $a, string $b): bool
|
||||
{
|
||||
return strncasecmp($a, $b, strlen($b)) === 0;
|
||||
}
|
||||
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
try {
|
||||
return (new Printer)->printNamespace($this);
|
||||
} catch (\Throwable $e) {
|
||||
if (PHP_VERSION_ID >= 70400) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
424
vendor/nette/php-generator/src/PhpGenerator/Printer.php
vendored
Normal file
424
vendor/nette/php-generator/src/PhpGenerator/Printer.php
vendored
Normal file
@@ -0,0 +1,424 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
use Nette;
|
||||
use Nette\Utils\Strings;
|
||||
|
||||
|
||||
/**
|
||||
* Generates PHP code.
|
||||
*/
|
||||
class Printer
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var int */
|
||||
public $wrapLength = 120;
|
||||
|
||||
/** @var string */
|
||||
protected $indentation = "\t";
|
||||
|
||||
/** @var int */
|
||||
protected $linesBetweenProperties = 0;
|
||||
|
||||
/** @var int */
|
||||
protected $linesBetweenMethods = 2;
|
||||
|
||||
/** @var string */
|
||||
protected $returnTypeColon = ': ';
|
||||
|
||||
/** @var ?PhpNamespace */
|
||||
protected $namespace;
|
||||
|
||||
/** @var ?Dumper */
|
||||
protected $dumper;
|
||||
|
||||
/** @var bool */
|
||||
private $resolveTypes = true;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->dumper = new Dumper;
|
||||
}
|
||||
|
||||
|
||||
public function printFunction(GlobalFunction $function, ?PhpNamespace $namespace = null): string
|
||||
{
|
||||
$this->namespace = $this->resolveTypes ? $namespace : null;
|
||||
$line = 'function '
|
||||
. ($function->getReturnReference() ? '&' : '')
|
||||
. $function->getName();
|
||||
$returnType = $this->printReturnType($function);
|
||||
$body = Helpers::simplifyTaggedNames($function->getBody(), $this->namespace);
|
||||
|
||||
return Helpers::formatDocComment($function->getComment() . "\n")
|
||||
. $this->printAttributes($function->getAttributes())
|
||||
. $line
|
||||
. $this->printParameters($function, strlen($line) + strlen($returnType) + 2) // 2 = parentheses
|
||||
. $returnType
|
||||
. "\n{\n" . $this->indent(ltrim(rtrim($body) . "\n")) . "}\n";
|
||||
}
|
||||
|
||||
|
||||
public function printClosure(Closure $closure, ?PhpNamespace $namespace = null): string
|
||||
{
|
||||
$this->namespace = $this->resolveTypes ? $namespace : null;
|
||||
$uses = [];
|
||||
foreach ($closure->getUses() as $param) {
|
||||
$uses[] = ($param->isReference() ? '&' : '') . '$' . $param->getName();
|
||||
}
|
||||
|
||||
$useStr = strlen($tmp = implode(', ', $uses)) > $this->wrapLength && count($uses) > 1
|
||||
? "\n" . $this->indentation . implode(",\n" . $this->indentation, $uses) . "\n"
|
||||
: $tmp;
|
||||
$body = Helpers::simplifyTaggedNames($closure->getBody(), $this->namespace);
|
||||
|
||||
return $this->printAttributes($closure->getAttributes(), true)
|
||||
. 'function '
|
||||
. ($closure->getReturnReference() ? '&' : '')
|
||||
. $this->printParameters($closure)
|
||||
. ($uses ? " use ($useStr)" : '')
|
||||
. $this->printReturnType($closure)
|
||||
. " {\n" . $this->indent(ltrim(rtrim($body) . "\n")) . '}';
|
||||
}
|
||||
|
||||
|
||||
public function printArrowFunction(Closure $closure, ?PhpNamespace $namespace = null): string
|
||||
{
|
||||
$this->namespace = $this->resolveTypes ? $namespace : null;
|
||||
foreach ($closure->getUses() as $use) {
|
||||
if ($use->isReference()) {
|
||||
throw new Nette\InvalidArgumentException('Arrow function cannot bind variables by-reference.');
|
||||
}
|
||||
}
|
||||
|
||||
$body = Helpers::simplifyTaggedNames($closure->getBody(), $this->namespace);
|
||||
|
||||
return $this->printAttributes($closure->getAttributes())
|
||||
. 'fn'
|
||||
. ($closure->getReturnReference() ? '&' : '')
|
||||
. $this->printParameters($closure)
|
||||
. $this->printReturnType($closure)
|
||||
. ' => ' . trim($body) . ';';
|
||||
}
|
||||
|
||||
|
||||
public function printMethod(Method $method, ?PhpNamespace $namespace = null): string
|
||||
{
|
||||
$this->namespace = $this->resolveTypes ? $namespace : null;
|
||||
$method->validate();
|
||||
$line = ($method->isAbstract() ? 'abstract ' : '')
|
||||
. ($method->isFinal() ? 'final ' : '')
|
||||
. ($method->getVisibility() ? $method->getVisibility() . ' ' : '')
|
||||
. ($method->isStatic() ? 'static ' : '')
|
||||
. 'function '
|
||||
. ($method->getReturnReference() ? '&' : '')
|
||||
. $method->getName();
|
||||
$returnType = $this->printReturnType($method);
|
||||
$params = $this->printParameters($method, strlen($line) + strlen($returnType) + strlen($this->indentation) + 2);
|
||||
$body = Helpers::simplifyTaggedNames((string) $method->getBody(), $this->namespace);
|
||||
|
||||
return Helpers::formatDocComment($method->getComment() . "\n")
|
||||
. $this->printAttributes($method->getAttributes())
|
||||
. $line
|
||||
. $params
|
||||
. $returnType
|
||||
. ($method->isAbstract() || $method->getBody() === null
|
||||
? ";\n"
|
||||
: (strpos($params, "\n") === false ? "\n" : ' ')
|
||||
. "{\n"
|
||||
. $this->indent(ltrim(rtrim($body) . "\n"))
|
||||
. "}\n");
|
||||
}
|
||||
|
||||
|
||||
public function printClass(ClassType $class, ?PhpNamespace $namespace = null): string
|
||||
{
|
||||
$this->namespace = $this->resolveTypes ? $namespace : null;
|
||||
$class->validate();
|
||||
$resolver = $this->namespace
|
||||
? [$namespace, 'simplifyType']
|
||||
: function ($s) { return $s; };
|
||||
|
||||
$traits = [];
|
||||
foreach ($class->getTraitResolutions() as $trait) {
|
||||
$resolutions = $trait->getResolutions();
|
||||
$traits[] = Helpers::formatDocComment((string) $trait->getComment())
|
||||
. 'use ' . $resolver($trait->getName())
|
||||
. ($resolutions
|
||||
? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n"
|
||||
: ";\n");
|
||||
}
|
||||
|
||||
$cases = [];
|
||||
foreach ($class->getCases() as $case) {
|
||||
$cases[] = Helpers::formatDocComment((string) $case->getComment())
|
||||
. $this->printAttributes($case->getAttributes())
|
||||
. 'case ' . $case->getName()
|
||||
. ($case->getValue() === null ? '' : ' = ' . $this->dump($case->getValue()))
|
||||
. ";\n";
|
||||
}
|
||||
|
||||
$enumType = isset($case) && $case->getValue() !== null
|
||||
? $this->returnTypeColon . Type::getType($case->getValue())
|
||||
: '';
|
||||
|
||||
$consts = [];
|
||||
foreach ($class->getConstants() as $const) {
|
||||
$def = ($const->isFinal() ? 'final ' : '')
|
||||
. ($const->getVisibility() ? $const->getVisibility() . ' ' : '')
|
||||
. 'const ' . $const->getName() . ' = ';
|
||||
|
||||
$consts[] = Helpers::formatDocComment((string) $const->getComment())
|
||||
. $this->printAttributes($const->getAttributes())
|
||||
. $def
|
||||
. $this->dump($const->getValue(), strlen($def)) . ";\n";
|
||||
}
|
||||
|
||||
$properties = [];
|
||||
foreach ($class->getProperties() as $property) {
|
||||
$property->validate();
|
||||
$type = $property->getType();
|
||||
$def = (($property->getVisibility() ?: 'public')
|
||||
. ($property->isStatic() ? ' static' : '')
|
||||
. ($property->isReadOnly() && $type ? ' readonly' : '')
|
||||
. ' '
|
||||
. ltrim($this->printType($type, $property->isNullable()) . ' ')
|
||||
. '$' . $property->getName());
|
||||
|
||||
$properties[] = Helpers::formatDocComment((string) $property->getComment())
|
||||
. $this->printAttributes($property->getAttributes())
|
||||
. $def
|
||||
. ($property->getValue() === null && !$property->isInitialized()
|
||||
? ''
|
||||
: ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = '
|
||||
. ";\n";
|
||||
}
|
||||
|
||||
$methods = [];
|
||||
foreach ($class->getMethods() as $method) {
|
||||
$methods[] = $this->printMethod($method, $namespace);
|
||||
}
|
||||
|
||||
$members = array_filter([
|
||||
implode('', $traits),
|
||||
$this->joinProperties($cases),
|
||||
$this->joinProperties($consts),
|
||||
$this->joinProperties($properties),
|
||||
($methods && $properties ? str_repeat("\n", $this->linesBetweenMethods - 1) : '')
|
||||
. implode(str_repeat("\n", $this->linesBetweenMethods), $methods),
|
||||
]);
|
||||
|
||||
return Strings::normalize(
|
||||
Helpers::formatDocComment($class->getComment() . "\n")
|
||||
. $this->printAttributes($class->getAttributes())
|
||||
. ($class->isAbstract() ? 'abstract ' : '')
|
||||
. ($class->isFinal() ? 'final ' : '')
|
||||
. ($class->getName() ? $class->getType() . ' ' . $class->getName() . $enumType . ' ' : '')
|
||||
. ($class->getExtends() ? 'extends ' . implode(', ', array_map($resolver, (array) $class->getExtends())) . ' ' : '')
|
||||
. ($class->getImplements() ? 'implements ' . implode(', ', array_map($resolver, $class->getImplements())) . ' ' : '')
|
||||
. ($class->getName() ? "\n" : '') . "{\n"
|
||||
. ($members ? $this->indent(implode("\n", $members)) : '')
|
||||
. '}'
|
||||
) . ($class->getName() ? "\n" : '');
|
||||
}
|
||||
|
||||
|
||||
public function printNamespace(PhpNamespace $namespace): string
|
||||
{
|
||||
$this->namespace = $this->resolveTypes ? $namespace : null;
|
||||
$name = $namespace->getName();
|
||||
$uses = $this->printUses($namespace)
|
||||
. $this->printUses($namespace, PhpNamespace::NameFunction)
|
||||
. $this->printUses($namespace, PhpNamespace::NameConstant);
|
||||
|
||||
$items = [];
|
||||
foreach ($namespace->getClasses() as $class) {
|
||||
$items[] = $this->printClass($class, $namespace);
|
||||
}
|
||||
|
||||
foreach ($namespace->getFunctions() as $function) {
|
||||
$items[] = $this->printFunction($function, $namespace);
|
||||
}
|
||||
|
||||
$body = ($uses ? $uses . "\n" : '')
|
||||
. implode("\n", $items);
|
||||
|
||||
if ($namespace->hasBracketedSyntax()) {
|
||||
return 'namespace' . ($name ? " $name" : '') . "\n{\n"
|
||||
. $this->indent($body)
|
||||
. "}\n";
|
||||
|
||||
} else {
|
||||
return ($name ? "namespace $name;\n\n" : '')
|
||||
. $body;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function printFile(PhpFile $file): string
|
||||
{
|
||||
$namespaces = [];
|
||||
foreach ($file->getNamespaces() as $namespace) {
|
||||
$namespaces[] = $this->printNamespace($namespace);
|
||||
}
|
||||
|
||||
return Strings::normalize(
|
||||
"<?php\n"
|
||||
. ($file->getComment() ? "\n" . Helpers::formatDocComment($file->getComment() . "\n") : '')
|
||||
. "\n"
|
||||
. ($file->hasStrictTypes() ? "declare(strict_types=1);\n\n" : '')
|
||||
. implode("\n\n", $namespaces)
|
||||
) . "\n";
|
||||
}
|
||||
|
||||
|
||||
protected function printUses(PhpNamespace $namespace, string $of = PhpNamespace::NameNormal): string
|
||||
{
|
||||
$prefix = [
|
||||
PhpNamespace::NameNormal => '',
|
||||
PhpNamespace::NameFunction => 'function ',
|
||||
PhpNamespace::NameConstant => 'const ',
|
||||
][$of];
|
||||
$name = $namespace->getName();
|
||||
$uses = [];
|
||||
foreach ($namespace->getUses($of) as $alias => $original) {
|
||||
$uses[] = Helpers::extractShortName($original) === $alias
|
||||
? "use $prefix$original;\n"
|
||||
: "use $prefix$original as $alias;\n";
|
||||
}
|
||||
|
||||
return implode('', $uses);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Closure|GlobalFunction|Method $function
|
||||
*/
|
||||
protected function printParameters($function, int $column = 0): string
|
||||
{
|
||||
$params = [];
|
||||
$list = $function->getParameters();
|
||||
$special = false;
|
||||
|
||||
foreach ($list as $param) {
|
||||
$param->validate();
|
||||
$variadic = $function->isVariadic() && $param === end($list);
|
||||
$type = $param->getType();
|
||||
$promoted = $param instanceof PromotedParameter ? $param : null;
|
||||
$params[] =
|
||||
($promoted ? Helpers::formatDocComment((string) $promoted->getComment()) : '')
|
||||
. ($attrs = $this->printAttributes($param->getAttributes(), true))
|
||||
. ($promoted ?
|
||||
($promoted->getVisibility() ?: 'public')
|
||||
. ($promoted->isReadOnly() && $type ? ' readonly' : '')
|
||||
. ' ' : '')
|
||||
. ltrim($this->printType($type, $param->isNullable()) . ' ')
|
||||
. ($param->isReference() ? '&' : '')
|
||||
. ($variadic ? '...' : '')
|
||||
. '$' . $param->getName()
|
||||
. ($param->hasDefaultValue() && !$variadic ? ' = ' . $this->dump($param->getDefaultValue()) : '');
|
||||
|
||||
$special = $special || $promoted || $attrs;
|
||||
}
|
||||
|
||||
$line = implode(', ', $params);
|
||||
|
||||
return count($params) > 1 && ($special || strlen($line) + $column > $this->wrapLength)
|
||||
? "(\n" . $this->indent(implode(",\n", $params)) . ($special ? ',' : '') . "\n)"
|
||||
: "($line)";
|
||||
}
|
||||
|
||||
|
||||
protected function printType(?string $type, bool $nullable): string
|
||||
{
|
||||
if ($type === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($this->namespace) {
|
||||
$type = $this->namespace->simplifyType($type);
|
||||
}
|
||||
|
||||
if ($nullable && strcasecmp($type, 'mixed')) {
|
||||
$type = strpos($type, '|') === false
|
||||
? '?' . $type
|
||||
: $type . '|null';
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Closure|GlobalFunction|Method $function
|
||||
*/
|
||||
private function printReturnType($function): string
|
||||
{
|
||||
return ($tmp = $this->printType($function->getReturnType(), $function->isReturnNullable()))
|
||||
? $this->returnTypeColon . $tmp
|
||||
: '';
|
||||
}
|
||||
|
||||
|
||||
private function printAttributes(array $attrs, bool $inline = false): string
|
||||
{
|
||||
if (!$attrs) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->dumper->indentation = $this->indentation;
|
||||
$items = [];
|
||||
foreach ($attrs as $attr) {
|
||||
$args = $this->dumper->format('...?:', $attr->getArguments());
|
||||
$args = Helpers::simplifyTaggedNames($args, $this->namespace);
|
||||
$items[] = $this->printType($attr->getName(), false) . ($args ? "($args)" : '');
|
||||
}
|
||||
|
||||
return $inline
|
||||
? '#[' . implode(', ', $items) . '] '
|
||||
: '#[' . implode("]\n#[", $items) . "]\n";
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setTypeResolving(bool $state = true): self
|
||||
{
|
||||
$this->resolveTypes = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
protected function indent(string $s): string
|
||||
{
|
||||
$s = str_replace("\t", $this->indentation, $s);
|
||||
return Strings::indent($s, 1, $this->indentation);
|
||||
}
|
||||
|
||||
|
||||
protected function dump($var, int $column = 0): string
|
||||
{
|
||||
$this->dumper->indentation = $this->indentation;
|
||||
$this->dumper->wrapLength = $this->wrapLength;
|
||||
$s = $this->dumper->dump($var, $column);
|
||||
$s = Helpers::simplifyTaggedNames($s, $this->namespace);
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
private function joinProperties(array $props): string
|
||||
{
|
||||
return $this->linesBetweenProperties
|
||||
? implode(str_repeat("\n", $this->linesBetweenProperties), $props)
|
||||
: preg_replace('#^(\w.*\n)\n(?=\w.*;)#m', '$1', implode("\n", $props));
|
||||
}
|
||||
}
|
||||
48
vendor/nette/php-generator/src/PhpGenerator/PromotedParameter.php
vendored
Normal file
48
vendor/nette/php-generator/src/PhpGenerator/PromotedParameter.php
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Promoted parameter in constructor.
|
||||
*/
|
||||
final class PromotedParameter extends Parameter
|
||||
{
|
||||
use Traits\VisibilityAware;
|
||||
use Traits\CommentAware;
|
||||
|
||||
/** @var bool */
|
||||
private $readOnly = false;
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setReadOnly(bool $state = true): self
|
||||
{
|
||||
$this->readOnly = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isReadOnly(): bool
|
||||
{
|
||||
return $this->readOnly;
|
||||
}
|
||||
|
||||
|
||||
/** @throws Nette\InvalidStateException */
|
||||
public function validate(): void
|
||||
{
|
||||
if ($this->readOnly && !$this->getType()) {
|
||||
throw new Nette\InvalidStateException("Property \${$this->getName()}: Read-only properties are only supported on typed property.");
|
||||
}
|
||||
}
|
||||
}
|
||||
145
vendor/nette/php-generator/src/PhpGenerator/Property.php
vendored
Normal file
145
vendor/nette/php-generator/src/PhpGenerator/Property.php
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
use Nette;
|
||||
use Nette\Utils\Type;
|
||||
|
||||
|
||||
/**
|
||||
* Class property description.
|
||||
*
|
||||
* @property mixed $value
|
||||
*/
|
||||
final class Property
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
use Traits\NameAware;
|
||||
use Traits\VisibilityAware;
|
||||
use Traits\CommentAware;
|
||||
use Traits\AttributeAware;
|
||||
|
||||
/** @var mixed */
|
||||
private $value;
|
||||
|
||||
/** @var bool */
|
||||
private $static = false;
|
||||
|
||||
/** @var string|null */
|
||||
private $type;
|
||||
|
||||
/** @var bool */
|
||||
private $nullable = false;
|
||||
|
||||
/** @var bool */
|
||||
private $initialized = false;
|
||||
|
||||
/** @var bool */
|
||||
private $readOnly = false;
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setValue($val): self
|
||||
{
|
||||
$this->value = $val;
|
||||
$this->initialized = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function &getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setStatic(bool $state = true): self
|
||||
{
|
||||
$this->static = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isStatic(): bool
|
||||
{
|
||||
return $this->static;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setType(?string $type): self
|
||||
{
|
||||
$this->type = Helpers::validateType($type, $this->nullable);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Type|string|null
|
||||
*/
|
||||
public function getType(bool $asObject = false)
|
||||
{
|
||||
return $asObject && $this->type
|
||||
? Type::fromString($this->type)
|
||||
: $this->type;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setNullable(bool $state = true): self
|
||||
{
|
||||
$this->nullable = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isNullable(): bool
|
||||
{
|
||||
return $this->nullable;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setInitialized(bool $state = true): self
|
||||
{
|
||||
$this->initialized = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isInitialized(): bool
|
||||
{
|
||||
return $this->initialized || $this->value !== null;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setReadOnly(bool $state = true): self
|
||||
{
|
||||
$this->readOnly = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isReadOnly(): bool
|
||||
{
|
||||
return $this->readOnly;
|
||||
}
|
||||
|
||||
|
||||
/** @throws Nette\InvalidStateException */
|
||||
public function validate(): void
|
||||
{
|
||||
if ($this->readOnly && !$this->type) {
|
||||
throw new Nette\InvalidStateException("Property \$$this->name: Read-only properties are only supported on typed property.");
|
||||
}
|
||||
}
|
||||
}
|
||||
23
vendor/nette/php-generator/src/PhpGenerator/PsrPrinter.php
vendored
Normal file
23
vendor/nette/php-generator/src/PhpGenerator/PsrPrinter.php
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
|
||||
/**
|
||||
* Generates PHP code compatible with PSR-2 and PSR-12.
|
||||
*/
|
||||
final class PsrPrinter extends Printer
|
||||
{
|
||||
/** @var string */
|
||||
protected $indentation = ' ';
|
||||
|
||||
/** @var int */
|
||||
protected $linesBetweenMethods = 1;
|
||||
}
|
||||
49
vendor/nette/php-generator/src/PhpGenerator/TraitUse.php
vendored
Normal file
49
vendor/nette/php-generator/src/PhpGenerator/TraitUse.php
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* use Trait
|
||||
*/
|
||||
final class TraitUse
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
use Traits\NameAware;
|
||||
use Traits\CommentAware;
|
||||
|
||||
/** @var array */
|
||||
private $resolutions = [];
|
||||
|
||||
|
||||
public function __construct(string $name)
|
||||
{
|
||||
if (!Nette\PhpGenerator\Helpers::isNamespaceIdentifier($name, true)) {
|
||||
throw new Nette\InvalidArgumentException("Value '$name' is not valid trait name.");
|
||||
}
|
||||
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
|
||||
public function addResolution(string $resolution): self
|
||||
{
|
||||
$this->resolutions[] = $resolution;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function getResolutions(): array
|
||||
{
|
||||
return $this->resolutions;
|
||||
}
|
||||
}
|
||||
49
vendor/nette/php-generator/src/PhpGenerator/Traits/AttributeAware.php
vendored
Normal file
49
vendor/nette/php-generator/src/PhpGenerator/Traits/AttributeAware.php
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
<?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\PhpGenerator\Traits;
|
||||
|
||||
use Nette\PhpGenerator\Attribute;
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait AttributeAware
|
||||
{
|
||||
/** @var Attribute[] */
|
||||
private $attributes = [];
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function addAttribute(string $name, array $args = []): self
|
||||
{
|
||||
$this->attributes[] = new Attribute($name, $args);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Attribute[] $attrs
|
||||
* @return static
|
||||
*/
|
||||
public function setAttributes(array $attrs): self
|
||||
{
|
||||
(function (Attribute ...$attrs) {})(...$attrs);
|
||||
$this->attributes = $attrs;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/** @return Attribute[] */
|
||||
public function getAttributes(): array
|
||||
{
|
||||
return $this->attributes;
|
||||
}
|
||||
}
|
||||
42
vendor/nette/php-generator/src/PhpGenerator/Traits/CommentAware.php
vendored
Normal file
42
vendor/nette/php-generator/src/PhpGenerator/Traits/CommentAware.php
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
<?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\PhpGenerator\Traits;
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait CommentAware
|
||||
{
|
||||
/** @var string|null */
|
||||
private $comment;
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setComment(?string $val): self
|
||||
{
|
||||
$this->comment = $val;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function getComment(): ?string
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function addComment(string $val): self
|
||||
{
|
||||
$this->comment .= $this->comment ? "\n$val" : $val;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
188
vendor/nette/php-generator/src/PhpGenerator/Traits/FunctionLike.php
vendored
Normal file
188
vendor/nette/php-generator/src/PhpGenerator/Traits/FunctionLike.php
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
<?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\PhpGenerator\Traits;
|
||||
|
||||
use Nette;
|
||||
use Nette\PhpGenerator\Dumper;
|
||||
use Nette\PhpGenerator\Parameter;
|
||||
use Nette\Utils\Type;
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait FunctionLike
|
||||
{
|
||||
/** @var string */
|
||||
private $body = '';
|
||||
|
||||
/** @var Parameter[] */
|
||||
private $parameters = [];
|
||||
|
||||
/** @var bool */
|
||||
private $variadic = false;
|
||||
|
||||
/** @var string|null */
|
||||
private $returnType;
|
||||
|
||||
/** @var bool */
|
||||
private $returnReference = false;
|
||||
|
||||
/** @var bool */
|
||||
private $returnNullable = false;
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setBody(string $code, ?array $args = null): self
|
||||
{
|
||||
$this->body = $args === null
|
||||
? $code
|
||||
: (new Dumper)->format($code, ...$args);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function getBody(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function addBody(string $code, ?array $args = null): self
|
||||
{
|
||||
$this->body .= ($args === null ? $code : (new Dumper)->format($code, ...$args)) . "\n";
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Parameter[] $val
|
||||
* @return static
|
||||
*/
|
||||
public function setParameters(array $val): self
|
||||
{
|
||||
(function (Parameter ...$val) {})(...array_values($val));
|
||||
$this->parameters = [];
|
||||
foreach ($val as $v) {
|
||||
$this->parameters[$v->getName()] = $v;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/** @return Parameter[] */
|
||||
public function getParameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $name without $
|
||||
*/
|
||||
public function addParameter(string $name, $defaultValue = null): Parameter
|
||||
{
|
||||
$param = new Parameter($name);
|
||||
if (func_num_args() > 1) {
|
||||
$param->setDefaultValue($defaultValue);
|
||||
}
|
||||
|
||||
return $this->parameters[$name] = $param;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $name without $
|
||||
* @return static
|
||||
*/
|
||||
public function removeParameter(string $name): self
|
||||
{
|
||||
unset($this->parameters[$name]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setVariadic(bool $state = true): self
|
||||
{
|
||||
$this->variadic = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isVariadic(): bool
|
||||
{
|
||||
return $this->variadic;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setReturnType(?string $type): self
|
||||
{
|
||||
$this->returnType = Nette\PhpGenerator\Helpers::validateType($type, $this->returnNullable);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Type|string|null
|
||||
*/
|
||||
public function getReturnType(bool $asObject = false)
|
||||
{
|
||||
return $asObject && $this->returnType
|
||||
? Type::fromString($this->returnType)
|
||||
: $this->returnType;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setReturnReference(bool $state = true): self
|
||||
{
|
||||
$this->returnReference = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function getReturnReference(): bool
|
||||
{
|
||||
return $this->returnReference;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setReturnNullable(bool $state = true): self
|
||||
{
|
||||
$this->returnNullable = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isReturnNullable(): bool
|
||||
{
|
||||
return $this->returnNullable;
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated use isReturnNullable() */
|
||||
public function getReturnNullable(): bool
|
||||
{
|
||||
return $this->returnNullable;
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated */
|
||||
public function setNamespace(?Nette\PhpGenerator\PhpNamespace $val = null): self
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
50
vendor/nette/php-generator/src/PhpGenerator/Traits/NameAware.php
vendored
Normal file
50
vendor/nette/php-generator/src/PhpGenerator/Traits/NameAware.php
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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\PhpGenerator\Traits;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait NameAware
|
||||
{
|
||||
/** @var string */
|
||||
private $name;
|
||||
|
||||
|
||||
public function __construct(string $name)
|
||||
{
|
||||
if (!Nette\PhpGenerator\Helpers::isIdentifier($name)) {
|
||||
throw new Nette\InvalidArgumentException("Value '$name' is not valid name.");
|
||||
}
|
||||
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns clone with a different name.
|
||||
* @return static
|
||||
*/
|
||||
public function cloneWithName(string $name): self
|
||||
{
|
||||
$dolly = clone $this;
|
||||
$dolly->__construct($name);
|
||||
return $dolly;
|
||||
}
|
||||
}
|
||||
86
vendor/nette/php-generator/src/PhpGenerator/Traits/VisibilityAware.php
vendored
Normal file
86
vendor/nette/php-generator/src/PhpGenerator/Traits/VisibilityAware.php
vendored
Normal 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\PhpGenerator\Traits;
|
||||
|
||||
use Nette;
|
||||
use Nette\PhpGenerator\ClassType;
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait VisibilityAware
|
||||
{
|
||||
/** @var string|null public|protected|private */
|
||||
private $visibility;
|
||||
|
||||
|
||||
/**
|
||||
* @param string|null $val public|protected|private
|
||||
* @return static
|
||||
*/
|
||||
public function setVisibility(?string $val): self
|
||||
{
|
||||
if (!in_array($val, [ClassType::VisibilityPublic, ClassType::VisibilityProtected, ClassType::VisibilityPrivate, null], true)) {
|
||||
throw new Nette\InvalidArgumentException('Argument must be public|protected|private.');
|
||||
}
|
||||
|
||||
$this->visibility = $val;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function getVisibility(): ?string
|
||||
{
|
||||
return $this->visibility;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setPublic(): self
|
||||
{
|
||||
$this->visibility = ClassType::VisibilityPublic;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isPublic(): bool
|
||||
{
|
||||
return $this->visibility === ClassType::VisibilityPublic || $this->visibility === null;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setProtected(): self
|
||||
{
|
||||
$this->visibility = ClassType::VisibilityProtected;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isProtected(): bool
|
||||
{
|
||||
return $this->visibility === ClassType::VisibilityProtected;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setPrivate(): self
|
||||
{
|
||||
$this->visibility = ClassType::VisibilityPrivate;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function isPrivate(): bool
|
||||
{
|
||||
return $this->visibility === ClassType::VisibilityPrivate;
|
||||
}
|
||||
}
|
||||
92
vendor/nette/php-generator/src/PhpGenerator/Type.php
vendored
Normal file
92
vendor/nette/php-generator/src/PhpGenerator/Type.php
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
<?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\PhpGenerator;
|
||||
|
||||
|
||||
/**
|
||||
* PHP return, property and parameter types.
|
||||
*/
|
||||
class Type
|
||||
{
|
||||
public const
|
||||
String = 'string',
|
||||
Int = 'int',
|
||||
Float = 'float',
|
||||
Bool = 'bool',
|
||||
Array = 'array',
|
||||
Object = 'object',
|
||||
Callable = 'callable',
|
||||
Iterable = 'iterable',
|
||||
Void = 'void',
|
||||
Never = 'never',
|
||||
Mixed = 'mixed',
|
||||
False = 'false',
|
||||
Null = 'null',
|
||||
Self = 'self',
|
||||
Parent = 'parent',
|
||||
Static = 'static';
|
||||
|
||||
/** @deprecated */
|
||||
public const
|
||||
STRING = self::String,
|
||||
INT = self::Int,
|
||||
FLOAT = self::Float,
|
||||
BOOL = self::Bool,
|
||||
ARRAY = self::Array,
|
||||
OBJECT = self::Object,
|
||||
CALLABLE = self::Callable,
|
||||
ITERABLE = self::Iterable,
|
||||
VOID = self::Void,
|
||||
NEVER = self::Never,
|
||||
MIXED = self::Mixed,
|
||||
FALSE = self::False,
|
||||
NULL = self::Null,
|
||||
SELF = self::Self,
|
||||
PARENT = self::Parent,
|
||||
STATIC = self::Static;
|
||||
|
||||
|
||||
public static function nullable(string $type, bool $state = true): string
|
||||
{
|
||||
return ($state ? '?' : '') . ltrim($type, '?');
|
||||
}
|
||||
|
||||
|
||||
public static function union(string ...$types): string
|
||||
{
|
||||
return implode('|', $types);
|
||||
}
|
||||
|
||||
|
||||
public static function intersection(string ...$types): string
|
||||
{
|
||||
return implode('&', $types);
|
||||
}
|
||||
|
||||
|
||||
public static function getType($value): ?string
|
||||
{
|
||||
if (is_object($value)) {
|
||||
return get_class($value);
|
||||
} elseif (is_int($value)) {
|
||||
return self::INT;
|
||||
} elseif (is_float($value)) {
|
||||
return self::FLOAT;
|
||||
} elseif (is_string($value)) {
|
||||
return self::STRING;
|
||||
} elseif (is_bool($value)) {
|
||||
return self::BOOL;
|
||||
} elseif (is_array($value)) {
|
||||
return self::ARRAY;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
vendor/nette/utils/.phpstorm.meta.php
vendored
Normal file
19
vendor/nette/utils/.phpstorm.meta.php
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PHPSTORM_META;
|
||||
|
||||
override(\Nette\Utils\Arrays::get(0), elementType(0));
|
||||
override(\Nette\Utils\Arrays::getRef(0), elementType(0));
|
||||
override(\Nette\Utils\Arrays::grep(0), type(0));
|
||||
override(\Nette\Utils\Arrays::toObject(0), type(1));
|
||||
|
||||
expectedArguments(\Nette\Utils\Arrays::grep(), 2, PREG_GREP_INVERT);
|
||||
expectedArguments(\Nette\Utils\Image::resize(), 2, \Nette\Utils\Image::SHRINK_ONLY, \Nette\Utils\Image::STRETCH, \Nette\Utils\Image::FIT, \Nette\Utils\Image::FILL, \Nette\Utils\Image::EXACT);
|
||||
expectedArguments(\Nette\Utils\Image::calculateSize(), 4, \Nette\Utils\Image::SHRINK_ONLY, \Nette\Utils\Image::STRETCH, \Nette\Utils\Image::FIT, \Nette\Utils\Image::FILL, \Nette\Utils\Image::EXACT);
|
||||
expectedArguments(\Nette\Utils\Json::encode(), 1, \Nette\Utils\Json::PRETTY);
|
||||
expectedArguments(\Nette\Utils\Json::decode(), 1, \Nette\Utils\Json::FORCE_ARRAY);
|
||||
expectedArguments(\Nette\Utils\Strings::split(), 2, \PREG_SPLIT_NO_EMPTY | \PREG_OFFSET_CAPTURE);
|
||||
expectedArguments(\Nette\Utils\Strings::match(), 2, \PREG_OFFSET_CAPTURE | \PREG_UNMATCHED_AS_NULL);
|
||||
expectedArguments(\Nette\Utils\Strings::matchAll(), 2, \PREG_OFFSET_CAPTURE | \PREG_UNMATCHED_AS_NULL | \PREG_PATTERN_ORDER);
|
||||
51
vendor/nette/utils/composer.json
vendored
Normal file
51
vendor/nette/utils/composer.json
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "nette/utils",
|
||||
"description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
|
||||
"keywords": ["nette", "images", "json", "password", "validation", "utility", "string", "array", "core", "slugify", "utf-8", "unicode", "paginator", "datetime"],
|
||||
"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": ">=7.2 <8.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "~2.0",
|
||||
"tracy/tracy": "^2.3",
|
||||
"phpstan/phpstan": "^1.0",
|
||||
"jetbrains/phpstorm-attributes": "dev-master"
|
||||
},
|
||||
"conflict": {
|
||||
"nette/di": "<3.0.6"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
|
||||
"ext-json": "to use Nette\\Utils\\Json",
|
||||
"ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
|
||||
"ext-mbstring": "to use Strings::lower() etc...",
|
||||
"ext-xml": "to use Strings::length() etc. when mbstring is not available",
|
||||
"ext-gd": "to use Image",
|
||||
"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["src/"]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"scripts": {
|
||||
"phpstan": "phpstan analyse",
|
||||
"tester": "tester tests -s"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.2-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
60
vendor/nette/utils/license.md
vendored
Normal file
60
vendor/nette/utils/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)
|
||||
54
vendor/nette/utils/readme.md
vendored
Normal file
54
vendor/nette/utils/readme.md
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
Nette Utility Classes
|
||||
=====================
|
||||
|
||||
[](https://packagist.org/packages/nette/utils)
|
||||
[](https://github.com/nette/utils/actions)
|
||||
[](https://coveralls.io/github/nette/utils?branch=master)
|
||||
[](https://github.com/nette/utils/releases)
|
||||
[](https://github.com/nette/utils/blob/master/license.md)
|
||||
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
In package nette/utils you will find a set of [useful classes](https://doc.nette.org/utils) for everyday use:
|
||||
|
||||
- [Arrays](https://doc.nette.org/utils/arrays) - manipulate arrays
|
||||
- [Callback](https://doc.nette.org/utils/callback) - PHP callbacks
|
||||
- [Date and Time](https://doc.nette.org/utils/datetime) - modify times and dates
|
||||
- [Filesystem](https://doc.nette.org/utils/filesystem) - copying, renaming, …
|
||||
- [Helper Functions](https://doc.nette.org/utils/helpers)
|
||||
- [HTML elements](https://doc.nette.org/utils/html-elements) - generate HTML
|
||||
- [Images](https://doc.nette.org/utils/images) - crop, resize, rotate images
|
||||
- [JSON](https://doc.nette.org/utils/json) - encoding and decoding
|
||||
- [Generating Random Strings](https://doc.nette.org/utils/random)
|
||||
- [Paginator](https://doc.nette.org/utils/paginator) - pagination math
|
||||
- [PHP Reflection](https://doc.nette.org/utils/reflection)
|
||||
- [Strings](https://doc.nette.org/utils/strings) - useful text functions
|
||||
- [SmartObject](https://doc.nette.org/utils/smartobject) - PHP object enhancements
|
||||
- [Validation](https://doc.nette.org/utils/validators) - validate inputs
|
||||
- [Type](https://doc.nette.org/utils/type) - PHP data type
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
The recommended way to install is via Composer:
|
||||
|
||||
```
|
||||
composer require nette/utils
|
||||
```
|
||||
|
||||
- Nette Utils 3.2 is compatible with PHP 7.2 to 8.3
|
||||
- Nette Utils 3.1 is compatible with PHP 7.1 to 8.0
|
||||
- Nette Utils 3.0 is compatible with PHP 7.1 to 8.0
|
||||
- Nette Utils 2.5 is compatible with PHP 5.6 to 8.0
|
||||
|
||||
[Support Me](https://github.com/sponsors/dg)
|
||||
--------------------------------------------
|
||||
|
||||
Do you like Nette Utils? Are you looking forward to the new features?
|
||||
|
||||
[](https://github.com/sponsors/dg)
|
||||
|
||||
Thank you!
|
||||
22
vendor/nette/utils/src/HtmlStringable.php
vendored
Normal file
22
vendor/nette/utils/src/HtmlStringable.php
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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;
|
||||
|
||||
|
||||
interface HtmlStringable
|
||||
{
|
||||
/**
|
||||
* Returns string in HTML format
|
||||
*/
|
||||
function __toString(): string;
|
||||
}
|
||||
|
||||
|
||||
interface_exists(Utils\IHtmlString::class);
|
||||
167
vendor/nette/utils/src/Iterators/CachingIterator.php
vendored
Normal file
167
vendor/nette/utils/src/Iterators/CachingIterator.php
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
<?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\Iterators;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Smarter caching iterator.
|
||||
*
|
||||
* @property-read bool $first
|
||||
* @property-read bool $last
|
||||
* @property-read bool $empty
|
||||
* @property-read bool $odd
|
||||
* @property-read bool $even
|
||||
* @property-read int $counter
|
||||
* @property-read mixed $nextKey
|
||||
* @property-read mixed $nextValue
|
||||
*/
|
||||
class CachingIterator extends \CachingIterator implements \Countable
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var int */
|
||||
private $counter = 0;
|
||||
|
||||
|
||||
public function __construct($iterator)
|
||||
{
|
||||
if (is_array($iterator) || $iterator instanceof \stdClass) {
|
||||
$iterator = new \ArrayIterator($iterator);
|
||||
|
||||
} elseif ($iterator instanceof \IteratorAggregate) {
|
||||
do {
|
||||
$iterator = $iterator->getIterator();
|
||||
} while ($iterator instanceof \IteratorAggregate);
|
||||
|
||||
assert($iterator instanceof \Iterator);
|
||||
|
||||
} elseif ($iterator instanceof \Iterator) {
|
||||
} elseif ($iterator instanceof \Traversable) {
|
||||
$iterator = new \IteratorIterator($iterator);
|
||||
} else {
|
||||
throw new Nette\InvalidArgumentException(sprintf('Invalid argument passed to %s; array or Traversable expected, %s given.', self::class, is_object($iterator) ? get_class($iterator) : gettype($iterator)));
|
||||
}
|
||||
|
||||
parent::__construct($iterator, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the current element the first one?
|
||||
*/
|
||||
public function isFirst(?int $gridWidth = null): bool
|
||||
{
|
||||
return $this->counter === 1 || ($gridWidth && $this->counter !== 0 && (($this->counter - 1) % $gridWidth) === 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the current element the last one?
|
||||
*/
|
||||
public function isLast(?int $gridWidth = null): bool
|
||||
{
|
||||
return !$this->hasNext() || ($gridWidth && ($this->counter % $gridWidth) === 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the iterator empty?
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return $this->counter === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the counter odd?
|
||||
*/
|
||||
public function isOdd(): bool
|
||||
{
|
||||
return $this->counter % 2 === 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the counter even?
|
||||
*/
|
||||
public function isEven(): bool
|
||||
{
|
||||
return $this->counter % 2 === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the counter.
|
||||
*/
|
||||
public function getCounter(): int
|
||||
{
|
||||
return $this->counter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the count of elements.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
$inner = $this->getInnerIterator();
|
||||
if ($inner instanceof \Countable) {
|
||||
return $inner->count();
|
||||
|
||||
} else {
|
||||
throw new Nette\NotSupportedException('Iterator is not countable.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Forwards to the next element.
|
||||
*/
|
||||
public function next(): void
|
||||
{
|
||||
parent::next();
|
||||
if (parent::valid()) {
|
||||
$this->counter++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rewinds the Iterator.
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
parent::rewind();
|
||||
$this->counter = parent::valid() ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the next key.
|
||||
* @return mixed
|
||||
*/
|
||||
public function getNextKey()
|
||||
{
|
||||
return $this->getInnerIterator()->key();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the next element.
|
||||
* @return mixed
|
||||
*/
|
||||
public function getNextValue()
|
||||
{
|
||||
return $this->getInnerIterator()->current();
|
||||
}
|
||||
}
|
||||
35
vendor/nette/utils/src/Iterators/Mapper.php
vendored
Normal file
35
vendor/nette/utils/src/Iterators/Mapper.php
vendored
Normal 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\Iterators;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Applies the callback to the elements of the inner iterator.
|
||||
*/
|
||||
class Mapper extends \IteratorIterator
|
||||
{
|
||||
/** @var callable */
|
||||
private $callback;
|
||||
|
||||
|
||||
public function __construct(\Traversable $iterator, callable $callback)
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
$this->callback = $callback;
|
||||
}
|
||||
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
return ($this->callback)(parent::current(), parent::key());
|
||||
}
|
||||
}
|
||||
140
vendor/nette/utils/src/SmartObject.php
vendored
Normal file
140
vendor/nette/utils/src/SmartObject.php
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
<?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;
|
||||
|
||||
use Nette\Utils\ObjectHelpers;
|
||||
|
||||
|
||||
/**
|
||||
* Strict class for better experience.
|
||||
* - 'did you mean' hints
|
||||
* - access to undeclared members throws exceptions
|
||||
* - support for @property annotations
|
||||
* - support for calling event handlers stored in $onEvent via onEvent()
|
||||
*/
|
||||
trait SmartObject
|
||||
{
|
||||
/**
|
||||
* @throws MemberAccessException
|
||||
*/
|
||||
public function __call(string $name, array $args)
|
||||
{
|
||||
$class = static::class;
|
||||
|
||||
if (ObjectHelpers::hasProperty($class, $name) === 'event') { // calling event handlers
|
||||
$handlers = $this->$name ?? null;
|
||||
if (is_iterable($handlers)) {
|
||||
foreach ($handlers as $handler) {
|
||||
$handler(...$args);
|
||||
}
|
||||
} elseif ($handlers !== null) {
|
||||
throw new UnexpectedValueException("Property $class::$$name must be iterable or null, " . gettype($handlers) . ' given.');
|
||||
}
|
||||
} else {
|
||||
ObjectHelpers::strictCall($class, $name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @throws MemberAccessException
|
||||
*/
|
||||
public static function __callStatic(string $name, array $args)
|
||||
{
|
||||
ObjectHelpers::strictStaticCall(static::class, $name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @throws MemberAccessException if the property is not defined.
|
||||
*/
|
||||
public function &__get(string $name)
|
||||
{
|
||||
$class = static::class;
|
||||
|
||||
if ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property getter
|
||||
if (!($prop & 0b0001)) {
|
||||
throw new MemberAccessException("Cannot read a write-only property $class::\$$name.");
|
||||
}
|
||||
|
||||
$m = ($prop & 0b0010 ? 'get' : 'is') . ucfirst($name);
|
||||
if ($prop & 0b10000) {
|
||||
$trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call()
|
||||
$loc = isset($trace['file'], $trace['line'])
|
||||
? " in $trace[file] on line $trace[line]"
|
||||
: '';
|
||||
trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
if ($prop & 0b0100) { // return by reference
|
||||
return $this->$m();
|
||||
} else {
|
||||
$val = $this->$m();
|
||||
return $val;
|
||||
}
|
||||
} else {
|
||||
ObjectHelpers::strictGet($class, $name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
* @throws MemberAccessException if the property is not defined or is read-only
|
||||
*/
|
||||
public function __set(string $name, $value)
|
||||
{
|
||||
$class = static::class;
|
||||
|
||||
if (ObjectHelpers::hasProperty($class, $name)) { // unsetted property
|
||||
$this->$name = $value;
|
||||
|
||||
} elseif ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property setter
|
||||
if (!($prop & 0b1000)) {
|
||||
throw new MemberAccessException("Cannot write to a read-only property $class::\$$name.");
|
||||
}
|
||||
|
||||
$m = 'set' . ucfirst($name);
|
||||
if ($prop & 0b10000) {
|
||||
$trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call()
|
||||
$loc = isset($trace['file'], $trace['line'])
|
||||
? " in $trace[file] on line $trace[line]"
|
||||
: '';
|
||||
trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
$this->$m($value);
|
||||
|
||||
} else {
|
||||
ObjectHelpers::strictSet($class, $name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws MemberAccessException
|
||||
*/
|
||||
public function __unset(string $name)
|
||||
{
|
||||
$class = static::class;
|
||||
if (!ObjectHelpers::hasProperty($class, $name)) {
|
||||
throw new MemberAccessException("Cannot unset the property $class::\$$name.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function __isset(string $name): bool
|
||||
{
|
||||
return isset(ObjectHelpers::getMagicProperties(static::class)[$name]);
|
||||
}
|
||||
}
|
||||
37
vendor/nette/utils/src/StaticClass.php
vendored
Normal file
37
vendor/nette/utils/src/StaticClass.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;
|
||||
|
||||
|
||||
/**
|
||||
* Static class.
|
||||
*/
|
||||
trait StaticClass
|
||||
{
|
||||
/**
|
||||
* @return never
|
||||
* @throws \Error
|
||||
*/
|
||||
final public function __construct()
|
||||
{
|
||||
throw new \Error('Class ' . static::class . ' is static and cannot be instantiated.');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Call to undefined static method.
|
||||
* @return void
|
||||
* @throws MemberAccessException
|
||||
*/
|
||||
public static function __callStatic(string $name, array $args)
|
||||
{
|
||||
Utils\ObjectHelpers::strictStaticCall(static::class, $name);
|
||||
}
|
||||
}
|
||||
27
vendor/nette/utils/src/Translator.php
vendored
Normal file
27
vendor/nette/utils/src/Translator.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<?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\Localization;
|
||||
|
||||
|
||||
/**
|
||||
* Translator adapter.
|
||||
*/
|
||||
interface Translator
|
||||
{
|
||||
/**
|
||||
* Translates the given string.
|
||||
* @param mixed $message
|
||||
* @param mixed ...$parameters
|
||||
*/
|
||||
function translate($message, ...$parameters): string;
|
||||
}
|
||||
|
||||
|
||||
interface_exists(ITranslator::class);
|
||||
105
vendor/nette/utils/src/Utils/ArrayHash.php
vendored
Normal file
105
vendor/nette/utils/src/Utils/ArrayHash.php
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
<?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\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Provides objects to work as array.
|
||||
* @template T
|
||||
* @implements \RecursiveArrayIterator<array-key, T>
|
||||
* @implements \ArrayAccess<array-key, T>
|
||||
*/
|
||||
class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* Transforms array to ArrayHash.
|
||||
* @param array<T> $array
|
||||
* @return static
|
||||
*/
|
||||
public static function from(array $array, bool $recursive = true)
|
||||
{
|
||||
$obj = new static;
|
||||
foreach ($array as $key => $value) {
|
||||
$obj->$key = $recursive && is_array($value)
|
||||
? static::from($value, true)
|
||||
: $value;
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an iterator over all items.
|
||||
* @return \RecursiveArrayIterator<array-key, T>
|
||||
*/
|
||||
public function getIterator(): \RecursiveArrayIterator
|
||||
{
|
||||
return new \RecursiveArrayIterator((array) $this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns items count.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count((array) $this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replaces or appends a item.
|
||||
* @param array-key $key
|
||||
* @param T $value
|
||||
*/
|
||||
public function offsetSet($key, $value): void
|
||||
{
|
||||
if (!is_scalar($key)) { // prevents null
|
||||
throw new Nette\InvalidArgumentException(sprintf('Key must be either a string or an integer, %s given.', gettype($key)));
|
||||
}
|
||||
|
||||
$this->$key = $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a item.
|
||||
* @param array-key $key
|
||||
* @return T
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($key)
|
||||
{
|
||||
return $this->$key;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether a item exists.
|
||||
* @param array-key $key
|
||||
*/
|
||||
public function offsetExists($key): bool
|
||||
{
|
||||
return isset($this->$key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the element from this list.
|
||||
* @param array-key $key
|
||||
*/
|
||||
public function offsetUnset($key): void
|
||||
{
|
||||
unset($this->$key);
|
||||
}
|
||||
}
|
||||
137
vendor/nette/utils/src/Utils/ArrayList.php
vendored
Normal file
137
vendor/nette/utils/src/Utils/ArrayList.php
vendored
Normal 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\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Provides the base class for a generic list (items can be accessed by index).
|
||||
* @template T
|
||||
* @implements \IteratorAggregate<int, T>
|
||||
* @implements \ArrayAccess<int, T>
|
||||
*/
|
||||
class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var mixed[] */
|
||||
private $list = [];
|
||||
|
||||
|
||||
/**
|
||||
* Transforms array to ArrayList.
|
||||
* @param list<T> $array
|
||||
* @return static
|
||||
*/
|
||||
public static function from(array $array)
|
||||
{
|
||||
if (!Arrays::isList($array)) {
|
||||
throw new Nette\InvalidArgumentException('Array is not valid list.');
|
||||
}
|
||||
|
||||
$obj = new static;
|
||||
$obj->list = $array;
|
||||
return $obj;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an iterator over all items.
|
||||
* @return \ArrayIterator<int, T>
|
||||
*/
|
||||
public function getIterator(): \ArrayIterator
|
||||
{
|
||||
return new \ArrayIterator($this->list);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns items count.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->list);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replaces or appends a item.
|
||||
* @param int|null $index
|
||||
* @param T $value
|
||||
* @throws Nette\OutOfRangeException
|
||||
*/
|
||||
public function offsetSet($index, $value): void
|
||||
{
|
||||
if ($index === null) {
|
||||
$this->list[] = $value;
|
||||
|
||||
} elseif (!is_int($index) || $index < 0 || $index >= count($this->list)) {
|
||||
throw new Nette\OutOfRangeException('Offset invalid or out of range');
|
||||
|
||||
} else {
|
||||
$this->list[$index] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a item.
|
||||
* @param int $index
|
||||
* @return T
|
||||
* @throws Nette\OutOfRangeException
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($index)
|
||||
{
|
||||
if (!is_int($index) || $index < 0 || $index >= count($this->list)) {
|
||||
throw new Nette\OutOfRangeException('Offset invalid or out of range');
|
||||
}
|
||||
|
||||
return $this->list[$index];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether a item exists.
|
||||
* @param int $index
|
||||
*/
|
||||
public function offsetExists($index): bool
|
||||
{
|
||||
return is_int($index) && $index >= 0 && $index < count($this->list);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the element at the specified position in this list.
|
||||
* @param int $index
|
||||
* @throws Nette\OutOfRangeException
|
||||
*/
|
||||
public function offsetUnset($index): void
|
||||
{
|
||||
if (!is_int($index) || $index < 0 || $index >= count($this->list)) {
|
||||
throw new Nette\OutOfRangeException('Offset invalid or out of range');
|
||||
}
|
||||
|
||||
array_splice($this->list, $index, 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepends a item.
|
||||
* @param T $value
|
||||
*/
|
||||
public function prepend($value): void
|
||||
{
|
||||
$first = array_slice($this->list, 0, 1);
|
||||
$this->offsetSet(0, $value);
|
||||
array_splice($this->list, 1, 0, $first);
|
||||
}
|
||||
}
|
||||
460
vendor/nette/utils/src/Utils/Arrays.php
vendored
Normal file
460
vendor/nette/utils/src/Utils/Arrays.php
vendored
Normal file
@@ -0,0 +1,460 @@
|
||||
<?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\Utils;
|
||||
|
||||
use JetBrains\PhpStorm\Language;
|
||||
use Nette;
|
||||
use function is_array, is_int, is_object, count;
|
||||
|
||||
|
||||
/**
|
||||
* Array tools library.
|
||||
*/
|
||||
class Arrays
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
/**
|
||||
* Returns item from array. If it does not exist, it throws an exception, unless a default value is set.
|
||||
* @template T
|
||||
* @param array<T> $array
|
||||
* @param array-key|array-key[] $key
|
||||
* @param ?T $default
|
||||
* @return ?T
|
||||
* @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
|
||||
*/
|
||||
public static function get(array $array, $key, $default = null)
|
||||
{
|
||||
foreach (is_array($key) ? $key : [$key] as $k) {
|
||||
if (is_array($array) && array_key_exists($k, $array)) {
|
||||
$array = $array[$k];
|
||||
} else {
|
||||
if (func_num_args() < 3) {
|
||||
throw new Nette\InvalidArgumentException("Missing item '$k'.");
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns reference to array item. If the index does not exist, new one is created with value null.
|
||||
* @template T
|
||||
* @param array<T> $array
|
||||
* @param array-key|array-key[] $key
|
||||
* @return ?T
|
||||
* @throws Nette\InvalidArgumentException if traversed item is not an array
|
||||
*/
|
||||
public static function &getRef(array &$array, $key)
|
||||
{
|
||||
foreach (is_array($key) ? $key : [$key] as $k) {
|
||||
if (is_array($array) || $array === null) {
|
||||
$array = &$array[$k];
|
||||
} else {
|
||||
throw new Nette\InvalidArgumentException('Traversed item is not an array.');
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Recursively merges two fields. It is useful, for example, for merging tree structures. It behaves as
|
||||
* the + operator for array, ie. it adds a key/value pair from the second array to the first one and retains
|
||||
* the value from the first array in the case of a key collision.
|
||||
* @template T1
|
||||
* @template T2
|
||||
* @param array<T1> $array1
|
||||
* @param array<T2> $array2
|
||||
* @return array<T1|T2>
|
||||
*/
|
||||
public static function mergeTree(array $array1, array $array2): array
|
||||
{
|
||||
$res = $array1 + $array2;
|
||||
foreach (array_intersect_key($array1, $array2) as $k => $v) {
|
||||
if (is_array($v) && is_array($array2[$k])) {
|
||||
$res[$k] = self::mergeTree($v, $array2[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns zero-indexed position of given array key. Returns null if key is not found.
|
||||
* @param array-key $key
|
||||
* @return int|null offset if it is found, null otherwise
|
||||
*/
|
||||
public static function getKeyOffset(array $array, $key): ?int
|
||||
{
|
||||
return Helpers::falseToNull(array_search(self::toKey($key), array_keys($array), true));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated use getKeyOffset()
|
||||
*/
|
||||
public static function searchKey(array $array, $key): ?int
|
||||
{
|
||||
return self::getKeyOffset($array, $key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests an array for the presence of value.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function contains(array $array, $value): bool
|
||||
{
|
||||
return in_array($value, $array, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the first item from the array or null if array is empty.
|
||||
* @template T
|
||||
* @param array<T> $array
|
||||
* @return ?T
|
||||
*/
|
||||
public static function first(array $array)
|
||||
{
|
||||
return count($array) ? reset($array) : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the last item from the array or null if array is empty.
|
||||
* @template T
|
||||
* @param array<T> $array
|
||||
* @return ?T
|
||||
*/
|
||||
public static function last(array $array)
|
||||
{
|
||||
return count($array) ? end($array) : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inserts the contents of the $inserted array into the $array immediately after the $key.
|
||||
* If $key is null (or does not exist), it is inserted at the beginning.
|
||||
* @param array-key|null $key
|
||||
*/
|
||||
public static function insertBefore(array &$array, $key, array $inserted): void
|
||||
{
|
||||
$offset = $key === null ? 0 : (int) self::getKeyOffset($array, $key);
|
||||
$array = array_slice($array, 0, $offset, true)
|
||||
+ $inserted
|
||||
+ array_slice($array, $offset, count($array), true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inserts the contents of the $inserted array into the $array before the $key.
|
||||
* If $key is null (or does not exist), it is inserted at the end.
|
||||
* @param array-key|null $key
|
||||
*/
|
||||
public static function insertAfter(array &$array, $key, array $inserted): void
|
||||
{
|
||||
if ($key === null || ($offset = self::getKeyOffset($array, $key)) === null) {
|
||||
$offset = count($array) - 1;
|
||||
}
|
||||
|
||||
$array = array_slice($array, 0, $offset + 1, true)
|
||||
+ $inserted
|
||||
+ array_slice($array, $offset + 1, count($array), true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renames key in array.
|
||||
* @param array-key $oldKey
|
||||
* @param array-key $newKey
|
||||
*/
|
||||
public static function renameKey(array &$array, $oldKey, $newKey): bool
|
||||
{
|
||||
$offset = self::getKeyOffset($array, $oldKey);
|
||||
if ($offset === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$val = &$array[$oldKey];
|
||||
$keys = array_keys($array);
|
||||
$keys[$offset] = $newKey;
|
||||
$array = array_combine($keys, $array);
|
||||
$array[$newKey] = &$val;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns only those array items, which matches a regular expression $pattern.
|
||||
* @param string[] $array
|
||||
* @return string[]
|
||||
*/
|
||||
public static function grep(
|
||||
array $array,
|
||||
#[Language('RegExp')]
|
||||
string $pattern,
|
||||
int $flags = 0
|
||||
): array
|
||||
{
|
||||
return Strings::pcre('preg_grep', [$pattern, $array, $flags]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Transforms multidimensional array to flat array.
|
||||
*/
|
||||
public static function flatten(array $array, bool $preserveKeys = false): array
|
||||
{
|
||||
$res = [];
|
||||
$cb = $preserveKeys
|
||||
? function ($v, $k) use (&$res): void { $res[$k] = $v; }
|
||||
: function ($v) use (&$res): void { $res[] = $v; };
|
||||
array_walk_recursive($array, $cb);
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the array is indexed in ascending order of numeric keys from zero, a.k.a list.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isList($value): bool
|
||||
{
|
||||
return is_array($value) && (PHP_VERSION_ID < 80100
|
||||
? !$value || array_keys($value) === range(0, count($value) - 1)
|
||||
: array_is_list($value)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reformats table to associative tree. Path looks like 'field|field[]field->field=field'.
|
||||
* @param string|string[] $path
|
||||
* @return array|\stdClass
|
||||
*/
|
||||
public static function associate(array $array, $path)
|
||||
{
|
||||
$parts = is_array($path)
|
||||
? $path
|
||||
: preg_split('#(\[\]|->|=|\|)#', $path, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
if (!$parts || $parts === ['->'] || $parts[0] === '=' || $parts[0] === '|') {
|
||||
throw new Nette\InvalidArgumentException("Invalid path '$path'.");
|
||||
}
|
||||
|
||||
$res = $parts[0] === '->' ? new \stdClass : [];
|
||||
|
||||
foreach ($array as $rowOrig) {
|
||||
$row = (array) $rowOrig;
|
||||
$x = &$res;
|
||||
|
||||
for ($i = 0; $i < count($parts); $i++) {
|
||||
$part = $parts[$i];
|
||||
if ($part === '[]') {
|
||||
$x = &$x[];
|
||||
|
||||
} elseif ($part === '=') {
|
||||
if (isset($parts[++$i])) {
|
||||
$x = $row[$parts[$i]];
|
||||
$row = null;
|
||||
}
|
||||
} elseif ($part === '->') {
|
||||
if (isset($parts[++$i])) {
|
||||
if ($x === null) {
|
||||
$x = new \stdClass;
|
||||
}
|
||||
|
||||
$x = &$x->{$row[$parts[$i]]};
|
||||
} else {
|
||||
$row = is_object($rowOrig) ? $rowOrig : (object) $row;
|
||||
}
|
||||
} elseif ($part !== '|') {
|
||||
$x = &$x[(string) $row[$part]];
|
||||
}
|
||||
}
|
||||
|
||||
if ($x === null) {
|
||||
$x = $row;
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes array to associative array. Replace numeric keys with their values, the new value will be $filling.
|
||||
* @param mixed $filling
|
||||
*/
|
||||
public static function normalize(array $array, $filling = null): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($array as $k => $v) {
|
||||
$res[is_int($k) ? $v : $k] = is_int($k) ? $filling : $v;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns and removes the value of an item from an array. If it does not exist, it throws an exception,
|
||||
* or returns $default, if provided.
|
||||
* @template T
|
||||
* @param array<T> $array
|
||||
* @param array-key $key
|
||||
* @param ?T $default
|
||||
* @return ?T
|
||||
* @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
|
||||
*/
|
||||
public static function pick(array &$array, $key, $default = null)
|
||||
{
|
||||
if (array_key_exists($key, $array)) {
|
||||
$value = $array[$key];
|
||||
unset($array[$key]);
|
||||
return $value;
|
||||
|
||||
} elseif (func_num_args() < 3) {
|
||||
throw new Nette\InvalidArgumentException("Missing item '$key'.");
|
||||
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether at least one element in the array passes the test implemented by the
|
||||
* provided callback with signature `function ($value, $key, array $array): bool`.
|
||||
*/
|
||||
public static function some(iterable $array, callable $callback): bool
|
||||
{
|
||||
foreach ($array as $k => $v) {
|
||||
if ($callback($v, $k, $array)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests whether all elements in the array pass the test implemented by the provided function,
|
||||
* which has the signature `function ($value, $key, array $array): bool`.
|
||||
*/
|
||||
public static function every(iterable $array, callable $callback): bool
|
||||
{
|
||||
foreach ($array as $k => $v) {
|
||||
if (!$callback($v, $k, $array)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calls $callback on all elements in the array and returns the array of return values.
|
||||
* The callback has the signature `function ($value, $key, array $array): bool`.
|
||||
*/
|
||||
public static function map(iterable $array, callable $callback): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($array as $k => $v) {
|
||||
$res[$k] = $callback($v, $k, $array);
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invokes all callbacks and returns array of results.
|
||||
* @param callable[] $callbacks
|
||||
*/
|
||||
public static function invoke(iterable $callbacks, ...$args): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($callbacks as $k => $cb) {
|
||||
$res[$k] = $cb(...$args);
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invokes method on every object in an array and returns array of results.
|
||||
* @param object[] $objects
|
||||
*/
|
||||
public static function invokeMethod(iterable $objects, string $method, ...$args): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($objects as $k => $obj) {
|
||||
$res[$k] = $obj->$method(...$args);
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copies the elements of the $array array to the $object object and then returns it.
|
||||
* @template T of object
|
||||
* @param T $object
|
||||
* @return T
|
||||
*/
|
||||
public static function toObject(iterable $array, $object)
|
||||
{
|
||||
foreach ($array as $k => $v) {
|
||||
$object->$k = $v;
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts value to array key.
|
||||
* @param mixed $value
|
||||
* @return array-key
|
||||
*/
|
||||
public static function toKey($value)
|
||||
{
|
||||
return key([$value => null]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns copy of the $array where every item is converted to string
|
||||
* and prefixed by $prefix and suffixed by $suffix.
|
||||
* @param string[] $array
|
||||
* @return string[]
|
||||
*/
|
||||
public static function wrap(array $array, string $prefix = '', string $suffix = ''): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($array as $k => $v) {
|
||||
$res[$k] = $prefix . $v . $suffix;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
185
vendor/nette/utils/src/Utils/Callback.php
vendored
Normal file
185
vendor/nette/utils/src/Utils/Callback.php
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
<?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\Utils;
|
||||
|
||||
use Nette;
|
||||
use function is_array, is_object, is_string;
|
||||
|
||||
|
||||
/**
|
||||
* PHP callable tools.
|
||||
*/
|
||||
final class Callback
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
/**
|
||||
* @param string|object|callable $callable class, object, callable
|
||||
* @deprecated use Closure::fromCallable()
|
||||
*/
|
||||
public static function closure($callable, ?string $method = null): \Closure
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated, use Closure::fromCallable().', E_USER_DEPRECATED);
|
||||
try {
|
||||
return \Closure::fromCallable($method === null ? $callable : [$callable, $method]);
|
||||
} catch (\TypeError $e) {
|
||||
throw new Nette\InvalidArgumentException($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invokes callback.
|
||||
* @return mixed
|
||||
* @deprecated
|
||||
*/
|
||||
public static function invoke($callable, ...$args)
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated, use native invoking.', E_USER_DEPRECATED);
|
||||
self::check($callable);
|
||||
return $callable(...$args);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invokes callback with an array of parameters.
|
||||
* @return mixed
|
||||
* @deprecated
|
||||
*/
|
||||
public static function invokeArgs($callable, array $args = [])
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated, use native invoking.', E_USER_DEPRECATED);
|
||||
self::check($callable);
|
||||
return $callable(...$args);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invokes internal PHP function with own error handler.
|
||||
* @return mixed
|
||||
*/
|
||||
public static function invokeSafe(string $function, array $args, callable $onError)
|
||||
{
|
||||
$prev = set_error_handler(function ($severity, $message, $file) use ($onError, &$prev, $function): ?bool {
|
||||
if ($file === __FILE__) {
|
||||
$msg = ini_get('html_errors')
|
||||
? Html::htmlToText($message)
|
||||
: $message;
|
||||
$msg = preg_replace("#^$function\\(.*?\\): #", '', $msg);
|
||||
if ($onError($msg, $severity) !== false) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $prev ? $prev(...func_get_args()) : false;
|
||||
});
|
||||
|
||||
try {
|
||||
return $function(...$args);
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks that $callable is valid PHP callback. Otherwise throws exception. If the $syntax is set to true, only verifies
|
||||
* that $callable has a valid structure to be used as a callback, but does not verify if the class or method actually exists.
|
||||
* @param mixed $callable
|
||||
* @return callable
|
||||
* @throws Nette\InvalidArgumentException
|
||||
*/
|
||||
public static function check($callable, bool $syntax = false)
|
||||
{
|
||||
if (!is_callable($callable, $syntax)) {
|
||||
throw new Nette\InvalidArgumentException(
|
||||
$syntax
|
||||
? 'Given value is not a callable type.'
|
||||
: sprintf("Callback '%s' is not callable.", self::toString($callable))
|
||||
);
|
||||
}
|
||||
|
||||
return $callable;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts PHP callback to textual form. Class or method may not exists.
|
||||
* @param mixed $callable
|
||||
*/
|
||||
public static function toString($callable): string
|
||||
{
|
||||
if ($callable instanceof \Closure) {
|
||||
$inner = self::unwrap($callable);
|
||||
return '{closure' . ($inner instanceof \Closure ? '}' : ' ' . self::toString($inner) . '}');
|
||||
} elseif (is_string($callable) && $callable[0] === "\0") {
|
||||
return '{lambda}';
|
||||
} else {
|
||||
is_callable(is_object($callable) ? [$callable, '__invoke'] : $callable, true, $textual);
|
||||
return $textual;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns reflection for method or function used in PHP callback.
|
||||
* @param callable $callable type check is escalated to ReflectionException
|
||||
* @return \ReflectionMethod|\ReflectionFunction
|
||||
* @throws \ReflectionException if callback is not valid
|
||||
*/
|
||||
public static function toReflection($callable): \ReflectionFunctionAbstract
|
||||
{
|
||||
if ($callable instanceof \Closure) {
|
||||
$callable = self::unwrap($callable);
|
||||
}
|
||||
|
||||
if (is_string($callable) && strpos($callable, '::')) {
|
||||
return new \ReflectionMethod($callable);
|
||||
} elseif (is_array($callable)) {
|
||||
return new \ReflectionMethod($callable[0], $callable[1]);
|
||||
} elseif (is_object($callable) && !$callable instanceof \Closure) {
|
||||
return new \ReflectionMethod($callable, '__invoke');
|
||||
} else {
|
||||
return new \ReflectionFunction($callable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether PHP callback is function or static method.
|
||||
*/
|
||||
public static function isStatic(callable $callable): bool
|
||||
{
|
||||
return is_string(is_array($callable) ? $callable[0] : $callable);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unwraps closure created by Closure::fromCallable().
|
||||
* @return callable|array
|
||||
*/
|
||||
public static function unwrap(\Closure $closure)
|
||||
{
|
||||
$r = new \ReflectionFunction($closure);
|
||||
$class = $r->getClosureScopeClass();
|
||||
if (substr($r->name, -1) === '}') {
|
||||
return $closure;
|
||||
|
||||
} elseif (($obj = $r->getClosureThis()) && $class && get_class($obj) === $class->name) {
|
||||
return [$obj, $r->name];
|
||||
|
||||
} elseif ($class) {
|
||||
return [$class->name, $r->name];
|
||||
|
||||
} else {
|
||||
return $r->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
147
vendor/nette/utils/src/Utils/DateTime.php
vendored
Normal file
147
vendor/nette/utils/src/Utils/DateTime.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\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* DateTime.
|
||||
*/
|
||||
class DateTime extends \DateTime implements \JsonSerializable
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** minute in seconds */
|
||||
public const MINUTE = 60;
|
||||
|
||||
/** hour in seconds */
|
||||
public const HOUR = 60 * self::MINUTE;
|
||||
|
||||
/** day in seconds */
|
||||
public const DAY = 24 * self::HOUR;
|
||||
|
||||
/** week in seconds */
|
||||
public const WEEK = 7 * self::DAY;
|
||||
|
||||
/** average month in seconds */
|
||||
public const MONTH = 2629800;
|
||||
|
||||
/** average year in seconds */
|
||||
public const YEAR = 31557600;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a DateTime object from a string, UNIX timestamp, or other DateTimeInterface object.
|
||||
* @param string|int|\DateTimeInterface $time
|
||||
* @return static
|
||||
* @throws \Exception if the date and time are not valid.
|
||||
*/
|
||||
public static function from($time)
|
||||
{
|
||||
if ($time instanceof \DateTimeInterface) {
|
||||
return new static($time->format('Y-m-d H:i:s.u'), $time->getTimezone());
|
||||
|
||||
} elseif (is_numeric($time)) {
|
||||
if ($time <= self::YEAR) {
|
||||
$time += time();
|
||||
}
|
||||
|
||||
return (new static('@' . $time))->setTimezone(new \DateTimeZone(date_default_timezone_get()));
|
||||
|
||||
} else { // textual or null
|
||||
return new static((string) $time);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates DateTime object.
|
||||
* @return static
|
||||
* @throws Nette\InvalidArgumentException if the date and time are not valid.
|
||||
*/
|
||||
public static function fromParts(
|
||||
int $year,
|
||||
int $month,
|
||||
int $day,
|
||||
int $hour = 0,
|
||||
int $minute = 0,
|
||||
float $second = 0.0
|
||||
) {
|
||||
$s = sprintf('%04d-%02d-%02d %02d:%02d:%02.5F', $year, $month, $day, $hour, $minute, $second);
|
||||
if (
|
||||
!checkdate($month, $day, $year)
|
||||
|| $hour < 0
|
||||
|| $hour > 23
|
||||
|| $minute < 0
|
||||
|| $minute > 59
|
||||
|| $second < 0
|
||||
|| $second >= 60
|
||||
) {
|
||||
throw new Nette\InvalidArgumentException("Invalid date '$s'");
|
||||
}
|
||||
|
||||
return new static($s);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns new DateTime object formatted according to the specified format.
|
||||
* @param string $format The format the $time parameter should be in
|
||||
* @param string $time
|
||||
* @param string|\DateTimeZone $timezone (default timezone is used if null is passed)
|
||||
* @return static|false
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public static function createFromFormat($format, $time, $timezone = null)
|
||||
{
|
||||
if ($timezone === null) {
|
||||
$timezone = new \DateTimeZone(date_default_timezone_get());
|
||||
|
||||
} elseif (is_string($timezone)) {
|
||||
$timezone = new \DateTimeZone($timezone);
|
||||
|
||||
} elseif (!$timezone instanceof \DateTimeZone) {
|
||||
throw new Nette\InvalidArgumentException('Invalid timezone given');
|
||||
}
|
||||
|
||||
$date = parent::createFromFormat($format, $time, $timezone);
|
||||
return $date ? static::from($date) : false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns JSON representation in ISO 8601 (used by JavaScript).
|
||||
*/
|
||||
public function jsonSerialize(): string
|
||||
{
|
||||
return $this->format('c');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the date and time in the format 'Y-m-d H:i:s'.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a copy with a modified time.
|
||||
* @return static
|
||||
*/
|
||||
public function modifyClone(string $modify = '')
|
||||
{
|
||||
$dolly = clone $this;
|
||||
return $modify ? $dolly->modify($modify) : $dolly;
|
||||
}
|
||||
}
|
||||
262
vendor/nette/utils/src/Utils/FileSystem.php
vendored
Normal file
262
vendor/nette/utils/src/Utils/FileSystem.php
vendored
Normal file
@@ -0,0 +1,262 @@
|
||||
<?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\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* File system tool.
|
||||
*/
|
||||
final class FileSystem
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
/**
|
||||
* Creates a directory if it does not exist, including parent directories.
|
||||
* @throws Nette\IOException on error occurred
|
||||
*/
|
||||
public static function createDir(string $dir, int $mode = 0777): void
|
||||
{
|
||||
if (!is_dir($dir) && !@mkdir($dir, $mode, true) && !is_dir($dir)) { // @ - dir may already exist
|
||||
throw new Nette\IOException(sprintf(
|
||||
"Unable to create directory '%s' with mode %s. %s",
|
||||
self::normalizePath($dir),
|
||||
decoct($mode),
|
||||
Helpers::getLastError()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copies a file or an entire directory. Overwrites existing files and directories by default.
|
||||
* @throws Nette\IOException on error occurred
|
||||
* @throws Nette\InvalidStateException if $overwrite is set to false and destination already exists
|
||||
*/
|
||||
public static function copy(string $origin, string $target, bool $overwrite = true): void
|
||||
{
|
||||
if (stream_is_local($origin) && !file_exists($origin)) {
|
||||
throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($origin)));
|
||||
|
||||
} elseif (!$overwrite && file_exists($target)) {
|
||||
throw new Nette\InvalidStateException(sprintf("File or directory '%s' already exists.", self::normalizePath($target)));
|
||||
|
||||
} elseif (is_dir($origin)) {
|
||||
static::createDir($target);
|
||||
foreach (new \FilesystemIterator($target) as $item) {
|
||||
static::delete($item->getPathname());
|
||||
}
|
||||
|
||||
foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($origin, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) {
|
||||
if ($item->isDir()) {
|
||||
static::createDir($target . '/' . $iterator->getSubPathName());
|
||||
} else {
|
||||
static::copy($item->getPathname(), $target . '/' . $iterator->getSubPathName());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
static::createDir(dirname($target));
|
||||
if (
|
||||
($s = @fopen($origin, 'rb'))
|
||||
&& ($d = @fopen($target, 'wb'))
|
||||
&& @stream_copy_to_stream($s, $d) === false
|
||||
) { // @ is escalated to exception
|
||||
throw new Nette\IOException(sprintf(
|
||||
"Unable to copy file '%s' to '%s'. %s",
|
||||
self::normalizePath($origin),
|
||||
self::normalizePath($target),
|
||||
Helpers::getLastError()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deletes a file or an entire directory if exists. If the directory is not empty, it deletes its contents first.
|
||||
* @throws Nette\IOException on error occurred
|
||||
*/
|
||||
public static function delete(string $path): void
|
||||
{
|
||||
if (is_file($path) || is_link($path)) {
|
||||
$func = DIRECTORY_SEPARATOR === '\\' && is_dir($path) ? 'rmdir' : 'unlink';
|
||||
if (!@$func($path)) { // @ is escalated to exception
|
||||
throw new Nette\IOException(sprintf(
|
||||
"Unable to delete '%s'. %s",
|
||||
self::normalizePath($path),
|
||||
Helpers::getLastError()
|
||||
));
|
||||
}
|
||||
} elseif (is_dir($path)) {
|
||||
foreach (new \FilesystemIterator($path) as $item) {
|
||||
static::delete($item->getPathname());
|
||||
}
|
||||
|
||||
if (!@rmdir($path)) { // @ is escalated to exception
|
||||
throw new Nette\IOException(sprintf(
|
||||
"Unable to delete directory '%s'. %s",
|
||||
self::normalizePath($path),
|
||||
Helpers::getLastError()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renames or moves a file or a directory. Overwrites existing files and directories by default.
|
||||
* @throws Nette\IOException on error occurred
|
||||
* @throws Nette\InvalidStateException if $overwrite is set to false and destination already exists
|
||||
*/
|
||||
public static function rename(string $origin, string $target, bool $overwrite = true): void
|
||||
{
|
||||
if (!$overwrite && file_exists($target)) {
|
||||
throw new Nette\InvalidStateException(sprintf("File or directory '%s' already exists.", self::normalizePath($target)));
|
||||
|
||||
} elseif (!file_exists($origin)) {
|
||||
throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($origin)));
|
||||
|
||||
} else {
|
||||
static::createDir(dirname($target));
|
||||
if (realpath($origin) !== realpath($target)) {
|
||||
static::delete($target);
|
||||
}
|
||||
|
||||
if (!@rename($origin, $target)) { // @ is escalated to exception
|
||||
throw new Nette\IOException(sprintf(
|
||||
"Unable to rename file or directory '%s' to '%s'. %s",
|
||||
self::normalizePath($origin),
|
||||
self::normalizePath($target),
|
||||
Helpers::getLastError()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads the content of a file.
|
||||
* @throws Nette\IOException on error occurred
|
||||
*/
|
||||
public static function read(string $file): string
|
||||
{
|
||||
$content = @file_get_contents($file); // @ is escalated to exception
|
||||
if ($content === false) {
|
||||
throw new Nette\IOException(sprintf(
|
||||
"Unable to read file '%s'. %s",
|
||||
self::normalizePath($file),
|
||||
Helpers::getLastError()
|
||||
));
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes the string to a file.
|
||||
* @throws Nette\IOException on error occurred
|
||||
*/
|
||||
public static function write(string $file, string $content, ?int $mode = 0666): void
|
||||
{
|
||||
static::createDir(dirname($file));
|
||||
if (@file_put_contents($file, $content) === false) { // @ is escalated to exception
|
||||
throw new Nette\IOException(sprintf(
|
||||
"Unable to write file '%s'. %s",
|
||||
self::normalizePath($file),
|
||||
Helpers::getLastError()
|
||||
));
|
||||
}
|
||||
|
||||
if ($mode !== null && !@chmod($file, $mode)) { // @ is escalated to exception
|
||||
throw new Nette\IOException(sprintf(
|
||||
"Unable to chmod file '%s' to mode %s. %s",
|
||||
self::normalizePath($file),
|
||||
decoct($mode),
|
||||
Helpers::getLastError()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets file permissions to `$fileMode` or directory permissions to `$dirMode`.
|
||||
* Recursively traverses and sets permissions on the entire contents of the directory as well.
|
||||
* @throws Nette\IOException on error occurred
|
||||
*/
|
||||
public static function makeWritable(string $path, int $dirMode = 0777, int $fileMode = 0666): void
|
||||
{
|
||||
if (is_file($path)) {
|
||||
if (!@chmod($path, $fileMode)) { // @ is escalated to exception
|
||||
throw new Nette\IOException(sprintf(
|
||||
"Unable to chmod file '%s' to mode %s. %s",
|
||||
self::normalizePath($path),
|
||||
decoct($fileMode),
|
||||
Helpers::getLastError()
|
||||
));
|
||||
}
|
||||
} elseif (is_dir($path)) {
|
||||
foreach (new \FilesystemIterator($path) as $item) {
|
||||
static::makeWritable($item->getPathname(), $dirMode, $fileMode);
|
||||
}
|
||||
|
||||
if (!@chmod($path, $dirMode)) { // @ is escalated to exception
|
||||
throw new Nette\IOException(sprintf(
|
||||
"Unable to chmod directory '%s' to mode %s. %s",
|
||||
self::normalizePath($path),
|
||||
decoct($dirMode),
|
||||
Helpers::getLastError()
|
||||
));
|
||||
}
|
||||
} else {
|
||||
throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($path)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines if the path is absolute.
|
||||
*/
|
||||
public static function isAbsolute(string $path): bool
|
||||
{
|
||||
return (bool) preg_match('#([a-z]:)?[/\\\\]|[a-z][a-z0-9+.-]*://#Ai', $path);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes `..` and `.` and directory separators in path.
|
||||
*/
|
||||
public static function normalizePath(string $path): string
|
||||
{
|
||||
$parts = $path === '' ? [] : preg_split('~[/\\\\]+~', $path);
|
||||
$res = [];
|
||||
foreach ($parts as $part) {
|
||||
if ($part === '..' && $res && end($res) !== '..' && end($res) !== '') {
|
||||
array_pop($res);
|
||||
} elseif ($part !== '.') {
|
||||
$res[] = $part;
|
||||
}
|
||||
}
|
||||
|
||||
return $res === ['']
|
||||
? DIRECTORY_SEPARATOR
|
||||
: implode(DIRECTORY_SEPARATOR, $res);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Joins all segments of the path and normalizes the result.
|
||||
*/
|
||||
public static function joinPaths(string ...$paths): string
|
||||
{
|
||||
return self::normalizePath(implode('/', $paths));
|
||||
}
|
||||
}
|
||||
107
vendor/nette/utils/src/Utils/Floats.php
vendored
Normal file
107
vendor/nette/utils/src/Utils/Floats.php
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
<?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\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Floating-point numbers comparison.
|
||||
*/
|
||||
class Floats
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
private const Epsilon = 1e-10;
|
||||
|
||||
|
||||
public static function isZero(float $value): bool
|
||||
{
|
||||
return abs($value) < self::Epsilon;
|
||||
}
|
||||
|
||||
|
||||
public static function isInteger(float $value): bool
|
||||
{
|
||||
return abs(round($value) - $value) < self::Epsilon;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compare two floats. If $a < $b it returns -1, if they are equal it returns 0 and if $a > $b it returns 1
|
||||
* @throws \LogicException if one of parameters is NAN
|
||||
*/
|
||||
public static function compare(float $a, float $b): int
|
||||
{
|
||||
if (is_nan($a) || is_nan($b)) {
|
||||
throw new \LogicException('Trying to compare NAN');
|
||||
|
||||
} elseif (!is_finite($a) && !is_finite($b) && $a === $b) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$diff = abs($a - $b);
|
||||
if (($diff < self::Epsilon || ($diff / max(abs($a), abs($b)) < self::Epsilon))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $a < $b ? -1 : 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if $a = $b
|
||||
* @throws \LogicException if one of parameters is NAN
|
||||
*/
|
||||
public static function areEqual(float $a, float $b): bool
|
||||
{
|
||||
return self::compare($a, $b) === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if $a < $b
|
||||
* @throws \LogicException if one of parameters is NAN
|
||||
*/
|
||||
public static function isLessThan(float $a, float $b): bool
|
||||
{
|
||||
return self::compare($a, $b) < 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if $a <= $b
|
||||
* @throws \LogicException if one of parameters is NAN
|
||||
*/
|
||||
public static function isLessThanOrEqualTo(float $a, float $b): bool
|
||||
{
|
||||
return self::compare($a, $b) <= 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if $a > $b
|
||||
* @throws \LogicException if one of parameters is NAN
|
||||
*/
|
||||
public static function isGreaterThan(float $a, float $b): bool
|
||||
{
|
||||
return self::compare($a, $b) > 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if $a >= $b
|
||||
* @throws \LogicException if one of parameters is NAN
|
||||
*/
|
||||
public static function isGreaterThanOrEqualTo(float $a, float $b): bool
|
||||
{
|
||||
return self::compare($a, $b) >= 0;
|
||||
}
|
||||
}
|
||||
91
vendor/nette/utils/src/Utils/Helpers.php
vendored
Normal file
91
vendor/nette/utils/src/Utils/Helpers.php
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
<?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\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
class Helpers
|
||||
{
|
||||
/**
|
||||
* Executes a callback and returns the captured output as a string.
|
||||
*/
|
||||
public static function capture(callable $func): string
|
||||
{
|
||||
ob_start(function () {});
|
||||
try {
|
||||
$func();
|
||||
return ob_get_clean();
|
||||
} catch (\Throwable $e) {
|
||||
ob_end_clean();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the last occurred PHP error or an empty string if no error occurred. Unlike error_get_last(),
|
||||
* it is nit affected by the PHP directive html_errors and always returns text, not HTML.
|
||||
*/
|
||||
public static function getLastError(): string
|
||||
{
|
||||
$message = error_get_last()['message'] ?? '';
|
||||
$message = ini_get('html_errors') ? Html::htmlToText($message) : $message;
|
||||
$message = preg_replace('#^\w+\(.*?\): #', '', $message);
|
||||
return $message;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts false to null, does not change other values.
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
public static function falseToNull($value)
|
||||
{
|
||||
return $value === false ? null : $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns value clamped to the inclusive range of min and max.
|
||||
* @param int|float $value
|
||||
* @param int|float $min
|
||||
* @param int|float $max
|
||||
* @return int|float
|
||||
*/
|
||||
public static function clamp($value, $min, $max)
|
||||
{
|
||||
if ($min > $max) {
|
||||
throw new Nette\InvalidArgumentException("Minimum ($min) is not less than maximum ($max).");
|
||||
}
|
||||
|
||||
return min(max($value, $min), $max);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Looks for a string from possibilities that is most similar to value, but not the same (for 8-bit encoding).
|
||||
* @param string[] $possibilities
|
||||
*/
|
||||
public static function getSuggestion(array $possibilities, string $value): ?string
|
||||
{
|
||||
$best = null;
|
||||
$min = (strlen($value) / 4 + 1) * 10 + .1;
|
||||
foreach (array_unique($possibilities) as $item) {
|
||||
if ($item !== $value && ($len = levenshtein($item, $value, 10, 11, 10)) < $min) {
|
||||
$min = $len;
|
||||
$best = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return $best;
|
||||
}
|
||||
}
|
||||
887
vendor/nette/utils/src/Utils/Html.php
vendored
Normal file
887
vendor/nette/utils/src/Utils/Html.php
vendored
Normal file
@@ -0,0 +1,887 @@
|
||||
<?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\Utils;
|
||||
|
||||
use Nette;
|
||||
use Nette\HtmlStringable;
|
||||
use function is_array, is_float, is_object, is_string;
|
||||
|
||||
|
||||
/**
|
||||
* HTML helper.
|
||||
*
|
||||
* @property string|null $accept
|
||||
* @property string|null $accesskey
|
||||
* @property string|null $action
|
||||
* @property string|null $align
|
||||
* @property string|null $allow
|
||||
* @property string|null $alt
|
||||
* @property bool|null $async
|
||||
* @property string|null $autocapitalize
|
||||
* @property string|null $autocomplete
|
||||
* @property bool|null $autofocus
|
||||
* @property bool|null $autoplay
|
||||
* @property string|null $charset
|
||||
* @property bool|null $checked
|
||||
* @property string|null $cite
|
||||
* @property string|null $class
|
||||
* @property int|null $cols
|
||||
* @property int|null $colspan
|
||||
* @property string|null $content
|
||||
* @property bool|null $contenteditable
|
||||
* @property bool|null $controls
|
||||
* @property string|null $coords
|
||||
* @property string|null $crossorigin
|
||||
* @property string|null $data
|
||||
* @property string|null $datetime
|
||||
* @property string|null $decoding
|
||||
* @property bool|null $default
|
||||
* @property bool|null $defer
|
||||
* @property string|null $dir
|
||||
* @property string|null $dirname
|
||||
* @property bool|null $disabled
|
||||
* @property bool|null $download
|
||||
* @property string|null $draggable
|
||||
* @property string|null $dropzone
|
||||
* @property string|null $enctype
|
||||
* @property string|null $for
|
||||
* @property string|null $form
|
||||
* @property string|null $formaction
|
||||
* @property string|null $formenctype
|
||||
* @property string|null $formmethod
|
||||
* @property bool|null $formnovalidate
|
||||
* @property string|null $formtarget
|
||||
* @property string|null $headers
|
||||
* @property int|null $height
|
||||
* @property bool|null $hidden
|
||||
* @property float|null $high
|
||||
* @property string|null $href
|
||||
* @property string|null $hreflang
|
||||
* @property string|null $id
|
||||
* @property string|null $integrity
|
||||
* @property string|null $inputmode
|
||||
* @property bool|null $ismap
|
||||
* @property string|null $itemprop
|
||||
* @property string|null $kind
|
||||
* @property string|null $label
|
||||
* @property string|null $lang
|
||||
* @property string|null $list
|
||||
* @property bool|null $loop
|
||||
* @property float|null $low
|
||||
* @property float|null $max
|
||||
* @property int|null $maxlength
|
||||
* @property int|null $minlength
|
||||
* @property string|null $media
|
||||
* @property string|null $method
|
||||
* @property float|null $min
|
||||
* @property bool|null $multiple
|
||||
* @property bool|null $muted
|
||||
* @property string|null $name
|
||||
* @property bool|null $novalidate
|
||||
* @property bool|null $open
|
||||
* @property float|null $optimum
|
||||
* @property string|null $pattern
|
||||
* @property string|null $ping
|
||||
* @property string|null $placeholder
|
||||
* @property string|null $poster
|
||||
* @property string|null $preload
|
||||
* @property string|null $radiogroup
|
||||
* @property bool|null $readonly
|
||||
* @property string|null $rel
|
||||
* @property bool|null $required
|
||||
* @property bool|null $reversed
|
||||
* @property int|null $rows
|
||||
* @property int|null $rowspan
|
||||
* @property string|null $sandbox
|
||||
* @property string|null $scope
|
||||
* @property bool|null $selected
|
||||
* @property string|null $shape
|
||||
* @property int|null $size
|
||||
* @property string|null $sizes
|
||||
* @property string|null $slot
|
||||
* @property int|null $span
|
||||
* @property string|null $spellcheck
|
||||
* @property string|null $src
|
||||
* @property string|null $srcdoc
|
||||
* @property string|null $srclang
|
||||
* @property string|null $srcset
|
||||
* @property int|null $start
|
||||
* @property float|null $step
|
||||
* @property string|null $style
|
||||
* @property int|null $tabindex
|
||||
* @property string|null $target
|
||||
* @property string|null $title
|
||||
* @property string|null $translate
|
||||
* @property string|null $type
|
||||
* @property string|null $usemap
|
||||
* @property string|null $value
|
||||
* @property int|null $width
|
||||
* @property string|null $wrap
|
||||
*
|
||||
* @method self accept(?string $val)
|
||||
* @method self accesskey(?string $val, bool $state = null)
|
||||
* @method self action(?string $val)
|
||||
* @method self align(?string $val)
|
||||
* @method self allow(?string $val, bool $state = null)
|
||||
* @method self alt(?string $val)
|
||||
* @method self async(?bool $val)
|
||||
* @method self autocapitalize(?string $val)
|
||||
* @method self autocomplete(?string $val)
|
||||
* @method self autofocus(?bool $val)
|
||||
* @method self autoplay(?bool $val)
|
||||
* @method self charset(?string $val)
|
||||
* @method self checked(?bool $val)
|
||||
* @method self cite(?string $val)
|
||||
* @method self class(?string $val, bool $state = null)
|
||||
* @method self cols(?int $val)
|
||||
* @method self colspan(?int $val)
|
||||
* @method self content(?string $val)
|
||||
* @method self contenteditable(?bool $val)
|
||||
* @method self controls(?bool $val)
|
||||
* @method self coords(?string $val)
|
||||
* @method self crossorigin(?string $val)
|
||||
* @method self datetime(?string $val)
|
||||
* @method self decoding(?string $val)
|
||||
* @method self default(?bool $val)
|
||||
* @method self defer(?bool $val)
|
||||
* @method self dir(?string $val)
|
||||
* @method self dirname(?string $val)
|
||||
* @method self disabled(?bool $val)
|
||||
* @method self download(?bool $val)
|
||||
* @method self draggable(?string $val)
|
||||
* @method self dropzone(?string $val)
|
||||
* @method self enctype(?string $val)
|
||||
* @method self for(?string $val)
|
||||
* @method self form(?string $val)
|
||||
* @method self formaction(?string $val)
|
||||
* @method self formenctype(?string $val)
|
||||
* @method self formmethod(?string $val)
|
||||
* @method self formnovalidate(?bool $val)
|
||||
* @method self formtarget(?string $val)
|
||||
* @method self headers(?string $val, bool $state = null)
|
||||
* @method self height(?int $val)
|
||||
* @method self hidden(?bool $val)
|
||||
* @method self high(?float $val)
|
||||
* @method self hreflang(?string $val)
|
||||
* @method self id(?string $val)
|
||||
* @method self integrity(?string $val)
|
||||
* @method self inputmode(?string $val)
|
||||
* @method self ismap(?bool $val)
|
||||
* @method self itemprop(?string $val)
|
||||
* @method self kind(?string $val)
|
||||
* @method self label(?string $val)
|
||||
* @method self lang(?string $val)
|
||||
* @method self list(?string $val)
|
||||
* @method self loop(?bool $val)
|
||||
* @method self low(?float $val)
|
||||
* @method self max(?float $val)
|
||||
* @method self maxlength(?int $val)
|
||||
* @method self minlength(?int $val)
|
||||
* @method self media(?string $val)
|
||||
* @method self method(?string $val)
|
||||
* @method self min(?float $val)
|
||||
* @method self multiple(?bool $val)
|
||||
* @method self muted(?bool $val)
|
||||
* @method self name(?string $val)
|
||||
* @method self novalidate(?bool $val)
|
||||
* @method self open(?bool $val)
|
||||
* @method self optimum(?float $val)
|
||||
* @method self pattern(?string $val)
|
||||
* @method self ping(?string $val, bool $state = null)
|
||||
* @method self placeholder(?string $val)
|
||||
* @method self poster(?string $val)
|
||||
* @method self preload(?string $val)
|
||||
* @method self radiogroup(?string $val)
|
||||
* @method self readonly(?bool $val)
|
||||
* @method self rel(?string $val)
|
||||
* @method self required(?bool $val)
|
||||
* @method self reversed(?bool $val)
|
||||
* @method self rows(?int $val)
|
||||
* @method self rowspan(?int $val)
|
||||
* @method self sandbox(?string $val, bool $state = null)
|
||||
* @method self scope(?string $val)
|
||||
* @method self selected(?bool $val)
|
||||
* @method self shape(?string $val)
|
||||
* @method self size(?int $val)
|
||||
* @method self sizes(?string $val)
|
||||
* @method self slot(?string $val)
|
||||
* @method self span(?int $val)
|
||||
* @method self spellcheck(?string $val)
|
||||
* @method self src(?string $val)
|
||||
* @method self srcdoc(?string $val)
|
||||
* @method self srclang(?string $val)
|
||||
* @method self srcset(?string $val)
|
||||
* @method self start(?int $val)
|
||||
* @method self step(?float $val)
|
||||
* @method self style(?string $property, string $val = null)
|
||||
* @method self tabindex(?int $val)
|
||||
* @method self target(?string $val)
|
||||
* @method self title(?string $val)
|
||||
* @method self translate(?string $val)
|
||||
* @method self type(?string $val)
|
||||
* @method self usemap(?string $val)
|
||||
* @method self value(?string $val)
|
||||
* @method self width(?int $val)
|
||||
* @method self wrap(?string $val)
|
||||
*/
|
||||
class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringable
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var array<string, mixed> element's attributes */
|
||||
public $attrs = [];
|
||||
|
||||
/** @var bool use XHTML syntax? */
|
||||
public static $xhtml = false;
|
||||
|
||||
/** @var array<string, int> void elements */
|
||||
public static $emptyElements = [
|
||||
'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1,
|
||||
'source' => 1, 'base' => 1, 'col' => 1, 'link' => 1, 'param' => 1, 'basefont' => 1, 'frame' => 1,
|
||||
'isindex' => 1, 'wbr' => 1, 'command' => 1, 'track' => 1,
|
||||
];
|
||||
|
||||
/** @var array<int, HtmlStringable|string> nodes */
|
||||
protected $children = [];
|
||||
|
||||
/** @var string element's name */
|
||||
private $name;
|
||||
|
||||
/** @var bool is element empty? */
|
||||
private $isEmpty;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs new HTML element.
|
||||
* @param array|string $attrs element's attributes or plain text content
|
||||
* @return static
|
||||
*/
|
||||
public static function el(?string $name = null, $attrs = null)
|
||||
{
|
||||
$el = new static;
|
||||
$parts = explode(' ', (string) $name, 2);
|
||||
$el->setName($parts[0]);
|
||||
|
||||
if (is_array($attrs)) {
|
||||
$el->attrs = $attrs;
|
||||
|
||||
} elseif ($attrs !== null) {
|
||||
$el->setText($attrs);
|
||||
}
|
||||
|
||||
if (isset($parts[1])) {
|
||||
foreach (Strings::matchAll($parts[1] . ' ', '#([a-z0-9:-]+)(?:=(["\'])?(.*?)(?(2)\2|\s))?#i') as $m) {
|
||||
$el->attrs[$m[1]] = $m[3] ?? true;
|
||||
}
|
||||
}
|
||||
|
||||
return $el;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an object representing HTML text.
|
||||
*/
|
||||
public static function fromHtml(string $html): self
|
||||
{
|
||||
return (new static)->setHtml($html);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an object representing plain text.
|
||||
*/
|
||||
public static function fromText(string $text): self
|
||||
{
|
||||
return (new static)->setText($text);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts to HTML.
|
||||
*/
|
||||
final public function toHtml(): string
|
||||
{
|
||||
return $this->render();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts to plain text.
|
||||
*/
|
||||
final public function toText(): string
|
||||
{
|
||||
return $this->getText();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts given HTML code to plain text.
|
||||
*/
|
||||
public static function htmlToText(string $html): string
|
||||
{
|
||||
return html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Changes element's name.
|
||||
* @return static
|
||||
*/
|
||||
final public function setName(string $name, ?bool $isEmpty = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->isEmpty = $isEmpty ?? isset(static::$emptyElements[$name]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns element's name.
|
||||
*/
|
||||
final public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is element empty?
|
||||
*/
|
||||
final public function isEmpty(): bool
|
||||
{
|
||||
return $this->isEmpty;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets multiple attributes.
|
||||
* @return static
|
||||
*/
|
||||
public function addAttributes(array $attrs)
|
||||
{
|
||||
$this->attrs = array_merge($this->attrs, $attrs);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appends value to element's attribute.
|
||||
* @param mixed $value
|
||||
* @param mixed $option
|
||||
* @return static
|
||||
*/
|
||||
public function appendAttribute(string $name, $value, $option = true)
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$prev = isset($this->attrs[$name]) ? (array) $this->attrs[$name] : [];
|
||||
$this->attrs[$name] = $value + $prev;
|
||||
|
||||
} elseif ((string) $value === '') {
|
||||
$tmp = &$this->attrs[$name]; // appending empty value? -> ignore, but ensure it exists
|
||||
|
||||
} elseif (!isset($this->attrs[$name]) || is_array($this->attrs[$name])) { // needs array
|
||||
$this->attrs[$name][$value] = $option;
|
||||
|
||||
} else {
|
||||
$this->attrs[$name] = [$this->attrs[$name] => true, $value => $option];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets element's attribute.
|
||||
* @param mixed $value
|
||||
* @return static
|
||||
*/
|
||||
public function setAttribute(string $name, $value)
|
||||
{
|
||||
$this->attrs[$name] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns element's attribute.
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAttribute(string $name)
|
||||
{
|
||||
return $this->attrs[$name] ?? null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unsets element's attribute.
|
||||
* @return static
|
||||
*/
|
||||
public function removeAttribute(string $name)
|
||||
{
|
||||
unset($this->attrs[$name]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unsets element's attributes.
|
||||
* @return static
|
||||
*/
|
||||
public function removeAttributes(array $attributes)
|
||||
{
|
||||
foreach ($attributes as $name) {
|
||||
unset($this->attrs[$name]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Overloaded setter for element's attribute.
|
||||
* @param mixed $value
|
||||
*/
|
||||
final public function __set(string $name, $value): void
|
||||
{
|
||||
$this->attrs[$name] = $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Overloaded getter for element's attribute.
|
||||
* @return mixed
|
||||
*/
|
||||
final public function &__get(string $name)
|
||||
{
|
||||
return $this->attrs[$name];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Overloaded tester for element's attribute.
|
||||
*/
|
||||
final public function __isset(string $name): bool
|
||||
{
|
||||
return isset($this->attrs[$name]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Overloaded unsetter for element's attribute.
|
||||
*/
|
||||
final public function __unset(string $name): void
|
||||
{
|
||||
unset($this->attrs[$name]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Overloaded setter for element's attribute.
|
||||
* @return mixed
|
||||
*/
|
||||
final public function __call(string $m, array $args)
|
||||
{
|
||||
$p = substr($m, 0, 3);
|
||||
if ($p === 'get' || $p === 'set' || $p === 'add') {
|
||||
$m = substr($m, 3);
|
||||
$m[0] = $m[0] | "\x20";
|
||||
if ($p === 'get') {
|
||||
return $this->attrs[$m] ?? null;
|
||||
|
||||
} elseif ($p === 'add') {
|
||||
$args[] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($args) === 0) { // invalid
|
||||
|
||||
} elseif (count($args) === 1) { // set
|
||||
$this->attrs[$m] = $args[0];
|
||||
|
||||
} else { // add
|
||||
$this->appendAttribute($m, $args[0], $args[1]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Special setter for element's attribute.
|
||||
* @return static
|
||||
*/
|
||||
final public function href(string $path, ?array $query = null)
|
||||
{
|
||||
if ($query) {
|
||||
$query = http_build_query($query, '', '&');
|
||||
if ($query !== '') {
|
||||
$path .= '?' . $query;
|
||||
}
|
||||
}
|
||||
|
||||
$this->attrs['href'] = $path;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Setter for data-* attributes. Booleans are converted to 'true' resp. 'false'.
|
||||
* @param mixed $value
|
||||
* @return static
|
||||
*/
|
||||
public function data(string $name, $value = null)
|
||||
{
|
||||
if (func_num_args() === 1) {
|
||||
$this->attrs['data'] = $name;
|
||||
} else {
|
||||
$this->attrs["data-$name"] = is_bool($value)
|
||||
? json_encode($value)
|
||||
: $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets element's HTML content.
|
||||
* @param HtmlStringable|string $html
|
||||
* @return static
|
||||
*/
|
||||
final public function setHtml($html)
|
||||
{
|
||||
$this->children = [(string) $html];
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns element's HTML content.
|
||||
*/
|
||||
final public function getHtml(): string
|
||||
{
|
||||
return implode('', $this->children);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets element's textual content.
|
||||
* @param HtmlStringable|string|int|float $text
|
||||
* @return static
|
||||
*/
|
||||
final public function setText($text)
|
||||
{
|
||||
if (!$text instanceof HtmlStringable) {
|
||||
$text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
$this->children = [(string) $text];
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns element's textual content.
|
||||
*/
|
||||
final public function getText(): string
|
||||
{
|
||||
return self::htmlToText($this->getHtml());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds new element's child.
|
||||
* @param HtmlStringable|string $child Html node or raw HTML string
|
||||
* @return static
|
||||
*/
|
||||
final public function addHtml($child)
|
||||
{
|
||||
return $this->insert(null, $child);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appends plain-text string to element content.
|
||||
* @param HtmlStringable|string|int|float $text
|
||||
* @return static
|
||||
*/
|
||||
public function addText($text)
|
||||
{
|
||||
if (!$text instanceof HtmlStringable) {
|
||||
$text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
return $this->insert(null, $text);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates and adds a new Html child.
|
||||
* @param array|string $attrs element's attributes or raw HTML string
|
||||
* @return static created element
|
||||
*/
|
||||
final public function create(string $name, $attrs = null)
|
||||
{
|
||||
$this->insert(null, $child = static::el($name, $attrs));
|
||||
return $child;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inserts child node.
|
||||
* @param HtmlStringable|string $child Html node or raw HTML string
|
||||
* @return static
|
||||
*/
|
||||
public function insert(?int $index, $child, bool $replace = false)
|
||||
{
|
||||
$child = $child instanceof self ? $child : (string) $child;
|
||||
if ($index === null) { // append
|
||||
$this->children[] = $child;
|
||||
|
||||
} else { // insert or replace
|
||||
array_splice($this->children, $index, $replace ? 1 : 0, [$child]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inserts (replaces) child node (\ArrayAccess implementation).
|
||||
* @param int|null $index position or null for appending
|
||||
* @param Html|string $child Html node or raw HTML string
|
||||
*/
|
||||
final public function offsetSet($index, $child): void
|
||||
{
|
||||
$this->insert($index, $child, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns child node (\ArrayAccess implementation).
|
||||
* @param int $index
|
||||
* @return HtmlStringable|string
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
final public function offsetGet($index)
|
||||
{
|
||||
return $this->children[$index];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Exists child node? (\ArrayAccess implementation).
|
||||
* @param int $index
|
||||
*/
|
||||
final public function offsetExists($index): bool
|
||||
{
|
||||
return isset($this->children[$index]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes child node (\ArrayAccess implementation).
|
||||
* @param int $index
|
||||
*/
|
||||
public function offsetUnset($index): void
|
||||
{
|
||||
if (isset($this->children[$index])) {
|
||||
array_splice($this->children, $index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns children count.
|
||||
*/
|
||||
final public function count(): int
|
||||
{
|
||||
return count($this->children);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all children.
|
||||
*/
|
||||
public function removeChildren(): void
|
||||
{
|
||||
$this->children = [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Iterates over elements.
|
||||
* @return \ArrayIterator<int, HtmlStringable|string>
|
||||
*/
|
||||
final public function getIterator(): \ArrayIterator
|
||||
{
|
||||
return new \ArrayIterator($this->children);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all children.
|
||||
*/
|
||||
final public function getChildren(): array
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renders element's start tag, content and end tag.
|
||||
*/
|
||||
final public function render(?int $indent = null): string
|
||||
{
|
||||
$s = $this->startTag();
|
||||
|
||||
if (!$this->isEmpty) {
|
||||
// add content
|
||||
if ($indent !== null) {
|
||||
$indent++;
|
||||
}
|
||||
|
||||
foreach ($this->children as $child) {
|
||||
if ($child instanceof self) {
|
||||
$s .= $child->render($indent);
|
||||
} else {
|
||||
$s .= $child;
|
||||
}
|
||||
}
|
||||
|
||||
// add end tag
|
||||
$s .= $this->endTag();
|
||||
}
|
||||
|
||||
if ($indent !== null) {
|
||||
return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2));
|
||||
}
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
final public function __toString(): string
|
||||
{
|
||||
try {
|
||||
return $this->render();
|
||||
} catch (\Throwable $e) {
|
||||
if (PHP_VERSION_ID >= 70400) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns element's start tag.
|
||||
*/
|
||||
final public function startTag(): string
|
||||
{
|
||||
return $this->name
|
||||
? '<' . $this->name . $this->attributes() . (static::$xhtml && $this->isEmpty ? ' />' : '>')
|
||||
: '';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns element's end tag.
|
||||
*/
|
||||
final public function endTag(): string
|
||||
{
|
||||
return $this->name && !$this->isEmpty ? '</' . $this->name . '>' : '';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns element's attributes.
|
||||
* @internal
|
||||
*/
|
||||
final public function attributes(): string
|
||||
{
|
||||
if (!is_array($this->attrs)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$s = '';
|
||||
$attrs = $this->attrs;
|
||||
foreach ($attrs as $key => $value) {
|
||||
if ($value === null || $value === false) {
|
||||
continue;
|
||||
|
||||
} elseif ($value === true) {
|
||||
if (static::$xhtml) {
|
||||
$s .= ' ' . $key . '="' . $key . '"';
|
||||
} else {
|
||||
$s .= ' ' . $key;
|
||||
}
|
||||
|
||||
continue;
|
||||
|
||||
} elseif (is_array($value)) {
|
||||
if (strncmp($key, 'data-', 5) === 0) {
|
||||
$value = Json::encode($value);
|
||||
|
||||
} else {
|
||||
$tmp = null;
|
||||
foreach ($value as $k => $v) {
|
||||
if ($v != null) { // intentionally ==, skip nulls & empty string
|
||||
// composite 'style' vs. 'others'
|
||||
$tmp[] = $v === true
|
||||
? $k
|
||||
: (is_string($k) ? $k . ':' . $v : $v);
|
||||
}
|
||||
}
|
||||
|
||||
if ($tmp === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp);
|
||||
}
|
||||
} elseif (is_float($value)) {
|
||||
$value = rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.');
|
||||
|
||||
} else {
|
||||
$value = (string) $value;
|
||||
}
|
||||
|
||||
$q = strpos($value, '"') === false ? '"' : "'";
|
||||
$s .= ' ' . $key . '=' . $q
|
||||
. str_replace(
|
||||
['&', $q, '<'],
|
||||
['&', $q === '"' ? '"' : ''', self::$xhtml ? '<' : '<'],
|
||||
$value
|
||||
)
|
||||
. (strpos($value, '`') !== false && strpbrk($value, ' <>"\'') === false ? ' ' : '')
|
||||
. $q;
|
||||
}
|
||||
|
||||
$s = str_replace('@', '@', $s);
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clones all children too.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach ($this->children as $key => $value) {
|
||||
if (is_object($value)) {
|
||||
$this->children[$key] = clone $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
772
vendor/nette/utils/src/Utils/Image.php
vendored
Normal file
772
vendor/nette/utils/src/Utils/Image.php
vendored
Normal file
@@ -0,0 +1,772 @@
|
||||
<?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\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Basic manipulation with images. Supported types are JPEG, PNG, GIF, WEBP, AVIF and BMP.
|
||||
*
|
||||
* <code>
|
||||
* $image = Image::fromFile('nette.jpg');
|
||||
* $image->resize(150, 100);
|
||||
* $image->sharpen();
|
||||
* $image->send();
|
||||
* </code>
|
||||
*
|
||||
* @method Image affine(array $affine, array $clip = null)
|
||||
* @method array affineMatrixConcat(array $m1, array $m2)
|
||||
* @method array affineMatrixGet(int $type, mixed $options = null)
|
||||
* @method void alphaBlending(bool $on)
|
||||
* @method void antialias(bool $on)
|
||||
* @method void arc($x, $y, $w, $h, $start, $end, $color)
|
||||
* @method void char(int $font, $x, $y, string $char, $color)
|
||||
* @method void charUp(int $font, $x, $y, string $char, $color)
|
||||
* @method int colorAllocate($red, $green, $blue)
|
||||
* @method int colorAllocateAlpha($red, $green, $blue, $alpha)
|
||||
* @method int colorAt($x, $y)
|
||||
* @method int colorClosest($red, $green, $blue)
|
||||
* @method int colorClosestAlpha($red, $green, $blue, $alpha)
|
||||
* @method int colorClosestHWB($red, $green, $blue)
|
||||
* @method void colorDeallocate($color)
|
||||
* @method int colorExact($red, $green, $blue)
|
||||
* @method int colorExactAlpha($red, $green, $blue, $alpha)
|
||||
* @method void colorMatch(Image $image2)
|
||||
* @method int colorResolve($red, $green, $blue)
|
||||
* @method int colorResolveAlpha($red, $green, $blue, $alpha)
|
||||
* @method void colorSet($index, $red, $green, $blue)
|
||||
* @method array colorsForIndex($index)
|
||||
* @method int colorsTotal()
|
||||
* @method int colorTransparent($color = null)
|
||||
* @method void convolution(array $matrix, float $div, float $offset)
|
||||
* @method void copy(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH)
|
||||
* @method void copyMerge(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity)
|
||||
* @method void copyMergeGray(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity)
|
||||
* @method void copyResampled(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
|
||||
* @method void copyResized(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
|
||||
* @method Image cropAuto(int $mode = -1, float $threshold = .5, int $color = -1)
|
||||
* @method void ellipse($cx, $cy, $w, $h, $color)
|
||||
* @method void fill($x, $y, $color)
|
||||
* @method void filledArc($cx, $cy, $w, $h, $s, $e, $color, $style)
|
||||
* @method void filledEllipse($cx, $cy, $w, $h, $color)
|
||||
* @method void filledPolygon(array $points, $numPoints, $color)
|
||||
* @method void filledRectangle($x1, $y1, $x2, $y2, $color)
|
||||
* @method void fillToBorder($x, $y, $border, $color)
|
||||
* @method void filter($filtertype)
|
||||
* @method void flip(int $mode)
|
||||
* @method array ftText($size, $angle, $x, $y, $col, string $fontFile, string $text, array $extrainfo = null)
|
||||
* @method void gammaCorrect(float $inputgamma, float $outputgamma)
|
||||
* @method array getClip()
|
||||
* @method int interlace($interlace = null)
|
||||
* @method bool isTrueColor()
|
||||
* @method void layerEffect($effect)
|
||||
* @method void line($x1, $y1, $x2, $y2, $color)
|
||||
* @method void openPolygon(array $points, int $num_points, int $color)
|
||||
* @method void paletteCopy(Image $source)
|
||||
* @method void paletteToTrueColor()
|
||||
* @method void polygon(array $points, $numPoints, $color)
|
||||
* @method array psText(string $text, $font, $size, $color, $backgroundColor, $x, $y, $space = null, $tightness = null, float $angle = null, $antialiasSteps = null)
|
||||
* @method void rectangle($x1, $y1, $x2, $y2, $col)
|
||||
* @method mixed resolution(int $res_x = null, int $res_y = null)
|
||||
* @method Image rotate(float $angle, $backgroundColor)
|
||||
* @method void saveAlpha(bool $saveflag)
|
||||
* @method Image scale(int $newWidth, int $newHeight = -1, int $mode = IMG_BILINEAR_FIXED)
|
||||
* @method void setBrush(Image $brush)
|
||||
* @method void setClip(int $x1, int $y1, int $x2, int $y2)
|
||||
* @method void setInterpolation(int $method = IMG_BILINEAR_FIXED)
|
||||
* @method void setPixel($x, $y, $color)
|
||||
* @method void setStyle(array $style)
|
||||
* @method void setThickness($thickness)
|
||||
* @method void setTile(Image $tile)
|
||||
* @method void string($font, $x, $y, string $s, $col)
|
||||
* @method void stringUp($font, $x, $y, string $s, $col)
|
||||
* @method void trueColorToPalette(bool $dither, $ncolors)
|
||||
* @method array ttfText($size, $angle, $x, $y, $color, string $fontfile, string $text)
|
||||
* @property-read positive-int $width
|
||||
* @property-read positive-int $height
|
||||
* @property-read resource|\GdImage $imageResource
|
||||
*/
|
||||
class Image
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** Prevent from getting resized to a bigger size than the original */
|
||||
public const SHRINK_ONLY = 0b0001;
|
||||
|
||||
/** Resizes to a specified width and height without keeping aspect ratio */
|
||||
public const STRETCH = 0b0010;
|
||||
|
||||
/** Resizes to fit into a specified width and height and preserves aspect ratio */
|
||||
public const FIT = 0b0000;
|
||||
|
||||
/** Resizes while bounding the smaller dimension to the specified width or height and preserves aspect ratio */
|
||||
public const FILL = 0b0100;
|
||||
|
||||
/** Resizes to the smallest possible size to completely cover specified width and height and reserves aspect ratio */
|
||||
public const EXACT = 0b1000;
|
||||
|
||||
/** image types */
|
||||
public const
|
||||
JPEG = IMAGETYPE_JPEG,
|
||||
PNG = IMAGETYPE_PNG,
|
||||
GIF = IMAGETYPE_GIF,
|
||||
WEBP = IMAGETYPE_WEBP,
|
||||
AVIF = 19, // IMAGETYPE_AVIF,
|
||||
BMP = IMAGETYPE_BMP;
|
||||
|
||||
public const EMPTY_GIF = "GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;";
|
||||
|
||||
private const Formats = [self::JPEG => 'jpeg', self::PNG => 'png', self::GIF => 'gif', self::WEBP => 'webp', self::AVIF => 'avif', self::BMP => 'bmp'];
|
||||
|
||||
/** @var resource|\GdImage */
|
||||
private $image;
|
||||
|
||||
|
||||
/**
|
||||
* Returns RGB color (0..255) and transparency (0..127).
|
||||
*/
|
||||
public static function rgb(int $red, int $green, int $blue, int $transparency = 0): array
|
||||
{
|
||||
return [
|
||||
'red' => max(0, min(255, $red)),
|
||||
'green' => max(0, min(255, $green)),
|
||||
'blue' => max(0, min(255, $blue)),
|
||||
'alpha' => max(0, min(127, $transparency)),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads an image from a file and returns its type in $type.
|
||||
* @throws Nette\NotSupportedException if gd extension is not loaded
|
||||
* @throws UnknownImageFileException if file not found or file type is not known
|
||||
* @return static
|
||||
*/
|
||||
public static function fromFile(string $file, ?int &$type = null)
|
||||
{
|
||||
if (!extension_loaded('gd')) {
|
||||
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
|
||||
}
|
||||
|
||||
$type = self::detectTypeFromFile($file);
|
||||
if (!$type) {
|
||||
throw new UnknownImageFileException(is_file($file) ? "Unknown type of file '$file'." : "File '$file' not found.");
|
||||
}
|
||||
|
||||
return self::invokeSafe('imagecreatefrom' . self::Formats[$type], $file, "Unable to open file '$file'.", __METHOD__);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads an image from a string and returns its type in $type.
|
||||
* @return static
|
||||
* @throws Nette\NotSupportedException if gd extension is not loaded
|
||||
* @throws ImageException
|
||||
*/
|
||||
public static function fromString(string $s, ?int &$type = null)
|
||||
{
|
||||
if (!extension_loaded('gd')) {
|
||||
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
|
||||
}
|
||||
|
||||
$type = self::detectTypeFromString($s);
|
||||
if (!$type) {
|
||||
throw new UnknownImageFileException('Unknown type of image.');
|
||||
}
|
||||
|
||||
return self::invokeSafe('imagecreatefromstring', $s, 'Unable to open image from string.', __METHOD__);
|
||||
}
|
||||
|
||||
|
||||
private static function invokeSafe(string $func, string $arg, string $message, string $callee): self
|
||||
{
|
||||
$errors = [];
|
||||
$res = Callback::invokeSafe($func, [$arg], function (string $message) use (&$errors): void {
|
||||
$errors[] = $message;
|
||||
});
|
||||
|
||||
if (!$res) {
|
||||
throw new ImageException($message . ' Errors: ' . implode(', ', $errors));
|
||||
} elseif ($errors) {
|
||||
trigger_error($callee . '(): ' . implode(', ', $errors), E_USER_WARNING);
|
||||
}
|
||||
|
||||
return new static($res);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new true color image of the given dimensions. The default color is black.
|
||||
* @param positive-int $width
|
||||
* @param positive-int $height
|
||||
* @return static
|
||||
* @throws Nette\NotSupportedException if gd extension is not loaded
|
||||
*/
|
||||
public static function fromBlank(int $width, int $height, ?array $color = null)
|
||||
{
|
||||
if (!extension_loaded('gd')) {
|
||||
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
|
||||
}
|
||||
|
||||
if ($width < 1 || $height < 1) {
|
||||
throw new Nette\InvalidArgumentException('Image width and height must be greater than zero.');
|
||||
}
|
||||
|
||||
$image = imagecreatetruecolor($width, $height);
|
||||
if ($color) {
|
||||
$color += ['alpha' => 0];
|
||||
$color = imagecolorresolvealpha($image, $color['red'], $color['green'], $color['blue'], $color['alpha']);
|
||||
imagealphablending($image, false);
|
||||
imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, $color);
|
||||
imagealphablending($image, true);
|
||||
}
|
||||
|
||||
return new static($image);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the type of image from file.
|
||||
*/
|
||||
public static function detectTypeFromFile(string $file, &$width = null, &$height = null): ?int
|
||||
{
|
||||
[$width, $height, $type] = @getimagesize($file); // @ - files smaller than 12 bytes causes read error
|
||||
return isset(self::Formats[$type]) ? $type : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the type of image from string.
|
||||
*/
|
||||
public static function detectTypeFromString(string $s, &$width = null, &$height = null): ?int
|
||||
{
|
||||
[$width, $height, $type] = @getimagesizefromstring($s); // @ - strings smaller than 12 bytes causes read error
|
||||
return isset(self::Formats[$type]) ? $type : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the file extension for the given `Image::XXX` constant.
|
||||
*/
|
||||
public static function typeToExtension(int $type): string
|
||||
{
|
||||
if (!isset(self::Formats[$type])) {
|
||||
throw new Nette\InvalidArgumentException("Unsupported image type '$type'.");
|
||||
}
|
||||
|
||||
return self::Formats[$type];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the `Image::XXX` constant for given file extension.
|
||||
*/
|
||||
public static function extensionToType(string $extension): int
|
||||
{
|
||||
$extensions = array_flip(self::Formats) + ['jpg' => self::JPEG];
|
||||
$extension = strtolower($extension);
|
||||
if (!isset($extensions[$extension])) {
|
||||
throw new Nette\InvalidArgumentException("Unsupported file extension '$extension'.");
|
||||
}
|
||||
|
||||
return $extensions[$extension];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the mime type for the given `Image::XXX` constant.
|
||||
*/
|
||||
public static function typeToMimeType(int $type): string
|
||||
{
|
||||
return 'image/' . self::typeToExtension($type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wraps GD image.
|
||||
* @param resource|\GdImage $image
|
||||
*/
|
||||
public function __construct($image)
|
||||
{
|
||||
$this->setImageResource($image);
|
||||
imagesavealpha($image, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns image width.
|
||||
* @return positive-int
|
||||
*/
|
||||
public function getWidth(): int
|
||||
{
|
||||
return imagesx($this->image);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns image height.
|
||||
* @return positive-int
|
||||
*/
|
||||
public function getHeight(): int
|
||||
{
|
||||
return imagesy($this->image);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets image resource.
|
||||
* @param resource|\GdImage $image
|
||||
* @return static
|
||||
*/
|
||||
protected function setImageResource($image)
|
||||
{
|
||||
if (!$image instanceof \GdImage && !(is_resource($image) && get_resource_type($image) === 'gd')) {
|
||||
throw new Nette\InvalidArgumentException('Image is not valid.');
|
||||
}
|
||||
|
||||
$this->image = $image;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns image GD resource.
|
||||
* @return resource|\GdImage
|
||||
*/
|
||||
public function getImageResource()
|
||||
{
|
||||
return $this->image;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Scales an image.
|
||||
* @param int|string|null $width in pixels or percent
|
||||
* @param int|string|null $height in pixels or percent
|
||||
* @return static
|
||||
*/
|
||||
public function resize($width, $height, int $flags = self::FIT)
|
||||
{
|
||||
if ($flags & self::EXACT) {
|
||||
return $this->resize($width, $height, self::FILL)->crop('50%', '50%', $width, $height);
|
||||
}
|
||||
|
||||
[$newWidth, $newHeight] = static::calculateSize($this->getWidth(), $this->getHeight(), $width, $height, $flags);
|
||||
|
||||
if ($newWidth !== $this->getWidth() || $newHeight !== $this->getHeight()) { // resize
|
||||
$newImage = static::fromBlank($newWidth, $newHeight, self::rgb(0, 0, 0, 127))->getImageResource();
|
||||
imagecopyresampled(
|
||||
$newImage,
|
||||
$this->image,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
$newWidth,
|
||||
$newHeight,
|
||||
$this->getWidth(),
|
||||
$this->getHeight()
|
||||
);
|
||||
$this->image = $newImage;
|
||||
}
|
||||
|
||||
if ($width < 0 || $height < 0) {
|
||||
imageflip($this->image, $width < 0 ? ($height < 0 ? IMG_FLIP_BOTH : IMG_FLIP_HORIZONTAL) : IMG_FLIP_VERTICAL);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates dimensions of resized image.
|
||||
* @param int|string|null $newWidth in pixels or percent
|
||||
* @param int|string|null $newHeight in pixels or percent
|
||||
*/
|
||||
public static function calculateSize(
|
||||
int $srcWidth,
|
||||
int $srcHeight,
|
||||
$newWidth,
|
||||
$newHeight,
|
||||
int $flags = self::FIT
|
||||
): array
|
||||
{
|
||||
if ($newWidth === null) {
|
||||
} elseif (self::isPercent($newWidth)) {
|
||||
$newWidth = (int) round($srcWidth / 100 * abs($newWidth));
|
||||
$percents = true;
|
||||
} else {
|
||||
$newWidth = abs($newWidth);
|
||||
}
|
||||
|
||||
if ($newHeight === null) {
|
||||
} elseif (self::isPercent($newHeight)) {
|
||||
$newHeight = (int) round($srcHeight / 100 * abs($newHeight));
|
||||
$flags |= empty($percents) ? 0 : self::STRETCH;
|
||||
} else {
|
||||
$newHeight = abs($newHeight);
|
||||
}
|
||||
|
||||
if ($flags & self::STRETCH) { // non-proportional
|
||||
if (!$newWidth || !$newHeight) {
|
||||
throw new Nette\InvalidArgumentException('For stretching must be both width and height specified.');
|
||||
}
|
||||
|
||||
if ($flags & self::SHRINK_ONLY) {
|
||||
$newWidth = (int) round($srcWidth * min(1, $newWidth / $srcWidth));
|
||||
$newHeight = (int) round($srcHeight * min(1, $newHeight / $srcHeight));
|
||||
}
|
||||
} else { // proportional
|
||||
if (!$newWidth && !$newHeight) {
|
||||
throw new Nette\InvalidArgumentException('At least width or height must be specified.');
|
||||
}
|
||||
|
||||
$scale = [];
|
||||
if ($newWidth > 0) { // fit width
|
||||
$scale[] = $newWidth / $srcWidth;
|
||||
}
|
||||
|
||||
if ($newHeight > 0) { // fit height
|
||||
$scale[] = $newHeight / $srcHeight;
|
||||
}
|
||||
|
||||
if ($flags & self::FILL) {
|
||||
$scale = [max($scale)];
|
||||
}
|
||||
|
||||
if ($flags & self::SHRINK_ONLY) {
|
||||
$scale[] = 1;
|
||||
}
|
||||
|
||||
$scale = min($scale);
|
||||
$newWidth = (int) round($srcWidth * $scale);
|
||||
$newHeight = (int) round($srcHeight * $scale);
|
||||
}
|
||||
|
||||
return [max($newWidth, 1), max($newHeight, 1)];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Crops image.
|
||||
* @param int|string $left in pixels or percent
|
||||
* @param int|string $top in pixels or percent
|
||||
* @param int|string $width in pixels or percent
|
||||
* @param int|string $height in pixels or percent
|
||||
* @return static
|
||||
*/
|
||||
public function crop($left, $top, $width, $height)
|
||||
{
|
||||
[$r['x'], $r['y'], $r['width'], $r['height']]
|
||||
= static::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height);
|
||||
if (gd_info()['GD Version'] === 'bundled (2.1.0 compatible)') {
|
||||
$this->image = imagecrop($this->image, $r);
|
||||
imagesavealpha($this->image, true);
|
||||
} else {
|
||||
$newImage = static::fromBlank($r['width'], $r['height'], self::RGB(0, 0, 0, 127))->getImageResource();
|
||||
imagecopy($newImage, $this->image, 0, 0, $r['x'], $r['y'], $r['width'], $r['height']);
|
||||
$this->image = $newImage;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates dimensions of cutout in image.
|
||||
* @param int|string $left in pixels or percent
|
||||
* @param int|string $top in pixels or percent
|
||||
* @param int|string $newWidth in pixels or percent
|
||||
* @param int|string $newHeight in pixels or percent
|
||||
*/
|
||||
public static function calculateCutout(int $srcWidth, int $srcHeight, $left, $top, $newWidth, $newHeight): array
|
||||
{
|
||||
if (self::isPercent($newWidth)) {
|
||||
$newWidth = (int) round($srcWidth / 100 * $newWidth);
|
||||
}
|
||||
|
||||
if (self::isPercent($newHeight)) {
|
||||
$newHeight = (int) round($srcHeight / 100 * $newHeight);
|
||||
}
|
||||
|
||||
if (self::isPercent($left)) {
|
||||
$left = (int) round(($srcWidth - $newWidth) / 100 * $left);
|
||||
}
|
||||
|
||||
if (self::isPercent($top)) {
|
||||
$top = (int) round(($srcHeight - $newHeight) / 100 * $top);
|
||||
}
|
||||
|
||||
if ($left < 0) {
|
||||
$newWidth += $left;
|
||||
$left = 0;
|
||||
}
|
||||
|
||||
if ($top < 0) {
|
||||
$newHeight += $top;
|
||||
$top = 0;
|
||||
}
|
||||
|
||||
$newWidth = min($newWidth, $srcWidth - $left);
|
||||
$newHeight = min($newHeight, $srcHeight - $top);
|
||||
return [$left, $top, $newWidth, $newHeight];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sharpens image a little bit.
|
||||
* @return static
|
||||
*/
|
||||
public function sharpen()
|
||||
{
|
||||
imageconvolution($this->image, [ // my magic numbers ;)
|
||||
[-1, -1, -1],
|
||||
[-1, 24, -1],
|
||||
[-1, -1, -1],
|
||||
], 16, 0);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Puts another image into this image.
|
||||
* @param int|string $left in pixels or percent
|
||||
* @param int|string $top in pixels or percent
|
||||
* @param int<0, 100> $opacity 0..100
|
||||
* @return static
|
||||
*/
|
||||
public function place(self $image, $left = 0, $top = 0, int $opacity = 100)
|
||||
{
|
||||
$opacity = max(0, min(100, $opacity));
|
||||
if ($opacity === 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$width = $image->getWidth();
|
||||
$height = $image->getHeight();
|
||||
|
||||
if (self::isPercent($left)) {
|
||||
$left = (int) round(($this->getWidth() - $width) / 100 * $left);
|
||||
}
|
||||
|
||||
if (self::isPercent($top)) {
|
||||
$top = (int) round(($this->getHeight() - $height) / 100 * $top);
|
||||
}
|
||||
|
||||
$output = $input = $image->image;
|
||||
if ($opacity < 100) {
|
||||
$tbl = [];
|
||||
for ($i = 0; $i < 128; $i++) {
|
||||
$tbl[$i] = round(127 - (127 - $i) * $opacity / 100);
|
||||
}
|
||||
|
||||
$output = imagecreatetruecolor($width, $height);
|
||||
imagealphablending($output, false);
|
||||
if (!$image->isTrueColor()) {
|
||||
$input = $output;
|
||||
imagefilledrectangle($output, 0, 0, $width, $height, imagecolorallocatealpha($output, 0, 0, 0, 127));
|
||||
imagecopy($output, $image->image, 0, 0, 0, 0, $width, $height);
|
||||
}
|
||||
|
||||
for ($x = 0; $x < $width; $x++) {
|
||||
for ($y = 0; $y < $height; $y++) {
|
||||
$c = \imagecolorat($input, $x, $y);
|
||||
$c = ($c & 0xFFFFFF) + ($tbl[$c >> 24] << 24);
|
||||
\imagesetpixel($output, $x, $y, $c);
|
||||
}
|
||||
}
|
||||
|
||||
imagealphablending($output, true);
|
||||
}
|
||||
|
||||
imagecopy(
|
||||
$this->image,
|
||||
$output,
|
||||
$left,
|
||||
$top,
|
||||
0,
|
||||
0,
|
||||
$width,
|
||||
$height
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves image to the file. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9).
|
||||
* @throws ImageException
|
||||
*/
|
||||
public function save(string $file, ?int $quality = null, ?int $type = null): void
|
||||
{
|
||||
$type = $type ?? self::extensionToType(pathinfo($file, PATHINFO_EXTENSION));
|
||||
$this->output($type, $quality, $file);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Outputs image to string. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9).
|
||||
*/
|
||||
public function toString(int $type = self::JPEG, ?int $quality = null): string
|
||||
{
|
||||
return Helpers::capture(function () use ($type, $quality) {
|
||||
$this->output($type, $quality);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Outputs image to string.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
try {
|
||||
return $this->toString();
|
||||
} catch (\Throwable $e) {
|
||||
if (func_num_args() || PHP_VERSION_ID >= 70400) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Outputs image to browser. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9).
|
||||
* @throws ImageException
|
||||
*/
|
||||
public function send(int $type = self::JPEG, ?int $quality = null): void
|
||||
{
|
||||
header('Content-Type: ' . self::typeToMimeType($type));
|
||||
$this->output($type, $quality);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Outputs image to browser or file.
|
||||
* @throws ImageException
|
||||
*/
|
||||
private function output(int $type, ?int $quality, ?string $file = null): void
|
||||
{
|
||||
switch ($type) {
|
||||
case self::JPEG:
|
||||
$quality = $quality === null ? 85 : max(0, min(100, $quality));
|
||||
$success = @imagejpeg($this->image, $file, $quality); // @ is escalated to exception
|
||||
break;
|
||||
|
||||
case self::PNG:
|
||||
$quality = $quality === null ? 9 : max(0, min(9, $quality));
|
||||
$success = @imagepng($this->image, $file, $quality); // @ is escalated to exception
|
||||
break;
|
||||
|
||||
case self::GIF:
|
||||
$success = @imagegif($this->image, $file); // @ is escalated to exception
|
||||
break;
|
||||
|
||||
case self::WEBP:
|
||||
$quality = $quality === null ? 80 : max(0, min(100, $quality));
|
||||
$success = @imagewebp($this->image, $file, $quality); // @ is escalated to exception
|
||||
break;
|
||||
|
||||
case self::AVIF:
|
||||
$quality = $quality === null ? 30 : max(0, min(100, $quality));
|
||||
$success = @imageavif($this->image, $file, $quality); // @ is escalated to exception
|
||||
break;
|
||||
|
||||
case self::BMP:
|
||||
$success = @imagebmp($this->image, $file); // @ is escalated to exception
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Nette\InvalidArgumentException("Unsupported image type '$type'.");
|
||||
}
|
||||
|
||||
if (!$success) {
|
||||
throw new ImageException(Helpers::getLastError() ?: 'Unknown error');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Call to undefined method.
|
||||
* @return mixed
|
||||
* @throws Nette\MemberAccessException
|
||||
*/
|
||||
public function __call(string $name, array $args)
|
||||
{
|
||||
$function = 'image' . $name;
|
||||
if (!function_exists($function)) {
|
||||
ObjectHelpers::strictCall(static::class, $name);
|
||||
}
|
||||
|
||||
foreach ($args as $key => $value) {
|
||||
if ($value instanceof self) {
|
||||
$args[$key] = $value->getImageResource();
|
||||
|
||||
} elseif (is_array($value) && isset($value['red'])) { // rgb
|
||||
$args[$key] = imagecolorallocatealpha(
|
||||
$this->image,
|
||||
$value['red'],
|
||||
$value['green'],
|
||||
$value['blue'],
|
||||
$value['alpha']
|
||||
) ?: imagecolorresolvealpha(
|
||||
$this->image,
|
||||
$value['red'],
|
||||
$value['green'],
|
||||
$value['blue'],
|
||||
$value['alpha']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$res = $function($this->image, ...$args);
|
||||
return $res instanceof \GdImage || (is_resource($res) && get_resource_type($res) === 'gd')
|
||||
? $this->setImageResource($res)
|
||||
: $res;
|
||||
}
|
||||
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
ob_start(function () {});
|
||||
imagepng($this->image, null, 0);
|
||||
$this->setImageResource(imagecreatefromstring(ob_get_clean()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param int|string $num in pixels or percent
|
||||
*/
|
||||
private static function isPercent(&$num): bool
|
||||
{
|
||||
if (is_string($num) && substr($num, -1) === '%') {
|
||||
$num = (float) substr($num, 0, -1);
|
||||
return true;
|
||||
} elseif (is_int($num) || $num === (string) (int) $num) {
|
||||
$num = (int) $num;
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new Nette\InvalidArgumentException("Expected dimension in int|string, '$num' given.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prevents serialization.
|
||||
*/
|
||||
public function __sleep(): array
|
||||
{
|
||||
throw new Nette\NotSupportedException('You cannot serialize or unserialize ' . self::class . ' instances.');
|
||||
}
|
||||
}
|
||||
63
vendor/nette/utils/src/Utils/Json.php
vendored
Normal file
63
vendor/nette/utils/src/Utils/Json.php
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
<?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\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* JSON encoder and decoder.
|
||||
*/
|
||||
final class Json
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
public const FORCE_ARRAY = JSON_OBJECT_AS_ARRAY;
|
||||
public const PRETTY = JSON_PRETTY_PRINT;
|
||||
public const ESCAPE_UNICODE = 1 << 19;
|
||||
|
||||
|
||||
/**
|
||||
* Converts value to JSON format. The flag can be Json::PRETTY, which formats JSON for easier reading and clarity,
|
||||
* and Json::ESCAPE_UNICODE for ASCII output.
|
||||
* @param mixed $value
|
||||
* @throws JsonException
|
||||
*/
|
||||
public static function encode($value, int $flags = 0): string
|
||||
{
|
||||
$flags = ($flags & self::ESCAPE_UNICODE ? 0 : JSON_UNESCAPED_UNICODE)
|
||||
| JSON_UNESCAPED_SLASHES
|
||||
| ($flags & ~self::ESCAPE_UNICODE)
|
||||
| (defined('JSON_PRESERVE_ZERO_FRACTION') ? JSON_PRESERVE_ZERO_FRACTION : 0); // since PHP 5.6.6 & PECL JSON-C 1.3.7
|
||||
|
||||
$json = json_encode($value, $flags);
|
||||
if ($error = json_last_error()) {
|
||||
throw new JsonException(json_last_error_msg(), $error);
|
||||
}
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses JSON to PHP value. The flag can be Json::FORCE_ARRAY, which forces an array instead of an object as the return value.
|
||||
* @return mixed
|
||||
* @throws JsonException
|
||||
*/
|
||||
public static function decode(string $json, int $flags = 0)
|
||||
{
|
||||
$value = json_decode($json, null, 512, $flags | JSON_BIGINT_AS_STRING);
|
||||
if ($error = json_last_error()) {
|
||||
throw new JsonException(json_last_error_msg(), $error);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
229
vendor/nette/utils/src/Utils/ObjectHelpers.php
vendored
Normal file
229
vendor/nette/utils/src/Utils/ObjectHelpers.php
vendored
Normal file
@@ -0,0 +1,229 @@
|
||||
<?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\Utils;
|
||||
|
||||
use Nette;
|
||||
use Nette\MemberAccessException;
|
||||
|
||||
|
||||
/**
|
||||
* Nette\SmartObject helpers.
|
||||
* @internal
|
||||
*/
|
||||
final class ObjectHelpers
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
/**
|
||||
* @return never
|
||||
* @throws MemberAccessException
|
||||
*/
|
||||
public static function strictGet(string $class, string $name): void
|
||||
{
|
||||
$rc = new \ReflectionClass($class);
|
||||
$hint = self::getSuggestion(array_merge(
|
||||
array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }),
|
||||
self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m')
|
||||
), $name);
|
||||
throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return never
|
||||
* @throws MemberAccessException
|
||||
*/
|
||||
public static function strictSet(string $class, string $name): void
|
||||
{
|
||||
$rc = new \ReflectionClass($class);
|
||||
$hint = self::getSuggestion(array_merge(
|
||||
array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }),
|
||||
self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m')
|
||||
), $name);
|
||||
throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return never
|
||||
* @throws MemberAccessException
|
||||
*/
|
||||
public static function strictCall(string $class, string $method, array $additionalMethods = []): void
|
||||
{
|
||||
$trace = debug_backtrace(0, 3); // suppose this method is called from __call()
|
||||
$context = ($trace[1]['function'] ?? null) === '__call'
|
||||
? ($trace[2]['class'] ?? null)
|
||||
: null;
|
||||
|
||||
if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method()
|
||||
$class = get_parent_class($context);
|
||||
}
|
||||
|
||||
if (method_exists($class, $method)) { // insufficient visibility
|
||||
$rm = new \ReflectionMethod($class, $method);
|
||||
$visibility = $rm->isPrivate()
|
||||
? 'private '
|
||||
: ($rm->isProtected() ? 'protected ' : '');
|
||||
throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.'));
|
||||
|
||||
} else {
|
||||
$hint = self::getSuggestion(array_merge(
|
||||
get_class_methods($class),
|
||||
self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:static[ \t]+)?(?:\S+[ \t]+)??(\w+)\(~m'),
|
||||
$additionalMethods
|
||||
), $method);
|
||||
throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return never
|
||||
* @throws MemberAccessException
|
||||
*/
|
||||
public static function strictStaticCall(string $class, string $method): void
|
||||
{
|
||||
$trace = debug_backtrace(0, 3); // suppose this method is called from __callStatic()
|
||||
$context = ($trace[1]['function'] ?? null) === '__callStatic'
|
||||
? ($trace[2]['class'] ?? null)
|
||||
: null;
|
||||
|
||||
if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method()
|
||||
$class = get_parent_class($context);
|
||||
}
|
||||
|
||||
if (method_exists($class, $method)) { // insufficient visibility
|
||||
$rm = new \ReflectionMethod($class, $method);
|
||||
$visibility = $rm->isPrivate()
|
||||
? 'private '
|
||||
: ($rm->isProtected() ? 'protected ' : '');
|
||||
throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.'));
|
||||
|
||||
} else {
|
||||
$hint = self::getSuggestion(
|
||||
array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), function ($m) { return $m->isStatic(); }),
|
||||
$method
|
||||
);
|
||||
throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns array of magic properties defined by annotation @property.
|
||||
* @return array of [name => bit mask]
|
||||
* @internal
|
||||
*/
|
||||
public static function getMagicProperties(string $class): array
|
||||
{
|
||||
static $cache;
|
||||
$props = &$cache[$class];
|
||||
if ($props !== null) {
|
||||
return $props;
|
||||
}
|
||||
|
||||
$rc = new \ReflectionClass($class);
|
||||
preg_match_all(
|
||||
'~^ [ \t*]* @property(|-read|-write|-deprecated) [ \t]+ [^\s$]+ [ \t]+ \$ (\w+) ()~mx',
|
||||
(string) $rc->getDocComment(),
|
||||
$matches,
|
||||
PREG_SET_ORDER
|
||||
);
|
||||
|
||||
$props = [];
|
||||
foreach ($matches as [, $type, $name]) {
|
||||
$uname = ucfirst($name);
|
||||
$write = $type !== '-read'
|
||||
&& $rc->hasMethod($nm = 'set' . $uname)
|
||||
&& ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic();
|
||||
$read = $type !== '-write'
|
||||
&& ($rc->hasMethod($nm = 'get' . $uname) || $rc->hasMethod($nm = 'is' . $uname))
|
||||
&& ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic();
|
||||
|
||||
if ($read || $write) {
|
||||
$props[$name] = $read << 0 | ($nm[0] === 'g') << 1 | $rm->returnsReference() << 2 | $write << 3 | ($type === '-deprecated') << 4;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($rc->getTraits() as $trait) {
|
||||
$props += self::getMagicProperties($trait->name);
|
||||
}
|
||||
|
||||
if ($parent = get_parent_class($class)) {
|
||||
$props += self::getMagicProperties($parent);
|
||||
}
|
||||
|
||||
return $props;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds the best suggestion (for 8-bit encoding).
|
||||
* @param (\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionClass|\ReflectionProperty|string)[] $possibilities
|
||||
* @internal
|
||||
*/
|
||||
public static function getSuggestion(array $possibilities, string $value): ?string
|
||||
{
|
||||
$norm = preg_replace($re = '#^(get|set|has|is|add)(?=[A-Z])#', '+', $value);
|
||||
$best = null;
|
||||
$min = (strlen($value) / 4 + 1) * 10 + .1;
|
||||
foreach (array_unique($possibilities, SORT_REGULAR) as $item) {
|
||||
$item = $item instanceof \Reflector ? $item->name : $item;
|
||||
if ($item !== $value && (
|
||||
($len = levenshtein($item, $value, 10, 11, 10)) < $min
|
||||
|| ($len = levenshtein(preg_replace($re, '*', $item), $norm, 10, 11, 10)) < $min
|
||||
)) {
|
||||
$min = $len;
|
||||
$best = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return $best;
|
||||
}
|
||||
|
||||
|
||||
private static function parseFullDoc(\ReflectionClass $rc, string $pattern): array
|
||||
{
|
||||
do {
|
||||
$doc[] = $rc->getDocComment();
|
||||
$traits = $rc->getTraits();
|
||||
while ($trait = array_pop($traits)) {
|
||||
$doc[] = $trait->getDocComment();
|
||||
$traits += $trait->getTraits();
|
||||
}
|
||||
} while ($rc = $rc->getParentClass());
|
||||
|
||||
return preg_match_all($pattern, implode($doc), $m) ? $m[1] : [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the public non-static property exists.
|
||||
* @return bool|string returns 'event' if the property exists and has event like name
|
||||
* @internal
|
||||
*/
|
||||
public static function hasProperty(string $class, string $name)
|
||||
{
|
||||
static $cache;
|
||||
$prop = &$cache[$class][$name];
|
||||
if ($prop === null) {
|
||||
$prop = false;
|
||||
try {
|
||||
$rp = new \ReflectionProperty($class, $name);
|
||||
if ($rp->isPublic() && !$rp->isStatic()) {
|
||||
$prop = $name >= 'onA' && $name < 'on_' ? 'event' : true;
|
||||
}
|
||||
} catch (\ReflectionException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
return $prop;
|
||||
}
|
||||
}
|
||||
41
vendor/nette/utils/src/Utils/ObjectMixin.php
vendored
Normal file
41
vendor/nette/utils/src/Utils/ObjectMixin.php
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<?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\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Nette\Object behaviour mixin.
|
||||
* @deprecated
|
||||
*/
|
||||
final class ObjectMixin
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
/** @deprecated use ObjectHelpers::getSuggestion() */
|
||||
public static function getSuggestion(array $possibilities, string $value): ?string
|
||||
{
|
||||
trigger_error(__METHOD__ . '() has been renamed to Nette\Utils\ObjectHelpers::getSuggestion()', E_USER_DEPRECATED);
|
||||
return ObjectHelpers::getSuggestion($possibilities, $value);
|
||||
}
|
||||
|
||||
|
||||
public static function setExtensionMethod(): void
|
||||
{
|
||||
trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
|
||||
public static function getExtensionMethod(): void
|
||||
{
|
||||
trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
|
||||
}
|
||||
}
|
||||
251
vendor/nette/utils/src/Utils/Paginator.php
vendored
Normal file
251
vendor/nette/utils/src/Utils/Paginator.php
vendored
Normal file
@@ -0,0 +1,251 @@
|
||||
<?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\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Paginating math.
|
||||
*
|
||||
* @property int $page
|
||||
* @property-read int $firstPage
|
||||
* @property-read int|null $lastPage
|
||||
* @property-read int<0,max> $firstItemOnPage
|
||||
* @property-read int<0,max> $lastItemOnPage
|
||||
* @property int $base
|
||||
* @property-read bool $first
|
||||
* @property-read bool $last
|
||||
* @property-read int<0,max>|null $pageCount
|
||||
* @property positive-int $itemsPerPage
|
||||
* @property int<0,max>|null $itemCount
|
||||
* @property-read int<0,max> $offset
|
||||
* @property-read int<0,max>|null $countdownOffset
|
||||
* @property-read int<0,max> $length
|
||||
*/
|
||||
class Paginator
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var int */
|
||||
private $base = 1;
|
||||
|
||||
/** @var int */
|
||||
private $itemsPerPage = 1;
|
||||
|
||||
/** @var int */
|
||||
private $page = 1;
|
||||
|
||||
/** @var int|null */
|
||||
private $itemCount;
|
||||
|
||||
|
||||
/**
|
||||
* Sets current page number.
|
||||
* @return static
|
||||
*/
|
||||
public function setPage(int $page)
|
||||
{
|
||||
$this->page = $page;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns current page number.
|
||||
*/
|
||||
public function getPage(): int
|
||||
{
|
||||
return $this->base + $this->getPageIndex();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns first page number.
|
||||
*/
|
||||
public function getFirstPage(): int
|
||||
{
|
||||
return $this->base;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns last page number.
|
||||
*/
|
||||
public function getLastPage(): ?int
|
||||
{
|
||||
return $this->itemCount === null
|
||||
? null
|
||||
: $this->base + max(0, $this->getPageCount() - 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the sequence number of the first element on the page
|
||||
* @return int<0, max>
|
||||
*/
|
||||
public function getFirstItemOnPage(): int
|
||||
{
|
||||
return $this->itemCount !== 0
|
||||
? $this->offset + 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the sequence number of the last element on the page
|
||||
* @return int<0, max>
|
||||
*/
|
||||
public function getLastItemOnPage(): int
|
||||
{
|
||||
return $this->offset + $this->length;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets first page (base) number.
|
||||
* @return static
|
||||
*/
|
||||
public function setBase(int $base)
|
||||
{
|
||||
$this->base = $base;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns first page (base) number.
|
||||
*/
|
||||
public function getBase(): int
|
||||
{
|
||||
return $this->base;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns zero-based page number.
|
||||
* @return int<0, max>
|
||||
*/
|
||||
protected function getPageIndex(): int
|
||||
{
|
||||
$index = max(0, $this->page - $this->base);
|
||||
return $this->itemCount === null
|
||||
? $index
|
||||
: min($index, max(0, $this->getPageCount() - 1));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the current page the first one?
|
||||
*/
|
||||
public function isFirst(): bool
|
||||
{
|
||||
return $this->getPageIndex() === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the current page the last one?
|
||||
*/
|
||||
public function isLast(): bool
|
||||
{
|
||||
return $this->itemCount === null
|
||||
? false
|
||||
: $this->getPageIndex() >= $this->getPageCount() - 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the total number of pages.
|
||||
* @return int<0, max>|null
|
||||
*/
|
||||
public function getPageCount(): ?int
|
||||
{
|
||||
return $this->itemCount === null
|
||||
? null
|
||||
: (int) ceil($this->itemCount / $this->itemsPerPage);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the number of items to display on a single page.
|
||||
* @return static
|
||||
*/
|
||||
public function setItemsPerPage(int $itemsPerPage)
|
||||
{
|
||||
$this->itemsPerPage = max(1, $itemsPerPage);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of items to display on a single page.
|
||||
* @return positive-int
|
||||
*/
|
||||
public function getItemsPerPage(): int
|
||||
{
|
||||
return $this->itemsPerPage;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the total number of items.
|
||||
* @return static
|
||||
*/
|
||||
public function setItemCount(?int $itemCount = null)
|
||||
{
|
||||
$this->itemCount = $itemCount === null ? null : max(0, $itemCount);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the total number of items.
|
||||
* @return int<0, max>|null
|
||||
*/
|
||||
public function getItemCount(): ?int
|
||||
{
|
||||
return $this->itemCount;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the absolute index of the first item on current page.
|
||||
* @return int<0, max>
|
||||
*/
|
||||
public function getOffset(): int
|
||||
{
|
||||
return $this->getPageIndex() * $this->itemsPerPage;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the absolute index of the first item on current page in countdown paging.
|
||||
* @return int<0, max>|null
|
||||
*/
|
||||
public function getCountdownOffset(): ?int
|
||||
{
|
||||
return $this->itemCount === null
|
||||
? null
|
||||
: max(0, $this->itemCount - ($this->getPageIndex() + 1) * $this->itemsPerPage);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of items on current page.
|
||||
* @return int<0, max>
|
||||
*/
|
||||
public function getLength(): int
|
||||
{
|
||||
return $this->itemCount === null
|
||||
? $this->itemsPerPage
|
||||
: min($this->itemsPerPage, $this->itemCount - $this->getPageIndex() * $this->itemsPerPage);
|
||||
}
|
||||
}
|
||||
46
vendor/nette/utils/src/Utils/Random.php
vendored
Normal file
46
vendor/nette/utils/src/Utils/Random.php
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
<?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\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Secure random string generator.
|
||||
*/
|
||||
final class Random
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
/**
|
||||
* Generates a random string of given length from characters specified in second argument.
|
||||
* Supports intervals, such as `0-9` or `A-Z`.
|
||||
*/
|
||||
public static function generate(int $length = 10, string $charlist = '0-9a-z'): string
|
||||
{
|
||||
$charlist = count_chars(preg_replace_callback('#.-.#', function (array $m): string {
|
||||
return implode('', range($m[0][0], $m[0][2]));
|
||||
}, $charlist), 3);
|
||||
$chLen = strlen($charlist);
|
||||
|
||||
if ($length < 1) {
|
||||
throw new Nette\InvalidArgumentException('Length must be greater than zero.');
|
||||
} elseif ($chLen < 2) {
|
||||
throw new Nette\InvalidArgumentException('Character list must contain at least two chars.');
|
||||
}
|
||||
|
||||
$res = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$res .= $charlist[random_int(0, $chLen - 1)];
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
418
vendor/nette/utils/src/Utils/Reflection.php
vendored
Normal file
418
vendor/nette/utils/src/Utils/Reflection.php
vendored
Normal file
@@ -0,0 +1,418 @@
|
||||
<?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\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* PHP reflection helpers.
|
||||
*/
|
||||
final class Reflection
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
/**
|
||||
* Determines if type is PHP built-in type. Otherwise, it is the class name.
|
||||
*/
|
||||
public static function isBuiltinType(string $type): bool
|
||||
{
|
||||
return Validators::isBuiltinType($type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines if type is special class name self/parent/static.
|
||||
*/
|
||||
public static function isClassKeyword(string $name): bool
|
||||
{
|
||||
return Validators::isClassKeyword($name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the type of return value of given function or method and normalizes `self`, `static`, and `parent` to actual class names.
|
||||
* If the function does not have a return type, it returns null.
|
||||
* If the function has union or intersection type, it throws Nette\InvalidStateException.
|
||||
* @deprecated use Nette\Utils\Type::fromReflection()
|
||||
*/
|
||||
public static function getReturnType(\ReflectionFunctionAbstract $func): ?string
|
||||
{
|
||||
$type = $func->getReturnType() ?? (PHP_VERSION_ID >= 80100 && $func instanceof \ReflectionMethod ? $func->getTentativeReturnType() : null);
|
||||
return self::getType($func, $type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public static function getReturnTypes(\ReflectionFunctionAbstract $func): array
|
||||
{
|
||||
$type = Type::fromReflection($func);
|
||||
return $type ? $type->getNames() : [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the type of given parameter and normalizes `self` and `parent` to the actual class names.
|
||||
* If the parameter does not have a type, it returns null.
|
||||
* If the parameter has union or intersection type, it throws Nette\InvalidStateException.
|
||||
* @deprecated use Nette\Utils\Type::fromReflection()
|
||||
*/
|
||||
public static function getParameterType(\ReflectionParameter $param): ?string
|
||||
{
|
||||
return self::getType($param, $param->getType());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public static function getParameterTypes(\ReflectionParameter $param): array
|
||||
{
|
||||
$type = Type::fromReflection($param);
|
||||
return $type ? $type->getNames() : [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the type of given property and normalizes `self` and `parent` to the actual class names.
|
||||
* If the property does not have a type, it returns null.
|
||||
* If the property has union or intersection type, it throws Nette\InvalidStateException.
|
||||
* @deprecated use Nette\Utils\Type::fromReflection()
|
||||
*/
|
||||
public static function getPropertyType(\ReflectionProperty $prop): ?string
|
||||
{
|
||||
return self::getType($prop, PHP_VERSION_ID >= 70400 ? $prop->getType() : null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public static function getPropertyTypes(\ReflectionProperty $prop): array
|
||||
{
|
||||
$type = Type::fromReflection($prop);
|
||||
return $type ? $type->getNames() : [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \ReflectionFunction|\ReflectionMethod|\ReflectionParameter|\ReflectionProperty $reflection
|
||||
*/
|
||||
private static function getType($reflection, ?\ReflectionType $type): ?string
|
||||
{
|
||||
if ($type === null) {
|
||||
return null;
|
||||
|
||||
} elseif ($type instanceof \ReflectionNamedType) {
|
||||
return Type::resolve($type->getName(), $reflection);
|
||||
|
||||
} elseif ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) {
|
||||
throw new Nette\InvalidStateException('The ' . self::toString($reflection) . ' is not expected to have a union or intersection type.');
|
||||
|
||||
} else {
|
||||
throw new Nette\InvalidStateException('Unexpected type of ' . self::toString($reflection));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the default value of parameter. If it is a constant, it returns its value.
|
||||
* @return mixed
|
||||
* @throws \ReflectionException If the parameter does not have a default value or the constant cannot be resolved
|
||||
*/
|
||||
public static function getParameterDefaultValue(\ReflectionParameter $param)
|
||||
{
|
||||
if ($param->isDefaultValueConstant()) {
|
||||
$const = $orig = $param->getDefaultValueConstantName();
|
||||
$pair = explode('::', $const);
|
||||
if (isset($pair[1])) {
|
||||
$pair[0] = Type::resolve($pair[0], $param);
|
||||
try {
|
||||
$rcc = new \ReflectionClassConstant($pair[0], $pair[1]);
|
||||
} catch (\ReflectionException $e) {
|
||||
$name = self::toString($param);
|
||||
throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name.", 0, $e);
|
||||
}
|
||||
|
||||
return $rcc->getValue();
|
||||
|
||||
} elseif (!defined($const)) {
|
||||
$const = substr((string) strrchr($const, '\\'), 1);
|
||||
if (!defined($const)) {
|
||||
$name = self::toString($param);
|
||||
throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name.");
|
||||
}
|
||||
}
|
||||
|
||||
return constant($const);
|
||||
}
|
||||
|
||||
return $param->getDefaultValue();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a reflection of a class or trait that contains a declaration of given property. Property can also be declared in the trait.
|
||||
*/
|
||||
public static function getPropertyDeclaringClass(\ReflectionProperty $prop): \ReflectionClass
|
||||
{
|
||||
foreach ($prop->getDeclaringClass()->getTraits() as $trait) {
|
||||
if ($trait->hasProperty($prop->name)
|
||||
// doc-comment guessing as workaround for insufficient PHP reflection
|
||||
&& $trait->getProperty($prop->name)->getDocComment() === $prop->getDocComment()
|
||||
) {
|
||||
return self::getPropertyDeclaringClass($trait->getProperty($prop->name));
|
||||
}
|
||||
}
|
||||
|
||||
return $prop->getDeclaringClass();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a reflection of a method that contains a declaration of $method.
|
||||
* Usually, each method is its own declaration, but the body of the method can also be in the trait and under a different name.
|
||||
*/
|
||||
public static function getMethodDeclaringMethod(\ReflectionMethod $method): \ReflectionMethod
|
||||
{
|
||||
// file & line guessing as workaround for insufficient PHP reflection
|
||||
$decl = $method->getDeclaringClass();
|
||||
if ($decl->getFileName() === $method->getFileName()
|
||||
&& $decl->getStartLine() <= $method->getStartLine()
|
||||
&& $decl->getEndLine() >= $method->getEndLine()
|
||||
) {
|
||||
return $method;
|
||||
}
|
||||
|
||||
$hash = [$method->getFileName(), $method->getStartLine(), $method->getEndLine()];
|
||||
if (($alias = $decl->getTraitAliases()[$method->name] ?? null)
|
||||
&& ($m = new \ReflectionMethod($alias))
|
||||
&& $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()]
|
||||
) {
|
||||
return self::getMethodDeclaringMethod($m);
|
||||
}
|
||||
|
||||
foreach ($decl->getTraits() as $trait) {
|
||||
if ($trait->hasMethod($method->name)
|
||||
&& ($m = $trait->getMethod($method->name))
|
||||
&& $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()]
|
||||
) {
|
||||
return self::getMethodDeclaringMethod($m);
|
||||
}
|
||||
}
|
||||
|
||||
return $method;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds out if reflection has access to PHPdoc comments. Comments may not be available due to the opcode cache.
|
||||
*/
|
||||
public static function areCommentsAvailable(): bool
|
||||
{
|
||||
static $res;
|
||||
return $res ?? $res = (bool) (new \ReflectionMethod(__METHOD__))->getDocComment();
|
||||
}
|
||||
|
||||
|
||||
public static function toString(\Reflector $ref): string
|
||||
{
|
||||
if ($ref instanceof \ReflectionClass) {
|
||||
return $ref->name;
|
||||
} elseif ($ref instanceof \ReflectionMethod) {
|
||||
return $ref->getDeclaringClass()->name . '::' . $ref->name . '()';
|
||||
} elseif ($ref instanceof \ReflectionFunction) {
|
||||
return $ref->name . '()';
|
||||
} elseif ($ref instanceof \ReflectionProperty) {
|
||||
return self::getPropertyDeclaringClass($ref)->name . '::$' . $ref->name;
|
||||
} elseif ($ref instanceof \ReflectionParameter) {
|
||||
return '$' . $ref->name . ' in ' . self::toString($ref->getDeclaringFunction());
|
||||
} else {
|
||||
throw new Nette\InvalidArgumentException;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Expands the name of the class to full name in the given context of given class.
|
||||
* Thus, it returns how the PHP parser would understand $name if it were written in the body of the class $context.
|
||||
* @throws Nette\InvalidArgumentException
|
||||
*/
|
||||
public static function expandClassName(string $name, \ReflectionClass $context): string
|
||||
{
|
||||
$lower = strtolower($name);
|
||||
if (empty($name)) {
|
||||
throw new Nette\InvalidArgumentException('Class name must not be empty.');
|
||||
|
||||
} elseif (Validators::isBuiltinType($lower)) {
|
||||
return $lower;
|
||||
|
||||
} elseif ($lower === 'self' || $lower === 'static') {
|
||||
return $context->name;
|
||||
|
||||
} elseif ($lower === 'parent') {
|
||||
return $context->getParentClass()
|
||||
? $context->getParentClass()->name
|
||||
: 'parent';
|
||||
|
||||
} elseif ($name[0] === '\\') { // fully qualified name
|
||||
return ltrim($name, '\\');
|
||||
}
|
||||
|
||||
$uses = self::getUseStatements($context);
|
||||
$parts = explode('\\', $name, 2);
|
||||
if (isset($uses[$parts[0]])) {
|
||||
$parts[0] = $uses[$parts[0]];
|
||||
return implode('\\', $parts);
|
||||
|
||||
} elseif ($context->inNamespace()) {
|
||||
return $context->getNamespaceName() . '\\' . $name;
|
||||
|
||||
} else {
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @return array<string, class-string> of [alias => class] */
|
||||
public static function getUseStatements(\ReflectionClass $class): array
|
||||
{
|
||||
if ($class->isAnonymous()) {
|
||||
throw new Nette\NotImplementedException('Anonymous classes are not supported.');
|
||||
}
|
||||
|
||||
static $cache = [];
|
||||
if (!isset($cache[$name = $class->name])) {
|
||||
if ($class->isInternal()) {
|
||||
$cache[$name] = [];
|
||||
} else {
|
||||
$code = file_get_contents($class->getFileName());
|
||||
$cache = self::parseUseStatements($code, $name) + $cache;
|
||||
}
|
||||
}
|
||||
|
||||
return $cache[$name];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses PHP code to [class => [alias => class, ...]]
|
||||
*/
|
||||
private static function parseUseStatements(string $code, ?string $forClass = null): array
|
||||
{
|
||||
try {
|
||||
$tokens = token_get_all($code, TOKEN_PARSE);
|
||||
} catch (\ParseError $e) {
|
||||
trigger_error($e->getMessage(), E_USER_NOTICE);
|
||||
$tokens = [];
|
||||
}
|
||||
|
||||
$namespace = $class = null;
|
||||
$classLevel = $level = 0;
|
||||
$res = $uses = [];
|
||||
|
||||
$nameTokens = PHP_VERSION_ID < 80000
|
||||
? [T_STRING, T_NS_SEPARATOR]
|
||||
: [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED];
|
||||
|
||||
while ($token = current($tokens)) {
|
||||
next($tokens);
|
||||
switch (is_array($token) ? $token[0] : $token) {
|
||||
case T_NAMESPACE:
|
||||
$namespace = ltrim(self::fetch($tokens, $nameTokens) . '\\', '\\');
|
||||
$uses = [];
|
||||
break;
|
||||
|
||||
case T_CLASS:
|
||||
case T_INTERFACE:
|
||||
case T_TRAIT:
|
||||
case PHP_VERSION_ID < 80100
|
||||
? T_CLASS
|
||||
: T_ENUM:
|
||||
if ($name = self::fetch($tokens, T_STRING)) {
|
||||
$class = $namespace . $name;
|
||||
$classLevel = $level + 1;
|
||||
$res[$class] = $uses;
|
||||
if ($class === $forClass) {
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case T_USE:
|
||||
while (!$class && ($name = self::fetch($tokens, $nameTokens))) {
|
||||
$name = ltrim($name, '\\');
|
||||
if (self::fetch($tokens, '{')) {
|
||||
while ($suffix = self::fetch($tokens, $nameTokens)) {
|
||||
if (self::fetch($tokens, T_AS)) {
|
||||
$uses[self::fetch($tokens, T_STRING)] = $name . $suffix;
|
||||
} else {
|
||||
$tmp = explode('\\', $suffix);
|
||||
$uses[end($tmp)] = $name . $suffix;
|
||||
}
|
||||
|
||||
if (!self::fetch($tokens, ',')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} elseif (self::fetch($tokens, T_AS)) {
|
||||
$uses[self::fetch($tokens, T_STRING)] = $name;
|
||||
|
||||
} else {
|
||||
$tmp = explode('\\', $name);
|
||||
$uses[end($tmp)] = $name;
|
||||
}
|
||||
|
||||
if (!self::fetch($tokens, ',')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case T_CURLY_OPEN:
|
||||
case T_DOLLAR_OPEN_CURLY_BRACES:
|
||||
case '{':
|
||||
$level++;
|
||||
break;
|
||||
|
||||
case '}':
|
||||
if ($level === $classLevel) {
|
||||
$class = $classLevel = 0;
|
||||
}
|
||||
|
||||
$level--;
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
private static function fetch(array &$tokens, $take): ?string
|
||||
{
|
||||
$res = null;
|
||||
while ($token = current($tokens)) {
|
||||
[$token, $s] = is_array($token) ? $token : [$token, $token];
|
||||
if (in_array($token, (array) $take, true)) {
|
||||
$res .= $s;
|
||||
} elseif (!in_array($token, [T_DOC_COMMENT, T_WHITESPACE, T_COMMENT], true)) {
|
||||
break;
|
||||
}
|
||||
|
||||
next($tokens);
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
595
vendor/nette/utils/src/Utils/Strings.php
vendored
Normal file
595
vendor/nette/utils/src/Utils/Strings.php
vendored
Normal file
@@ -0,0 +1,595 @@
|
||||
<?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\Utils;
|
||||
|
||||
use JetBrains\PhpStorm\Language;
|
||||
use Nette;
|
||||
use function is_array, is_object, strlen;
|
||||
|
||||
|
||||
/**
|
||||
* String tools library.
|
||||
*/
|
||||
class Strings
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
public const TRIM_CHARACTERS = " \t\n\r\0\x0B\u{A0}";
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the string is valid in UTF-8 encoding.
|
||||
*/
|
||||
public static function checkEncoding(string $s): bool
|
||||
{
|
||||
return $s === self::fixEncoding($s);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all invalid UTF-8 characters from a string.
|
||||
*/
|
||||
public static function fixEncoding(string $s): string
|
||||
{
|
||||
// removes xD800-xDFFF, x110000 and higher
|
||||
return htmlspecialchars_decode(htmlspecialchars($s, ENT_NOQUOTES | ENT_IGNORE, 'UTF-8'), ENT_NOQUOTES);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a specific character in UTF-8 from code point (number in range 0x0000..D7FF or 0xE000..10FFFF).
|
||||
* @throws Nette\InvalidArgumentException if code point is not in valid range
|
||||
*/
|
||||
public static function chr(int $code): string
|
||||
{
|
||||
if ($code < 0 || ($code >= 0xD800 && $code <= 0xDFFF) || $code > 0x10FFFF) {
|
||||
throw new Nette\InvalidArgumentException('Code point must be in range 0x0 to 0xD7FF or 0xE000 to 0x10FFFF.');
|
||||
} elseif (!extension_loaded('iconv')) {
|
||||
throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.');
|
||||
}
|
||||
|
||||
return iconv('UTF-32BE', 'UTF-8//IGNORE', pack('N', $code));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts the $haystack string with the prefix $needle?
|
||||
*/
|
||||
public static function startsWith(string $haystack, string $needle): bool
|
||||
{
|
||||
return strncmp($haystack, $needle, strlen($needle)) === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ends the $haystack string with the suffix $needle?
|
||||
*/
|
||||
public static function endsWith(string $haystack, string $needle): bool
|
||||
{
|
||||
return $needle === '' || substr($haystack, -strlen($needle)) === $needle;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Does $haystack contain $needle?
|
||||
*/
|
||||
public static function contains(string $haystack, string $needle): bool
|
||||
{
|
||||
return strpos($haystack, $needle) !== false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a part of UTF-8 string specified by starting position and length. If start is negative,
|
||||
* the returned string will start at the start'th character from the end of string.
|
||||
*/
|
||||
public static function substring(string $s, int $start, ?int $length = null): string
|
||||
{
|
||||
if (function_exists('mb_substr')) {
|
||||
return mb_substr($s, $start, $length, 'UTF-8'); // MB is much faster
|
||||
} elseif (!extension_loaded('iconv')) {
|
||||
throw new Nette\NotSupportedException(__METHOD__ . '() requires extension ICONV or MBSTRING, neither is loaded.');
|
||||
} elseif ($length === null) {
|
||||
$length = self::length($s);
|
||||
} elseif ($start < 0 && $length < 0) {
|
||||
$start += self::length($s); // unifies iconv_substr behavior with mb_substr
|
||||
}
|
||||
|
||||
return iconv_substr($s, $start, $length, 'UTF-8');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes control characters, normalizes line breaks to `\n`, removes leading and trailing blank lines,
|
||||
* trims end spaces on lines, normalizes UTF-8 to the normal form of NFC.
|
||||
*/
|
||||
public static function normalize(string $s): string
|
||||
{
|
||||
// convert to compressed normal form (NFC)
|
||||
if (class_exists('Normalizer', false) && ($n = \Normalizer::normalize($s, \Normalizer::FORM_C)) !== false) {
|
||||
$s = $n;
|
||||
}
|
||||
|
||||
$s = self::normalizeNewLines($s);
|
||||
|
||||
// remove control characters; leave \t + \n
|
||||
$s = self::pcre('preg_replace', ['#[\x00-\x08\x0B-\x1F\x7F-\x9F]+#u', '', $s]);
|
||||
|
||||
// right trim
|
||||
$s = self::pcre('preg_replace', ['#[\t ]+$#m', '', $s]);
|
||||
|
||||
// leading and trailing blank lines
|
||||
$s = trim($s, "\n");
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Standardize line endings to unix-like.
|
||||
*/
|
||||
public static function normalizeNewLines(string $s): string
|
||||
{
|
||||
return str_replace(["\r\n", "\r"], "\n", $s);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts UTF-8 string to ASCII, ie removes diacritics etc.
|
||||
*/
|
||||
public static function toAscii(string $s): string
|
||||
{
|
||||
$iconv = defined('ICONV_IMPL') ? trim(ICONV_IMPL, '"\'') : null;
|
||||
static $transliterator = null;
|
||||
if ($transliterator === null) {
|
||||
if (class_exists('Transliterator', false)) {
|
||||
$transliterator = \Transliterator::create('Any-Latin; Latin-ASCII');
|
||||
} else {
|
||||
trigger_error(__METHOD__ . "(): it is recommended to enable PHP extensions 'intl'.", E_USER_NOTICE);
|
||||
$transliterator = false;
|
||||
}
|
||||
}
|
||||
|
||||
// remove control characters and check UTF-8 validity
|
||||
$s = self::pcre('preg_replace', ['#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{2FF}\x{370}-\x{10FFFF}]#u', '', $s]);
|
||||
|
||||
// transliteration (by Transliterator and iconv) is not optimal, replace some characters directly
|
||||
$s = strtr($s, ["\u{201E}" => '"', "\u{201C}" => '"', "\u{201D}" => '"', "\u{201A}" => "'", "\u{2018}" => "'", "\u{2019}" => "'", "\u{B0}" => '^', "\u{42F}" => 'Ya', "\u{44F}" => 'ya', "\u{42E}" => 'Yu', "\u{44E}" => 'yu', "\u{c4}" => 'Ae', "\u{d6}" => 'Oe', "\u{dc}" => 'Ue', "\u{1e9e}" => 'Ss', "\u{e4}" => 'ae', "\u{f6}" => 'oe', "\u{fc}" => 'ue', "\u{df}" => 'ss']); // „ “ ” ‚ ‘ ’ ° Я я Ю ю Ä Ö Ü ẞ ä ö ü ß
|
||||
if ($iconv !== 'libiconv') {
|
||||
$s = strtr($s, ["\u{AE}" => '(R)', "\u{A9}" => '(c)', "\u{2026}" => '...', "\u{AB}" => '<<', "\u{BB}" => '>>', "\u{A3}" => 'lb', "\u{A5}" => 'yen', "\u{B2}" => '^2', "\u{B3}" => '^3', "\u{B5}" => 'u', "\u{B9}" => '^1', "\u{BA}" => 'o', "\u{BF}" => '?', "\u{2CA}" => "'", "\u{2CD}" => '_', "\u{2DD}" => '"', "\u{1FEF}" => '', "\u{20AC}" => 'EUR', "\u{2122}" => 'TM', "\u{212E}" => 'e', "\u{2190}" => '<-', "\u{2191}" => '^', "\u{2192}" => '->', "\u{2193}" => 'V', "\u{2194}" => '<->']); // ® © … « » £ ¥ ² ³ µ ¹ º ¿ ˊ ˍ ˝ ` € ™ ℮ ← ↑ → ↓ ↔
|
||||
}
|
||||
|
||||
if ($transliterator) {
|
||||
$s = $transliterator->transliterate($s);
|
||||
// use iconv because The transliterator leaves some characters out of ASCII, eg → ʾ
|
||||
if ($iconv === 'glibc') {
|
||||
$s = strtr($s, '?', "\x01"); // temporarily hide ? to distinguish them from the garbage that iconv creates
|
||||
$s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
|
||||
$s = str_replace(['?', "\x01"], ['', '?'], $s); // remove garbage and restore ? characters
|
||||
} elseif ($iconv === 'libiconv') {
|
||||
$s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
|
||||
} else { // null or 'unknown' (#216)
|
||||
$s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); // remove non-ascii chars
|
||||
}
|
||||
} elseif ($iconv === 'glibc' || $iconv === 'libiconv') {
|
||||
// temporarily hide these characters to distinguish them from the garbage that iconv creates
|
||||
$s = strtr($s, '`\'"^~?', "\x01\x02\x03\x04\x05\x06");
|
||||
if ($iconv === 'glibc') {
|
||||
// glibc implementation is very limited. transliterate into Windows-1250 and then into ASCII, so most Eastern European characters are preserved
|
||||
$s = iconv('UTF-8', 'WINDOWS-1250//TRANSLIT//IGNORE', $s);
|
||||
$s = strtr(
|
||||
$s,
|
||||
"\xa5\xa3\xbc\x8c\xa7\x8a\xaa\x8d\x8f\x8e\xaf\xb9\xb3\xbe\x9c\x9a\xba\x9d\x9f\x9e\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe\x96\xa0\x8b\x97\x9b\xa6\xad\xb7",
|
||||
'ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt- <->|-.'
|
||||
);
|
||||
$s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]);
|
||||
} else {
|
||||
$s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
|
||||
}
|
||||
|
||||
// remove garbage that iconv creates during transliteration (eg Ý -> Y')
|
||||
$s = str_replace(['`', "'", '"', '^', '~', '?'], '', $s);
|
||||
// restore temporarily hidden characters
|
||||
$s = strtr($s, "\x01\x02\x03\x04\x05\x06", '`\'"^~?');
|
||||
} else {
|
||||
$s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); // remove non-ascii chars
|
||||
}
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Modifies the UTF-8 string to the form used in the URL, ie removes diacritics and replaces all characters
|
||||
* except letters of the English alphabet and numbers with a hyphens.
|
||||
*/
|
||||
public static function webalize(string $s, ?string $charlist = null, bool $lower = true): string
|
||||
{
|
||||
$s = self::toAscii($s);
|
||||
if ($lower) {
|
||||
$s = strtolower($s);
|
||||
}
|
||||
|
||||
$s = self::pcre('preg_replace', ['#[^a-z0-9' . ($charlist !== null ? preg_quote($charlist, '#') : '') . ']+#i', '-', $s]);
|
||||
$s = trim($s, '-');
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Truncates a UTF-8 string to given maximal length, while trying not to split whole words. Only if the string is truncated,
|
||||
* an ellipsis (or something else set with third argument) is appended to the string.
|
||||
*/
|
||||
public static function truncate(string $s, int $maxLen, string $append = "\u{2026}"): string
|
||||
{
|
||||
if (self::length($s) > $maxLen) {
|
||||
$maxLen -= self::length($append);
|
||||
if ($maxLen < 1) {
|
||||
return $append;
|
||||
|
||||
} elseif ($matches = self::match($s, '#^.{1,' . $maxLen . '}(?=[\s\x00-/:-@\[-`{-~])#us')) {
|
||||
return $matches[0] . $append;
|
||||
|
||||
} else {
|
||||
return self::substring($s, 0, $maxLen) . $append;
|
||||
}
|
||||
}
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Indents a multiline text from the left. Second argument sets how many indentation chars should be used,
|
||||
* while the indent itself is the third argument (*tab* by default).
|
||||
*/
|
||||
public static function indent(string $s, int $level = 1, string $chars = "\t"): string
|
||||
{
|
||||
if ($level > 0) {
|
||||
$s = self::replace($s, '#(?:^|[\r\n]+)(?=[^\r\n])#', '$0' . str_repeat($chars, $level));
|
||||
}
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts all characters of UTF-8 string to lower case.
|
||||
*/
|
||||
public static function lower(string $s): string
|
||||
{
|
||||
return mb_strtolower($s, 'UTF-8');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts the first character of a UTF-8 string to lower case and leaves the other characters unchanged.
|
||||
*/
|
||||
public static function firstLower(string $s): string
|
||||
{
|
||||
return self::lower(self::substring($s, 0, 1)) . self::substring($s, 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts all characters of a UTF-8 string to upper case.
|
||||
*/
|
||||
public static function upper(string $s): string
|
||||
{
|
||||
return mb_strtoupper($s, 'UTF-8');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts the first character of a UTF-8 string to upper case and leaves the other characters unchanged.
|
||||
*/
|
||||
public static function firstUpper(string $s): string
|
||||
{
|
||||
return self::upper(self::substring($s, 0, 1)) . self::substring($s, 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts the first character of every word of a UTF-8 string to upper case and the others to lower case.
|
||||
*/
|
||||
public static function capitalize(string $s): string
|
||||
{
|
||||
return mb_convert_case($s, MB_CASE_TITLE, 'UTF-8');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compares two UTF-8 strings or their parts, without taking character case into account. If length is null, whole strings are compared,
|
||||
* if it is negative, the corresponding number of characters from the end of the strings is compared,
|
||||
* otherwise the appropriate number of characters from the beginning is compared.
|
||||
*/
|
||||
public static function compare(string $left, string $right, ?int $length = null): bool
|
||||
{
|
||||
if (class_exists('Normalizer', false)) {
|
||||
$left = \Normalizer::normalize($left, \Normalizer::FORM_D); // form NFD is faster
|
||||
$right = \Normalizer::normalize($right, \Normalizer::FORM_D); // form NFD is faster
|
||||
}
|
||||
|
||||
if ($length < 0) {
|
||||
$left = self::substring($left, $length, -$length);
|
||||
$right = self::substring($right, $length, -$length);
|
||||
} elseif ($length !== null) {
|
||||
$left = self::substring($left, 0, $length);
|
||||
$right = self::substring($right, 0, $length);
|
||||
}
|
||||
|
||||
return self::lower($left) === self::lower($right);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds the common prefix of strings or returns empty string if the prefix was not found.
|
||||
* @param string[] $strings
|
||||
*/
|
||||
public static function findPrefix(array $strings): string
|
||||
{
|
||||
$first = array_shift($strings);
|
||||
for ($i = 0; $i < strlen($first); $i++) {
|
||||
foreach ($strings as $s) {
|
||||
if (!isset($s[$i]) || $first[$i] !== $s[$i]) {
|
||||
while ($i && $first[$i - 1] >= "\x80" && $first[$i] >= "\x80" && $first[$i] < "\xC0") {
|
||||
$i--;
|
||||
}
|
||||
|
||||
return substr($first, 0, $i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $first;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns number of characters (not bytes) in UTF-8 string.
|
||||
* That is the number of Unicode code points which may differ from the number of graphemes.
|
||||
*/
|
||||
public static function length(string $s): int
|
||||
{
|
||||
return function_exists('mb_strlen')
|
||||
? mb_strlen($s, 'UTF-8')
|
||||
: strlen(utf8_decode($s));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes all left and right side spaces (or the characters passed as second argument) from a UTF-8 encoded string.
|
||||
*/
|
||||
public static function trim(string $s, string $charlist = self::TRIM_CHARACTERS): string
|
||||
{
|
||||
$charlist = preg_quote($charlist, '#');
|
||||
return self::replace($s, '#^[' . $charlist . ']+|[' . $charlist . ']+$#Du', '');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pads a UTF-8 string to given length by prepending the $pad string to the beginning.
|
||||
* @param non-empty-string $pad
|
||||
*/
|
||||
public static function padLeft(string $s, int $length, string $pad = ' '): string
|
||||
{
|
||||
$length = max(0, $length - self::length($s));
|
||||
$padLen = self::length($pad);
|
||||
return str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen) . $s;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pads UTF-8 string to given length by appending the $pad string to the end.
|
||||
* @param non-empty-string $pad
|
||||
*/
|
||||
public static function padRight(string $s, int $length, string $pad = ' '): string
|
||||
{
|
||||
$length = max(0, $length - self::length($s));
|
||||
$padLen = self::length($pad);
|
||||
return $s . str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reverses UTF-8 string.
|
||||
*/
|
||||
public static function reverse(string $s): string
|
||||
{
|
||||
if (!extension_loaded('iconv')) {
|
||||
throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.');
|
||||
}
|
||||
|
||||
return iconv('UTF-32LE', 'UTF-8', strrev(iconv('UTF-8', 'UTF-32BE', $s)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns part of $haystack before $nth occurence of $needle or returns null if the needle was not found.
|
||||
* Negative value means searching from the end.
|
||||
*/
|
||||
public static function before(string $haystack, string $needle, int $nth = 1): ?string
|
||||
{
|
||||
$pos = self::pos($haystack, $needle, $nth);
|
||||
return $pos === null
|
||||
? null
|
||||
: substr($haystack, 0, $pos);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns part of $haystack after $nth occurence of $needle or returns null if the needle was not found.
|
||||
* Negative value means searching from the end.
|
||||
*/
|
||||
public static function after(string $haystack, string $needle, int $nth = 1): ?string
|
||||
{
|
||||
$pos = self::pos($haystack, $needle, $nth);
|
||||
return $pos === null
|
||||
? null
|
||||
: substr($haystack, $pos + strlen($needle));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns position in characters of $nth occurence of $needle in $haystack or null if the $needle was not found.
|
||||
* Negative value of `$nth` means searching from the end.
|
||||
*/
|
||||
public static function indexOf(string $haystack, string $needle, int $nth = 1): ?int
|
||||
{
|
||||
$pos = self::pos($haystack, $needle, $nth);
|
||||
return $pos === null
|
||||
? null
|
||||
: self::length(substr($haystack, 0, $pos));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns position in characters of $nth occurence of $needle in $haystack or null if the needle was not found.
|
||||
*/
|
||||
private static function pos(string $haystack, string $needle, int $nth = 1): ?int
|
||||
{
|
||||
if (!$nth) {
|
||||
return null;
|
||||
} elseif ($nth > 0) {
|
||||
if ($needle === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$pos = 0;
|
||||
while (($pos = strpos($haystack, $needle, $pos)) !== false && --$nth) {
|
||||
$pos++;
|
||||
}
|
||||
} else {
|
||||
$len = strlen($haystack);
|
||||
if ($needle === '') {
|
||||
return $len;
|
||||
} elseif ($len === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$pos = $len - 1;
|
||||
while (($pos = strrpos($haystack, $needle, $pos - $len)) !== false && ++$nth) {
|
||||
$pos--;
|
||||
}
|
||||
}
|
||||
|
||||
return Helpers::falseToNull($pos);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Splits a string into array by the regular expression. Parenthesized expression in the delimiter are captured.
|
||||
* Parameter $flags can be any combination of PREG_SPLIT_NO_EMPTY and PREG_OFFSET_CAPTURE flags.
|
||||
*/
|
||||
public static function split(
|
||||
string $subject,
|
||||
#[Language('RegExp')]
|
||||
string $pattern,
|
||||
int $flags = 0
|
||||
): array
|
||||
{
|
||||
return self::pcre('preg_split', [$pattern, $subject, -1, $flags | PREG_SPLIT_DELIM_CAPTURE]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if given string matches a regular expression pattern and returns an array with first found match and each subpattern.
|
||||
* Parameter $flags can be any combination of PREG_OFFSET_CAPTURE and PREG_UNMATCHED_AS_NULL flags.
|
||||
*/
|
||||
public static function match(
|
||||
string $subject,
|
||||
#[Language('RegExp')]
|
||||
string $pattern,
|
||||
int $flags = 0,
|
||||
int $offset = 0
|
||||
): ?array
|
||||
{
|
||||
if ($offset > strlen($subject)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset])
|
||||
? $m
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds all occurrences matching regular expression pattern and returns a two-dimensional array. Result is array of matches (ie uses by default PREG_SET_ORDER).
|
||||
* Parameter $flags can be any combination of PREG_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL and PREG_PATTERN_ORDER flags.
|
||||
*/
|
||||
public static function matchAll(
|
||||
string $subject,
|
||||
#[Language('RegExp')]
|
||||
string $pattern,
|
||||
int $flags = 0,
|
||||
int $offset = 0
|
||||
): array
|
||||
{
|
||||
if ($offset > strlen($subject)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
self::pcre('preg_match_all', [
|
||||
$pattern, $subject, &$m,
|
||||
($flags & PREG_PATTERN_ORDER) ? $flags : ($flags | PREG_SET_ORDER),
|
||||
$offset,
|
||||
]);
|
||||
return $m;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replaces all occurrences matching regular expression $pattern which can be string or array in the form `pattern => replacement`.
|
||||
* @param string|array $pattern
|
||||
* @param string|callable $replacement
|
||||
*/
|
||||
public static function replace(
|
||||
string $subject,
|
||||
#[Language('RegExp')]
|
||||
$pattern,
|
||||
$replacement = '',
|
||||
int $limit = -1
|
||||
): string
|
||||
{
|
||||
if (is_object($replacement) || is_array($replacement)) {
|
||||
if (!is_callable($replacement, false, $textual)) {
|
||||
throw new Nette\InvalidStateException("Callback '$textual' is not callable.");
|
||||
}
|
||||
|
||||
return self::pcre('preg_replace_callback', [$pattern, $replacement, $subject, $limit]);
|
||||
|
||||
} elseif (is_array($pattern) && is_string(key($pattern))) {
|
||||
$replacement = array_values($pattern);
|
||||
$pattern = array_keys($pattern);
|
||||
}
|
||||
|
||||
return self::pcre('preg_replace', [$pattern, $replacement, $subject, $limit]);
|
||||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
public static function pcre(string $func, array $args)
|
||||
{
|
||||
$res = Callback::invokeSafe($func, $args, function (string $message) use ($args): void {
|
||||
// compile-time error, not detectable by preg_last_error
|
||||
throw new RegexpException($message . ' in pattern: ' . implode(' or ', (array) $args[0]));
|
||||
});
|
||||
|
||||
if (($code = preg_last_error()) // run-time error, but preg_last_error & return code are liars
|
||||
&& ($res === null || !in_array($func, ['preg_filter', 'preg_replace_callback', 'preg_replace'], true))
|
||||
) {
|
||||
throw new RegexpException((RegexpException::MESSAGES[$code] ?? 'Unknown error')
|
||||
. ' (pattern: ' . implode(' or ', (array) $args[0]) . ')', $code);
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
282
vendor/nette/utils/src/Utils/Type.php
vendored
Normal file
282
vendor/nette/utils/src/Utils/Type.php
vendored
Normal file
@@ -0,0 +1,282 @@
|
||||
<?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\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* PHP type reflection.
|
||||
*/
|
||||
final class Type
|
||||
{
|
||||
/** @var array<int, string|self> */
|
||||
private $types;
|
||||
|
||||
/** @var bool */
|
||||
private $simple;
|
||||
|
||||
/** @var string |, & */
|
||||
private $kind;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a Type object based on reflection. Resolves self, static and parent to the actual class name.
|
||||
* If the subject has no type, it returns null.
|
||||
* @param \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection
|
||||
*/
|
||||
public static function fromReflection($reflection): ?self
|
||||
{
|
||||
if ($reflection instanceof \ReflectionProperty && PHP_VERSION_ID < 70400) {
|
||||
return null;
|
||||
} elseif ($reflection instanceof \ReflectionMethod) {
|
||||
$type = $reflection->getReturnType() ?? (PHP_VERSION_ID >= 80100 ? $reflection->getTentativeReturnType() : null);
|
||||
} else {
|
||||
$type = $reflection instanceof \ReflectionFunctionAbstract
|
||||
? $reflection->getReturnType()
|
||||
: $reflection->getType();
|
||||
}
|
||||
|
||||
return $type ? self::fromReflectionType($type, $reflection, true) : null;
|
||||
}
|
||||
|
||||
|
||||
private static function fromReflectionType(\ReflectionType $type, $of, bool $asObject)
|
||||
{
|
||||
if ($type instanceof \ReflectionNamedType) {
|
||||
$name = self::resolve($type->getName(), $of);
|
||||
return $asObject
|
||||
? new self($type->allowsNull() && $name !== 'mixed' ? [$name, 'null'] : [$name])
|
||||
: $name;
|
||||
|
||||
} elseif ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) {
|
||||
return new self(
|
||||
array_map(
|
||||
function ($t) use ($of) { return self::fromReflectionType($t, $of, false); },
|
||||
$type->getTypes()
|
||||
),
|
||||
$type instanceof \ReflectionUnionType ? '|' : '&'
|
||||
);
|
||||
|
||||
} else {
|
||||
throw new Nette\InvalidStateException('Unexpected type of ' . Reflection::toString($of));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates the Type object according to the text notation.
|
||||
*/
|
||||
public static function fromString(string $type): self
|
||||
{
|
||||
if (!Validators::isTypeDeclaration($type)) {
|
||||
throw new Nette\InvalidArgumentException("Invalid type '$type'.");
|
||||
}
|
||||
|
||||
if ($type[0] === '?') {
|
||||
return new self([substr($type, 1), 'null']);
|
||||
}
|
||||
|
||||
$unions = [];
|
||||
foreach (explode('|', $type) as $part) {
|
||||
$part = explode('&', trim($part, '()'));
|
||||
$unions[] = count($part) === 1 ? $part[0] : new self($part, '&');
|
||||
}
|
||||
|
||||
return count($unions) === 1 && $unions[0] instanceof self
|
||||
? $unions[0]
|
||||
: new self($unions);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolves 'self', 'static' and 'parent' to the actual class name.
|
||||
* @param \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $of
|
||||
*/
|
||||
public static function resolve(string $type, $of): string
|
||||
{
|
||||
$lower = strtolower($type);
|
||||
if ($of instanceof \ReflectionFunction) {
|
||||
return $type;
|
||||
} elseif ($lower === 'self' || $lower === 'static') {
|
||||
return $of->getDeclaringClass()->name;
|
||||
} elseif ($lower === 'parent' && $of->getDeclaringClass()->getParentClass()) {
|
||||
return $of->getDeclaringClass()->getParentClass()->name;
|
||||
} else {
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function __construct(array $types, string $kind = '|')
|
||||
{
|
||||
$o = array_search('null', $types, true);
|
||||
if ($o !== false) { // null as last
|
||||
array_splice($types, $o, 1);
|
||||
$types[] = 'null';
|
||||
}
|
||||
|
||||
$this->types = $types;
|
||||
$this->simple = is_string($types[0]) && ($types[1] ?? 'null') === 'null';
|
||||
$this->kind = count($types) > 1 ? $kind : '';
|
||||
}
|
||||
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$multi = count($this->types) > 1;
|
||||
if ($this->simple) {
|
||||
return ($multi ? '?' : '') . $this->types[0];
|
||||
}
|
||||
|
||||
$res = [];
|
||||
foreach ($this->types as $type) {
|
||||
$res[] = $type instanceof self && $multi ? "($type)" : $type;
|
||||
}
|
||||
return implode($this->kind, $res);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the array of subtypes that make up the compound type as strings.
|
||||
* @return array<int, string|string[]>
|
||||
*/
|
||||
public function getNames(): array
|
||||
{
|
||||
return array_map(function ($t) {
|
||||
return $t instanceof self ? $t->getNames() : $t;
|
||||
}, $this->types);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the array of subtypes that make up the compound type as Type objects:
|
||||
* @return self[]
|
||||
*/
|
||||
public function getTypes(): array
|
||||
{
|
||||
return array_map(function ($t) {
|
||||
return $t instanceof self ? $t : new self([$t]);
|
||||
}, $this->types);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the type name for simple types, otherwise null.
|
||||
*/
|
||||
public function getSingleName(): ?string
|
||||
{
|
||||
return $this->simple
|
||||
? $this->types[0]
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true whether it is a union type.
|
||||
*/
|
||||
public function isUnion(): bool
|
||||
{
|
||||
return $this->kind === '|';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true whether it is an intersection type.
|
||||
*/
|
||||
public function isIntersection(): bool
|
||||
{
|
||||
return $this->kind === '&';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true whether it is a simple type. Single nullable types are also considered to be simple types.
|
||||
*/
|
||||
public function isSimple(): bool
|
||||
{
|
||||
return $this->simple;
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated use isSimple() */
|
||||
public function isSingle(): bool
|
||||
{
|
||||
return $this->simple;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true whether the type is both a simple and a PHP built-in type.
|
||||
*/
|
||||
public function isBuiltin(): bool
|
||||
{
|
||||
return $this->simple && Validators::isBuiltinType($this->types[0]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true whether the type is both a simple and a class name.
|
||||
*/
|
||||
public function isClass(): bool
|
||||
{
|
||||
return $this->simple && !Validators::isBuiltinType($this->types[0]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines if type is special class name self/parent/static.
|
||||
*/
|
||||
public function isClassKeyword(): bool
|
||||
{
|
||||
return $this->simple && Validators::isClassKeyword($this->types[0]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies type compatibility. For example, it checks if a value of a certain type could be passed as a parameter.
|
||||
*/
|
||||
public function allows(string $subtype): bool
|
||||
{
|
||||
if ($this->types === ['mixed']) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$subtype = self::fromString($subtype);
|
||||
return $subtype->isUnion()
|
||||
? Arrays::every($subtype->types, function ($t) {
|
||||
return $this->allows2($t instanceof self ? $t->types : [$t]);
|
||||
})
|
||||
: $this->allows2($subtype->types);
|
||||
}
|
||||
|
||||
|
||||
private function allows2(array $subtypes): bool
|
||||
{
|
||||
return $this->isUnion()
|
||||
? Arrays::some($this->types, function ($t) use ($subtypes) {
|
||||
return $this->allows3($t instanceof self ? $t->types : [$t], $subtypes);
|
||||
})
|
||||
: $this->allows3($this->types, $subtypes);
|
||||
}
|
||||
|
||||
|
||||
private function allows3(array $types, array $subtypes): bool
|
||||
{
|
||||
return Arrays::every($types, function ($type) use ($subtypes) {
|
||||
$builtin = Validators::isBuiltinType($type);
|
||||
return Arrays::some($subtypes, function ($subtype) use ($type, $builtin) {
|
||||
return $builtin
|
||||
? strcasecmp($type, $subtype) === 0
|
||||
: is_a($subtype, $type, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
425
vendor/nette/utils/src/Utils/Validators.php
vendored
Normal file
425
vendor/nette/utils/src/Utils/Validators.php
vendored
Normal file
@@ -0,0 +1,425 @@
|
||||
<?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\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Validation utilities.
|
||||
*/
|
||||
class Validators
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
private const BuiltinTypes = [
|
||||
'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1,
|
||||
'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1, 'mixed' => 1, 'false' => 1,
|
||||
'never' => 1, 'true' => 1,
|
||||
];
|
||||
|
||||
/** @var array<string,?callable> */
|
||||
protected static $validators = [
|
||||
// PHP types
|
||||
'array' => 'is_array',
|
||||
'bool' => 'is_bool',
|
||||
'boolean' => 'is_bool',
|
||||
'float' => 'is_float',
|
||||
'int' => 'is_int',
|
||||
'integer' => 'is_int',
|
||||
'null' => 'is_null',
|
||||
'object' => 'is_object',
|
||||
'resource' => 'is_resource',
|
||||
'scalar' => 'is_scalar',
|
||||
'string' => 'is_string',
|
||||
|
||||
// pseudo-types
|
||||
'callable' => [self::class, 'isCallable'],
|
||||
'iterable' => 'is_iterable',
|
||||
'list' => [Arrays::class, 'isList'],
|
||||
'mixed' => [self::class, 'isMixed'],
|
||||
'none' => [self::class, 'isNone'],
|
||||
'number' => [self::class, 'isNumber'],
|
||||
'numeric' => [self::class, 'isNumeric'],
|
||||
'numericint' => [self::class, 'isNumericInt'],
|
||||
|
||||
// string patterns
|
||||
'alnum' => 'ctype_alnum',
|
||||
'alpha' => 'ctype_alpha',
|
||||
'digit' => 'ctype_digit',
|
||||
'lower' => 'ctype_lower',
|
||||
'pattern' => null,
|
||||
'space' => 'ctype_space',
|
||||
'unicode' => [self::class, 'isUnicode'],
|
||||
'upper' => 'ctype_upper',
|
||||
'xdigit' => 'ctype_xdigit',
|
||||
|
||||
// syntax validation
|
||||
'email' => [self::class, 'isEmail'],
|
||||
'identifier' => [self::class, 'isPhpIdentifier'],
|
||||
'uri' => [self::class, 'isUri'],
|
||||
'url' => [self::class, 'isUrl'],
|
||||
|
||||
// environment validation
|
||||
'class' => 'class_exists',
|
||||
'interface' => 'interface_exists',
|
||||
'directory' => 'is_dir',
|
||||
'file' => 'is_file',
|
||||
'type' => [self::class, 'isType'],
|
||||
];
|
||||
|
||||
/** @var array<string,callable> */
|
||||
protected static $counters = [
|
||||
'string' => 'strlen',
|
||||
'unicode' => [Strings::class, 'length'],
|
||||
'array' => 'count',
|
||||
'list' => 'count',
|
||||
'alnum' => 'strlen',
|
||||
'alpha' => 'strlen',
|
||||
'digit' => 'strlen',
|
||||
'lower' => 'strlen',
|
||||
'space' => 'strlen',
|
||||
'upper' => 'strlen',
|
||||
'xdigit' => 'strlen',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Verifies that the value is of expected types separated by pipe.
|
||||
* @param mixed $value
|
||||
* @throws AssertionException
|
||||
*/
|
||||
public static function assert($value, string $expected, string $label = 'variable'): void
|
||||
{
|
||||
if (!static::is($value, $expected)) {
|
||||
$expected = str_replace(['|', ':'], [' or ', ' in range '], $expected);
|
||||
$translate = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float', 'NULL' => 'null'];
|
||||
$type = $translate[gettype($value)] ?? gettype($value);
|
||||
if (is_int($value) || is_float($value) || (is_string($value) && strlen($value) < 40)) {
|
||||
$type .= ' ' . var_export($value, true);
|
||||
} elseif (is_object($value)) {
|
||||
$type .= ' ' . get_class($value);
|
||||
}
|
||||
|
||||
throw new AssertionException("The $label expects to be $expected, $type given.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies that element $key in array is of expected types separated by pipe.
|
||||
* @param mixed[] $array
|
||||
* @param int|string $key
|
||||
* @throws AssertionException
|
||||
*/
|
||||
public static function assertField(
|
||||
array $array,
|
||||
$key,
|
||||
?string $expected = null,
|
||||
string $label = "item '%' in array"
|
||||
): void
|
||||
{
|
||||
if (!array_key_exists($key, $array)) {
|
||||
throw new AssertionException('Missing ' . str_replace('%', $key, $label) . '.');
|
||||
|
||||
} elseif ($expected) {
|
||||
static::assert($array[$key], $expected, str_replace('%', $key, $label));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies that the value is of expected types separated by pipe.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function is($value, string $expected): bool
|
||||
{
|
||||
foreach (explode('|', $expected) as $item) {
|
||||
if (substr($item, -2) === '[]') {
|
||||
if (is_iterable($value) && self::everyIs($value, substr($item, 0, -2))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
continue;
|
||||
} elseif (substr($item, 0, 1) === '?') {
|
||||
$item = substr($item, 1);
|
||||
if ($value === null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[$type] = $item = explode(':', $item, 2);
|
||||
if (isset(static::$validators[$type])) {
|
||||
try {
|
||||
if (!static::$validators[$type]($value)) {
|
||||
continue;
|
||||
}
|
||||
} catch (\TypeError $e) {
|
||||
continue;
|
||||
}
|
||||
} elseif ($type === 'pattern') {
|
||||
if (Strings::match($value, '|^' . ($item[1] ?? '') . '$|D')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
continue;
|
||||
} elseif (!$value instanceof $type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($item[1])) {
|
||||
$length = $value;
|
||||
if (isset(static::$counters[$type])) {
|
||||
$length = static::$counters[$type]($value);
|
||||
}
|
||||
|
||||
$range = explode('..', $item[1]);
|
||||
if (!isset($range[1])) {
|
||||
$range[1] = $range[0];
|
||||
}
|
||||
|
||||
if (($range[0] !== '' && $length < $range[0]) || ($range[1] !== '' && $length > $range[1])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds whether all values are of expected types separated by pipe.
|
||||
* @param mixed[] $values
|
||||
*/
|
||||
public static function everyIs(iterable $values, string $expected): bool
|
||||
{
|
||||
foreach ($values as $value) {
|
||||
if (!static::is($value, $expected)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the value is an integer or a float.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isNumber($value): bool
|
||||
{
|
||||
return is_int($value) || is_float($value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the value is an integer or a integer written in a string.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isNumericInt($value): bool
|
||||
{
|
||||
return is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]+$#D', $value));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the value is a number or a number written in a string.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isNumeric($value): bool
|
||||
{
|
||||
return is_float($value) || is_int($value) || (is_string($value) && preg_match('#^[+-]?([0-9]++\.?[0-9]*|\.[0-9]+)$#D', $value));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the value is a syntactically correct callback.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isCallable($value): bool
|
||||
{
|
||||
return $value && is_callable($value, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the value is a valid UTF-8 string.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isUnicode($value): bool
|
||||
{
|
||||
return is_string($value) && preg_match('##u', $value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the value is 0, '', false or null.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isNone($value): bool
|
||||
{
|
||||
return $value == null; // intentionally ==
|
||||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
public static function isMixed(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if a variable is a zero-based integer indexed array.
|
||||
* @param mixed $value
|
||||
* @deprecated use Nette\Utils\Arrays::isList
|
||||
* @return ($value is list ? true : false)
|
||||
*/
|
||||
public static function isList($value): bool
|
||||
{
|
||||
return Arrays::isList($value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the value is in the given range [min, max], where the upper or lower limit can be omitted (null).
|
||||
* Numbers, strings and DateTime objects can be compared.
|
||||
* @param mixed $value
|
||||
*/
|
||||
public static function isInRange($value, array $range): bool
|
||||
{
|
||||
if ($value === null || !(isset($range[0]) || isset($range[1]))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$limit = $range[0] ?? $range[1];
|
||||
if (is_string($limit)) {
|
||||
$value = (string) $value;
|
||||
} elseif ($limit instanceof \DateTimeInterface) {
|
||||
if (!$value instanceof \DateTimeInterface) {
|
||||
return false;
|
||||
}
|
||||
} elseif (is_numeric($value)) {
|
||||
$value *= 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (!isset($range[0]) || ($value >= $range[0])) && (!isset($range[1]) || ($value <= $range[1]));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the value is a valid email address. It does not verify that the domain actually exists, only the syntax is verified.
|
||||
*/
|
||||
public static function isEmail(string $value): bool
|
||||
{
|
||||
$atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 unquoted characters in local-part
|
||||
$alpha = "a-z\x80-\xFF"; // superset of IDN
|
||||
return (bool) preg_match(<<<XX
|
||||
(^
|
||||
("([ !#-[\\]-~]*|\\\\[ -~])+"|$atom+(\\.$atom+)*) # quoted or unquoted
|
||||
@
|
||||
([0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)+ # domain - RFC 1034
|
||||
[$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain
|
||||
$)Dix
|
||||
XX
|
||||
, $value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the value is a valid URL address.
|
||||
*/
|
||||
public static function isUrl(string $value): bool
|
||||
{
|
||||
$alpha = "a-z\x80-\xFF";
|
||||
return (bool) preg_match(<<<XX
|
||||
(^
|
||||
https?://(
|
||||
(([-_0-9$alpha]+\\.)* # subdomain
|
||||
[0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)? # domain
|
||||
[$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain
|
||||
|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3} # IPv4
|
||||
|\\[[0-9a-f:]{3,39}\\] # IPv6
|
||||
)(:\\d{1,5})? # port
|
||||
(/\\S*)? # path
|
||||
(\\?\\S*)? # query
|
||||
(\\#\\S*)? # fragment
|
||||
$)Dix
|
||||
XX
|
||||
, $value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the value is a valid URI address, that is, actually a string beginning with a syntactically valid schema.
|
||||
*/
|
||||
public static function isUri(string $value): bool
|
||||
{
|
||||
return (bool) preg_match('#^[a-z\d+\.-]+:\S+$#Di', $value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the input is a class, interface or trait.
|
||||
*/
|
||||
public static function isType(string $type): bool
|
||||
{
|
||||
return class_exists($type) || interface_exists($type) || trait_exists($type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the input is a valid PHP identifier.
|
||||
*/
|
||||
public static function isPhpIdentifier(string $value): bool
|
||||
{
|
||||
return preg_match('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#D', $value) === 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines if type is PHP built-in type. Otherwise, it is the class name.
|
||||
*/
|
||||
public static function isBuiltinType(string $type): bool
|
||||
{
|
||||
return isset(self::BuiltinTypes[strtolower($type)]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines if type is special class name self/parent/static.
|
||||
*/
|
||||
public static function isClassKeyword(string $name): bool
|
||||
{
|
||||
return (bool) preg_match('#^(self|parent|static)$#Di', $name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the given type declaration is syntactically valid.
|
||||
*/
|
||||
public static function isTypeDeclaration(string $type): bool
|
||||
{
|
||||
return (bool) preg_match(<<<'XX'
|
||||
~(
|
||||
\?? (?<type> \\? (?<name> [a-zA-Z_\x7f-\xff][\w\x7f-\xff]*) (\\ (?&name))* ) |
|
||||
(?<intersection> (?&type) (& (?&type))+ ) |
|
||||
(?<upart> (?&type) | \( (?&intersection) \) ) (\| (?&upart))+
|
||||
)$~xAD
|
||||
XX
|
||||
, $type);
|
||||
}
|
||||
}
|
||||
58
vendor/nette/utils/src/Utils/exceptions.php
vendored
Normal file
58
vendor/nette/utils/src/Utils/exceptions.php
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
<?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\Utils;
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when an image error occurs.
|
||||
*/
|
||||
class ImageException extends \Exception
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that indicates invalid image file.
|
||||
*/
|
||||
class UnknownImageFileException extends ImageException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that indicates error of JSON encoding/decoding.
|
||||
*/
|
||||
class JsonException extends \Exception
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that indicates error of the last Regexp execution.
|
||||
*/
|
||||
class RegexpException extends \Exception
|
||||
{
|
||||
public const MESSAGES = [
|
||||
PREG_INTERNAL_ERROR => 'Internal error',
|
||||
PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit was exhausted',
|
||||
PREG_RECURSION_LIMIT_ERROR => 'Recursion limit was exhausted',
|
||||
PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 data',
|
||||
PREG_BAD_UTF8_OFFSET_ERROR => 'Offset didn\'t correspond to the begin of a valid UTF-8 code point',
|
||||
6 => 'Failed due to limited JIT stack space', // PREG_JIT_STACKLIMIT_ERROR
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that indicates assertion error.
|
||||
*/
|
||||
class AssertionException extends \Exception
|
||||
{
|
||||
}
|
||||
32
vendor/nette/utils/src/compatibility.php
vendored
Normal file
32
vendor/nette/utils/src/compatibility.php
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
<?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\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
if (false) {
|
||||
/** @deprecated use Nette\HtmlStringable */
|
||||
interface IHtmlString extends Nette\HtmlStringable
|
||||
{
|
||||
}
|
||||
} elseif (!interface_exists(IHtmlString::class)) {
|
||||
class_alias(Nette\HtmlStringable::class, IHtmlString::class);
|
||||
}
|
||||
|
||||
namespace Nette\Localization;
|
||||
|
||||
if (false) {
|
||||
/** @deprecated use Nette\Localization\Translator */
|
||||
interface ITranslator extends Translator
|
||||
{
|
||||
}
|
||||
} elseif (!interface_exists(ITranslator::class)) {
|
||||
class_alias(Translator::class, ITranslator::class);
|
||||
}
|
||||
109
vendor/nette/utils/src/exceptions.php
vendored
Normal file
109
vendor/nette/utils/src/exceptions.php
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
<?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;
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when the value of an argument is
|
||||
* outside the allowable range of values as defined by the invoked method.
|
||||
*/
|
||||
class ArgumentOutOfRangeException extends \InvalidArgumentException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when a method call is invalid for the object's
|
||||
* current state, method has been invoked at an illegal or inappropriate time.
|
||||
*/
|
||||
class InvalidStateException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when a requested method or operation is not implemented.
|
||||
*/
|
||||
class NotImplementedException extends \LogicException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when an invoked method is not supported. For scenarios where
|
||||
* it is sometimes possible to perform the requested operation, see InvalidStateException.
|
||||
*/
|
||||
class NotSupportedException extends \LogicException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when a requested method or operation is deprecated.
|
||||
*/
|
||||
class DeprecatedException extends NotSupportedException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when accessing a class member (property or method) fails.
|
||||
*/
|
||||
class MemberAccessException extends \Error
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when an I/O error occurs.
|
||||
*/
|
||||
class IOException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when accessing a file that does not exist on disk.
|
||||
*/
|
||||
class FileNotFoundException extends IOException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when part of a file or directory cannot be found.
|
||||
*/
|
||||
class DirectoryNotFoundException extends IOException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when an argument does not match with the expected value.
|
||||
*/
|
||||
class InvalidArgumentException extends \InvalidArgumentException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when an illegal index was requested.
|
||||
*/
|
||||
class OutOfRangeException extends \OutOfRangeException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The exception that is thrown when a value (typically returned by function) does not match with the expected value.
|
||||
*/
|
||||
class UnexpectedValueException extends \UnexpectedValueException
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user