Generator kodu PHP
- Obsługuje wszystkie najnowsze funkcje PHP (takie jak wyliczenia itp.)
- Pozwala na łatwą modyfikację istniejących klas
- Wyjście zgodne ze stylem kodowania PSR-12 / PER
- Dojrzała, stabilna i szeroko stosowana biblioteka
Instalacja
Pobierz i zainstaluj pakiet za pomocą Composera:
composer require nette/php-generator
W celu uzyskania informacji o kompatybilności z PHP, patrz tabela.
Klasy
Zacznijmy od prostego przykładu generowania klasy za pomocą 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');
// aby wygenerować kod PHP wystarczy rzutować na string lub użyć echo:
echo $class;
Wyrenderuje on taki wynik:
/**
* Description of class.
* Second line
*
* @property-read Nette\Forms\Form $form
*/
final class Demo extends ParentClass implements Countable
{
}
Do wygenerowania kodu możemy również użyć drukarki, którą w przeciwieństwie do echo $class
, będziemy
mogli dodatkowo skonfigurować:
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class);
Możemy dodać stałe (klasa Constant) i właściwości (klasa Property):
$class->addConstant('ID', 123)
->setProtected() // stała widoczność
->setType('int')
->setFinal();
$class->addProperty('items', [1, 2, 3])
->setPrivate() // lub setVisibility('private')
->setStatic()
->addComment('@var int[]');
$class->addProperty('list')
->setType('?array')
->setInitialized(); // drukuje '= null'
Generuje:
final protected const int ID = 123;
/** @var int[] */
private static $items = [1, 2, 3];
public ?array $list = null;
I możemy dodać metody:
$method = $class->addMethod('count')
->addComment('Count it.')
->setFinal()
->setProtected()
->setReturnType('?int') // metoda return type
->setBody('return count($items ?: $this->items);');
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
Wynika z tego, że:
/**
* Count it.
*/
final protected function count(array &$items = []): ?int
{
return count($items ?: $this->items);
}
Parametry promowane wprowadzone przez PHP 8.0 mogą być przekazywane do konstruktora:
$method = $class->addMethod('__construct');
$method->addPromotedParameter('name');
$method->addPromotedParameter('args', [])
->setPrivate();
Skutkuje to:
public function __construct(
public $name,
private $args = [],
) {
}
Właściwości i klasy readonly mogą być oznaczone poprzez setReadOnly()
.
Jeśli dodana właściwość, stała, metoda lub parametr już istnieje, rzuca wyjątek.
Członków można usunąć używając removeProperty()
, removeConstant()
, removeMethod()
lub removeParameter()
.
Możesz również dodać do klasy istniejące obiekty Method
, Property
lub 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);
Możesz sklonować istniejące metody, właściwości i stałe z inną nazwą używając cloneWithName()
:
$methodCount = $class->getMethod('count');
$methodRecount = $methodCount->cloneWithName('recount');
$class->addMember($methodRecount);
Interface lub Trait
Można tworzyć interfejsy i cechy (klasy InterfaceType i TraitType):
$interface = new Nette\PhpGenerator\InterfaceType('MyInterface');
$trait = new Nette\PhpGenerator\TraitType('MyTrait');
Wykorzystanie cech:
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addTrait('SmartObject');
$class->addTrait('MyTrait')
->addResolution('sayHello as protected')
->addComment('@use MyTrait<Foo>');
echo $class;
Wynik:
class Demo
{
use SmartObject;
/** @use MyTrait<Foo> */
use MyTrait {
sayHello as protected;
}
}
Enums
Możesz łatwo stworzyć enum, które przynosi PHP 8.1 (klasa EnumType):
$enum = new Nette\PhpGenerator\EnumType('Suit');
$enum->addCase('Clubs');
$enum->addCase('Diamonds');
$enum->addCase('Hearts');
$enum->addCase('Spades');
echo $enum;
Wynik:
enum Suit
{
case Clubs;
case Diamonds;
case Hearts;
case Spades;
}
Możesz również zdefiniować skalarne odpowiedniki dla przypadków, aby utworzyć backed enum:
$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');
Możliwe jest dodanie komentarza lub atrybutów do każdego przypadku za pomocą
addComment()
lub addAttribute()
.
Klasa anonimowa
Podaj null
jako nazwę i masz klasę anonimową:
$class = new Nette\PhpGenerator\ClassType(null);
$class->addMethod('__construct')
->addParameter('foo');
echo '$obj = new class ($val) ' . $class . ';';
Wynik:
$obj = new class ($val) {
public function __construct($foo)
{
}
};
Funkcja globalna
Kod funkcji wygeneruje klasa GlobalFunction:
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('return $a + $b;');
$function->addParameter('a');
$function->addParameter('b');
echo $function;
// lub użyć PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);
Result:
function foo($a, $b)
{
return $a + $b;
}
Zamknięcie
Kod zamknięcia wygeneruje klasę Closure:
$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('return $a + $b;');
$closure->addParameter('a');
$closure->addParameter('b');
$closure->addUse('c')
->setReference();
echo $closure;
// lub użyć PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);
Wynik:
function ($a, $b) use (&$c) {
return $a + $b;
}
Funkcja strzałki
Możesz również wydrukować zamknięcie jako funkcję strzałki za pomocą drukarki:
$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('$a + $b');
$closure->addParameter('a');
$closure->addParameter('b');
echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure);
Wynik:
fn($a, $b) => $a + $b
Sygnatura metody i funkcji
Metody są reprezentowane przez klasę Method. Możesz ustawić widoczność, wartość zwrotną, dodać komentarze, atrybuty itp:
$method = $class->addMethod('count')
->addComment('Count it.')
->setFinal()
->setProtected()
->setReturnType('?int');
Każdy parametr jest reprezentowany przez klasę Parameter. Ponownie, możesz ustawić każdą możliwą właściwość:
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
// function count(&$items = [])
Aby zdefiniować tzw. parametry variadics (lub również operator splat, spread, elipsa, rozpakowywanie czy trzy kropki),
należy użyć setVariadic()
:
$method = $class->addMethod('count');
$method->setVariadic(true);
$method->addParameter('items');
Generates:
function count(...$items)
{
}
Metoda i ciało funkcji
Ciało może być przekazane do metody setBody()
jednorazowo lub sekwencyjnie (linia po linii) poprzez wielokrotne
wywołanie addBody()
:
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('$a = rand(10, 20);');
$function->addBody('return $a;');
echo $function;
Result
function foo()
{
$a = rand(10, 20);
return $a;
}
Możesz użyć specjalnych zamienników dla poręcznego sposobu wstrzykiwania zmiennych.
Proste symbole miejsc ?
$str = 'any string';
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('return substr(?, ?);', [$str, $num]);
echo $function;
Wynik:
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;
Wynik:
function foo()
{
myfunc(1, 2, 3);
}
Możesz również użyć PHP 8 nazwanych parametrów używając placeholder ...?:
$items = ['foo' => 1, 'bar' => true];
$function->setBody('myfunc(...?:);', [$items]);
// myfunc(foo: 1, bar: true);
Ucieknij od placeholder używając slash \?
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addParameter('a');
$function->addBody('return $a \? 10 : ?;', [$num]);
echo $function;
Wynik:
function foo($a)
{
return $a ? 10 : 3;
}
Drukarki i zgodność z PSR
Klasa Printer służy do generowania kodu PHP:
$class = new Nette\PhpGenerator\ClassType('Demo');
// ...
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // to samo co: echo $class
Może generować kod dla wszystkich innych elementów, oferując metody takie jak printFunction()
,
printNamespace()
, itp.
Dodatkowo dostępna jest klasa PsrPrinter
, której dane wyjściowe są zgodne ze stylem kodowania PSR-2 / PSR-12
/ PER:
$printer = new Nette\PhpGenerator\PsrPrinter;
echo $printer->printClass($class);
Chcesz dostosować zachowanie do swoich potrzeb? Utwórz własną drukarkę, dziedzicząc z klasy Printer
.
Możesz ponownie skonfigurować te zmienne:
class MyPrinter extends Nette\PhpGenerator\Printer
{
// długość linii, po której linia zostanie przerwana
public int $wrapLength = 120;
// znak wcięcia, może być zastąpiony ciągiem spacji
public string $indentation = "\t";
// liczba pustych linii między właściwościami
public int $linesBetweenProperties = 0;
// liczba pustych linii między metodami
public int $linesBetweenMethods = 2;
// liczba pustych linii między grupami instrukcji użycia dla klas, funkcji i stałych
public int $linesBetweenUseTypes = 0;
// pozycja nawiasu klamrowego otwierającego dla funkcji i metod
public bool $bracesOnNextLine = true;
// umieszczenie jednego parametru w jednej linii, nawet jeśli ma atrybut lub jest promowany
public bool $singleParameterOnOneLine = false;
// omits namespaces that do not contain any class or function
public bool $omitEmptyNamespaces = true;
// separator między prawym nawiasem a typem zwracanej funkcji i metody
public string $returnTypeColon = ': ';
}
Czym i dlaczego dokładnie różnią się standardowe Printer
i PsrPrinter
? Dlaczego w pakiecie nie
ma tylko jednej drukarki, PsrPrinter
?
Standard Printer
formatuje kod tak, jak robimy to we wszystkich Nette. Ponieważ Nette powstało znacznie
wcześniej niż PSR, a także dlatego, że PSR przez wiele lat nie dostarczało standardów na czas, ale czasami nawet
z kilkuletnim opóźnieniem od wprowadzenia nowej funkcji w PHP, spowodowało to kilka drobnych różnic w standardzie kodowania. Większą różnicą jest właśnie
używanie tabulatorów zamiast spacji. Wiemy, że stosując tabulatory w naszych projektach umożliwiamy dostosowanie szerokości,
co jest istotne dla osób z wadami
wzroku. Przykładem drobnej różnicy jest umieszczenie nawiasu klamrowego w osobnej linii dla funkcji i metod oraz zawsze.
Uważamy, że zalecenie PSR jest nielogiczne i prowadzi do zmniejszenia przejrzystości
kodu.
Typy
Każdy typ lub typ unii / przecięcia może być przekazany jako ciąg, możesz również użyć predefiniowanych stałych dla typów natywnych:
use Nette\PhpGenerator\Type;
$member->setType('array'); // lub 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'); // lub Type::intersection(Foo::class, Bar::class)
$member->setType(null); // usuwa typ
To samo dotyczy metody setReturnType()
.
Literały
Dzięki Literal
możesz przekazać dowolny kod PHP do, na przykład, domyślnych wartości właściwości lub
parametrów itp:
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;
Wynik:
class Demo
{
public $foo = Iterator::SELF_FIRST;
public function bar($id = 1 + 2)
{
}
}
Możesz również przekazać parametry do Literal
i mieć je sformatowane w poprawny kod PHP za pomocą specjalnych placeholderów:
new Literal('substr(?, ?)', [$a, $b]);
// generuje, na przykład: substr('hello', 5);
Literał reprezentujący utworzenie nowego obiektu jest łatwo generowany przez metodę new
:
Literal::new(Demo::class, [$a, 'foo' => $b]);
// generuje na przykład: new Demo(10, foo: 20)
Atrybuty
Możesz dodać atrybuty PHP 8 do wszystkich klas, metod, właściwości, stałych, przypadków enum, funkcji, domknięć i parametrów. Literały mogą być również używane jako wartości parametrów.
$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;
Wynik:
#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])]
class Demo
{
#[Deprecated]
public $list;
#[Foo\Cached(mode: true)]
public function count(
#[Bar]
$items,
) {
}
}
Przestrzeń nazw
Klasy, cechy, interfejsy i enum (dalej klasy) mogą być grupowane w przestrzenie nazw(PhpNamespace):
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
// tworzyć nowe klasy w przestrzeni nazw
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');
// lub wstawić istniejącą klasę do przestrzeni nazw
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);
Jeśli klasa już istnieje, to rzuca wyjątek.
Możesz zdefiniować oświadczenia o użyciu:
// 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');
Aby uprościć w pełni kwalifikowaną nazwę klasy, funkcji lub stałej zgodnie ze zdefiniowanymi aliasami, użyj metody
simplifyName
:
echo $namespace->simplifyName('Foo\Bar'); // 'Bar', ponieważ 'Foo' jest bieżącą przestrzenią nazw
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', z powodu zdefiniowanej deklaracji użycia
I odwrotnie, można przekształcić uproszczoną nazwę klasy, funkcji lub stałej na w pełni kwalifikowaną, używając
metody resolveName
:
echo $namespace->resolveName('Bar'); // 'Foo\Bar'
echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'
Class Names Resolving (rozwiązywanie nazw klas)
Gdy klasa jest częścią przestrzeni nazw, jest renderowana nieco inaczej: wszystkie typy (np. podpowiedzi typów, typy zwracane, nazwa klasy nadrzędnej, zaimplementowane interfejsy, używane cechy i atrybuty) są automatycznie rozwiązywane (chyba że to wyłączysz, patrz poniżej). Oznacza to, że musisz używać w pełni kwalifikowanych nazw klas w definicjach, a zostaną one zastąpione aliasami (opartymi na klauzulach użycia) lub w pełni kwalifikowanymi nazwami w wynikowym kodzie:
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
$namespace->addUse('Bar\AliasedClass');
$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // to się uprości do A
->addTrait('Bar\AliasedClass'); // uprości się do AliasedClass
$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo')); // w komentarzach uprościć ręcznie
$method->addParameter('arg')
->setType('Bar\OtherClass'); // zostanie rozwiązany do klasy \Bar\Bar\Class
echo $namespace;
// lub użyć PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);
Wynik:
namespace Foo;
use Bar\AliasedClass;
class Demo implements A
{
use AliasedClass;
/**
* @return D
*/
public function method(\Bar\OtherClass $arg)
{
}
}
Auto-resolving można wyłączyć w ten sposób:
$printer = new Nette\PhpGenerator\Printer; // lub PsrPrinter
$printer->setTypeResolving(false);
echo $printer->printNamespace($namespace);
Pliki PHP.
Klasy, funkcje i przestrzenie nazw mogą być pogrupowane w pliki PHP reprezentowane przez klasę PhpFile:
$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('This file is auto-generated.');
$file->setStrictTypes(); // dodaje declare(strict_types=1)
$class = $file->addClass('Foo\A');
$function = $file->addFunction('Foo\foo');
// lub
// $namespace = $file->addNamespace('Foo');
// $class = $namespace->addClass('A');
// $function = $namespace->addFunction('foo');
echo $file;
// lub użyj PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);
Wynik:
<?php
/**
* This file is auto-generated.
*/
declare(strict_types=1);
namespace Foo;
class A
{
}
function foo()
{
}
Uwaga: Do plików nie można dodawać żadnego dodatkowego kodu poza funkcjami i klasami.
Generowanie według istniejących
Oprócz możliwości modelowania klas i funkcji za pomocą opisanego powyżej API, można również zlecić ich automatyczne generowanie według istniejących:
// tworzy klasę identyczną z klasą PDO
$class = Nette\PhpGenerator\ClassType::from(PDO::class);
// tworzy funkcję identyczną z trim()
$function = Nette\PhpGenerator\GlobalFunction::from('trim');
// tworzy zamknięcie identyczne jak
$closure = Nette\PhpGenerator\Closure::from(
function (stdClass $a, $b = null) {},
);
Ciała funkcji i metod są domyślnie puste. Jeśli chcesz je również załadować, użyj tego sposobu (wymaga on
zainstalowania nikic/php-parser
):
$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true);
$function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);
Ładowanie z pliku PHP
Można też ładować funkcje, klasy, interfejsy i enumy bezpośrednio z ciągu kodu PHP. Na przykład tworzymy obiekt
ClassType
w ten sposób:
$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX
<?php
class Demo
{
public $foo;
}
XX);
Podczas ładowania klas z kodu PHP, komentarze jednolinijkowe poza ciałami metod są ignorowane (np. dla właściwości itp.), ponieważ ta biblioteka nie posiada API do pracy z nimi.
Możesz również załadować bezpośrednio cały plik PHP, który może zawierać dowolną liczbę klas, funkcji, a nawet wiele przestrzeni nazw:
$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));
Ładowany jest również początkowy komentarz do pliku oraz deklaracja strict_types
. Z drugiej strony, wszystkie
inne globalne kody są ignorowane.
Wymaga to zainstalowania nikic/php-parser
.
Jeśli musisz manipulować globalnym kodem w plikach lub pojedynczymi stwierdzeniami w ciałach metod, lepiej jest
użyć bezpośrednio biblioteki nikic/php-parser
.
Class Manipulator
Klasa ClassManipulator zapewnia narzędzia do manipulowania klasami.
$class = new Nette\PhpGenerator\ClassType('Demo');
$manipulator = new Nette\PhpGenerator\ClassManipulator($class);
Metoda inheritMethod()
kopiuje metodę z klasy nadrzędnej lub zaimplementowanego interfejsu do klasy
użytkownika. Pozwala to na nadpisanie metody lub rozszerzenie jej sygnatury:
$method = $manipulator->inheritMethod('bar');
$method->setBody('...');
Metoda inheritProperty()
kopiuje właściwość z klasy nadrzędnej do klasy użytkownika. Jest to przydatne, gdy
chcesz mieć tę samą właściwość w swojej klasie, ale prawdopodobnie z inną wartością domyślną:
$property = $manipulator->inheritProperty('foo');
$property->setValue('new value');
Metoda implementInterface()
automatycznie implementuje wszystkie metody z danego interfejsu w klasie:
$manipulator->implementInterface(SomeInterface::class);
// Teraz twoja klasa implementuje interfejs SomeInterface i zawiera wszystkie jego metody
Zrzutka zmiennych
Dumper zwraca parsowalną reprezentację zmiennej w PHP. Zapewnia lepsze i bardziej przejrzyste wyjście niż funkcja natywna
var_export()
.
$dumper = new Nette\PhpGenerator\Dumper;
$var = ['a', 'b', 123];
echo $dumper->dump($var); // drukuje ['a', 'b', 123]
Tabela kompatybilności
PhpGenerator 4.0 i 4.1 są kompatybilne z PHP 8.0 do 8.3.