Gerador de código PHP
- Oferece suporte a todos os recursos mais recentes do PHP (como enums, etc.)
- Permite modificar facilmente as classes existentes
- Saída compatível com o estilo de codificação PSR-12 / PER
- Biblioteca madura, estável e amplamente utilizada
Instalação
Baixe e instale o pacote usando o Composer:
composer require nette/php-generator
Para compatibilidade com PHP, consulte a tabela.
Aulas
Vamos começar com um exemplo simples de geração de classe usando o 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 gerar o código PHP simplesmente lançar para string ou usar eco:
echo $class;
Ele renderá este resultado:
/**
* Description of class.
* Second line
*
* @property-read Nette\Forms\Form $form
*/
final class Demo extends ParentClass implements Countable
{
}
Também podemos utilizar uma impressora para gerar o código, que, ao contrário de echo $class
, poderemos configurar ainda mais:
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class);
Podemos acrescentar constantes (classe Constante) e propriedades (classe Propriedade):
$class->addConstant('ID', 123)
->setProtected() // visiblidade constante
->setType('int')
->setFinal();
$class->addProperty('items', [1, 2, 3])
->setPrivate() // ou setVisibilidade ("privado")
->setStatic()
->addComment('@var int[]');
$class->addProperty('list')
->setType('?array')
->setInitialized(); // estampas '= nulo
Ele gera:
final protected const int ID = 123;
/** @var int[] */
private static $items = [1, 2, 3];
public ?array $list = null;
E podemos acrescentar métodos:
$method = $class->addMethod('count')
->addComment('Count it.')
->setFinal()
->setProtected()
->setReturnType('?int') // tipo de retorno do método
->setBody('return count($items ?: $this->items);');
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
O resultado é
/**
* Count it.
*/
final protected function count(array &$items = []): ?int
{
return count($items ?: $this->items);
}
Os parâmetros promovidos introduzidos pelo PHP 8.0 podem ser passados para o construtor:
$method = $class->addMethod('__construct');
$method->addPromotedParameter('name');
$method->addPromotedParameter('args', [])
->setPrivate();
O resultado é
public function __construct(
public $name,
private $args = [],
) {
}
As propriedades e classes só de leitura podem ser marcadas via setReadOnly()
.
Se a propriedade adicionada, constante, método ou parâmetro já existir, ela lança uma exceção.
Os membros podem ser removidos usando removeProperty()
, removeConstant()
, removeMethod()
ou removeParameter()
.
Você também pode adicionar objetos existentes Method
, Property
ou Constant
à
classe:
$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);
Você pode clonar métodos, propriedades e constantes existentes com um nome diferente usando cloneWithName()
:
$methodCount = $class->getMethod('count');
$methodRecount = $methodCount->cloneWithName('recount');
$class->addMember($methodRecount);
Interface ou Traço
Você pode criar interfaces e traços (classes InterfaceType e TraitType):
$interface = new Nette\PhpGenerator\InterfaceType('MyInterface');
$trait = new Nette\PhpGenerator\TraitType('MyTrait');
Usando traços:
$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
Você pode criar facilmente os enums que o PHP 8.1 traz (classe 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;
}
Você também pode definir equivalentes escalares para casos a fim de criar um enumero de apoio:
$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');
É possível adicionar um comentário ou atributos a cada caso usando addComment()
ou addAttribute()
.
Classe Anônima
Dê null
como o nome e você tem uma classe 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)
{
}
};
Função Global
O código de funções irá gerar a classe GlobalFunction:
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('return $a + $b;');
$function->addParameter('a');
$function->addParameter('b');
echo $function;
// ou usar PsrPrinter para saída conforme PSR-2 / PSR-12 / PER
// echo (novo Nette\PhpGenerator\PsrPrinter)->printFunction($function);
Resultado:
function foo($a, $b)
{
return $a + $b;
}
Fechamento
O código de fechamentos gerará fechamento de classe:
$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('return $a + $b;');
$closure->addParameter('a');
$closure->addParameter('b');
$closure->addUse('c')
->setReference();
echo $closure;
// ou usar PsrPrinter para saída conforme PSR-2 / PSR-12 / PER
// echo (novo Nette\PhpGenerator\PsrPrinter)->printClosure($closure);
Resultado:
function ($a, $b) use (&$c) {
return $a + $b;
}
Função de Seta
Você também pode imprimir o fechamento como função de seta usando a impressora:
$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
Método e Assinatura da Função
Os métodos são representados pelo método de classe. Você pode definir visibilidade, valor de retorno, adicionar comentários, atributos, etc:
$method = $class->addMethod('count')
->addComment('Count it.')
->setFinal()
->setProtected()
->setReturnType('?int');
Cada parâmetro é representado por um parâmetro de classe. Novamente, você pode definir todos os bens concebíveis:
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
// function count(&$items = [])
Para definir os chamados parâmetros de variação (ou também o splat, spread, elipse, desempacotamento ou operador de três
pontos), use setVariadic()
:
$method = $class->addMethod('count');
$method->setVariadic(true);
$method->addParameter('items');
Gera:
function count(...$items)
{
}
Método e Função Corpo
O corpo pode ser passado para o método setBody()
de uma vez ou sequencialmente (linha por linha), ligando
repetidamente para 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;
}
Você pode usar porta-lugares especiais para injetar variáveis de forma prática.
Porta-lugares 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);
}
Variadic placeholder ...?
$items = [1, 2, 3];
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('myfunc(...?);', [$items]);
echo $function;
Resultado:
function foo()
{
myfunc(1, 2, 3);
}
Você também pode usar parâmetros nomeados no PHP 8 usando placeholder ...?:
$items = ['foo' => 1, 'bar' => true];
$function->setBody('myfunc(...?:);', [$items]);
// myfunc(foo: 1, bar: true);
Porta-lugar de fuga usando 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;
}
Impressoras e conformidade PSR
A classe Printer é usada para gerar código PHP:
$class = new Nette\PhpGenerator\ClassType('Demo');
// ...
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // o mesmo que: echo $class
Ele pode gerar código para todos os outros elementos, oferecendo métodos como printFunction()
,
printNamespace()
, etc.
Além disso, a classe PsrPrinter
está disponível, cuja saída está em conformidade com o estilo de
codificação PSR-2 / PSR-12 / PER:
$printer = new Nette\PhpGenerator\PsrPrinter;
echo $printer->printClass($class);
Precisa ajustar o comportamento de acordo com suas necessidades? Crie sua própria impressora herdando a classe
Printer
. Você pode reconfigurar essas variáveis:
class MyPrinter extends Nette\PhpGenerator\Printer
{
// comprimento da linha após a qual a linha será interrompida
public int $wrapLength = 120;
// caractere de recuo, pode ser substituído por uma sequência de espaços
public string $indentation = "\t";
// número de linhas em branco entre as propriedades
public int $linesBetweenProperties = 0;
// número de linhas em branco entre os métodos
public int $linesBetweenMethods = 2;
// número de linhas em branco entre grupos de instruções de uso para classes, funções e constantes
public int $linesBetweenUseTypes = 0;
// posição da chave de abertura para funções e métodos
public bool $bracesOnNextLine = true;
// colocar um parâmetro em uma linha, mesmo que ele tenha um atributo ou seja promovido
public bool $singleParameterOnOneLine = false;
// omits namespaces that do not contain any class or function
public bool $omitEmptyNamespaces = true;
// separador entre o parêntese direito e o tipo de retorno de funções e métodos
public string $returnTypeColon = ': ';
}
Como e por que exatamente o padrão Printer
e o PsrPrinter
diferem? Por que não há apenas uma
impressora, a PsrPrinter
, no pacote?
O padrão Printer
formata o código como fazemos em toda a Nette. Como a Nette foi criada muito antes da PSR, e
também porque a PSR por muitos anos não forneceu padrões a tempo, mas às vezes até com vários anos de atraso da introdução
de um novo recurso no PHP, isso resultou em algumas pequenas diferenças no padrão de codificação. A maior diferença é apenas o uso de
tabulações em vez de espaços. Sabemos que, ao usar tabulações em nossos projetos, permitimos o ajuste da largura, o que é
essencial para pessoas com
deficiências visuais. Um exemplo de uma diferença menor é o posicionamento da chave em uma linha separada para funções e
métodos e sempre. Para nós, a recomendação do PSR é ilógica e leva a uma diminuição da clareza do
código.
Tipos
Cada tipo ou tipo de união/intersecção pode ser passado como uma corda, você também pode usar constantes pré-definidas para tipos nativos:
use Nette\PhpGenerator\Type;
$member->setType('array'); // or 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'); // or Type::intersection(Foo::class, Bar::class)
$member->setType(null); // removes type
O mesmo se aplica ao método setReturnType()
.
Literals
Com Literal
você pode passar código PHP arbitrário para, por exemplo, propriedade padrão ou valores de
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)
{
}
}
Você também pode passar os parâmetros para Literal
e formatá-lo em código PHP válido usando marcadores de lugar especiais:
new Literal('substr(?, ?)', [$a, $b]);
// gera, por exemplo: substrato ("olá", 5);
O literal que representa a criação de um novo objeto é facilmente gerado pelo método new
:
Literal::new(Demo::class, [$a, 'foo' => $b]);
// gera, por exemplo: new Demo(10, foo: 20)
Atributos
Você pode adicionar atributos PHP 8 a todas as classes, métodos, propriedades, constantes, casos de enumeração, funções, fechamentos e parâmetros. Os literais também podem 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,
) {
}
}
Namespace
Classes, traços, interfaces e enumeros (doravante classes) podem ser agrupados em namespaces (PhpNamespace):
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
// criar novas classes no namespace
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');
// ou inserir uma classe existente no namespace
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);
Se a classe já existe, ela lança uma exceção.
Você pode definir as declarações 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 uma classe, função ou nome constante totalmente qualificado de acordo com os pseudônimos definidos, utilize
o método simplifyName
:
echo $namespace->simplifyName('Foo\Bar'); // 'Bar', porque 'Foo' é o namespace atual
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', por causa do uso-definido
Por outro lado, você pode converter uma classe, função ou nome constante simplificado para uma classe totalmente qualificada
usando o método resolveName
:
echo $namespace->resolveName('Bar'); // 'Foo\Bar'; // 'Foo\Bar'.
echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'
Resolução de Nomes de Classe
Quando uma classe faz parte de um namespace, ela é renderizada de forma um pouco diferente: todos os tipos (por exemplo, dicas de tipo, tipos de retorno, nome da classe pai, interfaces implementadas, características usadas e atributos) são automaticamente resolvidos (a menos que você desative essa opção, veja abaixo). Isso significa que você deve usar nomes de classe totalmente qualificados nas definições, e eles serão substituídos por aliases (com base em cláusulas de uso) ou nomes totalmente qualificados no código resultante:
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
$namespace->addUse('Bar\AliasedClass');
$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // simplificará para A
->addTrait('Bar\AliasedClass'); // simplificará para a AliasedClass
$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // em comentários simplifique manualmente
$method->addParameter('arg')
->setType('Bar\OtherClass'); // resolverá a barrar a outra classe
echo $namespace;
// ou usar PsrPrinter para saída em conformidade com 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)
{
}
}
A auto-resolução pode ser desligada desta maneira:
$printer = new Nette\PhpGenerator\Printer; // ou PsrPrinter
$printer->setTypeResolving(false);
echo $printer->printNamespace($namespace);
Arquivos PHP
As classes, funções e namespaces podem ser agrupadas em arquivos PHP representados pela classe PhpFile:
$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('This file is auto-generated.');
$file->setStrictTypes(); // adiciona declare(strict_types=1)
$class = $file->addClass('Foo\A');
$function = $file->addFunction('Foo\foo');
// ou
// $namespace = $file->addNamespace('Foo');
// $class = $namespace->addClass('A');
// $function = $namespace->addFunction('foo');
echo $file;
// ou usar PsrPrinter para saída em conformidade com 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()
{
}
Observação: Nenhum código adicional pode ser adicionado aos arquivos fora das funções e classes.
Gerando de acordo com os já existentes
Além de poder modelar classes e funções usando a API descrita acima, você também pode tê-las geradas automaticamente usando as já existentes:
// cria uma classe idêntica à classe da DOP
$class = Nette\PhpGenerator\ClassType::from(PDO::class);
// cria uma função idêntica à guarnição()
$function = Nette\PhpGenerator\GlobalFunction::from('trim');
// cria um fechamento como especificado
$closure = Nette\PhpGenerator\Closure::from(
function (stdClass $a, $b = null) {},
);
A função e o método estão vazios por padrão. Se você quiser carregá-los também, use desta forma (requer
nikic/php-parser
para ser instalado):
$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true);
$function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);
Carregando do arquivo PHP
Você também pode carregar funções, classes, interfaces e enumeros diretamente de uma seqüência de código PHP. Por
exemplo, criamos o objeto ClassType
desta forma:
$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX
<?php
class Demo
{
public $foo;
}
XX);
Ao carregar classes do código PHP, os comentários de linha única fora dos corpos do método são ignorados (por exemplo, para propriedades, etc.) porque esta biblioteca não tem uma API para trabalhar com eles.
Você também pode carregar o arquivo PHP inteiro diretamente, que pode conter qualquer número de classes, funções ou até mesmo vários espaços de nomes:
$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));
O comentário inicial do arquivo e a declaração strict_types
também são carregados. Por outro lado, todos os
outros códigos globais são ignorados.
Isto requer que nikic/php-parser
seja instalado.
Se você precisar manipular o código global em arquivos ou declarações individuais em corpos de métodos, é
melhor usar a biblioteca nikic/php-parser
diretamente.
Manipulador de classe
A classe ClassManipulator fornece ferramentas para a manipulação de classes.
$class = new Nette\PhpGenerator\ClassType('Demo');
$manipulator = new Nette\PhpGenerator\ClassManipulator($class);
O método inheritMethod()
copia um método de uma classe pai ou de uma interface implementada em sua classe. Isso
permite que você substitua o método ou estenda sua assinatura:
$method = $manipulator->inheritMethod('bar');
$method->setBody('...');
O método inheritProperty()
copia uma propriedade de uma classe principal para a sua classe. Isso é útil quando
você deseja ter a mesma propriedade em sua classe, mas possivelmente com um valor padrão diferente:
$property = $manipulator->inheritProperty('foo');
$property->setValue('new value');
O método implementInterface()
implementa automaticamente todos os métodos da interface fornecida em sua
classe:
$manipulator->implementInterface(SomeInterface::class);
// Agora sua classe implementa SomeInterface e inclui todos os seus métodos
Variáveis Dumper
O Dumper retorna uma parábola de representação de uma variável em PHP. Fornece uma saída melhor e mais clara que a
função nativa var_export()
.
$dumper = new Nette\PhpGenerator\Dumper;
$var = ['a', 'b', 123];
echo $dumper->dump($var); // gravuras ['a', 'b', 123]
Tabela de Compatibilidade
O PhpGenerator 4.0 e 4.1 são compatíveis com o PHP 8.0 a 8.3