Generatore di codice PHP

  • Avete bisogno di generare codice PHP per classi, funzioni, file PHP e così via?
  • Supporta tutte le più recenti caratteristiche di PHP, come enum, attributi, ecc.
  • Permette di modificare facilmente le classi esistenti
  • Output conforme a PSR-12 / PER coding style
  • Libreria altamente 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 setVariadics():

$method = $class->addMethod('count');
$method->setVariadics(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;
	// 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|string'); // o Type::union('array', 'stringa')
$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);

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('Deprecated');

$class->addProperty('list')
	->addAttribute('WithArguments', [1, 2]);

$method = $class->addMethod('count')
	->addAttribute('Foo\Cached', ['mode' => true]);

$method->addParameter('items')
	->addAttribute('Bar');

echo $class;

Risultato:

#[Deprecated]
class Demo
{
	#[WithArguments(1, 2)]
	public $list;


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

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 la classe fa parte dello spazio dei nomi, viene resa in modo leggermente diverso: tutti i tipi (cioè i suggerimenti sui tipi, i tipi di ritorno, il nome della classe genitore, interfacce implementate, tratti e attributi utilizzati) sono automaticamente risolti (a meno che non lo si disattivi, vedere sotto). Ciò significa che bisogna usare i nomi completi delle classi nelle definizioni, che saranno sostituiti da alias (secondo le dichiarazioni d'uso) o da 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()
{
}

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.

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.0 è compatibile con PHP 8.0 – 8.2

versione: 4.0