Под катом делюсь обзором своего самописного PHP-фреймворка Gy — попытки сделать легковесного «убийцу» Битрикса весом 350 Кб. Расскажу, как я реализовал вызов компонентов, зачем написал кастомный SQL-движок на текстовых файлах PhpFileSql.

Костыли, велосипеды, 3 года разработки по выходным, 315 коммитов, 14232 строки кода, поддержка практически всех версий PHP и ровно 0 пользователей.


Зачем, цели и какая связь с Битриксом

Проект начался в 2018 году, тогда же были сделаны первые коммиты. В то время на основной работе я взаимодействовал исключительно с 1С-Битрикс (бэкенд-разработка), причем со старым его ядром. Новое ядро D7 тогда уже вышло, но мне не попадалось ни одного живого проекта, где бы оно использовалось.

Параллельно у меня периодически появлялась подработка — то одному знакомому, то другому нужно было сделать простенький сайт на пару страниц с информацией и удобной админкой для редактирования данных. На Битриксе я мог собрать такое очень быстро. Сам компонентный подход и код API казались мне простыми в освоении и использовании. Но было жирное «но», Битрикс стоил денег и был гигантского размера, и чтобы купить лицензию, нужно было заморачиваться через партнеров.

Примерно тогда же на Хабре под какой-то очередной статьей, яростно хающей Битрикс, я наткнулся на комментарий в духе: «А почему никто не сделалает бесплатный аналог Битрикса?». И я подумал, а почему бы и нет?

Изначально цель была грандиозной — написать PHP-фреймворк с похожими вызовами компонентов и архитектурой. Идея заключалась в том, чтобы можно было в будущем легко переносить проекты с Битрикса на мой движок, практически не меняя код самого сайта. Конечно, Битрикс огромный, в нем очень много функционала, поэтому написать его полный аналог в одиночку было нереально. Тогда я сузил задачу, сделать легковесный инструмент, на котором можно с той же «битриксовой» легкостью собирать маленькие сайты (лендинги или информационные визитки) и удобно управлять данными через админку.

Второй важной целью было желание иметь собственный пет-проект. Место, где я сам принимаю архитектурные решения, а не согласовываю каждую кнопочку и строчку кода с менеджерами. Полная свобода, делаю то, что захочу и как захочу.

Ну и третья цель — это изучение веб-программирования и вызов самому себе. Смогу ли я в одиночку написать что-то подобное и реально работающее? Мне нужен был собственный полигон для испытаний, чтобы пробовать новые подходы, внедрять изученные технологии и тестировать безумные идеи на практике.

Разработка и отрыв от Битрикса

Так как в то время я плотно работал с Битриксом, у меня не было особого выбора или альтернативного опыта, на который можно было опереться. Я каждый день видел вызовы компонентов, подключение пролога, работал со старым API. При этом внутрь самого ядра коммерческой CMS я никогда не заглядывал и не изучал, как там всё устроено под капотом.

Видя только внешнюю, интерфейсную часть, я просто предположил, как эта магия могла бы работать изнутри. И попытался воссоздать похожую логику с нуля, так появились механизмы подключения ядра и вызова компонентов.

Скорее всего, внутреннее устройство моего фреймворка кардинально отличается от Битрикса, а в коде скрываются те еще архитектурные костыли. Но главное — система работает.

Некоторые вещи в итоге получились похожими на оригинал, но многие концепции я пересмотрел и сделал совершенно иначе. Например, у меня появилось что-то вроде модели для работы с данными, что в старом Битриксе не было очевидным. Поэтому любые совпадения архитектуры «под капотом» — это чистая случайность. Да и вряд ли они там найдутся.

Особенности процесса разработки

В режиме активного кодинга проект находился около 3 лет, начиная с 2018 года. После этого наступило относительное затишье, занялся более крупным проектом в физическом мире, требующий кучу моего времени. Я выпускал лишь небольшие доработки с одной целью, доделать необходимое для целостности, что бы была возможность минимальные сайты сделать полностью.

