PHP Code Generator

Generate PHP code, classes, namespaces, etc. with a simple API.

Installation:

composer require nette/php-generator

Classes

Let's start with a straightforward example of generating class using ClassType:

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

$class
	->setFinal()
	->setExtends('ParentClass')
	->addImplement('Countable')
	->addTrait('Nette\SmartObject')
	->addComment("Description of class.\nSecond line\n")
	->addComment('@property-read Nette\Forms\Form $form');

// to generate PHP code simply cast to string or use echo:
echo $class;

It will render this result:

/**
 * Description of class.
 * Second line
 *
 * @property-read Nette\Forms\Form $form
 */
final class Demo extends ParentClass implements Countable
{
	use Nette\SmartObject;
}

We can add constants (Constant) and properties (Property):

$class->addConstant('ID', 123);

$class->addProperty('items', [1, 2, 3])
	->setVisibility('private')
	->setStatic()
	->addComment('@var int[]');

It generates:

const ID = 123;

/** @var int[] */
private static $items = [1, 2, 3];

And we can add methods (Method) with parameters (Parameter):

$method = $class->addMethod('count')
	->addComment('Count it.')
	->addComment('@return int')
	->setFinal()
	->setVisibility('protected')
	->setBody('return count($items ?: $this->items);');

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

It results in:

/**
 * Count it.
 * @return int
 */
final protected function count(array &$items = [])
{
	return count($items ?: $this->items);
}

If the property, constant, method or parameter already exist, it will be overwritten.

PHP Generator supports all new PHP 7.3 features:

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

$class->addConstant('ID', 123)
	->setVisibility('private'); // constant visiblity

$method = $class->addMethod('getValue')
	->setReturnType('int') // method return type
	->setReturnNullable() // nullable return type
	->setBody('return count($this->items);');

$method->addParameter('id')
		->setTypeHint('int') // scalar type hint
		->setNullable(); // nullable type hint

echo $class;

Result:

class Demo
{
	private const ID = 123;

	public function getValue(?int $id): ?int
	{
		return count($this->items);
	}
}

Tabs versus spaces

The generated code uses tabs for indentation, which makes it very easy to change it to any number of spaces:

use Nette\PhpGenerator\Helpers;

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

echo Helpers::tabsToSpaces((string) $class); // 4 spaces indentation
echo Helpers::tabsToSpaces((string) $class, 2); // 2 spaces indentation

Literals

You can pass any PHP code to property or parameter default values via PhpLiteral:

use Nette\PhpGenerator\PhpLiteral;

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

$class->addProperty('foo', new PhpLiteral('Iterator::SELF_FIRST'));

$class->addMethod('bar')
	->addParameter('id', new PhpLiteral('1 + 2'));

echo $class;

Result:

class Demo
{
	public $foo = Iterator::SELF_FIRST;

	public function bar($id = 1 + 2)
	{
	}
}

Interface or Trait

$class = new Nette\PhpGenerator\ClassType('DemoInterface');
$class->setType('interface');
// or $class->setType('trait');

Trait Resolutions and Visibility

$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addTrait('SmartObject', ['sayHello as protected']);
echo $class;

Result:

class Demo
{
	use SmartObject {
		sayHello as protected;
	}
}

Anonymous Class

$class = new Nette\PhpGenerator\ClassType(null);
$class->addMethod('__construct')
	->addParameter('foo');

echo '$obj = new class ($val) ' . $class . ';';

Result:

$obj = new class ($val) {

	public function __construct($foo)
	{
	}
};

Global Function

Code of function will generate class GlobalFunction:

$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('return $a + $b;');
$function->addParameter('a');
$function->addParameter('b');
echo $function;

Result:

function foo($a, $b)
{
	return $a + $b;
}

Closure

Code of closure will generate class Closure:

$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('return $a + $b;');
$closure->addParameter('a');
$closure->addParameter('b');
$closure->addUse('c')
	->setReference();
echo $closure;

Result:

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

Method and Function Body Generator

You can use special placeholders for handy way to generate method or function body.

Simple placeholders:

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

Result:

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;

Result:

function foo()
{
	myfunc(1, 2, 3);
}

Escape placeholder using slash:

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

Result:

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

Namespace

Classes, traits and interfaces (hereinafter classes) can be grouped into namespaces:

$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');

$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');

If the class already exists, it will be overwritten.

You can define use-statements:

$namespace->addUse('Http\Request'); // use Http\Request;
$namespace->addUse('Http\Request', 'HttpReq'); // use Http\Request as HttpReq;

IMPORTANT NOTE: when the class is part of the namespace, it is rendered slightly differently: all types (ie. type hints, return types, parent class name, implemented interfaces and used traits) are automatically resolved. It means that you have to use full class names in definitions and they will be replaced with aliases (according to the use-statements) or fully qualified names in the resulting code:

$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
$namespace->addUse('Bar\AliasedClass');

$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // it will resolve to A
	->addTrait('Bar\AliasedClass'); // it will resolve to AliasedClass

$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->unresolveName('Foo\D')); // in comments resolve manually
$method->addParameter('arg')
	->setTypeHint('Bar\OtherClass'); // it will resolve to \Bar\OtherClass

echo $namespace;

Result:

namespace Foo;

use Bar\AliasedClass;

class Demo implements A
{
	use AliasedClass;

	/**
	 * @return D
	 */
	public function method(\Bar\OtherClass $arg)
	{
	}
}

PHP Files

PHP files can contains multiple classes, namespaces and comments:

$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('This file is auto-generated.');

$namespace = $file->addNamespace('Foo');
$class = $namespace->addClass('A');
$class->addMethod('hello');

echo $file;

Result:

<?php

/**
 * This file is auto-generated.
 */

namespace Foo;

class A
{
	public function hello()
	{
	}
}

Generate using Reflection

Another common use case is to create class or function based on existing ones:

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

$function = Nette\PhpGenerator\GlobalFunction::from('trim');

$closure = Nette\PhpGenerator\Closure::from(
	function (stdClass $a, $b = null) {}
);
version: 4.0 3.x 2.x