Générateur de code PHP
- Vous avez besoin de générer du code PHP pour des classes, des fonctions, des fichiers PHP, etc.
- Supporte toutes les dernières fonctionnalités de PHP comme les enums, les attributs, etc.
- Vous permet de modifier facilement les classes existantes
- Sortie conforme à PSR-12
- Bibliothèque très mature, stable et largement utilisée.
Installation
Téléchargez et installez le paquet en utilisant Composer:
composer require nette/php-generator
Pour la compatibilité avec PHP, voir le tableau.
Classes
Commençons par un exemple simple de génération de classe à l'aide de 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');
// pour générer du code PHP, il suffit de le convertir en chaîne ou d'utiliser echo:
echo $class;
Cela donnera le résultat suivant :
/**
* Description of class.
* Second line
*
* @property-read Nette\Forms\Form $form
*/
final class Demo extends ParentClass implements Countable
{
}
Nous pouvons également utiliser une imprimante pour générer le code, qui, contrairement à echo $class
, pourra
être configuré ultérieurement:
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class);
Nous pouvons ajouter des constantes (classe Constant) et des propriétés (classe Property) :
$class->addConstant('ID', 123)
->setProtected() // visibilité constante
->setType('int')
->setFinal();
$class->addProperty('items', [1, 2, 3])
->setPrivate() // ou setVisibility('private')
->setStatic()
->addComment('@var int[]');
$class->addProperty('list')
->setType('?array')
->setInitialized(); // imprime '= null'.
Il génère :
final protected const int ID = 123 ;
/** @var int[] */
private static $items = [1, 2, 3];
public ?array $list = null;
Et on peut ajouter des méthodes:
$method = $class->addMethod('count')
->addComment('Count it.')
->setFinal()
->setProtected()
->setReturnType('?int') // méthode retour type
->setBody('return count($items ?: $this->items);');
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
Il en résulte :
/**
* Count it.
*/
final protected function count(array &$items = []): ?int
{
return count($items ?: $this->items);
}
Les paramètres promus introduits par PHP 8.0 peuvent être passés au constructeur :
$method = $class->addMethod('__construct');
$method->addPromotedParameter('name');
$method->addPromotedParameter('args', [])
->setPrivate();
Il en résulte :
public function __construct(
public $name,
private $args = [],
) {
}
Les propriétés et les classes en lecture seule peuvent être marquées via setReadOnly()
.
Si la propriété, la constante, la méthode ou le paramètre ajouté existe déjà, une exception est levée.
Les membres peuvent être supprimés en utilisant removeProperty()
, removeConstant()
,
removeMethod()
ou removeParameter()
.
Vous pouvez également ajouter des objets existants Method
, Property
ou Constant
à la
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);
Vous pouvez cloner des méthodes, propriétés et constantes existantes avec un nom différent en utilisant
cloneWithName()
:
$methodCount = $class->getMethod('count');
$methodRecount = $methodCount->cloneWithName('recount');
$class->addMember($methodRecount);
Interface ou Trait
Vous pouvez créer des interfaces et des traits (classes InterfaceType et TraitType) :
$interface = new Nette\PhpGenerator\InterfaceType('MyInterface');
$trait = new Nette\PhpGenerator\TraitType('MyTrait');
Utilisation des traits de caractère :
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addTrait('SmartObject');
$class->addTrait('MyTrait')
->addResolution('sayHello as protected')
->addComment('@use MyTrait<Foo>');
echo $class;
Résultat :
class Demo
{
use SmartObject;
/** @use MyTrait<Foo> */
use MyTrait {
sayHello as protected;
}
}
Enums
Vous pouvez facilement créer les enums que PHP 8.1 apporte (classe EnumType) :
$enum = new Nette\PhpGenerator\EnumType('Suit');
$enum->addCase('Clubs');
$enum->addCase('Diamonds');
$enum->addCase('Hearts');
$enum->addCase('Spades');
echo $enum;
Résultat :
enum Suit
{
case Clubs;
case Diamonds;
case Hearts;
case Spades;
}
Vous pouvez également définir des équivalents scalaires pour les cas afin de créer un enum adossé :
$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');
Il est possible d'ajouter un commentaire ou des attributs à chaque cas en utilisant
addComment()
ou addAttribute()
.
Classe anonyme
Donnez le nom null
et vous avez une classe anonyme :
$class = new Nette\PhpGenerator\ClassType(null);
$class->addMethod('__construct')
->addParameter('foo');
echo '$obj = new class ($val) ' . $class . ';';
Résultat :
$obj = new class ($val) {
public function __construct($foo)
{
}
};
Fonction globale
Le code des fonctions va générer la classe GlobalFunction:
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('return $a + $b;');
$function->addParameter('a');
$function->addParameter('b');
echo $function;
// ou utiliser PsrPrinter pour une sortie conforme à PSR-2 / PSR-12
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);
Résultat :
function foo($a, $b)
{
return $a + $b;
}
Fermeture
Le code des fermetures va générer 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;
// ou utiliser PsrPrinter pour une sortie conforme à PSR-2 / PSR-12
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);
Résultat :
function ($a, $b) use (&$c) {
return $a + $b;
}
Fonction flèche
Vous pouvez également imprimer la fermeture comme fonction de flèche en utilisant une imprimante :
$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('$a + $b');
$closure->addParameter('a');
$closure->addParameter('b');
echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure);
Résultat :
fn($a, $b) => $a + $b
Signature de la méthode et de la fonction
Les méthodes sont représentées par la classe Method. Vous pouvez définir la visibilité, la valeur de retour, ajouter des commentaires, des attributs, etc :
$method = $class->addMethod('count')
->addComment('Count it.')
->setFinal()
->setProtected()
->setReturnType('?int');
Chaque paramètre est représenté par une classe Paramètre. Là encore, vous pouvez définir toutes les propriétés imaginables :
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
// function count(&$items = [])
Pour définir les paramètres dits variadiques (ou également l'opérateur splat, spread, ellipsis, unpacking ou trois points),
utilisez setVariadics()
:
$method = $class->addMethod('count');
$method->setVariadics(true);
$method->addParameter('items');
Génère :
function count(...$items)
{
}
Méthode et corps de fonction
Le corps peut être transmis à la méthode setBody()
en une seule fois ou de manière séquentielle (ligne par
ligne) en appelant à plusieurs reprises addBody()
:
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('$a = rand(10, 20);');
$function->addBody('return $a;');
echo $function;
Résultat
function foo()
{
$a = rand(10, 20);
return $a;
}
Vous pouvez utiliser des caractères de remplacement spéciaux pour injecter des variables de manière pratique.
Caractères de remplacement simples ?
$str = 'any string';
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('return substr(?, ?);', [$str, $num]);
echo $function;
Résultat :
function foo()
{
return substr('any string', 3);
}
Variadic placeholder ...?
$items = [1, 2, 3];
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('myfunc(...?);', [$items]);
echo $function;
Résultat :
function foo()
{
myfunc(1, 2, 3);
}
Vous pouvez également utiliser les paramètres nommés de PHP 8 en utilisant des caractères de
substitution. ...?:
$items = ['foo' => 1, 'bar' => true];
$function->setBody('myfunc(...?:);', [$items]);
// myfunc(foo: 1, bar: true);
Échapper au caractère de remplacement avec une barre oblique \?
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addParameter('a');
$function->addBody('return $a \? 10 : ?;', [$num]);
echo $function;
Résultat :
function foo($a)
{
return $a ? 10 : 3;
}
Imprimantes et conformité aux RPS
Le code PHP est généré par les objets Printer
. Il existe un PsrPrinter
dont la sortie est conforme
à PSR-2 et PSR-12 et qui utilise des espaces pour l'indentation, et un Printer
qui utilise des tabulations pour
l'indentation.
$class = new Nette\PhpGenerator\ClassType('Demo');
// ...
$printer = new Nette\PhpGenerator\PsrPrinter;
echo $printer->printClass($class); // Retrait de 4 espaces
Vous avez besoin de personnaliser le comportement de l'imprimante ? Créez le vôtre en héritant de la classe
Printer
. Vous pouvez reconfigurer ces variables :
class MyPrinter extends Nette\PhpGenerator\Printer
{
public int $wrapLength = 120;
public string $indentation = "\t";
public int $linesBetweenProperties = 0;
public int $linesBetweenMethods = 2;
public int $linesBetweenUseTypes = 0;
public bool $bracesOnNextLine = true;
public string $returnTypeColon = ': ';
}
Types
Chaque type ou type d'union/intersection peut être passé comme une chaîne de caractères, vous pouvez également utiliser des constantes prédéfinies pour les types natifs :
use Nette\PhpGenerator\Type;
$member->setType('array'); // ou Type::Array;
$member->setType('array|string'); // ou Type::union('array', 'string')
$member->setType('Foo&Bar'); // ou Type::intersection(Foo::class, Bar::class)
$member->setType(null); // supprime le type
Il en va de même pour la méthode setReturnType()
.
Littéraux
Avec Literal
, vous pouvez transmettre un code PHP arbitraire, par exemple, aux valeurs par défaut des
propriétés ou des paramètres, 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;
Résultat :
class Demo
{
public $foo = Iterator::SELF_FIRST;
public function bar($id = 1 + 2)
{
}
}
Vous pouvez également passer des paramètres à Literal
et les faire formater en code PHP valide à l'aide de caractères de remplacement spéciaux:
new Literal('substr(?, ?)', [$a, $b]);
// génère, par exemple: substr('hello', 5);
Attributs
Vous pouvez ajouter des attributs PHP 8 à toutes les classes, méthodes, propriétés, constantes, cas d'enum, fonctions, fermetures et paramètres. Les littéraux peuvent aussi être utilisés comme valeurs de paramètres.
$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;
Résultat :
#[Deprecated]
class Demo
{
#[WithArguments(1, 2)]
public $list;
#[Foo\Cached(mode: true)]
public function count(#[Bar] $items)
{
}
}
Espace de nommage
Les classes, traits, interfaces et enums (ci-après dénommés “classes”) peuvent être regroupés en espaces de noms (PhpNamespace) :
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
// créer de nouvelles classes dans l'espace de noms
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');
// ou insérer une classe existante dans l'espace de noms
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);
Si la classe existe déjà, elle lève une exception.
Vous pouvez définir des déclarations d'utilisation :
// 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');
Pour simplifier un nom de classe, de fonction ou de constante entièrement qualifié en fonction des alias définis, utilisez
la méthode simplifyName
:
echo $namespace->simplifyName('Foo\Bar'); // 'Bar', car 'Foo' est l'espace de nom courant
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', à cause de l'énoncé d'utilisation défini
Inversement, vous pouvez convertir un nom de classe, de fonction ou de constante simplifié en un nom pleinement qualifié en
utilisant la méthode resolveName
:
echo $namespace->resolveName('Bar'); // 'Foo\Bar'
echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'
Résolution des noms de classe
Lorsque la classe fait partie de l'espace de nom, elle est rendue de manière légèrement différente : tous les types (c'est-à-dire les indications de type, les types de retour, le nom de la classe parente, interfaces implémentées, traits et attributs utilisés) sont automatiquement résolus (sauf si vous le désactivez, voir ci-dessous). Cela signifie que vous devez utiliser les noms de classe complets dans les définitions et qu'ils seront remplacés par des alias (selon les déclarations d'utilisation) ou des noms pleinement qualifiés dans le code résultant :
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
$namespace->addUse('Bar\AliasedClass');
$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // il sera simplifié en A
->addTrait('Bar\AliasedClass'); // il sera simplifié en AliasedClass
$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')) ; // dans les commentaires simplifier manuellement
$method->addParameter('arg')
->setType('Bar\OtherClass'); // elle sera résolue en \Bar\OtherClass
echo $namespace;
// ou utiliser PsrPrinter pour une sortie conforme à PSR-2 / PSR-12
// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);
Résultat :
namespace Foo;
use Bar\AliasedClass;
class Demo implements A
{
use AliasedClass;
/**
* @return D
*/
public function method(\Bar\OtherClass $arg)
{
}
}
La résolution automatique peut être désactivée de cette façon :
$printer = new Nette\PhpGenerator\Printer; // ou PsrPrinter
$printer->setTypeResolving(false);
echo $printer->printNamespace($namespace);
Fichiers PHP
Les classes, fonctions et espaces de noms peuvent être regroupés en fichiers PHP représentés par la classe PhpFile:
$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('This file is auto-generated.');
$file->setStrictTypes(); // ajoute declare(strict_types=1)
$class = $file->addClass('Foo\A');
$function = $file->addFunction('Foo\foo');
// ou
// $namespace = $file->addNamespace('Foo');
// $class = $namespace->addClass('A');
// $function = $namespace->addFunction('foo');
echo $file;
// ou utiliser PsrPrinter pour une sortie conforme à PSR-2 / PSR-12
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);
Résultat :
<?php
/**
* This file is auto-generated.
*/
declare(strict_types=1);
namespace Foo;
class A
{
}
function foo()
{
}
Générer en fonction de ceux qui existent déjà
En plus de pouvoir modéliser des classes et des fonctions à l'aide de l'API décrite ci-dessus, vous pouvez également les faire générer automatiquement en fonction de celles qui existent déjà :
// crée une classe identique à la classe PDO
$class = Nette\PhpGenerator\ClassType::from(PDO::class);
// crée une fonction identique à trim()
$function = Nette\PhpGenerator\GlobalFunction::from('trim');
// crée une fermeture comme spécifié
$closure = Nette\PhpGenerator\Closure::from(
function (stdClass $a, $b = null) {},
);
Les corps des fonctions et des méthodes sont vides par défaut. Si vous souhaitez les charger également, utilisez cette
méthode (elle nécessite l'installation de nikic/php-parser
) :
$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true);
$function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);
Chargement depuis le fichier PHP
Vous pouvez également charger des fonctions, des classes, des interfaces et des enums directement à partir d'une chaîne de
code PHP. Par exemple, nous créons l'objet ClassType
de cette manière :
$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX
<?php
class Demo
{
public $foo;
}
XX);
Lors du chargement de classes à partir de code PHP, les commentaires d'une seule ligne en dehors du corps des méthodes sont ignorés (par exemple pour les propriétés, etc.) car cette bibliothèque ne dispose pas d'une API pour les gérer.
Vous pouvez également charger directement le fichier PHP entier, qui peut contenir n'importe quel nombre de classes, de fonctions ou même plusieurs espaces de noms :
$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));
Le commentaire initial du fichier et la déclaration strict_types
sont également chargés. En revanche, tout
autre code global est ignoré.
Cela nécessite l'installation de nikic/php-parser
.
Si vous devez manipuler du code global dans des fichiers ou des instructions individuelles dans des corps de
méthodes, il est préférable d'utiliser directement la bibliothèque nikic/php-parser
.
Dumper de variables
La fonction Dumper renvoie la représentation d'une variable sous forme de chaîne PHP. Fournit une sortie meilleure et plus
claire que la fonction native var_export()
.
$dumper = new Nette\PhpGenerator\Dumper;
$var = ['a', 'b', 123];
echo $dumper->dump($var); // imprime ['a', 'b', 123]
Tableau de compatibilité
PhpGenerator 4.0 est compatible avec PHP 8.0 à 8.2.