Nette Documentation Preview

syntax
Nette PhpGenerator
******************

<div class="perex">
Ищете инструмент для генерации PHP-кода классов, функций или целых файлов?

- Поддерживает все последние возможности PHP (такие как property hooks, enums, атрибуты и т.д.)
- Позволяет легко модифицировать существующие классы
- Выходной код соответствует стилю кодирования PSR-12 / PER
- Зрелая, стабильная и широко используемая библиотека
</div>


Установка
---------

Вы можете скачать и установить библиотеку с помощью [Composer|best-practices:composer]:

```shell
composer require nette/php-generator
```

Совместимость с PHP можно найти в [таблице |#Таблица совместимости].


Классы
------

Начнем сразу с примера создания класса с помощью [ClassType |api:Nette\PhpGenerator\ClassType]:

```php
$class = new Nette\PhpGenerator\ClassType('Demo');

$class
	->setFinal()
	->setExtends(ParentClass::class)
	->addImplement(Countable::class)
	->addComment("Описание класса.\nВторая строка\n")
	->addComment('@property-read Nette\Forms\Form $form');

// код легко сгенерировать приведением к строке или использованием echo:
echo $class;
```

Возвращает следующий результат:

```php
/**
 * Описание класса
 * Вторая строка
 *
 * @property-read Nette\Forms\Form $form
 */
final class Demo extends ParentClass implements Countable
{
}
```

Для генерации кода мы также можем использовать так называемый printer, который, в отличие от `echo $class`, мы сможем [далее конфигурировать |#Printer и соответствие PSR]:

```php
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class);
```

Мы можем добавить константы (класс [Constant |api:Nette\PhpGenerator\Constant]) и свойства (класс [Property |api:Nette\PhpGenerator\Property]):

```php
$class->addConstant('ID', 123)
	->setProtected() // видимость констант
	->setType('int')
	->setFinal();

$class->addProperty('items', [1, 2, 3])
	->setPrivate() // или setVisibility('private')
	->setStatic()
	->addComment('@var int[]');

$class->addProperty('list')
	->setType('?array')
	->setInitialized(); // выведет '= null'
```

Сгенерирует:

```php
final protected const int ID = 123;

/** @var int[] */
private static $items = [1, 2, 3];

public ?array $list = null;
```

И мы можем добавить [методы |#Сигнатуры методов и функций]:

```php
$method = $class->addMethod('count')
	->addComment('Подсчитать.')
	->setFinal()
	->setProtected()
	->setReturnType('?int') // возвращаемые типы у методов
	->setBody('return count($items ?: $this->items);');

$method->addParameter('items', []) // $items = []
	->setReference()           // &$items = []
	->setType('array');        // array &$items = []
```

Результат:

```php
/**
 * Подсчитать.
 */
final protected function count(array &$items = []): ?int
{
	return count($items ?: $this->items);
}
```

Продвигаемые параметры, введенные в PHP 8.0, можно передать конструктору:

```php
$method = $class->addMethod('__construct');
$method->addPromotedParameter('name');
$method->addPromotedParameter('args', [])
	->setPrivate();
```

Результат:

```php
public function __construct(
	public $name,
	private $args = [],
) {
}
```

Свойства и классы, предназначенные только для чтения, можно отметить с помощью функции `setReadOnly()`.

------

Если добавляемое свойство, константа, метод или параметр уже существуют, будет выброшено исключение.

Члены класса можно удалить с помощью `removeProperty()`, `removeConstant()`, `removeMethod()` или `removeParameter()`.

В класс можно также добавить существующие объекты `Method`, `Property` или `Constant`:

```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);
```

Вы также можете клонировать существующие методы, свойства и константы под другим именем с помощью `cloneWithName()`:

```php
$methodCount = $class->getMethod('count');
$methodRecount = $methodCount->cloneWithName('recount');
$class->addMember($methodRecount);
```


Интерфейс или трейт
-------------------

Вы можете создавать интерфейсы и трейты (классы [InterfaceType |api:Nette\PhpGenerator\InterfaceType] и [TraitType |api:Nette\PhpGenerator\TraitType]):

```php
$interface = new Nette\PhpGenerator\InterfaceType('MyInterface');
$trait = new Nette\PhpGenerator\TraitType('MyTrait');
```

Использование трейтов:

```php
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addTrait('SmartObject');
$class->addTrait('MyTrait')
	->addResolution('sayHello as protected')
	->addComment('@use MyTrait<Foo>');
echo $class;
```

Результат:

```php
class Demo
{
	use SmartObject;
	/** @use MyTrait<Foo> */
	use MyTrait {
		sayHello as protected;
	}
}
```


Перечисления (Enums)
--------------------

Перечисления, которые появились в PHP 8.1, можно легко создать так: (класс [EnumType |api:Nette\PhpGenerator\EnumType]):

```php
$enum = new Nette\PhpGenerator\EnumType('Suit');
$enum->addCase('Clubs');
$enum->addCase('Diamonds');
$enum->addCase('Hearts');
$enum->addCase('Spades');

echo $enum;
```

Результат:

```php
enum Suit
{
	case Clubs;
	case Diamonds;
	case Hearts;
	case Spades;
}
```

Вы также можете определить скалярные эквиваленты и создать так называемый "backed" enum (поддерживаемое перечисление):

```php
$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');
```

К каждому *case* можно добавить комментарий или [##атрибуты] с помощью `addComment()` или `addAttribute()`.


Анонимные классы
----------------

Передадим `null` в качестве имени, и у нас будет анонимный класс:

```php
$class = new Nette\PhpGenerator\ClassType(null);
$class->addMethod('__construct')
	->addParameter('foo');

echo '$obj = new class ($val) ' . $class . ';';
```

Результат:

```php
$obj = new class ($val) {

	public function __construct($foo)
	{
	}
};
```


Глобальные функции
------------------

Код функций генерирует класс [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]:

```php
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('return $a + $b;');
$function->addParameter('a');
$function->addParameter('b');
echo $function;

// или используйте PsrPrinter для вывода в соответствии с PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);
```

Результат:

```php
function foo($a, $b)
{
	return $a + $b;
}
```


Анонимные функции
-----------------

Код анонимных функций генерирует класс [Closure |api:Nette\PhpGenerator\Closure]:

```php
$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('return $a + $b;');
$closure->addParameter('a');
$closure->addParameter('b');
$closure->addUse('c')
	->setReference();
echo $closure;

// или используйте PsrPrinter для вывода в соответствии с PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);
```

Результат:

```php
function ($a, $b) use (&$c) {
	return $a + $b;
}
```


Короткие стрелочные функции
---------------------------

Вы также можете вывести короткую анонимную функцию с помощью printer:

```php
$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('$a + $b');
$closure->addParameter('a');
$closure->addParameter('b');

echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure);
```

Результат:

```php
fn($a, $b) => $a + $b
```


Сигнатуры методов и функций
---------------------------

Методы представляет класс [Method |api:Nette\PhpGenerator\Method]. Вы можете установить видимость, возвращаемое значение, добавить комментарии, [#атрибуты] и т.д.:

```php
$method = $class->addMethod('count')
	->addComment('Подсчитать.')
	->setFinal()
	->setProtected()
	->setReturnType('?int');
```

Отдельные параметры представляет класс [Parameter |api:Nette\PhpGenerator\Parameter]. Опять же, вы можете установить все мыслимые свойства:

```php
$method->addParameter('items', []) // $items = []
	->setReference()           // &$items = []
	->setType('array');        // array &$items = []

// function count(array &$items = [])
```

Для определения так называемых variadic параметров (или splat оператора) служит `setVariadic()`:

```php
$method = $class->addMethod('count');
$method->setVariadic(true);
$method->addParameter('items');
```

Сгенерирует:

```php
function count(...$items)
{
}
```


Тела методов и функций
----------------------

Тело можно передать сразу методу `setBody()` или постепенно (по строкам) повторным вызовом `addBody()`:

```php
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('$a = rand(10, 20);');
$function->addBody('return $a;');
echo $function;
```

Результат

```php
function foo()
{
	$a = rand(10, 20);
	return $a;
}
```

Вы можете использовать специальные плейсхолдеры для легкой вставки переменных.

Простые плейсхолдеры `?`

```php
$str = 'any string';
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('return substr(?, ?);', [$str, $num]);
echo $function;
```

Результат

```php
function foo()
{
	return substr('any string', 3);
}
```

Плейсхолдер для variadic `...?`

```php
$items = [1, 2, 3];
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('myfunc(...?);', [$items]);
echo $function;
```

Результат:

```php
function foo()
{
	myfunc(1, 2, 3);
}
```

Вы также можете использовать именованные параметры для PHP 8 с помощью `...?:`

```php
$items = ['foo' => 1, 'bar' => true];
$function->setBody('myfunc(...?:);', [$items]);

// myfunc(foo: 1, bar: true);
```

Плейсхолдер экранируется с помощью косой черты `\?`

```php
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addParameter('a');
$function->addBody('return $a \? 10 : ?;', [$num]);
echo $function;
```

Результат:

```php
function foo($a)
{
	return $a ? 10 : 3;
}
```


Printer и соответствие PSR
--------------------------

Для генерации PHP-кода служит класс [Printer |api:Nette\PhpGenerator\Printer]:

```php
$class = new Nette\PhpGenerator\ClassType('Demo');
// ...

$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // то же самое, что: echo $class
```

Он умеет генерировать код всех других элементов, предлагает методы, такие как `printFunction()`, `printNamespace()` и т.д.

Доступен также класс `PsrPrinter`, вывод которого соответствует стилю кодирования PSR-2 / PSR-12 / PER:

```php
$printer = new Nette\PhpGenerator\PsrPrinter;
echo $printer->printClass($class);
```

Нужно настроить поведение под себя? Создайте собственную версию, унаследовав класс `Printer`. Можно переконфигурировать следующие переменные:

```php
class MyPrinter extends Nette\PhpGenerator\Printer
{
	// длина строки, после которой происходит перенос строки
	public int $wrapLength = 120;
	// символ отступа, может быть заменен последовательностью пробелов
	public string $indentation = "\t";
	// количество пустых строк между свойствами
	public int $linesBetweenProperties = 0;
	// количество пустых строк между методами
	public int $linesBetweenMethods = 2;
	// количество пустых строк между группами 'use statements' для классов, функций и констант
	public int $linesBetweenUseTypes = 0;
	// позиция открывающей фигурной скобки для функций и методов
	public bool $bracesOnNextLine = true;
	// размещать один параметр на одной строке, даже если у него есть атрибут или он продвигаемый
	public bool $singleParameterOnOneLine = false;
	// пропускает пространства имен, которые не содержат ни одного класса или функции
	public bool $omitEmptyNamespaces = true;
	// разделитель между закрывающей скобкой и возвращаемым типом функций и методов
	public string $returnTypeColon = ': ';
}
```

Как и почему, собственно, отличаются стандартный `Printer` и `PsrPrinter`? Почему в пакете не один printer, а именно `PsrPrinter`?

Стандартный `Printer` форматирует код так, как это делаем мы во всем Nette. Поскольку Nette появилось гораздо раньше, чем PSR, а также потому, что PSR долгие годы не предоставляло стандарты вовремя, а, например, с многолетней задержкой после введения новой фичи в PHP, произошло то, что [стандарт кодирования |contributing:coding-standard] в нескольких мелочах отличается. Большим отличием является только использование табуляции вместо пробелов. Мы знаем, что использование табуляции в наших проектах позволяет настраивать ширину, что [необходимо для людей с нарушениями зрения |contributing:coding-standard#Табуляции вместо пробелов]. Примером незначительного отличия является размещение фигурной скобки на отдельной строке у функций и методов, и это всегда. Рекомендация PSR нам кажется нелогичной и ведет к [снижению читаемости кода |contributing:coding-standard#Перенос строк и скобки].


Типы
----

Каждый тип или union/intersection тип можно передать как строку, вы также можете использовать предопределенные константы для встроенных типов:

```php
use Nette\PhpGenerator\Type;

$member->setType('array'); // или Type::Array;
$member->setType('?array'); // или Type::nullable(Type::Array);
$member->setType('array|string'); // или Type::union(Type::Array, Type::String)
$member->setType('Foo&Bar'); // или Type::intersection(Foo::class, Bar::class)
$member->setType(null); // удаляет тип
```

То же самое относится к методу `setReturnType()`.


Литералы
--------

С помощью `Literal` вы можете передавать любой PHP-код, например, для значений по умолчанию свойств или параметров и т.д.:

```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;
```

Результат:

```php
class Demo
{
	public $foo = Iterator::SELF_FIRST;

	public function bar($id = 1 + 2)
	{
	}
}
```

Вы также можете передать параметры в `Literal` и отформатировать их в валидный PHP-код с помощью [плейсхолдеров |#Тела методов и функций]:

```php
new Literal('substr(?, ?)', [$a, $b]);
// генерирует, например: substr('hello', 5);
```

Литерал, представляющий создание нового объекта, можно легко сгенерировать с помощью метода `new`:

```php
Literal::new(Demo::class, [$a, 'foo' => $b]);
// генерирует, например: new Demo(10, foo: 20)
```


Атрибуты
--------

Атрибуты PHP 8 можно добавить ко всем классам, методам, свойствам, константам, перечислениям, функциям, замыканиям и параметрам. В качестве значений параметров можно использовать и [##литералы].

```php
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addAttribute('Table', [
	'name' => 'user',
	'constraints' => [
		Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]),
	],
]);

$class->addProperty('list')
	->addAttribute('Deprecated');

$method = $class->addMethod('count')
	->addAttribute('Foo\Cached', ['mode' => true]);

$method->addParameter('items')
	->addAttribute('Bar');

echo $class;
```

Результат:

```php
#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])]
class Demo
{
	#[Deprecated]
	public $list;


	#[Foo\Cached(mode: true)]
	public function count(
		#[Bar]
		$items,
	) {
	}
}
```


Property Hooks
--------------

С помощью property hooks (представленных классом [PropertyHook|api:Nette\PhpGenerator\PropertyHook]) вы можете определить операции get и set для свойств, что является функцией, введенной в PHP 8.4:

```php
$class = new Nette\PhpGenerator\ClassType('Demo');
$prop = $class->addProperty('firstName')
    ->setType('string');

$prop->addHook('set', 'strtolower($value)')
    ->addParameter('value')
	    ->setType('string');

$prop->addHook('get')
	->setBody('return ucfirst($this->firstName);');

echo $class;
```

Сгенерирует:

```php
class Demo
{
    public string $firstName {
        set(string $value) => strtolower($value);
        get {
            return ucfirst($this->firstName);
        }
    }
}
```

Свойства и property hooks могут быть абстрактными или финальными:

```php
$class->addProperty('id')
    ->setType('int')
    ->addHook('get')
        ->setAbstract();

$class->addProperty('role')
    ->setType('string')
    ->addHook('set', 'strtolower($value)')
        ->setFinal();
```


Асимметричная видимость
-----------------------

PHP 8.4 вводит асимметричную видимость для свойств. Вы можете установить разные уровни доступа для чтения и записи.

Видимость можно установить либо с помощью метода `setVisibility()` с двумя параметрами, либо с помощью `setPublic()`, `setProtected()` или `setPrivate()` с параметром `mode`, который определяет, относится ли видимость к чтению или записи свойства. Режим по умолчанию — `'get'`.

```php
$class = new Nette\PhpGenerator\ClassType('Demo');

$class->addProperty('name')
    ->setType('string')
    ->setVisibility('public', 'private'); // public для чтения, private для записи

$class->addProperty('id')
    ->setType('int')
    ->setProtected('set'); // protected для записи

echo $class;
```

Сгенерирует:

```php
class Demo
{
    public private(set) string $name;

    protected(set) int $id;
}
```


Пространство имен
-----------------

Классы, свойства, интерфейсы и перечисления (далее — классы) можно сгруппировать в пространства имен, представленные классом [PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]:

```php
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');

// создаем новые классы в пространстве имен
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');

// или вставляем существующий класс в пространство имен
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);
```

Если класс уже существует, будет выброшено исключение.

Вы можете определить use-выражения:

```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');
```

Чтобы упростить полностью квалифицированное имя класса, функции или константы в соответствии с определенными псевдонимами, используйте метод `simplifyName`:

```php
echo $namespace->simplifyName('Foo\Bar'); // 'Bar', так как 'Foo' - текущее пространство имен
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', из-за определенного use-выражения
```

Упрощенное имя класса, функции или константы вы можете, наоборот, преобразовать в полностью квалифицированное имя с помощью метода `resolveName`:

```php
echo $namespace->resolveName('Bar'); // 'Foo\Bar'
echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'
```


Разрешение имен классов
-----------------------

**Когда класс является частью пространства имен, он отображается немного иначе:** все типы (например, typehint'ы, возвращаемые типы, имя родительского класса, реализованные интерфейсы, используемые свойства и атрибуты) автоматически *разрешаются* (если вы это не отключите, см. ниже). Это означает, что вы должны в определениях **использовать полные имена классов**, и они будут заменены на псевдонимы (согласно use-выражениям) или на полностью квалифицированные имена в результирующем коде:

```php
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
$namespace->addUse('Bar\AliasedClass');

$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // будет упрощено до A
	->addTrait('Bar\AliasedClass'); // будет упрощено до AliasedClass

$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // в комментариях упрощаем вручную
$method->addParameter('arg')
	->setType('Bar\OtherClass'); // будет преобразовано в \Bar\OtherClass

echo $namespace;

// или используйте PsrPrinter для вывода в соответствии с PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);
```

Результат:

```php
namespace Foo;

use Bar\AliasedClass;

class Demo implements A
{
	use AliasedClass;

	/**
	 * @return D
	 */
	public function method(\Bar\OtherClass $arg)
	{
	}
}
```

Автоматическое разрешение можно отключить следующим образом:

```php
$printer = new Nette\PhpGenerator\Printer; // или PsrPrinter
$printer->setTypeResolving(false);
echo $printer->printNamespace($namespace);
```


PHP-файлы
---------

Классы, функции и пространства имен можно сгруппировать в PHP-файлы, представленные классом [PhpFile|api:Nette\PhpGenerator\PhpFile]:

```php
$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('Этот файл сгенерирован автоматически.');
$file->setStrictTypes(); // добавит declare(strict_types=1)

$class = $file->addClass('Foo\A');
$function = $file->addFunction('Foo\foo');

// или
// $namespace = $file->addNamespace('Foo');
// $class = $namespace->addClass('A');
// $function = $namespace->addFunction('foo');

echo $file;

// или используйте PsrPrinter для вывода в соответствии с PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);
```

Результат:

```php
<?php

/**
 * Этот файл сгенерирован автоматически.
 */

declare(strict_types=1);

namespace Foo;

class A
{
}

function foo()
{
}
```

**Внимание:** В файлы нельзя добавлять никакой другой код, кроме функций и классов.


Генерация на основе существующих
--------------------------------

Помимо того, что классы и функции можно моделировать с помощью описанного выше API, их также можно генерировать автоматически на основе существующих образцов:

```php
// создаст класс, идентичный классу PDO
$class = Nette\PhpGenerator\ClassType::from(PDO::class);

// создаст функцию, идентичную функции trim()
$function = Nette\PhpGenerator\GlobalFunction::from('trim');

// создаст closure на основе указанного
$closure = Nette\PhpGenerator\Closure::from(
	function (stdClass $a, $b = null) {},
);
```

Тела функций и методов по умолчанию пусты. Если вы хотите их также загрузить, используйте этот способ (требует установки пакета `nikic/php-parser`):

```php
$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true);

$function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);
```


Загрузка из PHP-файлов
----------------------

Функции, классы, интерфейсы и перечисления можно загружать также прямо из строки, содержащей PHP-код. Например, так создадим объект `ClassType`:

```php
$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX
	<?php

	class Demo
	{
		public $foo;
	}
	XX);
```

При загрузке классов из PHP-кода однострочные комментарии вне тел методов игнорируются (например, у свойств и т.д.), поскольку эта библиотека не имеет API для работы с ними.

Вы также можете загрузить прямо весь PHP-файл, который может содержать любое количество классов, функций или даже пространств имен:

```php
$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));
```

Загрузится также вступительный комментарий к файлу и декларация `strict_types`. Напротив, весь остальной глобальный код игнорируется.

Требуется, чтобы был установлен `nikic/php-parser`.

.[note]
Если вам нужно манипулировать глобальным кодом в файлах или отдельными инструкциями в телах методов, лучше использовать непосредственно библиотеку `nikic/php-parser`.


Class Manipulator
-----------------

Класс [ClassManipulator|api:Nette\PhpGenerator\ClassManipulator] предоставляет инструменты для манипулирования классами.

```php
$class = new Nette\PhpGenerator\ClassType('Demo');
$manipulator = new Nette\PhpGenerator\ClassManipulator($class);
```

Метод `inheritMethod()` скопирует метод из родительского класса или реализованного интерфейса в ваш класс. Это позволит вам переопределить метод или расширить его сигнатуру:

```php
$method = $manipulator->inheritMethod('bar');
$method->setBody('...');
```

Метод `inheritProperty()` скопирует свойство из родительского класса в ваш класс. Это полезно, когда вы хотите иметь в своем классе то же свойство, но, например, с другим значением по умолчанию:

```php
$property = $manipulator->inheritProperty('foo');
$property->setValue('new value');
```

Метод `implement()` автоматически реализует все методы и свойства из данного интерфейса или абстрактного класса в вашем классе:

```php
$manipulator->implement(SomeInterface::class);
// Теперь ваш класс реализует SomeInterface и содержит все его методы
```


Вывод переменных
----------------

Класс `Dumper` преобразует переменную в парсируемый PHP-код. Он предоставляет лучший и более понятный вывод, чем стандартная функция `var_export()`.

```php
$dumper = new Nette\PhpGenerator\Dumper;

$var = ['a', 'b', 123];

echo $dumper->dump($var); // выведет ['a', 'b', 123]
```


Таблица совместимости
---------------------

PhpGenerator 4.1 совместим с PHP 8.0 по 8.4.

{{leftbar: nette:@menu-topics}}

Nette PhpGenerator

Ищете инструмент для генерации PHP-кода классов, функций или целых файлов?
  • Поддерживает все последние возможности PHP (такие как property hooks, enums, атрибуты и т.д.)
  • Позволяет легко модифицировать существующие классы
  • Выходной код соответствует стилю кодирования PSR-12 / PER
  • Зрелая, стабильная и широко используемая библиотека

Установка

Вы можете скачать и установить библиотеку с помощью Composer:

composer require nette/php-generator

Совместимость с PHP можно найти в таблице.

Классы

Начнем сразу с примера создания класса с помощью ClassType:

$class = new Nette\PhpGenerator\ClassType('Demo');

$class
	->setFinal()
	->setExtends(ParentClass::class)
	->addImplement(Countable::class)
	->addComment("Описание класса.\nВторая строка\n")
	->addComment('@property-read Nette\Forms\Form $form');

// код легко сгенерировать приведением к строке или использованием echo:
echo $class;

Возвращает следующий результат:

/**
 * Описание класса
 * Вторая строка
 *
 * @property-read Nette\Forms\Form $form
 */
final class Demo extends ParentClass implements Countable
{
}

Для генерации кода мы также можем использовать так называемый printer, который, в отличие от echo $class, мы сможем далее конфигурировать:

$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class);

Мы можем добавить константы (класс Constant) и свойства (класс Property):

$class->addConstant('ID', 123)
	->setProtected() // видимость констант
	->setType('int')
	->setFinal();

$class->addProperty('items', [1, 2, 3])
	->setPrivate() // или setVisibility('private')
	->setStatic()
	->addComment('@var int[]');

$class->addProperty('list')
	->setType('?array')
	->setInitialized(); // выведет '= null'

Сгенерирует:

final protected const int ID = 123;

/** @var int[] */
private static $items = [1, 2, 3];

public ?array $list = null;

И мы можем добавить методы:

$method = $class->addMethod('count')
	->addComment('Подсчитать.')
	->setFinal()
	->setProtected()
	->setReturnType('?int') // возвращаемые типы у методов
	->setBody('return count($items ?: $this->items);');

$method->addParameter('items', []) // $items = []
	->setReference()           // &$items = []
	->setType('array');        // array &$items = []

Результат:

/**
 * Подсчитать.
 */
final protected function count(array &$items = []): ?int
{
	return count($items ?: $this->items);
}

Продвигаемые параметры, введенные в PHP 8.0, можно передать конструктору:

$method = $class->addMethod('__construct');
$method->addPromotedParameter('name');
$method->addPromotedParameter('args', [])
	->setPrivate();

Результат:

public function __construct(
	public $name,
	private $args = [],
) {
}

Свойства и классы, предназначенные только для чтения, можно отметить с помощью функции setReadOnly().


Если добавляемое свойство, константа, метод или параметр уже существуют, будет выброшено исключение.

Члены класса можно удалить с помощью removeProperty(), removeConstant(), removeMethod() или removeParameter().

В класс можно также добавить существующие объекты Method, Property или Constant:

$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);

Вы также можете клонировать существующие методы, свойства и константы под другим именем с помощью cloneWithName():

$methodCount = $class->getMethod('count');
$methodRecount = $methodCount->cloneWithName('recount');
$class->addMember($methodRecount);

Интерфейс или трейт

Вы можете создавать интерфейсы и трейты (классы InterfaceType и TraitType):

$interface = new Nette\PhpGenerator\InterfaceType('MyInterface');
$trait = new Nette\PhpGenerator\TraitType('MyTrait');

Использование трейтов:

$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addTrait('SmartObject');
$class->addTrait('MyTrait')
	->addResolution('sayHello as protected')
	->addComment('@use MyTrait<Foo>');
echo $class;

Результат:

class Demo
{
	use SmartObject;
	/** @use MyTrait<Foo> */
	use MyTrait {
		sayHello as protected;
	}
}

Перечисления (Enums)

Перечисления, которые появились в PHP 8.1, можно легко создать так: (класс EnumType):

$enum = new Nette\PhpGenerator\EnumType('Suit');
$enum->addCase('Clubs');
$enum->addCase('Diamonds');
$enum->addCase('Hearts');
$enum->addCase('Spades');

echo $enum;

Результат:

enum Suit
{
	case Clubs;
	case Diamonds;
	case Hearts;
	case Spades;
}

Вы также можете определить скалярные эквиваленты и создать так называемый „backed“ enum (поддерживаемое перечисление):

$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');

К каждому case можно добавить комментарий или атрибуты с помощью addComment() или addAttribute().

Анонимные классы

Передадим null в качестве имени, и у нас будет анонимный класс:

$class = new Nette\PhpGenerator\ClassType(null);
$class->addMethod('__construct')
	->addParameter('foo');

echo '$obj = new class ($val) ' . $class . ';';

Результат:

$obj = new class ($val) {

	public function __construct($foo)
	{
	}
};

Глобальные функции

Код функций генерирует класс GlobalFunction:

$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('return $a + $b;');
$function->addParameter('a');
$function->addParameter('b');
echo $function;

// или используйте PsrPrinter для вывода в соответствии с PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);

Результат:

function foo($a, $b)
{
	return $a + $b;
}

Анонимные функции

Код анонимных функций генерирует класс Closure:

$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('return $a + $b;');
$closure->addParameter('a');
$closure->addParameter('b');
$closure->addUse('c')
	->setReference();
echo $closure;

// или используйте PsrPrinter для вывода в соответствии с PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);

Результат:

function ($a, $b) use (&$c) {
	return $a + $b;
}

Короткие стрелочные функции

Вы также можете вывести короткую анонимную функцию с помощью printer:

$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('$a + $b');
$closure->addParameter('a');
$closure->addParameter('b');

echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure);

Результат:

fn($a, $b) => $a + $b

Сигнатуры методов и функций

Методы представляет класс Method. Вы можете установить видимость, возвращаемое значение, добавить комментарии, атрибуты и т.д.:

$method = $class->addMethod('count')
	->addComment('Подсчитать.')
	->setFinal()
	->setProtected()
	->setReturnType('?int');

Отдельные параметры представляет класс Parameter. Опять же, вы можете установить все мыслимые свойства:

$method->addParameter('items', []) // $items = []
	->setReference()           // &$items = []
	->setType('array');        // array &$items = []

// function count(array &$items = [])

Для определения так называемых variadic параметров (или splat оператора) служит setVariadic():

$method = $class->addMethod('count');
$method->setVariadic(true);
$method->addParameter('items');

Сгенерирует:

function count(...$items)
{
}

Тела методов и функций

Тело можно передать сразу методу setBody() или постепенно (по строкам) повторным вызовом addBody():

$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('$a = rand(10, 20);');
$function->addBody('return $a;');
echo $function;

Результат

function foo()
{
	$a = rand(10, 20);
	return $a;
}

Вы можете использовать специальные плейсхолдеры для легкой вставки переменных.

Простые плейсхолдеры ?

$str = 'any string';
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('return substr(?, ?);', [$str, $num]);
echo $function;

Результат

function foo()
{
	return substr('any string', 3);
}

Плейсхолдер для variadic ...?

$items = [1, 2, 3];
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('myfunc(...?);', [$items]);
echo $function;

Результат:

function foo()
{
	myfunc(1, 2, 3);
}

Вы также можете использовать именованные параметры для PHP 8 с помощью ...?:

$items = ['foo' => 1, 'bar' => true];
$function->setBody('myfunc(...?:);', [$items]);

// myfunc(foo: 1, bar: true);

Плейсхолдер экранируется с помощью косой черты \?

$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addParameter('a');
$function->addBody('return $a \? 10 : ?;', [$num]);
echo $function;

Результат:

function foo($a)
{
	return $a ? 10 : 3;
}

Printer и соответствие PSR

Для генерации PHP-кода служит класс Printer:

$class = new Nette\PhpGenerator\ClassType('Demo');
// ...

$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // то же самое, что: echo $class

Он умеет генерировать код всех других элементов, предлагает методы, такие как printFunction(), printNamespace() и т.д.

Доступен также класс PsrPrinter, вывод которого соответствует стилю кодирования PSR-2 / PSR-12 / PER:

$printer = new Nette\PhpGenerator\PsrPrinter;
echo $printer->printClass($class);

Нужно настроить поведение под себя? Создайте собственную версию, унаследовав класс Printer. Можно переконфигурировать следующие переменные:

class MyPrinter extends Nette\PhpGenerator\Printer
{
	// длина строки, после которой происходит перенос строки
	public int $wrapLength = 120;
	// символ отступа, может быть заменен последовательностью пробелов
	public string $indentation = "\t";
	// количество пустых строк между свойствами
	public int $linesBetweenProperties = 0;
	// количество пустых строк между методами
	public int $linesBetweenMethods = 2;
	// количество пустых строк между группами 'use statements' для классов, функций и констант
	public int $linesBetweenUseTypes = 0;
	// позиция открывающей фигурной скобки для функций и методов
	public bool $bracesOnNextLine = true;
	// размещать один параметр на одной строке, даже если у него есть атрибут или он продвигаемый
	public bool $singleParameterOnOneLine = false;
	// пропускает пространства имен, которые не содержат ни одного класса или функции
	public bool $omitEmptyNamespaces = true;
	// разделитель между закрывающей скобкой и возвращаемым типом функций и методов
	public string $returnTypeColon = ': ';
}

Как и почему, собственно, отличаются стандартный Printer и PsrPrinter? Почему в пакете не один printer, а именно PsrPrinter?

Стандартный Printer форматирует код так, как это делаем мы во всем Nette. Поскольку Nette появилось гораздо раньше, чем PSR, а также потому, что PSR долгие годы не предоставляло стандарты вовремя, а, например, с многолетней задержкой после введения новой фичи в PHP, произошло то, что стандарт кодирования в нескольких мелочах отличается. Большим отличием является только использование табуляции вместо пробелов. Мы знаем, что использование табуляции в наших проектах позволяет настраивать ширину, что необходимо для людей с нарушениями зрения. Примером незначительного отличия является размещение фигурной скобки на отдельной строке у функций и методов, и это всегда. Рекомендация PSR нам кажется нелогичной и ведет к снижению читаемости кода.

Типы

Каждый тип или union/intersection тип можно передать как строку, вы также можете использовать предопределенные константы для встроенных типов:

use Nette\PhpGenerator\Type;

$member->setType('array'); // или Type::Array;
$member->setType('?array'); // или Type::nullable(Type::Array);
$member->setType('array|string'); // или Type::union(Type::Array, Type::String)
$member->setType('Foo&Bar'); // или Type::intersection(Foo::class, Bar::class)
$member->setType(null); // удаляет тип

То же самое относится к методу setReturnType().

Литералы

С помощью Literal вы можете передавать любой 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;

Результат:

class Demo
{
	public $foo = Iterator::SELF_FIRST;

	public function bar($id = 1 + 2)
	{
	}
}

Вы также можете передать параметры в Literal и отформатировать их в валидный PHP-код с помощью плейсхолдеров:

new Literal('substr(?, ?)', [$a, $b]);
// генерирует, например: substr('hello', 5);

Литерал, представляющий создание нового объекта, можно легко сгенерировать с помощью метода new:

Literal::new(Demo::class, [$a, 'foo' => $b]);
// генерирует, например: new Demo(10, foo: 20)

Атрибуты

Атрибуты PHP 8 можно добавить ко всем классам, методам, свойствам, константам, перечислениям, функциям, замыканиям и параметрам. В качестве значений параметров можно использовать и литералы.

$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addAttribute('Table', [
	'name' => 'user',
	'constraints' => [
		Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]),
	],
]);

$class->addProperty('list')
	->addAttribute('Deprecated');

$method = $class->addMethod('count')
	->addAttribute('Foo\Cached', ['mode' => true]);

$method->addParameter('items')
	->addAttribute('Bar');

echo $class;

Результат:

#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])]
class Demo
{
	#[Deprecated]
	public $list;


	#[Foo\Cached(mode: true)]
	public function count(
		#[Bar]
		$items,
	) {
	}
}

Property Hooks

С помощью property hooks (представленных классом PropertyHook) вы можете определить операции get и set для свойств, что является функцией, введенной в PHP 8.4:

$class = new Nette\PhpGenerator\ClassType('Demo');
$prop = $class->addProperty('firstName')
    ->setType('string');

$prop->addHook('set', 'strtolower($value)')
    ->addParameter('value')
	    ->setType('string');

$prop->addHook('get')
	->setBody('return ucfirst($this->firstName);');

echo $class;

Сгенерирует:

class Demo
{
    public string $firstName {
        set(string $value) => strtolower($value);
        get {
            return ucfirst($this->firstName);
        }
    }
}

Свойства и property hooks могут быть абстрактными или финальными:

$class->addProperty('id')
    ->setType('int')
    ->addHook('get')
        ->setAbstract();

$class->addProperty('role')
    ->setType('string')
    ->addHook('set', 'strtolower($value)')
        ->setFinal();

Асимметричная видимость

PHP 8.4 вводит асимметричную видимость для свойств. Вы можете установить разные уровни доступа для чтения и записи.

Видимость можно установить либо с помощью метода setVisibility() с двумя параметрами, либо с помощью setPublic(), setProtected() или setPrivate() с параметром mode, который определяет, относится ли видимость к чтению или записи свойства. Режим по умолчанию — 'get'.

$class = new Nette\PhpGenerator\ClassType('Demo');

$class->addProperty('name')
    ->setType('string')
    ->setVisibility('public', 'private'); // public для чтения, private для записи

$class->addProperty('id')
    ->setType('int')
    ->setProtected('set'); // protected для записи

echo $class;

Сгенерирует:

class Demo
{
    public private(set) string $name;

    protected(set) int $id;
}

Пространство имен

Классы, свойства, интерфейсы и перечисления (далее — классы) можно сгруппировать в пространства имен, представленные классом PhpNamespace:

$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');

// создаем новые классы в пространстве имен
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');

// или вставляем существующий класс в пространство имен
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);

Если класс уже существует, будет выброшено исключение.

Вы можете определить use-выражения:

// 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');

Чтобы упростить полностью квалифицированное имя класса, функции или константы в соответствии с определенными псевдонимами, используйте метод simplifyName:

echo $namespace->simplifyName('Foo\Bar'); // 'Bar', так как 'Foo' - текущее пространство имен
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', из-за определенного use-выражения

Упрощенное имя класса, функции или константы вы можете, наоборот, преобразовать в полностью квалифицированное имя с помощью метода resolveName:

echo $namespace->resolveName('Bar'); // 'Foo\Bar'
echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'

Разрешение имен классов

Когда класс является частью пространства имен, он отображается немного иначе: все типы (например, typehint'ы, возвращаемые типы, имя родительского класса, реализованные интерфейсы, используемые свойства и атрибуты) автоматически разрешаются (если вы это не отключите, см. ниже). Это означает, что вы должны в определениях использовать полные имена классов, и они будут заменены на псевдонимы (согласно use-выражениям) или на полностью квалифицированные имена в результирующем коде:

$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
$namespace->addUse('Bar\AliasedClass');

$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // будет упрощено до A
	->addTrait('Bar\AliasedClass'); // будет упрощено до AliasedClass

$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // в комментариях упрощаем вручную
$method->addParameter('arg')
	->setType('Bar\OtherClass'); // будет преобразовано в \Bar\OtherClass

echo $namespace;

// или используйте PsrPrinter для вывода в соответствии с PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);

Результат:

namespace Foo;

use Bar\AliasedClass;

class Demo implements A
{
	use AliasedClass;

	/**
	 * @return D
	 */
	public function method(\Bar\OtherClass $arg)
	{
	}
}

Автоматическое разрешение можно отключить следующим образом:

$printer = new Nette\PhpGenerator\Printer; // или PsrPrinter
$printer->setTypeResolving(false);
echo $printer->printNamespace($namespace);

PHP-файлы

Классы, функции и пространства имен можно сгруппировать в PHP-файлы, представленные классом PhpFile:

$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('Этот файл сгенерирован автоматически.');
$file->setStrictTypes(); // добавит declare(strict_types=1)

$class = $file->addClass('Foo\A');
$function = $file->addFunction('Foo\foo');

// или
// $namespace = $file->addNamespace('Foo');
// $class = $namespace->addClass('A');
// $function = $namespace->addFunction('foo');

echo $file;

// или используйте PsrPrinter для вывода в соответствии с PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);

Результат:

<?php

/**
 * Этот файл сгенерирован автоматически.
 */

declare(strict_types=1);

namespace Foo;

class A
{
}

function foo()
{
}

Внимание: В файлы нельзя добавлять никакой другой код, кроме функций и классов.

Генерация на основе существующих

Помимо того, что классы и функции можно моделировать с помощью описанного выше API, их также можно генерировать автоматически на основе существующих образцов:

// создаст класс, идентичный классу PDO
$class = Nette\PhpGenerator\ClassType::from(PDO::class);

// создаст функцию, идентичную функции trim()
$function = Nette\PhpGenerator\GlobalFunction::from('trim');

// создаст closure на основе указанного
$closure = Nette\PhpGenerator\Closure::from(
	function (stdClass $a, $b = null) {},
);

Тела функций и методов по умолчанию пусты. Если вы хотите их также загрузить, используйте этот способ (требует установки пакета nikic/php-parser):

$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true);

$function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);

Загрузка из PHP-файлов

Функции, классы, интерфейсы и перечисления можно загружать также прямо из строки, содержащей PHP-код. Например, так создадим объект ClassType:

$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX
	<?php

	class Demo
	{
		public $foo;
	}
	XX);

При загрузке классов из PHP-кода однострочные комментарии вне тел методов игнорируются (например, у свойств и т.д.), поскольку эта библиотека не имеет API для работы с ними.

Вы также можете загрузить прямо весь PHP-файл, который может содержать любое количество классов, функций или даже пространств имен:

$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));

