Skip to content

Repository files navigation

Nette PHP Generator

Latest Stable VersionDownloads this Month

Are you looking for a tool to generate PHP code for classes, functions, or complete PHP files?

✅ Supports all the latest PHP features like property hooks, enums, attributes, etc.
✅ Allows you to easily modify existing classes
✅ Output compliant with PSR-12 / PER coding style
✅ Highly mature, stable, and widely used library

Installation

Download and install the library using the Composer tool:

composer require nette/php-generator

PhpGenerator 4.2 is compatible with PHP 8.1 to 8.5. Documentation can be found on the library's website.

Do you like PHP Generator? Are you looking forward to the new features?

Buy me a coffee

Thank you!

Classes

Let's start with an example of creating a class using ClassType:

$class = newNette\PhpGenerator\ClassType('Demo');
$class
->setFinal()
->setExtends(ParentClass::class)
->addImplement(Countable::class)
->addComment("Class description.\nSecond line\n")
->addComment('@property-read Nette\Forms\Form $form');
// generate code simply by typecasting to string or using echo:echo$class;

This will return:

/** * Class description * Second line * * @property-read Nette\Forms\Form $form */finalclass Demo extends ParentClass implements Countable
{
}

To generate the code, you can also use a so-called printer, which, unlike echo $class, can be further configured:

$printer = newNette\PhpGenerator\Printer;
echo$printer->printClass($class);

You can add constants (class Constant) and properties (class Property):

$class->addConstant('ID', 123)
->setProtected() // constant visibility
->setType('int')
->setFinal();
$class->addProperty('items', [1, 2, 3])
->setPrivate() // or setVisibility('private')
->setStatic()
->addComment('@var int[]');
$class->addProperty('list')
->setType('?array')
->setInitialized(); // outputs '= null'

This will generate:

finalprotectedconstintID = 123;
/** @var int[] */privatestatic$items = [1, 2, 3];
public ?array $list = null;

And you can add methods:

$method = $class->addMethod('count')
->addComment('Count it.')
->setFinal()
->setProtected()
->setReturnType('?int') // return types for methods
->setBody('return count($items ?: $this->items);');
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []

The result is:

/** * Count it. */finalprotectedfunctioncount(array &$items = []): ?int
{
returncount($items ?: $this->items);
}

Promoted parameters introduced in PHP 8.0 can be passed to the constructor:

$method = $class->addMethod('__construct');
$method->addPromotedParameter('name');
$method->addPromotedParameter('args', [])
->setPrivate();

The result is:

publicfunction__construct(
public$name,
private$args = [],
) {
}

Readonly properties and classes be marked using the setReadOnly() function.


If an added property, constant, method, or parameter already exists, an exception is thrown.

Class members can be removed using removeProperty(), removeConstant(), removeMethod(), or removeParameter().

You can also add existing Method, Property, or Constant objects to the class:

$method = newNette\PhpGenerator\Method('getHandle');
$property = newNette\PhpGenerator\Property('handle');
$const = newNette\PhpGenerator\Constant('ROLE');
$class = (newNette\PhpGenerator\ClassType('Demo'))
->addMember($method)
->addMember($property)
->addMember($const);

You can also clone existing methods, properties, and constants under a different name using cloneWithName():

$methodCount = $class->getMethod('count');
$methodRecount = $methodCount->cloneWithName('recount');
$class->addMember($methodRecount);

Interfaces or Traits

You can create interfaces and traits (classes InterfaceType and TraitType):

$interface = newNette\PhpGenerator\InterfaceType('MyInterface');
$trait = newNette\PhpGenerator\TraitType('MyTrait');

Using a trait:

$class = newNette\PhpGenerator\ClassType('Demo');
$class->addTrait('SmartObject');
$class->addTrait('MyTrait')
->addResolution('sayHello as protected')
->addComment('@use MyTrait<Foo>');
echo$class;

The result is:

class Demo
{
use SmartObject;
/** @use MyTrait<Foo> */use MyTrait {
sayHello asprotected;
}
}

Enums

You can easily create enums introduced in PHP 8.1 like this (class EnumType):

$enum = newNette\PhpGenerator\EnumType('Suit');
$enum->addCase('Clubs');
$enum->addCase('Diamonds');
$enum->addCase('Hearts');
$enum->addCase('Spades');
echo$enum;

The result is:

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

You can also define scalar equivalents and create a "backed" enum:

$enum->addCase('Clubs', '');
$enum->addCase('Diamonds', '');

For each case, you can add a comment or attributes using addComment() or addAttribute().

Anonymous Classes

Pass null as the name, and you have an anonymous class:

