Add Composer autoloading functions and PHPStan for testing

This commit is contained in:
Alex Cabal 2019-02-26 13:03:45 -06:00
parent e198c4db65
commit f5d7d4e02a
1518 changed files with 169063 additions and 30 deletions

View file

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

View file

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

60
vendor/nette/php-generator/license.md vendored Normal file
View file

@ -0,0 +1,60 @@
Licenses
========
Good news! You may use Nette Framework under the terms of either
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
The BSD License is recommended for most projects. It is easy to understand and it
places almost no restrictions on what you can do with the framework. If the GPL
fits better to your project, you can use the framework under this license.
You don't have to notify anyone which license you are using. You can freely
use Nette Framework in commercial projects as long as the copyright header
remains intact.
Please be advised that the name "Nette Framework" is a protected trademark and its
usage has some limitations. So please do not use word "Nette" in the name of your
project or top-level domain, and choose a name that stands on its own merits.
If your stuff is good, it will not take long to establish a reputation for yourselves.
New BSD License
---------------
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of "Nette Framework" nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
This software is provided by the copyright holders and contributors "as is" and
any express or implied warranties, including, but not limited to, the implied
warranties of merchantability and fitness for a particular purpose are
disclaimed. In no event shall the copyright owner or contributors be liable for
any direct, indirect, incidental, special, exemplary, or consequential damages
(including, but not limited to, procurement of substitute goods or services;
loss of use, data, or profits; or business interruption) however caused and on
any theory of liability, whether in contract, strict liability, or tort
(including negligence or otherwise) arising in any way out of the use of this
software, even if advised of the possibility of such damage.
GNU General Public License
--------------------------
GPL licenses are very very long, so instead of including them here we offer
you URLs with full text:
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)

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

