Gerador de código PHP

Procurando uma ferramenta para gerar código PHP para classes, funções ou arquivos completos?
  • 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.

versão: 4.0