Сайт вебмастера

Эффективная архитектура PHP-приложения

23-03-2024Reading time ~ 8 min.Albireo Framework / CMS 923

Изначально я хотел просто использовать Albireo Framework за основу Albireo CMS, но в процессе поставил перед собой задачи, для которых структура фрейворка не подходила для CMS. Хотя, вообще, если заглянуть намного назад, то был (в общем-то и есть) у меня проект, где я хотел написать свой полноценный PHP-фреймворк, наподобии CodeIgniter, только более гибкий. Разрабатывал я его в свободное время несколько лет и постепенно вырисовывалась очень приятственная картина. Дальше 18+.

Этот php-фреймворк построен по всем канонам ООП. Его роутинг спокойно работает с классами, функциями, файлами. Более того, можно сделать так, чтобы просто в роутинге указать модуль, и тот сам посмотрит какие ротуты в этом модуле используются. То есть можно без проблем создать полностью переносимый (между сайтами) модуль. Для тестирования с сделал todo-модуль, форму простую, форму регистрации, форму авторизации. Всё, есно, рабочее.

Там есть хуки, миграции, парсинги, база через PDO, безопасность, валидация форм и любых данных, полная документация и демо-сайтик. Ах, да ещё и админка с кучей наворотов. В общем, красота, пиздец, можно гордиться!

Админка в моём php-фреймворке

Свернул я этот проект в итоге.

Проблемка оказалась в том, что я слишком много насмотрелся других фреймворков, где много голимой теории, но мало практики. Нахера вообще нужен фреймворк? Правильно, для того, чтобы делать сайт. И вот если взять тот же CodeIgniter, то он даёт кучу php-кода, но не даёт возможности быстро построить сайт.

Парадокс вот в чём.

Сделать сайт на PHP может любой дурак. Текстовый файл с расширением php и лепи в нём что хочешь. Зачем для сайта вообще существующие php-фреймворки, если на них невозможно просто поднять примитивное hello world?

Свой php-фреймворк я делал из расчёта, что к нему можно будет прикрутить любой шаблон. Ну там шапку, подвал, дизайн какой-нибудь. Архитектурная проблема в том, что шаблон вывода это не что иное, как вьюшка в модели MVC и управлять этой вьюшкой нужно на уровне самого модуля. То есть нельзя просто так раскидать html и php-код в разных частях приложения. Если в модуле нужно поменять css-класс кнопки, то всё, нужно лезть в view. Жопа.

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

Ещё я хотел, чтобы можно было запустить страницу как html-файл без подключения к базе. Выяснилось, что нет таких фреймворков. Тупо нет. Все по умолчанию конектятся к майсэквел.

Потом я изучал Flat File CMS — те которые на файлах. Но что меня удивляет, так это то, что если система на файлах, то в ней не предусмотрена работа с базой, то есть обратная ситуация. Почему, может язык PHP не позволяет?

В итоге я составил ТЗ для будущей CMS, но потом потратил много времени на его переписывание. Я понял, что существующие фреймворки элементарно не предназначены для простого использования. Их авторы делают всё, чтобы создать кучу зависимостей и раскидать файлы по непонятным каталогам. Но самое главное это то, что авторы фреймворков не делают сайты. Просто нет опыта работы с клиентами. Я бы посмотрел, как авторы ларавеля, кодегнайтера или симфони сделают на своих детищах сайт-лендинг из двух страниц, где одна — форма обратной связи. Ну и всё это на обычном шаред-хостинге.

Короче, суть в том, что строить приложение нужно от конечных задач пользователя. То есть вся та мутотень со «стандартами» летит прямиком в помойку.

Есть админка и есть сам сайт. Связаны ли они между собой? Конечно, но с оговорками. Админка работает с файлами сайта и работает с базой. Сайт ничего не знает об админке, но работает с базой. Опять же работа с базой — только по необходимости.

Получаются три каталога (как пример): admin (для админки), website (для сайта), storage (для базы - пока речь об sqlite). То есть когда чел входит в admin, то работает админка. Замечу, что это каталог, а значит его можно легко менять на произвольный. Чтобы достучаться до баз и сайта, нужно вылезти на уровень выше. Технически это dirname().

Теперь вопрос будет ли у сайта и админки единое ядро. Да, делаем каталог system, где и будем держать код.

На практике, если пользователю нужна админка, он набирает адрес с admin, а если нужен сайт, то набирает адрес сайта, где корневой index.php подключает website.

Почему так? Всё от итоговых задач. Если нужно разделить сайт, админку, ядро и базы, то это отдельные каталоги. Это намного проще, чем пытаться разделять их функциональность в едином котле.

Из этого вырисовывается интересная возможность: по сути админка и сайт могут иметь одну и туже структуру и функциональность. Иметь свой шаблон вывода, дизайн, конфигурации, библиотеки и т.д. И всё это индивидуально. Если я захочу сделать ещё один сайт в подкаталоге домена, то всё, что нужно сделать, так это создать каталог и повторить структуру admin/website.

Исходя из этого определяем точку входа в приложение. По сути это обычный index.php в admin/website (он одинаковый для каждого каталога).

Что нужно делать в этом файле? Здесь всё будет зависеть от логики приложения, но в общем первый этап будет следующим:

  • Вначале подключаем ядро. Это функции из _system.
  • Потом выставляем константы приложения.
  • Если нужно, подключаем автолоад композера.
  • Выполняем функцию автолоада psr4 - spl_autoload_register().

