PhpGenerator 3.6: novinky z PHP 8.1 a spousta navíc

před rokem od David Grudl  

PhpGenerator ve verzi 3.6 přináší podporu pro všechny novinky chystaného PHP 8.1. Ale to není zdaleka všechno.

Enum

Hlavní novinkou PHP 8.1 jsou výčtové typy. Pomocí generátoru je vytvoříte snadno:

$enum = Nette\PhpGenerator\ClassType::enum('Suit');
$enum->addCase('Clubs');
$enum->addCase('Diamonds');
$enum->addCase('Hearts');
$enum->addCase('Spades');
echo $enum;

Výsledek:

enum Suit
{
        case Clubs;
        case Diamonds;
        case Hearts;
        case Spades;
}

Můžete také definovat skalární ekvivalenty a vytvořit tak „backed“ výčet:

$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');
$enum->addCase('Hearts', '♥');
$enum->addCase('Spades', '♠');

Výsledek:

enum Suit: string
{
        case Clubs = '♣';
        case Diamonds = '♦';
        case Hearts = '♥';
        case Spades = '♠';
}

Ke každému case je možné přidat komentář nebo atributy pomocí addComment() resp. addAttribute().

Další novinky PHP 8.1

Nově lze konstanty označovat jako finální, vytvářet readonly properties, používat intersection typy a operátor new ve výchozích hodnotách parametrů funkcí či metod:

use Nette\PhpGenerator\Literal;
use Nette\PhpGenerator\Type;

$class = new Nette\PhpGenerator\ClassType('Php81Features');

$class->addConstant('NAME', 'foo')
	->setFinal();

$class->addProperty('foo')
	->setType(Type::intersection('Foo', 'Bar')) // nebo 'Foo&Bar'
	->setReadOnly();

$class->addMethod('__construct')
	->addParameter('now', new Literal('new DateTime'));

echo $class;

Vypíše:

class Php81Features
{
	final public const NAME = 'foo';

	public readonly Foo&Bar $foo;

	public function __construct($now = new DateTime)
	{
	}
}

Dále Dumper umí vypisovat (statické) closures novou trojtečkovou syntaxí.

Funkce v souborech a jmenných prostorech

Nově můžete do PHP souborů nebo jmenných prostorů přidávat i funkce:

$file = new Nette\PhpGenerator\PhpFile;
$class = $file->addClass('Foo\A');
$function = $file->addFunction('Foo\foo');

// nebo
$namespace = $file->addNamespace('Foo');
$class = $namespace->addClass('A');
$function = $namespace->addFunction('foo');

A také definovat use pro funkce a konstanty:

// use function iter\range;
$namespace->addUseFunction('iter\range');
// use constant PHP_EOL;
$namespace->addUseConstant('PHP_EOL');

Načítání tříd ze souborů a řetězců

A pozor, tohle je velká novinka! Nyní lze reprezentace tříd, rozhraní, funkcí, enumů atd. načítat přímo z řetězce obsahujícího PHP kód, aniž by byl spuštěn:

$code = file_get_contents('classes.php');
$file = Nette\PhpGenerator\PhpFile::fromCode($code);
echo $file;

Pro načtení jediné třídy můžete použít rovnou:

$class = Nette\PhpGenerator\ClassType::fromCode($code);

Získáte tak přesnou reprezentaci třídy, včetně těl všech metod a funkcí.

(Vyžaduje doinstalovat composer require nikic/php-parser)

Načítání tříd z reflexe a traity

Už dříve bylo možné vytvářet reprezentace tříd na základě existujících s vyžitím reflexe metodou ClassType::from(). To samozřejmě funguje stále. Jediná obtíž s reflexí je ta, že neumí sdělit, které metody nebo properties pocházejí z trait. Proto Nette jednoduše traity materializovalo, tj. reprezentace neobsahovala use TraitFoo, ale přímo těla metod a vlastností.

trait TraitFoo
{
	public function foo()
	{
	}
}

class Bar
{
	use TraitFoo;
}

$class = Nette\PhpGenerator\ClassType::from(Bar::class);
echo $class;

Takto vytvořená třída se tedy vypsala jako:

class Bar
{
	public function foo()
	{
	}
}

Což je funkčně zcela identické s originální třídou, ale přesnou podobu to nezachovává.

Nette se postupně naučilo celkem dobře použití trait odhadovat a dnes už umí vygenerovat kód bez materializace. Od verze 4.0 to bude dělat automaticky, zatím mu to sdělíme pomocí parametru:

$class = Nette\PhpGenerator\ClassType::from(Bar::class, materializeTraits: false);
// pro PHP < 8 použijte from(Bar::class, true, false);

Chytřejší literály

Pomocí literalů můžete předávat do reprezentací libovolný kód PHP, například pro výchozí hodnoty vlastností nebo parametrů atd. Nyní lze literálům předat parametry a nechat je zformátovat do platného kódu PHP pomocí zástupných znaků:

use Nette\PhpGenerator\Literal;

$a = 10;
$b = 20;
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addMethod('bar')
	->addParameter('id', new Literal('? + ?', [$a, $b]));

echo $class;

Výsledek:

class Demo
{
	public function bar($id = 10 + 20)
	{
	}
}

Komentáře u trait

Pokud třída používá traity, tyto mohou nově mít komentáře. Jsou důležité třeba pro PHPStan.

$class = new Nette\PhpGenerator\ClassType('Bar');
$class->addTrait('GenericTrait', true)
	->addComment('@use GenericTrait<int>');

Vygeneruje:

class Bar
{
	/** @use GenericTrait<int> */
	use GenericTrait;
}

Důležitý je parametr true – původně metoda addTrait() vracela objekt ClassType (tedy $this), s uvedením true vrací nový objekt TraitUse, který právě umožňuje přidat komentář nebo informace o rozlišení (addResolution('hello as protected')). Ve verzi 4.0 tohle bude výchozí chování.

Spousta další drobností

Nová verze má spoustu dalších vychytávek jako třeba:

  • kontrola zakázaných názvů tříd
  • možnost nastavit maximální šířku řádku $printer->wrapLength
  • getType(true) a getReturnType(true) vrací objekty Type