Generatore di codice PHP
- Supporta tutte le più recenti caratteristiche di PHP (come gli agganci per le proprietà, gli enum, gli attributi e così via).
- Permette di modificare facilmente le classi esistenti
- Uscita conforme allo stile di codifica PSR-12 / PER
- Libreria matura, stabile e ampiamente utilizzata
Installazione
Scaricare e installare il pacchetto utilizzando Composer:
composer require nette/php-generator
Per la compatibilità con PHP, vedere la tabella.
Classi
Cominciamo con un esempio semplice di generazione di classi usando 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');
// per generare codice PHP è sufficiente eseguire il cast in stringa o usare echo:
echo $class;
Il risultato sarà questo:
/**
* Description of class.
* Second line
*
* @property-read Nette\Forms\Form $form
*/
final class Demo extends ParentClass implements Countable
{
}
Possiamo anche utilizzare una stampante 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 proprietà (classe Property):
$class->addConstant('ID', 123)
->setProtected() // visiblità costante
->setType('int')
->setFinal();
$class->addProperty('items', [1, 2, 3])
->setPrivate() // o setVisibility('private')
->setStatic()
->addComment('@var int[]');
$class->addProperty('list')
->setType('?array')
->setInitialized(); // stampa '= null'
Genera:
finale protetto 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') // metodo tipo di ritorno
->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 tramite setReadOnly()
.
Se la proprietà, la costante, il metodo o il parametro aggiunto esistono già, viene lanciata un'eccezione.
I membri possono essere rimossi utilizzando removeProperty()
, removeConstant()
,
removeMethod()
o removeParameter()
.
È anche possibile aggiungere alla classe oggetti esistenti Method
, Property
o
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);
È possibile 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 tratto
È possibile creare interfacce e tratti (classi InterfaceType e TraitType):
$interface = new Nette\PhpGenerator\InterfaceType('MyInterface');
$trait = new Nette\PhpGenerator\TraitType('MyTrait');
Utilizzo dei tratti:
$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
È possibile creare facilmente gli enum introdotti da PHP 8.1 (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;
}
È possibile definire anche equivalenti scalari per i casi, in modo da creare un'enum supportata:
$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');
È possibile aggiungere un commento o degli attributi a ciascun caso utilizzando
addComment()
o addAttribute()
.
Classe anonima
Date il nome null
e avrete 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)
{
}
};
Funzione globale
Il codice delle funzioni genererà la classe GlobalFunction:
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('return $a + $b;');
$function->addParameter('a');
$function->addParameter('b');
echo $function;
// oppure utilizzare PsrPrinter per ottenere un output conforme a PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);
Risultato:
function foo($a, $b)
{
return $a + $b;
}
Chiusura
Il codice delle chiusure genererà la 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 utilizzare PsrPrinter per un output conforme a PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);
Risultato:
function ($a, $b) use (&$c) {
return $a + $b;
}
Funzione freccia
È inoltre possibile stampare la chiusura come funzione freccia utilizzando la stampante:
$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
Firma di metodi e funzioni
I metodi sono rappresentati dalla classe Metodo. È possibile impostare la visibilità, il valore di ritorno, aggiungere commenti, attributi ecc:
$method = $class->addMethod('count')
->addComment('Count it.')
->setFinal()
->setProtected()
->setReturnType('?int');
Ogni parametro è rappresentato da una classe Parametro. Anche in questo caso, si possono impostare tutte le proprietà possibili:
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
// function count(&$items = [])
Per definire i cosiddetti parametri variadici (o anche gli operatori splat, spread, ellipsis, unpacking o tre punti),
utilizzare setVariadic()
:
$method = $class->addMethod('count');
$method->setVariadic(true);
$method->addParameter('items');
Genera:
function count(...$items)
{
}
Metodo e corpo della funzione
Il corpo può essere passato al metodo setBody()
in una sola volta o in sequenza (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;
}
È possibile utilizzare segnaposto speciali per iniettare variabili in modo pratico.
Segnaposto semplici ?
$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 variabile ...?
$items = [1, 2, 3];
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('myfunc(...?);', [$items]);
echo $function;
Risultato:
function foo()
{
myfunc(1, 2, 3);
}
È anche possibile utilizzare i parametri denominati di PHP 8 utilizzando il segnaposto ...?:
$items = ['foo' => 1, 'bar' => true];
$function->setBody('myfunc(...?:);', [$items]);
// myfunc(foo: 1, bar: true);
Sfuggire al segnaposto usando una barra \?
$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;
}
Stampanti e conformità PSR
La classe Printer viene utilizzata per generare codice PHP:
$class = new Nette\PhpGenerator\ClassType('Demo');
// ...
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // come: echo $class
Può generare codice per tutti gli altri elementi, offrendo metodi come printFunction()
,
printNamespace()
, ecc.
Inoltre, è disponibile 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);
Avete bisogno di regolare il comportamento in base alle vostre esigenze? Create la vostra stampante ereditando dalla classe
Printer
. È possibile riconfigurare queste variabili:
class MyPrinter extends Nette\PhpGenerator\Printer
{
// lunghezza della riga dopo la quale la riga si interromperà
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 gruppi di dichiarazioni d'uso per classi, funzioni e costanti
public int $linesBetweenUseTypes = 0;
// posizione della parentesi graffa di apertura per funzioni e metodi
public bool $bracesOnNextLine = true;
// inserire un parametro in una sola riga, anche se ha un attributo o è promosso
public bool $singleParameterOnOneLine = false;
// omits namespaces that do not contain any class or function
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 esattamente le stampanti standard Printer
e PsrPrinter
? Perché non c'è
una sola stampante, PsrPrinter
, nel pacchetto?
Lo standard Printer
formatta il codice come lo facciamo in tutto Nette. Poiché Nette è stato creato molto prima
di PSR, e anche perché PSR per molti anni non ha consegnato gli standard in tempo, ma a volte anche con diversi anni di ritardo
dall'introduzione di una nuova funzionalità in PHP, questo ha portato ad alcune piccole differenze nello standard di codifica. La differenza maggiore è solo l'uso dei
tabulatori al posto degli spazi. Sappiamo che l'uso delle tabulazioni nei nostri progetti consente di regolare la larghezza, il
che è essenziale per le persone con
problemi di vista. Un esempio di differenza minore è il posizionamento della parentesi graffa su una riga separata per
funzioni e metodi e sempre. Riteniamo che la raccomandazione del PSR sia illogica e porti a una diminuzione della chiarezza del
codice.
Tipi
Ogni tipo o tipo di unione/intersezione può essere passato come stringa; si possono anche usare costanti predefinite per i tipi nativi:
use Nette\PhpGenerator\Type;
$member->setType('array'); // o 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'); // o Type::intersection(Foo::class, Bar::class)
$member->setType(null); // rimuove il tipo
Lo stesso vale per il metodo setReturnType()
.
Letterali
Con Literal
è possibile passare codice PHP arbitrario, 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)
{
}
}
È anche possibile passare dei parametri a Literal
e farli formattare in codice PHP valido, utilizzando speciali segnaposto:
new Literal('substr(?, ?)', [$a, $b]);
// genera, ad esempio: substr('ciao', 5);
Il letterale che rappresenta la creazione di un nuovo oggetto è facilmente generabile con il metodo new
:
Literal::new(Demo::class, [$a, 'foo' => $b]);
// genera, ad esempio: nuovo Demo(10, pippo: 20)
Attributi
È possibile aggiungere attributi di PHP 8 a tutte le classi, i metodi, le proprietà, le costanti, i casi enum, le funzioni, le chiusure e i parametri. Anche i letterali possono essere usati come valori dei parametri.
$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,
) {
}
}
Ganci di proprietà
È possibile definire anche degli hook di proprietà (rappresentati dalla classe PropertyHook) per le operazioni di get e set, una caratteristica 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;
Questo genera:
class Demo
{
public string $firstName {
set(string $value) => strtolower($value);
get {
return ucfirst($this->firstName);
}
}
}
Le proprietà e i ganci di proprietà 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à. È possibile impostare diversi livelli di accesso per la lettura e la scrittura.
La visibilità può essere impostata utilizzando il metodo setVisibility()
con due parametri, oppure utilizzando
setPublic()
, setProtected()
, o setPrivate()
con il parametro mode
che
specifica se la visibilità si applica a ottenere o impostare la proprietà. La modalità predefinita è '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;
Questo genera:
class Demo
{
public private(set) string $name;
protected(set) int $id;
}
Spazio dei nomi
Classi, tratti, interfacce ed enum (di seguito classi) possono essere raggruppati in spazi dei nomi (PhpNamespace):
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
// creare nuove classi nello spazio dei nomi
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');
// o inserire una classe esistente nello spazio dei nomi
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);
Se la classe esiste già, viene lanciata un'eccezione.
È possibile definire dichiarazioni d'uso:
// 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 di una classe, di una funzione o di una costante completamente qualificata secondo gli alias
definiti, utilizzare il metodo simplifyName
:
echo $namespace->simplifyName('Foo\Bar'); // 'Bar', perché 'Foo' è lo spazio dei nomi attuale
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', a causa della dichiarazione d'uso definita
Al contrario, è possibile convertire un nome di classe, funzione o costante semplificato in uno pienamente qualificato,
utilizzando il metodo resolveName
:
echo $namespace->resolveName('Bar'); // 'Foo\Bar'
echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'
Risoluzione dei nomi di classe
Quando una classe fa parte di uno spazio dei nomi, viene resa in modo leggermente diverso: tutti i tipi (ad esempio, i suggerimenti sui tipi, i tipi di ritorno, il nome della classe madre, le interfacce implementate, i tratti utilizzati e gli attributi) vengono automaticamente risolti (a meno che non si disattivi, vedere sotto). Ciò significa che è necessario usare nomi di classe **completamente qualificati nelle definizioni, che verranno sostituiti con alias (basati su clausole di utilizzo) o nomi pienamente qualificati nel codice risultante:
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
$namespace->addUse('Bar\AliasedClass');
$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // si semplificherà in A
->addTrait('Bar\AliasedClass'); // semplificherà in AliasedClass
$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // nei commenti semplifica manualmente
$method->addParameter('arg')
->setType('Bar\OtherClass'); // si risolverà in \Bar\OtherClass
echo $namespace;
// o usare PsrPrinter per un 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)
{
}
}
In questo modo è possibile disattivare la risoluzione automatica:
$printer = new Nette\PhpGenerator\Printer; // o PsrPrinter
$printer->setTypeResolving(false);
echo $printer->printNamespace($namespace);
File PHP
Classi, funzioni e spazi dei nomi 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 utilizzare PsrPrinter per un 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()
{
}
**Nota: ** Non è possibile aggiungere altro codice ai file al di fuori delle funzioni e delle classi.
Generare in base a quelli esistenti
Oltre a poter modellare classi e funzioni utilizzando le API descritte in precedenza, è anche possibile generarle automaticamente in base a quelle esistenti:
// crea una classe identica alla classe PDO
$class = Nette\PhpGenerator\ClassType::from(PDO::class);
// crea una funzione identica a trim()
$function = Nette\PhpGenerator\GlobalFunction::from('trim');
// crea una chiusura come specificato
$closure = Nette\PhpGenerator\Closure::from(
function (stdClass $a, $b = null) {},
);
I corpi delle funzioni e dei metodi sono vuoti per impostazione predefinita. Se si desidera caricarli, utilizzare questo
metodo (richiede l'installazione di nikic/php-parser
):
$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true);
$function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);
Caricamento da file PHP
È anche possibile caricare funzioni, classi, interfacce ed enum direttamente da una stringa di codice PHP. Ad esempio, creiamo
l'oggetto ClassType
in questo modo:
$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX
<?php
class Demo
{
public $foo;
}
XX);
Quando si caricano le classi dal codice PHP, i commenti di una sola riga al di fuori dei corpi dei metodi vengono ignorati (ad esempio per le proprietà, ecc.), perché questa libreria non dispone di un'API per gestirli.
È anche possibile caricare direttamente l'intero file PHP, che può contenere un numero qualsiasi di classi, funzioni o anche spazi dei nomi multipli:
$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));
Vengono caricati anche il commento iniziale del file e la dichiarazione strict_types
. D'altra parte, tutto il
resto del codice globale viene ignorato.
Questo richiede l'installazione di nikic/php-parser
.
Se è necessario manipolare il codice globale nei file o le singole dichiarazioni nei corpi dei metodi, è meglio
usare direttamente la libreria nikic/php-parser
.
Manipolatore di classe
La classe ClassManipulator fornisce strumenti per la manipolazione delle classi.
$class = new Nette\PhpGenerator\ClassType('Demo');
$manipulator = new Nette\PhpGenerator\ClassManipulator($class);
Il metodo inheritMethod()
copia un metodo da una classe padre o da un'interfaccia implementata nella propria
classe. Ciò consente di sovrascrivere il metodo o di estenderne la firma:
$method = $manipulator->inheritMethod('bar');
$method->setBody('...');
Il metodo inheritProperty()
copia una proprietà da una classe padre nella vostra classe. È utile quando si vuole
avere la stessa proprietà nella propria classe, ma eventualmente con un valore predefinito diverso:
$property = $manipulator->inheritProperty('foo');
$property->setValue('new value');
Il metodo implement()
implementa automaticamente tutti i metodi e le proprietà dell'interfaccia o della classe
astratta indicata:
$manipolatore->implementa(SomeInterface::class);
// Ora la classe implementa SomeInterface e include tutti i suoi metodi
Dumper di variabili
Dumper restituisce una rappresentazione in stringhe PHP parsabili di una variabile. Fornisce un output migliore e più chiaro
rispetto alla funzione nativa 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 – 8.4.