Pull to refresh

Symfony: Сортировка по «виртуальному» столбцу в админке

В замечательном фрэймворке Symphony есть не менее замечательный генератор админок, позволяющий существенно облегчить нам жизнь. Однако не все так безоблачно и у админок, сгенерированных им, есть ряд недостатков. Сегодня мы затронем вопрос сортировки по «виртуальному» столбцу. Проблема в том, что admin generator в smfony 1.4, при использовании doctrine, делать этого «из коробки» не умеет.

Исходные данные


Рассмотрим простейший пример с таблицами клиентов и товаров, связанных с таблицей заказов отношением один ко многим.

Схема данных

Customer:
  columns:
    id:   { type: integer, primary: true, autoincrement:true }
    name: { type: string(255), notnull: true}
  options:
    collate: utf8_unicode_ci
    charset: utf8

Product:
  columns:
    id:     { type: integer, primary: true, autoincrement:true }
    name:   { type: string(255), notnull: true}
    price:  { type: float, notnull: true }
  options:
    collate: utf8_unicode_ci
    charset: utf8

Product_order:
  columns:
    id:                   { type: integer, primary: true, autoincrement:true }
    customer_id:          { type: integer, notnull: true }
    product_id:           { type: integer, notnull: true }
    quantity:             { type: integer, notnull: true }
  options:
    collate: utf8_unicode_ci
    charset: utf8
  relations:
    Customer: {local: customer_id, foreign: id}
    Product:  {local: product_id, foreign: id}


Fixtures

Клиенты

Customer:
  customer_1:
    name: Петр Петров
  customer_2:
    name: Вася Пупкин
  customer_3:
    name: Иван Иванов


Товары

Product:
  product_1:
    name: Nokia E66
    price: 12300
  product_2:
    name: HTC T8585 HD2
    price: 23500
  product_3:
    name: Samsung GT-S8500 Wave
    price: 18800

Заказы

Product_order:
  product_order_1:
    Customer: [customer_1]
    Product:  [product_1]
    quantity: 1
  product_order_2:
    Customer: [customer_2]
    Product:  [product_2]
    quantity: 1
  product_order_3:
    Customer: [customer_3]
    Product:  [product_3]
    quantity: 2


Конфигурация

generator.yml для сгенерированной админки выглядит следующим образом (module name такое же, как и model name в данном случае):

generator:
  class: sfDoctrineGenerator
  param:
    model_class:           product_order
    theme:                 admin
    non_verbose_templates: true
    with_show:             false
    singular:              ~
    plural:                ~
    route_prefix:          product_order
    with_doctrine_route:   true
    actions_base_class:    sfActions

    config:
      actions: ~
      fields:  ~
      list:
        display: [customer, product, quantity]
      filter:  ~
      form:    ~
      edit:    ~
      new:     ~


Проблема


Все вроде бы замечательно. Вместо id в таблице выведены имена клиентов и наименования товаров, но сортировать по ним нельзя.

No sort

Это происходит потому, что админка заточена под сортировку только существующих в таблице столбцов. Если открыть actions.class.php, что автоматически создается админкой в кэше, то можно найти там интересную функцию, которая все объясняет:

protected function isValidSortColumn($column)
{
  return Doctrine::getTable('product_order')->hasColumn($column);
}


Решение


Можно в нашем классе — декораторе action.class в модуле product_order переписать всю «неправильно» работающую логику, но тогда теряется смысл от использования родной админки, поэтому пойдем другим путем.

Для начала зададим свой table_method для вывода листа с заказами. Для этого добавим функцию retrieveOrdersList в Product_orderTable.class.

public function retrieveOrdersList(Doctrine_Query $q)
{
    $rootAlias = $q->getRootAlias();

    $q->select('c.name as customer_id, p.name as product_id, quantity')
      ->leftJoin($rootAlias . '.Customer c')
      ->leftJoin($rootAlias . '.Product p');

    return $q;
}


Потом изменим секцию config в generator.yml

    config:
      actions: ~
      fields:  ~
      list:
        display: [customer_id, product_id, quantity]
        table_method: retrieveOrdersList
      filter:  ~
      form:    ~
      edit:    ~
      new:     ~


Теперь возможность сортировки вернулась, но это еще не все. Если приглядеться, то можно увидеть, что сортировка проходит не верно. Почему же это происходит? Все просто. Если вы пытаетесь сортировать по имени клиента, doctrine честно добавляет в запрос, выдаваемый нашей функцией retrieveOrdersList, 'ORDER BY customer_id' и сортировка идет по id, а не по name.

Исправим это. Для этого еще раз подглянем в action.class в кэше. Там есть функция addSortQuery. Скопируем ее в наш action.class и немного изменим.

  protected function addSortQuery($query)
  {
    if (array(null, null) == ($sort = $this->getSort()))
    {
      return;
    }

    if (!in_array(strtolower($sort[1]), array('asc', 'desc')))
    {
      $sort[1] = 'asc';
    }

    switch ($sort[0])
    {
      case 'customer_id':
        $sort[0] = 'c.name';
        break;

      case 'product_id':
        $sort[0] = 'p.name';
        break;

      default:
        break;
    }

    $query->addOrderBy($sort[0] . ' ' . $sort[1]);
  }


Теперь все работает правильно.

image

P.S. Удивительно, но для propel сортировка по «виртуальным» столбцам была реализована, однако для doctrine этого почему-то делать не стали.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.