Принципиальная схема маленького (16 K), но вёрткого php-фреймворка, не ставящего во главу угла mvc с ООП, но претендующего на самый высокий в мире уровень автоматизации веб-приложений.

Что из чего следует в PHP-фреймворке «Friends»

«Friends» - рабочее название маленького PHP-фреймворка (16 K кода, вместе с CSS, но пока без javascript), находящегося в сомнительных отношениях с mvc, но бодро работающего.

В предыдущей статье мы начали описывать создание идеального php-фреймворка, продекларировав готовность отступать от принципов mvc-ООП при малейшей возможности. Начальные шаги были достаточно просты, и описывать их детально не требовалось, мы говорили явно только о некоторых необычных решениях. Фреймворк претерпел пять изменений, все они отображены (зафиксированы навеки) в папке http://ir2.ru/static/friends/www/repa/ . В изменениях мы последовательно воплощали принципы, заложенные в статье.

Последнее изменение (версия 6b – !mvc6b.zip) настолько усложнило фреймворк, что без подробного описания его работы уже трудно будет уследить за всеми происходящими qui pro quo (что из чего следует).

В файле .htaccess (1) правило rewrite перенаправляет все запросы к сайту на index.php (если не существует файл, вызванный запросом).

В файле index.php (2) определяются две глобальные переменные (2.1): корневая папка сайта ($def['site_path']) и папка библиотеки ($def['lib']), которая должна (сейчас) находиться на один уровень выше папки сайта; (2.2): находится функция __autoload(), подключающая файлы с вызываемыми классами (файлы должны быть в папке библиотеки).

В файле index.php создаются также два объекта (2.3): new Config() и new Controller(), которые потом чего-то там сами по себе делают. И это всё. Ну, есть там ещё несколько мелких обслуживающих функций (типа "получить текст файла").

Модель

Модель (3) определяется в двух массивах в классе Config: 'meta' – описание сущностей (место хранения данных, название, отношения с другими сущностями...); 'fields' – описание полей (отображаемые наименования, первичный ключ, надо ли выводить в списке...). Заведён отдельный класс в файле Model.php, но пока неясно, для чего его будем использовать. В Модели могут быть, конечно, свои методы, но не такие, как во фреймворке kissmvc (на который мы сейчас молчаливо ориентируемся). Например, в классе KISS_Model функция create() использует запрос INSERT INTO для добавления в таблицу нового пользователя. Этот запрос меняет количество данных, но не меняет их структуру, а значит, не меняется и Модель.

Модель может меняться, например, запросом ALTER TABLE. А может меняться и вообще без изменения структуры таблицы. Например, у нас в таблице people содержатся сейчас две сущности – Друзья и Враги. Если мы захотим добавить ещё одну сущность («Незнакомцы»), нам надо будет изменить Модель – добавить строку в массив meta:

Трудно (да и небезопасно) организовать метод Модели, меняющий PHP-файлы, поэтому ясно, что Модель должна храниться в более удобном для редактирования месте – в отдельном текстовом файле (csv, xml), а лучше, конечно в таблице БД. (3.1) Это на будущее. Да, Модель должна быть доступна из любого места скрипта, поэтому (в соответствии с принципом KISS) при создании объекта Config она помещается в глобальный массив $def.

