murtukov / php-code-generator
A library to generate php code
Installs: 2 461 826
Dependents: 2
Suggesters: 0
Security: 0
Stars: 25
Watchers: 3
Forks: 6
Open Issues: 9
Requires
- php: >=7.4
- ext-json: *
Requires (Dev)
- phpstan/phpstan: ^0.12.58
- phpunit/phpunit: ^9.4.3
README
A library to generate PHP 7.4 code
- Installation
- File
- OOP
- Functions
- Object Instantiation
- Arrays
- Control structures
- Comments
- Namespaces
- Literal
- Global Configs
Installation
composer require murtukov/php-code-generator
Components
File
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() { } }
Class
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 []; } }
Interface
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; }
Trait
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 []; } }
Function
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(); }
Method
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(); }
Constructor property promotion
$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);
Closure
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 Function
$arrow = ArrowFunction::new([ 'name' => 'Alrik', 'age' => 30 ]); $arrow->setStatic(); echo $arrow;
Result:
static fn() => [ 'name' => 'Alrik', 'age' => 30, ]
Object Instantiation
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 = '~';
Arrays
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'], ]
If ... else
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; }
Loops
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)
Comments
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!
Namespaces
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'));
Literal
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);
Global Configs
All global configs are stored as static properties in the Config
class.
Indent
Default indent contains 4 spaces. You can change it by rewriting the $indent
property:
use Murtukov\PHPCodeGenerator\Config; Config::$indent = ' ';
Shorten qualifiers
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;
Suppress symbol
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 = '%';