Generatore di codice PHP

Cerchi uno strumento per generare codice PHP per classi, funzioni o file completi?
  • 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.

versione: 4.0