Generator de coduri PHP

Sunteți în căutarea unui instrument pentru a genera cod PHP pentru clase, funcții sau fișiere complete?
  • Suportă toate cele mai recente caracteristici PHP (cum ar fi cârligele de proprietate, enumurile, atributele etc.)
  • Vă permite să modificați cu ușurință clasele existente
  • Ieșire conformă cu stilul de codare PSR-12 / PER
  • Bibliotecă matură, stabilă și utilizată pe scară largă

Instalare

Descărcați și instalați pachetul folosind Composer:

composer require nette/php-generator

Pentru compatibilitatea PHP, consultați tabelul.

Clase

Să începem cu un exemplu simplu de generare a unei clase folosind 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');

// pentru a genera cod PHP, pur și simplu transformați în șir de caractere sau utilizați echo:
echo $class;

Acesta va reda acest rezultat:

/**
 * Description of class.
 * Second line
 *
 * @property-read Nette\Forms\Form $form
 */
final class Demo extends ParentClass implements Countable
{
}

De asemenea, putem folosi o imprimantă pentru a genera codul, pe care, spre deosebire de echo $class, o vom putea configura ulterior:

$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class);

Putem adăuga constante (clasa Constant) și proprietăți (clasa Property):

$class->addConstant('ID', 123)
	->setProtected() // vizibilitate constantă
	->setType('int')
	->setFinal();

$class->addProperty('items', [1, 2, 3])
	->setPrivate() // sau setVisibility('private')
	->setStatic()
	->addComment('@var int[]');

$class->addProperty('list')
	->setType('?array')
	->setInitialized(); // tipărește '= null'

Se generează:

final protected const const int ID = 123;

/** @var int[] */
private static $items = [1, 2, 3];

public ?array $list = null;

Și putem adăuga metode:

$method = $class->addMethod('count')
	->addComment('Count it.')
	->setFinal()
	->setProtected()
	->setReturnType('?int') // tipul de returnare a metodei
	->setBody('return count($items ?: $this->items);');

$method->addParameter('items', []) // $items = []
	->setReference()           // &$items = []
	->setType('array');        // array &$items = []

Rezultă:

/**
 * Count it.
 */
final protected function count(array &$items = []): ?int
{
	return count($items ?: $this->items);
}

Parametrii promovați introduși de PHP 8.0 pot fi trecuți la constructor:

$method = $class->addMethod('__construct');
$method->addPromotedParameter('name');
$method->addPromotedParameter('args', [])
	->setPrivate();

Aceasta are ca rezultat:

public function __construct(
	public $name,
	private $args = [],
) {
}

Proprietățile și clasele de citire exclusivă pot fi marcate prin intermediul setReadOnly().


În cazul în care proprietatea, constanta, metoda sau parametrul adăugat există deja, se aruncă o excepție.

Membrii pot fi eliminați utilizând removeProperty(), removeConstant(), removeMethod() sau removeParameter().

De asemenea, se pot adăuga la clasă obiecte existente Method, Property sau 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);

Puteți clona metodele, proprietățile și constantele existente cu un nume diferit folosind cloneWithName():

$methodCount = $class->getMethod('count');
$methodRecount = $methodCount->cloneWithName('recount');
$class->addMember($methodRecount);

Interfață sau trăsătură

Puteți crea interfețe și trăsături (clasele InterfaceType și TraitType):

$interface = new Nette\PhpGenerator\InterfaceType('MyInterface');
$trait = new Nette\PhpGenerator\TraitType('MyTrait');

Utilizarea trăsăturilor:

$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addTrait('SmartObject');
$class->addTrait('MyTrait')
	->addResolution('sayHello as protected')
	->addComment('@use MyTrait<Foo>');
echo $class;

Rezultat:

class Demo
{
	use SmartObject;
	/** @use MyTrait<Foo> */
	use MyTrait {
		sayHello as protected;
	}
}

Enums

Puteți crea cu ușurință enumerațiile pe care le aduce PHP 8.1 (clasa EnumType):

$enum = new Nette\PhpGenerator\EnumType('Suit');
$enum->addCase('Clubs');
$enum->addCase('Diamonds');
$enum->addCase('Hearts');
$enum->addCase('Spades');

