PhpGenerator 3.6: news from PHP 8.1 and more
PhpGenerator version 3.6 brings support for all the new features of the upcoming PHP 8.1. But that's not all!
Enum
The main new feature of PHP 8.1 is enumeration types. The generator makes it easy to create them:
$enum = Nette\PhpGenerator\ClassType::enum('Suit');
$enum->addCase('Clubs');
$enum->addCase('Diamonds');
$enum->addCase('Hearts');
$enum->addCase('Spades');
echo $enum;
Result:
enum Suit
{
case Clubs;
case Diamonds;
case Hearts;
case Spades;
}
You can also define scalar equivalents to create a “backed” enumeration:
$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');
$enum->addCase('Hearts', '♥');
$enum->addCase('Spades', '♠');
Result:
enum Suit: string
{
case Clubs = '♣';
case Diamonds = '♦';
case Hearts = '♥';
case Spades = '♠';
}
You can add comments or attributes to each case
using
addComment()
and addAttribute()
respectively.
Other new features in PHP 8.1
You can now flag constants as final, create readonly properties, use
intersection types and the new
operator in the default values of
function or method parameters:
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')) // or 'Foo&Bar'
->setReadOnly();
$class->addMethod('__construct')
->addParameter('now', new Literal('new DateTime'));
echo $class;
Prints:
class Php81Features
{
final public const NAME = 'foo';
public readonly Foo&Bar $foo;
public function __construct($now = new DateTime)
{
}
}
Furthermore, Dumper
can output (static) closures using the new
three-dot
syntax.
Functions in Files and Namespaces
You can now add functions to PHP files or namespaces:
$file = new Nette\PhpGenerator\PhpFile;
$class = $file->addClass('Foo\A');
$function = $file->addFunction('Foo\foo');
// or
$namespace = $file->addNamespace('Foo');
$class = $namespace->addClass('A');
$function = $namespace->addFunction('foo');
And also define use-statements for functions and constants:
// use function iter\range;
$namespace->addUseFunction('iter\range');
// use constant PHP_EOL;
$namespace->addUseConstant('PHP_EOL');
Loading Classes from Files and Strings
And watch out, this is big news! You can now load representations of classes, interfaces, functions, enums, etc. directly from a string containing PHP code without running it:
$code = file_get_contents('classes.php');
$file = Nette\PhpGenerator\PhpFile::fromCode($code);
echo $file;
To load a single class, you can directly use:
$class = Nette\PhpGenerator\ClassType::fromCode($code);
This gives you an exact representation of the class, including the bodies of all methods and functions.
(Requires installing
composer require nikic/php-parser
)
Loading Classes from Reflection and Traits
It was already possible to create class representations based
on existing ones using the reflection via ClassType::from()
.
This still works, of course. The only difficulty with reflection is that it
can't tell which methods or properties come from traits. Therefore, Nette simply
materialized the traits, i.e., the representations did not contain
use TraitFoo
, but directly the methods and properties.
trait TraitFoo
{
public function foo()
{
}
}
class Bar
{
use TraitFoo;
}
$class = Nette\PhpGenerator\ClassType::from(Bar::class);
echo $class;
Thus, the created class was written out as:
class Bar
{
public function foo()
{
}
}
Which is functionally quite identical to the original class, but it does not preserve the exact form.
Nette has gradually learned to guess the use of traits quite well, and can now generate code without materialization. As of version 4.0 it will do this automatically, for now we tell it via a parameter:
$class = Nette\PhpGenerator\ClassType::from(Bar::class, materializeTraits: false);
// for PHP < 8 use from(Bar::class, true, false);
Smarter Literals
You can use literals to pass arbitrary PHP code to representations, for example for default values of properties or parameters etc. You can now pass parameters to literals and have them formatted into valid PHP code using placeholders:
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;
Result:
class Demo
{
public function bar($id = 10 + 20)
{
}
}
Comments for Traits
If a class uses traits, these can now have comments. They are important for example for PHPStan.
$class = new Nette\PhpGenerator\ClassType('Bar');
$class->addTrait('GenericTrait', true)
->addComment('@use GenericTrait<int>');
Result:
class Bar
{
/** @use GenericTrait<int> */
use GenericTrait;
}
The true
parameter is important – originally the
addTrait()
method returned a ClassType
object (i.e.
$this
), with a true value it returns a new TraitUse
object that allows to add a comment or resolution information
(addResolution('hello as protected')
). In version 4.0, this will be
the default behavior.
Lots of other little things
The new version has a lot of other tweaks like:
- checking for forbidden class names
- ability to set the maximum line width
$printer->wrapLength
getType(true)
andgetReturnType(true)
return Type objects
Sign in to submit a comment