Генератор на 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'); // 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;
/** @use 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, ellipsis, unpacking или three dots), използвайте 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 placeholder ...?
$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 = нов 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,
) {
}
}
Куки за имоти
Можете също така да дефинирате куки за свойства (представени от класа 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);
}
}
}
Свойствата и куките за свойства могат да бъдат абстрактни или окончателни:
$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 for read, private for write
$class->addProperty('id')
->setType('int')
->setProtected('set'); // protected for write
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 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
.
Манипулатор на класове
Класът 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.