Кто сказал, что в Модели должны быть какие-то классы и функции? Модель – это простая табличка, список. Точнее, две таблички: список моделируемых Сущностей реального мира и расширенное описание структуры Данных

PHP фреймворк, анти-MVC, без ООП

С самого начала изучения MVC мне не понравилось слово «модель». И не зря. На громадном количестве сайтов это понятие действительно не применимо. Там есть Данные, есть какое-никакое их Отображение. Вынуждено быть что-то вроде Контроллера (а то как бы пользователи попадали с одой страницы на другую). Но совершенно непонятно, что там могло бы называться Моделью.

В теории Модель – это схема какого-то объекта реального мира (или схема взаимоотношений нескольких таких объектов). Но вот какая, например, должна быть схема для объекта "Книга в одной странице"? Сущность такого объекта – массив текста (точнее, содержание этого текста). В Интернете это так и будет – одна страница текста. Всё. У объекта будет два свойства – сам текст и адрес, по которому можно этот текст найти (ну, на самом деле там ещё много мелких «атрибутов» типа названия, автора, года выпуска...). Теоретически Модель есть, но кому она такая нужна?

Потребность в моделировании объектов внешнего мира возникает, если на сайте, например, хранится больше одной книги. Такому сайту уже нужна будет Картотека (в терминах реальной библиотеки) или Каталог. Ну, если книг не очень много, можно обойтись и меню – что в реальности соответствовало бы списку всех книг на полях каждой книги. Это, конечно, ещё не очень большая модель, но всё-таки.

Второй звонок, указывающий на нечто, не выводимое ни из Данных, ни из их Представлений, прозвенел для меня при попытке обойти неровности «прямого» отображения Данных (статья http://dn.ir2.ru/stecker.aspx – о некоторых случаях асимметрии Данных и Представления при использовании шаблона проектирования «Калька»).

Третий звонок, заставивший задуматься о сущности Модели, прозвенел в вопросе на одном форуме: должна ли Модель использовать методы извлечения Данных из БД?

И тут, для полного разрешения всех накопившихся вопросов, под руку подвернулся очень удачный материал – схематичный мини-фреймворк, описанный в статье http://artanovy.com/2011/03/arhitekturnyj-pattern-mvc/. Удобен он оказался тремя важными свойствами: 1) микроскопическим размером (можно «охватить одним взглядом»), 2) тем, что исполнен в виде работающего примера (в отличие от многих подобных разработок), 3) данные не привязаны к mysql. При этом основной скелет MVC-ООП там присутствует. Практически идеальный материал для иллюстрации иллюзорности «классического» MVC-ООП в вебе (вот как выглядит этот мини-сайт в работе: http://ir2.ru/static/artanov/www/ – дальше будем называть его «веб-приложение Друзья»).

Редкое стечение обстоятельств. Обычно ведь как: разрекламирует кто-нть свою разработку, скачаешь, откроешь в браузере, а там Fatal error (это в лучшем случае, а чаще сразу Internal Server Error). Ну, охота копаться, искать проблему? А если оно «работает», так тоже лень бывает разгребать мегабайты (или сотни килобайт), чтобы посмотреть, как внутри устроено. А у Артановых всё работает, всё очень маленькое и удобное – вот я и дошёл до пункта 3), который стал решающим фактором в более глубоком понимании Модели. Я понял, что до сих пор мне мешала жёсткая привязка всех проектов к БД.

Модель – не Данные

Как мы показали на примере «одностраничной книги», Модель на сайте может практически не использоваться, но Данные должны быть обязательно. Однако нельзя сказать и того, что Данные не имеют никакого отношения к Модели. Модель всегда частично присутствует в Данных. Данные ведь всегда структурированы; даже если они не в БД, у них обязательно бывают всякие «метки» типа Даты последнего изменения, Заголовка, Автора... Вот совокупность этих меток как раз и относится к Модели; именно Модель (требования реального мира) определяет, что, например, в какой-то таблице Данных должны быть уникальные наименования (или уникальные идентификаторы), или наименования могут повторяться, пофиг.

