Nette PHP Generator Brings the Power of PHP 8.4

3 days ago by David Grudl  

Property Hooks and Asymmetric Visibility in PHP 8.4 represent a significant leap in object-oriented programming—finally, we can say goodbye to clunky getters and setters. Nette PHP Generator introduces support for these innovations alongside the release of PHP 8.4, offering an intuitive way to use these modern features in your code.

Ditch Getters and Setters

Imagine creating a Person class and wanting to validate the age property. Previously, your code might have looked like this:

class Person
{
    private int $age;

    public function setAge(int $age): void
    {
        if ($age < 0) {
            throw new \InvalidArgumentException;
        }
        $this->age = $age;
    }

    public function getAge(): int
    {
        return $this->age;
    }
}

With property hooks, the code becomes more elegant and readable:

class Person
{
    public int $age {
        set {
            if ($value < 0) {
                throw new \InvalidArgumentException;
            }
            $this->age = $value;
        }
    }
}

Generating this code is easy with Nette PhpGenerator. Simply use the new addHook() method:

$class = new ClassType('Person');
$class->addProperty('age')
    ->setType('int')
    ->addHook('set')
        ->setBody(<<<'PHP'
            if ($value < 0) {
                throw new InvalidArgumentException;
            }
            $this->age = $value;
            PHP);

You can also use a shorthand syntax:

$class = new ClassType('Person');
$class->addProperty('age')
    ->setType('int')
    ->addHook('set', '$value >= 0 ? $value : throw new \\InvalidArgumentException');

This generates a “short-setter”:

class Person
{
    public int $age {
        set => $value >= 0 ? $value : throw new \InvalidArgumentException;
    }
}

Properties in Interfaces and Abstract Classes

Yes, PHP now supports properties in interfaces and abstract classes. First, let’s define a property in an interface:

$interface = new Nette\PhpGenerator\InterfaceType('Named');
$interface->addProperty('name')
    ->setType('string')
    ->addHook('get'); // Read-only requirement

This generates:

interface Named
{
    public string $name { get; }
}

Here’s an example of an abstract class combining an abstract setter with a concrete getter:

$abstract = new Nette\PhpGenerator\ClassType('Person')
    ->setAbstract();
$prop = $abstract->addProperty('email')
    ->setType('string')
    ->setAbstract(); // Abstract property
$prop->addHook('set') // Abstract setter
    ->setAbstract();
$prop->addHook('get', '$this->email'); // Concrete getter

This generates:

abstract class Person
{
    public string $email {
        set;
        get => $this->email;
    }
}

Finally, let’s create the implementing class. Here, ClassManipulator helps:

$class = new ClassType('Employee');

// Use the manipulator to implement Person and Named
$manipulator = new Nette\PhpGenerator\ClassManipulator($class);
$manipulator->implement('Person'); // Adds the email property
$manipulator->implement('Named'); // Adds the name property

// Provide the implementation for the abstract property from Person
$class->getProperty('email')
    ->addHook('set')
        ->setBody(<<<'PHP'
            if (!Nette\Utils\Validators::isEmail($value)) {
                throw new InvalidArgumentException;
            }
            $this->email = $value;
            PHP);

echo $class;

This generates:

class Employee extends Person implements Named
{
    public string $email {
        set {
            if (!Nette\Utils\Validators::isEmail($value)) {
                throw new InvalidArgumentException;
            }
            $this->email = $value;
        }
    }

    public string $name;
}

Asymmetric Visibility: Precise Access Control

Asymmetric visibility brings finer control over who can read and who can write object properties in PHP.

You can configure visibility using either the setVisibility() method with two parameters or the setPublic(), setProtected(), or setPrivate() methods with a mode parameter specifying whether the visibility applies to reading or writing. The default mode is 'get'.

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

$class->addProperty('name')
    ->setType('string')
    ->setVisibility('public', 'private'); // Public for reading, private for writing

$class->addProperty('id')
    ->setType('int')
    ->setProtected('set'); // Protected for writing

This generates:

class Demo
{
    public private(set) string $name;

    protected(set) int $id;
}

Asymmetric visibility can be effectively combined with property hooks and used in class hierarchies.


All these features are fully supported in the methods ClassLike::from() for generating from existing classes and ClassLike::fromCode() for loading from source files. The fromCode() method requires the nikic/php-parser package, version 5.3.1 or later.

Support for these revolutionary features is introduced in Nette PHP Generator version 4.1.7.