A library to generate PHP 7.4 code
- Installation
- File
- OOP
- Functions
- Object Instantiation
- Arrays
- Control structures
- Comments
- Namespaces
- Literal
- Global Configs
composer require murtukov/php-code-generator
use Murtukov\PHPCodeGenerator\PhpFile;
$file = PhpFile::new()->setNamespace('App\Generator');
$class = $file->createClass('MyClass');
$class->setExtends('App\My\BaseClass')
->addImplements(Traversable::class, JsonSerializable::class)
->setFinal()
->addDocBlock("This file was generated and shouldn't be modified")
->addConstructor();
echo $file;
Result:
<?php
namespace App\Generator;
use App\My\BaseClass;
/**
* This file was generated and shouldn't be modified
*/
final class MyClass extends BaseClass implements Traversable, JsonSerializable
{
public function __construct()
{
}
}
use Murtukov\PHPCodeGenerator\Comment;
use Murtukov\PHPCodeGenerator\Literal;
use Murtukov\PHPCodeGenerator\Method;
use Murtukov\PHPCodeGenerator\Modifier;
use Murtukov\PHPCodeGenerator\PhpClass;
$class = PhpClass::new('Stringifier')
->addConst('KNOWN_TYPES', ['DYNAMIC', 'STATIC'], Modifier::PRIVATE)
->addProperty('errors', Modifier::PRIVATE, '', [])
->addImplements(JsonSerializable::class, ArrayAccess::class)
->setExtends(Exception::class)
->setFinal()
->addDocBlock('This is just a test class.');
$class->emptyLine();
$class->createConstructor()
->append('parent::__construct(...func_get_args())');
# Create a method separately
$method = Method::new('getErrors', Modifier::PUBLIC, 'array')
->append(Comment::slash('Add here your content...'))
->append('return ', new Literal('[]'))
;
$class->append($method);
echo $class;
Result:
/**
* This is just a test class.
*/
final class Stringifier extends Exception implements JsonSerializable, ArrayAccess
{
private const KNOWN_TYPES = ['DYNAMIC', 'STATIC'];
private $errors = [];
public function __construct()
{
parent::__construct(...func_get_args());
}
public function getErrors(): array
{
// Add here your content...
return [];
}
}
use Murtukov\PHPCodeGenerator\BlockInterface;
use Murtukov\PHPCodeGenerator\ConverterInterface;
use Murtukov\PHPCodeGenerator\Method;
use Murtukov\PHPCodeGenerator\Modifier;
use Murtukov\PHPCodeGenerator\PhpInterface;
$interface = PhpInterface::new('StringifierInterface')
->addExtends(BlockInterface::class, ConverterInterface::class)
->addConst('NAME', 'MyInterface')
->addConst('TYPE', 'Component')
->emptyLine()
->addSignature('parse', 'string');
$interface->emptyLine();
$signature = $interface->createSignature('stringify', 'string');
$signature->addArgument('escapeSlashes', 'bool', false);
$signature->addDocBlock('Convert value to string.');
$interface->emptyLine();
$dumpMethod = Method::new('dump', Modifier::PUBLIC, 'string');
$interface->addSignatureFromMethod($dumpMethod);
echo $interface;
Result:
interface StringifierInterface extends BlockInterface, ConverterInterface
{
public const NAME = 'MyInterface';
public const TYPE = 'Component';
public function parse(): string;
/**
* Convert value to string.
*/
public function stringify(bool $escapeSlashes = false): string;
public function dump(): string;
}
use Murtukov\PHPCodeGenerator\Comment;
use Murtukov\PHPCodeGenerator\Literal;
use Murtukov\PHPCodeGenerator\Method;
use Murtukov\PHPCodeGenerator\Modifier;
use Murtukov\PHPCodeGenerator\PhpTrait;
$trait = PhpTrait::new('Stringifier')
->addProperty('cache', Modifier::PRIVATE, 'array', [])
->addProperty('heap', Modifier::PROTECTED, SplHeap::class, null)
->addDocBlock('This is just a test trait.')
->emptyLine();
$constructor = Method::new('__construct')
->append('parent::__construct(...func_get_args())');
$method = Method::new('getErrors', Modifier::PUBLIC, 'array')
->append(Comment::hash('Add your content here...'))
->append('return ', new Literal('[]'));
$trait->append($constructor);
$trait->emptyLine();
$trait->append($method);
echo $trait;
Result:
/**
* This is just a test class.
*/
trait Stringifier
{
private array $cache = [];
protected ?SplHeap $heap = null;
public function __construct()
{
parent::__construct(...func_get_args());
}
public function getErrors(): array
{
# Add your content here...
return [];
}
}
use Murtukov\PHPCodeGenerator\Argument;
use Murtukov\PHPCodeGenerator\Instance;
use Murtukov\PHPCodeGenerator\Func;
$func = Func::new('myMethod', 'void');
# Crete argument and return it
$func->createArgument('arg1', SplHeap::class, null)->setNullable();
# Create argument and return function
$func->addArgument('arg2', 'string', '');
# Adding argument from object
$func->add(Argument::new('arg3'));
# Add content
$func->append('$object = ', Instance::new(stdClass::class));
echo $func;
Result:
function myMethod(?SplHeap $arg1 = null, string $arg2 = '', $arg3): void
{
$object = new stdClass();
}
use Murtukov\PHPCodeGenerator\Argument;
use Murtukov\PHPCodeGenerator\Instance;
use Murtukov\PHPCodeGenerator\Method;
use Murtukov\PHPCodeGenerator\Modifier;
$method = Method::new('myMethod', Modifier::PRIVATE, 'void');
# Crete argument and return it
$method->createArgument('arg1', SplHeap::class, null)->setNullable();
# Create argument and return function
$method->addArgument('arg2', 'string', '');
# Adding argument from object
$method->add(Argument::new('arg3'));
# Add content
$method->append('$object = ', Instance::new(stdClass::class));
echo $method;
Result:
private function myMethod(?SplHeap $arg1 = null, string $arg2 = '', $arg3): void
{
$object = new stdClass();
}
$method = Method::new('__construct');
$method->addArgument('firstName', 'string', Argument::NO_PARAM, Modifier::PRIVATE);
$method->addArgument('lastName', 'string', 'Kowalski', Modifier::PRIVATE);
$method->addArgument('age', 'int', 15);
$method->signature->setMultiline();
echo $method;
Result:
public function __construct(
private string $firstName,
private string $lastName = 'Kowalski',
int $age = 15
) {}
Same but creating from a class object:
$class = PhpClass::new('MyClass');
$class->createConstructor()
->addArgument('firstName', 'string', Argument::NO_PARAM, Modifier::PRIVATE)
->addArgument('lastName', 'string', 'Kowalski', Modifier::PRIVATE)
->addArgument('age', 'int', 15)
->signature->setMultiline();
Modifiers can also be set directly on argument objects:
$argument = Argument::new("firstName")->setModifier(Modifier::PUBLIC);
$constructor->add($argument);
use Murtukov\PHPCodeGenerator\Argument;
use Murtukov\PHPCodeGenerator\Loop;
# Create closure with 'array' return type
$closure = Closure::new('array');
# Create argument
$closure->addArgument('value');
# Create argument and return it
$closure->createArgument('options')
->setType('array')
->setDefaultValue([]);
# Create argument from object
$arg = Argument::new('filter', 'bool', false);
$closure->add($arg);
# Add uses of external variables
$closure->bindVar('this');
# by reference
$closure->bindVar('global', true);
# Create foreach loop
$foreach = Loop::foreach('$options as &$option')
->append('unset($option)');
# Append foreach as content of the closure
$closure->append($foreach);
Result:
function ($value, array $options = [], bool $filter = false) use ($this, &$global): array {
foreach ($options as &$option) {
unset($option);
}
}
$arrow = ArrowFunction::new([
'name' => 'Alrik',
'age' => 30
]);
$arrow->setStatic();
echo $arrow;
Result:
static fn() => [
'name' => 'Alrik',
'age' => 30,
]
use Murtukov\PHPCodeGenerator\Instance;
# Create an instance with a single argument
$instance = Instance::new('App\Entity\DateTime', '2000-01-01');
# Add a second argument
$instance->addArgument(null);
echo $instance;
Result:
new DateTime('2000-01-01', null);
You can prevent shortening of the class qualifier by prefixing its name with @
symbol.
$instance = Instance::new('@App\Entity\DateTime', '2000-01-01');
Result:
new App\Entity\DateTime('2000-01-01', null);
The @
suppress symbol can by changed with static config class.
use Murtukov\PHPCodeGenerator\Config;
Config::$suppressSymbol = '~';
This library provides a useful tool to stringify variables: Utils::stringify()
. It is similar to
the var_export
function, but with more control over arrays formatting. Arrays can also be wrapped
by the Collection
class, which internally use Utils::stringify()
.
Arrays with 0
-key defined are considered "numeric" and are stringified inline and without keys by default.
use Murtukov\PHPCodeGenerator\Utils;
echo Utils::stringify(['Test', 100, 5.67, array(), true, NULL]);
Result:
['Test', 100, 5.67, [], true, null]
All other arrays are stringified multiline and with keys:
use Murtukov\PHPCodeGenerator\Utils;
echo Utils::stringify(['name' => 'Justin', 'age' => 25]);
Result:
[
'name' => 'Justin',
'age' => 25
]
If you want to define yourself, how arrays are stringified, simply wrap them into Collection
class:
use Murtukov\PHPCodeGenerator\Collection;
echo Collection::numeric(['name' => 'Justin', 'age' => 25])
->setMultiline();
Result:
[
'Justin',
25,
]
assoc
collection example:
use Murtukov\PHPCodeGenerator\Collection;
echo Collection::assoc(['Tim', 'Max', 'Alfred']);
Result:
[
0 => 'Tim',
1 => 'Max',
2 => 'Alfred'
]
The Collection
class applies its formatting rules only to the top level array, using default formatting for
all nested arrays:
use Murtukov\PHPCodeGenerator\Collection;
echo Collection::numeric(['apple', 'banana', ['strawberry', 'tomato']])
->setMultiline();
Result:
[
'apple',
'banana',
['strawberry', 'tomato'],
]
use Murtukov\PHPCodeGenerator\IfElse;
use Murtukov\PHPCodeGenerator\Text;
echo IfElse::new('$name === 15')
->append('$names = ', "['name' => 'Timur']")
->createElseIf(new Text('$name === 95'))
->append('return null')
->end()
->createElseIf('$name === 95')
->append('return null')
->end()
->createElse()
->append('$x = 95')
->append('return false')
->end();
Result:
if ($name === 15) {
$names = ['name' => 'Timur'];
} elseif ('\$name === 95') {
return null;
} elseif ($name === 95) {
return null;
} else {
$x = 95;
return false;
}
use Murtukov\PHPCodeGenerator\Comment;
use Murtukov\PHPCodeGenerator\Loop;
echo Loop::for('$i = 1; $i < 1000; ++$i')
->append('$x = $i')
->emptyLine()
->append(Comment::hash('Ok, stop now...'))
->append('break');
echo Loop::foreach('$apples as $apple')
->append('$x = $apple')
->append('continue');
echo Loop::while('true')
->append('$x = $i')
->append('break');
echo Loop::doWhile('true')
->append(Comment::block('Hello, World!'));
Results:
for ($i = 1; $i < 1000; ++$i) {
$x = $i;
# Ok, stop now...
break;
}
foreach ($apples as $apple) {
$x = $apple;
continue;
}
while (true) {
$x = $i;
break;
}
do {
/*
* Hello, World!
*/
} while (true)
use Murtukov\PHPCodeGenerator\Comment;
echo Comment::block('Hello, World!');
echo Comment::hash('Hello, World!');
echo Comment::docBlock('Hello, World!');
echo Comment::slash('Hello, World!');
Result:
/*
* Hello, World!
*/
# Hello, World!
/**
* Hello, World!
*/
// Hello, World!
PhpFile
component automatically resolves class qualifiers from all its child components during the rendering.
use Murtukov\PHPCodeGenerator\Collection;
use Murtukov\PHPCodeGenerator\Instance;
use Murtukov\PHPCodeGenerator\PhpFile;
$file = PhpFile::new();
$class = $file->createClass('MyClass');
$construct = $class->createConstructor();
$array = Collection::numeric()
->push(Instance::new('App\Service\Converter'))
->push(Instance::new('App\Service\Normalizer'));
$construct->append('return ', $array);
echo $file;
Result:
<?php
use App\Service\Converter;
use App\Service\Normalizer;
class MyClass
{
public function __construct()
{
return [new Converter(), new Normalizer()];
}
}
However class qualifiers are NOT resolved from scalaras and arrays, unless wrapped in special objects:
use Murtukov\PHPCodeGenerator\Collection;
use Murtukov\PHPCodeGenerator\Literal;
use Murtukov\PHPCodeGenerator\PhpFile;
$file = PhpFile::new();
$class = $file->createClass('MyClass');
$construct = $class->createConstructor();
$array = Collection::numeric();
# This qualifiers are not resolved automatically
$array
->push(Literal::new('new App\Service\Converter()'))
->push('new App\Service\Normalizer()');
$construct->append('return ', $array);
Result:
<?php
class MyClass
{
public function __construct()
{
return [new App\Service\Converter(), 'new App\Service\Normalizer()'];
}
}
You can always add use statements manually by calling $file->addUse()
or $file->addUseGroup()
:
$file->addUse('App\Entity\User');
$file->addUse('App\Service\UserManager', 'Manager');
$file->addUseGroups('Symfony\Validator\Converters', 'NotNull', 'Length', 'Range');
Result:
use App\Entity\User;
use App\Service\UserManager as Manager;
use Symfony\Validator\Converters\{NotNull, Length, Range};
Although all components of this library implement the magic __toString()
method, avoid concatenating
them, as it will convert them into string scalars and all class qualifiers will be lost.
So instead of concatenation:
$method->append('return ' . Instance::new('App\MyClass'));
pass parts as separate arguments, as append
is a variadic function:
$method->append('return ', Instance::new('App\MyClass'));
Generates code literally as provided (without any additional processing)
echo Literal::new('$foo = "bar";');
Result:
$foo = "bar";
The string can hold placeholders similar to sprintf
function:
$literal = Literal::new(
'$foo = %s; %s',
Literal::new('"bar"'),
Literal::new('echo $foo;')
);
echo $literal;
Result:
$foo = "bar"; echo $foo;
Escaping the reserved %
char:
$literal = Literal::new(
'$foo = %s; sprintf("This value should not be quoted %%s.", %s);',
Literal::new('"bar"'),
Literal::new('$foo')
);
echo $literal;
Result:
$foo = "bar"; sprintf("This value should not be quoted %s.", $foo);
All global configs are stored as static properties in the Config
class.
Default indent contains 4 spaces. You can change it by rewriting the $indent
property:
use Murtukov\PHPCodeGenerator\Config;
Config::$indent = ' ';
As mentioned in the Namespaces section, class qualifiers are shortened automatically and added
to the top of PhpFile
output. In order to disable it overwrite the $shortenQualifiers
property:
use Murtukov\PHPCodeGenerator\Config;
Config::$shortenQualifiers = false;
As mentioned in the Object Instantiation section, you can suppress shortening of
class qualifiers by prefixing them with the @
symbol. In order to change this symbol, overwrite the
$suppressSymbol
property:
use Murtukov\PHPCodeGenerator\Config;
Config::$suppressSymbol = '%';