Générateur de code PHP
- Prend en charge toutes les dernières fonctionnalités de PHP (comme les property hooks, les enums, les attributs, etc.)
- Vous permet de modifier facilement les classes existantes
- Le code de sortie est conforme au style de codage PSR-12 / PER
- Bibliothèque mature, stable et largement utilisée
Installation
Vous pouvez télécharger et installer la bibliothèque à l'aide de Composer :
composer require nette/php-generator
La compatibilité avec PHP se trouve dans le tableau.
Classes
Commençons directement par un exemple de création de classe à l'aide de ClassType :
$class = new Nette\PhpGenerator\ClassType('Demo');
$class
->setFinal()
->setExtends(ParentClass::class)
->addImplement(Countable::class)
->addComment("Description de la classe.\nDeuxième ligne\n")
->addComment('@property-read Nette\Forms\Form $form');
// générez simplement le code en le castant en chaîne ou en utilisant echo :
echo $class;
Retourne le résultat suivant :
/**
* Description de la classe
* Deuxième ligne
*
* @property-read Nette\Forms\Form $form
*/
final class Demo extends ParentClass implements Countable
{
}
Pour générer le code, nous pouvons également utiliser ce qu'on appelle un printer, que nous pourrons configurer davantage contrairement à echo $class
:
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class);
Nous pouvons ajouter des constantes (classe Constant) et des variables (classe Property) :
$class->addConstant('ID', 123)
->setProtected() // visibilité des constantes
->setType('int')
->setFinal();
$class->addProperty('items', [1, 2, 3])
->setPrivate() // ou setVisibility('private')
->setStatic()
->addComment('@var int[]');
$class->addProperty('list')
->setType('?array')
->setInitialized(); // écrit '= null'
Génère :
final protected const int ID = 123;
/** @var int[] */
private static $items = [1, 2, 3];
public ?array $list = null;
Et nous pouvons ajouter des méthodes :
$method = $class->addMethod('count')
->addComment('Comptez-le.')
->setFinal()
->setProtected()
->setReturnType('?int') // types de retour pour les méthodes
->setBody('return count($items ?: $this->items);');
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
Le résultat est :
/**
* Comptez-le.
*/
final protected function count(array &$items = []): ?int
{
return count($items ?: $this->items);
}
Les paramètres promus introduits en PHP 8.0 peuvent être passés au constructeur :
$method = $class->addMethod('__construct');
$method->addPromotedParameter('name');
$method->addPromotedParameter('args', [])
->setPrivate();
Le résultat est :
public function __construct(
public $name,
private $args = [],
) {
}
Les propriétés et classes en lecture seule peuvent être marquées à l'aide de la fonction 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 de la classe peuvent être supprimés à l'aide de removeProperty()
, removeConstant()
,
removeMethod()
ou removeParameter()
.
Vous pouvez également ajouter des objets Method
, Property
ou Constant
existants à 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 également cloner des méthodes, propriétés et constantes existantes sous un autre nom à l'aide de
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 :
$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
Les énumérations, introduites par PHP 8.1, peuvent être facilement créées comme ceci : (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 et créer ainsi une énumération “backed” :
$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');
À chaque case, il est possible d'ajouter un commentaire ou des attributs à l'aide de
addComment()
ou addAttribute()
.
Classes anonymes
Nous passons null
comme nom et nous avons 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)
{
}
};
Fonctions globales
Le code des fonctions est généré par la classe GlobalFunction :
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('return $a + $b;');
$function->addParameter('a');
$function->addParameter('b');
echo $function;
// ou utilisez 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;
}
Fonctions anonymes
Le code des fonctions anonymes est généré par 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 utilisez 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;
}
Fonctions fléchées courtes
Vous pouvez également afficher une fonction anonyme courte à l'aide du printer :
$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
Signatures de méthodes et de fonctions
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('Comptez-le.')
->setFinal()
->setProtected()
->setReturnType('?int');
Les paramètres individuels sont représentés par la classe Parameter. Encore une fois, vous pouvez définir toutes les propriétés imaginables :
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
// function count(array &$items = [])
Pour définir les paramètres dits variadiques (ou aussi opérateur splat), utilisez setVariadic()
:
$method = $class->addMethod('count');
$method->setVariadic(true);
$method->addParameter('items');
Génère :
function count(...$items)
{
}
Corps de méthodes et de fonctions
Le corps peut être passé en une seule fois à la méthode setBody()
ou progressivement (ligne par ligne) en
appelant répétitivement 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 placeholders spéciaux pour insérer facilement des variables.
Placeholders simples ?
$str = 'n\'importe quelle chaîne';
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('return substr(?, ?);', [$str, $num]);
echo $function;
Résultat
function foo()
{
return substr('n\'importe quelle chaîne', 3);
}
Placeholder pour variadic ...?
$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 des paramètres nommés pour PHP 8 en utilisant ...?:
$items = ['foo' => 1, 'bar' => true];
$function->setBody('myfunc(...?:);', [$items]);
// myfunc(foo: 1, bar: true);
Le placeholder est échappé à l'aide d'une barre oblique inverse \?
$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;
}
Printer et conformité PSR
Pour générer du code PHP, la classe Printer est utilisée :
$class = new Nette\PhpGenerator\ClassType('Demo');
// ...
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // identique à : echo $class
Il peut générer le code de tous les autres éléments, offre des méthodes comme printFunction()
,
printNamespace()
, etc.
Il existe également la classe PsrPrinter
, dont la sortie est conforme au style de codage PSR-2 / PSR-12 /
PER :
$printer = new Nette\PhpGenerator\PsrPrinter;
echo $printer->printClass($class);
Besoin d'ajuster le comportement sur mesure ? Créez votre propre version en héritant de la classe Printer
. Ces
variables peuvent être reconfigurées :
class MyPrinter extends Nette\PhpGenerator\Printer
{
// longueur de ligne après laquelle un retour à la ligne se produit
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 'use statements' pour les classes, fonctions et constantes
public int $linesBetweenUseTypes = 0;
// position de l'accolade ouvrante pour les fonctions et méthodes
public bool $bracesOnNextLine = true;
// placez un seul paramètre sur une seule ligne, même s'il a un attribut ou est promu
public bool $singleParameterOnOneLine = false;
// omet les espaces de noms qui ne contiennent aucune classe ou fonction
public bool $omitEmptyNamespaces = true;
// séparateur entre la parenthèse droite et le type de retour des fonctions et méthodes
public string $returnTypeColon = ': ';
}
Comment et pourquoi le Printer
standard et le PsrPrinter
diffèrent-ils réellement ? Pourquoi n'y
a-t-il pas qu'un seul printer dans le paquet, à savoir PsrPrinter
?
Le Printer
standard formate le code comme nous le faisons dans tout Nette. Comme Nette a été créé bien avant
PSR, et aussi parce que PSR n'a pas fourni de normes à temps pendant de nombreuses années, mais par exemple seulement plusieurs
années après l'introduction d'une nouvelle fonctionnalité en PHP, il s'est avéré que le standard de codage diffère sur quelques détails mineurs. La
plus grande différence est l'utilisation de tabulations au lieu d'espaces. Nous savons que l'utilisation de tabulations dans nos
projets permet d'ajuster la largeur, ce qui est nécessaire pour les personnes ayant une
déficience visuelle. Un exemple de différence mineure est le placement de l'accolade sur une ligne distincte pour les
fonctions et les méthodes, et ce toujours. La recommandation PSR nous semble illogique et conduit à une réduction de la clarté du code.
Types
Chaque type ou type union/intersection peut être passé sous forme de chaîne, 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'); // ou Type::nullable(Type::Array);
$member->setType('array|string'); // ou 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
À l'aide de Literal
, vous pouvez passer n'importe quel code PHP, par exemple pour les 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 placeholders :
new Literal('substr(?, ?)', [$a, $b]);
// génère par exemple : substr('hello', 5);
Un littéral représentant la création d'un nouvel objet peut être facilement généré à l'aide de la méthode
new
:
Literal::new(Demo::class, [$a, 'foo' => $b]);
// génère par exemple : new Demo(10, foo: 20)
Attributs
Les attributs PHP 8 peuvent être ajoutés à toutes les classes, méthodes, propriétés, constantes, enums, fonctions, closures et paramètres. Il est également possible d'utiliser des literals 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,
) {
}
}
Property Hooks
À l'aide des property hooks (représentés par la classe PropertyHook), vous pouvez définir des opérations get et set pour les propriétés, une fonctionnalité introduite en 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;
Génère :
class Demo
{
public string $firstName {
set(string $value) => strtolower($value);
get {
return ucfirst($this->firstName);
}
}
}
Les propriétés et les property hooks peuvent être abstraits ou finaux :
$class->addProperty('id')
->setType('int')
->addHook('get')
->setAbstract();
$class->addProperty('role')
->setType('string')
->addHook('set', 'strtolower($value)')
->setFinal();
Visibilité asymétrique
PHP 8.4 introduit la visibilité asymétrique pour les propriétés. Vous pouvez définir différents niveaux d'accès pour la lecture et l'écriture.
La visibilité peut être définie soit à l'aide de la méthode setVisibility()
avec deux paramètres, soit à
l'aide de setPublic()
, setProtected()
ou setPrivate()
avec le paramètre mode
,
qui spécifie si la visibilité s'applique à la lecture ou à l'écriture de la propriété. Le mode par défaut est
'get'
.
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addProperty('name')
->setType('string')
->setVisibility('public', 'private'); // public pour la lecture, private pour l'écriture
$class->addProperty('id')
->setType('int')
->setProtected('set'); // protected pour l'écriture
echo $class;
Génère :
class Demo
{
public private(set) string $name;
protected(set) int $id;
}
Espace de noms
Les classes, propriétés, interfaces et énumérations (ci-après dénommées classes) peuvent être regroupées dans des espaces de noms représentés par la classe PhpNamespace :
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
// créons de nouvelles classes dans l'espace de noms
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');
// ou insérons une classe existante dans l'espace de noms
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);
Si la classe existe déjà, une exception est levée.
Vous pouvez définir des clauses 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');
Pour simplifier le nom de classe, de fonction ou de constante pleinement qualifié selon les alias définis, utilisez la
méthode simplifyName
:
echo $namespace->simplifyName('Foo\Bar'); // 'Bar', car 'Foo' est l'espace de noms actuel
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', à cause du use-statement défini
Inversement, vous pouvez convertir le nom simplifié de classe, de fonction ou de constante en nom pleinement qualifié à
l'aide de la méthode resolveName
:
echo $namespace->resolveName('Bar'); // 'Foo\Bar'
echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'
Traductions des noms de classes
Lorsqu'une classe fait partie d'un espace de noms, elle est rendue légèrement différemment : tous les types (par exemple, les typehints, les types de retour, le nom de la classe parente, les interfaces implémentées, les propriétés et attributs utilisés) sont automatiquement traduits (sauf si vous le désactivez, voir ci-dessous). Cela signifie que vous devez utiliser les noms de classes complets dans les définitions et ils seront remplacés par des alias (selon les clauses use) ou par 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') // sera simplifié en A
->addTrait('Bar\AliasedClass'); // sera simplifié en AliasedClass
$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // dans les commentaires, nous simplifions manuellement
$method->addParameter('arg')
->setType('Bar\OtherClass'); // sera traduit en \Bar\OtherClass
echo $namespace;
// ou utilisez 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 traduction automatique peut être désactivée de cette manière :
$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 dans des fichiers PHP représentés par la classe PhpFile :
$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('Ce fichier est auto-généré.');
$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 utilisez PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);
Résultat :
<?php
/**
* Ce fichier est auto-généré.
*/
declare(strict_types=1);
namespace Foo;
class A
{
}
function foo()
{
}
Attention : Il n'est pas possible d'ajouter d'autre code aux fichiers en dehors des fonctions et des classes.
Génération basée sur l'existant
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 à partir de modèles existants :
// crée une classe identique à la classe PDO
$class = Nette\PhpGenerator\ClassType::from(PDO::class);
// crée une fonction identique à la fonction trim()
$function = Nette\PhpGenerator\GlobalFunction::from('trim');
// crée une closure basée sur celle fournie
$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 également les charger, utilisez cette
méthode (nécessite l'installation du paquet nikic/php-parser
) :
$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true);
$function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);
Chargement depuis des fichiers PHP
Vous pouvez également charger des fonctions, classes, interfaces et enums directement à partir d'une chaîne contenant du
code PHP. Par exemple, voici comment créer un objet ClassType
:
$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX
<?php
class Demo
{
public $foo;
}
XX);
Lors du chargement de classes à partir de code PHP, les commentaires sur 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 n'a pas d'API pour les gérer.
Vous pouvez également charger directement un fichier PHP entier, qui peut contenir n'importe quel nombre de classes, de fonctions ou même d'espaces de noms :
$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));
Le commentaire d'introduction du fichier et la déclaration strict_types
sont également chargés. En revanche,
tout autre code global est ignoré.
Il est nécessaire que nikic/php-parser
soit installé.
Si vous avez besoin de manipuler du code global dans des fichiers ou des instructions individuelles dans les corps
de méthodes, il est préférable d'utiliser directement la bibliothèque nikic/php-parser
.
Class Manipulator
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 de la classe parente ou de l'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é de la classe parente dans votre classe. C'est utile lorsque vous souhaitez avoir la même propriété dans votre classe, mais peut-être avec une valeur par défaut différente :
```php
$property = $manipulator->inheritProperty('foo');
$property->setValue('nouvelle valeur');
La méthode implement()
implémente automatiquement toutes les méthodes et propriétés de l'interface ou de la
classe abstraite donnée dans votre classe :
$manipulator->implement(SomeInterface::class);
// Maintenant, votre classe implémente SomeInterface et contient toutes ses méthodes
Affichage des variables
La classe Dumper
convertit une variable en code PHP analysable. Elle fournit une sortie meilleure et plus claire
que la fonction standard var_export()
.
$dumper = new Nette\PhpGenerator\Dumper;
$var = ['a', 'b', 123];
echo $dumper->dump($var); // affiche ['a', 'b', 123]
Tableau de compatibilité
PhpGenerator 4.1 est compatible avec PHP 8.0 à 8.4.