Pull to refresh

Zend_Db_Table_Select Dynamic Finder

Reading time4 min
Views1.3K
Привет, Хабр! Dynamic Finder

Написал класс, использующий Zend_Db_Table_Select и позволяющий использовать Dynamic Finder в моделях в проектах на Zend Framework. Статья о том, что этот класс умеет, а также ссылка на исходный код предлагаются вашему вниманию.

Что это, зачем?



Dynamic Finder – способ, позволяющий получать данные из таблицы БД, записывая названия искомых полей в виде названия метода класса, а значения этих полей — в качестве аргумента метода. Например, может использоваться в экземпляре класса модели, связанной с какой-либо таблицей БД.

Dynamic Finder позволяет избежать написания ряда методов вида getById(…), getByLoginAndPassword(…), getAllByCountry(…) внутри модели в виде построения полноценных SQL-запросов и выборок. Вместо этого, в данной реализации достаточно подключить Dynamic Finder к модели должным образом, и, далее, программист может использовать эти методы модели непосредственно в контроллере или представлении. При этом реально эти методы в модели вообще не существуют.

Таким образом, экономится время работы программиста.

Dynamic Finder уже был реализован в том или ином виде в различных библиотеках и фреймворках, в частности, в Ruby on Rails.

В данной реализации Dynamic Finder является надстройкой, использующей Zend_Db_Select / Zend_Db_Table_Select, и предназначен для выборок из только одной таблицы.


Как этим пользоваться?



Используется несколько видов синтаксиса. Простейший:
$modelObj->getByTag($tag);


getBy… идентичен getAllBy… – т.е. подразумевается запрос с множеством строк. Для получения только одной строки можно использовать getOneBy…

Синтаксис имен запрашиваемых полей, используемый в названии метода, заключается в записывании названий этих полей в CamelCase – режиме. При этом поля с именем вида user_name должны быть записаны в названии метода как UserName (каждое слово, начинающееся с символа подчеркивания, заменяется на слово с большой буквы). Разные поля в имени «виртуального метода» разделяются логическими операторами And или Or.

Например,
$user->getOneByLoginAndPassword($login, $password);


Синтаксис аргумента «виртуального метода» поддерживает две формы: краткую (в качестве аргументов через запятую записывают значения полей, по которым строится запрос, см. примеры выше) и полную. Полная форма требует в виде аргумента метода ассоциативный массив и выглядит в самом общем виде так:
 
$data = $files->getAllByPaperId(
    array('values'=>array($paperId, ...),
    'options'=>array(
         'order'=>array('orig_filename ASC'),
         'offset' => $offset,
         'limit' => 10
    ) ) );
 


Порядок записи значений полей (массив values, либо простейший случай) должен соответствовать порядку названий полей в названии «виртуального метода». Массив options и содержащие его пары ключ-значение являются необязательными, однако для них нет и каких-либо значений по умолчанию. В качестве аргументов можно использовать и экземпляры класса Zend_Db_Expr:
$data = $tagObj->getAllByTag(new Zend_Db_Expr(sprintf(" LIKE '%s%%'" , $beginStr)));


Подключение к модели.



На мой взгляд, проще всего сам объект класса Dymanic Finder заводить в модели как private – свойство:
/**
 * Dynamic Finder object
 * @var DynamicFinder
 */

private $_dynFinder;
 
public function  __construct() {        
    if (!class_exists('DynamicFinder',false)) {
        require_once  'path_to'. '/DynamicFinder.php';
    }
 
    $this->_dynFinder = new DynamicFinder();
}
 


Первичная обработка «виртуального метода»; получение данных из БД после генерации желаемого Zend_Db_Table_Select:
public function __call($name, $arguments)
{
    $this->_dynFinder->select = $this->getTable()->select();
    $this->_dynFinder->allowedFields = $this->getTable()->info(Zend_Db_Table_Abstract::COLS);
 
    $select =  call_user_func_array(array($this->_dynFinder, $name), $arguments);
    if (strpos($name,'getOneBy')===0){
        return $this->getTable()->fetchRow($select);
    } else {
        return $this->getTable()->fetchAll($select);
    }
}
 


$this->getTable() должна возвращать ассоциированный с моделью объект Zend_Db_Table.

В принципе, всё может быть совсем по-другому. Например, вы можете скармливать DymanicFinder в качестве select каким-то образом предварительно подготовленный экземпляр Zend_Db_Table_Select (Zend_Db_Select), отвечающий именно вашей конкретной задаче. Так же, другим образом можно передать и массив-список доступных в таблице полей. Аналогично, по-другому могут быть и обработаны результаты преобразования объекта Zend_Db_Select() (вместо условий по проверке getOneBy). Именно ради возможных, нужных конечному программисту доработок, Dynamic Finder в данном случае и сделан в виде такого «полуфабриката». Ради всевозможной кастомизации.

Также есть вариант внешнего разбора имени метода (вместо внутреннего _camelCase2underscore() ). Т.е. ваши правила именования колонок в таблицах и в именах метода для файндера будут отличаться. В таком случае где-то до вызова метода __call надо определить свойство $this->columnParseCallback в виде callback (как для call_user_func()), в котором вы и зададите внешнюю функцию-парсер вашего имени метода.

Реализация.



А внутри у нас php-код, который не станет работать, если ему не передать ни наследника Zend_Db_Table_Select (достаточно и Zend_Db_Select при условии работы, например, с одной таблицей), ни список допустимых полей. А то мало ли что впоследствии программист при написании виртуального метода учудит? ;)

Исходный код класса доступен по ссылке.

UPD: Баг со строкой 337 исправлен, также чуть доправлено форматирование. Спасибо пользователю dmitry_dvp
Tags:
Hubs:
Total votes 38: ↑29 and ↓9+20
Comments47

Articles