- Создание бэкэнд-приложения
- Модули бэкэнд-приложения
- Проба бэкэнд-приложения
- Кэш Symfony
- Конфигурация бэкэнд-приложения
- Настройка заголовков
- Настройка полей
- Настройка представления
list
- Настройка вида форм
- Конфигурация фильтров
- Настройка контроллеров (actions)
- Настройка шаблонов
- Окончательный вид конфигурации
- Увидимся завтра
С дополнением, которое мы сделали в Jobeet вчера наше фронтэнд-приложение полностью готово к использованию как теми, кто ищет работу, так и теми, кто размещает объявления. Настало время немного поговорить о бэкэнд-приложении.
Сегодня благодаря возможностям Symfony по генерации интерфейса администратора мы разработаем полный интерфейс бэкэнда для Jobeet буквально за час.
Создание бэкэнд-приложения
Самый первый шаг - это создание приложения backend. Если у Вас хорошая память,
то Вы должны помнить, как это сделать при помощи задачи generate:app
:
$ php symfony generate:app backend
Бэкэнд-приложение теперь доступно по адресу
http://www.jobeet.com.localhost/backend.php/
для пользовательской среды, и
http://www.jobeet.com.localhost/backend_dev.php/
для среды разработки.
note
Когда Вы создавали фронтэнд-приложение пользовательский фронт-контроллер назывался
index.php
. Поскольку может быть только один файл index.php
в каталоге приложения,
Symfony создает index.php
только для самого первого пользовательского фронт-контроллера
и именует остальные контроллеры, начиная с имени приложения.
Если Вы теперь попробуете перезагрузить начальные данные при помощи задачи
doctrine:data-load
,
Вы увидите, что это больше не работает. Это происходит потому, что метод JobeetJob::save()
требует доступа к конфигурационному файлу app.yml
из frontend
-приложения.
Поскольку мы теперь имеем два приложения, Symfony использует первое, которое может найти,
а теперь это приложение backend
.
Но как мы видели в течение дня 8 конфигурирование может осуществляться на разных уровнях.
Путем переноса содержимого файла apps/frontend/config/app.yml
в config/app.yml
,
мы делаем конфигурацию доступной всем приложениям и проблема решена.
Сделайте эти изменения сейчас, поскольку мы собираемся использовать классы модели
достаточно широко при генерации администраторской части приложения и в связи с этим
переменные, определенные в app.yml
, нам будут нужны и в бэкэнд-приложении.
tip
Задача doctrine:data-load
также имеет --application
опцию. Так, если
Вы используете различные настройки в разных приложениях, то ее можно
использовать следующим образом:
$ php symfony doctrine:data-load --application=frontend
Модули бэкэнд-приложения
Для фронтэнд-приложения задача doctrine:generate-module
была использована
для создания базового CRUD модуля, построенного по классу модели. Для бэкэнда
задача
doctrine:generate-admin
будет использована для генерации полнофункционального
бэкэнд-интерфейса для класса модели:
$ php symfony doctrine:generate-admin backend JobeetJob --module=job $ php symfony doctrine:generate-admin backend JobeetCategory --module=category
Эти две команды создают job
и category
модули для JobeetJob
и JobeetCategory
классов модели соответственно.
If you have a look at our two generated modules, you will notice there is no
activated webdesign whereas the symfony built-in admin generator feature has a
basic graphic interface by default. For now, assets from the sfDoctrinePlugin
are not located under the web/
folder. We need to publish them under the
web/
folder thanks to the plugin:publish-assets
task:
$ php symfony plugin:publish-assets
Необязательная опция --module
позволяет перекрыть имя, создаваемое командой для модуля
по умолчанию (иначе имя было бы jobeet_job
для класса JobeetJob
).
За сценой задача generate-admin
также создала настройки маршрутизации для каждого модуля:
# apps/backend/config/routing.yml jobeet_job: class: sfDoctrineRouteCollection options: model: JobeetJob module: job prefix_path: job column: id with_wildcard_routes: true
То, что классом маршрута, используемым генератором админки, является
sfDoctrineRouteCollection
не должно стать для Вас сюрпризом, поскольку основная цель интерфейса администратора - это
управление жизненным циклом объектов модели.
Описание маршрута также определяет несколько атрибутов, которые нам ранее не встречались:
prefix_path
: Определяет префикс пути для маршрута (например, страница редактирования будет иметь путь вроде этого/job/1/edit
).column
: Определяет столбец таблицы в базе, который используется для ссылки на объект.with_wildcard_routes
: Поскольку интерфейс администратора будет содержать нечто большее, чем классические CRUD операции, этот атрибут позволяет определить больше операций над объектами и коллекциями без редактирования маршрута.
tip
Как всегда, чтение документации перед использованием новой задачи Doctrine - это хорошая идея.
$ php symfony help doctrine:generate-admin
Это даст Вам обзор всех аргументов и опций задачи а также некоторые классические примеры использования.
Проба бэкэнд-приложения
Безо всякой дополнительной работы мы уже можем использовать сгенеренные модули:
http://www.jobeet.com.localhost/backend_dev.php/job http://www.jobeet.com.localhost/backend_dev.php/category
Модули админки имеют много дополнительных функций по сравнению с простыми модулями, которые мы генерировали в предыдущие дни. Без единой новой строки кода на PHP, каждый модуль предоставляет следующие прекрасные возможности:
- Список объектов использует постраничную навигацию
- Список является сортируемым
- Список может быть отфильтрован
- Объекты могут быть добавлены, отредактированы и удалены
- Выделенные объекты могут быть удалены группой
- Используется валидация форм
- Мгновенные (flash) сообщения демонстрируют немедленный отклик на действия пользователя
- ... и многое-многое другое
Генератор админки позволяет получить все возможности, которые требуются для создания бэкэнд-интерфейса, просто сконфигурировав модуль.
Чтобы сделать работу пользователя немного удобнее, нам нужно настроить интерфейс, созданный генератором. Мы так же добавим простое меню для упрощения навигации между разными модулями.
Заменим содержимое сгенеренного layout.php
файла следующим кодом:
// apps/backend/templates/layout.php <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>Jobeet Admin Interface</title> <link rel="shortcut icon" href="/favicon.ico" /> <?php use_stylesheet('admin.css') ?> <?php include_javascripts() ?> <?php include_stylesheets() ?> </head> <body> <div id="container"> <div id="header"> <h1> <a href="<?php echo url_for('homepage') ?>"> <img src="/legacy/images/logo.jpg" alt="Jobeet Job Board" /> </a> </h1> </div> <div id="menu"> <ul> <li> <?php echo link_to('Jobs', 'jobeet_job') ?> </li> <li> <?php echo link_to('Categories', 'jobeet_category') ?> </li> </ul> </div> <div id="content"> <?php echo $sf_content ?> </div> <div id="footer"> <img src="/legacy/images/jobeet-mini.png" /> powered by <a href="/"> <img src="/legacy/images/symfony.gif" alt="symfony framework" /></a> </div> </div> </body> </html>
Этот layout использует файл admin.css
. Этот файл должен уже находиться в каталоге
web/css/
поскольку он был добавлен вместе с остальными файлами стилей в дне 4.
Наконец, изменим домашнюю страницу по умолчанию homepage
в routing.yml
:
# apps/backend/config/routing.yml homepage: url: / param: { module: job, action: index }
Кэш Symfony
Если Вы достаточно любопытны, возможно Вы уже открывали файлы, сгенерированные
задачей
doctrine:generate-admin
в каталоге apps/backend/modules/
. Если нет,
пожалуйста, откройте их сейчас. Сюрприз! Каталоги templates
пусты, и файлы
actions.class.php
также пусты:
// apps/backend/modules/job/actions/actions.class.php require_once dirname(__FILE__).'/../lib/jobGeneratorConfiguration.class.php'; require_once dirname(__FILE__).'/../lib/jobGeneratorHelper.class.php'; class jobActions extends autoJobActions { }
Как это может работать? Если Вы присмотритесь внимательнее, Вы заметите, что
класс jobActions
наследует autoJobActions
. Класс autoJobActions
автоматически
создается Symfony, если он не существует. Его можно найти в каталоге
cache/backend/dev/modules/autoJob/
, который содержит "настоящий" модуль:
// cache/backend/dev/modules/autoJob/actions/actions.class.php class autoJobActions extends sfActions { public function preExecute() { $this->configuration = new jobGeneratorConfiguration(); if (!$this->getUser()->hasCredential( $this->configuration->getCredentials($this->getActionName()) )) { // ...
Способ работы генератора админки должен напомнить Вам уже известное поведение.
Фактически, это очень похоже на то, что мы уж изучали на примере классов модели и форм.
Основываясь на схеме модели, Symfony создает классы модели и форм. В случае с генератором админки,
создаваемый модуль может быть сконфигурирован при помощи файла config/generator.yml
, находящегося
в каталоге модуля:
# apps/backend/modules/job/config/generator.yml generator: class: sfDoctrineGenerator param: model_class: JobeetJob theme: admin non_verbose_templates: true with_show: false singular: ~ plural: ~ route_prefix: jobeet_job with_doctrine_route: true config: actions: ~ fields: ~ list: ~ filter: ~ form: ~ edit: ~ new: ~
Каждый раз, когда Вы обновляете файл generator.yml
, Symfony обновляет кэш.
Как мы увидим сегодня, настройка создаваемых модулей админки делается просто, быстро
и красиво.
note
Автоматическое обновление файлов кэша происходит только в среде
разработки. В рабочей среде, Вам нужно чистить кэш самостоятельно при помощи
задачи cache:clear
.
Конфигурация бэкэнд-приложения
Модуль администратора может быть настроен путем редактирования значения ключа config
в файле generator.yml
. Конфигурация организована в виде семи секций:
actions
: Конфигурация контроллеров и экшенов для страниц списков и формfields
: Конфигурация полейlist
: Конфигурация списковfilter
: Конфигурация фильтровform
: Конфигурация для new/edit формedit
: Специфические настройки страницы редактирования (edit)new
: Специфические настройки страницы создания (new)
Итак, начнем настраивать.
Настройка заголовков
В секциях list
, edit
и new
заголовки для модуля category
могут быть определены
путем задания опции title
:
# apps/backend/modules/category/config/generator.yml config: actions: ~ fields: ~ list: title: Category Management filter: ~ form: ~ edit: title: Editing Category "%%name%%" new: title: New Category
Заголовок для секции edit
содержит динамические данные: все строки, заключенные
между %%
заменяются значениями полей соответствующего объекта.
Конфигурация модуля job
аналогична:
# apps/backend/modules/job/config/generator.yml config: actions: ~ fields: ~ list: title: Job Management filter: ~ form: ~ edit: title: Editing Job "%%company%% is looking for a %%position%%" new: title: Job Creation
Настройка полей
Различные виды (views) (list
, new
и edit
) составлены из полей (fields).
Поле может быть как столбцом класса модели, так и виртуальным столбцом, как мы увидим позже.
Настройка полей по умолчанию может быть осуществлена в секции fields
:
# apps/backend/modules/job/config/generator.yml config: fields: is_activated: { label: Activated?, help: Whether the user has activated the job, or not } is_public: { label: Public?, help: Whether the job can also be published on affiliate websites, or not }
Секция fields
перекрывает конфигурацию полей для всех представлений, что
означает, что метка (label) для поля is_activated
будет изменена для
представлений list
, edit
и new
.
Конфигурация генератора админки основана на каскадном принципе конфигурирования.
Например, если Вы хотите изменить метки только для представления list
,
определите опцию fields
только для секции list
:
# apps/backend/modules/job/config/generator.yml config: list: fields: is_public: { label: "Public? (label for the list)" }
Любые настройки, присутствующие в основной секции fields
могут быть перекрыты
специфическими настройками конкретного представления. Правило переопределения настроек
таково:
new
иedit
наследуют отform
, которое в свою очередь наследует отfields
list
наследует отfields
filter
наследует отfields
note
Для секций, относящихся к формам (form
, edit
и new
), опции label
и help
переопределяют соответствующие значения, определенные в классе формы.
Настройка представления list
display
По умолчанию, все столбцы спискового (list) представления являются соответствующими столбцами модели,
следующими в том же порядке, как в файле схемы. Опция display
переопределяет настройки по умолчанию
при помощи задания упорядоченного списка столбцов для отображения:
# apps/backend/modules/category/config/generator.yml config: list: title: Category Management display: [=name, slug]
Знак =
перед именем столбца name
- это соглашение, по которому строка преобразуется
в ссылку (link).
Сделаем то же самое для модуля job
, чтобы сделать его более читабельным:
# apps/backend/modules/job/config/generator.yml config: list: title: Job Management display: [company, position, location, url, is_activated, email]
layout
Список может быть отображен с использованием различных шаблонов (layouts). Шаблоном по умолчанию
является табличный (tabular
), что означает, что каждое значение столбца располагается в своем
собственном столбце таблицы. Но для модуля job
было бы лучше использовать текстовый (stacked) шаблон,
который так же является одним из встроенных шаблонов:
# apps/backend/modules/job/config/generator.yml config: list: title: Job Management layout: stacked display: [company, position, location, url, is_activated, email] params: | %%is_activated%% <small>%%category_id%%</small> - %%company%% (<em>%%email%%</em>) is looking for a %%=position%% (%%location%%)
В текстовом шаблоне, каждый объект представляется одной строкой, как определено
опцией params
.
note
Опция display
по-прежнему нужна, поскольку она определяет столбцы, которые
могут быть отсортированы пользователем.
"Виртуальные" столбцы
В этой конфигурации сегмент %%category_id%%
будет заменен значением первичного ключа
категории. Но было бы гораздо лучше, если бы отобразилось наименование категории.
Всякий раз, когда Вы используете %%
, переменная не обязана соответствовать
реальному столбцу в базе данных. Генератор админки нуждается только в наличии
соответствующего геттера в классе модели.
Чтобы отобразить имя категории, мы должны были бы определить метод getCategoryName()
в классе JobeetJob
и заменить %%category_id%%
на %%category_name%%
.
Но класс JobeetJob
уже содержит метод getJobeetCategory()
, который возвращает
соответствующий объект категории. И если Вы используете %%jobeet_category%%
,
это работает, поскольку класс JobeetCategory
содержит волшебный метод __toString()
,
который преобразует объект в строковое представление.
# apps/backend/modules/job/config/generator.yml %%is_activated%% <small>%%jobeet_category%%</small> - %%company%% (<em>%%email%%</em>) is looking for a %%=position%% (%%location%%)
sort
Как администратор, Вы возможно будете более заинтересованы в том, чтобы видеть предложения
работы, размещенные последними. Вы можете настроить сортировку столбцов, используя опцию sort
:
# apps/backend/modules/job/config/generator.yml config: list: sort: [expires_at, desc]
max_per_page
По умолчанию список использует постраничную навигацию, и каждая страница содержит 20 строк. Это значение
может быть изменено при помощи опции max_per_page
:
# apps/backend/modules/job/config/generator.yml config: list: max_per_page: 10
batch_actions
На странице списка операция может быть применена к нескольким объектам сразу (batch action). Это поведение
нежелательно для модуля category
, поэтому давайте уберем эту возможность:
# apps/backend/modules/category/config/generator.yml config: list: batch_actions: {}
Опция batch_actions
определяет список операций, которые могут быть массовыми. Пустой массив
позволяет избавиться от этого.
По умолчанию каждый модуль содержит в качестве массовой операцию delete
,
но давайте представим, что для модуля job
нам нужен способ продлить актуальность
нескольких выбранных вакансий еще на 30 дней:
# apps/backend/modules/job/config/generator.yml config: list: batch_actions: _delete: ~ extend: ~
Все действия,начинающиеся с _
, являются встроенными и обеспечены самим фреймворком.
Если Вы обновите страницу в браузере и выберете массовую операцию продления (extend),
symfony сгенерирует исключение, сообщающее Вам, что необходимо определить метод executeBatchExtend()
:
// apps/backend/modules/job/actions/actions.class.php class jobActions extends autoJobActions { public function executeBatchExtend(sfWebRequest $request) { $ids = $request->getParameter('ids'); $q = Doctrine_Query::create() ->from('JobeetJob j') ->whereIn('j.id', $ids); foreach ($q->execute() as $job) { $job->extend(true); } $this->getUser()->setFlash('notice', 'The selected jobs have been extended successfully.'); $this->redirect('jobeet_job'); } }
Выбранные первичные ключи (primary keys) находятся в параметре ids
запроса.
Для каждой выбранной вакансии вызывается метод JobeetJob::extend()
с дополнительным аргументом, позволяющим пропустить проверку активности.
Обновите метод extend()
, добавив в него этот новый аргумент:
// lib/model/doctrine/JobeetJob.class.php class JobeetJob extends BaseJobeetJob { public function extend($force = false) { if (!$force && !$this->expiresSoon()) { return false; } $this->setExpiresAt(date('Y-m-d', time() + 86400 * sfConfig::get('app_active_days'))); $this->save(); return true; } // ... }
После того, как все вакансии будут продлены, пользователь будет перенаправлен на домашнюю страницу
модуля job
.
object_actions
В списке есть дополнительный столбец для действий, которые Вы можете выполнить для
одного объекта. Для модуля category
давайте удалим их, поскольку мы имеем ссылку
на имени категории для редактирования и мы не нуждаемся в возможности удалить категорию
прямо из списка:
# apps/backend/modules/category/config/generator.yml config: list: object_actions: {}
Для модуля job
давайте сохраним существующие действия и добавим новое - extend
,
аналогично тому, как мы только что добавили его в список массовых операций:
# apps/backend/modules/job/config/generator.yml config: list: object_actions: extend: ~ _edit: ~ _delete: ~
Так же, как и в случае массовых операций, действия _delete
и _edit
являются предопределенными
самим фреймворком. Нам нужно определить действие (action) listExtend
, чтобы ссылка
extend
заработала:
// apps/backend/modules/job/actions/actions.class.php class jobActions extends autoJobActions { public function executeListExtend(sfWebRequest $request) { $job = $this->getRoute()->getObject(); $job->extend(true); $this->getUser()->setFlash('notice', 'The selected jobs have been extended successfully.'); $this->redirect('jobeet_job'); } // ... }
actions
Мы уже видели, как выполнить действие (action) для списка объектов или для одного объекта.
Опция actions
определяет действия, которые не требуют наличия объекта, например
создание нового объекта. Давайте удалим действие new
, созданное по умолчанию,
и добавим новое действие, которое удаляет все вакансии, которые не были активированы
более, чем 60 дней:
# apps/backend/modules/job/config/generator.yml config: list: actions: deleteNeverActivated: { label: Delete never activated jobs }
До сих пор все действия, которые мы определяли, имели ~
вместо параметров, что означает,
что Symfony настраивает действие автоматически. Каждое действие может быть настроено путем определения
массива параметров. Опция label
переопределяет метку (label), созданную Symfony.
По умолчанию действие, запускаемое, когда вы кликаете на ссылке с именем действия,
состоит из имени действия, предваренного словом list
.
Создадим действие listDeleteNeverActivated
в модуле job
:
// apps/backend/modules/job/actions/actions.class.php class jobActions extends autoJobActions { public function executeListDeleteNeverActivated(sfWebRequest $request) { $nb = Doctrine::getTable('JobeetJob')->cleanup(60); if ($nb) { $this->getUser()->setFlash('notice', sprintf('%d never activated jobs have been deleted successfully.', $nb)); } else { $this->getUser()->setFlash('notice', 'No job to delete.'); } $this->redirect('jobeet_job'); } // ... }
Мы повторно использовали метод JobeetJobTable::cleanup()
, определенный вчера. Это
еще один хороший пример пользы паттерна MVC.
note
Также Вы можете изменить имя вызываемого действия, добавив параметр action
:
deleteNeverActivated: { label: Delete never activated jobs, action: foo }
table_method
Количество запросов к базе данных, необходимое для отображения страницы со списком вакансий 14, как показано на панели web debug.
Если Вы кликнете по этому числу, Вы увидите, что большинство запросов извлекают категорию для каждой вакансии:
Чтобы снизить число запросов, Мы можем изменить метод, используемый по умолчанию для извлечения
вакансий, используя опцию table_method
:
# apps/backend/modules/job/config/generator.yml config: list: table_method: retrieveBackendJobList
Теперь Вы должны создать метод retrieveBackendJobList
в классе JobeetJobTable
из файла lib/model/doctrine/JobeetJobTable.class.php
.
// lib/model/doctrine/JobeetJobTable.class.php class JobeetJobTable extends Doctrine_Table { public function retrieveBackendJobList(Doctrine_Query $q) { $rootAlias = $q->getRootAlias(); $q->leftJoin($rootAlias . '.JobeetCategory c'); return $q; } // ...
Метод retrieveBackendJobList()
добавляет объединение таблиц job
и
category
и автоматически создает соответствующий объект категории для каждой вакансии.
Число запросов теперь снизилось до четырех:
Настройка вида форм
Настройка вида форм делается в трех секциях: form
, edit
и
new
. Они все имеют одинаковые возможности для настройки, и секция form
существует только для того, чтобы использоваться вместо секций edit
и new
,
если он не заполнены.
display
Так же, как и для списка, Вы можете изменить порядок отображения полей при
помощи опции display
. Но поскольку отображаемая форма определяется классом,
не пытайтесь удалить поле, поскольку это может привести к неожиданным ошибкам валидации.
Опция display
для отображения форм также может быть использована для разбиения
полей на группы:
# apps/backend/modules/job/config/generator.yml config: form: display: Content: [category_id, type, company, logo, url, position, ? location, description, how_to_apply, is_public, email] Admin: [_generated_token, is_activated, expires_at]
Приведенная конфигурация определяет две группы (Content
и Admin
), каждая из которых
содержит подмножество полей формы.
note
Столбцы, относящиеся к группе Admin
, еще не показаны в браузере, потому что они
не были определены в настройке отображения формы. Они появятся в нескольких секциях,
когда мы определим собственный класс формы вакансии для приложения администратора.
Генератор админки имеет встроенную поддержку отношения многие ко многим. На форме категории
есть поле ввода для имени, одно для преобразованного имени (slug
), и выпадающий
список для ассоциированных партнеров. Поскольку редактировать это отношение на
данной странице не имеет смысла, давайте удалим его:
// lib/form/doctrine/JobeetCategoryForm.class.php class JobeetCategoryForm extends BaseJobeetCategoryForm { public function configure() { unset($this['created_at'], $this['updated_at'], $this['jobeet_affiliates_list']); } }
"Виртуальные" столбцы
В опции display
для формы вакансии, поле _generated_token
начинается
со знака подчеркивания (_
). Это означает, что обработкой этого поля будет
заниматься собственный партиал (partial
), под названием _generated_token.php
Создадим его со следующим содержимым:
// apps/backend/modules/job/templates/_generated_token.php <div class="sf_admin_form_row"> <label>Token</label> <?php echo $form->getObject()->getToken() ?> </div>
В этом партиале Вы имеете доступ к текущей форме ($form
) и ассоциированному с ней объекту
посредством метода getObject()
.
note
Вы также можете делегировать разбор поля компоненту путем добавления символа тильды
(~
) перед именем поля.
class
Поскольку форма будет использоваться администраторами, мы отобразили больше информации,
чем в пользовательской форме. Однако сейчас некоторые из полей не присутствуют на форме,
т.к. они были удалены из класса JobeetJobForm
.
Чтобы иметь две разных формы для фронтэнда и бэкэнда, нам надо создать два класса формы.
Давайте создадим класс BackendJobeetJobForm
, расширяющий класс JobeetJobForm
.
Т.к. мы не хотим иметь одинаковые скрытые поля, нам также надо немного отрефакторить
класс JobeetJobForm
, чтобы перенести выражение unset()
в метод, который будет
переопределен в BackendJobeetJobForm
:
// lib/form/doctrine/JobeetJobForm.class.php class JobeetJobForm extends BaseJobeetJobForm { public function configure() { $this->removeFields(); $this->validatorSchema['email'] = new sfValidatorAnd(array( $this->validatorSchema['email'], new sfValidatorEmail(), )); // ... } protected function removeFields() { unset( $this['created_at'], $this['updated_at'], $this['expires_at'], $this['is_activated'], $this['token'] ); } } // lib/form/doctrine/BackendJobeetJobForm.class.php class BackendJobeetJobForm extends JobeetJobForm { public function configure() { parent::configure(); } protected function removeFields() { unset( $this['created_at'], $this['updated_at'], $this['token'] ); } }
Созданный по умолчанию класс формы, используемый генератором админки, может быть перекрыт
при помощи указания опции class
:
# apps/backend/modules/job/config/generator.yml config: form: class: BackendJobeetJobForm
note
Поскольку мы добавили новый класс, не забудьте почистить кэш.
Форма редактирования по-прежнему имеет небольшое несоответствие. Текущий загруженный
логотип не показан нигде и Вы не можете его удалить. Виджет sfWidgetFormInputFileEditable
добавляет возможности редактирования к простому виджету для загрузки файла:
// lib/form/doctrine/BackendJobeetJobForm.class.php class BackendJobeetJobForm extends JobeetJobForm { public function configure() { parent::configure(); $this->widgetSchema['logo'] = new sfWidgetFormInputFileEditable(array( 'label' => 'Company logo', 'file_src' => '/uploads/jobs/'.$this->getObject()->getLogo(), 'is_image' => true, 'edit_mode' => !$this->isNew(), 'template' => '<div>%file%<br />%input%<br />%delete% %delete_label%</div>', )); $this->validatorSchema['logo_delete'] = new sfValidatorPass(); } // ... }
Виджет sfWidgetFormInputFileEditable
имеет несколько опций для настройки его функций
и отображения:
file_src
: web-путь к файлу, содержащему текущий загруженный логотипis_image
: Еслиtrue
, то содежимое файла может быть показано как изображениеedit_mode
: Находится ли форма в состоянии редактирования или созданияwith_delete
: Надо ли отображать чекбокс для удаленияtemplate
: Шаблон для отображения виджета
tip
Вид генератора админки может быть очень легко настроен, поскольку генерируемые шаблоны
содержат много html-атрибутов class
и id
. Например, поле логотипа настраивается
использованием класса sf_admin_form_field_logo
. Все остальные поля также имеют
класс, зависящий от типа поля как sf_admin_text
или sf_admin_boolean
.
Опция edit_mode
использует метод sfDoctrineRecord::isNew()
.
Он возвращает true
, если объект модели для формы создается и false
в противном случае. Это на самом деле здорово, что Вы можете иметь разные виджеты или валидаторы
в зависимости от статуса объекта, привязанного к форме.
Конфигурация фильтров
Настройка фильтров очень похожа на настройку отображения формы. Фактически
фильтры - это те же формы. И - так же, как и для форм, - классы фильтров генерируются
задачей
doctrine:build --all
. Также Вы можете перегенерировать их при помощи задачи
doctrine:build --filters
.
Классы форм фильтров находятся в каталоге lib/filter/
, и каждый класс модели имеет
ассоциированный класс формы для фильтрации списков (JobeetJobFormFilter
для JobeetJobForm
).
Давайте полностью удалим фильтры для модуля category
:
# apps/backend/modules/category/config/generator.yml config: filter: class: false
Для модуля job
давайте удалим только некоторые из них:
# apps/backend/modules/job/config/generator.yml filter: display: [category_id, company, position, description, is_activated, ? is_public, email, expires_at]
Поскольку использование фильтров всегдя является необязательным, нет никакой необходимости переопределять класс формы фильтра для настройки полей, которые будут отображены.
Настройка контроллеров (actions)
Если конфигурация недостаточна, Вы можете добавить новые методы в класс контроллера
так, как мы это делали с дополнительным действием extend
, но Вы также можете переопределить
и сгенерированные методы контроллера:
Метод | Описание |
---|---|
executeIndex() |
Списковое представление |
executeFilter() |
Применение фильтра |
executeNew() |
Отображение формы для нового объекта |
executeCreate() |
Создание нового объекта |
executeEdit() |
Отображение формы для редактируемого объекта |
executeUpdate() |
Обновление объекта |
executeDelete() |
Удаление объекта |
executeBatch() |
Запуск массовой операции |
executeBatchDelete() |
Запуск массовой операции _delete |
processForm() |
Обработка формы объекта |
getFilters() |
Возвращает текущие значения фильтров |
setFilters() |
Устанавливает значения фильтров |
getPager() |
Возвращает объект, осуществляющий постраничную навигацию |
getPage() |
Возвращает текущую страницу |
setPage() |
Устанавливает текущую страницу |
buildCriteria() |
Строит критерий выборки для списка |
addSortCriteria() |
Добавляет критерий сортировки Criteria для списка |
getSort() |
Возвращает текущий отсортированный столбец |
setSort() |
Устанавливает текущий сортируемый столбец |
Поскольку каждый сгенерированный метод выполняет только одно действие, очень легко поменять поведение контроллера без копирования большого количества кода.
Настройка шаблонов
Мы уже видели, как настраивать сгенерированные шаблоны (templates
) благодаря атрибутам class
и id
, добавленным генератором админки в HTML код.
Так же как и для классов, Вы можете переопределить сами созданные шаблоны. Поскольку
они являются простыми PHP-файлами и не являются PHP-классами, шаблоны могут быть перекрыты
при помощи создани шаблона с таким же именем в модуле (например, в каталоге
apps/backend/modules/job/templates/
для бэкэнд-модуля job
):
Шаблон | Описание |
---|---|
_assets.php |
Подгружает CSS и JS для использования в остальных шаблонах |
_filters.php |
Подготавливает область фильтров для отображения |
_filters_field.php |
Подготавливает один фильтр для отображения |
_flashes.php |
Подготавливает flash-сообщения для отображения |
_form.php |
Отображает форму |
_form_actions.php |
Отображает действия для формы |
_form_field.php |
Отображает одно поле формы |
_form_fieldset.php |
Отображает группу полей формы |
_form_footer.php |
Отображает "нижний колонтитул" (footer ) формы |
_form_header.php |
Отображает заголовок формы |
_list.php |
Отображает список объектов |
_list_actions.php |
Отображает действия для списка |
_list_batch_actions.php |
Отображает массовые действия для списка |
_list_field_boolean.php |
Отображает одно булево поле в списке |
_list_footer.php |
Отображает "нижний колонтитул" (footer ) списка |
_list_header.php |
Отображает заголовок списка |
_list_td_actions.php |
Отображает действие для одного объекта в строке |
_list_td_batch_actions.php |
Отображает чекбокс для объекта |
_list_td_stacked.php |
Отображает объект с использованием строкового шаблона |
_list_td_tabular.php |
Отображает поле объекта с использованием табличного шаблона |
_list_th_stacked.php |
Отображает заголовок столбца объектов с использованием строкового шаблона |
_list_th_tabular.php |
Отображает заголовок столбца с использованием табличного шаблона |
_pagination.php |
Отображает панель постраничной навигации |
editSuccess.php |
Отображает страницу редактирования объекта |
indexSuccess.php |
Отображает страницу списка |
newSuccess.php |
Отображает страницу создания нового объекта |
Окончательный вид конфигурации
Окончательная конфигурация для модулей администратора Jobeet:
# apps/backend/modules/job/config/generator.yml generator: class: sfDoctrineGenerator param: model_class: JobeetJob theme: admin non_verbose_templates: true with_show: false singular: ~ plural: ~ route_prefix: jobeet_job with_doctrine_route: true config: actions: ~ fields: is_activated: { label: Activated?, help: Whether the user has activated the job, or not } is_public: { label: Public? } list: title: Job Management layout: stacked display: [company, position, location, url, is_activated, email] params: | %%is_activated%% <small>%%JobeetCategory%%</small> - %%company%% (<em>%%email%%</em>) is looking for a %%=position%% (%%location%%) max_per_page: 10 sort: [expires_at, desc] batch_actions: _delete: ~ extend: ~ object_actions: extend: ~ _edit: ~ _delete: ~ actions: deleteNeverActivated: { label: Delete never activated jobs } table_method: retrieveBackendJobList filter: display: [category_id, company, position, description, is_activated, is_public, email, expires_at] form: class: BackendJobeetJobForm display: Content: [category_id, type, company, logo, url, position, location, description, how_to_apply, is_public, email] Admin: [_generated_token, is_activated, expires_at] edit: title: Editing Job "%%company%% is looking for a %%position%%" new: title: Job Creation # apps/backend/modules/category/config/generator.yml generator: class: sfDoctrineGenerator param: model_class: JobeetCategory theme: admin non_verbose_templates: true with_show: false singular: ~ plural: ~ route_prefix: jobeet_category with_doctrine_route: true config: actions: ~ fields: ~ list: title: Category Management display: [=name, slug] batch_actions: {} object_actions: {} filter: class: false form: actions: _delete: ~ _list: ~ _save: ~ edit: title: Editing Category "%%name%%" new: title: New Category
Всего лишь используя эти два конфигурационных файла (и пару-тройку обновлений в классах - Прим. перев.), мы разработали великолепный интерфейс администратора для Jobeet буквально за минуты.
tip
Вы уже знаете, что когда что-то конфигрируется в YAML файле, также есть
возможность использовать PHP код. Для генератора админки Вы можете
редактировать файл apps/backend/modules/job/lib/jobGeneratorConfiguration.class.php
.
Это даст возможность использовать те же опции, что и YAML файл, но на PHP.
Чтобы узнать имена методов, посмотрите на сгенерированный базовый класс в файле
cache/backend/dev/modules/autoJob/lib/BaseJobGeneratorConfiguration.class.php
.
Увидимся завтра
Всего за час мы создали полнофункциональный бэкэнд-интерфейс для Jobeet. И в общей сложности мы написали меньше 50 строк PHP кода. Не так плохо для такого количества возможностей!
Завтра мы увидим как защитить бэкэнд-приложение при помощи логина и пароля. Это также будет поводом поговорить о классе пользователя Symfony.
This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.