Часто бывают ситуации, когда программисту требуется от базы данных нечто большее, чем заложено в функционале менеджеров. С такой же ситуацией столкнулся и я в процессе работы с датами через XCore::DBEntity.
Итак, приступим. В нашем распоряжении есть некая таблица с датами в формате DATETIME. Примем следующую эмпирическую структуру:
table example {
example_id : int 11;
example_content : text;
example_date_create : datetime;
example_date_update : datetime;
}
Довольно-таки знакомая структура. Ну что-же, теперь нужно придумать задачу, которую мы будем решать переопределением методов. Пусть это будет желание заказчика: «Необходимо рассчитать время в секундах между созданием Example и его обновлением и показать его»
В данном случае разработчик (в том числе и я раньше) сделает следующее (по вариантам) :
1) Получит напрямую запись из БД с помощью XDatabase::getRow или ::getAll (за что в нашем случае отбиваются руки и слетают зарубки)
2) Получит запись через DBEntity, возьмёт из неё значения example_date_create, example_date_update, преобразует из в unix_timestamp с помощью хитроумных парсов используя функционал PHP (функции mktime, двойной explode или любой свой вариант), сделает необходимые вычисления и возможно определит новое свойство объекта Example::DBEntity.
Как видно, первый способ отпадает сразу из вариантов, второй же малоэффективен и слишком трудозатратен. Что же предлагает под собой переобпределение Метода DBEntity::get() ? То, что для преобразования дат можно будет воспользоваться встроенным функционалом MySQL для работы с датами. Ну что же, начну описывать всё на примерах:
В Нашем случае изменения касаются только нашего наследника DBEntity, т.е. классе Example::DBEntity
class Example extends DBEntity { var $example_id; var $example_content; var $example_date_create; var $example_date_update; var $__table = "xc_example"; var $__uid = "example_id"; function Example($params = null) { $this->init($this->__table, $this->__uid, __CLASS__); if ($params){ $this->set($params); } } }
Стоит ли говорить, что это стандартное DBEntity? Функционал PHP вполне позволяет переопределить метод наследника так, чтобы он отличался от метода родителя. Чем мы и воспользуемся для того, чтобы при инициализации добавить поле с разницей во времени прямо в получаемый результат.
Как видно, DBEntity является наследником Entity и при вызове метода init() происходит инициализация класса DBEntityManager, через который и происходит вся работа с базой данных. Т.е. по факту при вызове DBEntity::get() происходит неявный вызов DBEntityManager::get(). Вы можете заметить это, если посмотрите в исходник DBEntity и DBEntityManager. Там же в DBEntityManager мы добираемся до нашей сути – SQL-запроса, который возвращает нам строку с необходимой записью.
Для того, чтобы наш наследник Example чувствовал себя вполне адекватно и переопределённый метод ::get() ничего нам не испортил – перенесём часть функционала с SQL-запросом из предков в наследника:
function get($aID) { if (!$aID) { $res = null; } else { $sSQL = "SELECT * FROM ".$this->__table." WHERE ".$this->__uid." = ? LIMIT 1"; $res = XDatabase::getRow($sSQL, $aID, array("integer")); if (PEAR::isError($res)) { $this->__DBEntityManager->_error = $res; $res = null; } } if ($res) { foreach ($res as $name => $value) { $this->{$name} = $value; } return true; } else { return false; } }
Поскольку при вызове DBEntity::init() Ключевые поля, переданные этому методу сразу же отправляются в DBEntityManager и нигде по пути не задерживаются – мы тоже будем вызывать всё через свойства DBEntityManager. Этот пример по функциональности абсолютно аналогичен тому, что сделает DBEntity, не переопределяй мы ничего вообще. Но в таком случае зачем мы это делаем?
Давайте составим запрос, который бы получал то, что нам нужно в дополнительное поле example_date_diff:
SELECT *, UNIX_TIMESTAMP(example_date_create)-UNIX_TIMESTAMP(example_date_update) AS example_date_diff FROM xc_example WHERE example_id = ?
А теперь вставим этот запрос в наш переопределённый метод:
$sSQL = "SELECT *, UNIX_TIMESTAMP(example_date_create)-UNIX_TIMESTAMP(example_date_update) AS example_date_diff FROM ".$this->__table." WHERE ".$this->__uid." = ? LIMIT 1"; $res = XDatabase::getRow($sSQL, $aID, array("integer"));
Так же необходимо внести example_date_diff в список параметров нашего класса-наследника. Конечно многие скажут, что плохо вставлять прямые параметры прямо в запрос, но поскольку мы переопределяем метод для потомкА, а не для родителя - ничего критичного я в этом не вижу. В Итоге получаем следующий класс:
class Example extends DBEntity { var $example_id; var $example_content; var $example_date_create; var $example_date_update; var $__table = "xc_example"; var $__uid = "example_id"; function Example($params = null) { $this->init($this->__table, $this->__uid, __CLASS__); if ($params){ $this->set($params); } } function get($aID) { if (!$aID) { $res = null; } else { $sSQL = " SELECT *, UNIX_TIMESTAMP(example_date_create)-UNIX_TIMESTAMP(example_date_update) AS example_date_diff FROM ".$this->__table." WHERE ".$this->__uid." = ? LIMIT 1"; $res = XDatabase::getRow($sSQL, $aID, array("integer")); if (PEAR::isError($res)) { $this->__DBEntityManager->_error = $res; $res = null; } } if ($res) { foreach ($res as $name => $value) { $this->{$name} = $value; } return true; } else { return false; } } }
Теперь при нормальном получении записи из БД используя DBEntity в классе Example в свойстве example_date_diff будет постоянно отображаться разница между датами.
Если у вас старый релиз врапера XDatabase - обязательно проверьте строку 674 (может быть другая) файла XDatabase.php. Она должна содержать примерно следующее:
if (is_array($aField) or is_object($aField) or $aKey[0] == '_' or (is_object($aFieldsValues) && !property_exists(get_class($aFieldsValues), $aKey))) {
Обязательно проверьте наличие всех условий, иначе при добавлении динамических свойств DBEntity перестанет обновляться.
Минус этого метода в том, что при переопределении Мы отходим от общей структуры и при возникновении ошибок может быть затруднён их поиск, а так же нарушается структура наследования объектов. Так же Минус в том, что наш усложнённый запрос с вычислениями будет выполняться каждый раз при получении записи.
Но в Этом же и плюс - мы не будем городить тонну PHP-кода или составлять ещё один SQL-запрос, чтобы добиться результата. Ещё один Плюс - в том, что мы можем определять новые свойства, используя всё те же старые DBEntity, используя в полную силу как их преимущества, так и преимущества работы с базой данных напрямую (т.е. можно использовать SQL как угодно и для этого НЕ НУЖНО писать ещё один дополнительный запрос).
Я использовал тривиальный пример, но, я думаю, найдётся уйма других вариантов, где такая методика будет полезна.
serg@nixsolutions.com, 2007/08/22 11:42:
всё классно тока не понятно, почему:
$sSQL = " SELECT *, UNIX_TIMESTAMP(example_date_create)-UNIX_TIMESTAMP(example_date_update) AS example_date_diff
FROM ".$this->__DBEntityManager->_table_name."
WHERE ".$this->__DBEntityManager->_UID." = ? LIMIT 1";
а не
$sSQL = " SELECT *, UNIX_TIMESTAMP(example_date_create)-UNIX_TIMESTAMP(example_date_update) AS example_date_diff
FROM ".$this->__table."
WHERE ".$this->__uid." = ? LIMIT 1";
вообще када в классе есть цепочки типа $this→XXX→YYY(→ZZZ и т.д.), это есть плохо.
а если помечтать, то надо бы иметь мапинг entity_field ⇒ table_field (в нашем случае example_date_diff ⇒ “UNIX_TIMESTAMP(example_date_create)-UNIX_TIMESTAMP(example_date_update)”, тогда ::get() бы сам всё мог сделать. а если entity_field реализовать как проперти, а не свойство, то можно и гетер классынй придумать, который бы по факту модифаил example_date_update. но пока что это помечтать
smart, 2007/08/22 12:09:
Серж, это потому что пример был исправлен в соответствии со статьёй по построению DBEntity. Бывают случаи, когда разработчик не определяет имя таблицы и UID в качестве свойств наследника DBEntity, а производит init() напрямую, вписывая название таблицы и UID прямо в передаваемые параметры метода. Тогда получается, что название таблицы и UID сторятся только в DBEntityManager, а наследник к ним не имеет никакого отношения.
serg@nixsolutions.com, 2007/08/22 13:11:
фигня. даже используя конструктор ты должен делать
var $__table;
var $__uid;
/**
* Constructor
*
* @access public
*/
function Discussion($aID = null)
{
// альтернативный способ инициализации
$this->__table = "message";
$this->__uid = "id";
$this->init($this->__table, $this->__uid);
$this->set($aID);
}
и никак иначе. нефиг что-то куда-то прятать.
serg@nixsolutions.com, 2007/08/22 13:16:
кстати у тебя так и сделано :)
...
function Example($params = null)
{
$this->init($this->__table, $this->__uid, __CLASS__);
...
так нафига тогда лезть в менеджер за этими данными?
smart, 2007/08/22 18:26:
Разные редакции статьи
Ты смотрел уже после утренних правок.
smart, 2007/08/22 18:29:
В любом случае исправил пример в соответствии с твоими желаниями.
serg@nixsolutions.com, 2007/08/22 19:07:
читер.
smart, 2007/08/24 22:31:
Угу, я знаю
В XDatabase есть такая классная фигня, как обновление записи по объекту. Дарк, ДМН, Вам привет!
Проипстался с ней гору времени V_v Зато теперь обновлю статью. Немного.