@ -0,0 +1,512 @@
Nette PHP Generator
===================
[![Downloads this Month](https://img.shields.io/packagist/dm/nette/php-generator.svg)](https://packagist.org/packages/nette/php-generator)
[![Build Status](https://travis-ci.org/nette/php-generator.svg?branch=master)](https://travis-ci.org/nette/php-generator)
[![Coverage Status](https://coveralls.io/repos/github/nette/php-generator/badge.svg?branch=master&v=1)](https://coveralls.io/github/nette/php-generator?branch=master)
[![Latest Stable Version](https://poser.pugx.org/nette/php-generator/v/stable)](https://github.com/nette/php-generator/releases)
[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/php-generator/blob/master/license.md)
Introduction
------------
Generate PHP code, classes, namespaces etc. with a simple programmatical API.
Documentation can be found on the [website](https://doc.nette.org/php-generator).
If you like Nette, **[please make a donation now](https://nette.org/donate)**. Thank you!
Installation
------------
The recommended way to install is via Composer:
```
composer require nette/php-generator
```
- v3.2 requires PHP 7.1 or newer (is compatible up to 7.3)
- v3.1 requires PHP 7.1 or newer (is compatible up to 7.3)
- v3.0 requires PHP 7.0 or newer (is compatible up to 7.3)
- v2.6 requires PHP 5.6 or newer (is compatible up to 7.3)
Usage
-----
Usage is very easy. Let's start with a straightforward example of generating class:
```php
$class = new Nette\PhpGenerator\ClassType('Demo');
$class
->setFinal()
->setExtends('ParentClass')
->addImplement('Countable')
->addTrait('Nette\SmartObject')
->addComment("Description of class.\nSecond line\n")
->addComment('@property-read Nette\Forms\Form $form');
// to generate PHP code simply cast to string or use echo:
echo $class;
```
It will render this result:
```php
/**
* Description of class.
* Second line
*
* @property-read Nette\Forms\Form $form
*/
final class Demo extends ParentClass implements Countable
{
use Nette\SmartObject;
}
```
We can add constants and properties:
```php
$class->addConstant('ID', 123);
$class->addProperty('items', [1, 2, 3])
->setVisibility('private')
->setStatic()
->addComment('@var int[]');
```
It generates:
```php
const ID = 123;
/** @var int[] */
private static $items = [1, 2, 3];
```
And we can add methods with parameters:
```php
$method = $class->addMethod('count')
->addComment('Count it.')
->addComment('@return int')
->setFinal()
->setVisibility('protected')
->setBody('return count($items ?: $this->items);');
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setTypeHint('array'); // array &$items = []
```
It results in:
```php
/**
* Count it.
* @return int
*/
final protected function count(array &$items = [])
{
return count($items ?: $this->items);
}
```
If the property, constant, method or parameter already exist, it will be overwritten.
Members can be removed using `removeProperty()`, `removeConstant()`, `removeMethod()` or `removeParameter()`.
PHP Generator supports all new PHP 7.3 features:
```php
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addConstant('ID', 123)
->setVisibility('private'); // constant visiblity
$method = $class->addMethod('getValue')
->setReturnType('int') // method return type
->setReturnNullable() // nullable return type
->setBody('return count($this->items);');
$method->addParameter('id')
->setTypeHint('int') // scalar type hint
->setNullable(); // nullable type hint
echo $class;
```
Result:
```php
class Demo
{
private const ID = 123;
public function getValue(?int $id): ?int
{
return count($this->items);
}
}
```
You can also add existing `Method`, `Property` or `Constant` objects to the class:
```php
$method = new Nette\PhpGenerator\Method('getHandle');
$property = new Nette\PhpGenerator\Property('handle');
$const = new Nette\PhpGenerator\Constant('ROLE');
$class = (new Nette\PhpGenerator\ClassType('Demo'))
->addMember($method)
->addMember($property)
->addMember($const);
```
You can clone existing methods, properties and constants with a different name using `cloneWithName()`:
```php
$methodCount = $class->getMethod('count');
$methodRecount = $methodCount->cloneWithName('recount');
$class->addMember($methodRecount);
```
Tabs versus spaces
------------------
The generated code uses tabs for indentation. If you want to have the output compatible with PSR-2 or PSR-12, use `PsrPrinter`:
```php
$printer = new Nette\PhpGenerator\PsrPrinter;
$class = new Nette\PhpGenerator\ClassType('Demo');
// ...
echo $printer->printClass($class); // 4 spaces indentation
```
It can be used also for functions, closures, namespaces etc.
Literals
--------
You can pass any PHP code to property or parameter default values via `PhpLiteral`:
```php
use Nette\PhpGenerator\PhpLiteral;
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addProperty('foo', new PhpLiteral('Iterator::SELF_FIRST'));
$class->addMethod('bar')
->addParameter('id', new PhpLiteral('1 + 2'));
echo $class;
```
Result:
```php
class Demo
{
public $foo = Iterator::SELF_FIRST;
public function bar($id = 1 + 2)
{
}
}
```
Interface or Trait
------------------
```php
$class = new Nette\PhpGenerator\ClassType('DemoInterface');
$class->setType('interface');
// or $class->setType('trait');
```
Trait Resolutions and Visibility
--------------------------------
```php
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addTrait('SmartObject', ['sayHello as protected']);
echo $class;
```
Result:
```php
class Demo
{
use SmartObject {
sayHello as protected;
}
}
```
Anonymous Class
---------------
```php
$class = new Nette\PhpGenerator\ClassType(null);
$class->addMethod('__construct')
->addParameter('foo');
echo '$obj = new class ($val) ' . $class . ';';
```
Result:
```php
$obj = new class ($val) {
public function __construct($foo)
{
}
};
```
Global Function
---------------
Code of function:
```php
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('return $a + $b;');
$function->addParameter('a');
$function->addParameter('b');
echo $function;
// or use PsrPrinter for output compatible with PSR-2 / PSR-12
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);
```
Result:
```php
function foo($a, $b)
{
return $a + $b;
}
```
Closure
-------
Code of closure:
```php
$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('return $a + $b;');
$closure->addParameter('a');
$closure->addParameter('b');
$closure->addUse('c')
->setReference();
echo $closure;
// or use PsrPrinter for output compatible with PSR-2 / PSR-12
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);
```
Result:
```php
function ($a, $b) use (&$c) {
return $a + $b;
}
```
Method and Function Body Generator
----------------------------------
You can use special placeholders for handy way to generate method or function body.
Simple placeholders:
```php
$str = 'any string';
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('return strlen(?, ?);', [$str, $num]);
echo $function;
```
Result:
```php
function foo()
{
return strlen('any string', 3);
}
```
Variadic placeholder:
```php
$items = [1, 2, 3];
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('myfunc(...?);', [$items]);
echo $function;
```
Result:
```php
function foo()
{
myfunc(1, 2, 3);
}
```
Escape placeholder using slash:
```php
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addParameter('a');
$function->addBody('return $a \? 10 : ?;', [$num]);
echo $function;
```
Result:
```php
function foo($a)
{
return $a ? 10 : 3;
}
```
Namespace
---------
Classes, traits and interfaces (hereinafter classes) can be grouped into namespaces:
```php
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');
// or
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);
```
If the class already exists, it will be overwritten.
You can define use-statements:
```php
$namespace->addUse('Http\Request'); // use Http\Request;
$namespace->addUse('Http\Request', 'HttpReq'); // use Http\Request as HttpReq;
```
**IMPORTANT NOTE:** when the class is part of the namespace, it is rendered slightly differently: all types (ie. type hints, return types, parent class name,
implemented interfaces and used traits) are automatically *resolved*. It means that you have to **use full class names** in definitions
and they will be replaced with aliases (according to the use-statements) or fully qualified names in the resulting code:
```php
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
$namespace->addUse('Bar\AliasedClass');
$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // it will resolve to A
->addTrait('Bar\AliasedClass'); // it will resolve to AliasedClass
$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->unresolveName('Foo\D')); // in comments resolve manually
$method->addParameter('arg')
->setTypeHint('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)
{
}
}
```
PHP Files
---------
PHP files can contains multiple classes, namespaces and comments:
```php
$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('This file is auto-generated.');
$file->setStrictTypes(); // adds declare(strict_types=1)
$namespace = $file->addNamespace('Foo');
$class = $namespace->addClass('A');
$class->addMethod('hello');
echo $file;
// or use PsrPrinter for output compatible with PSR-2 / PSR-12
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);
```
Result:
```php
<?php
/**
* This file is auto-generated.
*/
declare(strict_types=1);
namespace Foo;
class A
{
public function hello()
{
}
}
```
Generate using Reflection
-------------------------
Another common use case is to create class or method based on existing ones:
```php
$class = Nette\PhpGenerator\ClassType::from(PDO::class);
$function = Nette\PhpGenerator\GlobalFunction::from('trim');
$closure = Nette\PhpGenerator\Closure::from(
function (stdClass $a, $b = null) {}
);
```

View file

@ -0,0 +1,495 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
/**
* Class/Interface/Trait description.
*
* @property Method[] $methods
* @property Property[] $properties
*/
final class ClassType
{
use Nette\SmartObject;
use Traits\CommentAware;
public const
TYPE_CLASS = 'class',
TYPE_INTERFACE = 'interface',
TYPE_TRAIT = 'trait';
public const
VISIBILITY_PUBLIC = 'public',
VISIBILITY_PROTECTED = 'protected',
VISIBILITY_PRIVATE = 'private';
/** @var PhpNamespace|null */
private $namespace;
/** @var string|null */
private $name;
/** @var string class|interface|trait */
private $type = self::TYPE_CLASS;
/** @var bool */
private $final = false;
/** @var bool */
private $abstract = false;
/** @var string|string[] */
private $extends = [];
/** @var string[] */
private $implements = [];
/** @var array[] */
private $traits = [];
/** @var Constant[] name => Constant */
private $consts = [];
/** @var Property[] name => Property */
private $properties = [];
/** @var Method[] name => Method */
private $methods = [];
/**
* @param string|object $class
* @return static
*/
public static function from($class): self
{
return (new Factory)->fromClassReflection(new \ReflectionClass($class));
}
public function __construct(string $name = null, PhpNamespace $namespace = null)
{
$this->setName($name);
$this->namespace = $namespace;
}
public function __toString(): string
{
try {
return (new Printer)->printClass($this, $this->namespace);
} catch (\Throwable $e) {
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
}
}
/**
* Deprecated: an object can be in multiple namespaces.
* @deprecated
*/
public function getNamespace(): ?PhpNamespace
{
return $this->namespace;
}
/**
* @return static
*/
public function setName(?string $name): self
{
if ($name !== null && !Helpers::isIdentifier($name)) {
throw new Nette\InvalidArgumentException("Value '$name' is not valid class name.");
}
$this->name = $name;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
/**
* @return static
*/
public function setType(string $type): self
{
if (!in_array($type, [self::TYPE_CLASS, self::TYPE_INTERFACE, self::TYPE_TRAIT], true)) {
throw new Nette\InvalidArgumentException('Argument must be class|interface|trait.');
}
$this->type = $type;
return $this;
}
public function getType(): string
{
return $this->type;
}
/**
* @return static
*/
public function setFinal(bool $state = true): self
{
$this->final = $state;
return $this;
}
public function isFinal(): bool
{
return $this->final;
}
/**
* @return static
*/
public function setAbstract(bool $state = true): self
{
$this->abstract = $state;
return $this;
}
public function isAbstract(): bool
{
return $this->abstract;
}
/**
* @param string|string[] $names
* @return static
*/
public function setExtends($names): self
{
if (!is_string($names) && !is_array($names)) {
throw new Nette\InvalidArgumentException('Argument must be string or string[].');
}
$this->validateNames((array) $names);
$this->extends = $names;
return $this;
}
/**
* @return string|string[]
*/
public function getExtends()
{
return $this->extends;
}
/**
* @return static
*/
public function addExtend(string $name): self
{
$this->validateNames([$name]);
$this->extends = (array) $this->extends;
$this->extends[] = $name;
return $this;
}
/**
* @param string[] $names
* @return static
*/
public function setImplements(array $names): self
{
$this->validateNames($names);
$this->implements = $names;
return $this;
}
/**
* @return string[]
*/
public function getImplements(): array
{
return $this->implements;
}
/**
* @return static
*/
public function addImplement(string $name): self
{
$this->validateNames([$name]);
$this->implements[] = $name;
return $this;
}
/**
* @param string[] $names
* @return static
*/
public function setTraits(array $names): self
{
$this->validateNames($names);
$this->traits = array_fill_keys($names, []);
return $this;
}
/**
* @return string[]
*/
public function getTraits(): array
{
return array_keys($this->traits);
}
/**
* @internal
*/
public function getTraitResolutions(): array
{
return $this->traits;
}
/**
* @return static
*/
public function addTrait(string $name, array $resolutions = []): self
{
$this->validateNames([$name]);
$this->traits[$name] = $resolutions;
return $this;
}
/**
* @param Method|Property|Constant $member
* @return static
*/
public function addMember($member): self
{
if ($member instanceof Method) {
if ($this->type === self::TYPE_INTERFACE) {
$member->setBody(null);
}
$this->methods[$member->getName()] = $member;
} elseif ($member instanceof Property) {
$this->properties[$member->getName()] = $member;
} elseif ($member instanceof Constant) {
$this->consts[$member->getName()] = $member;
} else {
throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant.');
}
return $this;
}
/**
* @param Constant[]|mixed[] $consts
* @return static
*/
public function setConstants(array $consts): self
{
$this->consts = [];
foreach ($consts as $k => $v) {
$const = $v instanceof Constant ? $v : (new Constant($k))->setValue($v);
$this->consts[$const->getName()] = $const;
}
return $this;
}
/**
* @return Constant[]
*/
public function getConstants(): array
{
return $this->consts;
}
public function addConstant(string $name, $value): Constant
{
return $this->consts[$name] = (new Constant($name))->setValue($value);
}
/**
* @return static
*/
public function removeConstant(string $name): self
{
unset($this->consts[$name]);
return $this;
}
/**
* @param Property[] $props
* @return static
*/
public function setProperties(array $props): self
{
$this->properties = [];
foreach ($props as $v) {
if (!$v instanceof Property) {
throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Property[].');
}
$this->properties[$v->getName()] = $v;
}
return $this;
}
/**
* @return Property[]
*/
public function getProperties(): array
{
return $this->properties;
}
public function getProperty(string $name): Property
{
if (!isset($this->properties[$name])) {
throw new Nette\InvalidArgumentException("Property '$name' not found.");
}
return $this->properties[$name];
}
/**
* @param string $name without $
*/
public function addProperty(string $name, $value = null): Property
{
return $this->properties[$name] = (new Property($name))->setValue($value);
}
/**
* @param string $name without $
* @return static
*/
public function removeProperty(string $name): self
{
unset($this->properties[$name]);
return $this;
}
/**
* @param Method[] $methods
* @return static
*/
public function setMethods(array $methods): self
{
$this->methods = [];
foreach ($methods as $v) {
if (!$v instanceof Method) {
throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Method[].');
}
$this->methods[$v->getName()] = $v;
}
return $this;
}
/**
* @return Method[]
*/
public function getMethods(): array
{
return $this->methods;
}
public function getMethod(string $name): Method
{
if (!isset($this->methods[$name])) {
throw new Nette\InvalidArgumentException("Method '$name' not found.");
}
return $this->methods[$name];
}
public function addMethod(string $name): Method
{
$method = new Method($name);
if ($this->type === self::TYPE_INTERFACE) {
$method->setBody(null);
} else {
$method->setVisibility(self::VISIBILITY_PUBLIC);
}
return $this->methods[$name] = $method;
}
/**
* @return static
*/
public function removeMethod(string $name): self
{
unset($this->methods[$name]);
return $this;
}
/**
* @throws Nette\InvalidStateException
*/
public function validate(): void
{
if ($this->abstract && $this->final) {
throw new Nette\InvalidStateException('Class cannot be abstract and final.');
} elseif (!$this->name && ($this->abstract || $this->final)) {
throw new Nette\InvalidStateException('Anonymous class cannot be abstract or final.');
}
}
private function validateNames(array $names): void
{
foreach ($names as $name) {
if (!Helpers::isNamespaceIdentifier($name, true)) {
throw new Nette\InvalidArgumentException("Value '$name' is not valid class name.");
}
}
}
public function __clone()
{
$clone = function ($item) { return clone $item; };
$this->consts = array_map($clone, $this->consts);
$this->properties = array_map($clone, $this->properties);
$this->methods = array_map($clone, $this->methods);
}
}

View file

@ -0,0 +1,70 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
/**
* Closure.
*
* @property string $body
*/
final class Closure
{
use Nette\SmartObject;
use Traits\FunctionLike;
/** @var Parameter[] */
private $uses = [];
/**
* @return static
*/
public static function from(\Closure $closure): self
{
return (new Factory)->fromFunctionReflection(new \ReflectionFunction($closure));
}
public function __toString(): string
{
try {
return (new Printer)->printClosure($this);
} catch (\Throwable $e) {
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
}
}
/**
* @param Parameter[] $uses
* @return static
*/
public function setUses(array $uses): self
{
(function (Parameter ...$uses) {})(...$uses);
$this->uses = $uses;
return $this;
}
public function getUses(): array
{
return $this->uses;
}
public function addUse(string $name): Parameter
{
return $this->uses[] = new Parameter($name);
}
}

View file

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

View file

@ -0,0 +1,134 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
/**
* Creates a representation based on reflection.
*/
final class Factory
{
use Nette\SmartObject;
public function fromClassReflection(\ReflectionClass $from): ClassType
{
$class = $from->isAnonymous()
? new ClassType
: new ClassType($from->getShortName(), new PhpNamespace($from->getNamespaceName()));
$class->setType($from->isInterface() ? $class::TYPE_INTERFACE : ($from->isTrait() ? $class::TYPE_TRAIT : $class::TYPE_CLASS));
$class->setFinal($from->isFinal() && $class->getType() === $class::TYPE_CLASS);
$class->setAbstract($from->isAbstract() && $class->getType() === $class::TYPE_CLASS);
$ifaces = $from->getInterfaceNames();
foreach ($ifaces as $iface) {
$ifaces = array_filter($ifaces, function (string $item) use ($iface): bool {
return !is_subclass_of($iface, $item);
});
}
$class->setImplements($ifaces);
$class->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
if ($from->getParentClass()) {
$class->setExtends($from->getParentClass()->getName());
$class->setImplements(array_diff($class->getImplements(), $from->getParentClass()->getInterfaceNames()));
}
$props = $methods = [];
foreach ($from->getProperties() as $prop) {
if ($prop->isDefault() && $prop->getDeclaringClass()->getName() === $from->getName()) {
$props[] = $this->fromPropertyReflection($prop);
}
}
$class->setProperties($props);
foreach ($from->getMethods() as $method) {
if ($method->getDeclaringClass()->getName() === $from->getName()) {
$methods[] = $this->fromMethodReflection($method);
}
}
$class->setMethods($methods);
$class->setConstants($from->getConstants());
return $class;
}
public function fromMethodReflection(\ReflectionMethod $from): Method
{
$method = new Method($from->getName());
$method->setParameters(array_map([$this, 'fromParameterReflection'], $from->getParameters()));
$method->setStatic($from->isStatic());
$isInterface = $from->getDeclaringClass()->isInterface();
$method->setVisibility($from->isPrivate()
? ClassType::VISIBILITY_PRIVATE
: ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ($isInterface ? null : ClassType::VISIBILITY_PUBLIC))
);
$method->setFinal($from->isFinal());
$method->setAbstract($from->isAbstract() && !$isInterface);
$method->setBody($from->isAbstract() ? null : '');
$method->setReturnReference($from->returnsReference());
$method->setVariadic($from->isVariadic());
$method->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
if ($from->hasReturnType()) {
$method->setReturnType((string) $from->getReturnType());
$method->setReturnNullable($from->getReturnType()->allowsNull());
}
return $method;
}
/**
* @return GlobalFunction|Closure
*/
public function fromFunctionReflection(\ReflectionFunction $from)
{
$function = $from->isClosure() ? new Closure : new GlobalFunction($from->getName());
$function->setParameters(array_map([$this, 'fromParameterReflection'], $from->getParameters()));
$function->setReturnReference($from->returnsReference());
$function->setVariadic($from->isVariadic());
if (!$from->isClosure()) {
$function->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
}
if ($from->hasReturnType()) {
$function->setReturnType((string) $from->getReturnType());
$function->setReturnNullable($from->getReturnType()->allowsNull());
}
return $function;
}
public function fromParameterReflection(\ReflectionParameter $from): Parameter
{
$param = new Parameter($from->getName());
$param->setReference($from->isPassedByReference());
$param->setTypeHint($from->hasType() ? (string) $from->getType() : null);
$param->setNullable($from->hasType() && $from->getType()->allowsNull());
if ($from->isDefaultValueAvailable()) {
$param->setDefaultValue($from->isDefaultValueConstant()
? new PhpLiteral($from->getDefaultValueConstantName())
: $from->getDefaultValue());
$param->setNullable($param->isNullable() && $param->getDefaultValue() !== null);
}
return $param;
}
public function fromPropertyReflection(\ReflectionProperty $from): Property
{
$prop = new Property($from->getName());
$prop->setValue($from->getDeclaringClass()->getDefaultProperties()[$prop->getName()] ?? null);
$prop->setStatic($from->isStatic());
$prop->setVisibility($from->isPrivate()
? ClassType::VISIBILITY_PRIVATE
: ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ClassType::VISIBILITY_PUBLIC)
);
$prop->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
return $prop;
}
}

View file

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

View file

@ -0,0 +1,273 @@
<?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 Helpers
{
use Nette\StaticClass;
public const PHP_IDENT = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*';
public const WRAP_LENGTH = 100;
public const INDENT_LENGTH = 4;
private const MAX_DEPTH = 50;
/**
* Returns a PHP representation of a variable.
*/
public static function dump($var): string
{
return self::_dump($var);
}
private static function _dump(&$var, int $level = 0)
{
if ($var instanceof PhpLiteral) {
return (string) $var;
} elseif (is_float($var)) {
if (is_finite($var)) {
$var = var_export($var, true);
return strpos($var, '.') === false ? $var . '.0' : $var; // workaround for PHP < 7.0.2
}
return str_replace('.0', '', var_export($var, true)); // workaround for PHP 7.0.2
} elseif ($var === null) {
return 'null';
} elseif (is_string($var) && (preg_match('#[^\x09\x20-\x7E\xA0-\x{10FFFF}]#u', $var) || preg_last_error())) {
static $table;
if ($table === null) {
foreach (array_merge(range("\x00", "\x1F"), range("\x7F", "\xFF")) as $ch) {
$table[$ch] = '\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT);
}
$table['\\'] = '\\\\';
$table["\r"] = '\r';
$table["\n"] = '\n';
$table["\t"] = '\t';
$table['$'] = '\$';
$table['"'] = '\"';
}
return '"' . strtr($var, $table) . '"';
} elseif (is_string($var)) {
return "'" . preg_replace('#\'|\\\\(?=[\'\\\\]|\z)#', '\\\\$0', $var) . "'";
} elseif (is_array($var)) {
$space = str_repeat("\t", $level);
static $marker;
if ($marker === null) {
$marker = uniqid("\x00", true);
}
if (empty($var)) {
$out = '';
} elseif ($level > self::MAX_DEPTH || isset($var[$marker])) {
throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.');
} else {
$out = '';
$outWrapped = "\n$space";
$var[$marker] = true;
$counter = 0;
foreach ($var as $k => &$v) {
if ($k !== $marker) {
$item = ($k === $counter ? '' : self::_dump($k, $level + 1) . ' => ') . self::_dump($v, $level + 1);
$counter = is_int($k) ? max($k + 1, $counter) : $counter;
$out .= ($out === '' ? '' : ', ') . $item;
$outWrapped .= "\t$item,\n$space";
}
}
unset($var[$marker]);
}
$wrap = strpos($out, "\n") !== false || strlen($out) > self::WRAP_LENGTH - $level * self::INDENT_LENGTH;
return '[' . ($wrap ? $outWrapped : $out) . ']';
} elseif ($var instanceof \Serializable) {
$var = serialize($var);
return 'unserialize(' . self::_dump($var, $level) . ')';
} elseif ($var instanceof \Closure) {
throw new Nette\InvalidArgumentException('Cannot dump closure.');
} elseif (is_object($var)) {
$class = get_class($var);
if ((new \ReflectionObject($var))->isAnonymous()) {
throw new Nette\InvalidArgumentException('Cannot dump anonymous class.');
} elseif (in_array($class, ['DateTime', 'DateTimeImmutable'], true)) {
return self::formatArgs("new $class(?, new DateTimeZone(?))", [$var->format('Y-m-d H:i:s.u'), $var->getTimeZone()->getName()]);
}
$arr = (array) $var;
$space = str_repeat("\t", $level);
static $list = [];
if ($level > self::MAX_DEPTH || in_array($var, $list, true)) {
throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.');
} else {
$out = "\n";
$list[] = $var;
if (method_exists($var, '__sleep')) {
foreach ($var->__sleep() as $v) {
$props[$v] = $props["\x00*\x00$v"] = $props["\x00$class\x00$v"] = true;
}
}
foreach ($arr as $k => &$v) {
if (!isset($props) || isset($props[$k])) {
$out .= "$space\t" . self::_dump($k, $level + 1) . ' => ' . self::_dump($v, $level + 1) . ",\n";
}
}
array_pop($list);
$out .= $space;
}
return $class === 'stdClass'
? "(object) [$out]"
: __CLASS__ . "::createObject('$class', [$out])";
} elseif (is_resource($var)) {
throw new Nette\InvalidArgumentException('Cannot dump resource.');
} else {
return var_export($var, true);
}
}
/**
* Generates PHP statement.
*/
public static function format(string $statement, ...$args): string
{
return self::formatArgs($statement, $args);
}
/**
* Generates PHP statement.
*/
public static function formatArgs(string $statement, array $args): string
{
$tokens = preg_split('#(\.\.\.\?|\$\?|->\?|::\?|\\\\\?|\?\*|\?)#', $statement, -1, PREG_SPLIT_DELIM_CAPTURE);
$res = '';
foreach ($tokens as $n => $token) {
if ($n % 2 === 0) {
$res .= $token;
} elseif ($token === '\\?') {
$res .= '?';
} elseif (!$args) {
throw new Nette\InvalidArgumentException('Insufficient number of arguments.');
} elseif ($token === '?') {
$res .= self::dump(array_shift($args));
} elseif ($token === '...?' || $token === '?*') {
$arg = array_shift($args);
if (!is_array($arg)) {
throw new Nette\InvalidArgumentException('Argument must be an array.');
}
$items = [];
foreach ($arg as $tmp) {
$items[] = self::dump($tmp);
}
$res .= strlen($tmp = implode(', ', $items)) > self::WRAP_LENGTH && count($items) > 1
? "\n" . Nette\Utils\Strings::indent(implode(",\n", $items)) . "\n"
: $tmp;
} else { // $ -> ::
$res .= substr($token, 0, -1) . self::formatMember(array_shift($args));
}
}
if ($args) {
throw new Nette\InvalidArgumentException('Insufficient number of placeholders.');
}
return $res;
}
/**
* Returns a PHP representation of a object member.
*/
public static function formatMember($name): string
{
return $name instanceof PhpLiteral || !self::isIdentifier($name)
? '{' . self::_dump($name) . '}'
: $name;
}
public static function formatDocComment(string $content): string
{
if (($s = trim($content)) === '') {
return '';
} elseif (strpos($content, "\n") === false) {
return "/** $s */\n";
} else {
return str_replace("\n", "\n * ", "/**\n$s") . "\n */\n";
}
}
public static function unformatDocComment(string $comment): string
{
return preg_replace('#^\s*\* ?#m', '', trim(trim(trim($comment), '/*')));
}
public static function isIdentifier($value): bool
{
return is_string($value) && preg_match('#^' . self::PHP_IDENT . '\z#', $value);
}
public static function isNamespaceIdentifier($value, bool $allowLeadingSlash = false): bool
{
$re = '#^' . ($allowLeadingSlash ? '\\\\?' : '') . self::PHP_IDENT . '(\\\\' . self::PHP_IDENT . ')*\z#';
return is_string($value) && preg_match($re, $value);
}
/**
* @return object
* @internal
*/
public static function createObject(string $class, array $props)
{
return unserialize('O' . substr(serialize($class), 1, -1) . substr(serialize($props), 1));
}
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 = self::INDENT_LENGTH): string
{
return str_replace("\t", str_repeat(' ', $count), $s);
}
}

View file

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

View file

@ -0,0 +1,122 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
/**
* Method parameter description.
*
* @property mixed $defaultValue
*/
final class Parameter
{
use Nette\SmartObject;
use Traits\NameAware;
/** @var bool */
private $reference = false;
/** @var string|null */
private $typeHint;
/** @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 setTypeHint(?string $hint): self
{
$this->typeHint = $hint;
return $this;
}
public function getTypeHint(): ?string
{
return $this->typeHint;
}
/**
* @deprecated just use setDefaultValue()
* @return static
*/
public function setOptional(bool $state = true): self
{
trigger_error(__METHOD__ . '() is deprecated, use setDefaultValue()', E_USER_DEPRECATED);
$this->hasDefaultValue = $state;
return $this;
}
/**
* @return static
*/
public function setNullable(bool $state = true): self
{
$this->nullable = $state;
return $this;
}
public function isNullable(): bool
{
return $this->nullable;
}
/**
* @return static
*/
public function setDefaultValue($val): self
{
$this->defaultValue = $val;
$this->hasDefaultValue = true;
return $this;
}
public function getDefaultValue()
{
return $this->defaultValue;
}
public function hasDefaultValue(): bool
{
return $this->hasDefaultValue;
}
}

View file

@ -0,0 +1,115 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
/**
* Instance of PHP file.
*
* Generates:
* - opening tag (<?php)
* - doc comments
* - one or more namespaces
*/
final class PhpFile
{
use Nette\SmartObject;
use Traits\CommentAware;
/** @var PhpNamespace[] */
private $namespaces = [];
/** @var bool */
private $strictTypes = false;
public function addClass(string $name): ClassType
{
return $this
->addNamespace(Helpers::extractNamespace($name))
->addClass(Helpers::extractShortName($name));
}
public function addInterface(string $name): ClassType
{
return $this
->addNamespace(Helpers::extractNamespace($name))
->addInterface(Helpers::extractShortName($name));
}
public function addTrait(string $name): ClassType
{
return $this
->addNamespace(Helpers::extractNamespace($name))
->addTrait(Helpers::extractShortName($name));
}
public function addNamespace(string $name): PhpNamespace
{
if (!isset($this->namespaces[$name])) {
$this->namespaces[$name] = new PhpNamespace($name);
foreach ($this->namespaces as $namespace) {
$namespace->setBracketedSyntax(count($this->namespaces) > 1 && isset($this->namespaces['']));
}
}
return $this->namespaces[$name];
}
/**
* @return PhpNamespace[]
*/
public function getNamespaces(): array
{
return $this->namespaces;
}
/**
* @return static
*/
public function addUse(string $name, string $alias = null): self
{
$this->addNamespace('')->addUse($name, $alias);
return $this;
}
/**
* Adds declare(strict_types=1) to output.
* @return static
*/
public function setStrictTypes(bool $on = true): self
{
$this->strictTypes = $on;
return $this;
}
public function getStrictTypes(): bool
{
return $this->strictTypes;
}
public function __toString(): string
{
try {
return (new Printer)->printFile($this);
} catch (\Throwable $e) {
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
}
}
}

View file

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

View file

@ -0,0 +1,199 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
use Nette\InvalidStateException;
use Nette\Utils\Strings;
/**
* Namespaced part of a PHP file.
*
* Generates:
* - namespace statement
* - variable amount of use statements
* - one or more class declarations
*/
final class PhpNamespace
{
use Nette\SmartObject;
private const KEYWORDS = [
'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1,
'callable' => 1, 'iterable' => 1, 'void' => 1, 'self' => 1, 'parent' => 1,
];
/** @var string */
private $name;
/** @var bool */
private $bracketedSyntax = false;
/** @var string[] */
private $uses = [];
/** @var ClassType[] */
private $classes = [];
public function __construct(string $name)
{
if ($name !== '' && !Helpers::isNamespaceIdentifier($name)) {
throw new Nette\InvalidArgumentException("Value '$name' is not valid name.");
}
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
/**
* @return static
* @internal
*/
public function setBracketedSyntax(bool $state = true): self
{
$this->bracketedSyntax = $state;
return $this;
}
public function getBracketedSyntax(): bool
{
return $this->bracketedSyntax;
}
/**
* @throws InvalidStateException
* @return static
*/
public function addUse(string $name, string $alias = null, string &$aliasOut = null): self
{
$name = ltrim($name, '\\');
if ($alias === null && $this->name === Helpers::extractNamespace($name)) {
$alias = Helpers::extractShortName($name);
}
if ($alias === null) {
$path = explode('\\', $name);
$counter = null;
do {
if (empty($path)) {
$counter++;
} else {
$alias = array_pop($path) . $alias;
}
} while (isset($this->uses[$alias . $counter]) && $this->uses[$alias . $counter] !== $name);
$alias .= $counter;
} elseif (isset($this->uses[$alias]) && $this->uses[$alias] !== $name) {
throw new InvalidStateException(
"Alias '$alias' used already for '{$this->uses[$alias]}', cannot use for '{$name}'."
);
}
$aliasOut = $alias;
$this->uses[$alias] = $name;
asort($this->uses);
return $this;
}
/**
* @return string[]
*/
public function getUses(): array
{
return $this->uses;
}
public function unresolveName(string $name): string
{
if (isset(self::KEYWORDS[strtolower($name)]) || $name === '') {
return $name;
}
$name = ltrim($name, '\\');
$res = null;
$lower = strtolower($name);
foreach ($this->uses as $alias => $original) {
if (Strings::startsWith($lower . '\\', strtolower($original) . '\\')) {
$short = $alias . substr($name, strlen($original));
if (!isset($res) || strlen($res) > strlen($short)) {
$res = $short;
}
}
}
if (!$res && Strings::startsWith($lower, strtolower($this->name) . '\\')) {
return substr($name, strlen($this->name) + 1);
} else {
return $res ?: ($this->name ? '\\' : '') . $name;
}
}
/**
* @return static
*/
public function add(ClassType $class): self
{
$name = $class->getName();
if ($name === null) {
throw new Nette\InvalidArgumentException('Class does not have a name.');
}
$this->addUse($this->name . '\\' . $name);
$this->classes[$name] = $class;
return $this;
}
public function addClass(string $name): ClassType
{
$this->add($class = new ClassType($name, $this));
return $class;
}
public function addInterface(string $name): ClassType
{
return $this->addClass($name)->setType(ClassType::TYPE_INTERFACE);
}
public function addTrait(string $name): ClassType
{
return $this->addClass($name)->setType(ClassType::TYPE_TRAIT);
}
/**
* @return ClassType[]
*/
public function getClasses(): array
{
return $this->classes;
}
public function __toString(): string
{
try {
return (new Printer)->printNamespace($this);
} catch (\Throwable $e) {
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
}
}
}

View file

@ -0,0 +1,226 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
use Nette;
use Nette\Utils\Strings;
/**
* Generates PHP code.
*/
class Printer
{
use Nette\SmartObject;
/** @var string */
protected $indentation = "\t";
/** @var int */
protected $linesBetweenMethods = 2;
public function printFunction(GlobalFunction $function, PhpNamespace $namespace = null): string
{
return Helpers::formatDocComment($function->getComment() . "\n")
. 'function '
. ($function->getReturnReference() ? '&' : '')
. $function->getName()
. $this->printParameters($function, $namespace)
. $this->printReturnType($function, $namespace)
. "\n{\n" . $this->indent(ltrim(rtrim($function->getBody()) . "\n")) . "}\n";
}
public function printClosure(Closure $closure): string
{
$uses = [];
foreach ($closure->getUses() as $param) {
$uses[] = ($param->isReference() ? '&' : '') . '$' . $param->getName();
}
$useStr = strlen($tmp = implode(', ', $uses)) > Helpers::WRAP_LENGTH && count($uses) > 1
? "\n" . $this->indentation . implode(",\n" . $this->indentation, $uses) . "\n"
: $tmp;
return 'function '
. ($closure->getReturnReference() ? '&' : '')
. $this->printParameters($closure, null)
. ($uses ? " use ($useStr)" : '')
. $this->printReturnType($closure, null)
. " {\n" . $this->indent(ltrim(rtrim($closure->getBody()) . "\n")) . '}';
}
public function printMethod(Method $method, PhpNamespace $namespace = null): string
{
$method->validate();
return Helpers::formatDocComment($method->getComment() . "\n")
. ($method->isAbstract() ? 'abstract ' : '')
. ($method->isFinal() ? 'final ' : '')
. ($method->getVisibility() ? $method->getVisibility() . ' ' : '')
. ($method->isStatic() ? 'static ' : '')
. 'function '
. ($method->getReturnReference() ? '&' : '')
. $method->getName()
. ($params = $this->printParameters($method, $namespace))
. $this->printReturnType($method, $namespace)
. ($method->isAbstract() || $method->getBody() === null
? ";\n"
: (strpos($params, "\n") === false ? "\n" : ' ')
. "{\n"
. $this->indent(ltrim(rtrim($method->getBody()) . "\n"))
. "}\n");
}
public function printClass(ClassType $class, PhpNamespace $namespace = null): string
{
$class->validate();
$resolver = $namespace ? [$namespace, 'unresolveName'] : function ($s) { return $s; };
$traits = [];
foreach ($class->getTraitResolutions() as $trait => $resolutions) {
$traits[] = 'use ' . $resolver($trait)
. ($resolutions ? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n" : ";\n");
}
$consts = [];
foreach ($class->getConstants() as $const) {
$consts[] = Helpers::formatDocComment((string) $const->getComment())
. ($const->getVisibility() ? $const->getVisibility() . ' ' : '')
. 'const ' . $const->getName() . ' = ' . Helpers::dump($const->getValue()) . ";\n";
}
$properties = [];
foreach ($class->getProperties() as $property) {
$properties[] = Helpers::formatDocComment((string) $property->getComment())
. ($property->getVisibility() ?: 'public') . ($property->isStatic() ? ' static' : '') . ' $' . $property->getName()
. ($property->getValue() === null ? '' : ' = ' . Helpers::dump($property->getValue()))
. ";\n";
}
$methods = [];
foreach ($class->getMethods() as $method) {
$methods[] = $this->printMethod($method, $namespace);
}
$members = array_filter([
implode('', $traits),
implode('', $consts),
implode("\n", $properties),
($methods && $properties ? str_repeat("\n", $this->linesBetweenMethods - 1) : '')
. implode(str_repeat("\n", $this->linesBetweenMethods), $methods),
]);
return Strings::normalize(
Helpers::formatDocComment($class->getComment() . "\n")
. ($class->isAbstract() ? 'abstract ' : '')
. ($class->isFinal() ? 'final ' : '')
. ($class->getName() ? $class->getType() . ' ' . $class->getName() . ' ' : '')
. ($class->getExtends() ? 'extends ' . implode(', ', array_map($resolver, (array) $class->getExtends())) . ' ' : '')
. ($class->getImplements() ? 'implements ' . implode(', ', array_map($resolver, $class->getImplements())) . ' ' : '')
. ($class->getName() ? "\n" : '') . "{\n"
. ($members ? $this->indent(implode("\n", $members)) : '')
. '}'
) . ($class->getName() ? "\n" : '');
}
public function printNamespace(PhpNamespace $namespace): string
{
$name = $namespace->getName();
$uses = [];
foreach ($namespace->getUses() as $alias => $original) {
if ($original !== ($name ? $name . '\\' . $alias : $alias)) {
if ($alias === $original || substr($original, -(strlen($alias) + 1)) === '\\' . $alias) {
$uses[] = "use $original;";
} else {
$uses[] = "use $original as $alias;";
}
}
}
$classes = [];
foreach ($namespace->getClasses() as $class) {
$classes[] = $this->printClass($class, $namespace);
}
$body = ($uses ? implode("\n", $uses) . "\n\n" : '')
. implode("\n", $classes);
if ($namespace->getBracketedSyntax()) {
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->getStrictTypes() ? "declare(strict_types=1);\n\n" : '')
. implode("\n\n", $namespaces)
) . "\n";
}
protected function indent(string $s): string
{
return Strings::indent($s, 1, $this->indentation);
}
/**
* @param Nette\PhpGenerator\Traits\FunctionLike $function
*/
protected function printParameters($function, ?PhpNamespace $namespace): string
{
$params = [];
$list = $function->getParameters();
foreach ($list as $param) {
$variadic = $function->isVariadic() && $param === end($list);
$hint = $param->getTypeHint();
$params[] = ($hint ? ($param->isNullable() ? '?' : '') . ($namespace ? $namespace->unresolveName($hint) : $hint) . ' ' : '')
. ($param->isReference() ? '&' : '')
. ($variadic ? '...' : '')
. '$' . $param->getName()
. ($param->hasDefaultValue() && !$variadic ? ' = ' . Helpers::dump($param->getDefaultValue()) : '');
}
return strlen($tmp = implode(', ', $params)) > Helpers::WRAP_LENGTH && count($params) > 1
? "(\n" . $this->indentation . implode(",\n" . $this->indentation, $params) . "\n)"
: "($tmp)";
}
/**
* @param Nette\PhpGenerator\Traits\FunctionLike $function
*/
protected function printReturnType($function, ?PhpNamespace $namespace): string
{
return $function->getReturnType()
? ': ' . ($function->getReturnNullable() ? '?' : '') . ($namespace ? $namespace->unresolveName($function->getReturnType()) : $function->getReturnType())
: '';
}
}

View file

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

View file

@ -0,0 +1,23 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\PhpGenerator;
/**
* Generates PHP code compatible with PSR-2 and PSR-12.
*/
final class PsrPrinter extends Printer
{
/** @var string */
protected $indentation = ' ';
/** @var int */
protected $linesBetweenMethods = 1;
}

View file

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

View file

@ -0,0 +1,189 @@
<?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\Helpers;
use Nette\PhpGenerator\Parameter;
/**
* @internal
*/
trait FunctionLike
{
/** @var string */
private $body = '';
/** @var array of name => Parameter */
private $parameters = [];
/** @var bool */
private $variadic = false;
/** @var string|null */
private $returnType;
/** @var bool */
private $returnReference = false;
/** @var bool */
private $returnNullable = false;
/**
* @return static
*/
public function setBody(string $code, array $args = null): self
{
$this->body = $args === null ? $code : Helpers::formatArgs($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 : Helpers::formatArgs($code, $args)) . "\n";
return $this;
}
/**
* @param Parameter[] $val
* @return static
*/
public function setParameters(array $val): self
{
$this->parameters = [];
foreach ($val as $v) {
if (!$v instanceof Parameter) {
throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Parameter[].');
}
$this->parameters[$v->getName()] = $v;
}
return $this;
}
/**
* @return Parameter[]
*/
public function getParameters(): array
{
return $this->parameters;
}
/**
* @param string $name without $
*/
public function addParameter(string $name, $defaultValue = null): Parameter
{
$param = new Parameter($name);
if (func_num_args() > 1) {
$param->setDefaultValue($defaultValue);
}
return $this->parameters[$name] = $param;
}
/**
* @param string $name without $
* @return static
*/
public function removeParameter(string $name): self
{
unset($this->parameters[$name]);
return $this;
}
/**
* @return static
*/
public function setVariadic(bool $state = true): self
{
$this->variadic = $state;
return $this;
}
public function isVariadic(): bool
{
return $this->variadic;
}
/**
* @return static
*/
public function setReturnType(?string $val): self
{
$this->returnType = $val;
return $this;
}
public function getReturnType(): ?string
{
return $this->returnType;
}
/**
* @return static
*/
public function setReturnReference(bool $state = true): self
{
$this->returnReference = $state;
return $this;
}
public function getReturnReference(): bool
{
return $this->returnReference;
}
/**
* @return static
*/
public function setReturnNullable(bool $state = true): self
{
$this->returnNullable = $state;
return $this;
}
public function getReturnNullable(): bool
{
return $this->returnNullable;
}
/**
* @deprecated
*/
public function setNamespace(PhpNamespace $val = null): self
{
trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED);
return $this;
}
}

View file

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

View file

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