Генерация кода на PHP: от строковых шаблонов до синтаксических деревьев

Раздел: PHP -> Автоматизация

Введение в генерацию PHP-кода

Когда проекты становятся сложными, ручное написание однотипных конструкций утомляет. Автоматическая генерация кода позволяет создавать классы, конфигурации, SQL-запросы и даже целые модули на основе данных или метаданных. Рассмотрим основные подходы.

Основное решение: использование библиотеки PhpParser для программного построения AST

Как генерировать синтаксически корректный PHP-код, который будет поддерживать автозагрузку и автодополнение?

Библиотека nikic/php-parser позволяет создавать узлы абстрактного синтаксического дерева и затем преобразовывать их в строку кода. Это безопасно, легко читаемо и поддерживаемо.


use PhpParser\BuilderFactory;
use PhpParser\PrettyPrinter\Standard;

$factory = new BuilderFactory();
$class = $factory->class('User')
    ->makeFinal()
    ->addStmt($factory->property('name')->makePrivate())
    ->addStmt($factory->method('getName')
        ->makePublic()
        ->setReturnType('string')
        ->addStmt(new PhpParser\Node\Stmt\Return_(
            new PhpParser\Node\Expr\PropertyFetch(
                new PhpParser\Node\Expr\Variable('this'),
                'name'
            )
        ))
    );

$printer = new Standard();
$code = '<?php' . chr(10) . $printer->print($class->getNode());
echo $code;

Php exec docker (выполнение команд docker из php)

<?php
final class User
{
    private $name;
    public function getName(): string
    {
        return $this->name;
    }
}

Php генерация кода (генерация кода php)

Проблемы и ошибки:

  • Неверное построение дерева приводит к синтаксическим ошибкам. Используйте PrettyPrinter для проверки.
  • Забыли указать пространство имен – добавьте namepsace через $factory->namespace().
  • Сложность для новичков: требуется понимание структуры AST.

Цель использования: генерация классов сущностей, репозиториев, DTO на основе схемы БД или API-спецификации.

Вариант 1: heredoc и интерполяция переменных

Как быстро сгенерировать простой скрипт, не используя внешние библиотеки?

Самый простой способ – собирать строки с помощью heredoc или nowdoc.


$fields = ['id', 'name', 'email'];
$code = '<?php' . chr(10) . 'class User {' . chr(10);
foreach ($fields as $field) {
    $code .= '    private $' . $field . ';' . chr(10);
}
$code .= '}' . chr(10);
file_put_contents('User.php', $code);

Проблемы: легко допустить синтаксическую ошибку, нет проверки типов, сложно поддерживать при большом объеме.

Решение: использовать функции форматирования, например sprintf или шаблонизатор.

Вариант 2: Twig как шаблонизатор для PHP

Как отделить логику генерации от представления кода?

Twig позволяет писать шаблоны с синтаксисом, похожим на PHP, но с безопасным экранированием.


// template.twig
<?php
class {{ className }} {
{% for field in fields %}
    private ${{ field }};
{% endfor %}
}

$loader = new \Twig\Loader\FilesystemLoader('/templates');
$twig = new \Twig\Environment($loader);
$code = $twig->render('class.twig', [
    'className' => 'Product',
    'fields' => ['id','title','price']
]);
file_put_contents('Product.php', $code);

Проблема: для каждого нового типа кода нужен отдельный шаблон. Не подходит для динамических структур.

Вариант 3: Использование библиотеки Nette\PhpGenerator

Как генерировать код с поддержкой аннотаций и типов без ручного построения AST?

Библиотека предоставляет высокоуровневые классы для описания, например, классов, методов, констант.


use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\PhpFile;

$file = new PhpFile;
$file->addNamespace('App\Entity');
$class = $file->addClass('Order');
$class->addProperty('total')->setPrivate()->setType('float');
$class->addMethod('getTotal')->setReturnType('float')->setBody('return $this->total;');
echo $file;

Проблема: библиотека менее популярна, чем PhpParser, и требует дополнительной установки.

Расширенные примеры генерации PHP-кода

1. Создание сущности с атрибутами (PhpParser)

Покажем, как сгенерировать класс с PHP 8 атрибутами, например, для валидации.

Пример

use PhpParser\BuilderFactory;
use PhpParser\PrettyPrinter\Standard;
use PhpParser\Node\Attribute;
use PhpParser\Node\AttributeGroup;
use PhpParser\Node\Name;

$factory = new BuilderFactory();
$class = $factory->class('Product')
    ->makeFinal()
    ->addStmt(
        $factory->property('price')
            ->makePrivate()
            ->setType('float')
            ->addAttribute(
                new AttributeGroup([
                    new Attribute(
                        new Name\FullyQualified(['Symfony', 'Component', 'Validator', 'Constraints', 'PositiveOrZero'])
                    )
                ])
            )
    );

$printer = new Standard();
$code = '<?php' . chr(10) . $printer->print($class->getNode());
echo $code;
<?php
final class Product
{
    #[Symfony\Component\Validator\Constraints\PositiveOrZero]
    private float $price;
}

2. Генерация конфигурационного массива с помощью var_export

Нестандартный приём – использовать var_export для создания PHP-файла с массивом.

Пример

$config = [
    'database' => ['host' => 'localhost', 'port' => 3306, 'name' => 'test'],
    'debug' => true,
    'items' => ['item1', 'item2'],
];
$code = '<?php' . chr(10) . 'return ' . var_export($config, true) . ';' . chr(10);
file_put_contents('config.php', $code);
<?php
return array (
  'database' => 
  array (
    'host' => 'localhost',
    'port' => 3306,
    'name' => 'test',
  ),
  'debug' => true,
  'items' => 
  array (
    0 => 'item1',
    1 => 'item2',
  ),
);

3. Генерация DTO с помощью Twig и произвольной структуры

Создадим шаблон для DTO с геттерами.

Пример

{# dto.twig #}
<?php
declare(strict_types=1);

namespace {{ namespace }};

class {{ className }} 
{
{% for property in properties %}
    private {{ property.type }} ${{ property.name }};
{% endfor %}
{% for property in properties %}
    public function get{{ property.name|capitalize }}(): {{ property.type }}
    {
        return $this->{{ property.name }};
    }
{% endfor %}
}
Пример

$twig = new \Twig\Environment(new \Twig\Loader\FilesystemLoader('.'));
$code = $twig->render('dto.twig', [
    'namespace' => 'App\DTO',
    'className' => 'UserDTO',
    'properties' => [
        ['name' => 'id', 'type' => 'int'],
        ['name' => 'email', 'type' => 'string'],
    ]
]);
echo $code;
<?php
declare(strict_types=1);

namespace App\DTO;

class UserDTO 
{
    private int $id;
    private string $email;

    public function getId(): int
    {
        return $this->id;
    }

    public function getEmail(): string
    {
        return $this->email;
    }
}

Генерация кода PHP - comments

En
Php генерация кода (php)