$class = newNette\PhpGenerator\ClassType(null);
$class->addMethod('__construct')
->addParameter('foo');
echo'$obj = new class ($val) ' . $class . ';';

The result is:

$obj = newclass ($val) {
publicfunction__construct($foo)
{
}
};

Global Functions

The code for functions is generated by the class GlobalFunction:

$function = newNette\PhpGenerator\GlobalFunction('foo');
$function->setBody('return $a + $b;');
$function->addParameter('a');
$function->addParameter('b');
echo$function;
// or use the PsrPrinter for output compliant with PSR-2 / PSR-12 / PER// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);

The result is:

functionfoo($a, $b)
{
return$a + $b;
}

Anonymous Functions

The code for anonymous functions is generated by the class Closure:

$closure = newNette\PhpGenerator\Closure;
$closure->setBody('return $a + $b;');
$closure->addParameter('a');
$closure->addParameter('b');
$closure->addUse('c')
->setReference();
echo$closure;
// or use the PsrPrinter for output compliant with PSR-2 / PSR-12 / PER// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);

The result is:

function ($a, $b) use (&$c) {
return$a + $b;
}

Short Arrow Functions

You can also output a short anonymous function using the printer:

$closure = newNette\PhpGenerator\Closure;
$closure->setBody('$a + $b');
$closure->addParameter('a');
$closure->addParameter('b');
echo (newNette\PhpGenerator\Printer)->printArrowFunction($closure);

The result is:

fn($a, $b) => $a + $b

Method and Function Signatures

Methods are represented by the class Method. You can set visibility, return value, add comments, attributes, etc.:

$method = $class->addMethod('count')
->addComment('Count it.')
->setFinal()
->setProtected()
->setReturnType('?int');

Individual parameters are represented by the class Parameter. Again, you can set all conceivable properties:

$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []// function count(&$items = [])

To define the so-called variadics parameters (or also the splat, spread, ellipsis, unpacking or three dots operator), use setVariadic():

$method = $class->addMethod('count');
$method->setVariadic(true);
$method->addParameter('items');

This generates:

functioncount(...$items)
{
}

Method and Function Bodies

The body can be passed all at once to the setBody() method or gradually (line by line) by repeatedly calling addBody():

$function = newNette\PhpGenerator\GlobalFunction('foo');
$function->addBody('$a = rand(10, 20);');
$function->addBody('return $a;');
echo$function;

The result is:

functionfoo()
{
$a = rand(10, 20);
return$a;
}

You can use special placeholders for easy variable insertion.

Simple placeholders ?

$str = 'any string';
$num = 3;
$function = newNette\PhpGenerator\GlobalFunction('foo');
$function->addBody('return substr(?, ?);', [$str, $num]);
echo$function;

The result is:

functionfoo()
{
returnsubstr('any string', 3);
}

Placeholder for variadic ...?

$items = [1, 2, 3];
$function = newNette\PhpGenerator\GlobalFunction('foo');
$function->setBody('myfunc(...?);', [$items]);
echo$function;

The result is:

functionfoo()
{
myfunc(1, 2, 3);
}

You can also use named parameters for PHP 8 with ...?:

$items = ['foo' => 1, 'bar' => true];
$function->setBody('myfunc(...?:);', [$items]);
// myfunc(foo: 1, bar: true);

The placeholder is escaped with a backslash \?

$num = 3;
$function = newNette\PhpGenerator\GlobalFunction('foo');
$function->addParameter('a');
$function->addBody('return $a \? 10 : ?;', [$num]);
echo$function;

The result is:

functionfoo($a)
{
return$a ? 10 : 3;
}

Printer and PSR Compliance

The Printer class is used for generating PHP code:

$class = newNette\PhpGenerator\ClassType('Demo');
// ...$printer = newNette\PhpGenerator\Printer;
echo$printer->printClass($class); // same as: echo $class

It can generate code for all other elements, offering methods like printFunction(), printNamespace(), etc.

There's also the PsrPrinter class, which outputs in accordance with PSR-2 / PSR-12 / PER coding style:

$printer = newNette\PhpGenerator\PsrPrinter;
echo$printer->printClass($class);

Need custom behavior? Create your own version by inheriting the Printer class. You can reconfigure these variables:

class MyPrinter extendsNette\PhpGenerator\Printer
{
// length of the line after which the line will breakpublicint$wrapLength = 120;
// indentation character, can be replaced with a sequence of spacespublicstring$indentation = "\t";
// number of blank lines between propertiespublicint$linesBetweenProperties = 0;
// number of blank lines between methodspublicint$linesBetweenMethods = 2;
// number of blank lines between 'use statements' groups for classes, functions, and constantspublicint$linesBetweenUseTypes = 0;
// position of the opening curly brace for functions and methodspublicbool$bracesOnNextLine = true;
// place one parameter on one line, even if it has an attribute or is supportedpublicbool$singleParameterOnOneLine = false;
// omits namespaces that do not contain any class or functionpublicbool$omitEmptyNamespaces = true;
// separator between the right parenthesis and return type of functions and methodspublicstring$returnTypeColon = ': ';
}

How and why does the standard Printer differ from PsrPrinter? Why isn't there just one printer, the PsrPrinter, in the package?

The standard Printer formats the code as we do throughout Nette. Since Nette was established much earlier than PSR, and also because PSR took years to deliver standards on time, sometimes even several years after introducing a new feature in PHP, it resulted in a coding standard that differs in a few minor aspects. The major difference is the use of tabs instead of spaces. We know that by using tabs in our projects, we allow for width customization, which is essential for people with visual impairments. An example of a minor difference is placing the curly brace on a separate line for functions and methods, always. The PSR recommendation seems illogical to us and leads to reduced code clarity.

Types

Every type or union/intersection type can be passed as a string; you can also use predefined constants for native types:

useNette\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 the type

The same applies to the setReturnType() method.

Literals

Using Literal, you can pass any PHP code, for example, for default property values or parameters, etc:

useNette\PhpGenerator\Literal;
$class = newNette\PhpGenerator\ClassType('Demo');
$class->addProperty('foo', newLiteral('Iterator::SELF_FIRST'));
$class->addMethod('bar')
->addParameter('id', newLiteral('1 + 2'));
echo$class;

Result:

class Demo
{
public$foo = Iterator::SELF_FIRST;
publicfunctionbar($id = 1 + 2)
{
}
}

You can also pass parameters to Literal and have them formatted into valid PHP code using placeholders:

newLiteral('substr(?, ?)', [$a, $b]);
// generates for example: substr('hello', 5);

A literal representing the creation of a new object can easily be generated using the new method:

Literal::new(Demo::class, [$a, 'foo' => $b]);
// generates for example: new Demo(10, foo: 20)

Attributes

With PHP 8, you can add attributes to all classes, methods, properties, constants, enum cases, functions, closures, and parameters. You can also use literals as parameter values.

$class = newNette\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;

Result:

#[Table(name: 'user', constraints: [newUniqueConstraint(name: 'ean', columns: ['ean'])])]
class Demo
{
#[Deprecated]
public$list;
#[Foo\Cached(mode: true)]
publicfunctioncount(
#[Bar]
$items,
) {
}
}

Property Hooks

You can also define property hooks (represented by the class PropertyHook) for get and set operations, a feature introduced in PHP 8.4:

$class = newNette\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;

This generates:

class Demo
{
publicstring$firstName {
set(string$value) => strtolower($value);
get {
returnucfirst($this->firstName);
}
}
}

Properties and property hooks can be abstract or final:

$class->addProperty('id')
->setType('int')
->addHook('get')
->setAbstract();
$class->addProperty('role')
->setType('string')
->addHook('set', 'strtolower($value)')
->setFinal();

Asymmetric Visibility

PHP 8.4 introduces asymmetric visibility for properties. You can set different access levels for reading and writing. The visibility can be set using either the setVisibility() method with two parameters, or by using setPublic(), setProtected(), or setPrivate() with the mode parameter that specifies whether the visibility applies to getting or setting the property. The default mode is 'get'.

$class = newNette\PhpGenerator\ClassType('Demo');
$class->addProperty('name')
->setType('string')
->setVisibility('public', 'private'); // public for read, private for write$class->addProperty('id')
->setType('int')
->setProtected('set'); // protected for writeecho$class;

This generates:

class Demo
{
publicprivate(set)string$name;
protected(set)int$id;
}

Namespace

Classes, traits, interfaces, and enums (hereafter referred to as classes) can be grouped into namespaces represented by the PhpNamespace class:

$namespace = newNette\PhpGenerator\PhpNamespace('Foo');
// create new classes in the namespace$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');
// or insert an existing class into the namespace$class = newNette\PhpGenerator\ClassType('Task');
$namespace->add($class);

If the class already exists, an exception is thrown.

You can define use clauses:

// 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');

To simplify a fully qualified class, function, or constant name based on defined aliases, use the simplifyName method:

echo$namespace->simplifyName('Foo\Bar'); // 'Bar', because 'Foo' is the current namespaceecho$namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', due to the defined use-statement

Conversely, you can convert a simplified class, function, or constant name back to a fully qualified name using the resolveName method:

echo$namespace->resolveName('Bar'); // 'Foo\Bar'echo$namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'

Class Names Resolving

When a class is part of a namespace, it's rendered slightly differently: all types (e.g., type hints, return types, parent class name, implemented interfaces, used traits, and attributes) are automatically resolved (unless you turn it off, see below). This means you must use fully qualified class names in definitions, and they will be replaced with aliases (based on use clauses) or fully qualified names in the resulting code:

$namespace = newNette\PhpGenerator\PhpNamespace('Foo');
$namespace->addUse('Bar\AliasedClass');
$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // will be simplified to A
->addTrait('Bar\AliasedClass'); // will be simplified to AliasedClass$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // we manually simplify in comments$method->addParameter('arg')
->setType('Bar\OtherClass'); // will be translated to \Bar\OtherClassecho$namespace;
// or use the PsrPrinter for output in accordance with PSR-2 / PSR-12 / PER// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);

Result:

namespaceFoo;
useBar\AliasedClass;
class Demo implements A
{
use AliasedClass;
/** * @return D */publicfunctionmethod(\Bar\OtherClass$arg)
{
}
}

Auto-resolving can be turned off this way:

$printer = newNette\PhpGenerator\Printer; // or PsrPrinter$printer->setTypeResolving(false);
echo$printer->printNamespace($namespace);

PHP Files

Classes, functions, and namespaces can be grouped into PHP files represented by the PhpFile class:

$file = newNette\PhpGenerator\PhpFile;
$file->addComment('This file is auto-generated.');
$file->setStrictTypes(); // adds declare(strict_types=1)$class = $file->addClass('Foo\A');
$function = $file->addFunction('Foo\foo');
// or// $namespace = $file->addNamespace('Foo');// $class = $namespace->addClass('A');// $function = $namespace->addFunction('foo');echo$file;
// or use the PsrPrinter for output in accordance with PSR-2 / PSR-12 / PER// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);

Result:

<?php/** * This file is auto-generated. */declare(strict_types=1);
namespaceFoo;
class A
{
}
functionfoo()
{
}

Please note: No additional code can be added to the files outside of functions and classes.

Generating from Existing Ones

In addition to being able to model classes and functions using the API described above, you can also have them automatically generated using existing ones:

// creates a class identical to the PDO class$class = Nette\PhpGenerator\ClassType::from(PDO::class);
// creates a function identical to the trim() function$function = Nette\PhpGenerator\GlobalFunction::from('trim');
// creates a closure based on the provided one$closure = Nette\PhpGenerator\Closure::from(
function (stdClass$a, $b = null) {},
);

By default, function and method bodies are empty. If you also want to load them, use this method (requires the nikic/php-parser package to be installed):

$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true);
$function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);

Loading from PHP Files

You can also load functions, classes, interfaces, and enums directly from a string containing PHP code. For example, to create a ClassType object:

$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX	<?php	class Demo	{ public $foo;	}	XX);

When loading classes from PHP code, single-line comments outside method bodies are ignored (e.g., for properties, etc.), as this library doesn't have an API to work with them.

You can also directly load an entire PHP file, which can contain any number of classes, functions, or even namespaces:

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

The file's initial comment and strict_types declaration are also loaded. However, all other global code is ignored.

It requires nikic/php-parser to be installed.

(If you need to manipulate global code in files or individual statements in method bodies, it's better to use the nikic/php-parser library directly.)

Class Manipulator

The ClassManipulator class provides tools for manipulating classes.

$class = newNette\PhpGenerator\ClassType('Demo');
$manipulator = newNette\PhpGenerator\ClassManipulator($class);

The inheritMethod() method copies a method from a parent class or implemented interface into your class. This allows you to override the method or extend its signature:

$method = $manipulator->inheritMethod('bar');
$method->setBody('...');

The inheritProperty() method copies a property from a parent class into your class. This is useful when you want to have the same property in your class, but possibly with a different default value:

$property = $manipulator->inheritProperty('foo');
$property->setValue('new value');

The implement() method automatically implements all methods and properties from the given interface or abstract class:

$manipulator->implement(SomeInterface::class);
// Now your class implements SomeInterface and includes all its methods

Variable Dumping

The Dumper class converts a variable into parseable PHP code. It provides a better and clearer output than the standard var_export() function.

$dumper = newNette\PhpGenerator\Dumper;
$var = ['a', 'b', 123];
echo$dumper->dump($var); // outputs ['a', 'b', 123]

About

🐘 Generates neat PHP code for you. Supports new PHP 8.5 features.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2.3k stars

Watchers

59 watching

Forks

Releases

Sponsor this project

Used by

Contributors

Languages