Загрузится также вступительный комментарий к файлу и декларация strict_types. Напротив, весь остальной глобальный код игнорируется.

Требуется, чтобы был установлен nikic/php-parser.

Если вам нужно манипулировать глобальным кодом в файлах или отдельными инструкциями в телах методов, лучше использовать непосредственно библиотеку nikic/php-parser.

Class Manipulator

Класс ClassManipulator предоставляет инструменты для манипулирования классами.

$class = new Nette\PhpGenerator\ClassType('Demo');
$manipulator = new Nette\PhpGenerator\ClassManipulator($class);

Метод inheritMethod() скопирует метод из родительского класса или реализованного интерфейса в ваш класс. Это позволит вам переопределить метод или расширить его сигнатуру:

$method = $manipulator->inheritMethod('bar');
$method->setBody('...');

Метод inheritProperty() скопирует свойство из родительского класса в ваш класс. Это полезно, когда вы хотите иметь в своем классе то же свойство, но, например, с другим значением по умолчанию:

$property = $manipulator->inheritProperty('foo');
$property->setValue('new value');

Метод implement() автоматически реализует все методы и свойства из данного интерфейса или абстрактного класса в вашем классе:

$manipulator->implement(SomeInterface::class);
// Теперь ваш класс реализует SomeInterface и содержит все его методы

Вывод переменных

Класс Dumper преобразует переменную в парсируемый PHP-код. Он предоставляет лучший и более понятный вывод, чем стандартная функция var_export().

$dumper = new Nette\PhpGenerator\Dumper;

$var = ['a', 'b', 123];

echo $dumper->dump($var); // выведет ['a', 'b', 123]

Таблица совместимости

PhpGenerator 4.1 совместим с PHP 8.0 по 8.4.