Генератор на 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.