Generador de código PHP

  • ¿Necesita generar código PHP para clases, funciones, archivos PHP, etc.?
  • Soporta todas las últimas características de PHP como enums, atributos, etc.
  • Le permite modificar fácilmente las clases existentes
  • Salida compatible con PSR-12
  • Librería altamente 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
// 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
// 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

El código PHP es generado por los objetos Printer. Existe un PsrPrinter cuya salida se ajusta a PSR-2 y PSR-12 y utiliza espacios para la sangría, y un Printer que utiliza tabuladores para la sangría.

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

$printer = new Nette\PhpGenerator\PsrPrinter;
echo $printer->printClass($class); // 4 espacios de sangría

¿Necesita personalizar el comportamiento de la impresora? Cree la suya propia heredando la clase Printer. Puedes reconfigurar estas variables:

class MyPrinter extends Nette\PhpGenerator\Printer
{
	public int $wrapLength = 120;
	public string $indentation = "\t";
	public int $linesBetweenProperties = 0;
	public int $linesBetweenMethods = 2;
	public int $linesBetweenUseTypes = 0;
	public bool $bracesOnNextLine = true;
	public string $returnTypeColon = ': ';
}

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|string'); // o Type::union('array', '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);

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('Deprecated');

$class->addProperty('list')
	->addAttribute('WithArguments', [1, 2]);

$method = $class->addMethod('count')
	->addAttribute('Foo\Cached', ['mode' => true]);

$method->addParameter('items')
	->addAttribute('Bar');

echo $class;

Resultado:

#[Deprecated]
class Demo
{
	#[WithArguments(1, 2)]
	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 la clase es parte del espacio de nombres, se renderiza de forma ligeramente diferente: todos los tipos (ie. type hints, return types, parent class name, interfaces implementadas, rasgos y atributos usados) se resuelven automáticamente (a menos que lo desactives, ver más abajo). Esto significa que tienes que usar nombres de clase completos en las definiciones y serán reemplazados por alias (de acuerdo con las declaraciones 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
// 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
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);

Resultado:

<?php

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

declare(strict_types=1);

namespace Foo;

class A
{
}

function foo()
{
}

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 es compatible con PHP 8.0 a 8.2

versión: 4.0