初始化代码

This commit is contained in:
2025-12-22 14:34:25 +08:00
parent c2c5ae2fdd
commit a77dbc743f
1510 changed files with 213008 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
{
"name": "nette/php-generator",
"description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 7.3 features.",
"keywords": ["nette", "php", "code", "scaffolding"],
"homepage": "https://nette.org",
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"require": {
"php": ">=7.1",
"nette/utils": "^2.4.2 || ~3.0.0"
},
"require-dev": {
"nette/tester": "^2.0",
"tracy/tracy": "^2.3"
},
"autoload": {
"classmap": ["src/"]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "3.3-dev"
}
}
}

View File

@@ -0,0 +1,33 @@
How to contribute & use the issue tracker
=========================================
Nette welcomes your contributions. There are several ways to help out:
* Create an issue on GitHub, if you have found a bug
* Write test cases for open bug issues
* Write fixes for open bug/feature issues, preferably with test cases included
* Contribute to the [documentation](https://nette.org/en/writing)
Issues
------
Please **do not use the issue tracker to ask questions**. We will be happy to help you
on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette).
A good bug report shouldn't leave others needing to chase you up for more
information. Please try to be as detailed as possible in your report.
**Feature requests** are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to *you* to make a strong
case to convince the project's developers of the merits of this feature.
Contributing
------------
If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing).
The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them.
Please do not fix whitespace, format code, or make a purely cosmetic patch.
Thanks! :heart:

60
vendor/nette/php-generator/license.md vendored Normal file
View 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)

566
vendor/nette/php-generator/readme.md vendored Normal file
View File

