Generátor PHP kódu
- 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(&$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:
```php
$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.