Générateur de code PHP
- Supporte toutes les dernières fonctionnalités de PHP (comme les enums, etc.)
- Vous permet de modifier facilement les classes existantes
- Sortie conforme au style de codage PSR-12 / PER
- Bibliothèque 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 / PER
// 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 / PER
// 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 setVariadic()
:
$method = $class->addMethod('count');
$method->setVariadic(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 PSR
La classe Printer est utilisée pour générer du code PHP :
$class = new Nette\PhpGenerator\ClassType('Demo');
// ...
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // same as : echo $class
Il peut générer du code pour tous les autres éléments, en proposant des méthodes telles que printFunction()
,
printNamespace()
, etc.
En outre, la classe PsrPrinter
est disponible, dont la sortie est conforme au style de codage PSR-2 / PSR-12 /
PER :
$printer = new Nette\PhpGenerator\PsrPrinter;
echo $printer->printClass($class);
Vous souhaitez adapter le comportement de votre imprimante à vos besoins ? Créez votre propre imprimante en héritant de la
classe Printer
. Vous pouvez reconfigurer ces variables :
class MyPrinter extends Nette\PhpGenerator\Printer
{
// longueur de la ligne après laquelle la ligne sera coupée
public int $wrapLength = 120;
// caractère d'indentation, peut être remplacé par une séquence d'espaces
public string $indentation = "\t";
// nombre de lignes vides entre les propriétés
public int $linesBetweenProperties = 0;
// nombre de lignes vides entre les méthodes
public int $linesBetweenMethods = 2;
// nombre de lignes vides entre les groupes de déclarations d'utilisation pour les classes, les fonctions et les constantes
public int $linesBetweenUseTypes = 0;
// position de l'accolade d'ouverture pour les fonctions et les méthodes
public bool $bracesOnNextLine = true;
// placer un seul paramètre sur une seule ligne, même s'il possède un attribut ou est promu
public bool $singleParameterOnOneLine = false;
// omits namespaces that do not contain any class or function
public bool $omitEmptyNamespaces = true;
// séparateur entre la parenthèse droite et le type de retour des fonctions et méthodes
public string $returnTypeColon = ': ';
}
En quoi et pourquoi la norme Printer
et PsrPrinter
diffère-t-elle exactement ? Pourquoi n'y a-t-il
pas qu'une seule imprimante, la PsrPrinter
, dans le paquet ?
La norme Printer
formate le code comme nous le faisons dans l'ensemble de Nette. Comme Nette a été créé bien
avant PSR, et aussi parce que PSR pendant de nombreuses années n'a pas fourni de normes à temps, mais parfois même avec
plusieurs années de retard par rapport à l'introduction d'une nouvelle fonctionnalité dans PHP, cela a entraîné quelques
différences mineures dans la norme de codage. La différence
la plus importante est l'utilisation de tabulations au lieu d'espaces. Nous savons qu'en utilisant des tabulations dans nos
projets, nous permettons d'ajuster la largeur, ce qui est essentiel pour les personnes souffrant de
déficiences visuelles. Un exemple de différence mineure est le placement de l'accolade sur une ligne séparée pour les
fonctions et les méthodes et toujours. Nous considérons que la recommandation du PSR est illogique et qu'elle conduit à une diminution de la clarté du code.
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'); // or Type::nullable(Type::Array);
$member->setType('array|string'); // or Type::union(Type::Array, Type::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);
Le littéral représentant la création d'un nouvel objet est facilement généré par la méthode new
:
Literal::new(Demo::class, [$a, 'foo' => $b]);
// génère, par exemple: new Demo(10, foo: 20)
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('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;
Résultat :
#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])]
class Demo
{
#[Deprecated]
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
Lorsqu'une classe fait partie d'un espace de noms, son rendu est légèrement différent: tous les types (par exemple, les indications de type, les types de retour, le nom de la classe mère, les interfaces implémentées, les traits utilisés et les attributs) sont automatiquement résolus (sauf si vous le désactivez, voir ci-dessous). Cela signifie que vous devez utiliser des noms de classe pleinement qualifiés dans les définitions, et qu'ils seront remplacés par des alias (basés sur des clauses 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 / PER
// 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 / PER
// 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()
{
}
**Remarque : aucun code supplémentaire ne peut être ajouté aux fichiers en dehors des fonctions et des classes.
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
.
Manipulateur de classe
La classe ClassManipulator fournit des outils pour manipuler les classes.
$class = new Nette\PhpGenerator\ClassType('Demo');
$manipulator = new Nette\PhpGenerator\ClassManipulator($class);
La méthode inheritMethod()
copie une méthode d'une classe parente ou d'une interface implémentée dans votre
classe. Cela vous permet de remplacer la méthode ou d'étendre sa signature :
$method = $manipulator->inheritMethod('bar');
$method->setBody('...');
La méthode inheritProperty()
copie une propriété d'une classe parente dans votre classe. Cette méthode est
utile lorsque vous souhaitez avoir la même propriété dans votre classe, mais éventuellement avec une valeur par défaut
différente :
$property = $manipulator->inheritProperty('foo');
$property->setValue('new value');
La méthode implementInterface()
implémente automatiquement toutes les méthodes de l'interface donnée dans
votre classe :
$manipulator->implementInterface(SomeInterface::class);
// Maintenant, votre classe implémente SomeInterface et inclut toutes ses méthodes
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 et 4.1 sont compatibles avec PHP 8.0 à 8.3