Générateur de code PHP

Vous cherchez un outil pour générer du code PHP pour des classes, des fonctions ou des fichiers complets ?
  • Prend en charge toutes les dernières fonctionnalités de PHP (comme les crochets de propriété, les enums, les attributs, 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,
	) {
	}
}

Crochets de propriété

Vous pouvez également définir des crochets de propriété (représentés par la classe PropertyHook) pour les opérations get et set, 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;

Ceci génère :

class Demo
{
    public string $firstName {
        set(string $value) => strtolower($value);
        get {
            return ucfirst($this->firstName);
        }
    }
}

Les propriétés et les crochets de propriété 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 des niveaux d'accès différents pour la lecture et l'écriture.

La visibilité peut être définie en utilisant la méthode setVisibility() avec deux paramètres, ou en utilisant setPublic(), setProtected(), ou setPrivate() avec le paramètre mode qui spécifie si la visibilité s'applique à l'obtention ou à la définition 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 for read, private for write

$class->addProperty('id')
    ->setType('int')
    ->setProtected('set'); // protected for write

echo $class;

Ceci génère :

class Demo
{
    public private(set) string $name;

    protected(set) int $id;
}

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 implement() implémente automatiquement toutes les méthodes et propriétés de l'interface ou de la classe abstraite donnée :

$manipulator->implement(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.1 est compatible avec PHP 8.0 à 8.4.

version: 4.0