Nette PhpGenerator
- Umí všechny nejnovější vychytávky v PHP (jako property hooks, enumy, atributy atd.)
- Umožní vám snadno modifikovat existující třídy
- Výstupní kód je v souladu s PSR-12 / PER coding style
- Zralá, stabilní a široce používaná knihovna
Instalace
Knihovnu stáhnete a nainstalujete pomocí nástroje Composer:
composer require nette/php-generator
Kompatibilitu s PHP naleznete v tabulce.
Třídy
Začněme rovnou příkladem tvorby třídy pomocí ClassType:
$class = new Nette\PhpGenerator\ClassType('Demo'); $class ->setFinal() ->setExtends(ParentClass::class) ->addImplement(Countable::class) ->addComment("Popis třídy.\nDruhý řádek\n") ->addComment('@property-read Nette\Forms\Form $form'); // kód jednoduše vygenerujete přetypováním na řetězec nebo použitím echo: echo $class;
Vrátí následující výsledek:
/** * Popis třídy * Druhý řádek * * @property-read Nette\Forms\Form $form */ final class Demo extends ParentClass implements Countable { }
K vygenerování kódu můžeme také použít tzv. printer, který na rozdíl od echo $class
budeme moci dále konfigurovat:
$printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class);
Můžeme přidat konstanty (třída Constant) a proměnné (třída Property):
$class->addConstant('ID', 123) ->setProtected() // viditelnost konstant ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) ->setPrivate() // nebo setVisibility('private') ->setStatic() ->addComment('@var int[]'); $class->addProperty('list') ->setType('?array') ->setInitialized(); // vypíše '= null'
Vygeneruje:
final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; public ?array $list = null;
A můžeme přidat metody:
$method = $class->addMethod('count') ->addComment('Count it.') ->setFinal() ->setProtected() ->setReturnType('?int') // návratové typy u metod ->setBody('return count($items ?: $this->items);'); $method->addParameter('items', []) // $items = [] ->setReference() // &$items = [] ->setType('array'); // array &$items = []
Výsledkem je:
/** * Count it. */ final protected function count(array &$items = []): ?int { return count($items ?: $this->items); }
Propagované parametry zavedené PHP 8.0 lze předat konstruktoru:
$method = $class->addMethod('__construct'); $method->addPromotedParameter('name'); $method->addPromotedParameter('args', []) ->setPrivate();
Výsledkem je:
public function __construct( public $name, private $args = [], ) { }
Vlastnosti a třídy určené pouze pro čtení lze označit pomocí funkce setReadOnly()
.
Pokud přidaná vlastnost, konstanta, metoda nebo parametr již existují, vyhodí se výjimka.
Členy třídy lze odebrat pomocí removeProperty()
, removeConstant()
, removeMethod()
nebo removeParameter()
.
Do třídy můžete také přidat existující objekty Method
, Property
nebo 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);
Můžete také klonovat stávající metody, vlastnosti a konstanty pod jiným názvem pomocí cloneWithName()
:
$methodCount = $class->getMethod('count'); $methodRecount = $methodCount->cloneWithName('recount'); $class->addMember($methodRecount);
Interface nebo traita
Můžete vytvářet rozhraní a traity (třídy InterfaceType a TraitType):
$interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait');
Používání trait:
$class = new Nette\PhpGenerator\ClassType('Demo'); $class->addTrait('SmartObject'); $class->addTrait('MyTrait') ->addResolution('sayHello as protected') ->addComment('@use MyTrait<Foo>'); echo $class;
Výsledek:
class Demo { use SmartObject; /** @use MyTrait<Foo> */ use MyTrait { sayHello as protected; } }
Enums
Výčty, které přináší PHP 8.1, můžete snadno vytvořit takto: (třída EnumType):
$enum = new Nette\PhpGenerator\EnumType('Suit'); $enum->addCase('Clubs'); $enum->addCase('Diamonds'); $enum->addCase('Hearts'); $enum->addCase('Spades'); echo $enum;
Výsledek:
enum Suit { case Clubs; case Diamonds; case Hearts; case Spades; }
Můžete také definovat skalární ekvivalenty a vytvořit tak „backed“ výčet:
$enum->addCase('Clubs', '♣'); $enum->addCase('Diamonds', '♦');
Ke každému case je možné přidat komentář nebo atributy pomocí addComment()
nebo addAttribute()
.
Anonymní třídy
Jako název předáme null
a máme anonymní třídu:
$class = new Nette\PhpGenerator\ClassType(null); $class->addMethod('__construct') ->addParameter('foo'); echo '$obj = new class ($val) ' . $class . ';';
Výsledek:
$obj = new class ($val) { public function __construct($foo) { } };
Globální funkce
Kód funkcí generuje třída GlobalFunction:
$function = new Nette\PhpGenerator\GlobalFunction('foo'); $function->setBody('return $a + $b;'); $function->addParameter('a'); $function->addParameter('b'); echo $function; // nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);
Výsledek:
function foo($a, $b) { return $a + $b; }
Anonymní funkce
Kód anonymních funkcí generuje třída Closure:
$closure = new Nette\PhpGenerator\Closure; $closure->setBody('return $a + $b;'); $closure->addParameter('a'); $closure->addParameter('b'); $closure->addUse('c') ->setReference(); echo $closure; // nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);
Výsledek:
function ($a, $b) use (&$c) { return $a + $b; }
Zkrácené arrow funkce
Můžete také vypsat zkrácenou anonymní funkci pomocí printeru:
$closure = new Nette\PhpGenerator\Closure; $closure->setBody('$a + $b'); $closure->addParameter('a'); $closure->addParameter('b'); echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure);
Výsledek:
fn($a, $b) => $a + $b
Signatury metod a funkcí
Metody reprezentuje třída Method. Můžete nastavit viditelnost, návratovou hodnotu, přidat komentáře, atributy atd:
$method = $class->addMethod('count') ->addComment('Count it.') ->setFinal() ->setProtected() ->setReturnType('?int');
Jednotlivé parametry reprezentuje třídy Parameter. Opět můžete nastavit všechny myslitelné vlastnosti:
$method->addParameter('items', []) // $items = [] ->setReference() // &$items = [] ->setType('array'); // array &$items = [] // function count(array &$items = [])
Pro definici tzv. variadics parametrů (nebo též splat operátor) slouží setVariadic()
:
$method = $class->addMethod('count'); $method->setVariadic(true); $method->addParameter('items');
Vygeneruje:
function count(...$items) { }
Těla metod a funkcí
Tělo lze předat najednou metodě setBody()
nebo postupně (po řádcích) opakovaným voláním addBody()
:
$function = new Nette\PhpGenerator\GlobalFunction('foo'); $function->addBody('$a = rand(10, 20);'); $function->addBody('return $a;'); echo $function;
Výsledek
function foo() { $a = rand(10, 20); return $a; }
Můžete použít speciální zástupné znaky pro snadné vkládání proměnných.
Jednoduché zástupné symboly ?
$str = 'any string'; $num = 3; $function = new Nette\PhpGenerator\GlobalFunction('foo'); $function->addBody('return substr(?, ?);', [$str, $num]); echo $function;
Výsledek
function foo() { return substr('any string', 3); }
Zástupný znak pro variadic ...?
$items = [1, 2, 3]; $function = new Nette\PhpGenerator\GlobalFunction('foo'); $function->setBody('myfunc(...?);', [$items]); echo $function;
Výsledek:
function foo() { myfunc(1, 2, 3); }
Můžete také použít pojmenované parametry pro PHP 8 pomocí ...?:
$items = ['foo' => 1, 'bar' => true]; $function->setBody('myfunc(...?:);', [$items]); // myfunc(foo: 1, bar: true);
Zástupný symbol se escapuje pomocí lomítka \?
$num = 3; $function = new Nette\PhpGenerator\GlobalFunction('foo'); $function->addParameter('a'); $function->addBody('return $a \? 10 : ?;', [$num]); echo $function;
Výsledek:
function foo($a) { return $a ? 10 : 3; }
Printer a soulad s PSR
Ke generování PHP kódu slouží třída Printer:
$class = new Nette\PhpGenerator\ClassType('Demo'); // ... $printer = new Nette\PhpGenerator\Printer; echo $printer->printClass($class); // totéž, jako: echo $class
Umí vygenerovat kód všech dalších prvků, nabízí metody jako printFunction()
, printNamespace()
, atd.
K dispozici je také třída PsrPrinter
, jejíž výstup je v souladu s PSR-2 / PSR-12 / PER coding style:
$printer = new Nette\PhpGenerator\PsrPrinter; echo $printer->printClass($class);
Potřebujete chování doladit na míru? Vytvořte si vlastní verzi poděděním třídy Printer
. Lze překonfigurovat tyto proměnné:
class MyPrinter extends Nette\PhpGenerator\Printer { // délka řádku, po které dojde k zalamování řádku public int $wrapLength = 120; // znak odsazení, může být nahrazen sekvencí mezer public string $indentation = "\t"; // počet prázdných řádků mezi properties public int $linesBetweenProperties = 0; // počet prázdných řádků mezi metodami public int $linesBetweenMethods = 2; // počet prázdných řádků mezi skupinami 'use statements' pro třídy, funkce a konstanty public int $linesBetweenUseTypes = 0; // pozice otevírací složené závorky pro funkce a metody public bool $bracesOnNextLine = true; // umístěte jeden parametr na jeden řádek, i když má atribut nebo je podporován public bool $singleParameterOnOneLine = false; // omits namespaces that do not contain any class or function public bool $omitEmptyNamespaces = true; // oddělovač mezi pravou závorkou a návratovým typem funkcí a metod public string $returnTypeColon = ': '; }
Jak a proč se vlastně liší standardní Printer
a PsrPrinter
? Proč není v balíčku jen jeden printer, a to PsrPrinter
?
Standardní Printer
formátuje kód tak, jak to děláme v celém Nette. Tím, že Nette vzniklo mnohem dřív, než PSR, a také proto, že PSR dlouhé roky nedodávalo standardy včas, ale třeba až s několikaletým zpožděním od uvedení nové featury v PHP, došlo k tomu, že kódovací standard se v několika drobnostech liší. Větším rozdílem je jen používání tabulátorů místo mezer. Víme, že používáním tabulátorů v našich projektech umožňujeme přizpůsobení šířky, které je pro lidi se zrakovým postižením nezbytné. Příkladem drobné odlišnosti je umístění složené závorky na samostatném řádku u funkcí a metod a to vždy. Doporučení PSR se nám jeví jako nelogické a vede k snížení přehlednosti kódu.
Typy
Každý typ nebo union/intersection typ lze předat jako řetězec, můžete také použít předdefinované konstanty pro nativní typy:
use Nette\PhpGenerator\Type; $member->setType('array'); // nebo 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'); // nebo Type::intersection(Foo::class, Bar::class) $member->setType(null); // odstraní typ
Totéž platí pro metodu setReturnType()
.
Literály
Pomocí Literal
můžete předávat libovolný kód PHP, například pro výchozí hodnoty vlastností nebo parametrů atd:
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;
Výsledek:
class Demo { public $foo = Iterator::SELF_FIRST; public function bar($id = 1 + 2) { } }
Můžete také předat parametry do Literal
a nechat je zformátovat do platného kódu PHP pomocí zástupných znaků:
new Literal('substr(?, ?)', [$a, $b]); // generuje například: substr('hello', 5);
Literál představující vytvoření nového objektu lze snadno vygenerovat pomocí metody new
:
Literal::new(Demo::class, [$a, 'foo' => $b]); // generuje například: new Demo(10, foo: 20)
Atributy
PHP 8 atributy můžete přidat do všech tříd, metod, vlastností, konstant, enumů, funkcí, closures a parametrů. Jako hodnoty parametrů lze používat i literály.
$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;
Výsledek:
#[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
Pomocí property hooks (reprezentované třídou PropertyHook) můžete definovat operace get a set pro vlastnosti, což je funkce zavedená v 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;
Vygeneruje:
class Demo { public string $firstName { set(string $value) => strtolower($value); get { return ucfirst($this->firstName); } } }
Property a property hooks mohou být abstraktní nebo finální:
$class->addProperty('id') ->setType('int') ->addHook('get') ->setAbstract(); $class->addProperty('role') ->setType('string') ->addHook('set', 'strtolower($value)') ->setFinal();
Asymetrická viditelnost
PHP 8.4 zavádí asymetrickou viditelnost pro vlastnosti. Můžete nastavit různé úrovně přístupu pro čtení a zápis.
Viditelnost lze nastavit buď pomocí metody setVisibility()
se dvěma parametry, nebo pomocí setPublic()
, setProtected()
nebo setPrivate()
s parametrem mode
, který určuje, zda se viditelnost vztahuje ke čtení nebo zápisu vlastnosti. Výchozí režim je 'get'
.
$class = new Nette\PhpGenerator\ClassType('Demo'); $class->addProperty('name') ->setType('string') ->setVisibility('public', 'private'); // public pro čtení, private pro zápis $class->addProperty('id') ->setType('int') ->setProtected('set'); // protected pro zápis echo $class;
Vygeneruje:
class Demo { public private(set) string $name; protected(set) int $id; }
Jmenný prostor
Třídy, vlastnosti, rozhraní a výčty (dále jen třídy) lze seskupit do jmenných prostorů reprezentovaných třídou PhpNamespace:
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); // vytvoříme nové třídy v namespace $class = $namespace->addClass('Task'); $interface = $namespace->addInterface('Countable'); $trait = $namespace->addTrait('NameAware'); // nebo vložíme existující třídu do namespace $class = new Nette\PhpGenerator\ClassType('Task'); $namespace->add($class);
Pokud třída již existuje, vyhodí se výjimka.
Můžete definovat 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');
Chcete-li zjednodušit plně kvalifikovaný název třídy, funkce nebo konstanty podle definovaných aliasů, použijte metodu simplifyName
:
echo $namespace->simplifyName('Foo\Bar'); // 'Bar', protože 'Foo' je aktuální jmenný prostor echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', kvůli definovanému use-statement
Zjednodušený název třídy, funkce nebo konstanty můžete naopak převést na plně kvalifikovaný název pomocí metody resolveName
:
echo $namespace->resolveName('Bar'); // 'Foo\Bar' echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'
Překlady názvů tříd
Když je třída součástí jmenného prostoru, je vykreslena mírně odlišně: všechny typy (například typehinty, návratové typy, název rodičovské třídy, implementovaná rozhraní, použité vlastnosti a atributy) jsou automaticky překládány (pokud to nevypnete, viz níže). To znamená, že musíte v definicích používat úplné názvy tříd a ty budou nahrazeny za aliasy (podle klauzulí use) nebo za plně kvalifikovaná jména ve výsledném kódu:
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); $namespace->addUse('Bar\AliasedClass'); $class = $namespace->addClass('Demo'); $class->addImplement('Foo\A') // bude zjednodušen na A ->addTrait('Bar\AliasedClass'); // bude zjednodušen na AliasedClass $method = $class->addMethod('method'); $method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // v komentářích zjednodušíme manuálně $method->addParameter('arg') ->setType('Bar\OtherClass'); // bude přeložen na \Bar\OtherClass echo $namespace; // nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);
Výsledek:
namespace Foo; use Bar\AliasedClass; class Demo implements A { use AliasedClass; /** * @return D */ public function method(\Bar\OtherClass $arg) { } }
Automatické překládání lze vypnout tímto způsobem:
$printer = new Nette\PhpGenerator\Printer; // nebo PsrPrinter $printer->setTypeResolving(false); echo $printer->printNamespace($namespace);
PHP soubory
Třídy, funkce a jmenné prostory lze seskupit do PHP souborů reprezentovaných třídou PhpFile:
$file = new Nette\PhpGenerator\PhpFile; $file->addComment('This file is auto-generated.'); $file->setStrictTypes(); // přidá declare(strict_types=1) $class = $file->addClass('Foo\A'); $function = $file->addFunction('Foo\foo'); // nebo // $namespace = $file->addNamespace('Foo'); // $class = $namespace->addClass('A'); // $function = $namespace->addFunction('foo'); echo $file; // nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);
Výsledek:
<?php /** * This file is auto-generated. */ declare(strict_types=1); namespace Foo; class A { } function foo() { }
Upozornění: Do souborů není možné přidávat žádný další kód mimo funkce a třídy.
Generování podle existujících
Kromě toho, že třídy a funkce můžete modelovat pomocí výše popsaného API, je můžete také nechat vygenerovat automaticky podle existujících vzorů:
// vytvoří třídu stejnou jako třída PDO $class = Nette\PhpGenerator\ClassType::from(PDO::class); // vytvoří funkci totožnou s funkcí trim() $function = Nette\PhpGenerator\GlobalFunction::from('trim'); // vytvoří closure podle uvedené $closure = Nette\PhpGenerator\Closure::from( function (stdClass $a, $b = null) {}, );
Těla funkcí a metod jsou ve výchozím stavu prázdná. Pokud je chcete také načíst, použijte tento způsob (vyžaduje instalaci balíčku nikic/php-parser
):
$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);
Načítání z PHP souborů
Funkce, třídy, rozhraní a enumy můžete načítat také přímo z řetězce obsahujícího PHP kód. Například takto vytvoříme objekt ClassType
:
$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX <?php class Demo { public $foo; } XX);
Při načítání tříd z kódu PHP jsou jednořádkové komentáře mimo těla metod ignorovány (např. u properties atd.), protože tato knihovna nemá API pro práci s nimi.
Můžete také načíst přímo celý soubor PHP, který může obsahovat libovolný počet tříd, funkcí nebo dokonce jmenných prostorů:
$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));
Načte se také úvodní komentář k souboru a deklarace strict_types
. Naopak veškerý ostatní globální kód je ignorován.
Vyžaduje se, aby byl nainstalován nikic/php-parser
.
Pokud potřebujete manipulovat s globálním kódem v souborech nebo s jednotlivými příkazy v tělech metod, je lepší použít přímo knihovnu nikic/php-parser
.
Class Manipulator
Třída ClassManipulator poskytuje nástroje pro manipulaci s třídami.
$class = new Nette\PhpGenerator\ClassType('Demo'); $manipulator = new Nette\PhpGenerator\ClassManipulator($class);
Metoda inheritMethod()
zkopíruje metodu z rodičovské třídy nebo implementovaného rozhraní do vaší třídy. To vám umožní přepsat metodu nebo rozšířit její signaturu:
$method = $manipulator->inheritMethod('bar'); $method->setBody('...');
Metoda inheritProperty()
zkopíruje vlastnost z rodičovské třídy do vaší třídy. Je to užitečné, když chcete ve své třídě mít stejnou vlastnost, ale třeba s jinou výchozí hodnotou:
$property = $manipulator->inheritProperty('foo'); $property->setValue('new value');
Metoda implement()
automaticky implementuje všechny metody a vlastnosti z daného rozhraní nebo abstraktní třídy ve vaší třídě:
$manipulator->implement(SomeInterface::class); // Nyní vaše třída implementuje SomeInterface a obsahuje všechny jeho metody
Výpis proměnných
Třída Dumper
převede proměnnou do parsovatelného PHP kódu. Poskytuje lepší a přehlednější výstup než standardní funkce var_export()
.
$dumper = new Nette\PhpGenerator\Dumper; $var = ['a', 'b', 123]; echo $dumper->dump($var); // vypíše ['a', 'b', 123]
Tabulka kompatibility
PhpGenerator 4.1 je kompatibilní s PHP 8.0 až 8.4.