echo $enum;

Rezultat:

enum Suit
{
	case Clubs;
	case Diamonds;
	case Hearts;
	case Spades;
}

De asemenea, puteți defini echivalenți scalari pentru cazuri pentru a crea un enum susținut:

$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');

Este posibil să se adauge un comentariu sau atribute la fiecare caz folosind addComment() sau addAttribute().

Clasa Anonymous

Dați null ca nume și veți avea o clasă anonimă:

$class = new Nette\PhpGenerator\ClassType(null);
$class->addMethod('__construct')
	->addParameter('foo');

echo '$obj = new class ($val) ' . $class . ';';

Rezultat:

$obj = new class ($val) {

	public function __construct($foo)
	{
	}
};

Funcția globală

Codul de funcții va genera clasa GlobalFunction:

$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('return $a + $b;');
$function->addParameter('a');
$function->addParameter('b');
echo $function;

// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);

Rezultat:

function foo($a, $b)
{
	return $a + $b;
}

Închidere

Codul de închidere va genera clasa Closure:

$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('return $a + $b;');
$closure->addParameter('a');
$closure->addParameter('b');
$closure->addUse('c')
	->setReference();
echo $closure;

// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);

Rezultat:

function ($a, $b) use (&$c) {
	return $a + $b;
}

Funcția săgeată

De asemenea, puteți imprima închiderea ca funcție săgeată folosind imprimanta:

$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('$a + $b');
$closure->addParameter('a');
$closure->addParameter('b');

echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure);

Rezultat:

fn($a, $b) => $a + $b

Semnătura metodei și a funcției

Metodele sunt reprezentate de clasa Method. Puteți seta vizibilitatea, valoarea de returnare, adăuga comentarii, atribute etc:

$method = $class->addMethod('count')
	->addComment('Count it.')
	->setFinal()
	->setProtected()
	->setReturnType('?int');

Fiecare parametru este reprezentat de o clasă Parameter. Din nou, puteți seta toate proprietățile imaginabile:

$method->addParameter('items', []) // $items = []
	->setReference() // &$items = []
	->setType('array'); // array &$items = []

// function count(&$items = [])

Pentru a defini așa-numiții parametri variadici (sau, de asemenea, operatorul splat, spread, elipsis, unpacking sau trei puncte), utilizați setVariadic():

$method = $class->addMethod('count');
$method->setVariadic(true);
$method->addParameter('items');

Generates:

function count(...$items)
{
}

Metoda și corpul funcției

Corpul poate fi transmis metodei setBody() o dată sau secvențial (linie cu linie) prin apelarea repetată a metodei addBody():

$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('$a = rand(10, 20);');
$function->addBody('return $a;');
echo $function;

Rezultat

function foo()
{
	$a = rand(10, 20);
	return $a;
}

Puteți utiliza caractere de poziție speciale pentru a injecta variabilele în mod practic.

Semne de poziție simple ?

$str = 'any string';
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('return substr(?, ?);', [$str, $num]);
echo $function;

Rezultat:

function foo()
{
	return substr('any string', 3);
}

Variadic placeholder ...?

$items = [1, 2, 3];
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('myfunc(...?);', [$items]);
echo $function;

Rezultat:

function foo()
{
	myfunc(1, 2, 3);
}

De asemenea, puteți utiliza parametrii numiți din PHP 8 folosind caractere de poziție ...?:

$items = ['foo' => 1, 'bar' => true];
$function->setBody('myfunc(...?:);', [$items]);

// myfunc(foo: 1, bar: true);

Scăpați de locul rezervat utilizând slash \?

$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addParameter('a');
$function->addBody('return $a \? 10 : ?;', [$num]);
echo $function;

Rezultatul:

function foo($a)
{
	return $a ? 10 : 3;
}

Imprimantele și conformitatea PSR

Clasa Printer este utilizată pentru a genera cod PHP:

$class = new Nette\PhpGenerator\ClassType('Demo');
// ...

$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // la fel ca: echo $class

