Абстракция БД
Когда я только начинал писать на PHP, ООП я владел, пожалуй, на уровне синтаксиса, не более. В то время мне вполне хватало функций mysql_*. Со временем, опыта становилось больше, я начинал задумываться об оптимизации. Этот топик описывает эволюцию моего кода.
Первым шагом стало написание простенького класса, который облегчал работу с БД. Кода стало не на много меньше, в основном упрощались типовые запросы. Также, автоматически экранировались входные параметры. Бонусом, появилась возможность использовать код с БД, отличных от MySQL (написав класс с таким же интерфейсом). По крайней мере, большинство запросов переписывать не пришлось бы. Код выглядел примерно так:
$db = new MysqlDb($server, $user, $pass, $db, $prefix);
$db->select($key, $value); // Возможно было много вариантов запроса, используя различное количество параметров.
while ($db->fetch()) {
$row = $db->getRow();
…
}
Либо $rows = $db->getResult();
* This source code was highlighted with Source Code Highlighter.
Чтобы еще упростить работу с БД, я стал использовать Active Record. Мое решение во многом проигрывало сторонним, но мне тогда этого хватало. В то время появился Zend Framework, поэтому некоторые идеи взяты с него. Сами классы работали через PDO (хотя, библиотека для работы через php_mysql тоже имелась).
Пример кода:
$db = Cms_Db::factory('MySQL');
$db->connect($server, $user, $pass, $db, $prefix);
$element = $db->select('id', $id);
$element->name = 'New name';
$element->update();* This source code was highlighted with Source Code Highlighter.
Этот вариант прожил довольно долго. Но со временем и его стало не хватать. Класс развивался экстенсивно, добавлялись новые методы для обработки типовых запросов. К тому времени, вышел стабильный релиз Zend Framework. Стало надоедать постоянно добавлять новые типы запросов.
Со временем я начал писать модели под основные объекты сайта. Внутри все еще работал мой старый класс, но снаружи все операции выглядели довольно прозрачно. Такой подход мне понравился, и чтобы упростить реализацию, я решил использовать ORM. Вначале, присматривался к Doctrine и Propel, но в конце решил написать свою реализацию.
Работа с БД осуществляется через Zend_Db, для гибкости также применяется Zend_Db_Select. Описание модели во многом похоже на Doctrine. Но у меня модель содержит также данные, предназначенные для описания представления некоторых данных. Приведу пример:
class Model_News extends Cms_Essence_Model
{
public function setDefinition()
{
$this->addField('name', 'Заголовок')
->addField('link', 'Адрес', 'link')
->addField('date', 'Дата публикации', 'date')
->addField('description', 'Краткое описание', 'text,255', array(
'validate' => 'isVoid',
'detail' => true
))
->addField('content', 'Содержание', 'editor', array(
'detail' => true,
'minLength' => 40,
'validate' => array('isVoid', 'minLength'))
))
->setOption('editorTableClass', 'newsTable');
}
}
$element = Cms_Essence::get('Model_News')->getById(2);* This source code was highlighted with Source Code Highlighter.
Как и в полноценных ORM, также можно описать связи таблиц и использовать код вроде:
$user = Cms_Essence::get('Model_User')->getJoined('Model_Group', 'name', 'megahertz');
$groupName = $user->group->name;* This source code was highlighted with Source Code Highlighter.
Конечно, по удобству таких запросов мне еще далеко до полноценных ORM, но пока меня такой вариант устраивает.
Такой подход позволяет писать минимум кода. Имея описанную модель, я могу использовать ее в админке, при этом не написав ни строчки кода. Причем админка может быть любого тип, классическая, ajax редактор на сайте, через RPC на другом сайте и т.п. Также можно использовать компоненты в шаблонах, чтоб не писать код. Например, на Smarty компонент будет выглядеть так:
{state model=Model_News name=news limit=3}
<ul>
{list from=$news}
<p>
<b>{$newsList->name} {$newsList->date}</b><br />
{$newsList->description}<br />
<a href="{$newsList->getUrl()}">подробнее</a>
</p>
{/list}
* This source code was highlighted with Source Code Highlighter.
Еще один существенный плюс такой модели – появляется возможность автоматически кэшировать результат выборок. Любая выборка кэшируется, а при операциях изменения данных модели автоматически чистится кэш.
В дальнейшем я не исключаю возможности сохранения такого подхода, но на базе Doctrine. Но пока такой необходимости нет. Думаю, это далеко не последний вариант абстракции.