惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Privacy & Cybersecurity Law Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
宝玉的分享
宝玉的分享
V
V2EX
爱范儿
爱范儿
Last Week in AI
Last Week in AI
美团技术团队
人人都是产品经理
人人都是产品经理
WordPress大学
WordPress大学
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 叶小钗
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Apple Machine Learning Research
Apple Machine Learning Research
Security Latest
Security Latest
C
Cybersecurity and Infrastructure Security Agency CISA
Know Your Adversary
Know Your Adversary
I
Intezer
K
Kaspersky official blog
阮一峰的网络日志
阮一峰的网络日志
大猫的无限游戏
大猫的无限游戏
T
Tenable Blog
AWS News Blog
AWS News Blog
小众软件
小众软件
博客园 - 司徒正美
Cyberwarzone
Cyberwarzone
NISL@THU
NISL@THU
博客园 - 三生石上(FineUI控件)
C
CERT Recently Published Vulnerability Notes
博客园 - 聂微东
量子位
有赞技术团队
有赞技术团队
S
Schneier on Security
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
S
Secure Thoughts
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
V
Visual Studio Blog
Google DeepMind News
Google DeepMind News
L
Lohrmann on Cybersecurity
P
Palo Alto Networks Blog
P
Privacy International News Feed
L
LINUX DO - 最新话题
博客园 - Franky
雷峰网
雷峰网
月光博客
月光博客
Hacker News: Ask HN
Hacker News: Ask HN
Forbes - Security
Forbes - Security
博客园 - 【当耐特】
C
Cyber Attacks, Cyber Crime and Cyber Security

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Разработка длиною в 3 года: как я писал свой PHP CMS/Framework Gy, «убийцу» Битрикса, весом 350 Кб
ssv32 · 2026-06-23 · via Все публикации подряд на Хабре

Под катом делюсь обзором своего самописного 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.


Опросы

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

0%Нет, этого обзора вполне достаточно0

Никто еще не голосовал. Воздержался 1 пользователь.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

100%Да, продолжай, это интересный велосипед2

0%Нет смысла, проще взять условный Drupal и не изобретать колесо0

Проголосовали 2 пользователя. Воздержавшихся нет.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

100%Использовать готовые компоненты (Symfony и др.), чтобы не писать базу с нуля1

0%Писать абсолютно всё самому ради полной независимости и фана0

Проголосовал 1 пользователь. Воздержавшихся нет.