Следующий этап завязывается на роутинг. У меня в Albireo CMS — это файлы, поэтому происходит считывание всех файлов, их анализ на нужные данные, но если речь чем-то другом, то вначале нужно получить текущий URL и раскидать его в массив и сохранить в общем хранилище. (Пока все технические детали опускаю.)

Почему нужно получить текущий URL? Все адреса должны строиться от адреса сайта. Это может быть домен, а может быть каталог домена. Это нужно уметь определять автоматом. При необходимости автоматом будет меняться и .htaccess, поскольку на него завязано ЧПУ.

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

Теперь смотрим с обратной стороны. Как пользователь будет определять адрес страниц сайта? У меня это сам файл (как и в Albireo Framework): в файле указывается поле slug оно и проверяется в роутинге. Если бы я расширил роутинг на базу, то вначале проверял бы именно файлы, а если файла нет, то уже тогда подключил бы базу и проверил в нужной таблице поле slug. В любом случае роутинг находит соответствие и сохраняет его в общем хранилище. Само выполнение файла или данных из базы на уровне роутинга не происходит.

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

Таким образом точка входа представляет собой последовательность выполнения нескольких функций. В моём случае это примерно так:

require_once SYS_DIR . 'load_functions.php'; // функции ядра
setBaseConstants(); // устанавливаем базовые константы
if (file_exists(COMPOSER_DIR . 'autoload.php')) require_once COMPOSER_DIR . 'autoload.php';
psr4Autoload();   // PSR4 - autoload
getSessionId();   // запуск механизма сессий
createHtaccess(); // create .htaccess
readPages();      // считать данные всех pages
getCurrentUrl();  // получить данные по текущему URL
matchUrlPage();   // подключаемый файл страницы
pageOut();        // output

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

Как устроен каталог сайта (ну или админки, не важно)? Как правило, есть постоянные задачи, поэтому доступ для их решения должен быть простым.

Конфиги храним в config. Причём конфиг-файлы это обычные php-файлы, которые возвращают данные по return. Не нужно использовать вcякое говно, вроде ini, yaml, json, toml и т.п. В PHP есть свой формат, который ничем не уступает. Вот им и пользуемся. И это сразу исключает потребность в функциях или библиотеках, которые работают с этим говном.

Дальше, шаблоны с дизайнами храним в templates, где каждый шаблон в отдельном подкаталоге. По шаблонам потом напишу.

Есть каталог service, в котором всякое-такое, что не нужно держать на виду. Например файлы для инсталяции, кэши, бэкапы, а также собственные psr4-библиотеки.

Остальное на свой вкус. У меня есть events (что-то вроде хуков), widgets для виджетов и snippets для снипетов. Просто потому что так удобно. Не нужно всё прятать. Данные у меня хранятся в pages, потому что всё в файлах страниц.

Теперь смотрим со стороны пользователя. Он входит в основной каталог и ему сразу видны доступные возможности. Не где-то на задворках, а всё рядышком, но аккуратно расфасовано.

Как устроено ядро system? Удивительно просто. Есть каталог functions, где хранятся файлы функций. Причём (за редким исключением) каждая функция хранится в отдельном файле. Я к такой схеме пришёл не сразу, но пиздец, как это оказалось удобно! Все функции подключаются автоматом в load_functions.php через glob и foreach. То есть чтобы добавить новую функцию нужно просто сделать её файл и он автоматом подключится.

Каталог psr4 для классов. Тут стандартно работаем по namespace.

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

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

Такой подход решает сразу несколько задач. Однотипные каталоги сайтов. Всё красиво сгруппировано по функциональности. Обновление ядра не затрагивает данные и каталоги сайтов. Конфиги индивидуальны. Базы — отдельно и легко обслуживаются. Большие файлы в uploads — легко синхронизируются по ftp. Расширение функциональности в пределах каждого сайта индивидуально.

Ну и самое главное — это не тупой фреймворк, а полноценное php-приложение.


Слава Украине! Смерть рашистам!

Related Posts
Comments (2) RSS
1 Андрій 2024-03-27 01:50:01

Друже, я ні на чому не наполягаю, але сам трохи ходив цими граблями.

І от до чого прийшов: NodeJS має вже вбудований сервер, тобто не треба Apache, все з коробки працює. А хостинг дешевий знайти так само легко, як і для PHP. Простенький проект на Express ти зможеш написати за кілька годін/днів (залежить від підготовки, наявного часу, складності проекту, тощо). При чому, можеш як БД заюзати, так і статичні файли (мій особистий вибір: Markdown) - хоч окремо, хоч одночасно. На додачу, раджу робити окремо API, і окремо фронт: React або View доволі прості для розуміння. А на перспективу -- TypeScript і NestJS.

Перейшов, і не шкодую.

PS: PHP досі застосовую, в т.ч. веду проекти на WordPress, але все більше люблю власне підхід Headless WP


2 Віктор 2024-04-28 10:56:47

Окреме "дякую" за роз'яснення про каталог functions, бо в Альбірео я користувався виключно snippets для таких цілей, додаючи потрібне в pages, які інколи виглядали як 2-3 строки з підключенням потрібних сніпетів. Робив ще варіанти зі зміною шаблона сторінки, навіть змішані є (шаблон + сніпети), залежно від потрібного. Доволі гнучка система в цьому плані.

Leave a comment!