Generador de código PHP
- Soporta todas las últimas características de PHP (como ganchos de propiedades, enums, atributos, 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 setVariadic()
:
$method = $class->addMethod('count');
$method->setVariadic(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,
) {
}
}
Ganchos para propiedades
También puede definir ganchos de propiedad (representados por la clase PropertyHook) para operaciones get y set, 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;
Esto genera:
class Demo
{
public string $firstName {
set(string $value) => strtolower($value);
get {
return ucfirst($this->firstName);
}
}
}
Las propiedades y los ganchos de propiedades 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 visibilidad asimétrica para las propiedades. Puede establecer diferentes niveles de acceso para lectura y escritura.
La visibilidad puede establecerse 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 obtener o establecer la propiedad. El modo por defecto es 'get'
.
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addProperty('name')
->setType('string')
->setVisibility('public', 'private'); // public for read, private for write
$class->addProperty('id')
->setType('int')
->setProtected('set'); // protected for write
echo $class;
Esto genera:
class Demo
{
public private(set) string $name;
protected(set) int $id;
}
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
.
Manipulador de clases
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 una clase padre o de una interfaz implementada en 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 una clase padre en tu clase. Esto es útil cuando se desea
tener la misma propiedad en su clase, pero posiblemente 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:
$manipulador->implementar(CiertaInterfaz::clase);
// Ahora tu clase implementa CiertaInterfaz e incluye todos sus métodos
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.1 es compatible con PHP 8.0 a 8.4.