PHP-Code-Generator

Suchen Sie ein Tool, um PHP-Code für Klassen, Funktionen oder komplette Dateien zu generieren?
  • 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 $classweiter 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 setVariadics():

$method = $class->addMethod('count');
$method->setVariadics(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.

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

Version: 4.0