Вот это частичное присутствие Модели в Данных как раз и запутывает, застилает глаза. Я давно чувствовал, что где-то тут лажа зарыта, мне никак не хватало «меток» mysql для детального описания «метаданных», и я начал создавать «реестры» (в массивах PHP) для описания поведения данных после вытаскивания из БД. Наиболее очевидный и простой пример – русские наименования полей в таблицах. Допустим, надо вывести какой-то большой список (например, http://irkutsk.ir2.ru/list_idrow_DESC). Так что же, руками писать в заголовках таблицы «Рубрика», «Дата»?.. Если не руками, а прямо типа «дамп» из mysql делать – будут латинские наименования, не очень удобно. Так где взять русские? Вот и приходится создавать массив в PHP, где описано каждое поле таблицы (и где полям, в частности, сопоставлены русские наименования).

Может показаться, что русские имена полей – область действия Вью (Представления). Но это не так: Представление может выбирать, использовать локализованные имена, или «встроенные», внутренние; но принимать решение о наличии варианта с локализацией должна Модель.

Модель намертво привязана к Данным

Модель не может существовать «в воздухе», ей нужен какой-то материал. Модель – это, по сути, «расширенная» структура Данных, метаданные. Поэтому нельзя сказать, что она не зависит от Данных; Данные и Модель невозможны друг без друга, они связаны как форма и содержание, как система (совокупность объектов) и структура (совокупность связей в системе).

Модель привязана к Представлению

«Модель ничего не знает о Виде" – типичная фраза из статей об mvc. Практика показывает нечто иное. Во-первых, основная »модальность« Модели немного иная: Модель не »знает"", а предписывает, командует, прокладывает жёсткие рельсы, по которым потом едет веб-приложения (мы ведь именно о вебе говорим?). А, во-вторых, попробуйте-ка покомандовать, ничего не зная о подчинённых!

Представление – это тоже конкретный материал, такой же (ну, немного другой), как Данные. Например, в Представлении должна быть гиперссылка. Если Модель об этом не будет знать, она не подготовит правильно объект (у объекта не будет свойств, из которых Представление сможет формировать валидные ссылки).

Модель знает и помнит о Контроллере

Точнее, конечно, не Модель, а программист – должен запихать кусочек Модели в Контроллер (или определить «перед» Контроллером). Примерно такой кусочек:

Код 1: Модель – Контроллеру.

Это нужно для скорости работы. В реальности Контроллеру нашего веб-приложения «Враги» (о котором речь дальше) мог бы соответствовать библиотекарь. Схема его работы при «плохой» Модели: вы запрашиваете сущность «друзья», библиотекарь идёт в Каталог, ищет ящик с нужной биркой, возвращается и говорит, что такого ящика нет. При хорошей, «правильной» Модели библиотекарь держит массив сущностей в голове и сразу отвечает, что нужного ящика в каталоге нет. Ну, сущностей может быть так много, что в голове не удержишь; тогда в БД должен быть реестр – ма-аленький ящик со списком, по которому можно быстро определить, что нужного большого ящика нет.

У нас в реестре есть ещё и количество. Может, сущность есть, но карточки в ящике отсутствуют. Тогда при плохом раскладе библиотекарь находит нужный ящик, вытаскивает его и пытается пересчитать несуществующие карточки. При хорошей практике опять заглядывает в реестр и говорит, что сущность-то есть, но карточек нет – та же ошибка 404, быстро возвращаемая Контроллером с минимальным использованием БД.

Условности

Понятно, что разделение по типу mvc (или data + mvc) достаточно условно, и соседние «слои» в нём всегда будут отчасти проникать друг в друга. И в общем-то не так уж и важна «чистота» Модели или Контроллера. Важнее сводить к минимуму неявные зависимости – случаи, когда значения появляются «из ниоткуда». А модульность (куски относительно «свободного» кода, легко переносимые из одного приложения в другое) ведь может строиться совершенно перпендикулярно паттерну mvc.

Неявные зависимости

А вот теперь о главном. В веб-приложении «Друзья» (http://ir2.ru/static/artanov/www/mvc.zip – код) очень удобно искать узкие места (код простой, чистый и понятный). Во всех фреймворках я, например, сразу смотрю на конечный результат – на шаблоны. И если вижу два файла, в которых повторяется DOCTYPE или META charset, уже ясно, что какой-то непорядок. Общая для всех часть должна быть вынесена «за скобки». Это просто.

А есть и кое-что посложнее. Его можно обнаружить, если попытаться ввести какие-нибудь новые сущности – например, «Враги» и «Задачи». По существующей внутренней логике приложения каждой из новых сущностей нужен будет новый шаблон, который будет брать информацию из новых классов (Enemies->getItem(), Jobs->getItem()...). Это порочный путь: при таком подходе размер кода будет расти лавинообразно, и код быстро станет неуправляемым. Или неудобоуправляемым: если мы найдём неявную зависимость в классе Друзей, её надо будет устранять так же и у Врагов, а потом и в куче других мест.

Потому что ведь ясно, что Друзья и Враги – структурно одно и то же, только с разными знаками. Допустим, у класса Друзей есть метод «Дать мне деньги», а у класса Врагов «Отнять деньги» (ну, или, там, отнять хорошее настроение). В Математической Модели это будет одно действие: +100р. или -100р. Да и во всём остальном – Друзья и Враги обладают одними и теми же свойствами (Год рождения, Имя, e-mail...).

Итак, понятно, что класс у них должен быть один, и шаблон должен быть общий. Так как же тогда Приложение будет узнавать что писать в этом общем шаблоне в title: Мои друзья или Мои враги?

Другая структура классов

Ясно, что здесь не поможет очевидная замена <title>Мои друзья</title> на <title><?php echo $oFriend->title; ?></title>, потому что класс FriendList не умеет вычислять title – в Модели вообще не предусмотрен атрибут title для разных сущностей. А придётся. Это первое, с чего мы начинаем моделирование, – список сущностей, с которыми предполагается работать в Приложении:

Теперь, когда Контроллер имеет доступ к такому исчерпывающему списку, несложно перенаправить заклинание пользователя вида "GET /friends/" по адресу "class People". Но и это будет слишком мелко. Пока мы не планируем отображать такие действия, как "Дать деньги" или "Отнять деньги" (или, например, "Насрать на диван" – для Кошек), класс Кошек ничем принципиально не отличается от класса Друзей или Врагов. Ну, то есть у Кошек, конечно, будет другой набор атрибутов (например, у них нет e-mail). Но зачем же при выводе на экран перечислять эти атрибуты в Шаблоне руками (как это сделано сейчас):

Автоматизация

Ради чего вообще всё это было затеяно (ООП + mvc)? Чтоб было удобнее: расширяемость, там, всякая, переносимость кода... Какая же тут может быть расширяемость, если для добавления нового свойства придётся перелопатить кучу кода в разных файлах (в Модели, Представлении, Контроллере...)? Новое свойство (например, Лохматость) должно добавляться в одно место – в Центральный Массив Модели, и всё. После этого весь остальной код должен работать как ни в чём не бывало.

Я клоню к тому, что никакие классы Друзей, Врагов или Задач не нужны; основных классов манипуляции с данными, по требованиям текущей структуры, должно быть два: Таблица и Пункт (отдельный Объект). Ясно, что это не классы Модели. Это классы, как уже было сказано, манипуляции Данными. Мы вводим новый структурный элемент – Data: Controller -> Model -> Data -> View.

А Модель... Кто сказал, что в Модели должны быть какие-то классы? Модель – это простая табличка, список. Точнее, две таблички: одну мы уже показали (список Сущностей), вторая – расширенное описание структуры Данных:

Это всё. После такого детального описания работать с Данными одно удовольствие. Контроллер вызывает манипулятор Данными Table и передаёт ему извлечённую из запроса пользователя сущность "friends" (или "cats"). Или наоборот, Контроллер вызывает сразу шаблон Table, а тот уже запрашивает Данные у класса Table:

Ну, а в центре функции get_table() находится цикл, извлекающий без упоминания конкретных имён все данные из нужной таблицы БД (точнее, все данные, которые предписывает извлекать Модель). На самом деле всё намного сложнее. Но оно всё равно объяснимо, логично, понятно. И позволяет добавлять новые сущности и атрибуты только в один массив Модели (ну, и, конечно, в БД, если эти данные могут меняться), не трогая Контроллеры и Шаблоны. Смотрите: http://ir2.ru/static/friends/www/...

D.M., admin

Комментарии

Дилетант 14.05.2013 02:36:59

Мишко, да ведь я и не претендую на истину в последней инстанции. Только надо писать "прЕвратно", а не "привратно".

Мишко 02.04.2013 04:37:54

Ничего более убогого не видел. Как инженер высоконагруженных систем сразу скажу, вы привратно поняли термин "модель". Так mysql_query – уже самодостаточная модель. Вам бы учиться а не статьи писать, которые вводят в заблуждение начинающих читателей.

лесник 29.10.2011 02:42:02

добавлено много новых версий :-) Последняя – http://ir2.ru/static/friends/www/repa/!mvc8.zip (сейчас на сайте). Там уже есть mysql (и инструмент установки БД). Вообще, так как версии часто меняются, лучше смотреть прямо папку: http://ir2.ru/static/friends/www/repa/

лесник 24.10.2011 19:33:30

Добавлена версия http://ir2.ru/static/friends/www/!mvc4.zip

План: 1) допилить шаблоны (сделать функции, упорядочить); 2) сделать инсталлятор для заполнения некоторых параметров (типа подключения к БД); 3) после установки – изменения настроек только по паролю (типа админки); 4) сделать систему config'ов для разных машин, добыть переменные окружения

