Generator de cod PHP

Căutați un instrument pentru generarea codului PHP al claselor, funcțiilor sau fișierelor complete?
  • Cunoaște toate cele mai recente caracteristici PHP (cum ar fi property hooks, enumuri, atribute etc.)
  • Vă permite să modificați cu ușurință clasele existente
  • Codul de ieșire este în conformitate cu stilul de codare PSR-12 / PER
  • Bibliotecă matură, stabilă și utilizată pe scară largă

Instalare

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

composer require nette/php-generator

Compatibilitatea cu PHP o găsiți în tabel.

Clase

Să începem direct cu un exemplu de creare a unei clase folosind ClassType:

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

$class
	->setFinal()
	->setExtends(ParentClass::class)
	->addImplement(Countable::class)
	->addComment("Descrierea clasei.\nA doua linie\n")
	->addComment('@property-read Nette\Forms\Form $form');

// generați codul simplu prin conversie la șir sau folosind echo:
echo $class;

Returnează următorul rezultat:

/**
 * Descrierea clasei
 * A doua linie
 *
 * @property-read Nette\Forms\Form $form
 */
final class Demo extends ParentClass implements Countable
{
}

Pentru a genera codul, putem folosi și așa-numitul printer, pe care, spre deosebire de echo $class, îl vom putea configura ulterior:

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

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

$class->addConstant('ID', 123)
	->setProtected() // vizibilitatea constantelor
	->setType('int')
	->setFinal();

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

$class->addProperty('list')
	->setType('?array')
	->setInitialized(); // afișează '= null'

Generează:

final protected 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') // tipuri de returnare la metode
	->setBody('return count($items ?: $this->items);');

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

Rezultatul este:

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

Parametrii promovați introduși în PHP 8.0 pot fi transmiși constructorului:

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

Rezultatul este:

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

Proprietățile și clasele destinate doar citirii pot fi marcate folosind funcția setReadOnly().


Dacă proprietatea, constanta, metoda sau parametrul adăugat există deja, se aruncă o excepție.

Membrii clasei pot fi eliminați folosind removeProperty(), removeConstant(), removeMethod() sau removeParameter().

Puteți adăuga, de asemenea, obiecte Method, Property sau Constant existente în clasă:

$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, de asemenea, clona metode, proprietăți și constante existente sub un alt nume folosind cloneWithName():

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

Interfață sau trait

Puteți crea interfețe și trait-uri (clasele InterfaceType și TraitType):

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

Utilizarea trait-urilor:

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

Enumuri

Enumurile, introduse în PHP 8.1, pot fi create cu ușurință astfel: (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;
}

Puteți, de asemenea, defini echivalente scalare și crea astfel un enum “backed”:

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

La fiecare case este posibil să adăugați un comentariu sau atribute folosind addComment() sau addAttribute().

Clase anonime

Ca nume transmitem null și avem 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ții globale

Codul funcțiilor este generat de clasa GlobalFunction:

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

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

Rezultat:

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

Funcții anonime

Codul funcțiilor anonime este generat de 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 folosiți PsrPrinter pentru 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ții săgeată prescurtate

Puteți, de asemenea, afișa o funcție anonimă prescurtată folosind printerul:

$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ături de metode și funcții

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

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

Parametrii individuali sunt reprezentați de clasa Parameter. Din nou, puteți seta toate proprietățile imaginabile:

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

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

Pentru definirea așa-numiților parametri variadici (sau operatorul splat) se folosește setVariadic():

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

Generează:

function count(...$items)
{
}

Corpuri de metode și funcții

Corpul poate fi transmis dintr-o dată metodei setBody() sau treptat (linie cu linie) prin apelarea repetată a 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 folosi placeholder-uri speciale pentru inserarea ușoară a variabilelor.

Placeholder-uri 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);
}

Placeholder pentru variadic ...?

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

Rezultat:

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

Puteți folosi, de asemenea, parametri numiți pentru PHP 8 folosind ...?:

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

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

Placeholder-ul se escapează folosind slash \?

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

Rezultat:

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

Printer și conformitatea cu PSR

Pentru generarea codului PHP se folosește clasa Printer:

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

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

Poate genera codul tuturor celorlalte elemente, oferă metode precum printFunction(), printNamespace(), etc.

Este disponibilă și 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 comandă? Creați-vă propria versiune moștenind clasa Printer. Puteți reconfigura aceste variabile:

class MyPrinter extends Nette\PhpGenerator\Printer
{
	// lungimea liniei după care se va face împărțirea liniei
	public int $wrapLength = 120;
	// caracterul 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 grupurile de 'use statements' pentru clase, funcții și constante
	public int $linesBetweenUseTypes = 0;
	// poziția acoladei de deschidere pentru funcții și metode
	public bool $bracesOnNextLine = true;
	// plasați un singur parametru pe o singură linie, chiar dacă are un atribut sau este suportat
	public bool $singleParameterOnOneLine = false;
	// omite spațiile de nume care nu conțin nicio clasă sau funcție
	public bool $omitEmptyNamespaces = true;
	// separatorul între paranteza dreaptă și tipul de returnare al funcțiilor și metodelor
	public string $returnTypeColon = ': ';
}

Cum și de ce diferă de fapt Printer-ul standard și PsrPrinter? De ce nu există un singur printer în pachet, și anume PsrPrinter?

