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

Это происходит потому, что админка заточена под сортировку только существующих в таблице столбцов. Если открыть actions.class.php, что автоматически создается админкой в кэше, то можно найти там интересную функцию, которая все объясняет:
Можно в нашем классе — декораторе action.class в модуле product_order переписать всю «неправильно» работающую логику, но тогда теряется смысл от использования родной админки, поэтому пойдем другим путем.
Для начала зададим свой table_method для вывода листа с заказами. Для этого добавим функцию retrieveOrdersList в Product_orderTable.class.
Потом изменим секцию config в generator.yml
Теперь возможность сортировки вернулась, но это еще не все. Если приглядеться, то можно увидеть, что сортировка проходит не верно. Почему же это происходит? Все просто. Если вы пытаетесь сортировать по имени клиента, doctrine честно добавляет в запрос, выдаваемый нашей функцией retrieveOrdersList, 'ORDER BY customer_id' и сортировка идет по id, а не по name.
Исправим это. Для этого еще раз подглянем в action.class в кэше. Там есть функция addSortQuery. Скопируем ее в наш action.class и немного изменим.
Теперь все работает правильно.

P.S. Удивительно, но для propel сортировка по «виртуальным» столбцам была реализована, однако для 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 в таблице выведены имена клиентов и наименования товаров, но сортировать по ним нельзя.

Это происходит потому, что админка заточена под сортировку только существующих в таблице столбцов. Если открыть 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]); }
Теперь все работает правильно.

P.S. Удивительно, но для propel сортировка по «виртуальным» столбцам была реализована, однако для doctrine этого почему-то делать не стали.