Acesta poate genera cod pentru toate celelalte elemente, oferind metode precum printFunction(), printNamespace(), etc.

În plus, este disponibilă clasa PsrPrinter, a cărei ieșire este conformă cu stilul de codare PSR-2 / PSR-12 / PER:

$printer = new Nette\PhpGenerator\PsrPrinter;
echo $printer->printClass($class);

Aveți nevoie să ajustați comportamentul la nevoile dumneavoastră? Creați-vă propria imprimantă moștenind din clasa Printer. Puteți reconfigura aceste variabile:

class MyPrinter extends Nette\PhpGenerator\Printer
{
	// lungimea liniei după care se va întrerupe linia
	public int $wrapLength = 120;
	// caracter de indentare, poate fi înlocuit cu o secvență de spații
	public string $indentation = "\t";
	// numărul de linii goale între proprietăți
	public int $linesBetweenProperties = 0;
	// numărul de linii goale între metode
	public int $linesBetweenMethods = 2;
	// numărul de linii goale între grupuri de declarații de utilizare pentru clasă, funcții și constantă
	public int $linesBetweenUseTypes = 0;
	// poziția bretonului de deschidere pentru funcții și metode
	public bool $bracesOnNextLine = true;
	// plasează un parametru pe o singură linie, chiar dacă are un atribut sau este promovat
	public bool $singleParameterOnOneLine = false;
	// omits namespaces that do not contain any class or function
	public bool $omitEmptyNamespaces = true;
	// separator între paranteza dreaptă și tipul return al funcțiilor și metodelor
	public string $returnTypeColon = ': ';
}

Cum și de ce anume diferă standardul Printer și PsrPrinter? De ce nu există doar o singură imprimantă, PsrPrinter, în pachet?

Standardul Printer formatează codul așa cum o facem în toată Nette. Deoarece Nette a fost creat mult mai devreme decât PSR și, de asemenea, deoarece PSR timp de mulți ani nu a livrat standardele la timp, ci uneori chiar cu o întârziere de câțiva ani de la introducerea unei noi caracteristici în PHP, acest lucru a dus la câteva diferențe minore în standardul de codare. Cea mai mare diferență este doar utilizarea tabulatoarelor în loc de spații. Știm că, prin utilizarea tabulațiilor în proiectele noastre, permitem ajustarea lățimii, ceea ce este esențial pentru persoanele cu deficiențe de vedere. Un exemplu de diferență minoră este plasarea acoladei curly brace pe o linie separată pentru funcții și metode și întotdeauna. Considerăm că recomandarea PSR este ilogică și duce la o scădere a clarității codului.

Tipuri

Fiecare tip sau tip de uniune/intersecție poate fi transmis ca un șir de caractere, puteți utiliza, de asemenea, constante predefinite pentru tipurile native:

use Nette\PhpGenerator\Type;

$member->setType('array'); // sau 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'); // sau Type::intersection(Foo::class, Bar::class)
$member->setType(null); // elimină tipul

Același lucru este valabil și pentru metoda setReturnType().

Literali

Cu Literal puteți transmite cod PHP arbitrar, de exemplu, pentru valori implicite ale proprietăților sau parametrilor etc:

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;

Rezultat:

class Demo
{
	public $foo = Iterator::SELF_FIRST;

	public function bar($id = 1 + 2)
	{
	}
}

Puteți, de asemenea, să transmiteți parametri la Literal și să formatați codul PHP valid folosind caractere de poziție speciale:

new Literal('substr(?, ?)', [$a, $b]);
// generează, de exemplu: substr('hello', 5);

Literalul care reprezintă crearea unui nou obiect este generat cu ușurință prin metoda new:

Literal::new(Demo::class, [$a, 'foo' => $b]);
// generează, de exemplu: new Demo(10, foo: 20)

Atribute

Puteți adăuga atribute PHP 8 la toate clasele, metodele, proprietățile, constantele, cazurile enum, funcțiile, închiderile și parametrii. Literalii pot fi, de asemenea, utilizați ca valori de parametru.

$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;

Rezultat:

#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])]
class Demo
{
	#[Deprecated]
	public $list;


