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

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

Инсталация

Можете да изтеглите и инсталирате библиотеката с помощта на инструмента 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") // Class description.\nSecond line\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('Пребройте го.') // Count it.
	->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);

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

Можете да създавате интерфейси и трейтове (класове 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;
}

Съкратени arrow функции

Можете също така да изведете съкратена анонимна функция с помощта на 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('Пребройте го.') // Count it.
	->setFinal()
	->setProtected()
	->setReturnType('?int');

Отделните параметри се представят от класа Parameter. Отново можете да настроите всички възможни свойства:

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

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

За дефиниране на т.нар. variadics параметри (или също 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;
}

Можете да използвате специални placeholders за лесно вмъкване на променливи.

Прости placeholders ?

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

Placeholder за 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);

Placeholder се екранира с помощта на наклонена черта \?

$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 coding style:

$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 код с помощта на placeholders:

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

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

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

Атрибути

Можете да добавите PHP 8 атрибути към всички класове, методи, свойства, константи, енуми, функции, closures и параметри. Като стойности на параметрите могат да се използват и #литерали.

$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-statement

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

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

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

Когато класът е част от пространство от имена, той се изобразява леко различно: всички типове (например typehints, типове на връщане, име на родителски клас, имплементирани интерфейси, използвани свойства и атрибути) се превеждат автоматично (освен ако не го изключите, вижте по-долу). Това означава, че трябва да използвате пълни имена на класове в дефинициите и те ще бъдат заменени с псевдоними (според 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('Този файл е автоматично генериран.'); // 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

/**
 * Този файл е автоматично генериран.
 */

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()` копира свойство от родителски клас във вашия клас. Това е полезно, когато искате да имате същото свойство във вашия клас, но например с друга стойност по подразбиране:

```php
$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.

версия: 4.0