Generador de código PHP

¿Está buscando una herramienta para generar código PHP para clases, funciones o archivos completos?
  • Soporta todas las últimas características de PHP (como enums, etc.)
  • Le permite modificar fácilmente las clases existentes
  • Salida conforme con el estilo de codificación PSR-12 / PER
  • Biblioteca madura, estable y ampliamente utilizada

Instalación

Descargue e instale el paquete utilizando Composer:

composer require nette/php-generator

Para la compatibilidad con PHP, consulte la tabla.

Clases

Empecemos con un ejemplo sencillo de generación de clases utilizando 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');

// para generar código PHP simplemente cast to string o use echo:
echo $class;

Dará este resultado:

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

También podemos utilizar una impresora para generar el código, que, a diferencia de echo $class, podremos configurar posteriormente:

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

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

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

Genera:

final protected const int ID = 123;

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

public ?array $list = null;

Y podemos añadir métodos:

$method = $class->addMethod('count')
	->addComment('Count it.')
	->setFinal()
	->setProtected()
	->setReturnType('?int') // método devolver tipo
	->setBody('return count($items ?: $this->items);');

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

Resulta en:

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

Los parámetros promocionados introducidos por PHP 8.0 pueden pasarse al constructor:

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

Esto resulta en:

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

Las propiedades y clases de sólo lectura pueden marcarse a través de setReadOnly().


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

Los miembros pueden eliminarse utilizando removeProperty(), removeConstant(), removeMethod() o removeParameter().

También puede añadir objetos existentes Method, Property o Constant 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);

Puede clonar métodos, propiedades y constantes existentes con un nombre diferente utilizando 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');

Utilizar rasgos:

$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

Puedes crear fácilmente los enums que trae PHP 8.1 (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 para los casos para crear un enum respaldado:

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

Es posible añadir un comentario o atributos a cada caso utilizando addComment() o addAttribute().

Clase anónima

Dale null como nombre y tendrás 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)
	{
	}
};

Función global

El código de las funciones generará la clase GlobalFunction:

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

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

Resultado:

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

Cierre

El código de cierres generará 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 utilice PsrPrinter para una salida conforme a PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);

Resultado:

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

Función Flecha

También puede imprimir el cierre como función de flecha utilizando la impresora:

$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

Firma de método y función

Los métodos están representados por la clase Method. Se puede establecer la visibilidad, el valor de retorno, añadir comentarios, atributos, etc:

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

Cada parámetro está representado por una clase Parámetro. Una vez más, puede establecer todas las propiedades imaginables:

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

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

Para definir los llamados parámetros variádicos (o también el operador splat, spread, ellipsis, unpacking o tres puntos), utilice setVariadics():

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

Genera:

function count(...$items)
{
}

Método y cuerpo de la función

El cuerpo puede pasarse al método setBody() de una vez o secuencialmente (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 utilizar marcadores de posición especiales para inyectar variables de forma práctica.

Marcadores de posición 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);
}

Marcador de posición variable ...?

$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 utilizar PHP 8 parámetros con nombre utilizando marcador de posición ...?:

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

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

Escapar el marcador de posición usando la barra \?

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

Impresoras y cumplimiento del PSR

La clase Printer se utiliza para generar código PHP:

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

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

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

Además, está disponible 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);

¿Necesitas ajustar el comportamiento a tus necesidades? Cree su propia impresora heredando de la clase Printer. Puedes reconfigurar estas variables:

class MyPrinter extends Nette\PhpGenerator\Printer
{
	// longitud de la línea después de la cual la línea se romperá
	public int $wrapLength = 120;
	// carácter de sangría, puede ser sustituido por una secuencia de espacios
	public string $indentation = "\t";
	// número de líneas en blanco entre propiedades
	public int $linesBetweenProperties = 0;
	// número de líneas en blanco entre métodos
	public int $linesBetweenMethods = 2;
	// número de líneas en blanco entre grupos de declaraciones de uso de 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 en una línea, incluso si tiene un atributo o es promocionado
	public bool $singleParameterOnOneLine = false;
	// omits namespaces that do not contain any class or function
	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 exactamente el estándar Printer y PsrPrinter? ¿Por qué no hay sólo una impresora, la PsrPrinter, en el paquete?

El estándar Printer formatea el código tal y 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ó los estándares a tiempo, sino a veces incluso con varios años de retraso desde la introducción de una nueva característica en PHP, esto dio lugar a algunas pequeñas diferencias en el estándar de codificación. La mayor diferencia es simplemente el uso de tabuladores en lugar de espacios. Sabemos que utilizando tabuladores en nuestros proyectos permitimos ajustar la anchura, lo que es esencial para las personas con deficiencias visuales. Un ejemplo de diferencia menor es la colocación de la llave rizada en una línea separada para funciones y métodos y siempre. Consideramos que la recomendación del PSR es ilógica y conduce a una disminución de la claridad del código.

Tipos

Cada tipo o tipo de unión/intersección puede pasarse como una cadena, también puede utilizar constantes predefinidas para tipos nativos:

use Nette\PhpGenerator\Type;

$member->setType('array'); // o 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'); // 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 código PHP arbitrario a, por ejemplo, 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 hacer que se formatee en código PHP válido utilizando marcadores de posición especiales:

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

El literal que representa la creación de un nuevo objeto se genera fácilmente mediante el método new:

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

Atributos

Puede agregar atributos PHP 8 a todas las clases, métodos, propiedades, constantes, casos enum, funciones, cierres y parámetros. Los literales también pueden ser usados 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,
	) {
	}
}

Espacio de nombres

Las clases, traits, interfaces y enums (en adelante clases) pueden agruparse en espacios de nombres (PhpNamespace):

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

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

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

Si la clase ya existe, lanza excepción.

Puede definir declaraciones de uso:

// 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 completo de clase, función o constante según los alias definidos, utilice 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 a la declaración de uso definida

A la inversa, puede convertir un nombre de clase, función o constante simplificado en uno totalmente cualificado utilizando el método resolveName:

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

Resolución de nombres de clase

Cuando una clase es parte de un espacio de nombres, se renderiza de forma ligeramente diferente: todos los tipos (por ejemplo, sugerencias de tipo, tipos de retorno, nombre de la clase padre, interfaces implementadas, rasgos usados y atributos) se resuelven automáticamente (a menos que lo desactives, ver más abajo). Esto significa que debes usar nombres de clase completamente cualificados en las definiciones, y serán reemplazados por alias (basados en cláusulas de uso) o nombres completamente cualificados 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'); // simplificará a AliasedClass

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

echo $namespace;

// o utilizar PsrPrinter para 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 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 pueden agruparse 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 utilice PsrPrinter para 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()
{
}

Por favor, tenga en cuenta: No se puede añadir código adicional a los archivos fuera de las funciones y clases.

Generar en función de los existentes

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

// crea una clase idéntica a la clase PDO
$class = Nette\PhpGenerator\ClassType::from(PDO::class);

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

// crea un cierre como el especificado
$closure = Nette\PhpGenerator\Closure::from(
	function (stdClass $a, $b = null) {},
);

Los cuerpos de las funciones y métodos están vacíos por defecto. Si quieres cargarlos también, utiliza esta forma (requiere que nikic/php-parser esté instalado):

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

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

Carga desde archivo PHP

También puede cargar funciones, clases, interfaces y enums directamente desde una cadena de código PHP. Por ejemplo, creamos el objeto ClassType de esta manera:

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

	class Demo
	{
		public $foo;
	}
	XX);

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

También puede cargar directamente el archivo PHP completo, que puede contener cualquier número de clases, funciones o incluso múltiples 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. En cambio, el resto del código global se ignora.

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

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

Volquete de variables

El Dumper devuelve una representación de cadena PHP parseable de una variable. Proporciona una salida mejor y más clara que la función nativa var_export().

$dumper = new Nette\PhpGenerator\Dumper;

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

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

Tabla de compatibilidad

PhpGenerator 4.0 y 4.1 son compatibles con PHP 8.0 a 8.3

versión: 4.0