Nette PhpGenerator
- 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.