лесник 24.10.2011 05:18:59

Да, абзацы тут отделяются друг от друга пустой строкой. HTML не работает (ну, автоматом генерятся ссылки).

#Были какие-то условные метки, не знаю, работают ли сейчас.

лесник 24.10.2011 05:12:42

Нет уж, "понравился" - это несерьёзно! :-) Где разгромная критика?

1. Комментарии на этом сайте устроены так сложно, что я сам всего не помню (там-то уж точно не было ООП). а) е-mail вообще никак сейчас не используется (в базу, правда, записывается); б) есть несколько хитрых ловушек: например, проверка на наличие русских букв (если нету, коммент не добавляется), есть какой-то мутный чёрный список "рекламных" слов...

2. Знаете анекдот, как мужик попал в ад и спрашивает: "А где же пиво и девочки?". А ему отвечают: "Ты что, мужик, это же была реклама". "Анти-мвц-ооп" - это была реклама. Для пущего флейма... На самом деле не совсем реклама. Примеры. а) Я не знаю, может, у вас чисто для скорости и упрощения было использовано global, но я принципиально "за", а старые крутые оопэшникик с г сжирают за такое (например, в phpclub). б) Я принципиально против постановки мвц во главу угла; повторяю, что для меня важнее автоматизация (в последовательном применении которой, например, нарисовалось mdvc - Model - Data - View - Controller), которая теоретически может заставить перемешать какие-то части паттерна.

3. jobs - это тоже (вы будете смеятьcя) типа реклама. Специально, чтоб спрашивали. Там в первом "модельном" массиве (сущностей, в index.php) намеренно допущена ошибка: у сущности jobs указано число 1 (count). Но в Данных строк для сущности jobs нет, поэтому логично вываливается 404. Или снимите крестик (уберите 'count' => 1 в index.php), или ... добавьте строку с данными в файл Data.php.

4. Если идея понравилась, давайте допиливать вместе.

dartanov 19.10.2011 17:22:31

Надо мыло тоже звездой засветить<br> <br> Теперь по статье:<br> Судя по названию ожидал что фреймворк будет без ООП. Но в статье и проекте сплошное ООП. Или я чего-то не понял? "без ООП" к чему относится?<br> И те-же мысли меня посетили на счёт анти-мвц.<br> Пример мне понравился!<br> Но только jobs почему-то 404, наверное я что-то не досмотрел.

dartanov 19.10.2011 17:15:10

Попробовал оставить комментарий, получил Нет текста комментария. (, ) хотя поле было не пустое (доп.усл. мыло не написал, только автор)