Привет, Хабр!
Написал класс, использующий 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, и предназначен для выборок из только одной таблицы.
Используется несколько видов синтаксиса. Простейший:
getBy… идентичен getAllBy… – т.е. подразумевается запрос с множеством строк. Для получения только одной строки можно использовать getOneBy…
Синтаксис имен запрашиваемых полей, используемый в названии метода, заключается в записывании названий этих полей в CamelCase – режиме. При этом поля с именем вида user_name должны быть записаны в названии метода как UserName (каждое слово, начинающееся с символа подчеркивания, заменяется на слово с большой буквы). Разные поля в имени «виртуального метода» разделяются логическими операторами And или Or.
Например,
Синтаксис аргумента «виртуального метода» поддерживает две формы: краткую (в качестве аргументов через запятую записывают значения полей, по которым строится запрос, см. примеры выше) и полную. Полная форма требует в виде аргумента метода ассоциативный массив и выглядит в самом общем виде так:
Порядок записи значений полей (массив values, либо простейший случай) должен соответствовать порядку названий полей в названии «виртуального метода». Массив options и содержащие его пары ключ-значение являются необязательными, однако для них нет и каких-либо значений по умолчанию. В качестве аргументов можно использовать и экземпляры класса Zend_Db_Expr:
На мой взгляд, проще всего сам объект класса Dymanic Finder заводить в модели как private – свойство:
Первичная обработка «виртуального метода»; получение данных из БД после генерации желаемого Zend_Db_Table_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
Написал класс, использующий 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