Generátor PHP kódu
- Potřebujete generovat kód tříd, funkcí, PHP souborů atd?
- Umí všechny nejnovější vychytávky v PHP (jako enumy atd.)
- Dovolí vám snadno modifikovat existující třídy
- Výstup vyhovující PSR-12
- 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
{
use Nette\SmartObject;
}
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
->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 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 (od verze 3.5):
$method = $class->addMethod('__construct');
$method->addPromotedParameter('name');
$method->addPromotedParameter('args', [])
->setPrivate();
Výsledkem je:
public function __construct(
public $name,
private $args = [],
) {
}
Vlastnosti určené pouze pro čtení zavedené v PHP 8.1 lze označit pomocí funkce setReadOnly()
.
Pokud přidaná vlastnost, konstanta, metoda nebo parametr již existují, budou přepsány.
Č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
Lze vytvářet rozhraní a traity:
$interface = Nette\PhpGenerator\ClassType::interface('MyInterface');
$trait = Nette\PhpGenerator\ClassType::trait('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 {
sayHello as protected;
}
}
Enums
Výčty, které přináší PHP 8.1, můžete snadno vytvořit takto:
$enum = Nette\PhpGenerator\ClassType::enum('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
// 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
// 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í `…?: `(od verze 3.5)
$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
PHP kód generují objekty Printer
. K je vám tiskárna PsrPrinter
, jejíž výstup je v souladu
s PSR-2 a PSR-12 a k odsazování používá mezery, a dále Printer
, která pro odsazování používá
tabulátory.
$class = new Nette\PhpGenerator\ClassType('Demo');
// ...
$printer = new Nette\PhpGenerator\PsrPrinter;
echo $printer->printClass($class); // odsazení 4 mezerami
Potřebujete na míru upravit chování printeru? Vytvořte si vlastní poděděním třídy Printer
. Můžete
překonfigurovat tyto proměnné:
class MyPrinter extends Nette\PhpGenerator\Printer
{
protected $indentation = "\t";
protected $linesBetweenProperties = 0;
protected $linesBetweenMethods = 1;
protected $returnTypeColon = ': ';
}
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|string'); // nebo Type::union('array', '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);
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('Deprecated');
$class->addProperty('list')
->addAttribute('WithArguments', [1, 2]);
$method = $class->addMethod('count')
->addAttribute('Foo\Cached', ['mode' => true]);
$method->addParameter('items')
->addAttribute('Bar');
echo $class;
Výsledek:
#[Deprecated]
class Demo
{
#[WithArguments(1, 2)]
public $list;
#[Foo\Cached(mode: true)]
public function count(#[Bar] $items)
{
}
}
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, bude přepsána.
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::NAME_FUNCTION); // '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::NAME_FUNCTION); // '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->simplifyName('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
// 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
// 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()
{
}
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
a PhpGenerator verze 3.4):
$class = Nette\PhpGenerator\ClassType::withBodiesFrom(MyClass::class);
$function = Nette\PhpGenerator\GlobalFunction::withBodyFrom('dump');
Načítání z PHP souborů
Třídy nebo funkce můžete také načíst přímo ze souboru PHP, který ještě není inkludován, nebo z řetězce obsahujícího PHP kód:
$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX
<?php
class Demo
{
public $foo;
}
XX);
Načtení celého souboru PHP, který může obsahovat více tříd nebo dokonce více jmenných prostorů:
$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));
Vyžaduje, aby byl nainstalován nikic/php-parser
.
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
verze | kompatibilní s PHP |
---|---|
PhpGenerator 3.6 | PHP 7.2 až 8.2 |
PhpGenerator 3.2 – 3.5 | PHP 7.1 až 8.0 |
PhpGenerator 3.1 | PHP 7.1 až 7.3 |
PhpGenerator 3.0 | PHP 7.0 až 7.3 |