Класс Config (4) существует только для создания глобального массива def (4.1), в котором сейчас лежит что попало: и Модель, и Настройки, и Переменные окружения... Настройки, которые планируется в дальнейшем изменять через визуальный интерфейс, хранятся в файле site.ini (4.2). Это данные для подключение к БД (пользователь, пароль, хост...), debug_mode, почтовый адрес администратора сайта... Хотя все эти настройки перекрываются «местными» конфигурациями (4.3), которые (по совету, найденному в статье http://blgo.ru/blog/2011/01/26/config-joint-platform/) хранятся в папке lib/platforms в php-файлах с условными именами вида «homepc», «hosting1» etc. Управляет всем этим файл moe_mesto.php (там написано имя файла конфигурации для данного компьютера), который находится в папке на один уровень выше $_SERVER['DOCUMENT_ROOT'] (4.4). Если «местных» файлов нет (нет файла с именем, указанным в moe_mesto.php), все настройки берутся из site.ini.

В Модели могут быть описаны следующие отношения между Сущностями: Субъект А может требовать включения Субъекта Б:

Ключ add ссылается на массив, в котором можно перечислить дополнительные Сущности, которые следует отображать на странице после отображения основной Сущности. Ну, или не отображать, а извлекать эти дополнительные данные и ещё куда-нибудь девать. Зависимость: Представление должно проверять ключ add во входящих Данных.

Зарезервированное слово для наименования полей таблиц: func. Данные, помещённые в это поле, обрабатываются особым образом.

В Модели может быть указан способ отображения Сущности. Это зависимость: такой же способ отображения должен быть описан во View.

Контроллер

Контроллер (5) анализирует Запрос (хотя у нас в системе пока не определён такой объект) пользователя: REQUEST_URI и $_GET['id']. Пока у нас очень простая, одноуровневая структура, поэтому Контроллер проверяет только последнюю часть в строк запроса – /slovo.ext. Он ищет slovo (5.1) в массиве Сущностей Модели, и, если не находит, возвращает ответ "404 not found". Затем Контроллер проверяет $_GET['id'] (5.2), и если оно существует, помещает его в $def['id'] – чтобы все остальные (Данные, Представление) могли это знать.

Это неправильно. Нужно будет изменить эту схему. Ведь идентификатор может быть не только ID (например, в не используемой пока таблице Пользователей у нас первичный ключ name). Контроллер должен залезать в массив fields и проверять имя первичного ключа текущей Сущности (или имена – если ключ составной!), а затем помещать в $def['id'] не одно значение, а массив (5.3) вида "имя ключа" – "значение". И не только первичного ключа – ведь в запросе может быть определён отбор по какому-то другому полю.

Затем Контроллер (он у нас очень худой, всего 30 строк) вызывает класс View с параметрами Сущность и ИД.

Представление

Представление (6) и все Шаблоны (6.1) хранятся в одном классе View. В будущем, возможно, мы его разделим. Если будет потребность (а пока выполняем требования KISS – keep it simple, stupid!). Шаблоны у нас существуют в виде функций php: определяется первичный массив необходимых элементов для подстановок и заполняется значениями по умолчанию; затем входящий массив (параметр) перезаписывает элементы первичного массива; затем возвращается шаблон, заполненный переменными из массива.

Интернет наводнён воплями о разделении труда и о бедных верстальщиках, которые должны разбирать хитрые конструкции шаблонизаторов. Ну, ясно, что участие верстальщика в нашей системе вообще не предусмотрено. Как, впрочем, и должно быть по уму. Верстальщик – снаружи системы. И это ещё очень большой вопрос, кто кому должен говорить, что делать. Фреймворки ставят всё с ног на голову: предполагается, что php-программист должен сообщить верстальщику имена переменных (а то и систему условных конструкций!), которые верстальщик потом должен как-то запихивать в HTML.

Но в природе-то всё происходит наоборот! 1 => Дизайнер согласует с Клиентом общий вид Главной страницы; 2 => Дизайнер отдаёт Верстальщику бумажку, как всё должно выглядеть (ну, если очень добрый Дизайнер, может дать ещё файл .cdr); 3 => Верстальщик «нарезает графику» и воплощает всю эту красоту в HTML-коде; 4 => Программист берёт эту красивую HTML-страницу и превращает её в Шаблон (точнее, в набор Шаблонов).

Отношения (связи). И класс Data, и класс View принимают при создании объекта в качестве параметров Сущность, Идентификатор и проч. Но они могут работать и без входящих параметров – по умолчанию они берут всё необходимое из $def, где всегда есть актуальная Сущность и может быть актуальный Идентификатор. (7.1)

Основных шаблона сейчас три: шаблон страницы, шаблон таблицы (table), и шаблон объекта или списка (item). Части системы выбирают способ отображения, исходя из наличия $def['id'] (если есть – item, если нет – таблица, или особый шаблон, если он указан явно). Явно можно указать что угодно, но сейчас практически работает только тип отображения 'raw' – он принимает строку и помещает её между тэгами pre.

Наше Представление откуда-то знает (!), что для всех входящих Данных следует проверять ключ Модели add (и если там что-то есть, обращаться в Данные за дополнительными Сущностями).

Данные

Класс Data (8). Версия 6b фреймворка почти вдвое распухла по сравнению с пятой из-за расширения работы с Данными. Изначально они хранились у нас только в массивах php (8.1). В версии 6 их стало можно получать из mysql (8.2). Теоретически можно и из других источников: нужно добавить драйвер и указать источник в массиве meta Модели. Например, можно открыть страницу http://ir2.ru/static/friends/www/sql_tables (ссылки нет в меню), и увидеть в заголовке страницы (title): "source: file" (это отображение включено для отладки). В данном случае, конечно, нет отдельного класса для драйвера File ввиду простоты (там только одна маленькая функция), и это, видимо, плохо (надо будет всё привести к одному виду, как в армии) (8.3). А на странице http://ir2.ru/static/friends/www/friends.htm есть надпись source:Mysql, и это правда (есть такой класс – Mysql).

Все первичные данные (извлекаемые на самом низком уровне) обрабатываются чем-то вроде htmlspecialchars (8.4). Это неправильно (но пока хватает). Нужен а) входной параметр (из вызывающего объекта – Вью или какого-то другого), флажок, указывающий на необходимость такой обработки, б) нужно проверять Модель на наличие указаний об отношениях с HTML (можно ли его пропускать), а в Модели, соответственно, установить такой флаг (8.5).

