PHP-DataGen — генератор PHP классов со строго-типизированными свойствами +11


Введение


Помимо многих проблем, в PHP существует проблема строгой типизации переменных и свойств классов, точнее её отсутствие. Более того, нет даже возможности однозначно задать какие будут свойства у объектов того или иного класса, пользуясь только синтаксисом и не прибегая к так называемым магическим методам (потому что любое свойство может быть удалено при помощи оператора unset, а также к объекту может быть дописано несуществующее ранее свойство).

Однако при разработке часто возникает потребность в чётком знании, что можно ожидать от объекта, а чего можно не ожидать. Разумеется, можно пойти простым путём: сделать все свойства protected и понаписать геттеров и сеттеров. Много бойлерплейта, хочется проще. Лично я пытался решить эту проблему с помощью трейтов, но выходило всё равно некрасиво. Так и появилась идея этого проекта…

Кому интересно, добро пожаловать под кат!

Описание


Проект PHP-DataGen[1] является утилитой — генератором кода PHP классов со строго-типизированными свойствами и направлен на упрощение работы PHP программистов. Инструмент имеет как возможность управлять генерацией с помощью PHP скриптов, так и CLI для работы со встроенным парсером собственного языка (далее — PDGL).

Из соображений удобства использования целевой аудиторией (PHP программисты), для разработки был выбран язык PHP.

Версия последнего на данный момент релиза — v0.3-alpha. К моменту выхода стабильного релиза планируется переписать весь «низкокачественный» код (написанный без достаточной квалификации), но пока всё и так работает.

Краткий обзор


В рамках данного обзора я рассмотрю основные аспекты использования утилиты.

Проект рассматривается в состоянии коммита 75974bee3b4cccd1af1722acac775d68011f7fa6[2].

CLI


На данный момент PHP-DataGen поддерживает 2 собственные команды: compile и build. Первая используется для поштучной компиляции файлов, вторая для компиляции всех файлов в проекте (директории). Использование команд максимально интуитивное и может быть изучено вручную благодаря библиотеке Symfony Console[6], на которой основан CLI.

Также планируется добавить работу с файлом конфигурации для управления деталями процесса компиляции. При этом будет добавлено считывание файла конфигурации из корня проекта и команда config для удобного изменения конфигурации проекта.

PDGL


До начала разработки было несколько идей по поводу внедрения утилиты в проекты:

  1. Чтение PHPDoc и других комментариев
  2. Введение специальных модификаторов и т.п. в обычный PHP код
  3. Создание собственного языка

Так как изначально проект создавался «по образу и подобию» утилиты moc популярного C++ фреймворка Qt, в приоритете был второй вариант, однако после некоторых раздумий, первые два варианта были отброшены мной как неоправданно сложные для реализации.

PDGL предназначен для описания файлов PHP, которые получаются на выходе PHP-DataGen. Каждый файл можно представить в виде дерева, отдалённо напоминающего абстрактное синтаксическое дерево[3], которое состоит лишь из трёх типов узлов: файл, класс, поле.



Все поддерживающиеся языком операторы представлены в файле schema.md[4] в корне проекта, но без описания, что делает тот или иной оператор. Операторы namespace и use работают также как и в обычном PHP, однако с классом, полями и их модификаторами всё не так просто.

Из модификаторов класса можно выделить лишь один нестандартный для PHP модификатор final, который также имеет вариацию final!. Дело в том, что результат работы PHP-DataGen — класс, который для работы должен быть расширен с помощью другого класса.

Модификатор final превращает класс в готовый для непосредственного использования, путём убирания префикса (по умолчанию, пока что без возможности изменения, Data_) и модификатора abstract итогового PHP класса.

Модификатор final!, который «под капотом» именуется не иначе как «final final» является дополнением к модификатору final (и не может быть использован без него) и добавляет к итоговому PHP классу модификатор final.

Поле класса


Синтаксис поля класса очень мало похож на синтаксис свойств PHP и даже больше, на мой взгляд, напоминает синтаксис свойств классов Kotlin.

Начнём с того, что написано в файле schema.md[4]:

// Field declaration
[direct] <val/var> <Field name>[: <Type name>[, <Validator names>]][ <:/</>= [`[``]]<Default value>[`[``]]];

