Pull to refresh

Named scope для Zend Framework

Reading time 3 min
Views 1.4K
Работая с базой, постоянно приходится писать множество методов поиска. Вот типичный сценарий:

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

Articles