Практическое использование DBEntity.

Часть 1. Переопределение метода ::get()

Часто бывают ситуации, когда программисту требуется от базы данных нечто большее, чем заложено в функционале менеджеров. С такой же ситуацией столкнулся и я в процессе работы с датами через 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 как угодно и для этого НЕ НУЖНО писать ещё один дополнительный запрос).

Я использовал тривиальный пример, но, я думаю, найдётся уйма других вариантов, где такая методика будет полезна.

Discussion

serg@nixsolutions.com 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 smart, 2007/08/22 12:09:

Серж, это потому что пример был исправлен в соответствии со статьёй по построению DBEntity. Бывают случаи, когда разработчик не определяет имя таблицы и UID в качестве свойств наследника DBEntity, а производит init() напрямую, вписывая название таблицы и UID прямо в передаваемые параметры метода. Тогда получается, что название таблицы и UID сторятся только в DBEntityManager, а наследник к ним не имеет никакого отношения.


serg@nixsolutions.com 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 serg@nixsolutions.com, 2007/08/22 13:16:

кстати у тебя так и сделано :)

  ...
  function Example($params = null) 
  {
      $this->init($this->__table, $this->__uid, __CLASS__);
  ...

так нафига тогда лезть в менеджер за этими данными?


smart smart, 2007/08/22 18:26:

Разные редакции статьи ^_^ Ты смотрел уже после утренних правок.


smart smart, 2007/08/22 18:29:

В любом случае исправил пример в соответствии с твоими желаниями. ^_^


serg@nixsolutions.com serg@nixsolutions.com, 2007/08/22 19:07:

читер.


smart smart, 2007/08/24 22:31:

Угу, я знаю ^_^ В XDatabase есть такая классная фигня, как обновление записи по объекту. Дарк, ДМН, Вам привет!

Проипстался с ней гору времени V_v Зато теперь обновлю статью. Немного.


 
phpteam/xcore/rev1061/effective_dbentity.txt · Last modified: 2007/08/24 22:36 by smart