А теперь по порядку (операторы выделены жирным, подстановки — курсивом):

  • direct — модификатор. При наличии позволяет расширяющему классу обращаться к свойствам напрямую (устанавливает модификатор доступа protected вместо private);
  • val или var — оператор объявления поля. Если используется val — свойство недоступно для редактирования после установки в конструкторе, если var — доступно;
  • Field name — название поля, указывается без характерного для PHP знака доллара ($);
  • : — необязательный оператор двоеточия позволяет указать тип поля. Если не указан — тип поля считается mixed;
  • Type name — название типа. Может быть одним из стандартных типов PHP (без учёта регистра) или названием класса. Если оканчивается знаком вопроса (например, string?), тогда поле может хранить также значение null;
  • , — необязательный оператор запятая позволяет указать после названия типа (или валидатора) также название валидатора;
  • Validator name — название валидатора (см. следующий раздел);
  • <=, := или = — оператор присваивания значения по-умолчанию. В вариации <= присваивает значение при объявлении свойства. В вариации := присваивает значение при вызове конструктора без проверки типа и вызова валидаторов. В вариации = присваивает значение при вызове конструктора с проверкой типа и вызовом валидаторов;
  • ` или ``` — см. Default value;
  • Default value — значение поля по-умолчанию. Может быть окружено операторами ` или ``` при наличии точки с запятой (;) (кроме случаев, когда используется вариация оператора присваивания значения по-умолчанию <=). Нет разницы в использовании ` или ```, если в значении по-умолчанию не присутствует символов обратного апострофа (`), в этом случае необходимо использовать оператор ```.

Валидаторы


Для лучшей фильтрации возможных значений полей планируется ввести возможность добавлять свои валидаторы — функции проверки (или модификации) значения. При том что в коде PHP-DataGen обработка валидаторов присутствует, пока нет способа добавлять их. Это одна из возможностей, которые должны появиться с появлением чтения конфигурации.

Примеры работы


Некоторые примеры работы инструмента можно найти в репозитории. Среди них:
PDGL PHP
app/Type.pdata app/Data_Type.php
app/Model/FieldModel.pdata app/Model/FieldModel.php
tests/Test.pdata tests/Data_Test.php

Примеры приведены в виде ссылок из-за большого объёма кода

Разработка


В самом начале реализации идеи начался длительный «ступор» связанный с отсутствием продуманной архитектуры и, как следствие, неправильно выбранным порядком разработки. Вскоре после появления ясности в голове — зарождения в голове архитектуры будущего инструмента, была начата разработка.

Архитектура


Упрощённая архитектура утилиты
Упрощённая архитектура утилиты

На схеме выше изображена очень упрощённая архитектура PHP-DataGen. Она состоит из четырёх модулей:

  • Parser — модуль, отвечающий за разбор кода;
  • Building* — модуль, содержащий билдеры «сущностей», которыми оперирует компилятор;
  • Models — модуль, содержащий модели «сущностей», которыми оперирует компилятор. Объекты классов этого модуля порождают классы модуля Building;
  • Compiler — модуль, отвечающий за генерацию кода на основе моделей.


* — на схеме опечатка (не Builders, а Building).

Ход разработки


Если до появления цельной архитектуры я (безуспешно) пытался написать Parser, то сразу после её появления я взялся за модуль Building. Затем были написаны модели и Compiler. Таким образом, уже через два дня появился рабочий прототип, позволяющий генерировать код с помощью PHP скрипта.

Далее предстояло написать Parser. Из-за незнания об абстрактных синтаксических деревьях[3], ломать голову пришлось долго. В итоге получился конечный автомат[5], имеющий 3 состояния (которые также состоят из некоторых собственных состояний): FileState, ClassState и FieldState. Каждое из этих состояний при помощи соответствующих билдеров создаёт модели для Compiler.

Использованные при разработке библиотеки и инструменты


В проекте используются следующие библиотеки:

  • Symfony Console[6] — для разработки CLI;
  • Symfony Finder[7] — для поиска файлов при использовании CLI.

Проект разрабатывался при помощи следующих инструментов:

  • Vim — редактор кода;
  • Git — система контроля версий;

Также, при написании статьи, для рисования диаграмм, использовался онлайн сервис Creately[8].

Ссылки


  1. php-datagen — GitHub;
  2. 75974bee3b4cccd1af1722acac775d68011f7fa6 — GitHub;
  3. Абстрактное синтаксическое дерево — Википедия;
  4. Файл schema.md — GitHub;
  5. Теория вычислений. Введение в конечные автоматы — Хабр;
  6. The Console Component — Symfony Docs;
  7. The Finder Component — Symfony Docs;
  8. Creately.




К сожалению, не доступен сервер mySQL