Generador de código PHP

¿Busca una herramienta para generar código PHP para clases, funciones o archivos completos?
  • Admite todas las características más recientes de PHP (como property hooks, enums, atributos, etc.)
  • Le permite modificar fácilmente clases existentes
  • El código de salida cumple con el estilo de codificación PSR-12 / PER
  • Biblioteca madura, estable y ampliamente utilizada

Instalación

Descargue e instale la biblioteca usando Composer:

composer require nette/php-generator

Puede encontrar la compatibilidad con PHP en la tabla.

Clases

Comencemos directamente con un ejemplo de creación de una clase usando ClassType:

$class = new Nette\PhpGenerator\ClassType('Demo');

$class
	->setFinal()
	->setExtends(ParentClass::class)
	->addImplement(Countable::class)
	->addComment("Descripción de la clase.\nSegunda línea\n")
	->addComment('@property-read Nette\Forms\Form $form');

// simplemente genere el código convirtiéndolo a cadena o usando echo:
echo $class;

Devuelve el siguiente resultado:

/**
 * Descripción de la clase
 * Segunda línea
 *
 * @property-read Nette\Forms\Form $form
 */
final class Demo extends ParentClass implements Countable
{
}

También podemos usar el llamado printer para generar el código, que a diferencia de echo $class, podremos configurar más:

$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class);

Podemos agregar constantes (clase Constant) y propiedades (clase Property):

$class->addConstant('ID', 123)
	->setProtected() // visibilidad de constantes
	->setType('int')
	->setFinal();

$class->addProperty('items', [1, 2, 3])
	->setPrivate() // o setVisibility('private')
	->setStatic()
	->addComment('@var int[]');

$class->addProperty('list')
	->setType('?array')
	->setInitialized(); // imprime '= null'

Genera:

final protected const int ID = 123;

/** @var int[] */
private static $items = [1, 2, 3];

public ?array $list = null;

Y podemos agregar métodos:

$method = $class->addMethod('count')
	->addComment('Count it.')
	->setFinal()
	->setProtected()
	->setReturnType('?int') // tipos de retorno en métodos
	->setBody('return count($items ?: $this->items);');

$method->addParameter('items', []) // $items = []
	->setReference()           // &$items = []
	->setType('array');        // array &$items = []

El resultado es:

/**
 * Count it.
 */
final protected function count(array &$items = []): ?int
{
	return count($items ?: $this->items);
}

Los parámetros promocionados introducidos en PHP 8.0 se pueden pasar al constructor:

$method = $class->addMethod('__construct');
$method->addPromotedParameter('name');
$method->addPromotedParameter('args', [])
	->setPrivate();

El resultado es:

public function __construct(
	public $name,
	private $args = [],
) {
}

Las propiedades y clases de solo lectura se pueden marcar usando la función setReadOnly().


Si la propiedad, constante, método o parámetro agregado ya existe, se lanza una excepción.

Los miembros de la clase se pueden eliminar usando removeProperty(), removeConstant(), removeMethod() o removeParameter().

También puede agregar objetos Method, Property o Constant existentes a la clase:

$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);

También puede clonar métodos, propiedades y constantes existentes con un nombre diferente usando cloneWithName():

$methodCount = $class->getMethod('count');
$methodRecount = $methodCount->cloneWithName('recount');
$class->addMember($methodRecount);

Interfaz o trait

Puede crear interfaces y traits (clases InterfaceType y TraitType):

$interface = new Nette\PhpGenerator\InterfaceType('MyInterface');
$trait = new Nette\PhpGenerator\TraitType('MyTrait');

Uso de traits:

$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addTrait('SmartObject');
$class->addTrait('MyTrait')
	->addResolution('sayHello as protected')
	->addComment('@use MyTrait<Foo>');
echo $class;

Resultado:

class Demo
{
	use SmartObject;
	/** @use MyTrait<Foo> */
	use MyTrait {
		sayHello as protected;
	}
}

Enums

Los enums, introducidos en PHP 8.1, se pueden crear fácilmente así: (clase EnumType):

$enum = new Nette\PhpGenerator\EnumType('Suit');
$enum->addCase('Clubs');
$enum->addCase('Diamonds');
$enum->addCase('Hearts');
$enum->addCase('Spades');

echo $enum;

Resultado:

enum Suit
{
	case Clubs;
	case Diamonds;
	case Hearts;
	case Spades;
}

También puede definir equivalentes escalares y crear así un enum “backed”:

$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');

A cada case se le puede agregar un comentario o atributos usando addComment() o addAttribute().

Clases anónimas

Pasamos null como nombre y tenemos una clase anónima:

$class = new Nette\PhpGenerator\ClassType(null);
$class->addMethod('__construct')
	->addParameter('foo');

echo '$obj = new class ($val) ' . $class . ';';

Resultado:

$obj = new class ($val) {

	public function __construct($foo)
	{
	}
};

Funciones globales

El código de las funciones lo genera la clase GlobalFunction:

$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('return $a + $b;');
$function->addParameter('a');
$function->addParameter('b');
echo $function;

// o use PsrPrinter para salida conforme a PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);

Resultado:

function foo($a, $b)
{
	return $a + $b;
}

Funciones anónimas

El código de las funciones anónimas lo genera la clase Closure:

$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('return $a + $b;');
$closure->addParameter('a');
$closure->addParameter('b');
$closure->addUse('c')
	->setReference();
echo $closure;

// o use PsrPrinter para salida conforme a PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);

Resultado:

function ($a, $b) use (&$c) {
	return $a + $b;
}

Funciones flecha abreviadas

También puede imprimir una función anónima abreviada usando el printer:

$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('$a + $b');
$closure->addParameter('a');
$closure->addParameter('b');

echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure);

Resultado:

fn($a, $b) => $a + $b

Firmas de métodos y funciones

Los métodos los representa la clase Method. Puede establecer la visibilidad, el tipo de retorno, agregar comentarios, atributos, etc:

$method = $class->addMethod('count')
	->addComment('Count it.')
	->setFinal()
	->setProtected()
	->setReturnType('?int');

Los parámetros individuales los representa la clase Parameter. Nuevamente, puede establecer todas las propiedades imaginables:

$method->addParameter('items', []) // $items = []
	->setReference()           // &$items = []
	->setType('array');        // array &$items = []

// function count(array &$items = [])

Para definir los llamados parámetros variádicos (o también operador splat) sirve setVariadic():

$method = $class->addMethod('count');
$method->setVariadic(true);
$method->addParameter('items');

Genera:

function count(...$items)
{
}

Cuerpos de métodos y funciones

El cuerpo se puede pasar de una vez al método setBody() o gradualmente (línea por línea) llamando repetidamente a addBody():

$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('$a = rand(10, 20);');
$function->addBody('return $a;');
echo $function;

Resultado

function foo()
{
	$a = rand(10, 20);
	return $a;
}

Puede usar placeholders especiales para insertar variables fácilmente.

Placeholders simples ?

$str = 'any string';
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('return substr(?, ?);', [$str, $num]);
echo $function;

Resultado

function foo()
{
	return substr('any string', 3);
}

Placeholder para variadic ...?

$items = [1, 2, 3];
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('myfunc(...?);', [$items]);
echo $function;

Resultado:

function foo()
{
	myfunc(1, 2, 3);
}

También puede usar parámetros con nombre para PHP 8 usando ...?:

$items = ['foo' => 1, 'bar' => true];
$function->setBody('myfunc(...?:);', [$items]);

// myfunc(foo: 1, bar: true);

El placeholder se escapa con una barra invertida \?

$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addParameter('a');
$function->addBody('return $a \? 10 : ?;', [$num]);
echo $function;

Resultado:

function foo($a)
{
	return $a ? 10 : 3;
}

Printer y conformidad con PSR

Para generar código PHP sirve la clase Printer:

$class = new Nette\PhpGenerator\ClassType('Demo');
// ...

$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // lo mismo que: echo $class

Puede generar código de todos los demás elementos, ofrece métodos como printFunction(), printNamespace(), etc.

También está disponible la clase PsrPrinter, cuya salida cumple con el estilo de codificación PSR-2 / PSR-12 / PER:

$printer = new Nette\PhpGenerator\PsrPrinter;
echo $printer->printClass($class);

¿Necesita ajustar el comportamiento a medida? Cree su propia versión heredando la clase Printer. Se pueden reconfigurar estas variables:

class MyPrinter extends Nette\PhpGenerator\Printer
{
	// longitud de línea tras la cual se produce el salto de línea
	public int $wrapLength = 120;
	// carácter de indentación, puede ser reemplazado por una secuencia de espacios
	public string $indentation = "\t";
	// número de líneas vacías entre propiedades
	public int $linesBetweenProperties = 0;
	// número de líneas vacías entre métodos
	public int $linesBetweenMethods = 2;
	// número de líneas vacías entre grupos de 'use statements' para clases, funciones y constantes
	public int $linesBetweenUseTypes = 0;
	// posición de la llave de apertura para funciones y métodos
	public bool $bracesOnNextLine = true;
	// colocar un parámetro por línea, incluso si tiene un atributo o es promocionado
	public bool $singleParameterOnOneLine = false;
	// omite espacios de nombres que no contienen ninguna clase o función
	public bool $omitEmptyNamespaces = true;
	// separador entre el paréntesis derecho y el tipo de retorno de funciones y métodos
	public string $returnTypeColon = ': ';
}