Printer-ul standard formatează codul așa cum o facem în întregul Nette. Deoarece Nette a apărut cu mult înainte de PSR și, de asemenea, pentru că PSR nu a livrat standarde la timp timp de mulți ani, ci poate chiar cu câțiva ani întârziere de la introducerea unei noi caracteristici în PHP, s-a întâmplat ca standardul de codare să difere în câteva mici detalii. Diferența mai mare este doar utilizarea tabulatorilor în loc de spații. Știm că utilizarea tabulatorilor în proiectele noastre permite ajustarea lățimii, care este necesară pentru persoanele cu deficiențe de vedere. Un exemplu de mică diferență este plasarea acoladei pe o linie separată pentru funcții și metode, și întotdeauna. Recomandarea PSR ni se pare ilogică și duce la reducerea lizibilității codului.

Tipuri

Fiecare tip sau tip union/intersection poate fi transmis ca șir, puteți folosi și constante predefinite pentru tipuri native:

use Nette\PhpGenerator\Type;

$member->setType('array'); // sau Type::Array;
$member->setType('?array'); // sau Type::nullable(Type::Array);
$member->setType('array|string'); // sau 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

Folosind Literal puteți transmite orice cod PHP, de exemplu pentru valorile 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, transmite parametri la Literal și să îi formatați în cod PHP valid folosind placeholder-uri:

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

Literalul reprezentând crearea unui nou obiect poate fi generat cu ușurință folosind metoda new:

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

Atribute

Atributele PHP 8 pot fi adăugate la toate clasele, metodele, proprietățile, constantele, enumurile, funcțiile, closure-urile și parametrii. Ca valori ale parametrilor pot fi utilizate și literali.

$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,
	) {
	}
}

Property Hooks

Folosind property hooks (reprezentate de clasa PropertyHook) puteți defini operațiile get și set pentru proprietăți, o funcție 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;

Generează:

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

Proprietățile și property hooks 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 diferite niveluri de acces pentru citire și scriere.

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

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

$class->addProperty('name')
    ->setType('string')
    ->setVisibility('public', 'private'); // public pentru citire, private pentru scriere

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

echo $class;

Generează:

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

    protected(set) int $id;
}

Spațiu de nume

Clasele, proprietățile, interfețele și enumurile (denumite în continuare clase) pot fi grupate în spații de nume reprezentate de clasa PhpNamespace:

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

// creăm noi clase în namespace
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');

// sau inserăm o clasă existentă în namespace
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);

Dacă clasa există deja, se aruncă o excepție.

Puteți defini clauze 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');

Pentru a simplifica numele complet calificat al unei clase, funcții sau constante conform aliasurilor 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', datorită use-statement-ului definit

Numele simplificat al unei clase, funcții sau constante poate fi, dimpotrivă, convertit în numele complet calificat folosind metoda resolveName:

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

Traduceri de nume de clase

Când o clasă face parte dintr-un spațiu de nume, este redată ușor diferit: toate tipurile (de exemplu, typehint-uri, tipuri de returnare, numele clasei părinte, interfețele implementate, proprietățile și atributele utilizate) sunt automat traduse (dacă nu dezactivați acest lucru, vezi mai jos). Aceasta înseamnă că trebuie să utilizați nume complete de clase în definiții și acestea vor fi înlocuite cu aliasuri (conform clauzelor use) 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') // va fi simplificat la A
	->addTrait('Bar\AliasedClass'); // va fi simplificat la AliasedClass

$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // în comentarii simplificăm manual
$method->addParameter('arg')
	->setType('Bar\OtherClass'); // va fi tradus la \Bar\OtherClass

echo $namespace;

// sau folosiți PsrPrinter pentru 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)
	{
	}
}

Traducerea 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 folosiți PsrPrinter pentru 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 este posibil să adăugați niciun alt cod în fișiere în afara funcțiilor și claselor.

Generare conform celor existente

Pe lângă faptul că puteți modela clase și funcții folosind API-ul descris mai sus, le puteți lăsa să fie generate automat conform modelelor existente:

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

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

// creează o closure conform celei indicate
$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 acest mod (necesită instalarea pachetului nikic/php-parser):

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

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

Încărcare din fișiere PHP

Funcțiile, clasele, interfețele și enumurile pot fi încărcate și direct dintr-un șir care conține cod PHP. De exemplu, astfel creăm un obiect ClassType:

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

	class Demo
	{
		public $foo;
	}
	XX);

La încărcarea claselor din cod PHP, comentariile pe o singură linie din afara corpurilor metodelor sunt ignorate (de exemplu, la proprietăți etc.), deoarece această bibliotecă nu are API pentru lucrul cu ele.

Puteți, de asemenea, încărca direct un întreg fișier PHP, care poate conține orice număr de clase, funcții sau chiar spații de nume:

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

Se încarcă și comentariul introductiv al fișierului și declarația strict_types. În schimb, tot restul codului global este ignorat.

Este necesar să fie instalat nikic/php-parser.

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

Class Manipulator

Clasa ClassManipulator oferă instrumente pentru manipularea claselor.

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

Metoda inheritMethod() copiază o metodă din clasa părinte sau interfața implementată în clasa dvs. Acest lucru vă permite să suprascrieți metoda sau să extindeți semnătura sa:

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

Metoda `inheritProperty()` copiază o proprietate din clasa părinte în clasa dvs. Este util atunci când doriți să aveți aceeași proprietate în clasa dvs., dar poate cu o valoare implicită diferită:

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

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

$manipulator->implement(SomeInterface::class);
// Acum clasa dvs. implementează SomeInterface și conține toate metodele sale

Afișarea variabilelor

Clasa Dumper convertește o variabilă în cod PHP parsabil. Oferă o ieșire mai bună și mai clară decât funcția standard var_export().

$dumper = new Nette\PhpGenerator\Dumper;

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

echo $dumper->dump($var); // afișează ['a', 'b', 123]

Tabelul de compatibilitate

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

versiune: 4.0