Работая с базой, постоянно приходится писать множество методов поиска. Вот типичный сценарий:
Предположим, что нам надо выводить список пользователей на сайте. Вначале это может быть так — $user_table->fetchAll(). А если нужно выводить только девушек? Напишем метод getFemaleUsers(). А только тех, кто не забанен и имеет аватарку? А вывод в админке только девушек, но без учета статуса пользователя?
В конце концов мы получим вагон методов, которые частично друг друга перекрывают или вообще делают одно и тоже, а различается только сортировка. А ведь их еще нужно тестировать…
В таком подходе нет ничего плохого, но это быстро утомляет. Если в проекте несколько разработчиков, то иногда могут появляться методы, которые делают одно и тоже, но называются по разному.
У этой задачи есть решение и называется оно — named scope. Реализовано это в rails и выглядит так:
Небольшое пояснение. Методы в ruby очень часто генерятся, а при их вызове скобки почти всегда опускают. Выше идет вызов методов, а не свойств.
Как тоже самое реализовать в Zend Framework? На помощь нам придет Zend_Db_Table_Select. К сожалению ZF не дает возможности штатными средствами подменить select для таблиц, поэтому будем действовать самостоятельно.
Разбираем на примере шлюза dbTable_User. Для начала нужно переопределить метод select, и инстанцировать внутри, например, dbTable_User_Select (наследник Zend_Db_Table_Select). Затем открываем этот класс и пишем нужные нам методы.
Все! Можно пользоваться.
Комбинируя методы можно делать практически любые выборки, а протестировать достаточно только эти микро методы.
В итоге мы получили объекты типа Select, и для того чтобы сделать запрос к базе их придется вставлять в fetchRow или fetchAll шлюза. Здесь мы еще раз схитрим и сделаем для всех наших dbTable_… _Select общего родителя в котором определим следующие методы:
По сути select начинает проксировать к объекту dbTable, который хранит внутри себя. Как бонус мы получаем ряд удобных методов типа count, sum и т.п. Теперь для того чтобы из получившегося селекта достать то что нужно можно делать так:
Предположим, что нам надо выводить список пользователей на сайте. Вначале это может быть так — $user_table->fetchAll(). А если нужно выводить только девушек? Напишем метод getFemaleUsers(). А только тех, кто не забанен и имеет аватарку? А вывод в админке только девушек, но без учета статуса пользователя?
В конце концов мы получим вагон методов, которые частично друг друга перекрывают или вообще делают одно и тоже, а различается только сортировка. А ведь их еще нужно тестировать…
В таком подходе нет ничего плохого, но это быстро утомляет. Если в проекте несколько разработчиков, то иногда могут появляться методы, которые делают одно и тоже, но называются по разному.
У этой задачи есть решение и называется оно — named scope. Реализовано это в rails и выглядит так:
class User < ActiveRecord::Base
named_scope :active, :conditions => {:active => true}
named_scope :inactive, :conditions => {:active => false}
named_scope :male, :conditions => {:sex => male}
end
User.active # Выберет всех активных пользователей User.inactive # Выберет всех неактивных пользователей User.active.male # Выберет всех активных мужчин
Небольшое пояснение. Методы в ruby очень часто генерятся, а при их вызове скобки почти всегда опускают. Выше идет вызов методов, а не свойств.
Как тоже самое реализовать в Zend Framework? На помощь нам придет Zend_Db_Table_Select. К сожалению ZF не дает возможности штатными средствами подменить select для таблиц, поэтому будем действовать самостоятельно.
Разбираем на примере шлюза dbTable_User. Для начала нужно переопределить метод select, и инстанцировать внутри, например, dbTable_User_Select (наследник Zend_Db_Table_Select). Затем открываем этот класс и пишем нужные нам методы.
class dbTable_User_Select extends Zend_Db_Table_Select
{
public function status($status)
{
return $this->where('status = ?', $status);
}
public function sex($sex)
{
return $this->where('sex = ?', $sex);
}
public function hasAvatar()
{
return $this->where('avatar is not null');
}
public function sortById()
{
return $this->order('id DESC);
}
}
Все! Можно пользоваться.
$user_table->select()->hasAvatar()->sex('male');
$user_table->select()->status('active')->sortById();
Комбинируя методы можно делать практически любые выборки, а протестировать достаточно только эти микро методы.
В итоге мы получили объекты типа Select, и для того чтобы сделать запрос к базе их придется вставлять в fetchRow или fetchAll шлюза. Здесь мы еще раз схитрим и сделаем для всех наших dbTable_… _Select общего родителя в котором определим следующие методы:
class Ext_Db_Table_Select extends Zend_Db_Table_Select
{
public function fetchRow()
{
return $this->getTable()->fetchRow($this);
}
public function fetchRowIfExists($message = 'Данной страницы нет на сайте')
{
return $this->getTable()->fetchRowIfExists($this, $message);
}
public function fetchAll($limit = null, $offset = null) // Не самое удачное название метода)
{
if ($limit) {
$this->limit($limit, $offset);
}
return $this->getTable()->fetchAll($this);
}
public function getPaginator($page = 1, $limit = 10, $pageRange = 7)
{
$adaptee = new Ext_Paginator_AdapterAggregate($this);
$paginator = Zend_Paginator::factory($adaptee);
$paginator->setItemCountPerPage($limit);
$paginator->setCurrentPageNumber($page);
$paginator->setPageRange($pageRange);
return $paginator;
}
// Эти полезные методы тоже можно реализовать.
public function count()
public function max($field){};
public function min($field){};
public function sum($field){};
public function exists(){};
public function random($limit = 5) // Псевдо рандом
{
$count = $this->count();
$offset = ($count > $limit) ? $count - $limit : 0;
$this->limit($limit, mt_rand(0, $offset));
return $this->fetchAll();
}
}
По сути select начинает проксировать к объекту dbTable, который хранит внутри себя. Как бонус мы получаем ряд удобных методов типа count, sum и т.п. Теперь для того чтобы из получившегося селекта достать то что нужно можно делать так:
$select->fetchRow(); // Взять строку $select->getPaginator($page); // Пейджинг $select->fetchAll(5); // взять 5 строк // Часто нужен там, где при пустом результате нужно выбрасывать 404 $select->fetchRowIfExists();