Gerador de código PHP

Você está procurando uma ferramenta para gerar código PHP para classes, funções ou arquivos completos?
  • 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

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 setVariadics():

$method = $class->addMethod('count');
$method->setVariadics(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.

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

versão: 4.0