chore: 添加缺失的静态资源文件

添加多个前端界面所需的图片、字体、音频等静态资源文件,包括:
- 管理员后台的图标、背景图、组件图片
- 移动端页面的商品图片、用户中心图片、活动图标
- 第三方库的示例图片和字体文件
- 浏览器兼容性配置文件
- 图标字体文件(eot、ttf、woff、woff2格式)

这些资源是前端界面正常显示所必需的,之前可能因版本控制忽略或遗漏而缺失。
This commit is contained in:
2026-03-06 20:15:52 +08:00
parent c9a98f342c
commit 0060b2798f
7739 changed files with 1314791 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
{
"name": "nette/php-generator",
"description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.1 features.",
"keywords": ["nette", "php", "code", "scaffolding"],
"homepage": "https://nette.org",
"license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"require": {
"php": ">=7.2 <8.3",
"nette/utils": "^3.1.2"
},
"require-dev": {
"nette/tester": "^2.4",
"nikic/php-parser": "^4.13",
"tracy/tracy": "^2.8",
"phpstan/phpstan": "^0.12"
},
"suggest": {
"nikic/php-parser": "to use ClassType::withBodiesFrom() & GlobalFunction::withBodyFrom()"
},
"autoload": {
"classmap": ["src/"]
},
"minimum-stability": "dev",
"scripts": {
"phpstan": "phpstan analyse",
"tester": "tester tests -s"
},
"extra": {
"branch-alias": {
"dev-master": "3.6-dev"
}
}
}

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

14
vendor/nette/php-generator/ncs.php vendored Normal file
View File

@@ -0,0 +1,14 @@
<?php
/**
* Rules for Nette Coding Standard
* https://github.com/nette/coding-standard
*/
declare(strict_types=1);
return [
// constant NULL, FALSE in src/PhpGenerator/Type.php
'constant_case' => false,
'lowercase_static_reference' => false,
];

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

