Генератор PHP коду

  • Потрібно згенерувати PHP-код для класів, функцій, PHP-файлів тощо?
  • Підтримує всі найновіші можливості PHP, такі як перерахування, атрибути тощо.
  • Дозволяє легко модифікувати існуючі класи
  • Вихідні дані, сумісні з PSR-12
  • Досконала, стабільна та широко використовувана бібліотека

Встановлення

Завантажте та встановіть пакунок за допомогою Composer:

composer require nette/php-generator

Сумісність з PHP див. у таблиці.

Класи

Почнемо з простого прикладу створення класу за допомогою ClassType:

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

$class
	->setFinal()
	->setExtends(ParentClass::class)
	->addImplement(Countable::class)
	->addComment("Description of class.\nSecond line\n")
	->addComment('@property-read Nette\Forms\Form $form');

// Щоб згенерувати PHP-код, просто перетворіть його в рядок або використовуйте echo:
echo $class;

Він відобразить такий результат:

/**
 * Description of class.
 * Second line
 *
 * @property-read Nette\Forms\Form $form
 */
final class Demo extends ParentClass implements Countable
{

}

Ми також можемо використовувати принтер для генерації коду, який, на відміну від 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('Count it.')
	->setFinal()
	->setProtected()
	->setReturnType('?int') // тип повернення методу
	->setBody('return count($items ?: $this->items);');

$method->addParameter('items', []) // $items = []
	->setReference()           // &$items = []
	->setType('array');        // масив &$items = []

Це призводить до:

/**
 * Count it.
 */
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;
	/** @використовуйте MyTrait<Foo> */
	use MyTrait {
		sayHello as protected;
	}
}

Перерахування

Ви можете легко створювати зчислення, які з'явилися у 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;
}

Ви також можете визначити скалярні еквіваленти для випадків, щоб створити підтримуваний зчислення:

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

Ви можете додати коментар або атрибути до кожного випадку за допомогою 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
// 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
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);

Результат:

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

Функція стрілки

Ви також можете роздрукувати закриття як функцію стрілки за допомогою принтера:

$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('Count it.')
	->setFinal()
	->setProtected()
	->setReturnType('?int');

Кожен параметр представлений класом Parameter. Знову ж таки, ви можете встановити всі можливі властивості:

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

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

Для визначення так званих варіадичних параметрів (або також операторів splat, spread, еліпсис, розпакування або три крапки) використовуйте setVariadics():

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

Generates:

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

Варіадний заповнювач ...?

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

Відповідність принтерів та PSR вимогам

PHP-код генерується об'єктами Printer. Існує об'єкт PsrPrinter, вивід якого відповідає стандартам PSR-2 і PSR-12 і використовує пробіли для відступів, і об'єкт Printer, який використовує табуляцію для відступів.

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

$printer = new Nette\PhpGenerator\PsrPrinter;
echo $printer->printClass($class); // Відступ 4 пробіли

Потрібно налаштувати поведінку принтера? Створіть власний, успадкувавши клас Printer. Ви можете переналаштувати ці змінні:

class MyPrinter extends Nette\PhpGenerator\Printer
{
	public int $wrapLength = 120;
	public string $indentation = "\t";
	public int $linesBetweenProperties = 0;
	public int $linesBetweenMethods = 2;
	public int $linesBetweenUseTypes = 0;
	public bool $bracesOnNextLine = true;
	public string $returnTypeColon = ': ';
}

Типи

Кожен тип або тип об'єднання/перетину може бути переданий як рядок, ви також можете використовувати попередньо визначені константи для нативних типів:

use Nette\PhpGenerator\Type;

$member->setType('array'); // або Type::Array;
$member->setType('array|string'); // або Type::union('array', '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);

Атрибути

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

$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addAttribute('Deprecated');

$class->addProperty('list')
	->addAttribute('WithArguments', [1, 2]);

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

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

echo $class;

Результат:

#[Deprecated]
class Demo
{
	#[WithArguments(1, 2)]
	public $list;


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

Простір імен

Класи, риси, інтерфейси та зчислення (далі класи) можуть бути згруповані у простори імен(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 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', тому що визначено оператор використання

І навпаки, ви можете перетворити спрощене ім'я класу, функції або константи на повне за допомогою методу resolveName:

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

Приведення імен класів

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

$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
// 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('This file is auto-generated.');
$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
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);

Результат:

<?php

/**
 * This file is auto-generated.
 */

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 = 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.

Дампер змінних

Дампер повертає розбірне PHP-рядкове представлення змінної. Забезпечує кращий і чіткіший вивід, ніж рідна функція var_export().

$dumper = new Nette\PhpGenerator\Dumper;

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

echo $dumper->dump($var); // prints ['a', 'b', 123]

Таблиця сумісності

PhpGenerator 4.0 сумісний з версіями PHP від 8.0 до 8.2

версію: 4.0