Nette PhpGenerator

¿Busca una herramienta para generar código PHP de clases, funciones o archivos completos?
  • Soporta todas las últimas funciones de PHP (como los property hooks, los enums, los atributos, etc.)
  • Le permite modificar fácilmente las clases existentes
  • Salida conforme al estilo de codificación PSR-12 / PER
  • Biblioteca madura, estable y muy usada

Instalación

Descargue e instale la biblioteca con la herramienta Composer:

composer require nette/php-generator

Para la compatibilidad con PHP, vea la tabla de compatibilidad.

Clases

Empecemos con un ejemplo de creación de una clase con ClassType:

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

$class
	->setFinal()
	->setExtends(ParentClass::class)
	->addImplement(Countable::class)
	->addComment("Class description.\nSecond line\n")
	->addComment('@property-read Nette\Forms\Form $form');

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

Eso devuelve el siguiente resultado:

/**
 * Class description.
 * Second line
 *
 * @property-read Nette\Forms\Form $form
 */
final class Demo extends ParentClass implements Countable
{
}

Para generar el código también puede usar un printer, que, a diferencia de echo $class, se puede configurar más a fondo:

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

Puede añadir constantes (clase Constant) y propiedades (clase Property):

$class->addConstant('ID', 123)
	->setProtected() // visibilidad de la constante
	->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'

Eso genera:

final protected const int ID = 123;

/** @var int[] */
private static $items = [1, 2, 3];
public ?array $list = null;

Y puede añadir métodos:

$method = $class->addMethod('count')
	->addComment('Count it.')
	->setFinal()
	->setProtected()
	->setReturnType('?int') // tipos de retorno de los 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);
}

Al constructor se le pueden pasar los parámetros promovidos introducidos en PHP 8.0:

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

El resultado es:

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

Las propiedades y las clases readonly se pueden marcar con la función setReadOnly().


Si una propiedad, constante, método o trait que se añade ya existe, se lanza una excepción. Los parámetros, en cambio, se sobrescriben.

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

También puede añadir a la clase objetos Method, Property o Constant ya existentes:

$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 otro nombre usando cloneWithName():

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

Interfaces o traits

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

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

Uso de un trait:

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

El resultado es:

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

Enums

Los enums introducidos en PHP 8.1 se crean 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;

El resultado es:

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

También puede definir los equivalentes escalares y crear un backed enum:

$enum = new Nette\PhpGenerator\EnumType('Suit');
$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');

A cada caso le puede añadir un comentario o atributos con addComment() o addAttribute().

Clases anónimas

Pase null como nombre y tendrá una clase anónima:

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

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

El resultado es:

$obj = new class ($val) {
	public function __construct($foo)
	{
	}
};

Funciones globales

El código de las funciones globales 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 obtener una salida conforme a PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);

El resultado es:

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

Funciones anónimas

El código de las funciones anónimas (closures) 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 obtener una salida conforme a PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);

El resultado es:

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

Funciones flecha cortas

Con el printer también puede imprimir una función flecha corta:

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

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

El resultado es:

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, añadir comentarios, atributos, etc.:

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

Cada parámetro lo representa la clase Parameter. También aquí puede establecer todas las propiedades imaginables:

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

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

Para definir parámetros variádicos (conocidos también como operador splat), use setVariadic():

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

Eso genera:

function count(...$items)
{
}

Cuerpos de métodos y funciones

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

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

El resultado es:

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

Puede usar marcadores especiales para insertar variables con facilidad.

Marcadores simples ?:

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

El resultado es:

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

Marcador para los variádicos ...?:

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

El resultado es:

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

También puede usar los parámetros con nombre de PHP 8 con ...?::

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

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

El marcador 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;

El resultado es:

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

Printer y conformidad con PSR

Para generar el código PHP se usa 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 el código de todos los demás elementos y ofrece métodos como printFunction(), printNamespace(), etc.

También existe la clase PsrPrinter, cuya salida se ajusta al estilo de codificación PSR-2 / PSR-12 / PER:

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

¿Necesita personalizar el comportamiento? Cree su propia versión heredando de la clase Printer. Puede reconfigurar estas variables:

class MyPrinter extends Nette\PhpGenerator\Printer
{
	// longitud de línea a partir de la cual se parten las líneas
	public int $wrapLength = 120;
	// carácter de indentación, se puede sustituir por una secuencia de espacios
	public string $indentation = "\t";
	// número de líneas vacías entre las propiedades
	public int $linesBetweenProperties = 0;
	// número de líneas vacías entre los métodos
	public int $linesBetweenMethods = 2;
	// número de líneas vacías entre los grupos de 'use statement' de clases, funciones y constantes
	public int $linesBetweenUseTypes = 0;
	// posición de la llave de apertura de las funciones y los métodos
	public bool $bracesOnNextLine = true;
	// coloca un único parámetro en una línea, aunque tenga un atributo o sea promovido
	public bool $singleParameterOnOneLine = false;
	// omite los espacios de nombres que no contienen ninguna clase ni función
	public bool $omitEmptyNamespaces = true;
	// coloca declare(strict_types) en la misma línea que <?php
	public bool $declareOnOpenTag = false;
	// separador entre el paréntesis derecho y el tipo de retorno de las funciones y los métodos
	public string $returnTypeColon = ': ';
}