Фреймворк создавался по вечерам после основной работы, на выходных и даже во время отпусков. Порой код писался откровенно криво и на скорую руку. Мне хотелось поскорее закончить текущий тяжелый кусок логики, чтобы со спокойной душой переключиться на следующую интересную задачу.

Поэтому код фреймворка далек от идеала. Однако в процессе разработки я постоянно учился. Как только я узнавал какую-то новую технологию или концепцию, я тут же шел внедрять её в Gy. Так в проекте появились стандарты PSR, документирование кода, слой моделей для работы с данными, кастомные механизмы для БД и многое другое. Каждое такое внедрение приводило к масштабному рефакторингу, когда приходилось пересматривать и переписывать вообще все файлы в репозитории.

Параллельно с кодом я старался развивать и экосистему вокруг него. Я вел Wiki прямо на GitHub ( https://github.com/ssv32/gy/wiki  ), развернул отдельный сайт ( https://asisg.ru/projects/gy/ ) с документацией и даже записал несколько видеоруководств по работе с фреймворком (https://rutube.ru/plst/466964?r=wd  https://www.youtube.com/watch?v=SqHj6PZ62OM&list=PLaa5NmVx2nGjhKgdeBImncsf\_oIRcsZ2l). Сейчас за те записи мне стыдно — они получились довольно медленными, так что смотреть их стоит разве что на скорости х2. Но главное, что они есть, и по ним действительно можно понять, как развернуть сайт на моем движке.

Весь код можно увидеть тут https://github.com/ssv32/gy опубликован под лицензией GPL-3.0. Было добавлено 37 441 строк и убрано 23 209 строк, получается весь проект это 14 232 строк, но это не только php а html и css может и отступы.

Анатомия фреймворка Gy. Что внутри?

Весь фреймворк вместе с административной панелью и демо-данными весит всего 350 Кб. Для сравнения, это вес одной картинки среднего качества. При этом движок успешно тестировался на самых разных версиях PHP — от древней 5.6 до 7.2 (перед публикацией статьи протестировал на 8.3 и закрыл пару багов). Такая легковесность позволяет запускать систему буквально на «утюге» или самом дешевом копеечном хостинге.

Структура папок и архитектура

Структура файлов
gy
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── gy
│   ├── 404.php
│   ├── admin
│   │   ├── add-user.php
│   │   ├── ajax.php
│   │   ...
│   │   ├── header-admin.php
│   │   ├── index.php
│   │   ├── modules.php
│   │   ├── options.php
│   │   └── users.php
│   ├── classes
│   │   ├── Gy
│   │   │   ├── Core
│   │   │   │   ├── AbstractClasses
│   │   │   │   │   ├── Cache.php
│   │   │   │   │   └── Db.php
│   │   │   │   ├── App.php
│   │   │   │   ├── Cache
│   │   │   │   │   └── CacheFiles.php
│   │   │   │   ├── Capcha.php
│   │   │   │   ├── Component
│   │   │   │   │   ├── Component.php
│   │   │   │   │   └── Mvc
│   │   │   │   │       ├── Controller.php
│   │   │   │   │       ├── Model.php
│   │   │   │   │       └── Template.php
│   │   │   │   ├── Crypto.php
│   │   │   │   ├── Db
│   │   │   │   │   ├── MySql.php
│   │   │   │   │   ├── PgSql.php
│   │   │   │   │   └── PhpFileSqlClientForGy.php
│   │   │   │   ├── Image.php
│   │   │   │   ├── Lang.php
│   │   │   │   ├── Module.php
│   │   │   │   ├── Security.php
│   │   │   │   ├── Url.php
│   │   │   │   └── User
│   │   │   │       ├── AccessUserGroup.php
│   │   │   │       ├── GeneralUsersPropertys.php
│   │   │   │       └── User.php
│   │   │   └── Tools
│   │   │       └── Pagination.php
│   │   └── Psr
│   │       ├── Log
│   │       │   ├── AbstractLogger.php
│   │       │   ├── Logger
│   │       │   │   ├── FileRoute.php
│   │       │   │   ├── Logger.php
│   │       │   │   └── Route.php
│   │       │   ├── LoggerInterface.php
│   │       │   └── LogLevel.php
│   │       └── Psr4
│   │           └── Psr4AutoloaderClass.php
│   ├── component
│   │   ├── add_user
│   │   │   ├── componentInfo.php
│   │   │   ├── controller.php
│   │   │   ├── lang_componentInfo.php
│   │   │   ├── lang_controller.php
│   │   │   ├── model.php
│   │   │   └── teplates
│   │   │       └── 0
│   │   │           ├── lang_template.php
│   │   │           └── template.php
│   │   ├── admin
│   │   │   ├── componentInfo.php
│   │   │   ├── controller.php
│   │   │   ├── lang_componentInfo.php
│   │   │   ├── lang_controller.php
│   │   │   └── teplates
│   │   │       └── 0
│   │   │           ├── lang_template.php
│   │   │           └── template.php
│   │   ├── admin-button-public-site
│   │   │   ├── componentInfo.php
│   │   │   ├── controller.php
│   │   │   ├── lang_componentInfo.php
│   │   │   └── teplates
│   │   │       └── 0
│   │   │           ├── lang_template.php
│   │   │           ├── style.css
│   │   │           └── template.php
│   │   ├── capcha
│   │   │   ├── componentInfo.php
│   │   │   ├── controller.php
│   │   │   ├── lang_componentInfo.php
│   │   │   └── teplates
│   │   │       └── 0
│   │   │           ├── lang_template.php
│   │   │           └── template.php
│   │   ...
│   │   └── users_group_manager
│   │       ├── componentInfo.php
│   │       ├── controller.php
│   │       ├── lang_componentInfo.php
│   │       ├── lang_controller.php
│   │       └── teplates
│   │           └── 0
│   │               ├── lang_template.php
│   │               └── template.php
│   ├── config
│   │   └── gy_config.php
│   ├── fonts
│   │   └── 18018.otf
│   ├── gy.php
│   ├── images
│   │   ├── fon.png
│   │   └── gy-icons.jpg
│   ├── index.php
│   ├── install
│   │   ├── consoleInstallOptions.php
│   │   ├── installDataBaseTable.php
│   │   └── installDemoSite1.php
│   ├── js
│   │   └── main.js
│   ├── lang
│   │   ├── lang_404.php
│   │   ├── lang_component.php
│   │   └── lang_header-admin.php
│   ├── modules
│   │   ├── containerdata
│   │   │   ├── admin
│   │   │   │   ├── container-data-add.php
│   │   │   │   ├── container-data-edit.php
│   │   │   │   ├── container-data-element-list.php
│   │   │   │   ├── container-data-element-property.php
│   │   │   │   ├── container-data.php
│   │   │   │   └── container-data-property-edit.php
│   │   │   ├── classes
│   │   │   │   └── ContainerData.php
│   │   │   ├── component
│   │   │   │   ├── containerdata
│   │   │   │   │   ├── componentInfo.php
│   │   │   │   │   ├── controller.php
│   │   │   │   │   ├── lang_componentInfo.php
│   │   │   │   │   └── teplates
│   │   │   │   │       └── 0
│   │   │   │   │           ├── lang_template.php
│   │   │   │   │           └── template.php
│	│	│	│	...
│	│	│	│
│   │   │   │   └── news
│   │   │   │       ├── componentInfo.php
│   │   │   │       ├── controller.php
│   │   │   │       ├── lang_componentInfo.php
│   │   │   │       └── teplates
│   │   │   │           └── 0
│   │   │   │               ├── lang_template.php
│   │   │   │               └── template.php
│   │   │   ├── init.php
│   │   │   ├── install
│   │   │   │   └── installDataBaseTable.php
│   │   │   └── lang_init.php
│   │   └── filemodule
│   │       ├── admin
│   │       │   └── work-page-site.php
│   │       ├── classes
│   │       │   ├── AppFromConstructorPageComponent.php
│   │       │   ├── Files.php
│   │       │   └── SitePages.php
│   │       ├── component
│   │       │   └── work_page_site
│   │       │       ├── componentInfo.php
│   │       │       ├── controller.php
│   │       │       ├── lang_componentInfo.php
│   │       │       └── teplates
│   │       │           └── 0
│   │       │               ├── lang_template.php
│   │       │               ├── style.css
│   │       │               └── template.php
│   │       ├── init.php
│   │       └── lang_init.php
│   ├── style
│   │   └── main.css
│   └── test
│       └── testLoger.php
├── LICENSE
├── README.md
└── SECURITY.md

Вся логика движка изолирована в одной папке /gy/. По названиям внутренних директорий сразу понятно, где что находится: компоненты, шаблоны, системные классы и модули.

Основная фишка такой структуры — юридическая и архитектурная независимость кода:

·      Лицензии подлежит только папка /gy/.

·      Код страниц самого сайта (например, index.php), а также все кастомные наработки выносятся в отдельный раздел.

·      В кастомном разделе (/customDir/ ) разработчик может переопределять стандартные классы, шаблоны, логику компонентов или создавать свои с нуля. При этом раскрывать этот код или отдавать его под лицензией движка не требуется.

 Административная панель сосредоточена в папке /gy/admin/. При этом архитектура позволяет модулям иметь собственные независимые разделы админки, если это необходимо для управления их специфическими данными.

Как выглядит админ панель
Как выглядит админ панель
Админ панель, скриншоты

Тестирование (точнее, его отсутствие)

В репозитории можно найти папку /tests/, но пугаться или радоваться не стоит — тесты там так и не были написаны. Всё тестирование фреймворка проводилось исключительно вручную по мере разработки. Конечно, было бы круто покрыть код юнит-тестами, но дойти до 100% покрытия в одиночку — это колоссальный объем работы, на который банально не хватило времени.

Фронтенд без зависимостей

Финально в ядре фреймворка вообще не используется JavaScript. У разработчика есть возможность подключить файлы стилей (CSS) и скриптов (JS) для компонентов, а также стандартными средствами подключать. На ранних этапах разработки я внедрял jQuery, но позже сознательно полностью вырезал его, чтобы избавить систему от лишних внешних зависимостей и сохранить минимальный вес.

Слой моделей у компонентов

Полноценная архитектура подразумевает наличие слоя моделей (Model) у компонентов. Механика для этого создана, но написана она была уже на поздних этапах разработки. В качестве эксперимента и рабочего примера внедрил её всего в один компонент — add_user. Во всех остальных компонентах модель до сих пор сделана в виде заглушки, так как переписывать все компоненты фреймворка ради этого я не стал.

Установка и деплой

Для фреймворка написан отдельный установщик, репозиторий доступен на https://github.com/ssv32/install-gy-php-framework . Там же лежит скрипт, который автоматически собирает сам установщик. Развернуть Gy на хостинге можно тремя способами: через графический веб-интерфейс, напрямую через консоль или просто скопировав файлы из основного репозитория.

Графическая установка
Графическая установка
Скриншоты
Демо сайт
Демо сайт
Ещё скриншоты

Работа с СУБД и кастомная PhpFileSql

Поддержка баз данных реализована через наследование абстрактного класса Classes/Gy/Core/AbstractClasses/Db.php. Взаимодействие с БД строится на простейших операциях CRUD (создание, чтение, обновление, удаление). Благодаря отсутствию сложных перегруженных механик, подключить новую СУБД можно очень быстро. Сейчас движок «из коробки» поддерживает:

·      MySQL / MariaDB

·      PostgreSQL (добавил её чисто на кураже, сразу после посещения одной из IT-конференций).

·      PhpFileSql

Отдельная фишка — поддержка кастомной мини-СУБД PhpFileSql (https://github.com/ssv32/PhpFileSql). Когда-то я наткнулся на Stack Overflow на обсуждение файловых БД и решил ради фана написать свою. Это один класс, данные хранятся в зашифрованном файле.

Когда я интегрировал её в Gy, сработал обратный эффект, СУБД пришлось экстренно дорабатывать под нужды фреймворка. Я добавил туда полноценный PRIMARY_KEY_AUTO_INCREMENT для автоматического увеличения номера строки и исправил пачку всплывших багов, чтобы обе системы работали корректно. Для маленьких лендингов это оказалось идеальным решением — база весит около 200 Кб и лежит прямо в папке проекта (на разделе выше публичного, для безопасности).

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

// пример условия выборки данных
$dataElement3 = ContainerData::getElementContainerData(
    array(
        'AND' => array(
            array( '=' => array( 'id_container_data', $dataContentContainerData[0]['id']) ),
            array( '=' => array( 'code', "'title_h1'"))
        )
    )
);

Готовые модули

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

·      containerdata — отвечает за создание и управление «контейнерами данных» для хранения информации в админке.

·      filemodule — берет на себя управление страницами сайта напрямую из панели администратора (можно менять код файла или графически добавлять перемещать компоненты).

Также написан набор стандартных компонентов для публичной части: вывод новостей, постраничная навигация (пагинация) и вывод данных из админки.

Безопасность и архитектурная гигиена

·      Проверка окружения: Скрипты на страницах всегда проверяют, подключено ли ядро системы (defined("GY_CORE")), а в админке валидируются права текущего пользователя.

·      Фильтрация данных: Все входящие данные глобально очищаются. Единственное исключение, текстовые поля в админке, куда контент-менеджер сознательно может вводить чистый HTML-код для страниц.

·      Защита от брутфорса: Стандартный адрес админ-панели Gy можно скрыть за кастомным секретным URL-адресом.

 

Огромная часть фич так и осталась на стадии планов. Есть TODO-трекер проекта  https://github.com/users/ssv32/projects/2 но возможно уже не актуальный.

Пример использования Gy

Пример подключения gy php framework

<?php 
include $_SERVER["DOCUMENT_ROOT"]."/gy/gy.php"; // подключить ядро // include core

Пример проверки подключено ли ядро gy php framework

<?php
if (!defined("GY_CORE") && (GY_CORE !== true)) die( "gy: err include core" );

Пример вызова компонента

<?php 
include $_SERVER["DOCUMENT_ROOT"]."/gy/gy.php"; // подключить ядро // include core

// пример вызова компонента 
$APP->component(
    'form_auth',
    '0',
    array(
        'test' => 'asd',
        'idComponent' => 1,
    ),
);

Пример кода админ страницы, добавление пользователя

<?php
include "../../gy/gy.php"; // подключить ядро // include core

global $USER;

// проверим разрешено ли показывать админ панель текущему пользователю
if (Gy\Core\User\AccessUserGroup::accessThisUserByAction( 'show_admin_panel')) {

    include "../../gy/admin/header-admin.php";

    // Проверим разрешено ли работать с пользователями текущему пользователю
    if (Gy\Core\User\AccessUserGroup::accessThisUserByAction( 'edit_users')) {
        $APP->component(
            'add_user',
            '0',
            array(
                'back-url' => '/gy/admin/users.php'
            )
        );
    }
    
    include "../../gy/admin/footer-admin.php";

} else {
    header( 'Location: /gy/admin/' );
}

Код контроллера компонента add_user

Скрытый текст
<?php 
if (!defined("GY_CORE") && (GY_CORE !== true)) die( "gy: err include core" );

$data  = $_REQUEST;

// подключить модель (файл php model этого компонента) // include model this component
if (isset($this->model)) {
    $this->model->includeModel();
}

$this->model->backUrl = $this->arParam['back-url'];

$redirectUrl = str_replace('index.php', '', $_SERVER['SCRIPT_NAME']);

// взять все группы пользователей
$this->model->allUsersGroups = Gy\Core\User\AccessUserGroup::getAccessGroup();

function checkProperty($arr, $userProperty, $allUsersGroups){
    $result = true;
    foreach ($userProperty as $val) {
        if (empty($arr[$val])) {
            $result = false;
        }
    }

    if ($result) {
        foreach ($arr['groups'] as $value) {  // TODO протестировать

            if (empty($allUsersGroups[$value])) {
                $result = false;
            }

            if (!empty($arr['groups']['admins']) && !$USER->isAdmin()) { // TODO протестировать
                $result = false;
            }
        }
    }

    return $result;
}

if (!empty($data[$this->lang->getMessage('button')]) && ($data[$this->lang->getMessage('button')] == $this->lang->getMessage('button'))) {
    if (checkProperty($data, $this->model->userProperty, $this->model->allUsersGroups)) {
        // добавление пользователя
        global $USER;
        $arDaraUser = array();
        foreach ($this->model->userProperty as $val) {
            $arDaraUser[$val] = $data[$val];
        }

        // убрать группы из добавления
        unset($arDaraUser['groups']);

        if ($USER->addUsers($arDaraUser)) {
            // найти id добавленного пользователя
            global $DB;
            global $CRYPTO;
            $res = $DB->selectDb(
                $USER->tableName,
                array('*'),
                array(
                    'AND' => array(
                        array('=' => array('login', "'".$arDaraUser['login']."'")),
                        array('=' => array('pass', "'".md5($arDaraUser['pass'].$CRYPTO->getSole())."'") )
                    )
                )
            );
            $dataAddNewUser = $DB->fetch($res);

            // добавить пользователя к указанным группам
            Gy\Core\User\AccessUserGroup::deleteUserInAllGroups($dataAddNewUser['id']);
            foreach ($data['groups'] as $value) {
                Gy\Core\User\AccessUserGroup::addUserInGroup($dataAddNewUser['id'], $value);
            }

            $this->model->stat = 'ok';
        } else {
            $this->model->stat = 'err';
        }

    } else {
        $this->model->statText = $this->lang->getMessage('err_property') ; // ! Не все поля заполнены
        $this->model->stat = 'err';
    }

} elseif ((!empty($this->model->stat) && ($this->model->stat != 'err')) || empty($this->model->stat)) {
    $this->model->stat = 'add';
}

if (empty($data['stat'])) {
    header( 'Location: '.$redirectUrl.'?stat='.$this->model->stat );
} else {
    $this->model->stat = $data['stat'];
}

// установить модель этого компонента в шаблон (view)
$this->template->setModel($this->model);

// показать шаблон (view)
$this->template->show();

Код шаблона компонента add_user

Скрытый текст
<?php
if (!defined("GY_CORE") && (GY_CORE !== true)) die( "gy: err include core" );
?>
<h3><?=$this->lang->getMessage('title-add');?></h3>
<?php

if (!empty($this->model->backUrl)) {?>
    <br/>
    <br/>
    <a class="gy-admin-button" href="<?=$this->model->backUrl;?>"><?=$this->lang->getMessage('back');?></a>
    <br/>
    <br/>
<?php }?>
<?php if ($this->model->stat == 'add') {?>
    <form>
        <?php foreach ($this->model->userProperty as $key => $val) {?>
            <?=$this->lang->getMessage($val);?>:<br/>
            <?php if ($val != 'groups') {?>
                <input type="<?=(($val == 'pass')? 'password': 'text');?>" name="<?=$val;?>" />
            <?php } else {?>
                <select multiple name="groups[]">
                    <?php foreach ($this->model->allUsersGroups as $value) { ?>
                        <option value="<?=$value['code'];?>">
                            <?=$value['name']?> (<?=$value['code'];?>)
                        </option>
                    <?php }?>
                </select>
            <?php }?>
        <br/>
        <?php }?>
    <input class="gy-admin-button" type="submit" name="<?=$this->lang->getMessage('button');?>" value="<?=$this->lang->getMessage('button');?>" />

    </form>

<?php } elseif ($this->model->stat == 'ok') {?>
    <div class="gy-admin-good-message"><?=$this->lang->getMessage('add-ok');?></div>
    <br/>
    <a href="<?=$this->model->backUrl;?>" class="gy-admin-button"><?=$this->lang->getMessage('ok');?></a>
<?php } elseif ($this->model->stat == 'err') { ?>
    <div class="gy-admin-error-message"><?=$this->lang->getMessage('add-err');?></div>
    <?php if (!empty($this->model->statText)) {?>
        <br/> <?=$this->model->statText;?>
    <?php }?>
    <br/>
    <a href="<?=$this->model->backUrl;?>" class="gy-admin-button"><?=$this->lang->getMessage('ok');?></a>
<?php } 

Выводы

На разработку было потрачено прилично времени. Многое удалось реализовать, но еще больше так и осталось в планах. Чтобы три года работы не канули в лету окончательно, я и решил написать эту статью на Хабр — как минимум, чтобы зафиксировать этот опыт и узнать мнение сообщества.

Общий вывод: Проект может и не «взлететь», хотя полностью  пока не доделан. Да, всегда есть бесплатные альтернативы вроде Drupal, но они весят больше, и изучать их приходится дольше.

Вывод для себя: Это было отличное времяпрепровождение. Я решал задачи по собственному видению, написал кучу кода и прокачался в веб-разработке. Одиночка действительно может создать всё что угодно — вопрос лишь во времени. Ну и, возможно, проект кому-то понравится и появится желание поконтрибьютить в репозиторий.

Что касается будущего, есть понимание, куда двигаться дальше и что нужно переделать (я не боюсь перелопатить хоть весь проект заново). Но вот стоит ли оно затраченного времени — большой вопрос.

Если пофантазировать, путей развития несколько:

·      Интеграция сторонних решений: Подключить Composer и внедрить готовые компоненты от Symfony (например, для работы с Request/Response) или продолжать писать всё вручную.

·      Публикация пакета: Добить код до минимального стабильного состояния, чтобы на движке можно было гарантированно развернуть сайт с нуля, и выкатить его в Packagist.

·      Глубокий рефакторинг: Начать писать локальный аналог битриксового ядра D7 или просто переписать всё с нуля в Gy 2.0. Правда, если реализовать вообще все современные стандарты PSR, то на выходе получится аналог Symfony или Laravel, а делать их копии нет никакого смысла.

Также была забавная мысль, сделать на сайте фреймворка форму с тестом и в конце выдавать «Сертификат сертифицированного разработчика Gy». Который можно ради шутки добавить в свое резюме — пусть HR удивляются сертификату по абсолютно неизвестной CMS.


Опросы

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
1. Стоит ли написать на Хабр еще пару статей с подробностями, разбором архитектуры, реализацией MVC и прочим «мясом» и кодом?
20%Да4
80%Нет, этого обзора вполне достаточно16
Проголосовали 20 пользователей. Воздержались 4 пользователя.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
2. Стоит ли вообще продолжать развивать этот проект?
14.29%Да, продолжай, это интересный велосипед3
85.71%Нет смысла, проще взять условный Drupal и не изобретать колесо18
Проголосовал 21 пользователь. Воздержались 3 пользователя.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
3. Если продолжать разработку, какой путь выбрать?
70.59%Использовать готовые компоненты (Symfony и др.), чтобы не писать базу с нуля12
29.41%Писать абсолютно всё самому ради полной независимости и фана5
Проголосовали 17 пользователей. Воздержались 4 пользователя.