Nette PhpGenerator
- Obsługuje wszystkie najnowsze funkcje PHP (takie jak property hooks, enumy, atrybuty itp.)
- Umożliwia łatwą modyfikację istniejących klas
- Kod wyjściowy jest zgodny ze stylem kodowania PSR-12 / PER
- Dojrzała, stabilna i szeroko stosowana biblioteka
Instalacja
Bibliotekę pobierzesz i zainstalujesz za pomocą narzędzia Composer:
composer require nette/php-generator
Kompatybilność z PHP znajdziesz w tabeli.
Klasy
Zacznijmy od razu od przykładu tworzenia klasy za pomocą ClassType:
$class = new Nette\PhpGenerator\ClassType('Demo'); $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) ->addComment("Opis klasy.\nDruga linia\n") ->addComment('@property-read Nette\Forms\Form $form'); // kod można łatwo wygenerować przez rzutowanie na ciąg znaków lub użycie echo: echo $class;
Zwraca następujący wynik:
/** * Opis klasy * Druga linia * * @property-read Nette\Forms\Form $form */ final class Demo extends ParentClass implements Countable { }
Do wygenerowania kodu możemy również użyć tzw. printera, który w przeciwieństwie do echo $class
będziemy mogli dalej konfigurować:
$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() // widoczność stałych ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) ->setPrivate() // lub setVisibility('private') ->setStatic() ->addComment('@var int[]'); $class->addProperty('list') ->setType('?array') ->setInitialized(); // wypisze '= null'
Wygeneruje:
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('Policz to.') ->setFinal() ->setProtected() ->setReturnType('?int') // typy zwracane w metodach ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] ->setReference() // &$items = [] ->setType('array'); // array &$items = []
Wynikiem jest:
/** * Policz to. */ final protected function count(array &$items = []): ?int { return count($items ?: $this->items); }
Promowane parametry wprowadzone w PHP 8.0 można przekazać do konstruktora:
$method = $class->addMethod('__construct'); $method->addPromotedParameter('name'); $method->addPromotedParameter('args', []) ->setPrivate();
Wynikiem jest:
public function __construct( public $name, private $args = [], ) { }
Właściwości i klasy przeznaczone tylko do odczytu można oznaczyć za pomocą funkcji setReadOnly()
.
Jeśli dodawana właściwość, stała, metoda lub parametr już istnieją, zostanie rzucony wyjątek.
Członków klasy można usunąć za pomocą removeProperty()
, removeConstant()
, removeMethod()
lub removeParameter()
.
Do klasy można również dodać 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żna również klonować istniejące metody, właściwości i stałe pod inną nazwą za pomocą cloneWithName()
:
$methodCount = $class->getMethod('count'); $methodRecount = $methodCount->cloneWithName('recount'); $class->addMember($methodRecount);
Interfejs lub Trait
Można tworzyć interfejsy i traity (klasy InterfaceType i TraitType):
$interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait');
Używanie traitów:
$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; } }
Enumy
Wyliczenia (enumy), które wprowadza PHP 8.1, można łatwo utworzyć w ten sposób (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żna również zdefiniować ekvivalenty skalarne i utworzyć w ten sposób “backed” wyliczenie:
$enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦');
Do każdego case można dodać komentarz lub atrybuty za pomocą addComment()
lub addAttribute()
.
Klasy anonimowe
Jako nazwę przekażemy null
i mamy 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) { } };
Funkcje globalne
Kod funkcji generuje klasa GlobalFunction:
$function = new Nette\PhpGenerator\GlobalFunction('foo'); $function->setBody('return $a + $b;'); $function->addParameter('a'); $function->addParameter('b'); echo $function; // lub użyj PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);
Wynik:
function foo($a, $b) { return $a + $b; }
Funkcje anonimowe
Kod funkcji anonimowych generuje klasa 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żyj 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; }
Skrócone funkcje strzałkowe
Można również wypisać skróconą funkcję anonimową za pomocą printera:
$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
Sygnatury metod i funkcji
Metody reprezentuje klasa Method. Można ustawić widoczność, wartość zwracaną, dodać komentarze, atrybuty itp.:
$method = $class->addMethod('count') ->addComment('Policz to.') ->setFinal() ->setProtected() ->setReturnType('?int');
Poszczególne parametry reprezentuje klasa Parameter. Ponownie można ustawić wszystkie możliwe właściwości:
$method->addParameter('items', []) // $items = [] ->setReference() // &$items = [] ->setType('array'); // array &$items = [] // function count(array &$items = [])
Do definicji tzw. parametrów variadics (lub też operatora splat) służy setVariadic()
:
$method = $class->addMethod('count'); $method->setVariadic(true); $method->addParameter('items');
Wygeneruje:
function count(...$items) { }
Ciała metod i funkcji
Ciało można przekazać naraz metodzie setBody()
lub stopniowo (linia po linii) przez wielokrotne wywołanie addBody()
:
$function = new Nette\PhpGenerator\GlobalFunction('foo'); $function->addBody('$a = rand(10, 20);'); $function->addBody('return $a;'); echo $function;
Wynik
function foo() { $a = rand(10, 20); return $a; }
Można użyć specjalnych symboli zastępczych do łatwego wstawiania zmiennych.
Proste symbole zastępcze ?
$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); }
Symbol zastępczy dla variadic ...?
$items = [1, 2, 3]; $function = new Nette\PhpGenerator\GlobalFunction('foo'); $function->setBody('myfunc(...?);', [$items]); echo $function;
Wynik:
function foo() { myfunc(1, 2, 3); }
Można również użyć nazwanych parametrów dla PHP 8 za pomocą ...?:
$items = ['foo' => 1, 'bar' => true]; $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true);
Symbol zastępczy escapuje się za pomocą ukośnika \?
$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; }
Printer i zgodność z PSR
Do generowania kodu PHP służy klasa Printer:
$class = new Nette\PhpGenerator\ClassType('Demo'); // ... $printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class); // to samo, co: echo $class
Potrafi wygenerować kod wszystkich innych elementów, oferuje metody takie jak printFunction()
, printNamespace()
, itd.
Dostępna jest również klasa PsrPrinter
, której wyjście jest zgodne ze stylem kodowania PSR-2 / PSR-12 / PER:
$printer = new Nette\PhpGenerator\PsrPrinter; echo $printer->printClass($class);
Potrzebujesz dostosować zachowanie do własnych potrzeb? Utwórz własną wersję, dziedzicząc po klasie Printer
. Można przekonfigurować następujące zmienne:
class MyPrinter extends Nette\PhpGenerator\Printer { // długość linii, po której następuje zawijanie wiersza public int $wrapLength = 120; // znak wcięcia, może być zastąpiony sekwencją 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 'use statements' dla klas, funkcji i stałych public int $linesBetweenUseTypes = 0; // pozycja otwierającego nawiasu klamrowego dla funkcji i metod public bool $bracesOnNextLine = true; // umieść jeden parametr w jednej linii, nawet jeśli ma atrybut lub jest promowany public bool $singleParameterOnOneLine = false; // pomija przestrzenie nazw, które nie zawierają żadnej klasy ani funkcji public bool $omitEmptyNamespaces = true; // separator między prawym nawiasem a typem zwracanym funkcji i metod public string $returnTypeColon = ': '; }
Jak i dlaczego właściwie różnią się standardowy Printer
i PsrPrinter
? Dlaczego w pakiecie nie ma tylko jednego printera, a mianowicie PsrPrinter
?
Standardowy Printer
formatuje kod tak, jak to robimy w całym Nette. Ponieważ Nette powstało znacznie wcześniej niż PSR, a także dlatego, że PSR przez długie lata nie dostarczało standardów na czas, ale na przykład z kilkuletnim opóźnieniem od wprowadzenia nowej funkcji w PHP, doszło do tego, że standard kodowania różni się w kilku drobnych szczegółach. Większą różnicą jest tylko używanie tabulatorów zamiast spacji. Wiemy, że używanie tabulatorów w naszych projektach umożliwia dostosowanie szerokości, co jest niezbędne dla osób z wadami wzroku. Przykładem drobnej różnicy jest umieszczenie nawiasu klamrowego na osobnej linii w przypadku funkcji i metod, i to zawsze. Zalecenie PSR wydaje nam się nielogiczne i prowadzi do zmniejszenia czytelności kodu.
Typy
Każdy typ lub typ union/intersection można przekazać jako ciąg znaków, można również użyć predefiniowanych stałych dla typów natywnych:
use Nette\PhpGenerator\Type; $member->setType('array'); // lub Type::Array; $member->setType('?array'); // lub Type::nullable(Type::Array); $member->setType('array|string'); // lub 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
Za pomocą Literal
można przekazywać dowolny kod PHP, na przykład dla 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żna również przekazać parametry do Literal
i pozwolić je sformatować do poprawnego kodu PHP za pomocą symboli zastępczych:
new Literal('substr(?, ?)', [$a, $b]); // generuje na przykład: substr('hello', 5);
Literał reprezentujący utworzenie nowego obiektu można łatwo wygenerować za pomocą metody new
:
Literal::new(Demo::class, [$a, 'foo' => $b]); // generuje na przykład: new Demo(10, foo: 20)
Atrybuty
Atrybuty PHP 8 można dodać do wszystkich klas, metod, właściwości, stałych, enumów, funkcji, closures i parametrów. Jako wartości parametrów można używać również literaly.
$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, ) { } }
Property Hooks
Za pomocą property hooks (reprezentowanych przez klasę PropertyHook) można zdefiniować operacje get i set dla właściwości, co jest funkcją wprowadzoną w 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;
Wygeneruje:
class Demo { public string $firstName { set(string $value) => strtolower($value); get { return ucfirst($this->firstName); } } }
Właściwości i property hooks mogą być abstrakcyjne lub finalne:
$class->addProperty('id') ->setType('int') ->addHook('get') ->setAbstract(); $class->addProperty('role') ->setType('string') ->addHook('set', 'strtolower($value)') ->setFinal();
Widoczność asymetryczna
PHP 8.4 wprowadza widoczność asymetryczną dla właściwości. Można ustawić różne poziomy dostępu dla odczytu i zapisu.
Widoczność można ustawić albo za pomocą metody setVisibility()
z dwoma parametrami, albo za pomocą setPublic()
, setProtected()
lub setPrivate()
z parametrem mode
, który określa, czy widoczność dotyczy odczytu czy zapisu właściwości. Domyślny tryb to 'get'
.
$class = new Nette\PhpGenerator\ClassType('Demo'); $class->addProperty('name') ->setType('string') ->setVisibility('public', 'private'); // public dla odczytu, private dla zapisu $class->addProperty('id') ->setType('int') ->setProtected('set'); // protected dla zapisu echo $class;
Wygeneruje:
class Demo { public private(set) string $name; protected(set) int $id; }
Przestrzeń nazw
Klasy, właściwości, interfejsy i wyliczenia (dalej zwane klasami) można grupować w przestrzenie nazw reprezentowane przez klasę PhpNamespace:
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); // tworzymy nowe klasy w przestrzeni nazw $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); // lub wstawiamy istniejącą klasę do przestrzeni nazw $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class);
Jeśli klasa już istnieje, zostanie rzucony wyjątek.
Można zdefiniować klauzule 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');
Aby uprościć w pełni kwalifikowaną nazwę klasy, funkcji lub stałej zgodnie z zdefiniowanymi aliasami, użyj metody simplifyName
:
echo $namespace->simplifyName('Foo\Bar'); // 'Bar', ponieważ 'Foo' to bieżąca przestrzeń nazw echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', z powodu zdefiniowanego use-statement
Uproszczoną nazwę klasy, funkcji lub stałej można na odwrót przekształcić na w pełni kwalifikowaną nazwę za pomocą metody resolveName
:
echo $namespace->resolveName('Bar'); // 'Foo\Bar' echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'
Tłumaczenie nazw klas
Gdy klasa jest częścią przestrzeni nazw, jest renderowana nieco inaczej: wszystkie typy (na przykład typehinty, typy zwracane, nazwa klasy nadrzędnej, implementowane interfejsy, używane traity i atrybuty) są automatycznie tłumaczone (jeśli tego nie wyłączysz, patrz poniżej). Oznacza to, że w definicjach musisz używać pełnych nazw klas, a te zostaną zastąpione aliasami (zgodnie z klauzulami use) 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') // zostanie uproszczone do A ->addTrait('Bar\AliasedClass'); // zostanie uproszczone do AliasedClass $method = $class->addMethod('method'); $method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // w komentarzach upraszczamy ręcznie $method->addParameter('arg') ->setType('Bar\OtherClass'); // zostanie przetłumaczone na \Bar\OtherClass echo $namespace; // lub użyj 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) { } }
Automatyczne tłumaczenie 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 można grupować w pliki PHP reprezentowane przez klasę PhpFile:
$file = new Nette\PhpGenerator\PhpFile; $file->addComment('Ten plik jest generowany automatycznie.'); $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 /** * Ten plik jest generowany automatycznie. */ declare(strict_types=1); namespace Foo; class A { } function foo() { }
Uwaga: Do plików nie można dodawać żadnego innego kodu poza funkcjami i klasami.
Generowanie na podstawie istniejących
Oprócz tego, że klasy i funkcje można modelować za pomocą opisanego powyżej API, można je również wygenerować automatycznie na podstawie istniejących wzorców:
// tworzy klasę taką samą jak klasa PDO $class = Nette\PhpGenerator\ClassType::from(PDO::class); // tworzy funkcję identyczną z funkcją trim() $function = Nette\PhpGenerator\GlobalFunction::from('trim'); // tworzy closure na podstawie podanej $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 instalacji pakietu nikic/php-parser
):
$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);
Wczytywanie z plików PHP
Funkcje, klasy, interfejsy i enumy można wczytywać również bezpośrednio z ciągu znaków zawierającego kod PHP. Na przykład w ten sposób utworzymy obiekt ClassType
:
$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX <?php class Demo { public $foo; } XX);
Podczas wczytywania klas z kodu PHP, jednoliniowe komentarze poza ciałami metod są ignorowane (np. przy właściwościach itp.), ponieważ ta biblioteka nie ma API do pracy z nimi.
Można również wczytać bezpośrednio cały plik PHP, który może zawierać dowolną liczbę klas, funkcji lub nawet przestrzeni nazw:
$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));
Wczytany zostanie również komentarz początkowy pliku i deklaracja strict_types
. Natomiast cały pozostały kod globalny jest ignorowany.
Wymagane jest zainstalowanie nikic/php-parser
.
Jeśli potrzebujesz manipulować globalnym kodem w plikach lub poszczególnymi instrukcjami w ciałach metod, lepiej użyć bezpośrednio biblioteki nikic/php-parser
.
Class Manipulator
Klasa ClassManipulator dostarcza narzędzi do manipulacji klasami.
$class = new Nette\PhpGenerator\ClassType('Demo'); $manipulator = new Nette\PhpGenerator\ClassManipulator($class);
Metoda inheritMethod()
kopiuje metodę z klasy nadrzędnej lub implementowanego interfejsu do Twojej klasy. Pozwala to nadpisać metodę lub rozszerzyć jej sygnaturę:
$method = $manipulator->inheritMethod('bar'); $method->setBody('...');
Metoda inheritProperty()
kopiuje właściwość z klasy nadrzędnej do Twojej klasy. Jest to przydatne, gdy chcesz mieć w swojej klasie tę samą właściwość, ale na przykład z inną wartością domyślną:
$property = $manipulator->inheritProperty('foo'); $property->setValue('new value');
Metoda implement()
automatycznie implementuje wszystkie metody i właściwości z danego interfejsu lub klasy abstrakcyjnej w Twojej klasie:
$manipulator->implement(SomeInterface::class); // Teraz Twoja klasa implementuje SomeInterface i zawiera wszystkie jego metody
Zrzut zmiennych
Klasa Dumper
konwertuje zmienną na parsowalny kod PHP. Dostarcza lepsze i bardziej przejrzyste wyjście niż standardowa funkcja var_export()
.
$dumper = new Nette\PhpGenerator\Dumper; $var = ['a', 'b', 123]; echo $dumper->dump($var); // wypisze ['a', 'b', 123]
Tabela kompatybilności
PhpGenerator 4.1 jest kompatybilny z PHP 8.0 do 8.4.