Введение
Помимо многих проблем, в 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
До начала разработки было несколько идей по поводу внедрения утилиты в проекты:
- Чтение PHPDoc и других комментариев
- Введение специальных модификаторов и т.п. в обычный PHP код
- Создание собственного языка
Так как изначально проект создавался «по образу и подобию» утилиты 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].
Ссылки
Комментарии (25)
GenkaOk
02.07.2018 08:28+2Как мне кажется всё наоборот усложнили.
Проще написать свой класс с магическим методом и переменными аля «strVar» «intVar», где в начале тип переменной и наследоваться от него.
Хотя это в некоторых случаях наоборот помешает разработке.
Но в чем проблема get/set? Они не так много раздувают кода, а в современных IDE ещё и генерируются сами.
Всё-таки в языке есть способ для типизации переменных, да он немного замудренный, но позвольте, php с динамической типизацией, что тоже имеет свои плюсы.rjhdby
02.07.2018 14:28переменными аля «strVar» «intVar»
эдак и до Венгерской записи докатиться можно
AlexLeonov
02.07.2018 09:55-1Когда хочется писать на C++ и на Java но, извините, толку не хватает — рождаются подобные «проекты»
Имхо это всё от банального незнания PHP и неумения на нём писать.
Простите, если кого задел за живое.
zim32
02.07.2018 10:24-2Все равно ошибки в реантайме а не на этапе компиляции. У чем тогда вообщн смысл?
ArthurKushman
02.07.2018 11:07+2Есть проект, если интересно — входные данные RAML, выходные JSON-API (REST), в Laravel инфраструктуре с поддержкой всех компонентов из коробки, в кратце — генерит Controllers/Models/Migrations/Middlwares с поддержкой типов, функциональные тесты (на основе CodeCeption), конфиг модуля, laravel-modules — поддерживает генерацию последующих модулей типа V1, V2, APIv3 итд
github.com/RJAPI/raml-json-api
Всегда рад контрибьютам.
rjhdby
02.07.2018 11:12+4Вам бы в статью примеров кода и результата его компиляции.
А так то удачи вам в вашем начинании! И не слушайте брюзжания зануд — при прочих равных лучше делать, чем не делать. Главное, чтоб вам самому было интересно и полезно (прокачивало навыки), а там, может быть, и люди подтянутся (а может и нет, но это не повод все бросать ;)ProgMiner Автор
02.07.2018 20:53Спасибо! Идея проекта пришла не спонтанно, и скорее всего инструмент будет использоваться хотя бы в собственных проектах.
По поводу примеров, тут мой косяк. Добавил абзац со ссылками на примеры из репозитория.
alexeevdv
02.07.2018 17:31+1С типизированием свойств объектов в PHP нет особых проблем. В нормальном ООП у объекта не должно быть никаких публичных свойств, а весь доступ к внутреннему состоянию и его изменение происходит через методы которые прекрасно типизируются в современном PHP. Делая публично доступными свойства объекта мы пренебрегаем такой важной вещью как инкапсуляция
FrankSinatra
02.07.2018 18:11Почему бы описание синтаксиса элементов класса не вынести в шаблоны? От метода protected function compileClass(ClassModel $classModel, FileModel $fileModel) перехватило дух.
И не помешало бы натравить PHP Code Sniffer на ваш проект.ProgMiner Автор
02.07.2018 18:32К сожалению в коде сейчас очень много таких моментов, потому что писалось все в стиле "поскорее, пока не кончился запал".
В будущем планируется все это вычистить, в первую очередь компилятор и парсер.
Fantyk
02.07.2018 18:43+1Статье очень не хватает «что было на входе» и «что получили после применения инструмента».
По мне вы в PhpStorm не нашли кнопки Code\Generate.ProgMiner Автор
02.07.2018 20:53Добавил абзац со ссылками на примеры из репозитория.
На PhpStorm денег нет, а обманывать отечественного производителя желания нет.rjhdby
03.07.2018 11:221) У них есть open source лицензия
2) Месяц бесплатно, а дальше просто раз в пол-часа перезапуск среды
3) JetBrains довольно толерантны к нелицензионному использованию. Вплоть до того, что консультировали по проблемам со взломаным софтом на рутрекере (посыл такой — пиратку качают те, кто не может купить. Подсядут, разбогатеют и купят)
PS третий пункт для полноты картины, а не призыв делать неправильноt_kanstantsin
03.07.2018 19:55OpenSource есть, но надо регистрироваться как OpenSource — всё же ограничение.
Но есть ещё минимум 2 возможности: EAP, который доступен 90% времени, и студенческая лицензия
alekciy
03.07.2018 11:31-1На PhpStorm денег нет
У вас нет ~5,7к рублей? Я не верю, что профессиональный разработчик не может выделить из своего бюджета ~500 рублей/месяц в течении года.FrankSinatra
03.07.2018 15:20+1PhpStorm не панацея, а средство и по большому счету вкусовщина, как и Eclipse или NetBeans. А кто-то может довольствоваться и Visual Studio Code, у которого тоже есть плагины для генерации кода.
Автор же решил задачу по своему и при желании данный проект можно развить, чтобы генерировать не только PHP классы, а поддерживать разные языки.
vlreshet
03.07.2018 15:39Совсем непонятно, почему в итоговом коде (который сгенерирован):
- Отвратительный код-стайл без отступов
- Используется змеиная_нотация, хотя в PHP общепринятым является camelCase
- Не используется встроенный в язык тайпхинтинг
- Нет сеттеров (не, ну реально. геттеры вижу — сеттеров нет. далеко не всегда все поля устанавливаются в конструкторе, и больше не модифицируются)
?
Да и в конце концов — почему не работать поверх PHP кода (написал код — запустил генератор — он дописал класс до нужного состояния). А не вот это вот всё с *.pdata...)ProgMiner Автор
03.07.2018 16:471. Отступы нужны для понимая человеком, что по большому счёту не актуально для автоматически генерируемого кода. Конечно функция выравнивания после генерации в планах.
2. По той же причине, что и в первом пункте, но плюс к этому, чтобы не было неоднозначности, какой метод вызвать при осуществлении доступа к свойству (после префиксаget_
/set_
/validate_
используется точное название свойства с сохранением регистра).
3. Изначально он был использован, пока не оказалось (для меня это было неожиданно), что типы не поддерживаютnull
. Именно тогда произошёл отказ от тайпхинтинга и появилось разделение nullable и не nullable полей.
4. Похоже, что вы невнимательно читали описание языка. Сеттеры генерируются для полей объявленных с помощьюvar
.
Не очень представляю как именно это реализовать, использовать комментарии?oxidmod
03.07.2018 17:21Изначально он был использован, пока не оказалось (для меня это было неожиданно), что типы не поддерживают null.
PHP 7.1
vlreshet
03.07.2018 17:49Не очень представляю как именно это реализовать, использовать комментарии?
Как вариант. Это называется «Аннотации», и большинство современных фреймворков использует такой подход. Выглядит это как-то вот так:
class Foo { /** * @var integer */ public $bar; }
Конечно функция выравнивания после генерации в планах.
Очень рекомендую прикрутить готовую библиотеку вроде «php-cs-fixer» вместо велосипеда.
4. Похоже, что вы невнимательно читали описание языка. Сеттеры генерируются для полей объявленных с помощью var.
Согласен, упустил.ProgMiner Автор
03.07.2018 18:18Да, я имел ввиду аннотации, кстати упоминал даже такой подход в статье:
Чтение PHPDoc и других комментариев
Изначально я его отбросил, но если, например, упомянутый выше PHPParser умеет читать комментарии, то возможно вернусь к нему, если получится уместить все фичи в синтаксис аннотаций.
За библиотеку спасибо, посмотрю.rjhdby
03.07.2018 23:29Комментарии отлично понимает tokenizer
Вот, баловался в свое время (см. секцию Dynamic class mapping)
Класс, вычитывающий имена методов из PHPDocProgMiner Автор
04.07.2018 01:01Спасибо. Я уже примерно представляю, как можно сделать. Одним махом отказаться от файлов .pdata и лишних PHP файлов.
kruslan
Вашу-бы энергию, да в мирных целях…
andrew_tch
И с нормальным кодом, например PHPParser использовать?