@@ -0,0 +1,566 @@
Nette PHP Generator
===================
[![Downloads this Month](https://img.shields.io/packagist/dm/nette/php-generator.svg)](https://packagist.org/packages/nette/php-generator)
[![Build Status](https://travis-ci.org/nette/php-generator.svg?branch=master)](https://travis-ci.org/nette/php-generator)
[![Coverage Status](https://coveralls.io/repos/github/nette/php-generator/badge.svg?branch=master&v=1)](https://coveralls.io/github/nette/php-generator?branch=master)
[![Latest Stable Version](https://poser.pugx.org/nette/php-generator/v/stable)](https://github.com/nette/php-generator/releases)
[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/php-generator/blob/master/license.md)
Introduction
------------
Generate PHP code, classes, namespaces etc. with a simple programmatical API.
Documentation can be found on the [website](https://doc.nette.org/php-generator).
If you like Nette, **[please make a donation now](https://nette.org/donate)**. Thank you!
Installation
------------
The recommended way to install is via Composer:
```
composer require nette/php-generator
```
- PhpGenerator 3.3 & 3.2 is compatible with PHP 7.1 to 7.4
- 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
Usage
-----
Usage is very easy. Let's start with a straightforward example of generating class:
```php
$class = new Nette\PhpGenerator\ClassType('Demo');
$class
->setFinal()
->setExtends('ParentClass')
->addImplement('Countable')
->addTrait('Nette\SmartObject')
->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;
// or use printer:
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($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
{
use Nette\SmartObject;
}
```
We can add constants and properties:
```php
$class->addConstant('ID', 123);
$class->addProperty('items', [1, 2, 3])
->setVisibility('private')
->setStatic()
->addComment('@var int[]');
```
It generates:
```php
const ID = 123;
/** @var int[] */
private static $items = [1, 2, 3];
```
And we can add methods with parameters:
```php
$method = $class->addMethod('count')
->addComment('Count it.')
->addComment('@return int')
->setFinal()
->setVisibility('protected')
->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 = [])
{
return count($items ?: $this->items);
}
```
If the property, constant, method or parameter already exist, it will be overwritten.
Members can be removed using `removeProperty()`, `removeConstant()`, `removeMethod()` or `removeParameter()`.
PHP Generator supports all new PHP 7.3 and 7.4 features:
```php
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addConstant('ID', 123)
->setVisibility('private'); // constant visiblity
$class->addProperty('items')
->setType('array') // typed properites
->setNullable()
->setInitialized();
$method = $class->addMethod('getValue')
->setReturnType('int') // method return type
->setReturnNullable() // nullable return type
->setBody('return count($this->items);');
$method->addParameter('id')
->setType('int') // scalar type hint
->setNullable(); // nullable type hint
echo $class;
```
Result:
```php
class Demo
{
private const ID = 123;
public ?array $items = null;
public function getValue(?int $id): ?int
{
return count($this->items);
}
}
```
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);
```
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
```
It can be used also for functions, closures, namespaces etc.
Literals
--------
You can pass any PHP code to property or parameter default values via `PhpLiteral`:
```php
use Nette\PhpGenerator\PhpLiteral;
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addProperty('foo', new PhpLiteral('Iterator::SELF_FIRST'));
$class->addMethod('bar')
->addParameter('id', new PhpLiteral('1 + 2'));
echo $class;
```
Result:
```php
class Demo
{
public $foo = Iterator::SELF_FIRST;
public function bar($id = 1 + 2)
{
}
}
```
Interface or Trait
------------------
```php
$class = new Nette\PhpGenerator\ClassType('DemoInterface');
$class->setType('interface');
// or $class->setType('trait');
```
Trait Resolutions and Visibility
--------------------------------
```php
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addTrait('SmartObject', ['sayHello as protected']);
echo $class;
```
Result:
```php
class Demo
{
use SmartObject {
sayHello as protected;
}
}
```
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 function:
```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 closure:
```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('return $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;
```
Method and Function Body Generator
----------------------------------
You can use special placeholders for handy way to generate method or function body.
Simple placeholders:
```php
$str = 'any string';
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('return strlen(?, ?);', [$str, $num]);
echo $function;
```
Result:
```php
function foo()
{
return strlen('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);
}
```
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 and interfaces (hereinafter classes) can be grouped into namespaces:
```php
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');
// or
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);
```
If the class already exists, it will be overwritten.
You can define use-statements:
```php
$namespace->addUse('Http\Request'); // use Http\Request;
$namespace->addUse('Http\Request', 'HttpReq'); // use Http\Request as HttpReq;
```
**IMPORTANT NOTE:** 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 and used traits) 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 resolve to A
->addTrait('Bar\AliasedClass'); // it will resolve to AliasedClass
$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->unresolveName('Foo\D')); // in comments resolve 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
---------
PHP files can contains multiple classes, namespaces and comments:
```php
$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('This file is auto-generated.');
$file->setStrictTypes(); // adds declare(strict_types=1)
$namespace = $file->addNamespace('Foo');
$class = $namespace->addClass('A');
$class->addMethod('hello');
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
{
public function hello()
{
}
}
```
Generate using Reflection
-------------------------
Another common use case is to create class or method based on existing ones:
```php
$class = Nette\PhpGenerator\ClassType::from(PDO::class);
$function = Nette\PhpGenerator\GlobalFunction::from('trim');
$closure = Nette\PhpGenerator\Closure::from(
function (stdClass $a, $b = null) {}
);
```
Variables dumper
----------------
The Dumper returns a parsable PHP string representation of a variable. It provides a better function that you can use instead of `var_export()`
with more readable output.
```php
$dumper = new Nette\PhpGenerator\Dumper;
$var = ['a', 'b', 123];
echo $dumper->dump($var); // prints ['a', 'b', 123]
```

View File

@@ -0,0 +1,510 @@
<?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 description.
*
* @property Method[] $methods
* @property Property[] $properties
*/
final class ClassType
{
use Nette\SmartObject;
use Traits\CommentAware;
public const
TYPE_CLASS = 'class',
TYPE_INTERFACE = 'interface',
TYPE_TRAIT = 'trait';
public const
VISIBILITY_PUBLIC = 'public',
VISIBILITY_PROTECTED = 'protected',
VISIBILITY_PRIVATE = 'private';
/** @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 array[] */
private $traits = [];
/** @var Constant[] name => Constant */
private $consts = [];
/** @var Property[] name => Property */
private $properties = [];
/** @var Method[] name => Method */
private $methods = [];
/**
* @param string|object $class
* @return static
*/
public static function from($class): self
{
return (new Factory)->fromClassReflection(new \ReflectionClass($class));
}
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);
}
}
/**
* Deprecated: an object can be in multiple namespaces.
* @deprecated
*/
public function getNamespace(): ?PhpNamespace
{
return $this->namespace;
}
/**
* @return static
*/
public function setName(?string $name): self
{
if ($name !== null && !Helpers::isIdentifier($name)) {
throw new Nette\InvalidArgumentException("Value '$name' is not valid class name.");
}
$this->name = $name;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
/**
* @return static
*/
public function setType(string $type): self
{
if (!in_array($type, [self::TYPE_CLASS, self::TYPE_INTERFACE, self::TYPE_TRAIT], true)) {
throw new Nette\InvalidArgumentException('Argument must be class|interface|trait.');
}
$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;
}
/**
* @param string[] $names
* @return static
*/
public function setTraits(array $names): self
{
$this->validateNames($names);
$this->traits = array_fill_keys($names, []);
return $this;
}
/**
* @return string[]
*/
public function getTraits(): array
{
return array_keys($this->traits);
}
/**
* @internal
*/
public function getTraitResolutions(): array
{
return $this->traits;
}
/**
* @return static
*/
public function addTrait(string $name, array $resolutions = []): self
{
$this->validateNames([$name]);
$this->traits[$name] = $resolutions;
return $this;
}
/**
* @param Method|Property|Constant $member
* @return static
*/
public function addMember($member): self
{
if ($member instanceof Method) {
if ($this->type === self::TYPE_INTERFACE) {
$member->setBody(null);
}
$this->methods[$member->getName()] = $member;
} elseif ($member instanceof Property) {
$this->properties[$member->getName()] = $member;
} elseif ($member instanceof Constant) {
$this->consts[$member->getName()] = $member;
} else {
throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant.');
}
return $this;
}
/**
* @param Constant[]|mixed[] $consts
* @return static
*/
public function setConstants(array $consts): self
{
$this->consts = [];
foreach ($consts as $k => $v) {
$const = $v instanceof Constant ? $v : (new Constant($k))->setValue($v);
$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);
}
/**
* @return static
*/
public function removeConstant(string $name): self
{
unset($this->consts[$name]);
return $this;
}
/**
* @param Property[] $props
* @return static
*/
public function setProperties(array $props): self
{
$this->properties = [];
foreach ($props as $v) {
if (!$v instanceof Property) {
throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Property[].');
}
$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] = (new Property($name))->setValue($value);
}
/**
* @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
{
$this->methods = [];
foreach ($methods as $v) {
if (!$v instanceof Method) {
throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Method[].');
}
$this->methods[$v->getName()] = $v;
}
return $this;
}
/**
* @return Method[]
*/
public function getMethods(): array
{
return $this->methods;
}
public function getMethod(string $name): Method
{
if (!isset($this->methods[$name])) {
throw new Nette\InvalidArgumentException("Method '$name' not found.");
}
return $this->methods[$name];
}
public function addMethod(string $name): Method
{
$method = new Method($name);
if ($this->type === self::TYPE_INTERFACE) {
$method->setBody(null);
} else {
$method->setVisibility(self::VISIBILITY_PUBLIC);
}
return $this->methods[$name] = $method;
}
/**
* @return static
*/
public function removeMethod(string $name): self
{
unset($this->methods[$name]);
return $this;
}
public function hasMethod(string $name): bool
{
return isset($this->methods[$name]);
}
/**
* @throws Nette\InvalidStateException
*/
public function validate(): void
{
if ($this->abstract && $this->final) {
throw new Nette\InvalidStateException('Class cannot be abstract and final.');
} elseif (!$this->name && ($this->abstract || $this->final)) {
throw new Nette\InvalidStateException('Anonymous class cannot be abstract or final.');
}
}
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->consts = array_map($clone, $this->consts);
$this->properties = array_map($clone, $this->properties);
$this->methods = array_map($clone, $this->methods);
}
}

View 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;
/** @var Parameter[] */
private $uses = [];
/**
* @return static
*/
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);
}
}
/**
* @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);
}
}

View File

@@ -0,0 +1,43 @@
<?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;
/** @var mixed */
private $value;
/**
* @return static
*/
public function setValue($val): self
{
$this->value = $val;
return $this;
}
public function getValue()
{
return $this->value;
}
}

View 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\PhpGenerator;
use Nette;
/**
* PHP code generator utils.
*/
final class Dumper
{
private const INDENT_LENGTH = 4;
/** @var int */
public $maxDepth = 50;
/** @var int */
public $wrapLength = 120;
/**
* 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 instanceof PhpLiteral) {
return ltrim(Nette\Utils\Strings::indent(trim((string) $var), $level), "\t");
} elseif ($var === null) {
return 'null';
} elseif (is_string($var)) {
return $this->dumpString($var);
} elseif (is_array($var)) {
return $this->dumpArray($var, $parents, $level, $column);
} 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 $var): string
{
if (preg_match('#[^\x09\x20-\x7E\xA0-\x{10FFFF}]#u', $var) || preg_last_error()) {
static $table;
if ($table === null) {
foreach (array_merge(range("\x00", "\x1F"), range("\x7F", "\xFF")) as $ch) {
$table[$ch] = '\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT);
}
$table['\\'] = '\\\\';
$table["\r"] = '\r';
$table["\n"] = '\n';
$table["\t"] = '\t';
$table['$'] = '\$';
$table['"'] = '\"';
}
return '"' . strtr($var, $table) . '"';
}
return "'" . preg_replace('#\'|\\\\(?=[\'\\\\]|$)#D', '\\\\$0', $var) . "'";
}
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("\t", $level);
$outInline = '';
$outWrapped = "\n$space";
$parents[] = $var;
$counter = 0;
foreach ($var as $k => &$v) {
$keyPart = $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 .= "\t"
. $keyPart
. $this->dumpVar($v, $parents, $level + 1, strlen($keyPart))
. ",\n$space";
}
array_pop($parents);
$wrap = strpos($outInline, "\n") !== false || $level * self::INDENT_LENGTH + $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 \Closure) {
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', 'DateTimeImmutable'], 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("\t", $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\t"
. ($keyPart = $this->dumpVar($k) . ' => ')
. $this->dumpVar($v, $parents, $level + 1, strlen($keyPart))
. ",\n";
}
}
array_pop($parents);
$out .= $space;
return $class === 'stdClass'
? "(object) [$out]"
: __CLASS__ . "::createObject('$class', [$out])";
}
/**
* Generates PHP statement.
*/
public function format(string $statement, ...$args): string
{
$tokens = preg_split('#(\.\.\.\?|\$\?|->\?|::\?|\\\\\?|\?\*|\?)#', $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 === '?*') {
$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"));
} else { // $ -> ::
$arg = array_shift($args);
if ($arg instanceof PhpLiteral || !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): string
{
$outInline = $outWrapped = '';
foreach ($var as &$v) {
$outInline .= $outInline === '' ? '' : ', ';
$outInline .= $this->dumpVar($v, [$var], 0, $column + strlen($outInline));
$outWrapped .= ($outWrapped === '' ? '' : ',') . "\n\t" . $this->dumpVar($v, [$var], 1);
}
return count($var) > 1 && (strpos($outInline, "\n") !== false || $column + strlen($outInline) > $this->wrapLength)
? $outWrapped . "\n"
: $outInline;
}
/**
* @return object
* @internal
*/
public static function createObject(string $class, array $props)
{
return unserialize('O' . substr(serialize($class), 1, -1) . substr(serialize($props), 1));
}
}

View 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;
/**
* Creates a representation based on reflection.
*/
final class Factory
{
use Nette\SmartObject;
public function fromClassReflection(\ReflectionClass $from): ClassType
{
$class = $from->isAnonymous()
? new ClassType
: new ClassType($from->getShortName(), new PhpNamespace($from->getNamespaceName()));
$class->setType($from->isInterface() ? $class::TYPE_INTERFACE : ($from->isTrait() ? $class::TYPE_TRAIT : $class::TYPE_CLASS));
$class->setFinal($from->isFinal() && $class->getType() === $class::TYPE_CLASS);
$class->setAbstract($from->isAbstract() && $class->getType() === $class::TYPE_CLASS);
$ifaces = $from->getInterfaceNames();
foreach ($ifaces as $iface) {
$ifaces = array_filter($ifaces, function (string $item) use ($iface): bool {
return !is_subclass_of($iface, $item);
});
}
$class->setImplements($ifaces);
$class->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
if ($from->getParentClass()) {
$class->setExtends($from->getParentClass()->getName());
$class->setImplements(array_diff($class->getImplements(), $from->getParentClass()->getInterfaceNames()));
}
$props = $methods = [];
foreach ($from->getProperties() as $prop) {
if ($prop->isDefault() && $prop->getDeclaringClass()->getName() === $from->getName()) {
$props[] = $this->fromPropertyReflection($prop);
}
}
$class->setProperties($props);
foreach ($from->getMethods() as $method) {
if ($method->getDeclaringClass()->getName() === $from->getName()) {
$methods[] = $this->fromMethodReflection($method);
}
}
$class->setMethods($methods);
$class->setConstants($from->getConstants());
return $class;
}
public function fromMethodReflection(\ReflectionMethod $from): Method
{
$method = new Method($from->getName());
$method->setParameters(array_map([$this, 'fromParameterReflection'], $from->getParameters()));
$method->setStatic($from->isStatic());
$isInterface = $from->getDeclaringClass()->isInterface();
$method->setVisibility($from->isPrivate()
? ClassType::VISIBILITY_PRIVATE
: ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ($isInterface ? null : ClassType::VISIBILITY_PUBLIC))
);
$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()));
if ($from->hasReturnType()) {
$method->setReturnType($from->getReturnType()->getName());
$method->setReturnNullable($from->getReturnType()->allowsNull());
}
return $method;
}
/**
* @return GlobalFunction|Closure
*/
public function fromFunctionReflection(\ReflectionFunction $from)
{
$function = $from->isClosure() ? new Closure : new GlobalFunction($from->getName());
$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()));
}
if ($from->hasReturnType()) {
$function->setReturnType($from->getReturnType()->getName());
$function->setReturnNullable($from->getReturnType()->allowsNull());
}
return $function;
}
public function fromParameterReflection(\ReflectionParameter $from): Parameter
{
$param = new Parameter($from->getName());
$param->setReference($from->isPassedByReference());
$param->setType($from->hasType() ? $from->getType()->getName() : null);
$param->setNullable($from->hasType() && $from->getType()->allowsNull());
if ($from->isDefaultValueAvailable()) {
$param->setDefaultValue($from->isDefaultValueConstant()
? new PhpLiteral($from->getDefaultValueConstantName())
: $from->getDefaultValue());
$param->setNullable($param->isNullable() && $param->getDefaultValue() !== null);
}
return $param;
}
public function fromPropertyReflection(\ReflectionProperty $from): Property
{
$defaults = $from->getDeclaringClass()->getDefaultProperties();
$prop = new Property($from->getName());
$prop->setValue($defaults[$prop->getName()] ?? null);
$prop->setStatic($from->isStatic());
$prop->setVisibility($from->isPrivate()
? ClassType::VISIBILITY_PRIVATE
: ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ClassType::VISIBILITY_PUBLIC)
);
if (PHP_VERSION_ID >= 70400 && ($type = $from->getType())) {
$prop->setType($type->getName());
$prop->setNullable($type->allowsNull());
$prop->setInitialized(array_key_exists($prop->getName(), $defaults));
}
$prop->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
return $prop;
}
}

View File

@@ -0,0 +1,47 @@
<?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;
/**
* @return static
*/
public static function from(string $function): self
{
return (new Factory)->fromFunctionReflection(new \ReflectionFunction($function));
}
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);
}
}
}

View File

@@ -0,0 +1,100 @@
<?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 PHP_IDENT = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*';
/** @deprecated use Nette\PhpGenerator\Dumper::dump() */
public static function dump($var): string
{
return (new Dumper)->dump($var);
}
/** @deprecated use Nette\PhpGenerator\Dumper::format() */
public static function format(string $statement, ...$args): string
{
return (new Dumper)->format($statement, ...$args);
}
/** @deprecated use 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
{
if (($s = trim($content)) === '') {
return '';
} elseif (strpos($content, "\n") === false) {
return "/** $s */\n";
} else {
return str_replace("\n", "\n * ", "/**\n$s") . "\n */\n";
}
}
public static function unformatDocComment(string $comment): string
{
return preg_replace('#^\s*\* ?#m', '', trim(trim(trim($comment), '/*')));
}
public static function isIdentifier($value): bool
{
return is_string($value) && preg_match('#^' . self::PHP_IDENT . '$#D', $value);
}
public static function isNamespaceIdentifier($value, bool $allowLeadingSlash = false): bool
{
$re = '#^' . ($allowLeadingSlash ? '\\\\?' : '') . self::PHP_IDENT . '(\\\\' . self::PHP_IDENT . ')*$#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)
{
return Dumper::createObject($class, $props);
}
}

View File

@@ -0,0 +1,137 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\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;
/** @var string|null */
private $body = '';
/** @var bool */
private $static = false;
/** @var bool */
private $final = false;
/** @var bool */
private $abstract = false;
/**
* @param string|array $method
* @return static
*/
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 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;
}
/**
* @throws Nette\InvalidStateException
*/
public function validate(): void
{
if ($this->abstract && ($this->final || $this->visibility === ClassType::VISIBILITY_PRIVATE)) {
throw new Nette\InvalidStateException('Method cannot be abstract and final or private.');
}
}
}

View File

@@ -0,0 +1,137 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
/**
* Method parameter description.
*
* @property mixed $defaultValue
*/
final class Parameter
{
use Nette\SmartObject;
use Traits\NameAware;
/** @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 = $type;
return $this;
}
public function getType(): ?string
{
return $this->type;
}
/** @deprecated use setType() */
public function setTypeHint(?string $type): self
{
$this->type = $type;
return $this;
}
/** @deprecated use getType() */
public function getTypeHint(): ?string
{
return $this->type;
}
/**
* @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;
}
}

View File

@@ -0,0 +1,125 @@
<?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 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 addNamespace(string $name): PhpNamespace
{
if (!isset($this->namespaces[$name])) {
$this->namespaces[$name] = new PhpNamespace($name);
foreach ($this->namespaces as $namespace) {
$namespace->setBracketedSyntax(count($this->namespaces) > 1 && isset($this->namespaces['']));
}
}
return $this->namespaces[$name];
}
/**
* @return PhpNamespace[]
*/
public function getNamespaces(): array
{
return $this->namespaces;
}
/**
* @return static
*/
public function addUse(string $name, string $alias = null): self
{
$this->addNamespace('')->addUse($name, $alias);
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);
}
}
}

View 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\PhpGenerator;
/**
* PHP literal value.
*/
class PhpLiteral
{
/** @var string */
private $value;
public function __construct(string $value)
{
$this->value = $value;
}
public function __toString(): string
{
return $this->value;
}
}

View File

@@ -0,0 +1,209 @@
<?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;
use Nette\Utils\Strings;
/**
* 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;
private const KEYWORDS = [
'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1,
'callable' => 1, 'iterable' => 1, 'void' => 1, 'self' => 1, 'parent' => 1,
];
/** @var string */
private $name;
/** @var bool */
private $bracketedSyntax = false;
/** @var string[] */
private $uses = [];
/** @var ClassType[] */
private $classes = [];
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 &$aliasOut = null): self
{
$name = ltrim($name, '\\');
if ($alias === null && $this->name === Helpers::extractNamespace($name)) {
$alias = Helpers::extractShortName($name);
}
if ($alias === null) {
$path = explode('\\', $name);
$counter = null;
do {
if (empty($path)) {
$counter++;
} else {
$alias = array_pop($path) . $alias;
}
} while (isset($this->uses[$alias . $counter]) && $this->uses[$alias . $counter] !== $name);
$alias .= $counter;
} elseif (isset($this->uses[$alias]) && $this->uses[$alias] !== $name) {
throw new InvalidStateException(
"Alias '$alias' used already for '{$this->uses[$alias]}', cannot use for '{$name}'."
);
}
$aliasOut = $alias;
$this->uses[$alias] = $name;
asort($this->uses);
return $this;
}
/**
* @return string[]
*/
public function getUses(): array
{
return $this->uses;
}
public function unresolveName(string $name): string
{
if (isset(self::KEYWORDS[strtolower($name)]) || $name === '') {
return $name;
}
$name = ltrim($name, '\\');
$res = null;
$lower = strtolower($name);
foreach ($this->uses as $alias => $original) {
if (Strings::startsWith($lower . '\\', strtolower($original) . '\\')) {
$short = $alias . substr($name, strlen($original));
if (!isset($res) || strlen($res) > strlen($short)) {
$res = $short;
}
}
}
if (!$res && Strings::startsWith($lower, strtolower($this->name) . '\\')) {
return substr($name, strlen($this->name) + 1);
} else {
return $res ?: ($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.');
}
$this->addUse($this->name . '\\' . $name);
$this->classes[$name] = $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);
}
/**
* @return ClassType[]
*/
public function getClasses(): array
{
return $this->classes;
}
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);
}
}
}

View File

@@ -0,0 +1,275 @@
<?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 string */
protected $indentation = "\t";
/** @var int */
protected $linesBetweenMethods = 2;
/** @var bool */
private $resolveTypes = true;
public function printFunction(GlobalFunction $function, PhpNamespace $namespace = null): string
{
return Helpers::formatDocComment($function->getComment() . "\n")
. 'function '
. ($function->getReturnReference() ? '&' : '')
. $function->getName()
. $this->printParameters($function, $namespace)
. $this->printReturnType($function, $namespace)
. "\n{\n" . $this->indent(ltrim(rtrim($function->getBody()) . "\n")) . "}\n";
}
public function printClosure(Closure $closure): string
{
$uses = [];
foreach ($closure->getUses() as $param) {
$uses[] = ($param->isReference() ? '&' : '') . '$' . $param->getName();
}
$useStr = strlen($tmp = implode(', ', $uses)) > (new Dumper)->wrapLength && count($uses) > 1
? "\n" . $this->indentation . implode(",\n" . $this->indentation, $uses) . "\n"
: $tmp;
return 'function '
. ($closure->getReturnReference() ? '&' : '')
. $this->printParameters($closure, null)
. ($uses ? " use ($useStr)" : '')
. $this->printReturnType($closure, null)
. " {\n" . $this->indent(ltrim(rtrim($closure->getBody()) . "\n")) . '}';
}
public function printArrowFunction(Closure $closure): string
{
foreach ($closure->getUses() as $use) {
if ($use->isReference()) {
throw new Nette\InvalidArgumentException('Arrow function cannot bind variables by-reference.');
}
}
return 'fn '
. ($closure->getReturnReference() ? '&' : '')
. $this->printParameters($closure, null)
. $this->printReturnType($closure, null)
. ' => ' . trim($closure->getBody()) . ';';
}
public function printMethod(Method $method, PhpNamespace $namespace = null): string
{
$method->validate();
return Helpers::formatDocComment($method->getComment() . "\n")
. ($method->isAbstract() ? 'abstract ' : '')
. ($method->isFinal() ? 'final ' : '')
. ($method->getVisibility() ? $method->getVisibility() . ' ' : '')
. ($method->isStatic() ? 'static ' : '')
. 'function '
. ($method->getReturnReference() ? '&' : '')
. $method->getName()
. ($params = $this->printParameters($method, $namespace))
. $this->printReturnType($method, $namespace)
. ($method->isAbstract() || $method->getBody() === null
? ";\n"
: (strpos($params, "\n") === false ? "\n" : ' ')
. "{\n"
. $this->indent(ltrim(rtrim($method->getBody()) . "\n"))
. "}\n");
}
public function printClass(ClassType $class, PhpNamespace $namespace = null): string
{
$class->validate();
$resolver = $this->resolveTypes && $namespace ? [$namespace, 'unresolveName'] : function ($s) { return $s; };
$traits = [];
foreach ($class->getTraitResolutions() as $trait => $resolutions) {
$traits[] = 'use ' . $resolver($trait)
. ($resolutions ? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n" : ";\n");
}
$consts = [];
foreach ($class->getConstants() as $const) {
$def = ($const->getVisibility() ? $const->getVisibility() . ' ' : '') . 'const ' . $const->getName() . ' = ';
$consts[] = Helpers::formatDocComment((string) $const->getComment())
. $def
. $this->dump($const->getValue(), strlen($def)) . ";\n";
}
$properties = [];
foreach ($class->getProperties() as $property) {
$type = $property->getType();
$def = (($property->getVisibility() ?: 'public') . ($property->isStatic() ? ' static' : '') . ' '
. ($type ? ($property->isNullable() ? '?' : '') . ($this->resolveTypes && $namespace ? $namespace->unresolveName($type) : $type) . ' ' : '')
. '$' . $property->getName());
$properties[] = Helpers::formatDocComment((string) $property->getComment())
. $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),
implode('', $consts),
implode("\n", $properties),
($methods && $properties ? str_repeat("\n", $this->linesBetweenMethods - 1) : '')
. implode(str_repeat("\n", $this->linesBetweenMethods), $methods),
]);
return Strings::normalize(
Helpers::formatDocComment($class->getComment() . "\n")
. ($class->isAbstract() ? 'abstract ' : '')
. ($class->isFinal() ? 'final ' : '')
. ($class->getName() ? $class->getType() . ' ' . $class->getName() . ' ' : '')
. ($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
{
$name = $namespace->getName();
$uses = $this->printUses($namespace);
$classes = [];
foreach ($namespace->getClasses() as $class) {
$classes[] = $this->printClass($class, $namespace);
}
$body = ($uses ? $uses . "\n\n" : '')
. implode("\n", $classes);
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";
}
/**
* @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
{
return (new Dumper)->dump($var, $column);
}
protected function printUses(PhpNamespace $namespace): string
{
$name = $namespace->getName();
$uses = [];
foreach ($namespace->getUses() as $alias => $original) {
if ($original !== ($name ? $name . '\\' . $alias : $alias)) {
if ($alias === $original || substr($original, -(strlen($alias) + 1)) === '\\' . $alias) {
$uses[] = "use $original;";
} else {
$uses[] = "use $original as $alias;";
}
}
}
return implode("\n", $uses);
}
/**
* @param Nette\PhpGenerator\Traits\FunctionLike $function
*/
protected function printParameters($function, ?PhpNamespace $namespace): string
{
$params = [];
$list = $function->getParameters();
foreach ($list as $param) {
$variadic = $function->isVariadic() && $param === end($list);
$type = $param->getType();
$params[] = ($type ? ($param->isNullable() ? '?' : '') . ($this->resolveTypes && $namespace ? $namespace->unresolveName($type) : $type) . ' ' : '')
. ($param->isReference() ? '&' : '')
. ($variadic ? '...' : '')
. '$' . $param->getName()
. ($param->hasDefaultValue() && !$variadic ? ' = ' . $this->dump($param->getDefaultValue()) : '');
}
return strlen($tmp = implode(', ', $params)) > (new Dumper)->wrapLength && count($params) > 1
? "(\n" . $this->indentation . implode(",\n" . $this->indentation, $params) . "\n)"
: "($tmp)";
}
/**
* @param Nette\PhpGenerator\Traits\FunctionLike $function
*/
protected function printReturnType($function, ?PhpNamespace $namespace): string
{
return $function->getReturnType()
? ': ' . ($function->isReturnNullable() ? '?' : '') . ($this->resolveTypes && $namespace ? $namespace->unresolveName($function->getReturnType()) : $function->getReturnType())
: '';
}
}

View File

@@ -0,0 +1,121 @@
<?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 property description.
*
* @property mixed $value
*/
final class Property
{
use Nette\SmartObject;
use Traits\NameAware;
use Traits\VisibilityAware;
use Traits\CommentAware;
/** @var mixed */
private $value;
/** @var bool */
private $static = false;
/** @var string|null */
private $type;
/** @var bool */
private $nullable = false;
/** @var bool */
private $initialized = false;
/**
* @return static
*/
public function setValue($val): self
{
$this->value = $val;
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 $val): self
{
$this->type = $val;
return $this;
}
public function getType(): ?string
{
return $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;
}
}

View 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;
}

View 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\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;
}
}

View File

@@ -0,0 +1,196 @@
<?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;
/**
* @internal
*/
trait FunctionLike
{
/** @var string */
private $body = '';
/** @var array of name => 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
{
$this->parameters = [];
foreach ($val as $v) {
if (!$v instanceof Parameter) {
throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Parameter[].');
}
$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 $val): self
{
$this->returnType = $val;
return $this;
}
public function getReturnType(): ?string
{
return $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(PhpNamespace $val = null): self
{
trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED);
return $this;
}
}

View 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;
/**
* @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;
}
}

View File

@@ -0,0 +1,43 @@
<?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::VISIBILITY_PUBLIC, ClassType::VISIBILITY_PROTECTED, ClassType::VISIBILITY_PRIVATE, null], true)) {
throw new Nette\InvalidArgumentException('Argument must be public|protected|private.');
}
$this->visibility = $val;
return $this;
}
public function getVisibility(): ?string
{
return $this->visibility;
}
}

19
vendor/nette/utils/.phpstorm.meta.php vendored Normal file
View 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);
expectedArguments(\Nette\Utils\Strings::match(), 2, \PREG_OFFSET_CAPTURE);
expectedArguments(\Nette\Utils\Strings::matchAll(), 2, \PREG_OFFSET_CAPTURE, \PREG_SET_ORDER);

42
vendor/nette/utils/composer.json vendored Normal file
View File

@@ -0,0 +1,42 @@
{
"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", "GPL-3.0"],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"require": {
"php": ">=7.1"
},
"require-dev": {
"nette/tester": "~2.0",
"tracy/tracy": "^2.3"
},
"suggest": {
"ext-iconv": "to use Strings::webalize() and toAscii()",
"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",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
}
}

33
vendor/nette/utils/contributing.md vendored Normal file
View File

@@ -0,0 +1,33 @@
How to contribute & use the issue tracker
=========================================
Nette welcomes your contributions. There are several ways to help out:
* Create an issue on GitHub, if you have found a bug
* Write test cases for open bug issues
* Write fixes for open bug/feature issues, preferably with test cases included
* Contribute to the [documentation](https://nette.org/en/writing)
Issues
------
Please **do not use the issue tracker to ask questions**. We will be happy to help you
on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette).
A good bug report shouldn't leave others needing to chase you up for more
information. Please try to be as detailed as possible in your report.
**Feature requests** are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to *you* to make a strong
case to convince the project's developers of the merits of this feature.
Contributing
------------
If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing).
The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them.
Please do not fix whitespace, format code, or make a purely cosmetic patch.
Thanks! :heart:

60
vendor/nette/utils/license.md vendored Normal file
View 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)

41
vendor/nette/utils/readme.md vendored Normal file
View File

@@ -0,0 +1,41 @@
Nette Utility Classes
=====================
[![Downloads this Month](https://img.shields.io/packagist/dm/nette/utils.svg)](https://packagist.org/packages/nette/utils)
[![Build Status](https://travis-ci.org/nette/utils.svg?branch=master)](https://travis-ci.org/nette/utils)
[![Coverage Status](https://coveralls.io/repos/github/nette/utils/badge.svg?branch=master)](https://coveralls.io/github/nette/utils?branch=master)
[![Latest Stable Version](https://poser.pugx.org/nette/utils/v/stable)](https://github.com/nette/utils/releases)
[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/utils/blob/master/license.md)
Introduction
------------
In package nette/utils you will find a set of useful classes for everyday use:
- [Arrays](https://doc.nette.org/arrays) - manipulate arrays
- [Callback](https://doc.nette.org/callback) - PHP callbacks
- [Date and Time](https://doc.nette.org/datetime) - modify times and dates
- [Filesystem](https://doc.nette.org/filesystem) - copying, renaming, …
- [HTML elements](https://doc.nette.org/html-elements) - generate HTML
- [Images](https://doc.nette.org/images) - crop, resize, rotate images
- [JSON](https://doc.nette.org/json) - encoding and decoding
- [Generating Random Strings](https://doc.nette.org/random)
- [Pagination](https://doc.nette.org/pagination) - comfort pagination
- [Strings](https://doc.nette.org/strings) - useful text transpilers
- [SmartObject](https://doc.nette.org/smartobject) - PHP Object Enhancements
- [Validation](https://doc.nette.org/validators) - validate inputs
Documentation can be found on the [website](https://doc.nette.org/utils).
Installation
------------
The recommended way to install is via Composer:
```
composer require nette/utils
```
It requires PHP version 7.1 and supports PHP up to 7.3.

View File

@@ -0,0 +1,166 @@
<?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);
} elseif ($iterator instanceof \Traversable) {
if (!$iterator instanceof \Iterator) {
$iterator = new \IteratorIterator($iterator);
}
} else {
throw new Nette\InvalidArgumentException(sprintf('Invalid argument passed to %s; array or Traversable expected, %s given.', __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();
}
}

View File

@@ -0,0 +1,34 @@
<?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;
}
public function current()
{
return ($this->callback)(parent::current(), parent::key());
}
}

View File

@@ -0,0 +1,94 @@
<?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.
*/
class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \IteratorAggregate
{
/**
* @return static
*/
public static function from(array $arr, bool $recursive = true)
{
$obj = new static;
foreach ($arr as $key => $value) {
if ($recursive && is_array($value)) {
$obj->$key = static::from($value, true);
} else {
$obj->$key = $value;
}
}
return $obj;
}
/**
* Returns an iterator over all items.
*/
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.
*/
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.
* @return mixed
*/
public function offsetGet($key)
{
return $this->$key;
}
/**
* Determines whether a item exists.
*/
public function offsetExists($key): bool
{
return isset($this->$key);
}
/**
* Removes the element from this list.
*/
public function offsetUnset($key): void
{
unset($this->$key);
}
}

View File

@@ -0,0 +1,110 @@
<?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).
*/
class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
{
use Nette\SmartObject;
private $list = [];
/**
* Returns an iterator over all items.
*/
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
* @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 mixed
* @throws Nette\OutOfRangeException
*/
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.
*/
public function prepend($value): void
{
$first = array_slice($this->list, 0, 1);
$this->offsetSet(0, $value);
array_splice($this->list, 1, 0, $first);
}
}

300
vendor/nette/utils/src/Utils/Arrays.php vendored Normal file
View File

@@ -0,0 +1,300 @@
<?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_int, is_object, count;
/**
* Array tools library.
*/
class Arrays
{
use Nette\StaticClass;
/**
* Returns item from array or $default if item is not set.
* @param string|int|array $key one or more keys
* @return mixed
* @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
*/
public static function get(array $arr, $key, $default = null)
{
foreach (is_array($key) ? $key : [$key] as $k) {
if (is_array($arr) && array_key_exists($k, $arr)) {
$arr = $arr[$k];
} else {
if (func_num_args() < 3) {
throw new Nette\InvalidArgumentException("Missing item '$k'.");
}
return $default;
}
}
return $arr;
}
/**
* Returns reference to array item.
* @param string|int|array $key one or more keys
* @return mixed
* @throws Nette\InvalidArgumentException if traversed item is not an array
*/
public static function &getRef(array &$arr, $key)
{
foreach (is_array($key) ? $key : [$key] as $k) {
if (is_array($arr) || $arr === null) {
$arr = &$arr[$k];
} else {
throw new Nette\InvalidArgumentException('Traversed item is not an array.');
}
}
return $arr;
}
/**
* Recursively appends elements of remaining keys from the second array to the first.
*/
public static function mergeTree(array $arr1, array $arr2): array
{
$res = $arr1 + $arr2;
foreach (array_intersect_key($arr1, $arr2) as $k => $v) {
if (is_array($v) && is_array($arr2[$k])) {
$res[$k] = self::mergeTree($v, $arr2[$k]);
}
}
return $res;
}
/**
* Searches the array for a given key and returns the offset if successful.
* @return int|null offset if it is found, null otherwise
*/
public static function searchKey(array $arr, $key): ?int
{
$foo = [$key => null];
return ($tmp = array_search(key($foo), array_keys($arr), true)) === false ? null : $tmp;
}
/**
* Inserts new array before item specified by key.
*/
public static function insertBefore(array &$arr, $key, array $inserted): void
{
$offset = (int) self::searchKey($arr, $key);
$arr = array_slice($arr, 0, $offset, true) + $inserted + array_slice($arr, $offset, count($arr), true);
}
/**
* Inserts new array after item specified by key.
*/
public static function insertAfter(array &$arr, $key, array $inserted): void
{
$offset = self::searchKey($arr, $key);
$offset = $offset === null ? count($arr) : $offset + 1;
$arr = array_slice($arr, 0, $offset, true) + $inserted + array_slice($arr, $offset, count($arr), true);
}
/**
* Renames key in array.
*/
public static function renameKey(array &$arr, $oldKey, $newKey): void
{
$offset = self::searchKey($arr, $oldKey);
if ($offset !== null) {
$keys = array_keys($arr);
$keys[$offset] = $newKey;
$arr = array_combine($keys, $arr);
}
}
/**
* Returns array entries that match the pattern.
*/
public static function grep(array $arr, string $pattern, int $flags = 0): array
{
return Strings::pcre('preg_grep', [$pattern, $arr, $flags]);
}
/**
* Returns flattened array.
*/
public static function flatten(array $arr, 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($arr, $cb);
return $res;
}
/**
* Finds whether a variable is a zero-based integer indexed array.
*/
public static function isList($value): bool
{
return is_array($value) && (!$value || array_keys($value) === range(0, count($value) - 1));
}
/**
* Reformats table to associative tree. Path looks like 'field|field[]field->field=field'.
* @return array|\stdClass
*/
public static function associate(array $arr, $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 ($arr 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 to associative array.
*/
public static function normalize(array $arr, $filling = null): array
{
$res = [];
foreach ($arr as $k => $v) {
$res[is_int($k) ? $v : $k] = is_int($k) ? $filling : $v;
}
return $res;
}
/**
* Picks element from the array by key and return its value.
* @param string|int $key array key
* @return mixed
* @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
*/
public static function pick(array &$arr, $key, $default = null)
{
if (array_key_exists($key, $arr)) {
$value = $arr[$key];
unset($arr[$key]);
return $value;
} elseif (func_num_args() < 3) {
throw new Nette\InvalidArgumentException("Missing item '$key'.");
} else {
return $default;
}
}
/**
* Tests whether some element in the array passes the callback test.
*/
public static function some(array $arr, callable $callback): bool
{
foreach ($arr as $k => $v) {
if ($callback($v, $k, $arr)) {
return true;
}
}
return false;
}
/**
* Tests whether all elements in the array pass the callback test.
*/
public static function every(array $arr, callable $callback): bool
{
foreach ($arr as $k => $v) {
if (!$callback($v, $k, $arr)) {
return false;
}
}
return true;
}
/**
* Applies the callback to the elements of the array.
*/
public static function map(array $arr, callable $callback): array
{
$res = [];
foreach ($arr as $k => $v) {
$res[$k] = $callback($v, $k, $arr);
}
return $res;
}
/**
* Converts array to object
* @param object $obj
* @return object
*/
public static function toObject(array $arr, $obj)
{
foreach ($arr as $k => $v) {
$obj->$k = $v;
}
return $obj;
}
}

View File

@@ -0,0 +1,164 @@
<?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
{
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 = $message;
if (ini_get('html_errors')) {
$msg = html_entity_decode(strip_tags($msg));
}
$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();
}
}
/**
* @return callable
*/
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;
}
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;
}
}
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);
}
}
public static function isStatic(callable $callable): bool
{
return is_array($callable) ? is_string($callable[0]) : is_string($callable);
}
/**
* Unwraps closure created by Closure::fromCallable()
* @internal
*/
public static function unwrap(\Closure $closure): callable
{
$r = new \ReflectionFunction($closure);
if (substr($r->getName(), -1) === '}') {
return $closure;
} elseif ($obj = $r->getClosureThis()) {
return [$obj, $r->getName()];
} elseif ($class = $r->getClosureScopeClass()) {
return [$class->getName(), $r->getName()];
} else {
return $r->getName();
}
}
}

View File

@@ -0,0 +1,124 @@
<?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;
/**
* DateTime object factory.
* @param string|int|\DateTimeInterface $time
* @return static
*/
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($time);
}
}
/**
* Creates DateTime object.
* @return static
*/
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
*/
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');
}
public function __toString(): string
{
return $this->format('Y-m-d H:i:s');
}
/**
* @return static
*/
public function modifyClone(string $modify = '')
{
$dolly = clone $this;
return $modify ? $dolly->modify($modify) : $dolly;
}
}

View File

@@ -0,0 +1,159 @@
<?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.
* @throws Nette\IOException
*/
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("Unable to create directory '$dir'. " . self::getLastError());
}
}
/**
* Copies a file or directory.
* @throws Nette\IOException
*/
public static function copy(string $source, string $dest, bool $overwrite = true): void
{
if (stream_is_local($source) && !file_exists($source)) {
throw new Nette\IOException("File or directory '$source' not found.");
} elseif (!$overwrite && file_exists($dest)) {
throw new Nette\InvalidStateException("File or directory '$dest' already exists.");
} elseif (is_dir($source)) {
static::createDir($dest);
foreach (new \FilesystemIterator($dest) as $item) {
static::delete($item->getPathname());
}
foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) {
if ($item->isDir()) {
static::createDir($dest . '/' . $iterator->getSubPathName());
} else {
static::copy($item->getPathname(), $dest . '/' . $iterator->getSubPathName());
}
}
} else {
static::createDir(dirname($dest));
if (($s = @fopen($source, 'rb')) && ($d = @fopen($dest, 'wb')) && @stream_copy_to_stream($s, $d) === false) { // @ is escalated to exception
throw new Nette\IOException("Unable to copy file '$source' to '$dest'. " . self::getLastError());
}
}
}
/**
* Deletes a file or directory.
* @throws Nette\IOException
*/
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("Unable to delete '$path'. " . self::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("Unable to delete directory '$path'. " . self::getLastError());
}
}
}
/**
* Renames a file or directory.
* @throws Nette\IOException
* @throws Nette\InvalidStateException if the target file or directory already exist
*/
public static function rename(string $name, string $newName, bool $overwrite = true): void
{
if (!$overwrite && file_exists($newName)) {
throw new Nette\InvalidStateException("File or directory '$newName' already exists.");
} elseif (!file_exists($name)) {
throw new Nette\IOException("File or directory '$name' not found.");
} else {
static::createDir(dirname($newName));
if (realpath($name) !== realpath($newName)) {
static::delete($newName);
}
if (!@rename($name, $newName)) { // @ is escalated to exception
throw new Nette\IOException("Unable to rename file or directory '$name' to '$newName'. " . self::getLastError());
}
}
}
/**
* Reads file content.
* @throws Nette\IOException
*/
public static function read(string $file): string
{
$content = @file_get_contents($file); // @ is escalated to exception
if ($content === false) {
throw new Nette\IOException("Unable to read file '$file'. " . self::getLastError());
}
return $content;
}
/**
* Writes a string to a file.
* @throws Nette\IOException
*/
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("Unable to write file '$file'. " . self::getLastError());
}
if ($mode !== null && !@chmod($file, $mode)) { // @ is escalated to exception
throw new Nette\IOException("Unable to chmod file '$file'. " . self::getLastError());
}
}
/**
* Is path absolute?
*/
public static function isAbsolute(string $path): bool
{
return (bool) preg_match('#([a-z]:)?[/\\\\]|[a-z][a-z0-9+.-]*://#Ai', $path);
}
private static function getLastError(): string
{
return preg_replace('#^\w+\(.*?\): #', '', error_get_last()['message']);
}
}

614
vendor/nette/utils/src/Utils/Html.php vendored Normal file
View File

@@ -0,0 +1,614 @@
<?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_float, is_object, is_string;
/**
* HTML helper.
*
* <code>
* $el = Html::el('a')->href($link)->setText('Nette');
* $el->class = 'myclass';
* echo $el;
*
* echo $el->startTag(), $el->endTag();
* </code>
*/
class Html implements \ArrayAccess, \Countable, \IteratorAggregate, IHtmlString
{
use Nette\SmartObject;
/** @var array element's attributes */
public $attrs = [];
/** @var bool use XHTML syntax? */
public static $xhtml = false;
/** @var array empty (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 of Html | string nodes */
protected $children = [];
/** @var string element's name */
private $name;
/** @var bool is element empty? */
private $isEmpty;
/**
* Static factory.
* @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;
}
/**
* 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.
* @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.
* @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.
*/
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'.
* @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 IHtmlString|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 IHtmlString|string|int|float $text
* @return static
*/
final public function setText($text)
{
if (!$text instanceof IHtmlString) {
$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 html_entity_decode(strip_tags($this->getHtml()), ENT_QUOTES, 'UTF-8');
}
/**
* Adds new element's child.
* @param IHtmlString|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 IHtmlString|string|int|float $text
* @return static
*/
public function addText($text)
{
if (!$text instanceof IHtmlString) {
$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 IHtmlString|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 static|string
*/
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.
*/
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, '<'],
['&amp;', $q === '"' ? '&quot;' : '&#39;', self::$xhtml ? '&lt;' : '<'],
$value
)
. (strpos($value, '`') !== false && strpbrk($value, ' <>"\'') === false ? ' ' : '')
. $q;
}
$s = str_replace('@', '&#64;', $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;
}
}
}
}

View File

@@ -0,0 +1,20 @@
<?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;
interface IHtmlString
{
/**
* Returns string in HTML format
*/
function __toString(): string;
}

View 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\Localization;
/**
* Translator adapter.
*/
interface ITranslator
{
/**
* Translates the given string.
*/
function translate($message, ...$parameters): string;
}

623
vendor/nette/utils/src/Utils/Image.php vendored Normal file
View File

@@ -0,0 +1,623 @@
<?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.
*
* <code>
* $image = Image::fromFile('nette.jpg');
* $image->resize(150, 100);
* $image->sharpen();
* $image->send();
* </code>
*
* @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 dashedLine($x1, $y1, $x2, $y2, $color)
* @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 int interlace($interlace = null)
* @method bool isTrueColor()
* @method void layerEffect($effect)
* @method void line($x1, $y1, $x2, $y2, $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 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 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 int $width
* @property-read int $height
* @property-read resource $imageResource
*/
class Image
{
use Nette\SmartObject;
/** {@link resize()} only shrinks images */
public const SHRINK_ONLY = 0b0001;
/** {@link resize()} will ignore aspect ratio */
public const STRETCH = 0b0010;
/** {@link resize()} fits in given area so its dimensions are less than or equal to the required dimensions */
public const FIT = 0b0000;
/** {@link resize()} fills given area so its dimensions are greater than or equal to the required dimensions */
public const FILL = 0b0100;
/** {@link resize()} fills given area exactly */
public const EXACT = 0b1000;
/** image types */
public const
JPEG = IMAGETYPE_JPEG,
PNG = IMAGETYPE_PNG,
GIF = IMAGETYPE_GIF,
WEBP = 18; // IMAGETYPE_WEBP is available as of PHP 7.1
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'];
/** @var resource */
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)),
];
}
/**
* Opens image from file.
* @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 &$detectedFormat = null)
{
if (!extension_loaded('gd')) {
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
}
$detectedFormat = @getimagesize($file)[2]; // @ - files smaller than 12 bytes causes read error
if (!isset(self::FORMATS[$detectedFormat])) {
$detectedFormat = null;
throw new UnknownImageFileException(is_file($file) ? "Unknown type of file '$file'." : "File '$file' not found.");
}
return new static(Callback::invokeSafe('imagecreatefrom' . image_type_to_extension($detectedFormat, false), [$file], function (string $message): void {
throw new ImageException($message);
}));
}
/**
* Create a new image from the image stream in the string.
* @return static
* @throws ImageException
*/
public static function fromString(string $s, int &$detectedFormat = null)
{
if (!extension_loaded('gd')) {
throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
}
if (func_num_args() > 1) {
$tmp = @getimagesizefromstring($s)[2]; // @ - strings smaller than 12 bytes causes read error
$detectedFormat = isset(self::FORMATS[$tmp]) ? $tmp : null;
}
return new static(Callback::invokeSafe('imagecreatefromstring', [$s], function (string $message): void {
throw new ImageException($message);
}));
}
/**
* Creates blank image.
* @return static
*/
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);
}
/**
* Wraps GD image.
* @param resource $image
*/
public function __construct($image)
{
$this->setImageResource($image);
imagesavealpha($image, true);
}
/**
* Returns image width.
*/
public function getWidth(): int
{
return imagesx($this->image);
}
/**
* Returns image height.
*/
public function getHeight(): int
{
return imagesy($this->image);
}
/**
* Sets image resource.
* @param resource $image
* @return static
*/
protected function setImageResource($image)
{
if (!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
*/
public function getImageResource()
{
return $this->image;
}
/**
* Resizes image.
* @param int|string $width in pixels or percent
* @param int|string $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 $newWidth in pixels or percent
* @param int|string $newHeight in pixels or percent
*/
public static function calculateSize(int $srcWidth, int $srcHeight, $newWidth, $newHeight, int $flags = self::FIT): array
{
if (is_string($newWidth) && substr($newWidth, -1) === '%') {
$newWidth = (int) round($srcWidth / 100 * abs(substr($newWidth, 0, -1)));
$percents = true;
} else {
$newWidth = (int) abs($newWidth);
}
if (is_string($newHeight) && substr($newHeight, -1) === '%') {
$newHeight = (int) round($srcHeight / 100 * abs(substr($newHeight, 0, -1)));
$flags |= empty($percents) ? 0 : self::STRETCH;
} else {
$newHeight = (int) abs($newHeight);
}
if ($flags & self::STRETCH) { // non-proportional
if (empty($newWidth) || empty($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 (empty($newWidth) && empty($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);
$this->image = imagecrop($this->image, $r);
imagesavealpha($this->image, true);
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 (is_string($newWidth) && substr($newWidth, -1) === '%') {
$newWidth = (int) round($srcWidth / 100 * substr($newWidth, 0, -1));
}
if (is_string($newHeight) && substr($newHeight, -1) === '%') {
$newHeight = (int) round($srcHeight / 100 * substr($newHeight, 0, -1));
}
if (is_string($left) && substr($left, -1) === '%') {
$left = (int) round(($srcWidth - $newWidth) / 100 * substr($left, 0, -1));
}
if (is_string($top) && substr($top, -1) === '%') {
$top = (int) round(($srcHeight - $newHeight) / 100 * substr($top, 0, -1));
}
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];
}
/**
* Sharpen image.
* @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 $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 (is_string($left) && substr($left, -1) === '%') {
$left = (int) round(($this->getWidth() - $width) / 100 * substr($left, 0, -1));
}
if (is_string($top) && substr($top, -1) === '%') {
$top = (int) round(($this->getHeight() - $height) / 100 * substr($top, 0, -1));
}
$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 0..100 for JPEG and WEBP, 0..9 for PNG.
* @throws ImageException
*/
public function save(string $file, int $quality = null, int $type = null): void
{
if ($type === null) {
$extensions = array_flip(self::FORMATS) + ['jpg' => self::JPEG];
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
if (!isset($extensions[$ext])) {
throw new Nette\InvalidArgumentException("Unsupported file extension '$ext'.");
}
$type = $extensions[$ext];
}
$this->output($type, $quality, $file);
}
/**
* Outputs image to string. Quality is 0..100 for JPEG and WEBP, 0..9 for PNG.
*/
public function toString(int $type = self::JPEG, int $quality = null): string
{
ob_start(function () {});
$this->output($type, $quality);
return ob_get_clean();
}
/**
* 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 0..100 for JPEG and WEBP, 0..9 for PNG.
* @throws ImageException
*/
public function send(int $type = self::JPEG, int $quality = null): void
{
if (!isset(self::FORMATS[$type])) {
throw new Nette\InvalidArgumentException("Unsupported image type '$type'.");
}
header('Content-Type: ' . image_type_to_mime_type($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);
break;
case self::PNG:
$quality = $quality === null ? 9 : max(0, min(9, $quality));
$success = imagepng($this->image, $file, $quality);
break;
case self::GIF:
$success = imagegif($this->image, $file);
break;
case self::WEBP:
$quality = $quality === null ? 80 : max(0, min(100, $quality));
$success = imagewebp($this->image, $file, $quality);
break;
default:
throw new Nette\InvalidArgumentException("Unsupported image type '$type'.");
}
if (!$success) {
throw new ImageException(error_get_last()['message'] ?: '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(get_class($this), $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 is_resource($res) && get_resource_type($res) === 'gd' ? $this->setImageResource($res) : $res;
}
public function __clone()
{
ob_start(function () {});
imagegd2($this->image);
$this->setImageResource(imagecreatefromstring(ob_get_clean()));
}
/**
* Prevents serialization.
*/
public function __sleep(): array
{
throw new Nette\NotSupportedException('You cannot serialize or unserialize ' . self::class . ' instances.');
}
}

60
vendor/nette/utils/src/Utils/Json.php vendored Normal file
View File

@@ -0,0 +1,60 @@
<?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 = 0b0001;
public const PRETTY = 0b0010;
public const ESCAPE_UNICODE = 0b0100;
/**
* Returns the JSON representation of a value. Accepts flag Json::PRETTY.
*/
public static function encode($value, int $flags = 0): string
{
$flags = ($flags & self::ESCAPE_UNICODE ? 0 : JSON_UNESCAPED_UNICODE)
| JSON_UNESCAPED_SLASHES
| ($flags & self::PRETTY ? JSON_PRETTY_PRINT : 0)
| (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;
}
/**
* Decodes a JSON string. Accepts flag Json::FORCE_ARRAY.
* @return mixed
*/
public static function decode(string $json, int $flags = 0)
{
$forceArray = (bool) ($flags & self::FORCE_ARRAY);
$value = json_decode($json, $forceArray, 512, JSON_BIGINT_AS_STRING);
if ($error = json_last_error()) {
throw new JsonException(json_last_error_msg(), $error);
}
return $value;
}
}

View File

@@ -0,0 +1,186 @@
<?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.
*/
final class ObjectHelpers
{
use Nette\StaticClass;
/**
* @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?" : '.'));
}
/**
* @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?" : '.'));
}
/**
* @throws MemberAccessException
*/
public static function strictCall(string $class, string $method, array $additionalMethods = []): void
{
$hint = self::getSuggestion(array_merge(
get_class_methods($class),
self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:\S+[ \t]+)??(\w+)\(~m'),
$additionalMethods
), $method);
if (method_exists($class, $method)) { // called parent::$method()
$class = 'parent';
}
throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
}
/**
* @throws MemberAccessException
*/
public static function strictStaticCall(string $class, string $method): void
{
$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) [ \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)) && $rm->getName() === $nm && !$rm->isPrivate() && !$rm->isStatic();
$read = $type !== '-write'
&& ($rc->hasMethod($nm = 'get' . $uname) || $rc->hasMethod($nm = 'is' . $uname))
&& ($rm = $rc->getMethod($nm)) && $rm->getName() === $nm && !$rm->isPrivate() && !$rm->isStatic();
if ($read || $write) {
$props[$name] = $read << 0 | ($nm[0] === 'g') << 1 | $rm->returnsReference() << 2 | $write << 3;
}
}
foreach ($rc->getTraits() as $trait) {
$props += self::getMagicProperties($trait->getName());
}
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->getName() : $item;
if ($item !== $value && (
($len = levenshtein($item, $value, 10, 11, 10)) < $min
|| ($len = levenshtein(preg_replace($re, '', $item), $norm, 10, 11, 10) + 20) < $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;
}
}

View File

@@ -0,0 +1,43 @@
<?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($class, $name, $callback)
{
trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
}
public static function getExtensionMethod($class, $name)
{
trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
}
}

View File

@@ -0,0 +1,212 @@
<?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 int $base
* @property-read bool $first
* @property-read bool $last
* @property-read int|null $pageCount
* @property int $itemsPerPage
* @property int|null $itemCount
* @property-read int $offset
* @property-read int|null $countdownOffset
* @property-read int $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);
}
/**
* 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.
*/
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.
*/
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.
*/
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.
*/
public function getItemCount(): ?int
{
return $this->itemCount;
}
/**
* Returns the absolute index of the first item on current page.
*/
public function getOffset(): int
{
return $this->getPageIndex() * $this->itemsPerPage;
}
/**
* Returns the absolute index of the first item on current page in countdown paging.
*/
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.
*/
public function getLength(): int
{
return $this->itemCount === null
? $this->itemsPerPage
: min($this->itemsPerPage, $this->itemCount - $this->getPageIndex() * $this->itemsPerPage);
}
}

44
vendor/nette/utils/src/Utils/Random.php vendored Normal file
View File

@@ -0,0 +1,44 @@
<?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;
/**
* Generate random string.
*/
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;
}
}

View File

@@ -0,0 +1,303 @@
<?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;
private const BUILTIN_TYPES = [
'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1,
'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1,
];
public static function isBuiltinType(string $type): bool
{
return isset(self::BUILTIN_TYPES[strtolower($type)]);
}
public static function getReturnType(\ReflectionFunctionAbstract $func): ?string
{
return $func->hasReturnType()
? self::normalizeType($func->getReturnType()->getName(), $func)
: null;
}
public static function getParameterType(\ReflectionParameter $param): ?string
{
return $param->hasType()
? self::normalizeType($param->getType()->getName(), $param)
: null;
}
public static function getPropertyType(\ReflectionProperty $prop): ?string
{
return PHP_VERSION_ID >= 70400 && $prop->hasType()
? self::normalizeType($prop->getType()->getName(), $prop)
: null;
}
private static function normalizeType(string $type, $reflection): string
{
$lower = strtolower($type);
if ($lower === 'self') {
return $reflection->getDeclaringClass()->getName();
} elseif ($lower === 'parent' && $reflection->getDeclaringClass()->getParentClass()) {
return $reflection->getDeclaringClass()->getParentClass()->getName();
} else {
return $type;
}
}
/**
* @return mixed
* @throws \ReflectionException when default value is not available or resolvable
*/
public static function getParameterDefaultValue(\ReflectionParameter $param)
{
if ($param->isDefaultValueConstant()) {
$const = $orig = $param->getDefaultValueConstantName();
$pair = explode('::', $const);
if (isset($pair[1])) {
if (strtolower($pair[0]) === 'self') {
$pair[0] = $param->getDeclaringClass()->getName();
}
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 declaring class or trait.
*/
public static function getPropertyDeclaringClass(\ReflectionProperty $prop): \ReflectionClass
{
foreach ($prop->getDeclaringClass()->getTraits() as $trait) {
if ($trait->hasProperty($prop->getName())
&& $trait->getProperty($prop->getName())->getDocComment() === $prop->getDocComment()
) {
return self::getPropertyDeclaringClass($trait->getProperty($prop->getName()));
}
}
return $prop->getDeclaringClass();
}
/**
* Are documentation comments available?
*/
public static function areCommentsAvailable(): bool
{
static $res;
return $res === null
? $res = (bool) (new \ReflectionMethod(__METHOD__))->getDocComment()
: $res;
}
public static function toString(\Reflector $ref): string
{
if ($ref instanceof \ReflectionClass) {
return $ref->getName();
} elseif ($ref instanceof \ReflectionMethod) {
return $ref->getDeclaringClass()->getName() . '::' . $ref->getName();
} elseif ($ref instanceof \ReflectionFunction) {
return $ref->getName();
} elseif ($ref instanceof \ReflectionProperty) {
return self::getPropertyDeclaringClass($ref)->getName() . '::$' . $ref->getName();
} elseif ($ref instanceof \ReflectionParameter) {
return '$' . $ref->getName() . ' in ' . self::toString($ref->getDeclaringFunction()) . '()';
} else {
throw new Nette\InvalidArgumentException;
}
}
/**
* Expands class name into full name.
* @throws Nette\InvalidArgumentException
*/
public static function expandClassName(string $name, \ReflectionClass $rc): string
{
$lower = strtolower($name);
if (empty($name)) {
throw new Nette\InvalidArgumentException('Class name must not be empty.');
} elseif (isset(self::BUILTIN_TYPES[$lower])) {
return $lower;
} elseif ($lower === 'self') {
return $rc->getName();
} elseif ($name[0] === '\\') { // fully qualified name
return ltrim($name, '\\');
}
$uses = self::getUseStatements($rc);
$parts = explode('\\', $name, 2);
if (isset($uses[$parts[0]])) {
$parts[0] = $uses[$parts[0]];
return implode('\\', $parts);
} elseif ($rc->inNamespace()) {
return $rc->getNamespaceName() . '\\' . $name;
} else {
return $name;
}
}
/**
* @return array 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->getName()])) {
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 = $classLevel = $level = null;
$res = $uses = [];
while ($token = current($tokens)) {
next($tokens);
switch (is_array($token) ? $token[0] : $token) {
case T_NAMESPACE:
$namespace = ltrim(self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]) . '\\', '\\');
$uses = [];
break;
case T_CLASS:
case T_INTERFACE:
case T_TRAIT:
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, [T_STRING, T_NS_SEPARATOR]))) {
$name = ltrim($name, '\\');
if (self::fetch($tokens, '{')) {
while ($suffix = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR])) {
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 = null;
}
$level--;
}
}
return $res;
}
private static function fetch(&$tokens, $take)
{
$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;
}
}

View File

@@ -0,0 +1,121 @@
<?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 = get_class($this);
if (ObjectHelpers::hasProperty($class, $name) === 'event') { // calling event handlers
if (is_iterable($this->$name)) {
foreach ($this->$name as $handler) {
$handler(...$args);
}
} elseif ($this->$name !== null) {
throw new UnexpectedValueException("Property $class::$$name must be iterable or null, " . gettype($this->$name) . ' 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 = get_class($this);
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') . $name;
if ($prop & 0b0100) { // return by reference
return $this->$m();
} else {
$val = $this->$m();
return $val;
}
} else {
ObjectHelpers::strictGet($class, $name);
}
}
/**
* @return void
* @throws MemberAccessException if the property is not defined or is read-only
*/
public function __set(string $name, $value)
{
$class = get_class($this);
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.");
}
$this->{'set' . $name}($value);
} else {
ObjectHelpers::strictSet($class, $name);
}
}
/**
* @return void
* @throws MemberAccessException
*/
public function __unset(string $name)
{
$class = get_class($this);
if (!ObjectHelpers::hasProperty($class, $name)) {
throw new MemberAccessException("Cannot unset the property $class::\$$name.");
}
}
public function __isset(string $name): bool
{
return isset(ObjectHelpers::getMagicProperties(get_class($this))[$name]);
}
}

View File

@@ -0,0 +1,36 @@
<?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
{
/**
* @throws \Error
*/
final public function __construct()
{
throw new \Error('Class ' . get_class($this) . ' is static and cannot be instantiated.');
}
/**
* Call to undefined static method.
* @throws MemberAccessException
*/
public static function __callStatic(string $name, array $args)
{
Utils\ObjectHelpers::strictStaticCall(static::class, $name);
}
}

508
vendor/nette/utils/src/Utils/Strings.php vendored Normal file
View File

@@ -0,0 +1,508 @@
<?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, 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 for UTF-8 encoding.
*/
public static function checkEncoding(string $s): bool
{
return $s === self::fixEncoding($s);
}
/**
* Removes invalid code unit sequences from UTF-8 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 (0x0 to 0xD7FF or 0xE000 to 0x10FFFF).
* @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.');
}
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.
*/
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 ($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 special controls characters and normalizes line endings, spaces and normal form to NFC in UTF-8 string.
*/
public static function normalize(string $s): string
{
// convert to compressed normal form (NFC)
if (class_exists('Normalizer', false)) {
$s = \Normalizer::normalize($s, \Normalizer::FORM_C);
}
$s = self::normalizeNewLines($s);
// remove control characters; leave \t + \n
$s = preg_replace('#[\x00-\x08\x0B-\x1F\x7F-\x9F]+#u', '', $s);
// right trim
$s = 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.
*/
public static function toAscii(string $s): string
{
static $transliterator = null;
if ($transliterator === null && class_exists('Transliterator', false)) {
$transliterator = \Transliterator::create('Any-Latin; Latin-ASCII');
}
$s = preg_replace('#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{2FF}\x{370}-\x{10FFFF}]#u', '', $s);
$s = strtr($s, '`\'"^~?', "\x01\x02\x03\x04\x05\x06");
$s = str_replace(
["\u{201E}", "\u{201C}", "\u{201D}", "\u{201A}", "\u{2018}", "\u{2019}", "\u{B0}"],
["\x03", "\x03", "\x03", "\x02", "\x02", "\x02", "\x04"], $s
);
if ($transliterator !== null) {
$s = $transliterator->transliterate($s);
}
if (ICONV_IMPL === 'glibc') {
$s = str_replace(
["\u{BB}", "\u{AB}", "\u{2026}", "\u{2122}", "\u{A9}", "\u{AE}"],
['>>', '<<', '...', 'TM', '(c)', '(R)'], $s
);
$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 = preg_replace('#[^\x00-\x7F]++#', '', $s);
} else {
$s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
}
$s = str_replace(['`', "'", '"', '^', '~', '?'], '', $s);
return strtr($s, "\x01\x02\x03\x04\x05\x06", '`\'"^~?');
}
/**
* Converts UTF-8 string to web safe characters [a-z0-9-] text.
*/
public static function webalize(string $s, string $charlist = null, bool $lower = true): string
{
$s = self::toAscii($s);
if ($lower) {
$s = strtolower($s);
}
$s = preg_replace('#[^a-z0-9' . ($charlist !== null ? preg_quote($charlist, '#') : '') . ']+#i', '-', $s);
$s = trim($s, '-');
return $s;
}
/**
* Truncates UTF-8 string to maximal length.
*/
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 UTF-8 string from the left.
*/
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 UTF-8 string to lower case.
*/
public static function lower(string $s): string
{
return mb_strtolower($s, 'UTF-8');
}
/**
* Converts first character to lower case.
*/
public static function firstLower(string $s): string
{
return self::lower(self::substring($s, 0, 1)) . self::substring($s, 1);
}
/**
* Converts UTF-8 string to upper case.
*/
public static function upper(string $s): string
{
return mb_strtoupper($s, 'UTF-8');
}
/**
* Converts first character to upper case.
*/
public static function firstUpper(string $s): string
{
return self::upper(self::substring($s, 0, 1)) . self::substring($s, 1);
}
/**
* Capitalizes UTF-8 string.
*/
public static function capitalize(string $s): string
{
return mb_convert_case($s, MB_CASE_TITLE, 'UTF-8');
}
/**
* Case-insensitive compares UTF-8 strings.
*/
public static function compare(string $left, string $right, int $len = 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 ($len < 0) {
$left = self::substring($left, $len, -$len);
$right = self::substring($right, $len, -$len);
} elseif ($len !== null) {
$left = self::substring($left, 0, $len);
$right = self::substring($right, 0, $len);
}
return self::lower($left) === self::lower($right);
}
/**
* Finds the length of common prefix of strings.
* @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));
}
/**
* Strips whitespace from UTF-8 string.
*/
public static function trim(string $s, string $charlist = self::TRIM_CHARACTERS): string
{
$charlist = preg_quote($charlist, '#');
return self::replace($s, '#^[' . $charlist . ']+|[' . $charlist . ']+$#Du', '');
}
/**
* Pad a UTF-8 string to a certain length with another string.
*/
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;
}
/**
* Pad a UTF-8 string to a certain length with another string.
*/
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);
}
/**
* Reverse string.
*/
public static function reverse(string $s): string
{
return iconv('UTF-32LE', 'UTF-8', strrev(iconv('UTF-8', 'UTF-32BE', $s)));
}
/**
* Returns part of $haystack before $nth occurence of $needle (negative value means searching from the end).
* @return string|null returns null if the needle was not found
*/
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 (negative value means searching from the end).
* @return string|null returns null if the needle was not found
*/
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 of $nth occurence of $needle in $haystack (negative value means searching from the end).
* @return int|null offset in characters or null if the needle was not found
*/
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 of $nth occurence of $needle in $haystack.
* @return int|null offset in bytes 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;
}
$pos = $len - 1;
while (($pos = strrpos($haystack, $needle, $pos - $len)) !== false && ++$nth) {
$pos--;
}
}
return $pos === false ? null : $pos;
}
/**
* Splits string by a regular expression.
*/
public static function split(string $subject, string $pattern, int $flags = 0): array
{
return self::pcre('preg_split', [$pattern, $subject, -1, $flags | PREG_SPLIT_DELIM_CAPTURE]);
}
/**
* Performs a regular expression match. Accepts flag PREG_OFFSET_CAPTURE (returned in bytes).
*/
public static function match(string $subject, 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;
}
/**
* Performs a global regular expression match. Accepts flag PREG_OFFSET_CAPTURE (returned in bytes), PREG_SET_ORDER is default.
*/
public static function matchAll(string $subject, 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;
}
/**
* Perform a regular expression search and replace.
* @param string|array $pattern
* @param string|callable $replacement
*/
public static function replace(string $subject, $pattern, $replacement = null, 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 ($replacement === null && is_array($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;
}
}

View File

@@ -0,0 +1,344 @@
<?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;
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' => [__CLASS__, 'isCallable'],
'iterable' => 'is_iterable',
'list' => [Arrays::class, 'isList'],
'mixed' => [__CLASS__, 'isMixed'],
'none' => [__CLASS__, 'isNone'],
'number' => [__CLASS__, 'isNumber'],
'numeric' => [__CLASS__, 'isNumeric'],
'numericint' => [__CLASS__, 'isNumericInt'],
// string patterns
'alnum' => 'ctype_alnum',
'alpha' => 'ctype_alpha',
'digit' => 'ctype_digit',
'lower' => 'ctype_lower',
'pattern' => null,
'space' => 'ctype_space',
'unicode' => [__CLASS__, 'isUnicode'],
'upper' => 'ctype_upper',
'xdigit' => 'ctype_xdigit',
// syntax validation
'email' => [__CLASS__, 'isEmail'],
'identifier' => [__CLASS__, 'isPhpIdentifier'],
'uri' => [__CLASS__, 'isUri'],
'url' => [__CLASS__, 'isUrl'],
// environment validation
'class' => 'class_exists',
'interface' => 'interface_exists',
'directory' => 'is_dir',
'file' => 'is_file',
'type' => [__CLASS__, 'isType'],
];
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',
];
/**
* Throws exception if a variable is of unexpected type (separated by pipe).
*/
public static function assert($value, string $expected, string $label = 'variable'): void
{
if (!static::is($value, $expected)) {
$expected = str_replace(['|', ':'], [' or ', ' in range '], $expected);
static $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.");
}
}
/**
* Throws exception if an array field is missing or of unexpected type (separated by pipe).
*/
public static function assertField(array $arr, $field, string $expected = null, string $label = "item '%' in array"): void
{
if (!array_key_exists($field, $arr)) {
throw new AssertionException('Missing ' . str_replace('%', $field, $label) . '.');
} elseif ($expected) {
static::assert($arr[$field], $expected, str_replace('%', $field, $label));
}
}
/**
* Finds whether a variable is of expected type (separated by pipe).
*/
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 (preg_match('|^' . ($item[1] ?? '') . '$|D', $value)) {
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 type (separated by pipe).
*/
public static function everyIs(iterable $values, string $expected): bool
{
foreach ($values as $value) {
if (!static::is($value, $expected)) {
return false;
}
}
return true;
}
/**
* Finds whether a value is an integer or a float.
*/
public static function isNumber($value): bool
{
return is_int($value) || is_float($value);
}
/**
* Finds whether a value is an integer.
*/
public static function isNumericInt($value): bool
{
return is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]+$#D', $value));
}
/**
* Finds whether a string is a floating point number in decimal base.
*/
public static function isNumeric($value): bool
{
return is_float($value) || is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]*[.]?[0-9]+$#D', $value));
}
/**
* Finds whether a value is a syntactically correct callback.
*/
public static function isCallable($value): bool
{
return $value && is_callable($value, true);
}
/**
* Finds whether a value is an UTF-8 encoded string.
*/
public static function isUnicode($value): bool
{
return is_string($value) && preg_match('##u', $value);
}
/**
* Finds whether a value is "falsy".
*/
public static function isNone($value): bool
{
return $value == null; // intentionally ==
}
/** @internal */
public static function isMixed(): bool
{
return true;
}
/**
* Finds whether a variable is a zero-based integer indexed array.
*/
public static function isList($value): bool
{
return Arrays::isList($value);
}
/**
* Is a value in specified min and max value pair?
*/
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]));
}
/**
* Finds whether a string is a valid email address.
*/
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("(^
(\"([ !#-[\\]-~]*|\\\\[ -~])+\"|$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", $value);
}
/**
* Finds whether a string is a valid http(s) URL.
*/
public static function isUrl(string $value): bool
{
$alpha = "a-z\x80-\xFF";
return (bool) preg_match("(^
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", $value);
}
/**
* Finds whether a string is a valid URI according to RFC 1738.
*/
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 is_string($value) && preg_match('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#D', $value);
}
}

View File

@@ -0,0 +1,159 @@
<?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
{
}
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
{
}