Конструктор 
Давайте на секунду отвлечемся от задачи управления базой данных и поговорим о том, как интересно устроены некоторые вещи.
Наверное, каждый человек хоть раз в жизни собирал конструктор наподобие Лего. Давным-давно по телеку видел программу про Диснейленд, где показывали огромные скульптуры сделанные целиком из Лего. Настолько меня поразило увиденное, что воспоминания об этом прочно засели в моей памяти.
Если вдуматься, что такое лего? Разноцветные кубики небольшого размера. По отдельности ничего особенного из себя не представляющих. Но в умелых руках эти незамысловатые кубики превращаются в нечто красивое и невероятно сложное.
Самое интересное, что такая возможность достигается наличием всего пары свойств:
1. Малый размер.
Несмотря на то, что кубики бывают разного размера, можно с уверенностью сказать, что если разложить один большой кубик на более мелкие составляющие, то в итоге окажется, что все составные элементы абсолютно идентичны;
2. Возможность объединиться с любым другим элементом.
Если бы кубики нельзя было присоединять один к другому, то и собрать из них ничего дельного было бы невозможно.
Кстати 2-ое свойства мне кажется наиболее важным, сложность появляется именно там где возникает возможность взаимодействия. Сочетая разные вещи, получаем разные свойства. Это ли не замечательно?
А вот теперь вернемся к нашей задаче. Я уже обмолвился, что в основу всего я хочу положить простую таблицу и обернуть ее небольшим классом управления (это чтобы скрыть от разработчика работу с СУБД). Кстати, подумав, я решил, что правильнее ее будет называть не простой, а элементарной таблицей. Кроме свойства простоты еще добавим свойство взаимодействия и получаем своеобразный лего-кубик.
Давайте рассмотрим это на примере. Представим, что нам нужно сделать модуль новостей для сайта. Новости будут содержать следующую информацию: Дату, Название, Текст. Разбивать эти данные на три таблицы смысла не имеет, проще положить все в одну и назвать ее “Новости”, кроме трех перечисленных полей добавим еще поле “ID” и получится у нас таблица из четырех столбцов. Это на уровне Базы данных.
А на уровне языка программирования создадим три простых класса: Дата, Название, Текст и четвертый элементарный класс Новости, который, благодаря своему свойству “взаимодействие”, впитает их в себя. На псевдо языке это будет выглядеть примерно так:
Создать Дату();
Создать Название();
Создать Текст();
Создать Новости();
Новости->включить( Дату );
Новости->включить( Название );
Новости->включить( Текст );
Новости->создать_структуру();
Вроде все понятно кроме последней строчки. Что за “создать_структуру”? Смысл в том, что когда мы включили в новости три простых класса мы определили конфигурацию новости, и теперь только новость знает какие поля она содержит, а следовательно именно она должна создавать структуру базы данных. На псевдо языке это может выглядеть примерно так:
создать_структуру():
Определить начало SQL запроса: SQL_запрос = ‘CREATE TABLE НОВОСТИ(’;
Для каждого поля из новостей:
SQL_запрос += поле->получить_SQL_ для_создания_таблицы
Определить концовку SQL запроса: SQL_запрос += ‘PRIMARY KEY (id));’
Выполнить SQL_запрос
конец метода;
Таким образом, каждый класс (Дата, Текст, Название) содержит информацию о своей части построения запроса “CREATE” и выдает ее в методе создать структуру. Если мы исключим какой-то из этих классов класса “новости”, то и в создание структуры он не попадет.
Аналогичным образом строится обращение к данным, если нам нужно получить все новости, то делаем просто:
Новости->получить()
Если нам нужно получить, новости за определенную дату, то делаем так:
Новости->Дата->ограничить( сегодняшняя дата );
Новости->получить()
Таким образом сами Новости, не знаю, что их будут чем-то ограничивать, но составной класс Дата вносит в получение новостей свою часть SQL запроса и тем самым влияет на результат.
Добавление новости может выглядеть примерно вот так:
Новости->Дата->установить( сегодняшняя дата )
Новости->Заголовок->установить( Заголовок новости )
Новости->Текст->установить( Текст новости )
Новости->сохранить()
Опять же, каждый составной элемент отвечает только за свой кусочек SQL, который в последующем передает основному классу для исполнения.
Пока не буду рассматривать случаи, кода нужно использовать более сложные запросы или случаи когда нужно работать с несколькими таблицами. Скажу лишь, что об этих случаях я уже думал и, как мне кажется, учел эти варианты.
Пока на этом все. Надеюсь, моя идея стала еще чуточку понятнее. А совсем понятной она станет, когда я выложу код.
подписаться на блог
Александр
Гость
Мне кажется, или автор изобрел ORM?:)
Evgeny Sergeev
Веб-разработчик, автор блога codeart.ru
Александр, тут мэпинга данных нет, только построение запросов. Хотя никто не отрицает, что описанное выше есть некоторое подобие ORM.
Так что твоя ирония мимо кассы как-то…
Александр
Гость
Ну почему же. Не подобие, а точная копия. Просто в посте не указана ни одна ссылка на ORM, а если бы она была, то и 95% поста было бы ни к чему. Насчет мапинга, там где описано про “добавление новости” разве это не он самый?:)
А вообще мне понравилось. И подняло настроение. Если уж изобретать опять велосипед, то с умным видом:)
Спасибо.
Владимир Лучанинов
Гость
@Александр: true
Не понимаю, чем это отличается от кейкового
$category = $this->News->Category->find(array(’name’=>’Категория 1′));
$this->News->save(array(
‘dat’=>date(’Y-m-d H:i:s’),
‘category_id’=>$category['Category']['id'],
‘title’=> ‘Заголовок новости’,
‘content’=>$content,
)) or die(’Упс!’);
Владимир Лучанинов
Гость
Создать структуру проще заранее в БД (если хочется комформа, то через какой-то DBDesigner).
А потом волшебное cake bake (удобная фича - консоль).
Долгая работа с Zend Framework и CodeIgniter заставляют иногда придумывать велосипеды.
Evgeny Sergeev
Веб-разработчик, автор блога codeart.ru
Владимир Лучанинов, я уже говорил, что не знаком с CakePHP, поэтому не исключаю, что вещь в итоге получится идентичная.
“Долгая работа с Zend Framework и CodeIgniter заставляют иногда придумывать велосипеды. ”
Вова, тут ты тоже скорее всего прав. То, что у меня получается нечто похожее на уже созданную хорошую схему (при том, что я об этой схеме ничего не знал) говорит о том, что я иду правильным путем.
Мир большой и придумать, что-то новое не возможно в принципе (ну или очень сложно как минимум). Поэтому не обижаюсь на высказывания в стиле “зачем придумывать велосипед”
“Создать структуру проще заранее в БД (если хочется комформа, то через какой-то DBDesigner).
А потом волшебное cake bake (удобная фича - консоль).”
Мне нужно чтобы можно было структуру генерировать на лету. Поэтому даный совет не подходит. Опять же я вообще не должен знать, где у меня хранятся данные в базе или в файлах на диске.
Саша, даная заметка является продолжением предыдущей, там ссылка на ORM есть.
“А вообще мне понравилось. И подняло настроение. Если уж изобретать опять велосипед, то с умным видом:)
Спасибо”
Ты ведешь себя так будто это ты создал доктрину.
Молодец конечно, но это отдает ребячеством. В любом случае, тебе тоже спасибо за комментарии.
Evgeny Sergeev
Веб-разработчик, автор блога codeart.ru
“Просто в посте не указана ни одна ссылка на ORM, а если бы она была, то и 95% поста было бы ни к чему.”
Саша, ты явно не понимаешь, что такое ORM, хотя и пытаешься показать, что разбираешься в вопросе. Зачем эта показуха?
Александр
Гость
В гадалку играешь?
Я тоже:
Вот ты про ORM явно только слышал, но не использовал. Поэтому этот пост у тебя и получился. Признал бы, а то глупо смотришься.
Evgeny Sergeev
Веб-разработчик, автор блога codeart.ru
Саша, давай ты просто приведешь ту ссылку которая заменит 95% процентов данного поста и будет описанием ORM?
А что касается использования ORM, то не понятно, что значит “использовать ORM” ты имеешь в виду какую то конкретную реализацию?
Если в общем, то, например, с паттерном ActiveRecord я довольно часто сталкиваюсь. В том же cakePHP, судя по всему, как раз он и реализован.
Владимир Лучанинов
Гость
@Евгений:
> Мне нужно чтобы можно было структуру генерировать на лету
Зачем? Объясни задачу, может комментаторы подскажут как решить.
> Опять же я вообще не должен знать, где у меня хранятся данные в базе или в файлах на диске.
В Cake Model всё равно откуда брать данные. Там есть по умолчанию поддержка известных баз данных, но при желании за час можно написать поддержку файлов в нужном формате вместо базы данных.
Evgeny Sergeev
Веб-разработчик, автор блога codeart.ru
Вова, насчет CakePHP ты меня уже заинтересовал. Я вчера немного посмотрел документацию, посмотрел твой блог. Пока к однозначному мнению не пришел. Например, я еще не разобрался как кейк работает со сложными структурами данных, где нужно в один запрос увязать несколько таблиц.
Насчет задачи генерации структуры на лету. Я размышлял таким образом, допустим у меня есть готовый модуль, возьмем для примера все те же новости. В определенный момент времени заказчик мне говорит, все хорошо, но мне нужно, чтобы новости бились по категориям (естественно все добавленные на данный момент новости, по умолчанию относятся к определенной категории). Такую задачу я решаю так:
В сам модуль новости я не лезу. Ибо это базовый модуль и трогать его нет смысла. Вместо этого в конструкторе контролера новостей пишу следующие три строчки:
$this->News->register( ‘Category’, ‘Category’, ‘Categories’);
$this->News->Category->inResult();
$this->News->update();
Первая строчка добавляет в структуру новостей категорию, вторая заставляет по умолчанию в результат запроса к БД вставить название категории. Третья строчка производит обновление структуры базы данных.
Теперь остается только указать в шаблоне новостей где выводить название категории. Форма по добавлению новости строится автоматически на основе структуры.
Что делать если вдруг заказчик захотел древовидную структуру категории? Нет проблем, пишем так:
$this->News->register( ‘Category’, ‘Category’, ‘Categories’);
$this->News->Category->register(’CategoryID’, ‘Parent’);
$this->News->Category->inResult();
$this->News->update();
Функция register принимает на вход имя элементарной таблиц в данном случае категория, второй параметр это псевдоним по которому будет идти обращение, третий это название таблицы в БД, если он не указан, то поле создается в той же таблице. В данном случае для “Parent” создается поле в той же таблице.
Плюсы такого подхода следующие:
1. Я не лезу в код модуля и саму БД
2. Работаю только с Контролером и шаблонами (для меня это удобно)
3. Могу оперативно реагировать на “хотелки” заказчика.
В этом и есть смысл генерации “на лету” изначально у меня не было модуля с категориями, но я его сгенерировал по ходу дела.
Надеюсь, что понятно объяснил.
Владимир Лучанинов
Гость
Расскажу как я решал бы эту же задачу. Получается тоже быстро.
1. Надо добавить категории.
Создаю таблицу categories, cake bake создаёт модель.
В таблице news добавляю поле category_id, добавляю в модель новостей
var $belongsTo = array(
‘Category’ => array(’className’ => ‘Category’, ‘foreignKey’ => ‘category_id’)
);
2. Нужна древовидная структура.
В таблице categories добавляю поле parent_id, в модели категорий
var $belongsTo = array(
‘Parent’ => array(’className’ => ‘Category’,'foreignKey’ => ‘parent_id’),
);
var $actsAs = array(’TreeOrdered’=>array()); // TreeOrdered - это мой модуль, но есть похожий встроенный
Работать только с контроллером и шаблонами считается в Cake - “Bad cake”. Там хорошей практикой считается: 50% - модели, 30% - виды (шаблоны), 20% - контроллеры.
А у тебя модели и контроллеры совмещены. Паттерн MVC объясняет, почему это не очень хорошо в долгосрочной перспективе.
Evgeny Sergeev
Веб-разработчик, автор блога codeart.ru
Вовы и опять ты прав
выносить в контроллер код, который явно предназначен для модуля - плохой стиль. В описанном мной случае логичнее было-бы использовать наследование, а не изврат с контроллером.
Хочу писать код, который сам создает структуру и сам знает как с ней работать, а я только говорю, что вот в этом модуле будет храниться информация об Авторе, Дате публикации и содержании новости, а в вот в этом модуле будет содержаться информация о пользователях.
Но при этом идея создавать структуру на лету мне всеравно импонирует. Не хочу я сначала создавать структуру базы, потом писать код для работы с ней.
В идеале у меня должен быть набор элементарных классов, каждый из которых представляет из себя некую сущность, допустим класс ФИО может в модуле выступать в роли Автора, Пользователя, Комментатора. Но независимо от смысла везде будет использоваться Фамилия Имя и Отчество. Или класс Дата может выступать в роли “даты создания”, “даты модификации”, “даты последнего посещения”, но опять же везде это будет дата и для нее будут справедливы действия с датами.
Зачем создавать в базе поле “Дата создания”, потом в модуле писать код для обработки этой даты, если на 90% этот код будет похож на код написанный пятью минутами ранее для “даты последней модификации”? Или все же в Кейке есть возможность один раз написать код для обработки дат, а потом подсовывать его в разном контексте?
Я так понял, что Cake предлагает для каждой таблицы в базе создать свою модель? Или это на откуп разработчика остается?
Отношение 50%-30%-20% - это, насколько я понял, при разработке нового модуля? Но очень часто сталкиваешься с необходимостью повторно использовать готовые модули, тогда модель почти не меняется, а вот контроллер может изменяться значительно. Что Cake говорит на эту тему?
Владимир Лучанинов
Гость
Да, для каждой таблицы создаётся отдельная модель. В этом больше преимуществ, чем недостатков.
Если несколько моделей (в одном или разных проектах) используют общую функциональность, то считается хорошим тоном выносить их в Behavior. Я описал это в php.southpark.com.ua/2007/10/20/kak-sozdat-behavior-dlya-cakephp/.
Класс “Дата” - это, по-моему, уже слишком абстрактно для большинства программ на PHP. В Java я бы это понял, там любят создавать класс Dollar (часто используется в примерах). Если пользоваться удобными инструментами (я люблю Navicat), то менять структуру базы данных напрямую намного проще.
Evgeny Sergeev
Веб-разработчик, автор блога codeart.ru
Владимир Лучанинов, спасибо за советы. Буду потихоньку разбираться с CakePHP, возможно потом и пересяду на него.
Leave a Reply