Работая с базой, постоянно приходится писать множество методов поиска. Вот типичный сценарий:
Предположим, что нам надо выводить список пользователей на сайте. Вначале это может быть так — $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();