@@ -0,0 +1,779 @@
Nette PHP Generator
===================
[![Downloads this Month](https://img.shields.io/packagist/dm/nette/php-generator.svg)](https://packagist.org/packages/nette/php-generator)
[![Tests](https://github.com/nette/php-generator/workflows/Tests/badge.svg?branch=master)](https://github.com/nette/php-generator/actions)
[![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
------------
Do you need to generate PHP code of classes, functions, namespaces, etc.? This library with a friendly API will help you.
Documentation can be found on the [website](https://doc.nette.org/php-generator).
[Support Me](https://github.com/sponsors/dg)
--------------------------------------------
Do you like PHP Generator? Are you looking forward to the new features?
[![Buy me a coffee](https://files.nette.org/icons/donation-3.svg)](https://github.com/sponsors/dg)
Thank you!
Installation
------------
```shell
composer require nette/php-generator
```
- PhpGenerator 3.6 is compatible with PHP 7.2 to 8.1
- PhpGenerator 3.2 – 3.5 is compatible with PHP 7.1 to 8.0
- PhpGenerator 3.1 is compatible with PHP 7.1 to 7.3
- PhpGenerator 3.0 is compatible with PHP 7.0 to 7.3
- PhpGenerator 2.6 is compatible with PHP 5.6 to 7.3
Classes
-------
Let's start with a straightforward example of generating class using [ClassType](https://api.nette.org/php-generator/Nette/PhpGenerator/ClassType.html):
```php
$class = new Nette\PhpGenerator\ClassType('Demo');
$class
->setFinal()
->setExtends(ParentClass::class)
->addImplement(Countable::class)
->addComment("Description of class.\nSecond line\n")
->addComment('@property-read Nette\Forms\Form $form');
// to generate PHP code simply cast to string or use echo:
echo $class;
```
It will render this result:
```php
/**
* Description of class.
* Second line
*
* @property-read Nette\Forms\Form $form
*/
final class Demo extends ParentClass implements Countable
{
}
```
We can also use a printer to generate the code, which, unlike `echo $class`, we will be able to further configure:
```php
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class);
```
We can add constants ([Constant](https://api.nette.org/php-generator/Nette/PhpGenerator/Constant.html)) and properties ([Property](https://api.nette.org/php-generator/Nette/PhpGenerator/Property.html)):
```php
$class->addConstant('ID', 123)
->setProtected() // constant visiblity
->setFinal();
$class->addProperty('items', [1, 2, 3])
->setPrivate() // or setVisibility('private')
->setStatic()
->addComment('@var int[]');
$class->addProperty('list')
->setType('array')
->setNullable()
->setInitialized(); // prints '= null'
```
It generates:
```php
final protected const ID = 123;
/** @var int[] */
private static $items = [1, 2, 3];
public ?array $list = null;
```
And we can add methods with parameters:
```php
$method = $class->addMethod('count')
->addComment('Count it.')
->addComment('@return int')
->setFinal()
->setProtected()
->setReturnType('int') // method return type
->setReturnNullable() // nullable return type
->setBody('return count($items ?: $this->items);');
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
```
It results in:
```php
/**
* Count it.
* @return int
*/
final protected function count(array &$items = []): ?int
{
return count($items ?: $this->items);
}
```
Promoted parameters introduced by PHP 8.0 can be passed to the constructor:
```php
$method = $class->addMethod('__construct');
$method->addPromotedParameter('name');
$method->addPromotedParameter('args', [])
->setPrivate();
```
It results in:
```php
public function __construct(
public $name,
private $args = [],
) {
}
```
Readonly properties introduced by PHP 8.1 can be marked via `setReadOnly()`.
------
If the added property, constant, method or parameter already exist, it will be overwritten.
Members can be removed using `removeProperty()`, `removeConstant()`, `removeMethod()` or `removeParameter()`.
You can also add existing `Method`, `Property` or `Constant` objects to the class:
```php
$method = new Nette\PhpGenerator\Method('getHandle');
$property = new Nette\PhpGenerator\Property('handle');
$const = new Nette\PhpGenerator\Constant('ROLE');
$class = (new Nette\PhpGenerator\ClassType('Demo'))
->addMember($method)
->addMember($property)
->addMember($const);
```
You can clone existing methods, properties and constants with a different name using `cloneWithName()`:
```php
$methodCount = $class->getMethod('count');
$methodRecount = $methodCount->cloneWithName('recount');
$class->addMember($methodRecount);
```
Types
-----
Each type or union/intersection type can be passed as a string, you can also use predefined constants for native types:
```php
use Nette\PhpGenerator\Type;
$member->setType('array'); // or Type::ARRAY;
$member->setType('array|string'); // or Type::union('array', 'string')
$member->setType('Foo&Bar'); // or Type::intersection(Foo::class, Bar::class)
$member->setType(null); // removes type
```
The same applies to the method `setReturnType()`.
Tabs versus Spaces
------------------
The generated code uses tabs for indentation. If you want to have the output compatible with PSR-2 or PSR-12, use `PsrPrinter`:
```php
$class = new Nette\PhpGenerator\ClassType('Demo');
// ...
$printer = new Nette\PhpGenerator\PsrPrinter;
echo $printer->printClass($class); // 4 spaces indentation
```
Interface or Trait
------------------
You can create interfaces and traits:
```php
$interface = Nette\PhpGenerator\ClassType::interface('MyInterface');
$trait = Nette\PhpGenerator\ClassType::trait('MyTrait');
// in a similar way $class = Nette\PhpGenerator\ClassType::class('MyClass');
```
Enums
-----
You can easily create the enums that PHP 8.1 brings:
```php
$enum = Nette\PhpGenerator\ClassType::enum('Suit');
$enum->addCase('Clubs');
$enum->addCase('Diamonds');
$enum->addCase('Hearts');
$enum->addCase('Spades');
echo $enum;
```
Result:
```php
enum Suit
{
case Clubs;
case Diamonds;
case Hearts;
case Spades;
}
```
You can also define scalar equivalents for cases to create a backed enum:
```php
$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');
```
It is possible to add a comment or [attributes](#attributes) to each case using `addComment()` or `addAttribute()`.
Literals
--------
With `Literal` you can pass arbitrary PHP code to, for example, default property or parameter values etc:
```php
use Nette\PhpGenerator\Literal;
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addProperty('foo', new Literal('Iterator::SELF_FIRST'));
$class->addMethod('bar')
->addParameter('id', new Literal('1 + 2'));
echo $class;
```
Result:
```php
class Demo
{
public $foo = Iterator::SELF_FIRST;
public function bar($id = 1 + 2)
{
}
}
```
You can also pass parameters to `Literal` and have it formatted into valid PHP code using [special placeholders](#method-and-function-body-generator):
```php
new Literal('substr(?, ?)', [$a, $b]);
// generates, for example: substr('hello', 5);
```
Using Traits
------------
```php
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addTrait('SmartObject');
$class->addTrait('MyTrait', true)
->addResolution('sayHello as protected')
->addComment('@use MyTrait<Foo>');
echo $class;
```
Result:
```php
class Demo
{
use SmartObject;
/** @use MyTrait<Foo> */
use MyTrait {
sayHello as protected;
}
}
```
Anonymous Class
---------------
Give `null` as the name and you have an anonymous class:
```php
$class = new Nette\PhpGenerator\ClassType(null);
$class->addMethod('__construct')
->addParameter('foo');
echo '$obj = new class ($val) ' . $class . ';';
```
Result:
```php
$obj = new class ($val) {
public function __construct($foo)
{
}
};
```
Global Function
---------------
Code of functions will generate class [GlobalFunction](https://api.nette.org/php-generator/Nette/PhpGenerator/GlobalFunction.html):
```php
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('return $a + $b;');
$function->addParameter('a');
$function->addParameter('b');
echo $function;
// or use PsrPrinter for output compatible with PSR-2 / PSR-12
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);
```
Result:
```php
function foo($a, $b)
{
return $a + $b;
}
```
Closure
-------
Code of closures will generate class [Closure](https://api.nette.org/php-generator/Nette/PhpGenerator/Closure.html):
```php
$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('return $a + $b;');
$closure->addParameter('a');
$closure->addParameter('b');
$closure->addUse('c')
->setReference();
echo $closure;
// or use PsrPrinter for output compatible with PSR-2 / PSR-12
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);
```
Result:
```php
function ($a, $b) use (&$c) {
return $a + $b;
}
```
Arrow Function
--------------
You can also print closure as arrow function using printer:
```php
$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('$a + $b');
$closure->addParameter('a');
$closure->addParameter('b');
// or use PsrPrinter for output compatible with PSR-2 / PSR-12
echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure);
```
Result:
```php
fn($a, $b) => $a + $b
```
Attributes
----------
You can add PHP 8 attributes to all classes, methods, properties, constants, enum cases, functions, closures and parameters.
```php
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addAttribute('Deprecated');
$class->addProperty('list')
->addAttribute('WithArguments', [1, 2]);
$method = $class->addMethod('count')
->addAttribute('Foo\Cached', ['mode' => true]);
$method->addParameter('items')
->addAttribute('Bar');
echo $class;
```
Result:
```php
#[Deprecated]
class Demo
{
#[WithArguments(1, 2)]
public $list;
#[Foo\Cached(mode: true)]
public function count(#[Bar] $items)
{
}
}
```
Method and Function Body Generator
----------------------------------
The body can be passed to the `setBody()` method at once or sequentially (line by line) by repeatedly calling `addBody()`:
```php
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('$a = rand(10, 20);');
$function->addBody('return $a;');
echo $function;
```
Result
```php
function foo()
{
$a = rand(10, 20);
return $a;
}
```
You can use special placeholders for handy way to inject variables.
Simple placeholders `?`
```php
$str = 'any string';
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('return substr(?, ?);', [$str, $num]);
echo $function;
```
Result:
```php
function foo()
{
return substr('any string', 3);
}
```
Variadic placeholder `...?`
```php
$items = [1, 2, 3];
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('myfunc(...?);', [$items]);
echo $function;
```
Result:
```php
function foo()
{
myfunc(1, 2, 3);
}
```
You can also use PHP 8 named parameters using placeholder `...?:`
```php
$items = ['foo' => 1, 'bar' => true];
$function->setBody('myfunc(...?:);', [$items]);
// myfunc(foo: 1, bar: true);
```
Escape placeholder using slash `\?`
```php
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addParameter('a');
$function->addBody('return $a \? 10 : ?;', [$num]);
echo $function;
```
Result:
```php
function foo($a)
{
return $a ? 10 : 3;
}
```
Namespace
---------
Classes, traits, interfaces and enums (hereinafter classes) can be grouped into namespaces ([PhpNamespace](https://api.nette.org/php-generator/Nette/PhpGenerator/PhpNamespace.html)):
```php
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
// create new classes in the namespace
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');
// or insert an existing class into the namespace
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);
```
If the class already exists, it will be overwritten.
You can define use-statements:
```php
// use Http\Request;
$namespace->addUse(Http\Request::class);
// use Http\Request as HttpReq;
$namespace->addUse(Http\Request::class, 'HttpReq');
// use function iter\range;
$namespace->addUseFunction('iter\range');
```
To simplify a fully qualified class, function or constant name according to the defined aliases, use the `simplifyName` method:
```php
echo $namespace->simplifyName('Foo\Bar'); // 'Bar', because 'Foo' is current namespace
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', because of the defined use-statement
```
Conversely, you can convert a simplified class, function or constant name to a fully qualified one using the `resolveName` method:
```php
echo $namespace->resolveName('Bar'); // 'Foo\Bar'
echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'
```
Class Names Resolving
---------------------
**When the class is part of the namespace, it is rendered slightly differently**: all types (ie. type hints, return types, parent class name,
implemented interfaces, used traits and attributes) are automatically *resolved* (unless you turn it off, see below).
It means that you have to **use full class names** in definitions and they will be replaced
with aliases (according to the use-statements) or fully qualified names in the resulting code:
```php
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
$namespace->addUse('Bar\AliasedClass');
$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // it will simplify to A
->addTrait('Bar\AliasedClass'); // it will simplify to AliasedClass
$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // in comments simplify manually
$method->addParameter('arg')
->setType('Bar\OtherClass'); // it will resolve to \Bar\OtherClass
echo $namespace;
// or use PsrPrinter for output compatible with PSR-2 / PSR-12
// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);
```
Result:
```php
namespace Foo;
use Bar\AliasedClass;
class Demo implements A
{
use AliasedClass;
/**
* @return D
*/
public function method(\Bar\OtherClass $arg)
{
}
}
```
Auto-resolving can be turned off this way:
```php
$printer = new Nette\PhpGenerator\Printer; // or PsrPrinter
$printer->setTypeResolving(false);
echo $printer->printNamespace($namespace);
```
PHP Files
---------
Classes, functions and namespaces can be grouped into PHP files represented by the class [PhpFile](https://api.nette.org/php-generator/Nette/PhpGenerator/PhpFile.html):
```php
$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('This file is auto-generated.');
$file->setStrictTypes(); // adds declare(strict_types=1)
$class = $file->addClass('Foo\A');
$function = $file->addFunction('Foo\foo');
// or
// $namespace = $file->addNamespace('Foo');
// $class = $namespace->addClass('A');
// $function = $namespace->addFunction('foo');
echo $file;
// or use PsrPrinter for output compatible with PSR-2 / PSR-12
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);
```
Result:
```php
<?php
/**
* This file is auto-generated.
*/
declare(strict_types=1);
namespace Foo;
class A
{
}
function foo()
{
}
```
Generate using Reflection
-------------------------
Another common use case is to create class or function based on existing one:
```php
$class = Nette\PhpGenerator\ClassType::from(PDO::class);
$function = Nette\PhpGenerator\GlobalFunction::from('trim');
$closure = Nette\PhpGenerator\Closure::from(
function (stdClass $a, $b = null) {}
);
```
Function and method bodies are empty by default. If you want to load them as well, use this way
(it requires `nikic/php-parser` to be installed):
```php
$class = Nette\PhpGenerator\ClassType::withBodiesFrom(MyClass::class);
$function = Nette\PhpGenerator\GlobalFunction::withBodyFrom('dump');
```
Load class from file
--------------------
You can also load classes directly from a PHP file that is not already loaded or string of PHP code:
```php
$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX
<?php
class Demo
{
public $foo;
}
XX);
```
Loading the entire PHP file, which may contain multiple classes or even multiple namespaces:
```php
$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));
```
This requires `nikic/php-parser` to be installed.
Variables Dumper
----------------
The Dumper returns a parsable PHP string representation of a variable. Provides better and clearer output that native functon `var_export()`.
```php
$dumper = new Nette\PhpGenerator\Dumper;
$var = ['a', 'b', 123];
echo $dumper->dump($var); // prints ['a', 'b', 123]
```
Custom Printer
--------------
Need to customize printer behavior? Create your own by inheriting the `Printer` class. You can reconfigure these variables:
```php
class MyPrinter extends Nette\PhpGenerator\Printer
{
protected $indentation = "\t";
protected $linesBetweenProperties = 0;
protected $linesBetweenMethods = 1;
protected $returnTypeColon = ': ';
}
```

View File

@@ -0,0 +1,50 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
/**
* PHP Attribute.
*/
final class Attribute
{
use Nette\SmartObject;
/** @var string */
private $name;
/** @var array */
private $args;
public function __construct(string $name, array $args)
{
if (!Helpers::isNamespaceIdentifier($name)) {
throw new Nette\InvalidArgumentException("Value '$name' is not valid attribute name.");
}
$this->name = $name;
$this->args = $args;
}
public function getName(): string
{
return $this->name;
}
public function getArguments(): array
{
return $this->args;
}
}

View File

@@ -0,0 +1,676 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
/**
* Class/Interface/Trait/Enum description.
*
* @property Method[] $methods
* @property Property[] $properties
*/
final class ClassType
{
use Nette\SmartObject;
use Traits\CommentAware;
use Traits\AttributeAware;
public const
TYPE_CLASS = 'class',
TYPE_INTERFACE = 'interface',
TYPE_TRAIT = 'trait',
TYPE_ENUM = 'enum';
public const
VisibilityPublic = 'public',
VisibilityProtected = 'protected',
VisibilityPrivate = 'private';
public const
VISIBILITY_PUBLIC = self::VisibilityPublic,
VISIBILITY_PROTECTED = self::VisibilityProtected,
VISIBILITY_PRIVATE = self::VisibilityPrivate;
/** @var PhpNamespace|null */
private $namespace;
/** @var string|null */
private $name;
/** @var string class|interface|trait */
private $type = self::TYPE_CLASS;
/** @var bool */
private $final = false;
/** @var bool */
private $abstract = false;
/** @var string|string[] */
private $extends = [];
/** @var string[] */
private $implements = [];
/** @var TraitUse[] */
private $traits = [];
/** @var Constant[] name => Constant */
private $consts = [];
/** @var Property[] name => Property */
private $properties = [];
/** @var Method[] name => Method */
private $methods = [];
/** @var EnumCase[] name => EnumCase */
private $cases = [];
public static function class(?string $name): self
{
return new self($name);
}
public static function interface(string $name): self
{
return (new self($name))->setType(self::TYPE_INTERFACE);
}
public static function trait(string $name): self
{
return (new self($name))->setType(self::TYPE_TRAIT);
}
public static function enum(string $name): self
{
return (new self($name))->setType(self::TYPE_ENUM);
}
/**
* @param string|object $class
*/
public static function from($class, bool $withBodies = false, bool $materializeTraits = true): self
{
return (new Factory)
->fromClassReflection(new \ReflectionClass($class), $withBodies, $materializeTraits);
}
/**
* @param string|object $class
*/
public static function withBodiesFrom($class): self
{
return (new Factory)
->fromClassReflection(new \ReflectionClass($class), true);
}
public static function fromCode(string $code): self
{
return (new Factory)
->fromClassCode($code);
}
public function __construct(?string $name = null, ?PhpNamespace $namespace = null)
{
$this->setName($name);
$this->namespace = $namespace;
}
public function __toString(): string
{
try {
return (new Printer)->printClass($this, $this->namespace);
} catch (\Throwable $e) {
if (PHP_VERSION_ID >= 70400) {
throw $e;
}
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
return '';
}
}
/** @deprecated an object can be in multiple namespaces */
public function getNamespace(): ?PhpNamespace
{
return $this->namespace;
}
/** @return static */
public function setName(?string $name): self
{
if ($name !== null && (!Helpers::isIdentifier($name) || isset(Helpers::Keywords[strtolower($name)]))) {
throw new Nette\InvalidArgumentException("Value '$name' is not valid class name.");
}
$this->name = $name;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
/** @deprecated */
public function setClass(): self
{
$this->type = self::TYPE_CLASS;
return $this;
}
public function isClass(): bool
{
return $this->type === self::TYPE_CLASS;
}
/** @return static */
public function setInterface(): self
{
$this->type = self::TYPE_INTERFACE;
return $this;
}
public function isInterface(): bool
{
return $this->type === self::TYPE_INTERFACE;
}
/** @return static */
public function setTrait(): self
{
$this->type = self::TYPE_TRAIT;
return $this;
}
public function isTrait(): bool
{
return $this->type === self::TYPE_TRAIT;
}
public function isEnum(): bool
{
return $this->type === self::TYPE_ENUM;
}
/** @return static */
public function setType(string $type): self
{
if (!in_array($type, [self::TYPE_CLASS, self::TYPE_INTERFACE, self::TYPE_TRAIT, self::TYPE_ENUM], true)) {
throw new Nette\InvalidArgumentException('Argument must be class|interface|trait|enum.');
}
$this->type = $type;
return $this;
}
public function getType(): string
{
return $this->type;
}
/** @return static */
public function setFinal(bool $state = true): self
{
$this->final = $state;
return $this;
}
public function isFinal(): bool
{
return $this->final;
}
/** @return static */
public function setAbstract(bool $state = true): self
{
$this->abstract = $state;
return $this;
}
public function isAbstract(): bool
{
return $this->abstract;
}
/**
* @param string|string[] $names
* @return static
*/
public function setExtends($names): self
{
if (!is_string($names) && !is_array($names)) {
throw new Nette\InvalidArgumentException('Argument must be string or string[].');
}
$this->validateNames((array) $names);
$this->extends = $names;
return $this;
}
/** @return string|string[] */
public function getExtends()
{
return $this->extends;
}
/** @return static */
public function addExtend(string $name): self
{
$this->validateNames([$name]);
$this->extends = (array) $this->extends;
$this->extends[] = $name;
return $this;
}
/**
* @param string[] $names
* @return static
*/
public function setImplements(array $names): self
{
$this->validateNames($names);
$this->implements = $names;
return $this;
}
/** @return string[] */
public function getImplements(): array
{
return $this->implements;
}
/** @return static */
public function addImplement(string $name): self
{
$this->validateNames([$name]);
$this->implements[] = $name;
return $this;
}
/** @return static */
public function removeImplement(string $name): self
{
$this->implements = array_diff($this->implements, [$name]);
return $this;
}
/**
* @param string[]|TraitUse[] $traits
* @return static
*/
public function setTraits(array $traits): self
{
$this->traits = [];
foreach ($traits as $trait) {
if (!$trait instanceof TraitUse) {
$trait = new TraitUse($trait);
}
$this->traits[$trait->getName()] = $trait;
}
return $this;
}
/** @return string[] */
public function getTraits(): array
{
return array_keys($this->traits);
}
/** @internal */
public function getTraitResolutions(): array
{
return $this->traits;
}
/**
* @param array|bool $resolutions
* @return static|TraitUse
*/
public function addTrait(string $name, $resolutions = [])
{
$this->traits[$name] = $trait = new TraitUse($name);
if ($resolutions === true) {
return $trait;
}
array_map(function ($item) use ($trait) {
$trait->addResolution($item);
}, $resolutions);
return $this;
}
/** @return static */
public function removeTrait(string $name): self
{
unset($this->traits[$name]);
return $this;
}
/**
* @param Method|Property|Constant|EnumCase|TraitUse $member
* @return static
*/
public function addMember($member): self
{
if ($member instanceof Method) {
if ($this->isInterface()) {
$member->setBody(null);
}
$this->methods[strtolower($member->getName())] = $member;
} elseif ($member instanceof Property) {
$this->properties[$member->getName()] = $member;
} elseif ($member instanceof Constant) {
$this->consts[$member->getName()] = $member;
} elseif ($member instanceof EnumCase) {
$this->cases[$member->getName()] = $member;
} elseif ($member instanceof TraitUse) {
$this->traits[$member->getName()] = $member;
} else {
throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant|EnumCase|TraitUse.');
}
return $this;
}
/**
* @param Constant[]|mixed[] $consts
* @return static
*/
public function setConstants(array $consts): self
{
$this->consts = [];
foreach ($consts as $k => $const) {
if (!$const instanceof Constant) {
$const = (new Constant($k))->setValue($const)->setPublic();
}
$this->consts[$const->getName()] = $const;
}
return $this;
}
/** @return Constant[] */
public function getConstants(): array
{
return $this->consts;
}
public function addConstant(string $name, $value): Constant
{
return $this->consts[$name] = (new Constant($name))
->setValue($value)
->setPublic();
}
/** @return static */
public function removeConstant(string $name): self
{
unset($this->consts[$name]);
return $this;
}
/**
* Sets cases to enum
* @param EnumCase[] $cases
* @return static
*/
public function setCases(array $cases): self
{
(function (EnumCase ...$cases) {})(...array_values($cases));
$this->cases = [];
foreach ($cases as $case) {
$this->cases[$case->getName()] = $case;
}
return $this;
}
/** @return EnumCase[] */
public function getCases(): array
{
return $this->cases;
}
/** Adds case to enum */
public function addCase(string $name, $value = null): EnumCase
{
return $this->cases[$name] = (new EnumCase($name))
->setValue($value);
}
/** @return static */
public function removeCase(string $name): self
{
unset($this->cases[$name]);
return $this;
}
/**
* @param Property[] $props
* @return static
*/
public function setProperties(array $props): self
{
(function (Property ...$props) {})(...array_values($props));
$this->properties = [];
foreach ($props as $v) {
$this->properties[$v->getName()] = $v;
}
return $this;
}
/** @return Property[] */
public function getProperties(): array
{
return $this->properties;
}
public function getProperty(string $name): Property
{
if (!isset($this->properties[$name])) {
throw new Nette\InvalidArgumentException("Property '$name' not found.");
}
return $this->properties[$name];
}
/**
* @param string $name without $
*/
public function addProperty(string $name, $value = null): Property
{
return $this->properties[$name] = func_num_args() > 1
? (new Property($name))->setValue($value)
: new Property($name);
}
/**
* @param string $name without $
* @return static
*/
public function removeProperty(string $name): self
{
unset($this->properties[$name]);
return $this;
}
public function hasProperty(string $name): bool
{
return isset($this->properties[$name]);
}
/**
* @param Method[] $methods
* @return static
*/
public function setMethods(array $methods): self
{
(function (Method ...$methods) {})(...array_values($methods));
$this->methods = [];
foreach ($methods as $m) {
$this->methods[strtolower($m->getName())] = $m;
}
return $this;
}
/** @return Method[] */
public function getMethods(): array
{
$res = [];
foreach ($this->methods as $m) {
$res[$m->getName()] = $m;
}
return $res;
}
public function getMethod(string $name): Method
{
$m = $this->methods[strtolower($name)] ?? null;
if (!$m) {
throw new Nette\InvalidArgumentException("Method '$name' not found.");
}
return $m;
}
public function addMethod(string $name): Method
{
$method = new Method($name);
if ($this->isInterface()) {
$method->setBody(null);
} else {
$method->setPublic();
}
return $this->methods[strtolower($name)] = $method;
}
/** @return static */
public function removeMethod(string $name): self
{
unset($this->methods[strtolower($name)]);
return $this;
}
public function hasMethod(string $name): bool
{
return isset($this->methods[strtolower($name)]);
}
/** @throws Nette\InvalidStateException */
public function validate(): void
{
if ($this->isEnum() && ($this->abstract || $this->final || $this->extends || $this->properties)) {
throw new Nette\InvalidStateException("Enum '$this->name' cannot be abstract or final or extends class or have properties.");
} elseif (!$this->name && ($this->abstract || $this->final)) {
throw new Nette\InvalidStateException('Anonymous class cannot be abstract or final.');
} elseif ($this->abstract && $this->final) {
throw new Nette\InvalidStateException("Class '$this->name' cannot be abstract and final at the same time.");
}
}
private function validateNames(array $names): void
{
foreach ($names as $name) {
if (!Helpers::isNamespaceIdentifier($name, true)) {
throw new Nette\InvalidArgumentException("Value '$name' is not valid class name.");
}
}
}
public function __clone()
{
$clone = function ($item) { return clone $item; };
$this->traits = array_map($clone, $this->traits);
$this->cases = array_map($clone, $this->cases);
$this->consts = array_map($clone, $this->consts);
$this->properties = array_map($clone, $this->properties);
$this->methods = array_map($clone, $this->methods);
}
}

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;
use Traits\AttributeAware;
/** @var Parameter[] */
private $uses = [];
public static function from(\Closure $closure): self
{
return (new Factory)->fromFunctionReflection(new \ReflectionFunction($closure));
}
public function __toString(): string
{
try {
return (new Printer)->printClosure($this);
} catch (\Throwable $e) {
if (PHP_VERSION_ID >= 70400) {
throw $e;
}
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
return '';
}
}
/**
* @param Parameter[] $uses
* @return static
*/
public function setUses(array $uses): self
{
(function (Parameter ...$uses) {})(...$uses);
$this->uses = $uses;
return $this;
}
public function getUses(): array
{
return $this->uses;
}
public function addUse(string $name): Parameter
{
return $this->uses[] = new Parameter($name);
}
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
/**
* Class constant.
*/
final class Constant
{
use Nette\SmartObject;
use Traits\NameAware;
use Traits\VisibilityAware;
use Traits\CommentAware;
use Traits\AttributeAware;
/** @var mixed */
private $value;
/** @var bool */
private $final = false;
/** @return static */
public function setValue($val): self
{
$this->value = $val;
return $this;
}
public function getValue()
{
return $this->value;
}
/** @return static */
public function setFinal(bool $state = true): self
{
$this->final = $state;
return $this;
}
public function isFinal(): bool
{
return $this->final;
}
}

View File

@@ -0,0 +1,277 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
/**
* PHP code generator utils.
*/
final class Dumper
{
private const IndentLength = 4;
/** @var int */
public $maxDepth = 50;
/** @var int */
public $wrapLength = 120;
/** @var string */
public $indentation = "\t";
/**
* Returns a PHP representation of a variable.
*/
public function dump($var, int $column = 0): string
{
return $this->dumpVar($var, [], 0, $column);
}
private function dumpVar(&$var, array $parents = [], int $level = 0, int $column = 0): string
{
if ($var === null) {
return 'null';
} elseif (is_string($var)) {
return $this->dumpString($var);
} elseif (is_array($var)) {
return $this->dumpArray($var, $parents, $level, $column);
} elseif ($var instanceof Literal) {
return $this->dumpLiteral($var, $level);
} elseif (is_object($var)) {
return $this->dumpObject($var, $parents, $level);
} elseif (is_resource($var)) {
throw new Nette\InvalidArgumentException('Cannot dump resource.');
} else {
return var_export($var, true);
}
}
private function dumpString(string $s): string
{
static $special = [
"\r" => '\r',
"\n" => '\n',
"\t" => '\t',
"\e" => '\e',
'\\' => '\\\\',
];
$utf8 = preg_match('##u', $s);
$escaped = preg_replace_callback(
$utf8 ? '#[\p{C}\\\\]#u' : '#[\x00-\x1F\x7F-\xFF\\\\]#',
function ($m) use ($special) {
return $special[$m[0]] ?? (strlen($m[0]) === 1
? '\x' . str_pad(strtoupper(dechex(ord($m[0]))), 2, '0', STR_PAD_LEFT) . ''
: '\u{' . strtoupper(ltrim(dechex(self::utf8Ord($m[0])), '0')) . '}');
},
$s
);
return $s === str_replace('\\\\', '\\', $escaped)
? "'" . preg_replace('#\'|\\\\(?=[\'\\\\]|$)#D', '\\\\$0', $s) . "'"
: '"' . addcslashes($escaped, '"$') . '"';
}
private static function utf8Ord(string $c): int
{
$ord0 = ord($c[0]);
if ($ord0 < 0x80) {
return $ord0;
} elseif ($ord0 < 0xE0) {
return ($ord0 << 6) + ord($c[1]) - 0x3080;
} elseif ($ord0 < 0xF0) {
return ($ord0 << 12) + (ord($c[1]) << 6) + ord($c[2]) - 0xE2080;
} else {
return ($ord0 << 18) + (ord($c[1]) << 12) + (ord($c[2]) << 6) + ord($c[3]) - 0x3C82080;
}
}
private function dumpArray(array &$var, array $parents, int $level, int $column): string
{
if (empty($var)) {
return '[]';
} elseif ($level > $this->maxDepth || in_array($var, $parents, true)) {
throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.');
}
$space = str_repeat($this->indentation, $level);
$outInline = '';
$outWrapped = "\n$space";
$parents[] = $var;
$counter = 0;
$hideKeys = is_int(($tmp = array_keys($var))[0]) && $tmp === range($tmp[0], $tmp[0] + count($var) - 1);
foreach ($var as $k => &$v) {
$keyPart = $hideKeys && $k === $counter
? ''
: $this->dumpVar($k) . ' => ';
$counter = is_int($k) ? max($k + 1, $counter) : $counter;
$outInline .= ($outInline === '' ? '' : ', ') . $keyPart;
$outInline .= $this->dumpVar($v, $parents, 0, $column + strlen($outInline));
$outWrapped .= $this->indentation
. $keyPart
. $this->dumpVar($v, $parents, $level + 1, strlen($keyPart))
. ",\n$space";
}
array_pop($parents);
$wrap = strpos($outInline, "\n") !== false || $level * self::IndentLength + $column + strlen($outInline) + 3 > $this->wrapLength; // 3 = [],
return '[' . ($wrap ? $outWrapped : $outInline) . ']';
}
private function dumpObject($var, array $parents, int $level): string
{
if ($var instanceof \Serializable) {
return 'unserialize(' . $this->dumpString(serialize($var)) . ')';
} elseif ($var instanceof \UnitEnum) {
return '\\' . get_class($var) . '::' . $var->name;
} elseif ($var instanceof \Closure) {
$inner = Nette\Utils\Callback::unwrap($var);
if (Nette\Utils\Callback::isStatic($inner)) {
return PHP_VERSION_ID < 80100
? '\Closure::fromCallable(' . $this->dump($inner) . ')'
: implode('::', (array) $inner) . '(...)';
}
throw new Nette\InvalidArgumentException('Cannot dump closure.');
}
$class = get_class($var);
if ((new \ReflectionObject($var))->isAnonymous()) {
throw new Nette\InvalidArgumentException('Cannot dump anonymous class.');
} elseif (in_array($class, [\DateTime::class, \DateTimeImmutable::class], true)) {
return $this->format("new \\$class(?, new \\DateTimeZone(?))", $var->format('Y-m-d H:i:s.u'), $var->getTimeZone()->getName());
}
$arr = (array) $var;
$space = str_repeat($this->indentation, $level);
if ($level > $this->maxDepth || in_array($var, $parents, true)) {
throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.');
}
$out = "\n";
$parents[] = $var;
if (method_exists($var, '__sleep')) {
foreach ($var->__sleep() as $v) {
$props[$v] = $props["\x00*\x00$v"] = $props["\x00$class\x00$v"] = true;
}
}
foreach ($arr as $k => &$v) {
if (!isset($props) || isset($props[$k])) {
$out .= $space . $this->indentation
. ($keyPart = $this->dumpVar($k) . ' => ')
. $this->dumpVar($v, $parents, $level + 1, strlen($keyPart))
. ",\n";
}
}
array_pop($parents);
$out .= $space;
return $class === \stdClass::class
? "(object) [$out]"
: '\\' . self::class . "::createObject('$class', [$out])";
}
private function dumpLiteral(Literal $var, int $level): string
{
$s = $var->formatWith($this);
$s = Nette\Utils\Strings::indent(trim($s), $level, $this->indentation);
return ltrim($s, $this->indentation);
}
/**
* Generates PHP statement. Supports placeholders: ? \? $? ->? ::? ...? ...?: ?*
*/
public function format(string $statement, ...$args): string
{
$tokens = preg_split('#(\.\.\.\?:?|\$\?|->\?|::\?|\\\\\?|\?\*|\?(?!\w))#', $statement, -1, PREG_SPLIT_DELIM_CAPTURE);
$res = '';
foreach ($tokens as $n => $token) {
if ($n % 2 === 0) {
$res .= $token;
} elseif ($token === '\?') {
$res .= '?';
} elseif (!$args) {
throw new Nette\InvalidArgumentException('Insufficient number of arguments.');
} elseif ($token === '?') {
$res .= $this->dump(array_shift($args), strlen($res) - strrpos($res, "\n"));
} elseif ($token === '...?' || $token === '...?:' || $token === '?*') {
$arg = array_shift($args);
if (!is_array($arg)) {
throw new Nette\InvalidArgumentException('Argument must be an array.');
}
$res .= $this->dumpArguments($arg, strlen($res) - strrpos($res, "\n"), $token === '...?:');
} else { // $ -> ::
$arg = array_shift($args);
if ($arg instanceof Literal || !Helpers::isIdentifier($arg)) {
$arg = '{' . $this->dumpVar($arg) . '}';
}
$res .= substr($token, 0, -1) . $arg;
}
}
if ($args) {
throw new Nette\InvalidArgumentException('Insufficient number of placeholders.');
}
return $res;
}
private function dumpArguments(array &$var, int $column, bool $named): string
{
$outInline = $outWrapped = '';
foreach ($var as $k => &$v) {
$k = !$named || is_int($k) ? '' : $k . ': ';
$outInline .= $outInline === '' ? '' : ', ';
$outInline .= $k . $this->dumpVar($v, [$var], 0, $column + strlen($outInline));
$outWrapped .= ($outWrapped === '' ? '' : ',') . "\n"
. $this->indentation . $k . $this->dumpVar($v, [$var], 1);
}
return count($var) > 1 && (strpos($outInline, "\n") !== false || $column + strlen($outInline) > $this->wrapLength)
? $outWrapped . "\n"
: $outInline;
}
/**
* @internal
*/
public static function createObject(string $class, array $props): object
{
return unserialize('O' . substr(serialize($class), 1, -1) . substr(serialize($props), 1));
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
/**
* Enum case.
*/
final class EnumCase
{
use Nette\SmartObject;
use Traits\NameAware;
use Traits\CommentAware;
use Traits\AttributeAware;
/** @var string|int|null */
private $value;
/** @return static */
public function setValue($val): self
{
$this->value = $val;
return $this;
}
public function getValue()
{
return $this->value;
}
}

View File

@@ -0,0 +1,444 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
use PhpParser;
use PhpParser\Node;
use PhpParser\NodeFinder;
use PhpParser\ParserFactory;
/**
* Extracts information from PHP code.
* @internal
*/
final class Extractor
{
use Nette\SmartObject;
private $code;
private $statements;
private $printer;
public function __construct(string $code)
{
if (!class_exists(ParserFactory::class)) {
throw new Nette\NotSupportedException("PHP-Parser is required to load method bodies, install package 'nikic/php-parser' 4.7 or newer.");
}
$this->printer = new PhpParser\PrettyPrinter\Standard;
$this->parseCode($code);
}
private function parseCode(string $code): void
{
if (substr($code, 0, 5) !== '<?php') {
throw new Nette\InvalidStateException('The input string is not a PHP code.');
}
$this->code = str_replace("\r\n", "\n", $code);
$lexer = new PhpParser\Lexer\Emulative(['usedAttributes' => ['startFilePos', 'endFilePos', 'comments']]);
$parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7, $lexer);
$stmts = $parser->parse($this->code);
$traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor(new PhpParser\NodeVisitor\ParentConnectingVisitor);
$traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver(null, ['preserveOriginalNames' => true]));
$this->statements = $traverser->traverse($stmts);
}
public function extractMethodBodies(string $className): array
{
$nodeFinder = new NodeFinder;
$classNode = $nodeFinder->findFirst($this->statements, function (Node $node) use ($className) {
return $node instanceof Node\Stmt\ClassLike && $node->namespacedName->toString() === $className;
});
$res = [];
foreach ($nodeFinder->findInstanceOf($classNode, Node\Stmt\ClassMethod::class) as $methodNode) {
/** @var Node\Stmt\ClassMethod $methodNode */
if ($methodNode->stmts) {
$res[$methodNode->name->toString()] = $this->getReformattedContents($methodNode->stmts, 2);
}
}
return $res;
}
public function extractFunctionBody(string $name): ?string
{
/** @var Node\Stmt\Function_ $functionNode */
$functionNode = (new NodeFinder)->findFirst($this->statements, function (Node $node) use ($name) {
return $node instanceof Node\Stmt\Function_ && $node->namespacedName->toString() === $name;
});
return $this->getReformattedContents($functionNode->stmts, 1);
}
/** @param Node[] $statements */
private function getReformattedContents(array $statements, int $level): string
{
$body = $this->getNodeContents(...$statements);
$body = $this->performReplacements($body, $this->prepareReplacements($statements));
return Helpers::unindent($body, $level);
}
private function prepareReplacements(array $statements): array
{
$start = $this->getNodeStartPos($statements[0]);
$replacements = [];
(new NodeFinder)->find($statements, function (Node $node) use (&$replacements, $start) {
if ($node instanceof Node\Name\FullyQualified) {
if ($node->getAttribute('originalName') instanceof Node\Name) {
$of = $node->getAttribute('parent') instanceof Node\Expr\ConstFetch
? PhpNamespace::NameConstant
: ($node->getAttribute('parent') instanceof Node\Expr\FuncCall ? PhpNamespace::NameFunction : PhpNamespace::NameNormal);
$replacements[] = [
$node->getStartFilePos() - $start,
$node->getEndFilePos() - $start,
Helpers::tagName($node->toCodeString(), $of),
];
}
} elseif ($node instanceof Node\Scalar\String_ || $node instanceof Node\Scalar\EncapsedStringPart) {
// multi-line strings => singleline
$token = $this->getNodeContents($node);
if (strpos($token, "\n") !== false) {
$quote = $node instanceof Node\Scalar\String_ ? '"' : '';
$replacements[] = [
$node->getStartFilePos() - $start,
$node->getEndFilePos() - $start,
$quote . addcslashes($node->value, "\x00..\x1F") . $quote,
];
}
} elseif ($node instanceof Node\Scalar\Encapsed) {
// HEREDOC => "string"
if ($node->getAttribute('kind') === Node\Scalar\String_::KIND_HEREDOC) {
$replacements[] = [
$node->getStartFilePos() - $start,
$node->parts[0]->getStartFilePos() - $start - 1,
'"',
];
$replacements[] = [
end($node->parts)->getEndFilePos() - $start + 1,
$node->getEndFilePos() - $start,
'"',
];
}
}
});
return $replacements;
}
private function performReplacements(string $s, array $replacements): string
{
usort($replacements, function ($a, $b) { // sort by position in file
return $b[0] <=> $a[0];
});
foreach ($replacements as [$start, $end, $replacement]) {
$s = substr_replace($s, $replacement, $start, $end - $start + 1);
}
return $s;
}
public function extractAll(): PhpFile
{
$phpFile = new PhpFile;
$namespace = '';
$visitor = new class extends PhpParser\NodeVisitorAbstract {
public $callback;
public function enterNode(Node $node)
{
return ($this->callback)($node);
}
};
$visitor->callback = function (Node $node) use (&$class, &$namespace, $phpFile) {
if ($node instanceof Node\Stmt\DeclareDeclare && $node->key->name === 'strict_types') {
$phpFile->setStrictTypes((bool) $node->value->value);
} elseif ($node instanceof Node\Stmt\Namespace_) {
$namespace = $node->name ? $node->name->toString() : '';
} elseif ($node instanceof Node\Stmt\Use_) {
$this->addUseToNamespace($node, $phpFile->addNamespace($namespace));
} elseif ($node instanceof Node\Stmt\Class_) {
if (!$node->name) {
return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN;
}
$class = $this->addClassToFile($phpFile, $node);
} elseif ($node instanceof Node\Stmt\Interface_) {
$class = $this->addInterfaceToFile($phpFile, $node);
} elseif ($node instanceof Node\Stmt\Trait_) {
$class = $this->addTraitToFile($phpFile, $node);
} elseif ($node instanceof Node\Stmt\Enum_) {
$class = $this->addEnumToFile($phpFile, $node);
} elseif ($node instanceof Node\Stmt\Function_) {
$this->addFunctionToFile($phpFile, $node);
} elseif ($node instanceof Node\Stmt\TraitUse) {
$this->addTraitToClass($class, $node);
} elseif ($node instanceof Node\Stmt\Property) {
$this->addPropertyToClass($class, $node);
} elseif ($node instanceof Node\Stmt\ClassMethod) {
$this->addMethodToClass($class, $node);
} elseif ($node instanceof Node\Stmt\ClassConst) {
$this->addConstantToClass($class, $node);
} elseif ($node instanceof Node\Stmt\EnumCase) {
$this->addEnumCaseToClass($class, $node);
}
if ($node instanceof Node\FunctionLike) {
return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN;
}
};
if ($this->statements) {
$this->addCommentAndAttributes($phpFile, $this->statements[0]);
}
$traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor($visitor);
$traverser->traverse($this->statements);
return $phpFile;
}
private function addUseToNamespace(Node\Stmt\Use_ $node, PhpNamespace $namespace): void
{
$of = [
$node::TYPE_NORMAL => PhpNamespace::NameNormal,
$node::TYPE_FUNCTION => PhpNamespace::NameFunction,
$node::TYPE_CONSTANT => PhpNamespace::NameConstant,
][$node->type];
foreach ($node->uses as $use) {
$namespace->addUse($use->name->toString(), $use->alias ? $use->alias->toString() : null, $of);
}
}
private function addClassToFile(PhpFile $phpFile, Node\Stmt\Class_ $node): ClassType
{
$class = $phpFile->addClass($node->namespacedName->toString());
if ($node->extends) {
$class->setExtends($node->extends->toString());
}
foreach ($node->implements as $item) {
$class->addImplement($item->toString());
}
$class->setFinal($node->isFinal());
$class->setAbstract($node->isAbstract());
$this->addCommentAndAttributes($class, $node);
return $class;
}
private function addInterfaceToFile(PhpFile $phpFile, Node\Stmt\Interface_ $node): ClassType
{
$class = $phpFile->addInterface($node->namespacedName->toString());
foreach ($node->extends as $item) {
$class->addExtend($item->toString());
}
$this->addCommentAndAttributes($class, $node);
return $class;
}
private function addTraitToFile(PhpFile $phpFile, Node\Stmt\Trait_ $node): ClassType
{
$class = $phpFile->addTrait($node->namespacedName->toString());
$this->addCommentAndAttributes($class, $node);
return $class;
}
private function addEnumToFile(PhpFile $phpFile, Node\Stmt\Enum_ $node): ClassType
{
$class = $phpFile->addEnum($node->namespacedName->toString());
foreach ($node->implements as $item) {
$class->addImplement($item->toString());
}
$this->addCommentAndAttributes($class, $node);
return $class;
}
private function addFunctionToFile(PhpFile $phpFile, Node\Stmt\Function_ $node): void
{
$function = $phpFile->addFunction($node->namespacedName->toString());
$this->setupFunction($function, $node);
}
private function addTraitToClass(ClassType $class, Node\Stmt\TraitUse $node): void
{
foreach ($node->traits as $item) {
$trait = $class->addTrait($item->toString(), true);
}
foreach ($node->adaptations as $item) {
$trait->addResolution(trim($this->toPhp($item), ';'));
}
$this->addCommentAndAttributes($trait, $node);
}
private function addPropertyToClass(ClassType $class, Node\Stmt\Property $node): void
{
foreach ($node->props as $item) {
$prop = $class->addProperty($item->name->toString());
$prop->setStatic($node->isStatic());
$prop->setVisibility($this->toVisibility($node->flags));
$prop->setType($node->type ? $this->toPhp($node->type) : null);
if ($item->default) {
$prop->setValue(new Literal($this->getReformattedContents([$item->default], 1)));
}
$prop->setReadOnly(method_exists($node, 'isReadonly') && $node->isReadonly());
$this->addCommentAndAttributes($prop, $node);
}
}
private function addMethodToClass(ClassType $class, Node\Stmt\ClassMethod $node): void
{
$method = $class->addMethod($node->name->toString());
$method->setAbstract($node->isAbstract());
$method->setFinal($node->isFinal());
$method->setStatic($node->isStatic());
$method->setVisibility($this->toVisibility($node->flags));
$this->setupFunction($method, $node);
}
private function addConstantToClass(ClassType $class, Node\Stmt\ClassConst $node): void
{
foreach ($node->consts as $item) {
$value = $this->getReformattedContents([$item->value], 1);
$const = $class->addConstant($item->name->toString(), new Literal($value));
$const->setVisibility($this->toVisibility($node->flags));
$const->setFinal(method_exists($node, 'isFinal') && $node->isFinal());
$this->addCommentAndAttributes($const, $node);
}
}
private function addEnumCaseToClass(ClassType $class, Node\Stmt\EnumCase $node)
{
$case = $class->addCase($node->name->toString(), $node->expr ? $node->expr->value : null);
$this->addCommentAndAttributes($case, $node);
}
private function addCommentAndAttributes($element, Node $node): void
{
if ($node->getDocComment()) {
$comment = $node->getDocComment()->getReformattedText();
$comment = Helpers::unformatDocComment($comment);
$element->setComment($comment);
$node->setDocComment(new PhpParser\Comment\Doc(''));
}
foreach ($node->attrGroups ?? [] as $group) {
foreach ($group->attrs as $attribute) {
$args = [];
foreach ($attribute->args as $arg) {
$value = new Literal($this->getReformattedContents([$arg->value], 0));
if ($arg->name) {
$args[$arg->name->toString()] = $value;
} else {
$args[] = $value;
}
}
$element->addAttribute($attribute->name->toString(), $args);
}
}
}
/**
* @param GlobalFunction|Method $function
*/
private function setupFunction($function, Node\FunctionLike $node): void
{
$function->setReturnReference($node->returnsByRef());
$function->setReturnType($node->getReturnType() ? $this->toPhp($node->getReturnType()) : null);
foreach ($node->getParams() as $item) {
$visibility = $this->toVisibility($item->flags);
$isReadonly = (bool) ($item->flags & Node\Stmt\Class_::MODIFIER_READONLY);
$param = $visibility
? ($function->addPromotedParameter($item->var->name))->setVisibility($visibility)->setReadonly($isReadonly)
: $function->addParameter($item->var->name);
$param->setType($item->type ? $this->toPhp($item->type) : null);
$param->setReference($item->byRef);
$function->setVariadic($item->variadic);
if ($item->default) {
$param->setDefaultValue(new Literal($this->getReformattedContents([$item->default], 2)));
}
$this->addCommentAndAttributes($param, $item);
}
$this->addCommentAndAttributes($function, $node);
if ($node->getStmts()) {
$function->setBody($this->getReformattedContents($node->getStmts(), 2));
}
}
private function toVisibility(int $flags): ?string
{
if ($flags & Node\Stmt\Class_::MODIFIER_PUBLIC) {
return ClassType::VisibilityPublic;
} elseif ($flags & Node\Stmt\Class_::MODIFIER_PROTECTED) {
return ClassType::VisibilityProtected;
} elseif ($flags & Node\Stmt\Class_::MODIFIER_PRIVATE) {
return ClassType::VisibilityPrivate;
}
return null;
}
private function toPhp($value): string
{
return $this->printer->prettyPrint([$value]);
}
private function getNodeContents(Node ...$nodes): string
{
$start = $this->getNodeStartPos($nodes[0]);
return substr($this->code, $start, end($nodes)->getEndFilePos() - $start + 1);
}
private function getNodeStartPos(Node $node): int
{
return ($comments = $node->getComments())
? $comments[0]->getStartFilePos()
: $node->getStartFilePos();
}
}

View File

@@ -0,0 +1,368 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
use Nette\Utils\Reflection;
/**
* Creates a representation based on reflection.
*/
final class Factory
{
use Nette\SmartObject;
private $bodyCache = [];
private $extractorCache = [];
public function fromClassReflection(
\ReflectionClass $from,
bool $withBodies = false,
bool $materializeTraits = true
): ClassType {
if ($withBodies && $from->isAnonymous()) {
throw new Nette\NotSupportedException('The $withBodies parameter cannot be used for anonymous functions.');
}
$class = $from->isAnonymous()
? new ClassType
: new ClassType($from->getShortName(), new PhpNamespace($from->getNamespaceName()));
if (PHP_VERSION_ID >= 80100 && $from->isEnum()) {
$class->setType($class::TYPE_ENUM);
$from = new \ReflectionEnum($from->getName());
$enumIface = $from->isBacked() ? \BackedEnum::class : \UnitEnum::class;
} else {
$class->setType($from->isInterface() ? $class::TYPE_INTERFACE : ($from->isTrait() ? $class::TYPE_TRAIT : $class::TYPE_CLASS));
$class->setFinal($from->isFinal() && $class->isClass());
$class->setAbstract($from->isAbstract() && $class->isClass());
$enumIface = null;
}
$ifaces = $from->getInterfaceNames();
foreach ($ifaces as $iface) {
$ifaces = array_filter($ifaces, function (string $item) use ($iface): bool {
return !is_subclass_of($iface, $item);
});
}
if ($from->isInterface()) {
$class->setExtends($ifaces);
} else {
$ifaces = array_diff($ifaces, [$enumIface]);
$class->setImplements($ifaces);
}
$class->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
$class->setAttributes($this->getAttributes($from));
if ($from->getParentClass()) {
$class->setExtends($from->getParentClass()->name);
$class->setImplements(array_diff($class->getImplements(), $from->getParentClass()->getInterfaceNames()));
}
$props = [];
foreach ($from->getProperties() as $prop) {
$declaringClass = $materializeTraits
? $prop->getDeclaringClass()
: Reflection::getPropertyDeclaringClass($prop);
if ($prop->isDefault()
&& $declaringClass->name === $from->name
&& (PHP_VERSION_ID < 80000 || !$prop->isPromoted())
&& !$class->isEnum()
) {
$props[] = $this->fromPropertyReflection($prop);
}
}
$class->setProperties($props);
$methods = $resolutions = [];
foreach ($from->getMethods() as $method) {
$realMethod = Reflection::getMethodDeclaringMethod($method);
$declaringClass = ($materializeTraits ? $method : $realMethod)->getDeclaringClass();
if (
$declaringClass->name === $from->name
&& (!$enumIface || !method_exists($enumIface, $method->name))
) {
$methods[] = $m = $this->fromMethodReflection($method);
if ($withBodies) {
$realMethodClass = $realMethod->getDeclaringClass();
$bodies = &$this->bodyCache[$realMethodClass->name];
$bodies = $bodies ?? $this->getExtractor($realMethodClass)->extractMethodBodies($realMethodClass->name);
if (isset($bodies[$realMethod->name])) {
$m->setBody($bodies[$realMethod->name]);
}
}
}
$modifier = $realMethod->getModifiers() !== $method->getModifiers()
? ' ' . $this->getVisibility($method)
: null;
$alias = $realMethod->name !== $method->name ? ' ' . $method->name : '';
if ($modifier || $alias) {
$resolutions[] = $realMethod->name . ' as' . $modifier . $alias;
}
}
$class->setMethods($methods);
if (!$materializeTraits) {
foreach ($from->getTraitNames() as $trait) {
$class->addTrait($trait, $resolutions);
$resolutions = [];
}
}
$consts = $cases = [];
foreach ($from->getReflectionConstants() as $const) {
if ($class->isEnum() && $from->hasCase($const->name)) {
$cases[] = $this->fromCaseReflection($const);
} elseif ($const->getDeclaringClass()->name === $from->name) {
$consts[] = $this->fromConstantReflection($const);
}
}
$class->setConstants($consts);
$class->setCases($cases);
return $class;
}
public function fromMethodReflection(\ReflectionMethod $from): Method
{
$method = new Method($from->name);
$method->setParameters(array_map([$this, 'fromParameterReflection'], $from->getParameters()));
$method->setStatic($from->isStatic());
$isInterface = $from->getDeclaringClass()->isInterface();
$method->setVisibility($isInterface ? null : $this->getVisibility($from));
$method->setFinal($from->isFinal());
$method->setAbstract($from->isAbstract() && !$isInterface);
$method->setBody($from->isAbstract() ? null : '');
$method->setReturnReference($from->returnsReference());
$method->setVariadic($from->isVariadic());
$method->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
$method->setAttributes($this->getAttributes($from));
if ($from->getReturnType() instanceof \ReflectionNamedType) {
$method->setReturnType($from->getReturnType()->getName());
$method->setReturnNullable($from->getReturnType()->allowsNull());
} elseif (
$from->getReturnType() instanceof \ReflectionUnionType
|| $from->getReturnType() instanceof \ReflectionIntersectionType
) {
$method->setReturnType((string) $from->getReturnType());
}
return $method;
}
/** @return GlobalFunction|Closure */
public function fromFunctionReflection(\ReflectionFunction $from, bool $withBody = false)
{
$function = $from->isClosure() ? new Closure : new GlobalFunction($from->name);
$function->setParameters(array_map([$this, 'fromParameterReflection'], $from->getParameters()));
$function->setReturnReference($from->returnsReference());
$function->setVariadic($from->isVariadic());
if (!$from->isClosure()) {
$function->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
}
$function->setAttributes($this->getAttributes($from));
if ($from->getReturnType() instanceof \ReflectionNamedType) {
$function->setReturnType($from->getReturnType()->getName());
$function->setReturnNullable($from->getReturnType()->allowsNull());
} elseif (
$from->getReturnType() instanceof \ReflectionUnionType
|| $from->getReturnType() instanceof \ReflectionIntersectionType
) {
$function->setReturnType((string) $from->getReturnType());
}
if ($withBody) {
if ($from->isClosure()) {
throw new Nette\NotSupportedException('The $withBody parameter cannot be used for closures.');
}
$function->setBody($this->getExtractor($from)->extractFunctionBody($from->name));
}
return $function;
}
/** @return Method|GlobalFunction|Closure */
public function fromCallable(callable $from)
{
$ref = Nette\Utils\Callback::toReflection($from);
return $ref instanceof \ReflectionMethod
? $this->fromMethodReflection($ref)
: $this->fromFunctionReflection($ref);
}
public function fromParameterReflection(\ReflectionParameter $from): Parameter
{
$param = PHP_VERSION_ID >= 80000 && $from->isPromoted()
? new PromotedParameter($from->name)
: new Parameter($from->name);
$param->setReference($from->isPassedByReference());
if ($from->getType() instanceof \ReflectionNamedType) {
$param->setType($from->getType()->getName());
$param->setNullable($from->getType()->allowsNull());
} elseif (
$from->getType() instanceof \ReflectionUnionType
|| $from->getType() instanceof \ReflectionIntersectionType
) {
$param->setType((string) $from->getType());
}
if ($from->isDefaultValueAvailable()) {
if ($from->isDefaultValueConstant()) {
$parts = explode('::', $from->getDefaultValueConstantName());
if (count($parts) > 1) {
$parts[0] = Helpers::tagName($parts[0]);
}
$param->setDefaultValue(new Literal(implode('::', $parts)));
} elseif (is_object($from->getDefaultValue())) {
$param->setDefaultValue($this->fromObject($from->getDefaultValue()));
} else {
$param->setDefaultValue($from->getDefaultValue());
}
$param->setNullable($param->isNullable() && $param->getDefaultValue() !== null);
}
$param->setAttributes($this->getAttributes($from));
return $param;
}
public function fromConstantReflection(\ReflectionClassConstant $from): Constant
{
$const = new Constant($from->name);
$const->setValue($from->getValue());
$const->setVisibility($this->getVisibility($from));
$const->setFinal(PHP_VERSION_ID >= 80100 ? $from->isFinal() : false);
$const->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
$const->setAttributes($this->getAttributes($from));
return $const;
}
public function fromCaseReflection(\ReflectionClassConstant $from): EnumCase
{
$const = new EnumCase($from->name);
$const->setValue($from->getValue()->value ?? null);
$const->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
$const->setAttributes($this->getAttributes($from));
return $const;
}
public function fromPropertyReflection(\ReflectionProperty $from): Property
{
$defaults = $from->getDeclaringClass()->getDefaultProperties();
$prop = new Property($from->name);
$prop->setValue($defaults[$prop->getName()] ?? null);
$prop->setStatic($from->isStatic());
$prop->setVisibility($this->getVisibility($from));
if (PHP_VERSION_ID >= 70400) {
if ($from->getType() instanceof \ReflectionNamedType) {
$prop->setType($from->getType()->getName());
$prop->setNullable($from->getType()->allowsNull());
} elseif (
$from->getType() instanceof \ReflectionUnionType
|| $from->getType() instanceof \ReflectionIntersectionType
) {
$prop->setType((string) $from->getType());
}
$prop->setInitialized($from->hasType() && array_key_exists($prop->getName(), $defaults));
$prop->setReadOnly(PHP_VERSION_ID >= 80100 ? $from->isReadOnly() : false);
} else {
$prop->setInitialized(false);
}
$prop->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
$prop->setAttributes($this->getAttributes($from));
return $prop;
}
public function fromObject(object $obj): Literal
{
return new Literal('new \\' . get_class($obj) . '(/* unknown */)');
}
public function fromClassCode(string $code): ClassType
{
$classes = $this->fromCode($code)->getClasses();
if (!$classes) {
throw new Nette\InvalidStateException('The code does not contain any class.');
}
return reset($classes);
}
public function fromCode(string $code): PhpFile
{
$reader = new Extractor($code);
return $reader->extractAll();
}
private function getAttributes($from): array
{
if (PHP_VERSION_ID < 80000) {
return [];
}
return array_map(function ($attr) {
$args = $attr->getArguments();
foreach ($args as &$arg) {
if (is_object($arg)) {
$arg = $this->fromObject($arg);
}
}
return new Attribute($attr->getName(), $args);
}, $from->getAttributes());
}
private function getVisibility($from): string
{
return $from->isPrivate()
? ClassType::VisibilityPrivate
: ($from->isProtected() ? ClassType::VisibilityProtected : ClassType::VisibilityPublic);
}
private function getExtractor($from): Extractor
{
$file = $from->getFileName();
$cache = &$this->extractorCache[$file];
if ($cache !== null) {
return $cache;
} elseif (!$file) {
throw new Nette\InvalidStateException("Source code of $from->name not found.");
}
return new Extractor(file_get_contents($file));
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
/**
* Global function.
*
* @property string $body
*/
final class GlobalFunction
{
use Nette\SmartObject;
use Traits\FunctionLike;
use Traits\NameAware;
use Traits\CommentAware;
use Traits\AttributeAware;
public static function from(string $function, bool $withBody = false): self
{
return (new Factory)->fromFunctionReflection(new \ReflectionFunction($function), $withBody);
}
public static function withBodyFrom(string $function): self
{
return (new Factory)->fromFunctionReflection(new \ReflectionFunction($function), true);
}
public function __toString(): string
{
try {
return (new Printer)->printFunction($this);
} catch (\Throwable $e) {
if (PHP_VERSION_ID >= 70400) {
throw $e;
}
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
return '';
}
}
}

View File

@@ -0,0 +1,184 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
/**
* @internal
*/
final class Helpers
{
use Nette\StaticClass;
public const ReIdentifier = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*';
public const Keywords = [
// built-in types
'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1,
'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1, 'mixed' => 1, 'false' => 1,
'never' => 1,
// class keywords
'self' => 1, 'parent' => 1, 'static' => 1,
// PHP keywords
'include' => 1, 'include_once' => 1, 'eval' => 1, 'require' => 1, 'require_once' => 1, 'or' => 1, 'xor' => 1,
'and' => 1, 'instanceof' => 1, 'new' => 1, 'clone' => 1, 'exit' => 1, 'if' => 1, 'elseif' => 1, 'else' => 1,
'endif' => 1, 'echo' => 1, 'do' => 1, 'while' => 1, 'endwhile' => 1, 'for' => 1, 'endfor' => 1, 'foreach' => 1,
'endforeach' => 1, 'declare' => 1, 'enddeclare' => 1, 'as' => 1, 'try' => 1, 'catch' => 1, 'finally' => 1,
'throw' => 1, 'use' => 1, 'insteadof' => 1, 'global' => 1, 'var' => 1, 'unset' => 1, 'isset' => 1, 'empty' => 1,
'continue' => 1, 'goto' => 1, 'function' => 1, 'const' => 1, 'return' => 1, 'print' => 1, 'yield' => 1, 'list' => 1,
'switch' => 1, 'endswitch' => 1, 'case' => 1, 'default' => 1, 'break' => 1,
'extends' => 1, 'implements' => 1, 'namespace' => 1, 'trait' => 1, 'interface' => 1, 'class' => 1, '__CLASS__' => 1,
'__TRAIT__' => 1, '__FUNCTION__' => 1, '__METHOD__' => 1, '__LINE__' => 1, '__FILE__' => 1, '__DIR__' => 1,
'__NAMESPACE__' => 1, 'fn' => 1, 'match' => 1, 'enum' => 1, 'abstract' => 1, 'final' => 1,
'private' => 1, 'protected' => 1, 'public' => 1, 'readonly' => 1,
// additional reserved class names
'true' => 1,
];
/** @deprecated */
public const
PHP_IDENT = self::ReIdentifier,
KEYWORDS = self::Keywords;
/** @deprecated use (new Nette\PhpGenerator\Dumper)->dump() */
public static function dump($var): string
{
return (new Dumper)->dump($var);
}
/** @deprecated use (new Nette\PhpGenerator\Dumper)->format() */
public static function format(string $statement, ...$args): string
{
return (new Dumper)->format($statement, ...$args);
}
/** @deprecated use (new Nette\PhpGenerator\Dumper)->format() */
public static function formatArgs(string $statement, array $args): string
{
return (new Dumper)->format($statement, ...$args);
}
public static function formatDocComment(string $content): string
{
$s = trim($content);
$s = str_replace('*/', '* /', $s);
if ($s === '') {
return '';
} elseif (strpos($content, "\n") === false) {
return "/** $s */\n";
} else {
return str_replace("\n", "\n * ", "/**\n$s") . "\n */\n";
}
}
public static function tagName(string $name, string $of = PhpNamespace::NameNormal): string
{
return isset(self::Keywords[strtolower($name)])
? $name
: "/*($of*/$name";
}
public static function simplifyTaggedNames(string $code, ?PhpNamespace $namespace): string
{
return preg_replace_callback('~/\*\(([ncf])\*/([\w\x7f-\xff\\\\]++)~', function ($m) use ($namespace) {
[, $of, $name] = $m;
return $namespace
? $namespace->simplifyType($name, $of)
: $name;
}, $code);
}
public static function unformatDocComment(string $comment): string
{
return preg_replace('#^\s*\* ?#m', '', trim(trim(trim($comment), '/*')));
}
public static function unindent(string $s, int $level = 1): string
{
return $level
? preg_replace('#^(\t| {4}){1,' . $level . '}#m', '', $s)
: $s;
}
public static function isIdentifier($value): bool
{
return is_string($value) && preg_match('#^' . self::ReIdentifier . '$#D', $value);
}
public static function isNamespaceIdentifier($value, bool $allowLeadingSlash = false): bool
{
$re = '#^' . ($allowLeadingSlash ? '\\\\?' : '') . self::ReIdentifier . '(\\\\' . self::ReIdentifier . ')*$#D';
return is_string($value) && preg_match($re, $value);
}
public static function extractNamespace(string $name): string
{
return ($pos = strrpos($name, '\\')) ? substr($name, 0, $pos) : '';
}
public static function extractShortName(string $name): string
{
return ($pos = strrpos($name, '\\')) === false
? $name
: substr($name, $pos + 1);
}
public static function tabsToSpaces(string $s, int $count = 4): string
{
return str_replace("\t", str_repeat(' ', $count), $s);
}
/** @internal */
public static function createObject(string $class, array $props): object
{
return Dumper::createObject($class, $props);
}
public static function validateType(?string $type, bool &$nullable): ?string
{
if ($type === '' || $type === null) {
return null;
}
if (!preg_match('#(?:
\?[\w\\\\]+|
[\w\\\\]+ (?: (&[\w\\\\]+)* | (\|[\w\\\\]+)* )
)()$#xAD', $type)) {
throw new Nette\InvalidArgumentException("Value '$type' is not valid type.");
}
if ($type[0] === '?') {
$nullable = true;
return substr($type, 1);
}
return $type;
}
}

View File

@@ -0,0 +1,45 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
/**
* PHP literal value.
*/
class Literal
{
/** @var string */
private $value;
/** @var ?array */
private $args;
public function __construct(string $value, ?array $args = null)
{
$this->value = $value;
$this->args = $args;
}
public function __toString(): string
{
return $this->formatWith(new Dumper);
}
/** @internal */
public function formatWith(Dumper $dumper): string
{
return $this->args === null
? $this->value
: $dumper->format($this->value, ...$this->args);
}
}

View File

@@ -0,0 +1,145 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
/**
* Class method.
*
* @property string|null $body
*/
final class Method
{
use Nette\SmartObject;
use Traits\FunctionLike;
use Traits\NameAware;
use Traits\VisibilityAware;
use Traits\CommentAware;
use Traits\AttributeAware;
/** @var string|null */
private $body = '';
/** @var bool */
private $static = false;
/** @var bool */
private $final = false;
/** @var bool */
private $abstract = false;
/**
* @param string|array $method
*/
public static function from($method): self
{
return (new Factory)->fromMethodReflection(Nette\Utils\Callback::toReflection($method));
}
public function __toString(): string
{
try {
return (new Printer)->printMethod($this);
} catch (\Throwable $e) {
if (PHP_VERSION_ID >= 70400) {
throw $e;
}
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
return '';
}
}
/** @return static */
public function setBody(?string $code, ?array $args = null): self
{
$this->body = $args === null || $code === null
? $code
: (new Dumper)->format($code, ...$args);
return $this;
}
public function getBody(): ?string
{
return $this->body;
}
/** @return static */
public function setStatic(bool $state = true): self
{
$this->static = $state;
return $this;
}
public function isStatic(): bool
{
return $this->static;
}
/** @return static */
public function setFinal(bool $state = true): self
{
$this->final = $state;
return $this;
}
public function isFinal(): bool
{
return $this->final;
}
/** @return static */
public function setAbstract(bool $state = true): self
{
$this->abstract = $state;
return $this;
}
public function isAbstract(): bool
{
return $this->abstract;
}
/**
* @param string $name without $
*/
public function addPromotedParameter(string $name, $defaultValue = null): PromotedParameter
{
$param = new PromotedParameter($name);
if (func_num_args() > 1) {
$param->setDefaultValue($defaultValue);
}
return $this->parameters[$name] = $param;
}
/** @throws Nette\InvalidStateException */
public function validate(): void
{
if ($this->abstract && ($this->final || $this->visibility === ClassType::VisibilityPrivate)) {
throw new Nette\InvalidStateException("Method $this->name() cannot be abstract and final or private at the same time.");
}
}
}

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;
use Nette\Utils\Type;
/**
* Function/Method parameter description.
*
* @property mixed $defaultValue
*/
class Parameter
{
use Nette\SmartObject;
use Traits\NameAware;
use Traits\AttributeAware;
/** @var bool */
private $reference = false;
/** @var string|null */
private $type;
/** @var bool */
private $nullable = false;
/** @var bool */
private $hasDefaultValue = false;
/** @var mixed */
private $defaultValue;
/** @return static */
public function setReference(bool $state = true): self
{
$this->reference = $state;
return $this;
}
public function isReference(): bool
{
return $this->reference;
}
/** @return static */
public function setType(?string $type): self
{
$this->type = Helpers::validateType($type, $this->nullable);
return $this;
}
/**
* @return Type|string|null
*/
public function getType(bool $asObject = false)
{
return $asObject && $this->type
? Type::fromString($this->type)
: $this->type;
}
/** @deprecated use setType() */
public function setTypeHint(?string $type): self
{
return $this->setType($type);
}
/** @deprecated use getType() */
public function getTypeHint(): ?string
{
return $this->getType();
}
/**
* @deprecated just use setDefaultValue()
* @return static
*/
public function setOptional(bool $state = true): self
{
trigger_error(__METHOD__ . '() is deprecated, use setDefaultValue()', E_USER_DEPRECATED);
$this->hasDefaultValue = $state;
return $this;
}
/** @return static */
public function setNullable(bool $state = true): self
{
$this->nullable = $state;
return $this;
}
public function isNullable(): bool
{
return $this->nullable;
}
/** @return static */
public function setDefaultValue($val): self
{
$this->defaultValue = $val;
$this->hasDefaultValue = true;
return $this;
}
public function getDefaultValue()
{
return $this->defaultValue;
}
public function hasDefaultValue(): bool
{
return $this->hasDefaultValue;
}
public function validate(): void
{
}
}

View File

@@ -0,0 +1,184 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
/**
* Instance of PHP file.
*
* Generates:
* - opening tag (<?php)
* - doc comments
* - one or more namespaces
*/
final class PhpFile
{
use Nette\SmartObject;
use Traits\CommentAware;
/** @var PhpNamespace[] */
private $namespaces = [];
/** @var bool */
private $strictTypes = false;
public static function fromCode(string $code): self
{
return (new Factory)->fromCode($code);
}
public function addClass(string $name): ClassType
{
return $this
->addNamespace(Helpers::extractNamespace($name))
->addClass(Helpers::extractShortName($name));
}
public function addInterface(string $name): ClassType
{
return $this
->addNamespace(Helpers::extractNamespace($name))
->addInterface(Helpers::extractShortName($name));
}
public function addTrait(string $name): ClassType
{
return $this
->addNamespace(Helpers::extractNamespace($name))
->addTrait(Helpers::extractShortName($name));
}
public function addEnum(string $name): ClassType
{
return $this
->addNamespace(Helpers::extractNamespace($name))
->addEnum(Helpers::extractShortName($name));
}
/** @param string|PhpNamespace $namespace */
public function addNamespace($namespace): PhpNamespace
{
if ($namespace instanceof PhpNamespace) {
$res = $this->namespaces[$namespace->getName()] = $namespace;
} elseif (is_string($namespace)) {
$res = $this->namespaces[$namespace] = $this->namespaces[$namespace] ?? new PhpNamespace($namespace);
} else {
throw new Nette\InvalidArgumentException('Argument must be string|PhpNamespace.');
}
foreach ($this->namespaces as $namespace) {
$namespace->setBracketedSyntax(count($this->namespaces) > 1 && isset($this->namespaces['']));
}
return $res;
}
public function addFunction(string $name): GlobalFunction
{
return $this
->addNamespace(Helpers::extractNamespace($name))
->addFunction(Helpers::extractShortName($name));
}
/** @return PhpNamespace[] */
public function getNamespaces(): array
{
return $this->namespaces;
}
/** @return ClassType[] */
public function getClasses(): array
{
$classes = [];
foreach ($this->namespaces as $n => $namespace) {
$n .= $n ? '\\' : '';
foreach ($namespace->getClasses() as $c => $class) {
$classes[$n . $c] = $class;
}
}
return $classes;
}
/** @return GlobalFunction[] */
public function getFunctions(): array
{
$functions = [];
foreach ($this->namespaces as $n => $namespace) {
$n .= $n ? '\\' : '';
foreach ($namespace->getFunctions() as $f => $function) {
$functions[$n . $f] = $function;
}
}
return $functions;
}
/** @return static */
public function addUse(string $name, ?string $alias = null, string $of = PhpNamespace::NameNormal): self
{
$this->addNamespace('')->addUse($name, $alias, $of);
return $this;
}
/**
* Adds declare(strict_types=1) to output.
* @return static
*/
public function setStrictTypes(bool $on = true): self
{
$this->strictTypes = $on;
return $this;
}
public function hasStrictTypes(): bool
{
return $this->strictTypes;
}
/** @deprecated use hasStrictTypes() */
public function getStrictTypes(): bool
{
return $this->strictTypes;
}
public function __toString(): string
{
try {
return (new Printer)->printFile($this);
} catch (\Throwable $e) {
if (PHP_VERSION_ID >= 70400) {
throw $e;
}
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
return '';
}
}
}

View File

@@ -0,0 +1,15 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
class PhpLiteral extends Literal
{
}

View File

@@ -0,0 +1,367 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
use Nette\InvalidStateException;
/**
* Namespaced part of a PHP file.
*
* Generates:
* - namespace statement
* - variable amount of use statements
* - one or more class declarations
*/
final class PhpNamespace
{
use Nette\SmartObject;
public const
NameNormal = 'n',
NameFunction = 'f',
NameConstant = 'c';
public const
NAME_NORMAL = self::NameNormal,
NAME_FUNCTION = self::NameFunction,
NAME_CONSTANT = self::NameConstant;
/** @var string */
private $name;
/** @var bool */
private $bracketedSyntax = false;
/** @var string[][] */
private $aliases = [
self::NameNormal => [],
self::NameFunction => [],
self::NameConstant => [],
];
/** @var ClassType[] */
private $classes = [];
/** @var GlobalFunction[] */
private $functions = [];
public function __construct(string $name)
{
if ($name !== '' && !Helpers::isNamespaceIdentifier($name)) {
throw new Nette\InvalidArgumentException("Value '$name' is not valid name.");
}
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
/**
* @return static
* @internal
*/
public function setBracketedSyntax(bool $state = true): self
{
$this->bracketedSyntax = $state;
return $this;
}
public function hasBracketedSyntax(): bool
{
return $this->bracketedSyntax;
}
/** @deprecated use hasBracketedSyntax() */
public function getBracketedSyntax(): bool
{
return $this->bracketedSyntax;
}
/**
* @throws InvalidStateException
* @return static
*/
public function addUse(string $name, ?string $alias = null, string $of = self::NameNormal): self
{
if (
!Helpers::isNamespaceIdentifier($name, true)
|| (Helpers::isIdentifier($name) && isset(Helpers::Keywords[strtolower($name)]))
) {
throw new Nette\InvalidArgumentException("Value '$name' is not valid class/function/constant name.");
} elseif ($alias && (!Helpers::isIdentifier($alias) || isset(Helpers::Keywords[strtolower($alias)]))) {
throw new Nette\InvalidArgumentException("Value '$alias' is not valid alias.");
}
$name = ltrim($name, '\\');
$aliases = array_change_key_case($this->aliases[$of]);
$used = [self::NameNormal => $this->classes, self::NameFunction => $this->functions, self::NameConstant => []][$of];
if ($alias === null) {
$base = Helpers::extractShortName($name);
$counter = null;
do {
$alias = $base . $counter;
$lower = strtolower($alias);
$counter++;
} while ((isset($aliases[$lower]) && strcasecmp($aliases[$lower], $name) !== 0) || isset($used[$lower]));
} else {
$lower = strtolower($alias);
if (isset($aliases[$lower]) && strcasecmp($aliases[$lower], $name) !== 0) {
throw new InvalidStateException(
"Alias '$alias' used already for '{$aliases[$lower]}', cannot use for '$name'."
);
} elseif (isset($used[$lower])) {
throw new Nette\InvalidStateException("Name '$alias' used already for '$this->name\\{$used[$lower]->getName()}'.");
}
}
$this->aliases[$of][$alias] = $name;
return $this;
}
public function removeUse(string $name, string $of = self::NameNormal): void
{
foreach ($this->aliases[$of] as $alias => $item) {
if (strcasecmp($item, $name) === 0) {
unset($this->aliases[$of][$alias]);
}
}
}
/** @return static */
public function addUseFunction(string $name, ?string $alias = null): self
{
return $this->addUse($name, $alias, self::NameFunction);
}
/** @return static */
public function addUseConstant(string $name, ?string $alias = null): self
{
return $this->addUse($name, $alias, self::NameConstant);
}
/** @return string[] */
public function getUses(string $of = self::NameNormal): array
{
asort($this->aliases[$of]);
return array_filter(
$this->aliases[$of],
function ($name, $alias) { return strcasecmp(($this->name ? $this->name . '\\' : '') . $alias, $name); },
ARRAY_FILTER_USE_BOTH
);
}
/** @deprecated use simplifyName() */
public function unresolveName(string $name): string
{
return $this->simplifyName($name);
}
public function resolveName(string $name, string $of = self::NameNormal): string
{
if (isset(Helpers::Keywords[strtolower($name)]) || $name === '') {
return $name;
} elseif ($name[0] === '\\') {
return substr($name, 1);
}
$aliases = array_change_key_case($this->aliases[$of]);
if ($of !== self::NameNormal) {
return $aliases[strtolower($name)]
?? $this->resolveName(Helpers::extractNamespace($name) . '\\') . Helpers::extractShortName($name);
}
$parts = explode('\\', $name, 2);
return ($res = $aliases[strtolower($parts[0])] ?? null)
? $res . (isset($parts[1]) ? '\\' . $parts[1] : '')
: $this->name . ($this->name ? '\\' : '') . $name;
}
public function simplifyType(string $type, string $of = self::NameNormal): string
{
return preg_replace_callback('~[\w\x7f-\xff\\\\]+~', function ($m) use ($of) { return $this->simplifyName($m[0], $of); }, $type);
}
public function simplifyName(string $name, string $of = self::NameNormal): string
{
if (isset(Helpers::Keywords[strtolower($name)]) || $name === '') {
return $name;
}
$name = ltrim($name, '\\');
if ($of !== self::NameNormal) {
foreach ($this->aliases[$of] as $alias => $original) {
if (strcasecmp($original, $name) === 0) {
return $alias;
}
}
return $this->simplifyName(Helpers::extractNamespace($name) . '\\') . Helpers::extractShortName($name);
}
$shortest = null;
$relative = self::startsWith($name, $this->name . '\\')
? substr($name, strlen($this->name) + 1)
: null;
foreach ($this->aliases[$of] as $alias => $original) {
if ($relative && self::startsWith($relative . '\\', $alias . '\\')) {
$relative = null;
}
if (self::startsWith($name . '\\', $original . '\\')) {
$short = $alias . substr($name, strlen($original));
if (!isset($shortest) || strlen($shortest) > strlen($short)) {
$shortest = $short;
}
}
}
if (isset($shortest, $relative) && strlen($shortest) < strlen($relative)) {
return $shortest;
}
return $relative ?? $shortest ?? ($this->name ? '\\' : '') . $name;
}
/** @return static */
public function add(ClassType $class): self
{
$name = $class->getName();
if ($name === null) {
throw new Nette\InvalidArgumentException('Class does not have a name.');
}
$lower = strtolower($name);
if ($orig = array_change_key_case($this->aliases[self::NameNormal])[$lower] ?? null) {
throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig.");
}
$this->classes[$lower] = $class;
return $this;
}
public function addClass(string $name): ClassType
{
$this->add($class = new ClassType($name, $this));
return $class;
}
public function addInterface(string $name): ClassType
{
return $this->addClass($name)->setType(ClassType::TYPE_INTERFACE);
}
public function addTrait(string $name): ClassType
{
return $this->addClass($name)->setType(ClassType::TYPE_TRAIT);
}
public function addEnum(string $name): ClassType
{
return $this->addClass($name)->setType(ClassType::TYPE_ENUM);
}
public function removeClass(string $name): self
{
unset($this->classes[strtolower($name)]);
return $this;
}
public function addFunction(string $name): GlobalFunction
{
$lower = strtolower($name);
if ($orig = array_change_key_case($this->aliases[self::NameFunction])[$lower] ?? null) {
throw new Nette\InvalidStateException("Name '$name' used already as alias for $orig.");
}
return $this->functions[$lower] = new GlobalFunction($name);
}
public function removeFunction(string $name): self
{
unset($this->functions[strtolower($name)]);
return $this;
}
/** @return ClassType[] */
public function getClasses(): array
{
$res = [];
foreach ($this->classes as $class) {
$res[$class->getName()] = $class;
}
return $res;
}
/** @return GlobalFunction[] */
public function getFunctions(): array
{
$res = [];
foreach ($this->functions as $fn) {
$res[$fn->getName()] = $fn;
}
return $res;
}
private static function startsWith(string $a, string $b): bool
{
return strncasecmp($a, $b, strlen($b)) === 0;
}
public function __toString(): string
{
try {
return (new Printer)->printNamespace($this);
} catch (\Throwable $e) {
if (PHP_VERSION_ID >= 70400) {
throw $e;
}
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
return '';
}
}
}

View File

@@ -0,0 +1,424 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
use Nette\Utils\Strings;
/**
* Generates PHP code.
*/
class Printer
{
use Nette\SmartObject;
/** @var int */
public $wrapLength = 120;
/** @var string */
protected $indentation = "\t";
/** @var int */
protected $linesBetweenProperties = 0;
/** @var int */
protected $linesBetweenMethods = 2;
/** @var string */
protected $returnTypeColon = ': ';
/** @var ?PhpNamespace */
protected $namespace;
/** @var ?Dumper */
protected $dumper;
/** @var bool */
private $resolveTypes = true;
public function __construct()
{
$this->dumper = new Dumper;
}
public function printFunction(GlobalFunction $function, ?PhpNamespace $namespace = null): string
{
$this->namespace = $this->resolveTypes ? $namespace : null;
$line = 'function '
. ($function->getReturnReference() ? '&' : '')
. $function->getName();
$returnType = $this->printReturnType($function);
$body = Helpers::simplifyTaggedNames($function->getBody(), $this->namespace);
return Helpers::formatDocComment($function->getComment() . "\n")
. $this->printAttributes($function->getAttributes())
. $line
. $this->printParameters($function, strlen($line) + strlen($returnType) + 2) // 2 = parentheses
. $returnType
. "\n{\n" . $this->indent(ltrim(rtrim($body) . "\n")) . "}\n";
}
public function printClosure(Closure $closure, ?PhpNamespace $namespace = null): string
{
$this->namespace = $this->resolveTypes ? $namespace : null;
$uses = [];
foreach ($closure->getUses() as $param) {
$uses[] = ($param->isReference() ? '&' : '') . '$' . $param->getName();
}
$useStr = strlen($tmp = implode(', ', $uses)) > $this->wrapLength && count($uses) > 1
? "\n" . $this->indentation . implode(",\n" . $this->indentation, $uses) . "\n"
: $tmp;
$body = Helpers::simplifyTaggedNames($closure->getBody(), $this->namespace);
return $this->printAttributes($closure->getAttributes(), true)
. 'function '
. ($closure->getReturnReference() ? '&' : '')
. $this->printParameters($closure)
. ($uses ? " use ($useStr)" : '')
. $this->printReturnType($closure)
. " {\n" . $this->indent(ltrim(rtrim($body) . "\n")) . '}';
}
public function printArrowFunction(Closure $closure, ?PhpNamespace $namespace = null): string
{
$this->namespace = $this->resolveTypes ? $namespace : null;
foreach ($closure->getUses() as $use) {
if ($use->isReference()) {
throw new Nette\InvalidArgumentException('Arrow function cannot bind variables by-reference.');
}
}
$body = Helpers::simplifyTaggedNames($closure->getBody(), $this->namespace);
return $this->printAttributes($closure->getAttributes())
. 'fn'
. ($closure->getReturnReference() ? '&' : '')
. $this->printParameters($closure)
. $this->printReturnType($closure)
. ' => ' . trim($body) . ';';
}
public function printMethod(Method $method, ?PhpNamespace $namespace = null): string
{
$this->namespace = $this->resolveTypes ? $namespace : null;
$method->validate();
$line = ($method->isAbstract() ? 'abstract ' : '')
. ($method->isFinal() ? 'final ' : '')
. ($method->getVisibility() ? $method->getVisibility() . ' ' : '')
. ($method->isStatic() ? 'static ' : '')
. 'function '
. ($method->getReturnReference() ? '&' : '')
. $method->getName();
$returnType = $this->printReturnType($method);
$params = $this->printParameters($method, strlen($line) + strlen($returnType) + strlen($this->indentation) + 2);
$body = Helpers::simplifyTaggedNames((string) $method->getBody(), $this->namespace);
return Helpers::formatDocComment($method->getComment() . "\n")
. $this->printAttributes($method->getAttributes())
. $line
. $params
. $returnType
. ($method->isAbstract() || $method->getBody() === null
? ";\n"
: (strpos($params, "\n") === false ? "\n" : ' ')
. "{\n"
. $this->indent(ltrim(rtrim($body) . "\n"))
. "}\n");
}
public function printClass(ClassType $class, ?PhpNamespace $namespace = null): string
{
$this->namespace = $this->resolveTypes ? $namespace : null;
$class->validate();
$resolver = $this->namespace
? [$namespace, 'simplifyType']
: function ($s) { return $s; };
$traits = [];
foreach ($class->getTraitResolutions() as $trait) {
$resolutions = $trait->getResolutions();
$traits[] = Helpers::formatDocComment((string) $trait->getComment())
. 'use ' . $resolver($trait->getName())
. ($resolutions
? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n"
: ";\n");
}
$cases = [];
foreach ($class->getCases() as $case) {
$cases[] = Helpers::formatDocComment((string) $case->getComment())
. $this->printAttributes($case->getAttributes())
. 'case ' . $case->getName()
. ($case->getValue() === null ? '' : ' = ' . $this->dump($case->getValue()))
. ";\n";
}
$enumType = isset($case) && $case->getValue() !== null
? $this->returnTypeColon . Type::getType($case->getValue())
: '';
$consts = [];
foreach ($class->getConstants() as $const) {
$def = ($const->isFinal() ? 'final ' : '')
. ($const->getVisibility() ? $const->getVisibility() . ' ' : '')
. 'const ' . $const->getName() . ' = ';
$consts[] = Helpers::formatDocComment((string) $const->getComment())
. $this->printAttributes($const->getAttributes())
. $def
. $this->dump($const->getValue(), strlen($def)) . ";\n";
}
$properties = [];
foreach ($class->getProperties() as $property) {
$property->validate();
$type = $property->getType();
$def = (($property->getVisibility() ?: 'public')
. ($property->isStatic() ? ' static' : '')
. ($property->isReadOnly() && $type ? ' readonly' : '')
. ' '
. ltrim($this->printType($type, $property->isNullable()) . ' ')
. '$' . $property->getName());
$properties[] = Helpers::formatDocComment((string) $property->getComment())
. $this->printAttributes($property->getAttributes())
. $def
. ($property->getValue() === null && !$property->isInitialized()
? ''
: ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = '
. ";\n";
}
$methods = [];
foreach ($class->getMethods() as $method) {
$methods[] = $this->printMethod($method, $namespace);
}
$members = array_filter([
implode('', $traits),
$this->joinProperties($cases),
$this->joinProperties($consts),
$this->joinProperties($properties),
($methods && $properties ? str_repeat("\n", $this->linesBetweenMethods - 1) : '')
. implode(str_repeat("\n", $this->linesBetweenMethods), $methods),
]);
return Strings::normalize(
Helpers::formatDocComment($class->getComment() . "\n")
. $this->printAttributes($class->getAttributes())
. ($class->isAbstract() ? 'abstract ' : '')
. ($class->isFinal() ? 'final ' : '')
. ($class->getName() ? $class->getType() . ' ' . $class->getName() . $enumType . ' ' : '')
. ($class->getExtends() ? 'extends ' . implode(', ', array_map($resolver, (array) $class->getExtends())) . ' ' : '')
. ($class->getImplements() ? 'implements ' . implode(', ', array_map($resolver, $class->getImplements())) . ' ' : '')
. ($class->getName() ? "\n" : '') . "{\n"
. ($members ? $this->indent(implode("\n", $members)) : '')
. '}'
) . ($class->getName() ? "\n" : '');
}
public function printNamespace(PhpNamespace $namespace): string
{
$this->namespace = $this->resolveTypes ? $namespace : null;
$name = $namespace->getName();
$uses = $this->printUses($namespace)
. $this->printUses($namespace, PhpNamespace::NameFunction)
. $this->printUses($namespace, PhpNamespace::NameConstant);
$items = [];
foreach ($namespace->getClasses() as $class) {
$items[] = $this->printClass($class, $namespace);
}
foreach ($namespace->getFunctions() as $function) {
$items[] = $this->printFunction($function, $namespace);
}
$body = ($uses ? $uses . "\n" : '')
. implode("\n", $items);
if ($namespace->hasBracketedSyntax()) {
return 'namespace' . ($name ? " $name" : '') . "\n{\n"
. $this->indent($body)
. "}\n";
} else {
return ($name ? "namespace $name;\n\n" : '')
. $body;
}
}
public function printFile(PhpFile $file): string
{
$namespaces = [];
foreach ($file->getNamespaces() as $namespace) {
$namespaces[] = $this->printNamespace($namespace);
}
return Strings::normalize(
"<?php\n"
. ($file->getComment() ? "\n" . Helpers::formatDocComment($file->getComment() . "\n") : '')
. "\n"
. ($file->hasStrictTypes() ? "declare(strict_types=1);\n\n" : '')
. implode("\n\n", $namespaces)
) . "\n";
}
protected function printUses(PhpNamespace $namespace, string $of = PhpNamespace::NameNormal): string
{
$prefix = [
PhpNamespace::NameNormal => '',
PhpNamespace::NameFunction => 'function ',
PhpNamespace::NameConstant => 'const ',
][$of];
$name = $namespace->getName();
$uses = [];
foreach ($namespace->getUses($of) as $alias => $original) {
$uses[] = Helpers::extractShortName($original) === $alias
? "use $prefix$original;\n"
: "use $prefix$original as $alias;\n";
}
return implode('', $uses);
}
/**
* @param Closure|GlobalFunction|Method $function
*/
protected function printParameters($function, int $column = 0): string
{
$params = [];
$list = $function->getParameters();
$special = false;
foreach ($list as $param) {
$param->validate();
$variadic = $function->isVariadic() && $param === end($list);
$type = $param->getType();
$promoted = $param instanceof PromotedParameter ? $param : null;
$params[] =
($promoted ? Helpers::formatDocComment((string) $promoted->getComment()) : '')
. ($attrs = $this->printAttributes($param->getAttributes(), true))
. ($promoted ?
($promoted->getVisibility() ?: 'public')
. ($promoted->isReadOnly() && $type ? ' readonly' : '')
. ' ' : '')
. ltrim($this->printType($type, $param->isNullable()) . ' ')
. ($param->isReference() ? '&' : '')
. ($variadic ? '...' : '')
. '$' . $param->getName()
. ($param->hasDefaultValue() && !$variadic ? ' = ' . $this->dump($param->getDefaultValue()) : '');
$special = $special || $promoted || $attrs;
}
$line = implode(', ', $params);
return count($params) > 1 && ($special || strlen($line) + $column > $this->wrapLength)
? "(\n" . $this->indent(implode(",\n", $params)) . ($special ? ',' : '') . "\n)"
: "($line)";
}
protected function printType(?string $type, bool $nullable): string
{
if ($type === null) {
return '';
}
if ($this->namespace) {
$type = $this->namespace->simplifyType($type);
}
if ($nullable && strcasecmp($type, 'mixed')) {
$type = strpos($type, '|') === false
? '?' . $type
: $type . '|null';
}
return $type;
}
/**
* @param Closure|GlobalFunction|Method $function
*/
private function printReturnType($function): string
{
return ($tmp = $this->printType($function->getReturnType(), $function->isReturnNullable()))
? $this->returnTypeColon . $tmp
: '';
}
private function printAttributes(array $attrs, bool $inline = false): string
{
if (!$attrs) {
return '';
}
$this->dumper->indentation = $this->indentation;
$items = [];
foreach ($attrs as $attr) {
$args = $this->dumper->format('...?:', $attr->getArguments());
$args = Helpers::simplifyTaggedNames($args, $this->namespace);
$items[] = $this->printType($attr->getName(), false) . ($args ? "($args)" : '');
}
return $inline
? '#[' . implode(', ', $items) . '] '
: '#[' . implode("]\n#[", $items) . "]\n";
}
/** @return static */
public function setTypeResolving(bool $state = true): self
{
$this->resolveTypes = $state;
return $this;
}
protected function indent(string $s): string
{
$s = str_replace("\t", $this->indentation, $s);
return Strings::indent($s, 1, $this->indentation);
}
protected function dump($var, int $column = 0): string
{
$this->dumper->indentation = $this->indentation;
$this->dumper->wrapLength = $this->wrapLength;
$s = $this->dumper->dump($var, $column);
$s = Helpers::simplifyTaggedNames($s, $this->namespace);
return $s;
}
private function joinProperties(array $props): string
{
return $this->linesBetweenProperties
? implode(str_repeat("\n", $this->linesBetweenProperties), $props)
: preg_replace('#^(\w.*\n)\n(?=\w.*;)#m', '$1', implode("\n", $props));
}
}

View File

@@ -0,0 +1,48 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
/**
* Promoted parameter in constructor.
*/
final class PromotedParameter extends Parameter
{
use Traits\VisibilityAware;
use Traits\CommentAware;
/** @var bool */
private $readOnly = false;
/** @return static */
public function setReadOnly(bool $state = true): self
{
$this->readOnly = $state;
return $this;
}
public function isReadOnly(): bool
{
return $this->readOnly;
}
/** @throws Nette\InvalidStateException */
public function validate(): void
{
if ($this->readOnly && !$this->getType()) {
throw new Nette\InvalidStateException("Property \${$this->getName()}: Read-only properties are only supported on typed property.");
}
}
}

View File

@@ -0,0 +1,145 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
use Nette\Utils\Type;
/**
* Class property description.
*
* @property mixed $value
*/
final class Property
{
use Nette\SmartObject;
use Traits\NameAware;
use Traits\VisibilityAware;
use Traits\CommentAware;
use Traits\AttributeAware;
/** @var mixed */
private $value;
/** @var bool */
private $static = false;
/** @var string|null */
private $type;
/** @var bool */
private $nullable = false;
/** @var bool */
private $initialized = false;
/** @var bool */
private $readOnly = false;
/** @return static */
public function setValue($val): self
{
$this->value = $val;
$this->initialized = true;
return $this;
}
public function &getValue()
{
return $this->value;
}
/** @return static */
public function setStatic(bool $state = true): self
{
$this->static = $state;
return $this;
}
public function isStatic(): bool
{
return $this->static;
}
/** @return static */
public function setType(?string $type): self
{
$this->type = Helpers::validateType($type, $this->nullable);
return $this;
}
/**
* @return Type|string|null
*/
public function getType(bool $asObject = false)
{
return $asObject && $this->type
? Type::fromString($this->type)
: $this->type;
}
/** @return static */
public function setNullable(bool $state = true): self
{
$this->nullable = $state;
return $this;
}
public function isNullable(): bool
{
return $this->nullable;
}
/** @return static */
public function setInitialized(bool $state = true): self
{
$this->initialized = $state;
return $this;
}
public function isInitialized(): bool
{
return $this->initialized || $this->value !== null;
}
/** @return static */
public function setReadOnly(bool $state = true): self
{
$this->readOnly = $state;
return $this;
}
public function isReadOnly(): bool
{
return $this->readOnly;
}
/** @throws Nette\InvalidStateException */
public function validate(): void
{
if ($this->readOnly && !$this->type) {
throw new Nette\InvalidStateException("Property \$$this->name: Read-only properties are only supported on typed property.");
}
}
}

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,49 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
/**
* use Trait
*/
final class TraitUse
{
use Nette\SmartObject;
use Traits\NameAware;
use Traits\CommentAware;
/** @var array */
private $resolutions = [];
public function __construct(string $name)
{
if (!Nette\PhpGenerator\Helpers::isNamespaceIdentifier($name, true)) {
throw new Nette\InvalidArgumentException("Value '$name' is not valid trait name.");
}
$this->name = $name;
}
public function addResolution(string $resolution): self
{
$this->resolutions[] = $resolution;
return $this;
}
public function getResolutions(): array
{
return $this->resolutions;
}
}

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\PhpGenerator\Attribute;
/**
* @internal
*/
trait AttributeAware
{
/** @var Attribute[] */
private $attributes = [];
/** @return static */
public function addAttribute(string $name, array $args = []): self
{
$this->attributes[] = new Attribute($name, $args);
return $this;
}
/**
* @param Attribute[] $attrs
* @return static
*/
public function setAttributes(array $attrs): self
{
(function (Attribute ...$attrs) {})(...$attrs);
$this->attributes = $attrs;
return $this;
}
/** @return Attribute[] */
public function getAttributes(): array
{
return $this->attributes;
}
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator\Traits;
/**
* @internal
*/
trait CommentAware
{
/** @var string|null */
private $comment;
/** @return static */
public function setComment(?string $val): self
{
$this->comment = $val;
return $this;
}
public function getComment(): ?string
{
return $this->comment;
}
/** @return static */
public function addComment(string $val): self
{
$this->comment .= $this->comment ? "\n$val" : $val;
return $this;
}
}

View File

@@ -0,0 +1,188 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator\Traits;
use Nette;
use Nette\PhpGenerator\Dumper;
use Nette\PhpGenerator\Parameter;
use Nette\Utils\Type;
/**
* @internal
*/
trait FunctionLike
{
/** @var string */
private $body = '';
/** @var Parameter[] */
private $parameters = [];
/** @var bool */
private $variadic = false;
/** @var string|null */
private $returnType;
/** @var bool */
private $returnReference = false;
/** @var bool */
private $returnNullable = false;
/** @return static */
public function setBody(string $code, ?array $args = null): self
{
$this->body = $args === null
? $code
: (new Dumper)->format($code, ...$args);
return $this;
}
public function getBody(): string
{
return $this->body;
}
/** @return static */
public function addBody(string $code, ?array $args = null): self
{
$this->body .= ($args === null ? $code : (new Dumper)->format($code, ...$args)) . "\n";
return $this;
}
/**
* @param Parameter[] $val
* @return static
*/
public function setParameters(array $val): self
{
(function (Parameter ...$val) {})(...array_values($val));
$this->parameters = [];
foreach ($val as $v) {
$this->parameters[$v->getName()] = $v;
}
return $this;
}
/** @return Parameter[] */
public function getParameters(): array
{
return $this->parameters;
}
/**
* @param string $name without $
*/
public function addParameter(string $name, $defaultValue = null): Parameter
{
$param = new Parameter($name);
if (func_num_args() > 1) {
$param->setDefaultValue($defaultValue);
}
return $this->parameters[$name] = $param;
}
/**
* @param string $name without $
* @return static
*/
public function removeParameter(string $name): self
{
unset($this->parameters[$name]);
return $this;
}
/** @return static */
public function setVariadic(bool $state = true): self
{
$this->variadic = $state;
return $this;
}
public function isVariadic(): bool
{
return $this->variadic;
}
/** @return static */
public function setReturnType(?string $type): self
{
$this->returnType = Nette\PhpGenerator\Helpers::validateType($type, $this->returnNullable);
return $this;
}
/**
* @return Type|string|null
*/
public function getReturnType(bool $asObject = false)
{
return $asObject && $this->returnType
? Type::fromString($this->returnType)
: $this->returnType;
}
/** @return static */
public function setReturnReference(bool $state = true): self
{
$this->returnReference = $state;
return $this;
}
public function getReturnReference(): bool
{
return $this->returnReference;
}
/** @return static */
public function setReturnNullable(bool $state = true): self
{
$this->returnNullable = $state;
return $this;
}
public function isReturnNullable(): bool
{
return $this->returnNullable;
}
/** @deprecated use isReturnNullable() */
public function getReturnNullable(): bool
{
return $this->returnNullable;
}
/** @deprecated */
public function setNamespace(?Nette\PhpGenerator\PhpNamespace $val = null): self
{
trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED);
return $this;
}
}

View File

@@ -0,0 +1,50 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator\Traits;
use Nette;
/**
* @internal
*/
trait NameAware
{
/** @var string */
private $name;
public function __construct(string $name)
{
if (!Nette\PhpGenerator\Helpers::isIdentifier($name)) {
throw new Nette\InvalidArgumentException("Value '$name' is not valid name.");
}
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
/**
* Returns clone with a different name.
* @return static
*/
public function cloneWithName(string $name): self
{
$dolly = clone $this;
$dolly->__construct($name);
return $dolly;
}
}

View File

@@ -0,0 +1,86 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator\Traits;
use Nette;
use Nette\PhpGenerator\ClassType;
/**
* @internal
*/
trait VisibilityAware
{
/** @var string|null public|protected|private */
private $visibility;
/**
* @param string|null $val public|protected|private
* @return static
*/
public function setVisibility(?string $val): self
{
if (!in_array($val, [ClassType::VisibilityPublic, ClassType::VisibilityProtected, ClassType::VisibilityPrivate, null], true)) {
throw new Nette\InvalidArgumentException('Argument must be public|protected|private.');
}
$this->visibility = $val;
return $this;
}
public function getVisibility(): ?string
{
return $this->visibility;
}
/** @return static */
public function setPublic(): self
{
$this->visibility = ClassType::VisibilityPublic;
return $this;
}
public function isPublic(): bool
{
return $this->visibility === ClassType::VisibilityPublic || $this->visibility === null;
}
/** @return static */
public function setProtected(): self
{
$this->visibility = ClassType::VisibilityProtected;
return $this;
}
public function isProtected(): bool
{
return $this->visibility === ClassType::VisibilityProtected;
}
/** @return static */
public function setPrivate(): self
{
$this->visibility = ClassType::VisibilityPrivate;
return $this;
}
public function isPrivate(): bool
{
return $this->visibility === ClassType::VisibilityPrivate;
}
}

View File

@@ -0,0 +1,92 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
/**
* PHP return, property and parameter types.
*/
class Type
{
public const
String = 'string',
Int = 'int',
Float = 'float',
Bool = 'bool',
Array = 'array',
Object = 'object',
Callable = 'callable',
Iterable = 'iterable',
Void = 'void',
Never = 'never',
Mixed = 'mixed',
False = 'false',
Null = 'null',
Self = 'self',
Parent = 'parent',
Static = 'static';
/** @deprecated */
public const
STRING = self::String,
INT = self::Int,
FLOAT = self::Float,
BOOL = self::Bool,
ARRAY = self::Array,
OBJECT = self::Object,
CALLABLE = self::Callable,
ITERABLE = self::Iterable,
VOID = self::Void,
NEVER = self::Never,
MIXED = self::Mixed,
FALSE = self::False,
NULL = self::Null,
SELF = self::Self,
PARENT = self::Parent,
STATIC = self::Static;
public static function nullable(string $type, bool $state = true): string
{
return ($state ? '?' : '') . ltrim($type, '?');
}
public static function union(string ...$types): string
{
return implode('|', $types);
}
public static function intersection(string ...$types): string
{
return implode('&', $types);
}
public static function getType($value): ?string
{
if (is_object($value)) {
return get_class($value);
} elseif (is_int($value)) {
return self::INT;
} elseif (is_float($value)) {
return self::FLOAT;
} elseif (is_string($value)) {
return self::STRING;
} elseif (is_bool($value)) {
return self::BOOL;
} elseif (is_array($value)) {
return self::ARRAY;
} else {
return null;
}
}
}