¿Cómo y por qué se diferencian realmente el Printer estándar y PsrPrinter? ¿Por qué no hay en el paquete un único printer, PsrPrinter?

El Printer estándar formatea el código como lo hacemos en todo Nette. Como Nette nació mucho antes que PSR, y también porque las normas PSR llegaban a menudo tarde (a veces años después de introducirse una función nueva de PHP), el estándar de codificación de Nette se diferencia en unos pocos detalles menores. La diferencia principal es el uso de tabuladores en lugar de espacios. Sabemos que usar tabuladores en nuestros proyectos permite ajustar el ancho, algo esencial para las personas con discapacidad visual. Un ejemplo de diferencia menor es colocar la llave de apertura de las funciones y los métodos en una línea aparte, siempre. La recomendación de PSR nos parece ilógica y lleva a una menor claridad del código.

Tipos

Cualquier tipo, o tipo de unión o de intersección, se puede pasar como cadena; también puede usar las constantes predefinidas para los 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 vale para el método setReturnType().

Literales

Con Literal puede pasar cualquier código PHP, por ejemplo para los valores predeterminados de las propiedades o de los parámetros:

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 hacer que se formateen como código PHP válido usando marcadores:

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

Un literal que representa la creación de un objeto nuevo se genera fácilmente con 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 añadir a todas las clases, métodos, propiedades, constantes, enums, funciones, closures y parámetros. Como valores de los parámetros también se pueden usar literales.

$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 las operaciones get y set de las propiedades, una función 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;

Eso 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 de las propiedades. Puede establecer niveles de acceso distintos para la lectura y para la escritura.

La visibilidad se puede establecer con el método setVisibility() con dos parámetros, o con setPublic(), setProtected() o setPrivate() con el parámetro mode, que indica si la visibilidad se aplica a leer o a escribir la propiedad. El modo predeterminado es 'get'.

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

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

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

echo $class;

Eso genera:

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

    protected(set) int $id;
}

Espacios de nombres

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

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

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

// o inserta en el espacio de nombres una clase o función existente
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);

Si en el espacio de nombres ya existe una clase con el mismo nombre, 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 un nombre completamente cualificado de clase, función o constante según los alias definidos o el espacio de nombres actual, 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', gracias a la sentencia use definida

Al revés, puede convertir un nombre simplificado de clase, función o constante de vuelta a un nombre completamente cualificado con el método resolveName:

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

Resolución de los nombres de las clases

Cuando una clase forma parte de un espacio de nombres, se renderiza de forma ligeramente distinta: todos los tipos (p. ej. los type hints, los tipos de retorno, el nombre de la clase padre, las interfaces implementadas, los traits usados y los atributos) se resuelven automáticamente (a menos que lo desactive, vea más abajo). Eso significa que en las definiciones tiene que usar nombres de clase completamente cualificados, y en el código resultante se sustituirán por alias (según las cláusulas use) o por nombres simplificados (si están en el mismo espacio de nombres):

$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 los comentarios simplificamos a mano
$method->addParameter('arg')
	->setType('Bar\OtherClass'); // se traducirá a \Bar\OtherClass

echo $namespace;

// o use PsrPrinter para obtener una 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 así:

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

Archivos PHP

Las clases, las funciones y los 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(); // añade 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 obtener una 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()
{
}

También puede insertar en el archivo objetos de clase, función y espacio de nombres ya existentes con el método add():

$file = new Nette\PhpGenerator\PhpFile;
$class = new Nette\PhpGenerator\ClassType('Demo');
$file->add($class);

Tenga en cuenta: a los archivos no se les puede añadir ningún código adicional (como echo 'hello') fuera de las funciones, las clases o los espacios de nombres.

Generar a partir de elementos existentes

Además de modelar clases y funciones con la API descrita arriba, también puede hacer que se generen automáticamente a partir de las existentes mediante reflexión:

// crea una clase idéntica a 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 una closure a partir de la proporcionada
$closure = Nette\PhpGenerator\Closure::from(
	function (stdClass $a, $b = null) {},
);

De forma predeterminada, los cuerpos de las funciones y los métodos están vacíos. Si quiere cargarlos también, use este método (requiere tener instalado el paquete nikic/php-parser):

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

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

Cargar desde archivos PHP

También puede cargar funciones, clases, interfaces y enums directamente de una cadena que contenga código PHP. Por ejemplo, para crear un objeto ClassType:

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

	class Demo
	{
		public $foo;
	}
	XX);

Al cargar clases de código PHP, los comentarios de una sola línea que están fuera de los cuerpos de los métodos (p. ej. los de las propiedades) se ignoran, porque esta biblioteca no tiene una API para trabajar con ellos.

También puede cargar directamente un archivo PHP entero, 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 inicial del archivo y la declaración strict_types. El resto del código global, en cambio, se ignora.

Requiere tener instalado nikic/php-parser.

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

Manipulador de clases

La clase ClassManipulator proporciona herramientas para manipular las clases.

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

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

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

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

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

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

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

Volcado de variables

La clase Dumper convierte una variable en código PHP parseable. Ofrece 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.2 es compatible con PHP de 8.1 a 8.5.

Si está actualizando a una versión más reciente, vea la página de actualización.

versión: 4.x