¿Cómo y por qué difieren realmente el Printer estándar y el PsrPrinter? ¿Por qué no hay solo un printer en el paquete, es decir, PsrPrinter?

El Printer estándar formatea el código como lo hacemos en todo Nette. Dado que Nette se creó mucho antes que PSR, y también porque PSR durante muchos años no entregó estándares a tiempo, sino tal vez con varios años de retraso desde la introducción de una nueva característica en PHP, sucedió que el estándar de codificación difiere en algunos pequeños detalles. La mayor diferencia es solo el uso de tabuladores en lugar de espacios. Sabemos que al usar tabuladores en nuestros proyectos, permitimos la personalización del ancho, lo cual es necesario para personas con discapacidad visual. Un ejemplo de una pequeña diferencia es la colocación de la llave de apertura en una línea separada para funciones y métodos, y siempre. La recomendación de PSR nos parece ilógica y conduce a una reducción de la claridad del código.

Tipos

Cada tipo o tipo unión/intersección se puede pasar como una cadena, también puede usar constantes predefinidas para tipos nativos:

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); // elimina el tipo

Lo mismo se aplica al método setReturnType().

Literales

Con Literal puede pasar cualquier código PHP, por ejemplo, para valores predeterminados de propiedades o parámetros, 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;

Resultado:

class Demo
{
	public $foo = Iterator::SELF_FIRST;

	public function bar($id = 1 + 2)
	{
	}
}

También puede pasar parámetros a Literal y dejar que se formateen en código PHP válido usando placeholders:

new Literal('substr(?, ?)', [$a, $b]);
// genera por ejemplo: substr('hello', 5);

Un literal que representa la creación de un nuevo objeto se puede generar fácilmente usando el método new:

Literal::new(Demo::class, [$a, 'foo' => $b]);
// genera por ejemplo: new Demo(10, foo: 20)

Atributos

Los atributos de PHP 8 se pueden agregar a todas las clases, métodos, propiedades, constantes, enums, funciones, closures y parámetros. También se pueden usar literales como valores de parámetros.

$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;

Resultado:

#[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 los property hooks (representados por la clase PropertyHook) puede definir operaciones get y set para propiedades, una característica introducida 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;

Genera:

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

Las propiedades y los property hooks pueden ser abstractos o finales:

$class->addProperty('id')
    ->setType('int')
    ->addHook('get')
        ->setAbstract();

$class->addProperty('role')
    ->setType('string')
    ->addHook('set', 'strtolower($value)')
        ->setFinal();

Visibilidad asimétrica

PHP 8.4 introduce la visibilidad asimétrica para propiedades. Puede establecer diferentes niveles de acceso para lectura y escritura.

La visibilidad se puede establecer ya sea usando el método setVisibility() con dos parámetros, o usando setPublic(), setProtected() o setPrivate() con el parámetro mode, que especifica si la visibilidad se aplica a la lectura o escritura de la propiedad. El modo predeterminado es 'get'.

$class = new Nette\PhpGenerator\ClassType('Demo');

$class->addProperty('name')
    ->setType('string')
    ->setVisibility('public', 'private'); // public para lectura, private para escritura

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

echo $class;

Genera:

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

    protected(set) int $id;
}

Espacio de nombres

Las clases, propiedades, interfaces y enums (en adelante, clases) se pueden agrupar en espacios de nombres representados por la clase PhpNamespace:

$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');

// creamos nuevas clases en el namespace
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');

// o insertamos una clase existente en el namespace
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);

Si la clase ya existe, se lanza una excepción.

Puede definir cláusulas 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');

Para simplificar el nombre completamente calificado de una clase, función o constante según los alias definidos, use el método simplifyName:

echo $namespace->simplifyName('Foo\Bar'); // 'Bar', porque 'Foo' es el espacio de nombres actual
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', debido al use-statement definido

Por el contrario, puede convertir el nombre simplificado de una clase, función o constante a su nombre completamente calificado usando el método resolveName:

echo $namespace->resolveName('Bar'); // 'Foo\Bar'
echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'

Resolución de nombres de clases

Cuando una clase forma parte de un espacio de nombres, se renderiza de forma ligeramente diferente: todos los tipos (por ejemplo, typehints, tipos de retorno, nombre de la clase padre, interfaces implementadas, propiedades usadas y atributos) se resuelven automáticamente (a menos que lo desactive, ver más abajo). Esto significa que debe usar nombres de clase completos en las definiciones y se reemplazarán por alias (según las cláusulas use) o por nombres completamente calificados en el código resultante:

