Nette PhpGenerator
- Ismeri az összes legújabb PHP funkciót (mint a property hookok, enumok, attribútumok stb.)
- Lehetővé teszi a meglévő osztályok egyszerű módosítását
- A kimeneti kód megfelel a PSR-12 / PER kódolási stílusnak
- Érett, stabil és széles körben használt könyvtár
Telepítés
A könyvtárat a Composer segítségével töltheti le és telepítheti:
composer require nette/php-generator
A PHP kompatibilitást a kompatibilitási táblázatban találja.
Osztályok
Kezdjük rögtön egy példával egy osztály létrehozására a ClassType segítségével:
$class = new Nette\PhpGenerator\ClassType('Demo'); $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) ->addComment("Osztály leírása.\nMásodik sor\n") ->addComment('@property-read Nette\Forms\Form $form'); // a kódot egyszerűen generálhatja stringgé alakítással vagy az echo használatával: echo $class;
A következő eredményt adja vissza:
/** * Osztály leírása * Második sor * * @property-read Nette\Forms\Form $form */ final class Demo extends ParentClass implements Countable { }
A kód generálásához használhatunk egy ún. printert is, amelyet az echo $class
-szal ellentétben tovább konfigurálni tudunk:
$printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class);
Hozzáadhatunk konstansokat (Constant osztály) és propertyket (Property osztály):
$class->addConstant('ID', 123) ->setProtected() // konstansok láthatósága ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) ->setPrivate() // vagy setVisibility('private') ->setStatic() ->addComment('@var int[]'); $class->addProperty('list') ->setType('?array') ->setInitialized(); // kiírja '= null'
Generálja:
final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; public ?array $list = null;
És hozzáadhatunk metódusokat:
$method = $class->addMethod('count') ->addComment('Számold meg.') ->setFinal() ->setProtected() ->setReturnType('?int') // visszatérési típusok metódusoknál ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] ->setReference() // &$items = [] ->setType('array'); // array &$items = []
Az eredmény:
/** * Számold meg. */ final protected function count(array &$items = []): ?int { return count($items ?: $this->items); }
A PHP 8.0 által bevezetett promoted paramétereket átadhatjuk a konstruktornak:
$method = $class->addMethod('__construct'); $method->addPromotedParameter('name'); $method->addPromotedParameter('args', []) ->setPrivate();
Az eredmény:
public function __construct( public $name, private $args = [], ) { }
A csak olvasható propertyket és osztályokat a setReadOnly()
függvénnyel lehet megjelölni.
Ha a hozzáadott property, konstans, metódus vagy paraméter már létezik, kivétel dobódik.
Az osztály tagjait eltávolíthatjuk a removeProperty()
, removeConstant()
, removeMethod()
vagy removeParameter()
segítségével.
Az osztályhoz hozzáadhatunk meglévő Method
, Property
vagy Constant
objektumokat is:
$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);
Klónozhatunk meglévő metódusokat, propertyket és konstansokat más néven a cloneWithName()
segítségével:
$methodCount = $class->getMethod('count'); $methodRecount = $methodCount->cloneWithName('recount'); $class->addMember($methodRecount);
Interfész vagy Trait
Létrehozhat interfészeket és traitteket (InterfaceType és TraitType osztályok):
$interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait');
Trait használata:
$class = new Nette\PhpGenerator\ClassType('Demo'); $class->addTrait('SmartObject'); $class->addTrait('MyTrait') ->addResolution('sayHello as protected') ->addComment('@use MyTrait<Foo>'); echo $class;
Eredmény:
class Demo { use SmartObject; /** @use MyTrait<Foo> */ use MyTrait { sayHello as protected; } }
Enumok
A PHP 8.1 által bevezetett enumokat könnyen létrehozhatja így: (EnumType osztály):
$enum = new Nette\PhpGenerator\EnumType('Suit'); $enum->addCase('Clubs'); $enum->addCase('Diamonds'); $enum->addCase('Hearts'); $enum->addCase('Spades'); echo $enum;
Eredmény:
enum Suit { case Clubs; case Diamonds; case Hearts; case Spades; }
Definiálhat skaláris ekvivalenseket is, és létrehozhat egy “backed” enumot:
$enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦');
Minden case-hez hozzáadhat kommentet vagy Attribútumok a addComment()
vagy addAttribute()
segítségével.
Névtelen osztályok
Névként null
-t adunk át, és máris van egy névtelen osztályunk:
$class = new Nette\PhpGenerator\ClassType(null); $class->addMethod('__construct') ->addParameter('foo'); echo '$obj = new class ($val) ' . $class . ';';
Eredmény:
$obj = new class ($val) { public function __construct($foo) { } };
Globális függvények
A függvények kódját a GlobalFunction osztály generálja:
$function = new Nette\PhpGenerator\GlobalFunction('foo'); $function->setBody('return $a + $b;'); $function->addParameter('a'); $function->addParameter('b'); echo $function; // vagy használja a PsrPrintert a PSR-2 / PSR-12 / PER szerinti kimenethez // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);
Eredmény:
function foo($a, $b) { return $a + $b; }
Névtelen függvények
A névtelen függvények kódját a Closure osztály generálja:
$closure = new Nette\PhpGenerator\Closure; $closure->setBody('return $a + $b;'); $closure->addParameter('a'); $closure->addParameter('b'); $closure->addUse('c') ->setReference(); echo $closure; // vagy használja a PsrPrintert a PSR-2 / PSR-12 / PER szerinti kimenethez // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);
Eredmény:
function ($a, $b) use (&$c) { return $a + $b; }
Rövidített nyíl függvények
Kiírhat egy rövidített névtelen függvényt is a printer segítségével:
$closure = new Nette\PhpGenerator\Closure; $closure->setBody('$a + $b'); $closure->addParameter('a'); $closure->addParameter('b'); echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure);
Eredmény:
fn($a, $b) => $a + $b
Metódus és függvény szignatúrák
A metódusokat a Method osztály reprezentálja. Beállíthatja a láthatóságot, a visszatérési értéket, hozzáadhat kommenteket, attribútumokat stb.:
$method = $class->addMethod('count') ->addComment('Számold meg.') ->setFinal() ->setProtected() ->setReturnType('?int');
Az egyes paramétereket a Parameter osztály reprezentálja. Ismét beállíthat minden elképzelhető tulajdonságot:
$method->addParameter('items', []) // $items = [] ->setReference() // &$items = [] ->setType('array'); // array &$items = [] // function count(array &$items = [])
Az ún. variadics paraméterek (vagy splat operátor) definiálására a setVariadic()
szolgál:
$method = $class->addMethod('count'); $method->setVariadic(true); $method->addParameter('items');
Generálja:
function count(...$items) { }
Metódus és függvény törzsek
A törzset átadhatjuk egyszerre a setBody()
metódusnak, vagy fokozatosan (soronként) az addBody()
ismételt hívásával:
$function = new Nette\PhpGenerator\GlobalFunction('foo'); $function->addBody('$a = rand(10, 20);'); $function->addBody('return $a;'); echo $function;
Eredmény
function foo() { $a = rand(10, 20); return $a; }
Speciális helyettesítő karaktereket használhat a változók egyszerű beillesztéséhez.
Egyszerű helyettesítő szimbólumok ?
$str = 'any string'; $num = 3; $function = new Nette\PhpGenerator\GlobalFunction('foo'); $function->addBody('return substr(?, ?);', [$str, $num]); echo $function;
Eredmény
function foo() { return substr('any string', 3); }
Helyettesítő karakter variadic-hoz ...?
$items = [1, 2, 3]; $function = new Nette\PhpGenerator\GlobalFunction('foo'); $function->setBody('myfunc(...?);', [$items]); echo $function;
Eredmény:
function foo() { myfunc(1, 2, 3); }
Használhat névvel ellátott paramétereket is a PHP 8-hoz a ...?:
segítségével
$items = ['foo' => 1, 'bar' => true]; $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true);
A helyettesítő szimbólumot a \
karakterrel lehet escape-elni \?
$num = 3; $function = new Nette\PhpGenerator\GlobalFunction('foo'); $function->addParameter('a'); $function->addBody('return $a \? 10 : ?;', [$num]); echo $function;
Eredmény:
function foo($a) { return $a ? 10 : 3; }
Printer és PSR megfelelőség
A PHP kód generálására a Printer osztály szolgál:
$class = new Nette\PhpGenerator\ClassType('Demo'); // ... $printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class); // ugyanaz, mint: echo $class
Képes generálni az összes többi elem kódját, kínál metódusokat, mint a printFunction()
, printNamespace()
, stb.
Rendelkezésre áll a PsrPrinter
osztály is, amelynek kimenete megfelel a PSR-2 / PSR-12 / PER kódolási stílusnak:
$printer = new Nette\PhpGenerator\PsrPrinter; echo $printer->printClass($class);
Szeretné testre szabni a viselkedést? Hozzon létre saját verziót a Printer
osztály öröklésével. Ezeket a változókat lehet újrakonfigurálni:
class MyPrinter extends Nette\PhpGenerator\Printer { // sor hossza, amely után sortörés történik public int $wrapLength = 120; // behúzás karaktere, helyettesíthető szóközök sorozatával public string $indentation = "\t"; // üres sorok száma a propertyk között public int $linesBetweenProperties = 0; // üres sorok száma a metódusok között public int $linesBetweenMethods = 2; // üres sorok száma az 'use statements' csoportok között osztályokhoz, függvényekhez és konstansokhoz public int $linesBetweenUseTypes = 0; // nyitó kapcsos zárójel pozíciója függvényeknél és metódusoknál public bool $bracesOnNextLine = true; // helyezzen egy paramétert egy sorba, még akkor is, ha attribútuma van vagy promoted public bool $singleParameterOnOneLine = false; // kihagyja azokat a névtereket, amelyek nem tartalmaznak osztályt vagy függvényt public bool $omitEmptyNamespaces = true; // elválasztó a jobb zárójel és a függvények és metódusok visszatérési típusa között public string $returnTypeColon = ': '; }
Hogyan és miért különbözik valójában a standard Printer
és a PsrPrinter
? Miért nincs csak egy printer a csomagban, mégpedig a PsrPrinter
?
A standard Printer
úgy formázza a kódot, ahogyan azt az egész Nette-ben tesszük. Mivel a Nette sokkal korábban jött létre, mint a PSR, és mivel a PSR évekig nem szállított időben szabványokat, hanem például többéves késéssel az új PHP funkció bevezetése után, előfordult, hogy a kódolási szabvány néhány apróságban eltér. A nagyobb különbség csak a tabulátorok használata szóközök helyett. Tudjuk, hogy a tabulátorok használata projektjeinkben lehetővé teszi a szélesség testreszabását, ami látássérültek számára elengedhetetlen. Egy apró eltérés példája a kapcsos zárójel elhelyezése külön sorban a függvényeknél és metódusoknál, és ez mindig így van. A PSR ajánlása számunkra logikátlannak tűnik, és a kód olvashatóságának csökkenéséhez vezet.
Típusok
Minden típust vagy union/intersection típust átadhatunk stringként, használhatunk előre definiált konstansokat is a natív típusokhoz:
use Nette\PhpGenerator\Type; $member->setType('array'); // vagy Type::Array; $member->setType('?array'); // vagy Type::nullable(Type::Array); $member->setType('array|string'); // vagy Type::union(Type::Array, Type::String) $member->setType('Foo&Bar'); // vagy Type::intersection(Foo::class, Bar::class) $member->setType(null); // eltávolítja a típust
Ugyanez vonatkozik a setReturnType()
metódusra is.
Literálok
A Literal
segítségével tetszőleges PHP kódot adhatunk át, például propertyk vagy paraméterek alapértelmezett értékeihez stb.:
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;
Eredmény:
class Demo { public $foo = Iterator::SELF_FIRST; public function bar($id = 1 + 2) { } }
Paramétereket is átadhat a Literal
-nak, és hagyhatja, hogy érvényes PHP kóddá formázza őket helyettesítő karaktereket használva:
new Literal('substr(?, ?)', [$a, $b]); // generál például: substr('hello', 5);
Egy új objektum létrehozását reprezentáló literált könnyen generálhatunk a new
metódussal:
Literal::new(Demo::class, [$a, 'foo' => $b]); // generál például: new Demo(10, foo: 20)
Attribútumok
A PHP 8 attribútumokat hozzáadhatja az összes osztályhoz, metódushoz, propertyhez, konstanshoz, enumhoz, függvényhez, closure-höz és paraméterhez. Paraméterértékként Literálok is használhatók.
$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;
Eredmény:
#[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 Hookok
A property hookok segítségével (PropertyHook osztály által reprezentálva) definiálhat get és set műveleteket a propertykhez, ami a PHP 8.4-ben bevezetett funkció:
$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;
Generálja:
class Demo { public string $firstName { set(string $value) => strtolower($value); get { return ucfirst($this->firstName); } } }
A propertyk és property hookok lehetnek absztraktak vagy finálak:
$class->addProperty('id') ->setType('int') ->addHook('get') ->setAbstract(); $class->addProperty('role') ->setType('string') ->addHook('set', 'strtolower($value)') ->setFinal();
Aszimmetrikus láthatóság
A PHP 8.4 bevezeti az aszimmetrikus láthatóságot a propertykhez. Különböző hozzáférési szinteket állíthat be az olvasáshoz és íráshoz.
A láthatóságot beállíthatja vagy a setVisibility()
metódussal két paraméterrel, vagy a setPublic()
, setProtected()
vagy setPrivate()
metódusokkal a mode
paraméterrel, amely meghatározza, hogy a láthatóság az olvasásra vagy az írásra vonatkozik-e. Az alapértelmezett mód 'get'
.
$class = new Nette\PhpGenerator\ClassType('Demo'); $class->addProperty('name') ->setType('string') ->setVisibility('public', 'private'); // public olvasáshoz, private íráshoz $class->addProperty('id') ->setType('int') ->setProtected('set'); // protected íráshoz echo $class;
Generálja:
class Demo { public private(set) string $name; protected(set) int $id; }
Névtér
Az osztályokat, propertyket, interfészeket és enumokat (továbbiakban osztályok) csoportosíthatjuk névterekbe, amelyeket a PhpNamespace osztály reprezentál:
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); // új osztályok létrehozása a névtérben $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); // vagy meglévő osztály beillesztése a névtérbe $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class);
Ha az osztály már létezik, kivétel dobódik.
Definiálhat use klózokat:
// 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');
Ha egyszerűsíteni szeretné a teljesen minősített osztály-, függvény- vagy konstansnevet a definiált aliasok szerint, használja a simplifyName
metódust:
echo $namespace->simplifyName('Foo\Bar'); // 'Bar', mert a 'Foo' az aktuális névtér echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', a definiált use-statement miatt
Az egyszerűsített osztály-, függvény- vagy konstansnevet fordítva átalakíthatja teljesen minősített névre a resolveName
metódussal:
echo $namespace->resolveName('Bar'); // 'Foo\Bar' echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'
Osztálynevek fordítása
Ha egy osztály egy névtér része, kissé eltérően jelenik meg: minden típus (például typehintek, visszatérési típusok, szülőosztály neve, implementált interfészek, használt propertyk és attribútumok) automatikusan lefordításra kerül (hacsak nem kapcsolja ki, lásd alább). Ez azt jelenti, hogy a definíciókban teljes osztályneveket kell használnia, és ezeket aliasokra (a use klózok szerint) vagy teljesen minősített nevekre cseréli a végső kódban:
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); $namespace->addUse('Bar\AliasedClass'); $class = $namespace->addClass('Demo'); $class->addImplement('Foo\A') // A-ra lesz egyszerűsítve ->addTrait('Bar\AliasedClass'); // AliasedClass-ra lesz egyszerűsítve $method = $class->addMethod('method'); $method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // kommentekben manuálisan egyszerűsítünk $method->addParameter('arg') ->setType('Bar\OtherClass'); // \Bar\OtherClass-ra lesz fordítva echo $namespace; // vagy használja a PsrPrintert a PSR-2 / PSR-12 / PER szerinti kimenethez // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);
Eredmény:
namespace Foo; use Bar\AliasedClass; class Demo implements A { use AliasedClass; /** * @return D */ public function method(\Bar\OtherClass $arg) { } }
Az automatikus fordítást így lehet kikapcsolni:
$printer = new Nette\PhpGenerator\Printer; // vagy PsrPrinter $printer->setTypeResolving(false); echo $printer->printNamespace($namespace);
PHP fájlok
Az osztályokat, függvényeket és névtereket PHP fájlokba csoportosíthatjuk, amelyeket a PhpFile osztály reprezentál:
$file = new Nette\PhpGenerator\PhpFile; $file->addComment('Ez a fájl automatikusan generált.'); $file->setStrictTypes(); // hozzáadja declare(strict_types=1) $class = $file->addClass('Foo\A'); $function = $file->addFunction('Foo\foo'); // vagy // $namespace = $file->addNamespace('Foo'); // $class = $namespace->addClass('A'); // $function = $namespace->addFunction('foo'); echo $file; // vagy használja a PsrPrintert a PSR-2 / PSR-12 / PER szerinti kimenethez // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);
Eredmény:
<?php /** * Ez a fájl automatikusan generált. */ declare(strict_types=1); namespace Foo; class A { } function foo() { }
Figyelmeztetés: A fájlokhoz nem lehet további kódot hozzáadni a függvényeken és osztályokon kívül.
Generálás meglévők alapján
Amellett, hogy az osztályokat és függvényeket a fent leírt API segítségével modellezheti, automatikusan is generáltathatja őket meglévő minták alapján:
// létrehoz egy osztályt, amely megegyezik a PDO osztállyal $class = Nette\PhpGenerator\ClassType::from(PDO::class); // létrehoz egy függvényt, amely azonos a trim() függvénnyel $function = Nette\PhpGenerator\GlobalFunction::from('trim'); // létrehoz egy closure-t a megadott alapján $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {}, );
A függvények és metódusok törzsei alapértelmezés szerint üresek. Ha ezeket is be szeretné tölteni, használja ezt a módszert (szükséges a nikic/php-parser
csomag telepítése):
$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);
Betöltés PHP fájlokból
Függvényeket, osztályokat, interfészeket és enumokat közvetlenül PHP kódot tartalmazó stringből is betölthet. Például így hozunk létre egy ClassType
objektumot:
$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX <?php class Demo { public $foo; } XX);
Ha osztályokat PHP kódból tölt be, az egysoros kommentek a metódustörzseken kívül (pl. propertyknél stb.) figyelmen kívül maradnak, mivel ez a könyvtár nem rendelkezik API-val a kezelésükhöz.
Közvetlenül betölthet egy teljes PHP fájlt is, amely tetszőleges számú osztályt, függvényt vagy akár névteret tartalmazhat:
$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));
Betöltődik a fájl bevezető kommentje és a strict_types
deklaráció is. Ezzel szemben minden más globális kód figyelmen kívül marad.
Szükséges, hogy a nikic/php-parser
telepítve legyen.
Ha globális kódot kell manipulálnia fájlokban vagy egyes utasításokat metódustörzsekben, jobb közvetlenül a nikic/php-parser
könyvtárat használni.
Class Manipulator
A ClassManipulator osztály eszközöket biztosít az osztályok manipulálásához.
$class = new Nette\PhpGenerator\ClassType('Demo'); $manipulator = new Nette\PhpGenerator\ClassManipulator($class);
Az inheritMethod()
metódus átmásol egy metódust a szülőosztályból vagy implementált interfészből az Ön osztályába. Ez lehetővé teszi a metódus felülírását vagy a szignatúrájának kiterjesztését:
$method = $manipulator->inheritMethod('bar'); $method->setBody('...');
Az inheritProperty()
metódus átmásol egy propertyt a szülőosztályból az Ön osztályába. Ez akkor hasznos, ha ugyanazt a propertyt szeretné az osztályában, de esetleg más alapértelmezett értékkel:
$property = $manipulator->inheritProperty('foo'); $property->setValue('new value');
Az implement()
metódus automatikusan implementálja az összes metódust és propertyt a megadott interfészből vagy absztrakt osztályból az Ön osztályában:
$manipulator->implement(SomeInterface::class); // Most az Ön osztálya implementálja a SomeInterface-t és tartalmazza annak összes metódusát
Változók kiírása
A Dumper
osztály átalakít egy változót elemezhető PHP kóddá. Jobb és áttekinthetőbb kimenetet biztosít, mint a standard var_export()
függvény.
$dumper = new Nette\PhpGenerator\Dumper; $var = ['a', 'b', 123]; echo $dumper->dump($var); // kiírja ['a', 'b', 123]
Kompatibilitási táblázat
A PhpGenerator 4.1 kompatibilis a PHP 8.0-tól 8.4-ig.