Generator de cod PHP
- 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.