Generatore di codice PHP
- Supporta tutte le ultime novità di PHP (come property hooks, enum, attributi, ecc.)
- Ti consente di modificare facilmente le classi esistenti
- Il codice di output è conforme allo stile di codifica PSR-12 / PER
- Libreria matura, stabile e ampiamente utilizzata
Installazione
Scarica e installa la libreria utilizzando Composer:
composer require nette/php-generator
La compatibilità con PHP è disponibile nella tabella.
Classi
Iniziamo subito con un esempio di creazione di una classe utilizzando ClassType:
$class = new Nette\PhpGenerator\ClassType('Demo');
$class
->setFinal()
->setExtends(ParentClass::class)
->addImplement(Countable::class)
->addComment("Descrizione della classe.\nSeconda riga\n")
->addComment('@property-read Nette\Forms\Form $form');
// genera semplicemente il codice convertendolo in stringa o usando echo:
echo $class;
Restituisce il seguente risultato:
/**
* Descrizione della classe
* Seconda riga
*
* @property-read Nette\Forms\Form $form
*/
final class Demo extends ParentClass implements Countable
{
}
Possiamo anche utilizzare il cosiddetto printer per generare il codice, che a differenza di echo $class
potremo configurare ulteriormente:
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class);
Possiamo aggiungere costanti (classe Constant) e variabili (classe Property):
$class->addConstant('ID', 123)
->setProtected() // visibilità delle costanti
->setType('int')
->setFinal();
$class->addProperty('items', [1, 2, 3])
->setPrivate() // o setVisibility('private')
->setStatic()
->addComment('@var int[]');
$class->addProperty('list')
->setType('?array')
->setInitialized(); // scrive '= null'
Genera:
final protected const int ID = 123;
/** @var int[] */
private static $items = [1, 2, 3];
public ?array $list = null;
E possiamo aggiungere metodi:
$method = $class->addMethod('count')
->addComment('Count it.')
->setFinal()
->setProtected()
->setReturnType('?int') // tipi di ritorno nei metodi
->setBody('return count($items ?: $this->items);');
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
Il risultato è:
/**
* Count it.
*/
final protected function count(array &$items = []): ?int
{
return count($items ?: $this->items);
}
I parametri promossi introdotti da PHP 8.0 possono essere passati al costruttore:
$method = $class->addMethod('__construct');
$method->addPromotedParameter('name');
$method->addPromotedParameter('args', [])
->setPrivate();
Il risultato è:
public function __construct(
public $name,
private $args = [],
) {
}
Le proprietà e le classi di sola lettura possono essere contrassegnate utilizzando la funzione setReadOnly()
.
Se la proprietà, la costante, il metodo o il parametro aggiunto esistono già, viene lanciata un'eccezione.
I membri della classe possono essere rimossi utilizzando removeProperty()
, removeConstant()
,
removeMethod()
o removeParameter()
.
Puoi anche aggiungere oggetti Method
, Property
o Constant
esistenti alla classe:
$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);
Puoi anche clonare metodi, proprietà e costanti esistenti con un nome diverso utilizzando cloneWithName()
:
$methodCount = $class->getMethod('count');
$methodRecount = $methodCount->cloneWithName('recount');
$class->addMember($methodRecount);
Interfaccia o trait
Puoi creare interfacce e trait (classi InterfaceType e TraitType):
$interface = new Nette\PhpGenerator\InterfaceType('MyInterface');
$trait = new Nette\PhpGenerator\TraitType('MyTrait');
Utilizzo dei trait:
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addTrait('SmartObject');
$class->addTrait('MyTrait')
->addResolution('sayHello as protected')
->addComment('@use MyTrait<Foo>');
echo $class;
Risultato:
class Demo
{
use SmartObject;
/** @use MyTrait<Foo> */
use MyTrait {
sayHello as protected;
}
}
Enum
Puoi facilmente creare le enumerazioni introdotte da PHP 8.1 in questo modo: (classe EnumType):
$enum = new Nette\PhpGenerator\EnumType('Suit');
$enum->addCase('Clubs');
$enum->addCase('Diamonds');
$enum->addCase('Hearts');
$enum->addCase('Spades');
echo $enum;
Risultato:
enum Suit
{
case Clubs;
case Diamonds;
case Hearts;
case Spades;
}
Puoi anche definire equivalenti scalari e creare così un'enumerazione “backed”:
$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');
Ad ogni case è possibile aggiungere un commento o attributi utilizzando
addComment()
o addAttribute()
.
Classi anonime
Passiamo null
come nome e abbiamo una classe anonima:
$class = new Nette\PhpGenerator\ClassType(null);
$class->addMethod('__construct')
->addParameter('foo');
echo '$obj = new class ($val) ' . $class . ';';
Risultato:
$obj = new class ($val) {
public function __construct($foo)
{
}
};
Funzioni globali
Il codice delle funzioni viene generato dalla classe GlobalFunction:
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('return $a + $b;');
$function->addParameter('a');
$function->addParameter('b');
echo $function;
// oppure usa PsrPrinter per l'output conforme a PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);
Risultato:
function foo($a, $b)
{
return $a + $b;
}
Funzioni anonime
Il codice delle funzioni anonime viene generato dalla classe Closure:
$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('return $a + $b;');
$closure->addParameter('a');
$closure->addParameter('b');
$closure->addUse('c')
->setReference();
echo $closure;
// oppure usa PsrPrinter per l'output conforme a PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);
Risultato:
function ($a, $b) use (&$c) {
return $a + $b;
}
Funzioni freccia abbreviate
Puoi anche stampare una funzione anonima abbreviata utilizzando il printer:
$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('$a + $b');
$closure->addParameter('a');
$closure->addParameter('b');
echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure);
Risultato:
fn($a, $b) => $a + $b
Firme di metodi e funzioni
I metodi sono rappresentati dalla classe Method. Puoi impostare la visibilità, il valore di ritorno, aggiungere commenti, attributi, ecc:
$method = $class->addMethod('count')
->addComment('Count it.')
->setFinal()
->setProtected()
->setReturnType('?int');
I singoli parametri sono rappresentati dalla classe Parameter. Anche qui puoi impostare tutte le proprietà immaginabili:
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
// function count(array &$items = [])
Per definire i cosiddetti parametri variadici (o anche operatore splat) serve setVariadic()
:
$method = $class->addMethod('count');
$method->setVariadic(true);
$method->addParameter('items');
Genera:
function count(...$items)
{
}
Corpi di metodi e funzioni
Il corpo può essere passato tutto in una volta al metodo setBody()
o gradualmente (riga per riga) chiamando
ripetutamente addBody()
:
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('$a = rand(10, 20);');
$function->addBody('return $a;');
echo $function;
Risultato
function foo()
{
$a = rand(10, 20);
return $a;
}
Puoi usare segnaposto speciali per inserire facilmente le variabili.
Semplici segnaposto ?
$str = 'any string';
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('return substr(?, ?);', [$str, $num]);
echo $function;
Risultato
function foo()
{
return substr('any string', 3);
}
Segnaposto per variadic ...?
$items = [1, 2, 3];
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('myfunc(...?);', [$items]);
echo $function;
Risultato:
function foo()
{
myfunc(1, 2, 3);
}
Puoi anche usare parametri nominati per PHP 8 usando ...?:
$items = ['foo' => 1, 'bar' => true];
$function->setBody('myfunc(...?:);', [$items]);
// myfunc(foo: 1, bar: true);
Il segnaposto viene escapato con una barra rovesciata \?
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addParameter('a');
$function->addBody('return $a \? 10 : ?;', [$num]);
echo $function;
Risultato:
function foo($a)
{
return $a ? 10 : 3;
}
Printer e conformità con PSR
Per generare codice PHP serve la classe Printer:
$class = new Nette\PhpGenerator\ClassType('Demo');
// ...
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // lo stesso di: echo $class
Può generare codice per tutti gli altri elementi, offre metodi come printFunction()
,
printNamespace()
, ecc.
È disponibile anche la classe PsrPrinter
, il cui output è conforme allo stile di codifica PSR-2 / PSR-12
/ PER:
$printer = new Nette\PhpGenerator\PsrPrinter;
echo $printer->printClass($class);
Hai bisogno di personalizzare il comportamento? Crea la tua versione ereditando la classe Printer
. È possibile
riconfigurare queste variabili:
class MyPrinter extends Nette\PhpGenerator\Printer
{
// lunghezza della riga dopo la quale avviene l'a capo
public int $wrapLength = 120;
// carattere di indentazione, può essere sostituito da una sequenza di spazi
public string $indentation = "\t";
// numero di righe vuote tra le proprietà
public int $linesBetweenProperties = 0;
// numero di righe vuote tra i metodi
public int $linesBetweenMethods = 2;
// numero di righe vuote tra i gruppi di 'use statements' per classi, funzioni e costanti
public int $linesBetweenUseTypes = 0;
// posizione della parentesi graffa di apertura per funzioni e metodi
public bool $bracesOnNextLine = true;
// posiziona un singolo parametro su una riga, anche se ha un attributo o è supportato
public bool $singleParameterOnOneLine = false;
// omette i namespace che non contengono alcuna classe o funzione
public bool $omitEmptyNamespaces = true;
// separatore tra la parentesi destra e il tipo di ritorno di funzioni e metodi
public string $returnTypeColon = ': ';
}
Come e perché differiscono effettivamente il Printer
standard e il PsrPrinter
? Perché nel pacchetto
non c'è solo un printer, ovvero PsrPrinter
?
Il Printer
standard formatta il codice come facciamo in tutto Nette. Poiché Nette è nato molto prima di PSR, e
anche perché PSR per molti anni non ha fornito standard in tempo, ma ad esempio solo con diversi anni di ritardo rispetto
all'introduzione di una nuova feature in PHP, è successo che lo standard di codifica differisce in alcuni piccoli dettagli. La
differenza maggiore è solo l'uso di tabulazioni invece di spazi. Sappiamo che utilizzando le tabulazioni nei nostri progetti
consentiamo la personalizzazione della larghezza, che è necessaria per le persone con disabilità
visive. Un esempio di piccola differenza è il posizionamento della parentesi graffa su una riga separata per funzioni e
metodi e sempre. La raccomandazione di PSR ci sembra illogica e porta a una riduzione della leggibilità del
codice.
Tipi
Ogni tipo o tipo union/intersection può essere passato come stringa, puoi anche usare costanti predefinite per i tipi nativi:
use Nette\PhpGenerator\Type;
$member->setType('array'); // o Type::Array;
$member->setType('?array'); // o Type::nullable(Type::Array);
$member->setType('array|string'); // o Type::union(Type::Array, Type::String)
$member->setType('Foo&Bar'); // o Type::intersection(Foo::class, Bar::class)
$member->setType(null); // rimuove il tipo
Lo stesso vale per il metodo setReturnType()
.
Letterali
Con Literal
puoi passare qualsiasi codice PHP, ad esempio per i valori predefiniti di proprietà
o parametri, ecc:
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;
Risultato:
class Demo
{
public $foo = Iterator::SELF_FIRST;
public function bar($id = 1 + 2)
{
}
}
Puoi anche passare parametri a Literal
e farli formattare in codice PHP valido usando segnaposto:
new Literal('substr(?, ?)', [$a, $b]);
// genera ad esempio: substr('hello', 5);
Un letterale che rappresenta la creazione di un nuovo oggetto può essere facilmente generato usando il metodo
new
:
Literal::new(Demo::class, [$a, 'foo' => $b]);
// genera ad esempio: new Demo(10, foo: 20)
Attributi
Puoi aggiungere attributi PHP 8 a tutte le classi, metodi, proprietà, costanti, enum, funzioni, closure e parametri. Come valori dei parametri si possono usare anche letterali.
$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;
Risultato:
#[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
Con i property hooks (rappresentati dalla classe PropertyHook) puoi definire operazioni get e set per le proprietà, una funzione introdotta in 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;
Genera:
class Demo
{
public string $firstName {
set(string $value) => strtolower($value);
get {
return ucfirst($this->firstName);
}
}
}
Le proprietà e i property hooks possono essere astratti o finali:
$class->addProperty('id')
->setType('int')
->addHook('get')
->setAbstract();
$class->addProperty('role')
->setType('string')
->addHook('set', 'strtolower($value)')
->setFinal();
Visibilità asimmetrica
PHP 8.4 introduce la visibilità asimmetrica per le proprietà. Puoi impostare diversi livelli di accesso per la lettura e la scrittura.
La visibilità può essere impostata sia con il metodo setVisibility()
con due parametri, sia con
setPublic()
, setProtected()
o setPrivate()
con il parametro mode
, che
specifica se la visibilità si applica alla lettura o alla scrittura della proprietà. La modalità predefinita è
'get'
.
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addProperty('name')
->setType('string')
->setVisibility('public', 'private'); // public per la lettura, private per la scrittura
$class->addProperty('id')
->setType('int')
->setProtected('set'); // protected per la scrittura
echo $class;
Genera:
class Demo
{
public private(set) string $name;
protected(set) int $id;
}
Namespace
Classi, proprietà, interfacce ed enum (di seguito classi) possono essere raggruppate in namespace rappresentati dalla classe PhpNamespace:
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
// creiamo nuove classi nel namespace
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');
// oppure inseriamo una classe esistente nel namespace
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);
Se la classe esiste già, viene lanciata un'eccezione.
Puoi definire clausole 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');
Per semplificare il nome completo di una classe, funzione o costante secondo gli alias definiti, usa il metodo
simplifyName
:
echo $namespace->simplifyName('Foo\Bar'); // 'Bar', perché 'Foo' è il namespace corrente
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', a causa della use-statement definita
Al contrario, puoi convertire il nome semplificato di una classe, funzione o costante nel nome completo usando il metodo
resolveName
:
echo $namespace->resolveName('Bar'); // 'Foo\Bar'
echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'
Traduzioni dei nomi delle classi
Quando una classe fa parte di un namespace, viene renderizzata leggermente diversamente: tutti i tipi (ad esempio typehint, tipi di ritorno, nome della classe genitore, interfacce implementate, proprietà usate e attributi) vengono automaticamente tradotti (a meno che non lo disabiliti, vedi sotto). Ciò significa che devi usare i nomi completi delle classi nelle definizioni e questi verranno sostituiti da alias (secondo le clausole use) o da nomi completi nel codice risultante:
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
$namespace->addUse('Bar\AliasedClass');
$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // sarà semplificato in A
->addTrait('Bar\AliasedClass'); // sarà semplificato in AliasedClass
$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // nei commenti semplifichiamo manualmente
$method->addParameter('arg')
->setType('Bar\OtherClass'); // sarà tradotto in \Bar\OtherClass
echo $namespace;
// oppure usa PsrPrinter per l'output conforme a PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);
Risultato:
namespace Foo;
use Bar\AliasedClass;
class Demo implements A
{
use AliasedClass;
/**
* @return D
*/
public function method(\Bar\OtherClass $arg)
{
}
}
La traduzione automatica può essere disabilitata in questo modo:
$printer = new Nette\PhpGenerator\Printer; // o PsrPrinter
$printer->setTypeResolving(false);
echo $printer->printNamespace($namespace);
File PHP
Classi, funzioni e namespace possono essere raggruppati in file PHP rappresentati dalla classe PhpFile:
$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('This file is auto-generated.');
$file->setStrictTypes(); // aggiunge declare(strict_types=1)
$class = $file->addClass('Foo\A');
$function = $file->addFunction('Foo\foo');
// oppure
// $namespace = $file->addNamespace('Foo');
// $class = $namespace->addClass('A');
// $function = $namespace->addFunction('foo');
echo $file;
// oppure usa PsrPrinter per l'output conforme a PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);
Risultato:
<?php
/**
* This file is auto-generated.
*/
declare(strict_types=1);
namespace Foo;
class A
{
}
function foo()
{
}
Attenzione: Non è possibile aggiungere alcun altro codice ai file al di fuori di funzioni e classi.
Generazione basata su esistenti
Oltre a poter modellare classi e funzioni con l'API sopra descritta, puoi anche farle generare automaticamente basandoti su pattern esistenti:
// crea una classe uguale alla classe PDO
$class = Nette\PhpGenerator\ClassType::from(PDO::class);
// crea una funzione identica alla funzione trim()
$function = Nette\PhpGenerator\GlobalFunction::from('trim');
// crea una closure basata su quella fornita
$closure = Nette\PhpGenerator\Closure::from(
function (stdClass $a, $b = null) {},
);
I corpi delle funzioni e dei metodi sono vuoti per impostazione predefinita. Se vuoi caricarli anche tu, usa questo metodo
(richiede l'installazione del pacchetto nikic/php-parser
):
$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true);
$function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);
Caricamento da file PHP
Puoi caricare funzioni, classi, interfacce ed enum anche direttamente da una stringa contenente codice PHP. Ad esempio, in
questo modo creiamo un oggetto ClassType
:
$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX
<?php
class Demo
{
public $foo;
}
XX);
Quando si caricano classi da codice PHP, i commenti su riga singola al di fuori dei corpi dei metodi vengono ignorati (ad es. per le proprietà, ecc.), poiché questa libreria non ha un'API per lavorarci.
Puoi anche caricare direttamente un intero file PHP, che può contenere un numero qualsiasi di classi, funzioni o persino namespace:
$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));
Vengono caricati anche il commento iniziale del file e la dichiarazione strict_types
. Al contrario, tutto il resto
del codice globale viene ignorato.
È richiesto che sia installato nikic/php-parser
.
Se hai bisogno di manipolare il codice globale nei file o le singole istruzioni nei corpi dei metodi, è meglio
usare direttamente la libreria nikic/php-parser
.
Class Manipulator
La classe ClassManipulator fornisce strumenti per manipolare le classi.
$class = new Nette\PhpGenerator\ClassType('Demo');
$manipulator = new Nette\PhpGenerator\ClassManipulator($class);
Il metodo inheritMethod()
copia un metodo dalla classe genitore o dall'interfaccia implementata nella tua classe.
Ciò ti consente di sovrascrivere il metodo o estendere la sua firma:
$method = $manipulator->inheritMethod('bar');
$method->setBody('...');
```
Il metodo `inheritProperty()` copia una proprietà dalla classe genitore nella tua classe. È utile quando vuoi avere la stessa proprietà nella tua classe, ma magari con un valore predefinito diverso:
```php
$property = $manipulator->inheritProperty('foo');
$property->setValue('new value');
Il metodo implement()
implementa automaticamente tutti i metodi e le proprietà dall'interfaccia o dalla classe
astratta data nella tua classe:
$manipulator->implement(SomeInterface::class);
// Ora la tua classe implementa SomeInterface e contiene tutti i suoi metodi
Stampa di variabili
La classe Dumper
converte una variabile in codice PHP analizzabile. Fornisce un output migliore e più chiaro
rispetto alla funzione standard var_export()
.
$dumper = new Nette\PhpGenerator\Dumper;
$var = ['a', 'b', 123];
echo $dumper->dump($var); // stampa ['a', 'b', 123]
Tabella di compatibilità
PhpGenerator 4.1 è compatibile con PHP 8.0 fino a 8.4.