Администрирование

В классе Admin (9) доступны сейчас два действия: Установить БД (выполнить sql-запросы по созданию и частичному наполнению таблиц) и Переустановить БД (9.2). Класс "зарегистрирован" как Сущность в Модели (массивах meta и fields). Точнее, в качестве Сущности зарегистрирована более конкретная "команда" – instal (при написании с двумя l возникли какие-то проблемы на веб-сервере: ссылка http://ir2.ru/static/friends/www/instal возвращает ответ сервера 200, хотя такого файла нет, и сущность в Модели не описана), а класс Admin зарегистрирован как "таблица" (в которую могут быть включены и другие Сущности – команды).

Перед установкой БД следует правильно заполнить в файле site.ini (находится в папке сайта, где index.php) поля host, port, user, pw, bd, bd_prefix. Поле table_prefix заполняется прозвольно, как кому хочется (по умолчанию там значение 'fr_'). Если уж вы указали префикс БД, то наименование БД следует писать в файле site.ini без префикса (например: bd='friends', bd_prefix='ir2_' => внутри фреймворка будет использоваться их сочетание: ir2_friends). Если вы укажете неверные значения для подключения к БД, при попытке установить (создать) таблицы увидите соответствующую ошибку mysql. Да, помните, что значения в файлах, расположенных в папке friends/lib/platforms, имеют приоритет (будут перезаписывать значения из файла site.ini).

В папке сайта лежит файл install.0 (9.3). Сейчас он не работает. А должно быть так: если файл существует, процедуру установки может запустить любой дурак пользователь, если нет – только администратор. Ну, и процедура установки должна удалять этот файл, ясно море. Но пока у нас нет авторизации, в этом нет смысла. Устанавливайте и переустанавливайте, скоко хотите!

Главное todo

Пока не забыл. Схема получения данных должна быть такая.

  1. Берём из УРЛ (или ГЕТ) все параметры и сверяем их с описанием полей (по именам) в Модели (массив fields): если в запросе присутствуют ВСЕ параметры, составляющие (в Модели) первичный ключ, записываем их в $def['id'] (всегда в виде массива), и дальше Вью автоматически выбирается для одного объекта (а не таблицы).
  2. Если в запросе НЕ ВСЕ параметры для выбора по первичному ключу (или вообще нет параметров выбора, а только имя Сущности), во Вью выбирается Таблица.
  3. Если указан хотя бы один параметр в запросе (не обязательно полный первичный ключ), Данные выбираются на основании этих параметров (в Sql передаётся $where = array('name1'=>'value1', 'name2'=>'value2'... ), который по умолчанию соединяется там через AND).
  4. Ссылки для отдельных объектов формируются не явно по id, а на основании fields Модели: выбираются ВСЕ значения полей (составного) первичного ключа. Соответственно, указание должно быть не link => id, а link => 'primary key', и дальше как-то там это обрабатывать.

D.M., admin

Комментарии

лесник 15.11.2011 01:08:05

В версии 8 (см. папку http://ir2.ru/static/friends/www/repa/) выполнена задача по искоренению явных имён из Представления и Данных: теперь Контроллер ищет в Запросе не поле 'id', а первичный ключ, указанный для данной Сущности в Модели. Эта задача вообще-то была решена ещё в версии 7, но код версии 8 ещё и сильно отрефакторен (в седьмой версии накопилось много несообразностей).