	#[Foo\Cached(mode: true)]
	public function count(
		#[Bar]
		$items,
	) {
	}
}

Cârlige de proprietate

De asemenea, puteți defini cârlige de proprietate (reprezentate de clasa PropertyHook) pentru operațiile get și set, o caracteristică introdusă în 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;

Aceasta generează:

class Demo
{
    public string $firstName {
        set(string $value) => strtolower($value);
        get {
            return ucfirst($this->firstName);
        }
    }
}

Proprietățile și cârligele de proprietate pot fi abstracte sau finale:

$class->addProperty('id')
    ->setType('int')
    ->addHook('get')
        ->setAbstract();

$class->addProperty('role')
    ->setType('string')
    ->addHook('set', 'strtolower($value)')
        ->setFinal();

Vizibilitate asimetrică

PHP 8.4 introduce vizibilitatea asimetrică pentru proprietăți. Puteți seta niveluri de acces diferite pentru citire și scriere.

Vizibilitatea poate fi setată folosind fie metoda setVisibility() cu doi parametri, fie folosind setPublic(), setProtected(), sau setPrivate() cu parametrul mode care specifică dacă vizibilitatea se aplică la obținerea sau setarea proprietății. Modul implicit este 'get'.

$class = new Nette\PhpGenerator\ClassType('Demo');

$class->addProperty('name')
    ->setType('string')
    ->setVisibility('public', 'private'); // public for read, private for write

$class->addProperty('id')
    ->setType('int')
    ->setProtected('set'); // protected for write

echo $class;

Acest lucru generează:

class Demo
{
    public private(set) string $name;

    protected(set) int $id;
}

Spațiul de nume

Clasele, trăsăturile, interfețele și enumerațiile (denumite în continuare clase) pot fi grupate în spații de nume (PhpNamespace):

$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');

// crearea de noi clase în spațiul de nume
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');

// sau inserați o clasă existentă în spațiul de nume
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);

În cazul în care clasa există deja, se aruncă o excepție.

Puteți defini declarații de utilizare:

// 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');

Pentru a simplifica un nume de clasă, funcție sau constantă complet calificat în conformitate cu aliasurile definite, utilizați metoda simplifyName:

echo $namespace->simplifyName('Foo\Bar'); // 'Bar', deoarece 'Foo' este spațiul de nume curent
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // "range", din cauza declarației de utilizare definite

În schimb, puteți converti un nume simplificat de clasă, funcție sau constantă într-un nume complet calificat utilizând metoda resolveName:

echo $namespace->resolveName('Bar'); // 'Foo\Bar'
echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'

Rezolvarea numelor de clase

Când o clasă face parte dintr-un spațiu de nume, aceasta este redată în mod ușor diferit: toate tipurile (de exemplu, indicii de tip, tipuri de returnare, numele clasei părinte, interfețe implementate, trăsături utilizate și atribute) sunt automat rezolvate (dacă nu le dezactivați, a se vedea mai jos). Acest lucru înseamnă că trebuie să utilizați nume de clasă complet calificate în definiții, iar acestea vor fi înlocuite cu pseudonime (pe baza clauzelor de utilizare) sau cu nume complet calificate în codul rezultat:

$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
$namespace->addUse('Bar\AliasedClass');

$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // se va simplifica la A
	->addTrait('Bar\AliasedClass'); // se va simplifica în AliasedClass

$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // în comentarii simplificați manual
$method->addParameter('arg')
	->setType('Bar\OtherClass'); // se va rezolva în \Bar\OtherClass

echo $namespace;

// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);

Rezultat:

namespace Foo;

use Bar\AliasedClass;

class Demo implements A
{
	use AliasedClass;

	/**
	 * @return D
	 */
	public function method(\Bar\OtherClass $arg)
	{
	}
}

Rezolvarea automată poate fi dezactivată în acest mod:

$printer = new Nette\PhpGenerator\Printer; // sau PsrPrinter
$printer->setTypeResolving(false);
echo $printer->printNamespace($namespace);

Fișiere PHP

Clasele, funcțiile și spațiile de nume pot fi grupate în fișiere PHP reprezentate de clasa PhpFile:

$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('This file is auto-generated.');
$file->setStrictTypes(); // adaugă declare(strict_types=1)

$class = $file->addClass('Foo\A');
$function = $file->addFunction('Foo\foo');

// sau
// $namespace = $file->addNamespace('Foo');
// $class = $namespace->addClass('A');
// $function = $namespace->addFunction('foo');

echo $file;

// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);

Rezultat:

<?php

/**
 * This file is auto-generated.
 */

declare(strict_types=1);

namespace Foo;

class A
{
}

function foo()
{
}

Atenție: Nu se poate adăuga niciun cod suplimentar la fișiere în afara funcțiilor și claselor.

Generarea în funcție de cele existente

Pe lângă posibilitatea de a modela clase și funcții utilizând API-ul descris mai sus, puteți, de asemenea, să le generați automat în funcție de cele existente:

// creează o clasă identică cu clasa PDO
$class = Nette\PhpGenerator\ClassType::from(PDO::class);

// creează o funcție identică cu trim()
$function = Nette\PhpGenerator\GlobalFunction::from('trim');

// creează o închidere așa cum este specificat
$closure = Nette\PhpGenerator\Closure::from(
	function (stdClass $a, $b = null) {},
);

Corpurile funcțiilor și metodelor sunt goale în mod implicit. Dacă doriți să le încărcați și pe acestea, utilizați această modalitate (necesită instalarea nikic/php-parser ):

$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true);

$function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);

Încărcare din fișierul PHP

De asemenea, puteți încărca funcții, clase, interfețe și enume direct dintr-un șir de cod PHP. De exemplu, creăm obiectul ClassType în acest mod:

$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX
	<?php

	class Demo
	{
		public $foo;
	}
	XX);

La încărcarea claselor din codul PHP, comentariile pe o singură linie din afara corpului metodelor sunt ignorate (de exemplu, pentru proprietăți etc.), deoarece această bibliotecă nu dispune de un API pentru a lucra cu acestea.

De asemenea, puteți încărca direct întregul fișier PHP, care poate conține orice număr de clase, funcții sau chiar mai multe spații de nume:

$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));

Se încarcă, de asemenea, comentariul inițial al fișierului și declarația strict_types. Pe de altă parte, toate celelalte coduri globale sunt ignorate.

Acest lucru necesită instalarea nikic/php-parser.

Dacă aveți nevoie să manipulați codul global din fișiere sau declarațiile individuale din corpurile metodelor, este mai bine să utilizați direct biblioteca nikic/php-parser.

Manipulator de clasă

Clasa ClassManipulator oferă instrumente pentru manipularea claselor.

$class = new Nette\PhpGenerator\ClassType('Demo');
$manipulator = new Nette\PhpGenerator\ClassManipulator($class);

Metoda inheritMethod() copiază o metodă dintr-o clasă părinte sau dintr-o interfață implementată în clasa dumneavoastră. Acest lucru vă permite să suprascrieți metoda sau să-i extindeți semnătura:

$method = $manipulator->inheritMethod('bar');
$method->setBody('...');

Metoda inheritProperty() copiază o proprietate dintr-o clasă părinte în clasa dumneavoastră. Acest lucru este util atunci când doriți să aveți aceeași proprietate în clasa dvs., dar eventual cu o valoare implicită diferită:

$property = $manipulator->inheritProperty('foo');
$property->setValue('new value');

Metoda implement() implementează automat toate metodele și proprietățile din interfața sau clasa abstractă dată:

$manipulator->implement(SomeInterface::class);
// Acum clasa dvs. implementează SomeInterface și include toate metodele acesteia

Descărcător de variabile

Dumper returnează o reprezentare de tip șir de caractere PHP analizabilă a unei variabile. Oferă o ieșire mai bună și mai clară decât funcția nativă var_export().

$dumper = new Nette\PhpGenerator\Dumper;

$var = ['a', 'b', 123];

echo $dumper->dump($var); // prints ['a', 'b', 123]

Tabel de compatibilitate

PhpGenerator 4.1 este compatibil cu PHP 8.0 până la 8.4.

versiune: 4.0