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

Ви шукаєте інструмент для генерації PHP-коду для класів, функцій або цілих файлів?
  • Підтримує всі найновіші можливості PHP (наприклад, перерахування тощо).
  • Дозволяє легко модифікувати існуючі класи
  • Вихід, сумісний зі стилем кодування 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("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 / 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;
}

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

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

$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 вимогам

Клас 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:

$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;
	// кількість пропусків між групами інструкцій використання класів, функцій та констант
	public int $linesBetweenUseTypes = 0;
	// позиція відкриваючої дужки для функцій та методів
	public bool $bracesOnNextLine = true;
	// розміщувати один параметр в одному рядку, навіть якщо він має атрибут або є розкрученим
	public bool $singleParameterOnOneLine = false;
	// omits namespaces that do not contain any class or function
	public bool $omitEmptyNamespaces = true;
	// роздільник між правою круглою дужкою та типом повернення функцій та методів
	public string $returnTypeColon = ': ';
}

Як і чим саме відрізняються стандартні Printer та PsrPrinter? Чому в комплекті немає лише одного принтера PsrPrinter?

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

Типи

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

use Nette\PhpGenerator\Type;

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

Простір імен

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

версію: 4.0