Gerador de código PHP
- Suporta todos os recursos mais recentes do PHP (como property hooks, enums, atributos, etc.)
- Permite modificar facilmente classes existentes
- O código de saída está em conformidade com o estilo de codificação PSR-12 / PER
- Biblioteca madura, estável e amplamente utilizada
Instalação
Baixe e instale a biblioteca usando o Composer:
composer require nette/php-generator
A compatibilidade com PHP pode ser encontrada na tabela.
Classes
Vamos começar com um exemplo de criação de classe usando ClassType:
$class = new Nette\PhpGenerator\ClassType('Demo');
$class
->setFinal()
->setExtends(ParentClass::class)
->addImplement(Countable::class)
->addComment("Descrição da classe.\nSegunda linha\n")
->addComment('@property-read Nette\Forms\Form $form');
// o código é simplesmente gerado convertendo para string ou usando echo:
echo $class;
Retorna o seguinte resultado:
/**
* Descrição da classe
* Segunda linha
*
* @property-read Nette\Forms\Form $form
*/
final class Demo extends ParentClass implements Countable
{
}
Também podemos usar o chamado printer para gerar o código, que, ao contrário de echo $class
, poderemos configurar posteriormente:
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class);
Podemos adicionar constantes (classe Constant) e variáveis (classe Property):
$class->addConstant('ID', 123)
->setProtected() // visibilidade das constantes
->setType('int')
->setFinal();
$class->addProperty('items', [1, 2, 3])
->setPrivate() // ou setVisibility('private')
->setStatic()
->addComment('@var int[]');
$class->addProperty('list')
->setType('?array')
->setInitialized(); // imprime '= null'
Gera:
final protected const int ID = 123;
/** @var int[] */
private static $items = [1, 2, 3];
public ?array $list = null;
E podemos adicionar métodos:
$method = $class->addMethod('count')
->addComment('Count it.')
->setFinal()
->setProtected()
->setReturnType('?int') // tipos de retorno em métodos
->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);
}
Parâmetros promovidos introduzidos no 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 = [],
) {
}
Propriedades e classes somente leitura podem ser marcadas usando a função setReadOnly()
.
Se a propriedade, constante, método ou parâmetro adicionado já existir, uma exceção será lançada.
Membros da classe podem ser removidos usando removeProperty()
, removeConstant()
,
removeMethod()
ou removeParameter()
.
Você também pode adicionar objetos Method
, Property
ou Constant
existentes à
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ê também 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 Trait
Você pode criar interfaces e traits (classes InterfaceType e TraitType):
$interface = new Nette\PhpGenerator\InterfaceType('MyInterface');
$trait = new Nette\PhpGenerator\TraitType('MyTrait');
Usando traits:
$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
Enums, introduzidos no PHP 8.1, podem ser facilmente criados assim: (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 e criar assim um enum “backed”:
$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');
É possível adicionar um comentário ou atributos a cada case usando
addComment()
ou addAttribute()
.
Classes Anônimas
Passamos null
como nome e temos 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ções Globais
O código das funções é gerado pela classe GlobalFunction:
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('return $a + $b;');
$function->addParameter('a');
$function->addParameter('b');
echo $function;
// ou use PsrPrinter para saída em conformidade com PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);
Resultado:
function foo($a, $b)
{
return $a + $b;
}
Funções Anônimas
O código das funções anônimas é gerado pela classe Closure:
$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('return $a + $b;');
$closure->addParameter('a');
$closure->addParameter('b');
$closure->addUse('c')
->setReference();
echo $closure;
// ou use PsrPrinter para saída em conformidade com PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);
Resultado:
function ($a, $b) use (&$c) {
return $a + $b;
}
Funções de seta abreviadas
Você também pode imprimir uma função anônima abreviada usando o printer:
$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
Assinaturas de métodos e funções
Métodos são representados pela classe Method. Você pode definir visibilidade, valor de retorno, adicionar comentários, atributos, etc:
$method = $class->addMethod('count')
->addComment('Count it.')
->setFinal()
->setProtected()
->setReturnType('?int');
Parâmetros individuais são representados pela classe Parameter. Novamente, você pode definir todas as propriedades imagináveis:
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
// function count(array &$items = [])
Para definir os chamados parâmetros variádicos (ou também operador splat), use setVariadic()
:
$method = $class->addMethod('count');
$method->setVariadic(true);
$method->addParameter('items');
Gera:
function count(...$items)
{
}
Corpos de métodos e funções
O corpo pode ser passado de uma vez para o método setBody()
ou gradualmente (linha por linha) chamando
repetidamente 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 placeholders especiais para inserir variáveis facilmente.
Placeholders 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);
}
Placeholder para variadic ...?
$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 para PHP 8 usando ...?:
$items = ['foo' => 1, 'bar' => true];
$function->setBody('myfunc(...?:);', [$items]);
// myfunc(foo: 1, bar: true);
O placeholder é escapado com uma barra invertida \?
$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;
}
Printer e conformidade com PSR
Para gerar código PHP, use a classe Printer:
$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, oferece métodos como printFunction()
,
printNamespace()
, etc.
Também está disponível a classe PsrPrinter
, 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 sob medida? Crie sua própria versão herdando da classe Printer
. Estas
variáveis podem ser reconfiguradas:
class MyPrinter extends Nette\PhpGenerator\Printer
{
// comprimento da linha após o qual ocorrerá a quebra de linha
public int $wrapLength = 120;
// caractere de indentação, pode ser substituído por uma sequência de espaços
public string $indentation = "\t";
// número de linhas em branco entre propriedades
public int $linesBetweenProperties = 0;
// número de linhas em branco entre métodos
public int $linesBetweenMethods = 2;
// número de linhas em branco entre grupos de 'use statements' 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;
// coloque um parâmetro por linha, mesmo que tenha um atributo ou seja suportado
public bool $singleParameterOnOneLine = false;
// omite namespaces que não contêm nenhuma classe ou função
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 o Printer
padrão e o PsrPrinter
diferem? Por que não há apenas um printer no
pacote, o PsrPrinter
?
O Printer
padrão formata o código como fazemos em todo o Nette. Como o Nette surgiu muito antes do PSR, e
também porque o PSR por muitos anos não entregou padrões em tempo hábil, mas talvez apenas com vários anos de atraso após a
introdução de um novo recurso no PHP, aconteceu que o padrão
de codificação difere em alguns pequenos detalhes. A maior diferença é apenas o uso de tabulações em vez de espaços.
Sabemos que usar tabulações em nossos projetos permite o ajuste de largura, que é essencial para pessoas com deficiência
visual. Um exemplo de pequena diferença é a colocação da chave de abertura em uma linha separada para funções e
métodos, e sempre. A recomendação do PSR nos parece ilógica e leva a uma redução da clareza do código.
Tipos
Qualquer tipo ou tipo union/intersection pode ser passado como uma string, você também pode usar constantes predefinidas para tipos nativos:
use Nette\PhpGenerator\Type;
$member->setType('array'); // ou Type::Array;
$member->setType('?array'); // ou Type::nullable(Type::Array);
$member->setType('array|string'); // ou Type::union(Type::Array, Type::String)
$member->setType('Foo&Bar'); // ou Type::intersection(Foo::class, Bar::class)
$member->setType(null); // remove o tipo
O mesmo se aplica ao método setReturnType()
.
Literais
Usando Literal
, você pode passar qualquer código PHP, por exemplo, para valores padrão de propriedades ou
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 parâmetros para Literal
e formatá-los em código PHP válido usando placeholders:
new Literal('substr(?, ?)', [$a, $b]);
// gera por exemplo: substr('hello', 5);
Um literal representando a criação de um novo objeto pode ser facilmente gerado usando o método new
:
Literal::new(Demo::class, [$a, 'foo' => $b]);
// gera por exemplo: new Demo(10, foo: 20)
Atributos
Atributos do PHP 8 podem ser adicionados a todas as classes, métodos, propriedades, constantes, enums, funções, closures e parâmetros. Também é possível usar literais 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,
) {
}
}
Property Hooks
Usando property hooks (representados pela classe PropertyHook), você pode definir operações get e set para propriedades, um recurso introduzido no 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;
Gera:
class Demo
{
public string $firstName {
set(string $value) => strtolower($value);
get {
return ucfirst($this->firstName);
}
}
}
Propriedades e property hooks podem ser abstratos ou finais:
$class->addProperty('id')
->setType('int')
->addHook('get')
->setAbstract();
$class->addProperty('role')
->setType('string')
->addHook('set', 'strtolower($value)')
->setFinal();
Visibilidade Assimétrica
O PHP 8.4 introduz visibilidade assimétrica para propriedades. Você pode definir diferentes níveis de acesso para leitura e escrita.
A visibilidade pode ser definida usando o método setVisibility()
com dois parâmetros, ou usando
setPublic()
, setProtected()
ou setPrivate()
com o parâmetro mode
, que
especifica se a visibilidade se aplica à leitura ou escrita da propriedade. O modo padrão é 'get'
.
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addProperty('name')
->setType('string')
->setVisibility('public', 'private'); // public para leitura, private para escrita
$class->addProperty('id')
->setType('int')
->setProtected('set'); // protected para escrita
echo $class;
Gera:
class Demo
{
public private(set) string $name;
protected(set) int $id;
}
Namespace
Classes, propriedades, interfaces e enums (doravante classes) podem ser agrupados em namespaces representados pela classe PhpNamespace:
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
// criamos novas classes no namespace
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');
// ou inserimos uma classe existente no namespace
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);
Se a classe já existir, uma exceção será lançada.
Você pode 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 o nome totalmente qualificado de uma classe, função ou constante de acordo com os aliases definidos, use
o método simplifyName
:
echo $namespace->simplifyName('Foo\Bar'); // 'Bar', porque 'Foo' é o namespace atual
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', devido ao use-statement definido
Você pode converter o nome simplificado de uma classe, função ou constante para o nome totalmente qualificado usando
o método resolveName
:
echo $namespace->resolveName('Bar'); // 'Foo\Bar'
echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'
Traduções de nomes de classes
Quando uma classe faz parte de um namespace, ela é renderizada de forma ligeiramente diferente: todos os tipos (por exemplo, typehints, tipos de retorno, nome da classe pai, interfaces implementadas, propriedades e atributos usados) são automaticamente traduzidos (a menos que você desative isso, veja abaixo). Isso significa que você deve usar nomes de classe completos nas definições e eles serão substituídos por aliases (de acordo com as cláusulas use) ou por 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') // será simplificado para A
->addTrait('Bar\AliasedClass'); // será simplificado para AliasedClass
$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // em comentários, simplificamos manualmente
$method->addParameter('arg')
->setType('Bar\OtherClass'); // será traduzido para \Bar\OtherClass
echo $namespace;
// ou use 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 tradução automática pode ser desativada desta forma:
$printer = new Nette\PhpGenerator\Printer; // ou PsrPrinter
$printer->setTypeResolving(false);
echo $printer->printNamespace($namespace);
Arquivos PHP
Classes, funções e namespaces podem ser agrupados em arquivos PHP representados pela classe PhpFile:
$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('Este arquivo é gerado automaticamente.');
$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 use PsrPrinter para saída em conformidade com PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);
Resultado:
<?php
/**
* Este arquivo é gerado automaticamente.
*/
declare(strict_types=1);
namespace Foo;
class A
{
}
function foo()
{
}
Aviso: Não é possível adicionar nenhum outro código aos arquivos fora de funções e classes.
Geração baseada em existentes
Além de poder modelar classes e funções usando a API descrita acima, você também pode gerá-las automaticamente com base em padrões existentes:
// cria uma classe igual à classe PDO
$class = Nette\PhpGenerator\ClassType::from(PDO::class);
// cria uma função idêntica à função trim()
$function = Nette\PhpGenerator\GlobalFunction::from('trim');
// cria uma closure com base na fornecida
$closure = Nette\PhpGenerator\Closure::from(
function (stdClass $a, $b = null) {},
);
Os corpos das funções e métodos estão vazios por padrão. Se você também quiser carregá-los, use esta abordagem (requer
a instalação do pacote nikic/php-parser
):
$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true);
$function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);
Carregando de arquivos PHP
Você também pode carregar funções, classes, interfaces e enums diretamente de uma string contendo código PHP. Por exemplo,
criamos um objeto ClassType
assim:
$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX
<?php
class Demo
{
public $foo;
}
XX);
Ao carregar classes de código PHP, comentários de linha única fora dos corpos dos métodos são ignorados (por exemplo, em propriedades, etc.), pois esta biblioteca não possui uma API para trabalhar com eles.
Você também pode carregar diretamente um arquivo PHP inteiro, que pode conter qualquer número de classes, funções ou até namespaces:
$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, todo
o outro código global é ignorado.
É necessário que nikic/php-parser
esteja instalado.
Se você precisar manipular código global em arquivos ou instruções individuais nos corpos dos métodos, é
melhor usar diretamente a biblioteca nikic/php-parser
.
Class Manipulator
A classe ClassManipulator fornece ferramentas para manipular classes.
$class = new Nette\PhpGenerator\ClassType('Demo');
$manipulator = new Nette\PhpGenerator\ClassManipulator($class);
O método inheritMethod()
copia um método da classe pai ou interface implementada para sua classe. Isso permite
sobrescrever o método ou estender sua assinatura:
$method = $manipulator->inheritMethod('bar');
$method->setBody('...');
```
O método `inheritProperty()` copia uma propriedade da classe pai para sua classe. É útil quando você deseja ter a mesma propriedade em sua classe, mas talvez com um valor padrão diferente:
```php
$property = $manipulator->inheritProperty('foo');
$property->setValue('novo valor');
O método implement()
implementa automaticamente todos os métodos e propriedades da interface ou classe abstrata
fornecida em sua classe:
$manipulator->implement(SomeInterface::class);
// Agora sua classe implementa SomeInterface e contém todos os seus métodos
Exibição de variáveis
A classe Dumper
converte uma variável em código PHP analisável. Ela fornece uma saída melhor e mais clara do
que a função padrão var_export()
.
$dumper = new Nette\PhpGenerator\Dumper;
$var = ['a', 'b', 123];
echo $dumper->dump($var); // imprime ['a', 'b', 123]
Tabela de compatibilidade
PhpGenerator 4.1 é compatível com PHP 8.0 a 8.4.