$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
$namespace->addUse('Bar\AliasedClass');

$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // se simplificará a A
	->addTrait('Bar\AliasedClass'); // se simplificará a AliasedClass

$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // en comentarios simplificamos manualmente
$method->addParameter('arg')
	->setType('Bar\OtherClass'); // se resolverá a \Bar\OtherClass

echo $namespace;

// o use PsrPrinter para salida conforme a PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);

Resultado:

namespace Foo;

use Bar\AliasedClass;

class Demo implements A
{
	use AliasedClass;

	/**
	 * @return D
	 */
	public function method(\Bar\OtherClass $arg)
	{
	}
}

La resolución automática se puede desactivar de esta manera:

$printer = new Nette\PhpGenerator\Printer; // o PsrPrinter
$printer->setTypeResolving(false);
echo $printer->printNamespace($namespace);

Archivos PHP

Las clases, funciones y espacios de nombres se pueden agrupar en archivos PHP representados por la clase PhpFile:

$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('This file is auto-generated.');
$file->setStrictTypes(); // agrega declare(strict_types=1)

$class = $file->addClass('Foo\A');
$function = $file->addFunction('Foo\foo');

// o
// $namespace = $file->addNamespace('Foo');
// $class = $namespace->addClass('A');
// $function = $namespace->addFunction('foo');

echo $file;

// o use PsrPrinter para salida conforme a PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);

Resultado:

<?php

/**
 * This file is auto-generated.
 */

declare(strict_types=1);

namespace Foo;

class A
{
}

function foo()
{
}

Advertencia: No es posible agregar ningún otro código fuera de funciones y clases a los archivos.

Generación a partir de existentes

Además de poder modelar clases y funciones usando la API descrita anteriormente, también puede hacer que se generen automáticamente a partir de patrones existentes:

// crea una clase igual que la clase PDO
$class = Nette\PhpGenerator\ClassType::from(PDO::class);

// crea una función idéntica a la función trim()
$function = Nette\PhpGenerator\GlobalFunction::from('trim');

// crea un closure según el indicado
$closure = Nette\PhpGenerator\Closure::from(
	function (stdClass $a, $b = null) {},
);

Los cuerpos de funciones y métodos están vacíos de forma predeterminada. Si también desea cargarlos, use este método (requiere la instalación del paquete nikic/php-parser):

$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true);

$function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);

Carga desde archivos PHP

También puede cargar funciones, clases, interfaces y enums directamente desde una cadena que contenga código PHP. Por ejemplo, así creamos un objeto ClassType:

$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX
	<?php

	class Demo
	{
		public $foo;
	}
	XX);

Al cargar clases desde código PHP, los comentarios de una sola línea fuera de los cuerpos de los métodos se ignoran (por ejemplo, en propiedades, etc.), ya que esta biblioteca no tiene una API para trabajar con ellos.

También puede cargar directamente un archivo PHP completo, que puede contener cualquier número de clases, funciones o incluso espacios de nombres:

$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));

También se cargan el comentario introductorio del archivo y la declaración strict_types. Por el contrario, todo el demás código global se ignora.

Se requiere que esté instalado nikic/php-parser.

Si necesita manipular código global en archivos o sentencias individuales en cuerpos de métodos, es mejor usar directamente la biblioteca nikic/php-parser.

Class Manipulator

La clase ClassManipulator proporciona herramientas para manipular clases.

$class = new Nette\PhpGenerator\ClassType('Demo');
$manipulator = new Nette\PhpGenerator\ClassManipulator($class);

El método inheritMethod() copia un método de la clase padre o interfaz implementada a su clase. Esto le permite sobrescribir el método o extender su firma:

$method = $manipulator->inheritMethod('bar');
$method->setBody('...');

El método inheritProperty() copia una propiedad de la clase padre a su clase. Es útil cuando desea tener la misma propiedad en su clase, pero quizás con un valor predeterminado diferente:

$property = $manipulator->inheritProperty('foo');
$property->setValue('new value');

El método implement() implementa automáticamente todos los métodos y propiedades de la interfaz o clase abstracta dada en su clase:

$manipulator->implement(SomeInterface::class);
// Ahora su clase implementa SomeInterface y contiene todos sus métodos

Volcado de variables

La clase Dumper convierte una variable en código PHP analizable. Proporciona una salida mejor y más clara que la función estándar var_export().

$dumper = new Nette\PhpGenerator\Dumper;

$var = ['a', 'b', 123];

echo $dumper->dump($var); // imprime ['a', 'b', 123]

Tabla de compatibilidad

PhpGenerator 4.1 es compatible con PHP 8.0 a 8.4.

versión: 4.0