PHP-Code-Generator
- Unterstützt alle aktuellen PHP-Funktionen (wie Enums, etc.)
- Ermöglicht die einfache Änderung bestehender Klassen
- Ausgabe in Übereinstimmung mit PSR-12 / PER-Kodierung
- Ausgereifte, stabile und weit verbreitete Bibliothek
Installation
Laden Sie das Paket herunter und installieren Sie es mit Composer:
composer require nette/php-generator
Für die PHP-Kompatibilität siehe die Tabelle.
Klassen
Beginnen wir mit einem einfachen Beispiel für die Erzeugung von Klassen mit ClassType:
$class = new Nette\PhpGenerator\ClassType('Demo');
$class
->setFinal()
->setExtends(ParentClass::class)
->addImplement(Countable::class)
->addComment("Description of class.\nSecond line\n")
->addComment('@property-read Nette\Forms\Form $form');
// zur Erzeugung von PHP-Code einfach in einen String umwandeln oder echo verwenden:
echo $class;
Es wird dieses Ergebnis wiedergeben:
/**
* Description of class.
* Second line
*
* @property-read Nette\Forms\Form $form
*/
final class Demo extends ParentClass implements Countable
{
}
Wir können auch einen Drucker verwenden, um den Code zu erzeugen, den wir im Gegensatz zu echo $class
weiter konfigurieren können:
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class);
Wir können Konstanten (Klasse Constant) und Eigenschaften (Klasse Property) hinzufügen:
$class->addConstant('ID', 123)
->setProtected() // Konstante Sichtbarkeit
->setType('int')
->setFinal();
$class->addProperty('items', [1, 2, 3])
->setPrivate() // oder setVisibility('privat')
->setStatic()
->addComment('@var int[]');
$class->addProperty('list')
->setType('?array')
->setInitialized(); // druckt '= null'
Es erzeugt:
final protected const int ID = 123;
/** @var int[] */
private static $items = [1, 2, 3];
public ?array $list = null;
Und wir können Methoden hinzufügen:
$method = $class->addMethod('count')
->addComment('Count it.')
->setFinal()
->setProtected()
->setReturnType('?int') // Methode Rückgabetyp
->setBody('return count($items ?: $this->items);');
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
Das Ergebnis ist:
/**
* Count it.
*/
final protected function count(array &$items = []): ?int
{
return count($items ?: $this->items);
}
Die mit PHP 8.0 eingeführten promotierten Parameter können an den Konstruktor übergeben werden:
$method = $class->addMethod('__construct');
$method->addPromotedParameter('name');
$method->addPromotedParameter('args', [])
->setPrivate();
Das Ergebnis ist:
public function __construct(
public $name,
private $args = [],
) {
}
Readonly-Eigenschaften und -Klassen können über setReadOnly()
markiert werden.
Wenn die hinzugefügte Eigenschaft, Konstante, Methode oder der Parameter bereits existiert, wird eine Ausnahme ausgelöst.
Mitglieder können mit removeProperty()
, removeConstant()
, removeMethod()
oder
removeParameter()
entfernt werden.
Sie können auch bestehende Method
, Property
oder Constant
Objekte zur Klasse
hinzufügen:
$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);
Mit cloneWithName()
können Sie vorhandene Methoden, Eigenschaften und Konstanten mit einem anderen Namen
klonen:
$methodCount = $class->getMethod('count');
$methodRecount = $methodCount->cloneWithName('recount');
$class->addMember($methodRecount);
Schnittstelle oder Trait
Sie können Schnittstellen und Traits (Klassen InterfaceType und TraitType) erstellen:
$interface = new Nette\PhpGenerator\InterfaceType('MyInterface');
$trait = new Nette\PhpGenerator\TraitType('MyTrait');
Eigenschaften verwenden:
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addTrait('SmartObject');
$class->addTrait('MyTrait')
->addResolution('sayHello as protected')
->addComment('@use MyTrait<Foo>');
echo $class;
Ergebnis:
class Demo
{
use SmartObject;
/** @use MyTrait<Foo> */
use MyTrait {
sayHello as protected;
}
}
Enums
Sie können ganz einfach die Enums erstellen, die PHP 8.1 mitbringt (Klasse EnumType):
$enum = new Nette\PhpGenerator\EnumType('Suit');
$enum->addCase('Clubs');
$enum->addCase('Diamonds');
$enum->addCase('Hearts');
$enum->addCase('Spades');
echo $enum;
Ergebnis:
enum Suit
{
case Clubs;
case Diamonds;
case Hearts;
case Spades;
}
Sie können auch skalare Äquivalente für Fälle definieren, um eine zurückgesetzte Aufzählung zu erstellen:
$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');
Es ist möglich, jedem Fall einen Kommentar oder Attribute hinzuzufügen, indem Sie
addComment()
oder addAttribute()
verwenden.
Anonyme Klasse
Geben Sie null
als Namen an und Sie haben eine anonyme Klasse:
$class = new Nette\PhpGenerator\ClassType(null);
$class->addMethod('__construct')
->addParameter('foo');
echo '$obj = new class ($val) ' . $class . ';';
Ergebnis:
$obj = new class ($val) {
public function __construct($foo)
{
}
};
Globale Funktion
Der Code der Funktionen wird die Klasse GlobalFunction erzeugen:
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('return $a + $b;');
$function->addParameter('a');
$function->addParameter('b');
echo $function;
// oder PsrPrinter für PSR-2 / PSR-12 / PER-konforme Ausgabe verwenden
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);
Ergebnis:
function foo($a, $b)
{
return $a + $b;
}
Schließung
Der Code von Closures wird die Klasse Closure erzeugen:
$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('return $a + $b;');
$closure->addParameter('a');
$closure->addParameter('b');
$closure->addUse('c')
->setReference();
echo $closure;
// oder PsrPrinter für PSR-2 / PSR-12 / PER-konforme Ausgabe verwenden
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);
Ergebnis:
function ($a, $b) use (&$c) {
return $a + $b;
}
Pfeil-Funktion
Sie können den Abschluss auch als Pfeilfunktion mit einem Drucker ausdrucken:
$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('$a + $b');
$closure->addParameter('a');
$closure->addParameter('b');
echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure);
Ergebnis:
fn($a, $b) => $a + $b
Methode und Funktionssignatur
Methoden werden durch die Klasse Method repräsentiert. Sie können die Sichtbarkeit und den Rückgabewert festlegen, Kommentare und Attribute hinzufügen usw:
$method = $class->addMethod('count')
->addComment('Count it.')
->setFinal()
->setProtected()
->setReturnType('?int');
Jeder Parameter wird durch eine Klasse Parameter repräsentiert. Auch hier können Sie jede erdenkliche Eigenschaft einstellen:
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
// function count(&$items = [])
Um die sogenannten Variadics-Parameter (oder auch den Splat-, Spread-, Ellipsis-, Unpacking- oder Three Dots-Operator) zu
definieren, verwenden Sie setVariadic()
:
$method = $class->addMethod('count');
$method->setVariadic(true);
$method->addParameter('items');
Erzeugt:
function count(...$items)
{
}
Methode und Funktionskörper
Der Körper kann der Methode setBody()
auf einmal oder sequentiell (Zeile für Zeile) durch wiederholten Aufruf
von addBody()
übergeben werden:
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('$a = rand(10, 20);');
$function->addBody('return $a;');
echo $function;
Ergebnis
function foo()
{
$a = rand(10, 20);
return $a;
}
Sie können spezielle Platzhalter verwenden, um Variablen auf praktische Art und Weise zu injizieren.
Einfache Platzhalter ?
$str = 'any string';
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('return substr(?, ?);', [$str, $num]);
echo $function;
Ergebnis:
function foo()
{
return substr('any string', 3);
}
Variadischer Platzhalter ...?
$items = [1, 2, 3];
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('myfunc(...?);', [$items]);
echo $function;
Ergebnis:
function foo()
{
myfunc(1, 2, 3);
}
Sie können auch PHP 8 benannte Parameter mit Platzhaltern verwenden ...?:
$items = ['foo' => 1, 'bar' => true];
$function->setBody('myfunc(...?:);', [$items]);
// myfunc(foo: 1, bar: true);
Platzhalter mit Schrägstrich ausblenden \?
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addParameter('a');
$function->addBody('return $a \? 10 : ?;', [$num]);
echo $function;
Ergebnis:
function foo($a)
{
return $a ? 10 : 3;
}
Drucker und PSR-Konformität
Die Klasse Printer wird verwendet, um PHP-Code zu erzeugen:
$class = new Nette\PhpGenerator\ClassType('Demo');
// ...
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // gleich wie: echo $class
Es kann Code für alle anderen Elemente erzeugen und bietet Methoden wie printFunction()
,
printNamespace()
, etc.
Zusätzlich steht die Klasse PsrPrinter
zur Verfügung, deren Ausgabe dem PSR-2 / PSR-12 / PER-Kodierungsstil
entspricht:
$printer = new Nette\PhpGenerator\PsrPrinter;
echo $printer->printClass($class);
Möchten Sie das Verhalten an Ihre Bedürfnisse anpassen? Erstellen Sie Ihren eigenen Drucker, indem Sie von der Klasse
Printer
erben. Sie können diese Variablen neu konfigurieren:
class MyPrinter extends Nette\PhpGenerator\Printer
{
// Länge der Zeile, nach der die Zeile umgebrochen werden soll
public int $wrapLength = 120;
// Einrückungszeichen, kann durch eine Folge von Leerzeichen ersetzt werden
public string $indentation = "\t";
// Anzahl der Leerzeilen zwischen den Eigenschaften
public int $linesBetweenProperties = 0;
// Anzahl der Leerzeilen zwischen Methoden
public int $linesBetweenMethods = 2;
// Anzahl der Leerzeilen zwischen Gruppen von Verwendungsanweisungen für Klassen, Funktionen und Konstanten
public int $linesBetweenUseTypes = 0;
// Position der öffnenden Klammer für Funktionen und Methoden
public bool $bracesOnNextLine = true;
// Platzierung eines Parameters in einer Zeile, auch wenn er ein Attribut hat oder promotet wird
public bool $singleParameterOnOneLine = false;
// omits namespaces that do not contain any class or function
public bool $omitEmptyNamespaces = true;
// Trennzeichen zwischen der rechten Klammer und dem Rückgabetyp von Funktionen und Methoden
public string $returnTypeColon = ': ';
}
Wie und warum genau unterscheiden sich der Standard Printer
und PsrPrinter
? Warum gibt es nicht nur
einen Drucker, den PsrPrinter
, in diesem Paket?
Der Standard Printer
formatiert den Code so, wie wir es in ganz Nette tun. Da Nette viel früher als PSR
entstanden ist, und auch weil PSR viele Jahre lang keine Standards rechtzeitig geliefert hat, sondern manchmal sogar mit mehreren
Jahren Verspätung nach der Einführung einer neuen Funktion in PHP, führte dies zu einigen kleinen Unterschieden im Codierungsstandard. Der größte Unterschied ist die Verwendung von
Tabulatoren anstelle von Leerzeichen. Wir wissen, dass wir durch die Verwendung von Tabulatoren in unseren Projekten eine
Breitenanpassung ermöglichen, die für
Menschen mit Sehbehinderungen wichtig ist. Ein Beispiel für einen geringfügigen Unterschied ist die Platzierung der
geschweiften Klammer in einer separaten Zeile für Funktionen und Methoden und immer. Wir halten die PSR-Empfehlung für unlogisch
und führen zu einer Verringerung der Klarheit
des Codes.
Typen
Jeder Typ oder Gewerkschafts-/Schnittstellentyp kann als String übergeben werden, für native Typen können Sie auch vordefinierte Konstanten verwenden:
use Nette\PhpGenerator\Type;
$member->setType('array'); // oder 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'); // oder Type::intersection(Foo::class, Bar::class)
$member->setType(null); // Entfernt Typ
Das gleiche gilt für die Methode setReturnType()
.
Wörterbücher
Mit Literal
können Sie beliebigen PHP-Code an z.B. Standard-Eigenschafts- oder Parameterwerte etc.
übergeben:
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;
Ergebnis:
class Demo
{
public $foo = Iterator::SELF_FIRST;
public function bar($id = 1 + 2)
{
}
}
Sie können auch Parameter an Literal
übergeben und diese mit Hilfe spezieller Platzhalter in gültigen PHP-Code umwandeln lassen:
new Literal('substr(?, ?)', [$a, $b]);
// erzeugt zum Beispiel: substr('hallo', 5);
Das Literal, das die Erstellung eines neuen Objekts repräsentiert, kann leicht mit der Methode new
erzeugt
werden:
Literal::new(Demo::class, [$a, 'foo' => $b]);
// erzeugt, zum Beispiel: new Demo(10, foo: 20)
Attribute
Sie können PHP 8 Attribute zu allen Klassen, Methoden, Eigenschaften, Konstanten, Enum-Fällen, Funktionen, Closures und Parametern hinzufügen. Literale können auch als Parameterwerte verwendet werden.
$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;
Ergebnis:
#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])]
class Demo
{
#[Deprecated]
public $list;
#[Foo\Cached(mode: true)]
public function count(
#[Bar]
$items,
) {
}
}
Namensraum
Klassen, Traits, Schnittstellen und Enums (im Folgenden Klassen) können in Namespaces (PhpNamespace) gruppiert werden:
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
// neue Klassen im Namespace erstellen
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');
// oder eine bestehende Klasse in den Namespace einfügen
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);
Wenn die Klasse bereits existiert, wird eine Ausnahme geworfen.
Sie können Use-Statements definieren:
// 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');
Um einen voll qualifizierten Klassen-, Funktions- oder Konstantennamen entsprechend den definierten Aliases zu vereinfachen,
verwenden Sie die Methode simplifyName
:
echo $namespace->simplifyName('Foo\Bar'); // 'Bar', weil 'Foo' der aktuelle Namensraum ist
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', wegen der definierten Use-Anweisung
Umgekehrt können Sie einen vereinfachten Klassen-, Funktions- oder Konstantennamen mit der Methode resolveName
in
einen voll qualifizierten Namen umwandeln:
echo $namespace->resolveName('Bar'); // 'Foo\Bar'
echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'
Auflösen von Klassennamen
Wenn eine Klasse Teil eines Namensraums ist, wird sie etwas anders dargestellt: Alle Typen (z.B. Typ-Hinweise, Rückgabetypen, Name der Elternklasse, implementierte Schnittstellen, verwendete Traits und Attribute) werden automatisch aufgelöst (es sei denn, Sie schalten dies aus, siehe unten). Das bedeutet, dass Sie voll qualifizierte Klassennamen in Definitionen verwenden müssen, und diese werden im resultierenden Code durch Aliase (basierend auf Verwendungsklauseln) oder voll qualifizierte Namen ersetzt:
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
$namespace->addUse('Bar\AliasedClass');
$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // wird zu A vereinfacht
->addTrait('Bar\AliasedClass'); // es wird zu AliasedClass vereinfacht
$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // in Kommentaren manuell vereinfachen
$method->addParameter('arg')
->setType('Bar\OtherClass'); // es wird in \Bar\OtherClass aufgelöst
echo $namespace;
// oder verwenden Sie PsrPrinter für eine PSR-2 / PSR-12 / PER konforme Ausgabe
// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);
Ergebnis:
namespace Foo;
use Bar\AliasedClass;
class Demo implements A
{
use AliasedClass;
/**
* @return D
*/
public function method(\Bar\OtherClass $arg)
{
}
}
Die automatische Auflösung kann auf diese Weise ausgeschaltet werden:
$printer = new Nette\PhpGenerator\Printer; // oder PsrDrucker
$printer->setTypeResolving(false);
echo $printer->printNamespace($namespace);
PHP-Dateien
Klassen, Funktionen und Namespaces können in PHP-Dateien gruppiert werden, die durch die Klasse PhpFile repräsentiert werden:
$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('This file is auto-generated.');
$file->setStrictTypes(); // fügt declare(strict_types=1) hinzu
$class = $file->addClass('Foo\A');
$function = $file->addFunction('Foo\foo');
// oder
// $namespace = $file->addNamespace('Foo');
// $class = $namespace->addClass('A');
// $function = $namespace->addFunction('foo');
echo $file;
// oder PsrPrinter für PSR-2 / PSR-12 / PER konforme Ausgabe verwenden
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);
Ergebnis:
<?php
/**
* This file is auto-generated.
*/
declare(strict_types=1);
namespace Foo;
class A
{
}
function foo()
{
}
Bitte beachten Sie: Außer den Funktionen und Klassen kann den Dateien kein weiterer Code hinzugefügt werden.
Generierung nach vorhandenen Elementen
Neben der Möglichkeit, Klassen und Funktionen mit Hilfe der oben beschriebenen API zu modellieren, können Sie diese auch automatisch anhand vorhandener Klassen und Funktionen generieren lassen:
// erstellt eine Klasse, die mit der PDO-Klasse identisch ist
$class = Nette\PhpGenerator\ClassType::from(PDO::class);
// erstellt eine Funktion, die mit trim() identisch ist
$function = Nette\PhpGenerator\GlobalFunction::from('trim');
// erstellt eine Schließung wie angegeben
$closure = Nette\PhpGenerator\Closure::from(
function (stdClass $a, $b = null) {},
);
Funktions- und Methodenkörper sind standardmäßig leer. Wenn Sie diese auch laden wollen, verwenden Sie diesen Weg (dafür
muss nikic/php-parser
installiert sein):
$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true);
$function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);
Laden aus PHP-Datei
Sie können auch Funktionen, Klassen, Schnittstellen und Enums direkt aus einem String von PHP-Code laden. Zum Beispiel
erstellen wir das Objekt ClassType
auf diese Weise:
$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX
<?php
class Demo
{
public $foo;
}
XX);
Beim Laden von Klassen aus PHP-Code werden einzeilige Kommentare außerhalb von Methodenkörpern ignoriert (z. B. für Eigenschaften usw.), da diese Bibliothek keine API hat, um mit ihnen zu arbeiten.
Sie können auch die gesamte PHP-Datei direkt laden, die eine beliebige Anzahl von Klassen, Funktionen oder sogar mehrere Namespaces enthalten kann:
$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));
Der anfängliche Dateikommentar und die Erklärung strict_types
werden ebenfalls geladen. Alle anderen globalen
Codes werden dagegen ignoriert.
Dazu muss nikic/php-parser
installiert sein.
Wenn Sie globalen Code in Dateien oder einzelne Anweisungen in Methodenkörpern manipulieren müssen, ist es
besser, die Bibliothek nikic/php-parser
direkt zu verwenden.
Klasse Manipulator
Die Klasse ClassManipulator bietet Werkzeuge zur Manipulation von Klassen.
$class = new Nette\PhpGenerator\ClassType('Demo');
$manipulator = new Nette\PhpGenerator\ClassManipulator($class);
Die Methode inheritMethod()
kopiert eine Methode aus einer übergeordneten Klasse oder einer implementierten
Schnittstelle in Ihre Klasse. Auf diese Weise können Sie die Methode außer Kraft setzen oder ihre Signatur erweitern:
$method = $manipulator->inheritMethod('bar');
$method->setBody('...');
Die Methode inheritProperty()
kopiert eine Eigenschaft aus einer übergeordneten Klasse in Ihre Klasse. Dies ist
nützlich, wenn Sie die gleiche Eigenschaft in Ihrer Klasse haben möchten, aber möglicherweise mit einem anderen
Standardwert:
$property = $manipulator->inheritProperty('foo');
$property->setValue('new value');
Die Methode implementInterface()
implementiert automatisch alle Methoden der angegebenen Schnittstelle in Ihrer
Klasse:
$manipulator->implementInterface(SomeInterface::class);
// Ihre Klasse implementiert nun SomeInterface und enthält alle seine Methoden
Variablen-Dumper
Der Dumper gibt eine parsbare PHP-String-Darstellung einer Variablen zurück. Bietet eine bessere und übersichtlichere Ausgabe
als die native Funktion var_export()
.
$dumper = new Nette\PhpGenerator\Dumper;
$var = ['a', 'b', 123];
echo $dumper->dump($var); // druckt ['a', 'b', 123]
Kompatibilitätstabelle
PhpGenerator 4.0 und 4.1 sind kompatibel mit PHP 8.0 bis 8.3