Разрабатывая проекты на базе нового, но уже ставшего очень популярным фреймворка Symfony2 невольно сталкиваешься с кусками кода, которые с минимальными изменениями, а то и вовсе без них кочуют из одного проекта в другой. Собрав несколько таких «кусков» воедино я создал ShtumiUsefulBundle, об использовании которого хочу рассказать.
Найти сам бандл вы можете на GitHub: ShtumiUsefulBundle.
Установка и настройка бандла тривиальны и описаны в Reedme. Останавливаться на этом вопросе я не буду.
Поля формы с авто-подстановкой
В Symfony2 есть замечательный тип полей entity — поле, содержащее выпадающий список Select, заполненный записями определенной таблицы. При чем после отправки формы вы получаете не id выбранной записи, а сам объект. Очень удобно! Удобно, пока количество данных в таблице не превысило разумных пределов. Но когда у вас, к примеру, таблица пользователей содержит десятки тысяч записей и нужно сделать в форме выбор пользователя, данный подход становится неприменимым. Разумным решением в данном случае будет использование текстового поля с авто-подстановкой. ShtumiUsefulBundle позволяет создавать и гибко настраивать такие поля.
Для начала необходимо определить в настройках каждое поле с автоподстановкой (в примере их два).
//app/config/config.yml
shtumi_useful:
autocomplete_entities:
users:
class: AcmeDemoBundle:User
role: ROLE_ADMIN
property: email
products:
class: AcmeDemoBundle:Product
role: ROLE_ADMIN
search: contains
- class — Модель Doctrine.
- role — Роль пользователя, которая необходима для использования поля. По уполчанию: IS_AUTHENTICATED_ANONYMOUSLY (доступно всем)
- property — Свойство модели, которое будет использоваться для автоподстановки. По умочанию: title.
- search — Тип поиска для автоподстановки:
— begins_with — начинается с введенных символов (по умолчанию)
— ends_with — заканчивается введенными символами
— contains — содержит введенные символы внутри
Определение поля в настройках нужно для того, чтобы хакер не подставил в запрос к контроллеру автоподсказку, скажем, номеров кредитных карт. Контроллер подсказывает только значения тех полей, которые определены в настройках. И только тем пользователям, роли которых соответствуют указанным в настройках.
Использование поля очень просто:
$formBuilder
->add('product', 'shtumi_ajax_autocomplete', array('entity_alias'=>'products'));
Данное поле можно использовать и как фильтр в SonataAdminBundle (для тех, кто не знает — это генератор админок в Symfony2 — честно говоря, не представляю, как жил бы без этого бандла...). Для этого модель Sale должна содержать свойство user со связью ManyToOne к модели User.
//src/Acme/DemoBundle/Admin/SaleAdmin.php
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('id')
->add('user', 'shtumi_ajax_autocomplete', array('entity_alias'=>'users'))
;
}
В случае, если в модели, для которой добавляются фильтры нет фильтруемого поля со связкой ManyToOne, необходимо использовать Callback. Типично таких ситуаций бывает две. Во-первых у вас может быть структура моделей Hotel->Campaign->Offer. При этом, естественно, хранить отель в предложении смысла нет, т.к. это дублирование информации. Но вот фильтровать список предложений по кампании может быть необходимо. Во-вторых вы можете захотеть фильтровать таблицу пользователей по e-mail с использованием автоподстановки. Но проблема в том, что поле e-mail само находится в модели User, и ни о какой связи ManyToOne тут не может быт и речи.
Использование callback в поле shtumi_ajax_autocomplete не представляет никакой сложности:
//src/Acme/DemoBundle/Admin/UserAdmin.php
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
...
->add('email', 'shtumi_ajax_autocomplete', array('entity_alias'=>'users',
'callback' =>
function ($queryBuilder, $alias, $field, $data) {
if (!$data['value']) {
return;
}
if ($data['type']== 1){ //1 - no, 0 - yes
$eq = " != ";
} else {
$eq = " = ";
}
$queryBuilder
->andWhere($alias . '.email' . $eq . ':value1')
->setParameter('value1', $data['value']);
}))
;
}
Зависимое поле
Еще одним полезным и часто необходимым полем является выпадающий список, содержание которого зависят от значения другого поля. Типичный пример — страны и регионы. Необходимо в поле регионы выводить только те регионы, которые соответствуют выбранной стране. ShtumiUsefulBundle предоставляет универсальное решение для создания подобных полей — тип dependent_filtered_entity.
Для начала, как и с предыдущим типом, нам необходимо сконфигурировать используемые поля:
//app/config/config.yml
shtumi_useful:
dependent_filtered_entities:
region_by_country:
class: AcmeDemoBundle:Region
parent_property: country
property: title
role: ROLE_USER
no_result_msg: 'No regions found for that country'
order_property: title
order_direction: ASC
- class — Модель зависимого поля.
- role — роль пользователя, которому будет доступно использование поля. По-умолчанию — IS_AUTHENTICATED_ANONYMOUSLY — всем.
- parent_property — свойство модели, содержащее ссылку на главную модель со связкой ManyToOne.
- property — свойство модели, используемое как текст в зависимом выпадающем списке. По умолчанию: title
- no_result_msg — текст, который будет использоваться в зависимом выпадающем списке в случае, если зависимые элементы будут отсутствовать в БД. По уполчанию: No results were found. Данный текст можно переводить в файлах messages.локаль.php
- order_property — свойство модели, по которому будет осуществляться сортировка данных в выпадающем списке. По-умолчанию: id
- order_direction — направление сортировки:
— ASC — (по умолчанию)
— DESC
Использование самого поля опять не представляет никакой сложности.
$formBuilder
->add('country', 'entity', array('class' => 'AcmeDemoBundle:Country'
, 'required' => true
, 'empty_value'=> '== Choose country =='))
->add('region', 'shtumi_dependent_filtered_entity'
, array('entity_alias' => 'region_by_country'
, 'empty_value'=> '== Choose region =='
, 'parent_field'=>'country'))
parent_field — имя главного поля в данной форме.
Загрузка значений зависимого поля осуществляется с помощью AJAX. Поэтому использовать его можно на достаточно больших объемах данных.
В результате, после заполнения пользователем формы, мы получим объект Country и объект Region.
Работа с датами
В Symfony2 есть встроенный тип формы date. Он имеет несколько виджетов и может выглядеть как несколько выпадвющих списков, для выбора числа, месяца и года, так и простого текстового поля, куда дата выводится в определенном формате. В результате вы получаете объект DateTime. Подключить к этому полю всплывающий календарь не представляет никакой сложности. Для этого лишь необходимо передать атрибут класс элементу формы, а затем в шаблоне установить для него datepicker, предварительно подключив jQuery UI, либо любой другой календарь.
DateRange
Менее часто, но все же бывает необходимо выбирать в форме интервал дат. Здесь стандартного простого решения не существует, поэтому мной было разработано универсальное решение для этого. В ShtumiUsefulBundle существует тип shtumi_daterange. Данный тип полей работает с объектами класса DateRange, который определен внутри бандла. DateRange содержит дату начала и окончания периода, формат представления дат а также методы конвертации объекта в строку и обратно.
Использование данного типа опять же нужно начинать с настроек, где необходимо указать формат дат и временной интервал по умолчанию:
//app/config/config.yml
shtumi_useful:
date_range:
date_format: d/m/Y
default_interval: P30D
Для удобства создания объектов DateRange в ShtumiUsefulBundle зарегистрирован отдельный сервис.
Создать объект DateRange можно тремя способами:
1. Используя сервис shtumi_daterange
// public function createToDate($dateEnd="now", $date_format = null, $date_interval=null)
$dateRange1 = $this->container->get('shtumi_daterange')->createToDate();
$dateRange2 = $this->container->get('shtumi_daterange')->createToDate(new DateTime('2012-01-11'), 'd/m/Y', 'P14D');
2. Создавая объект непосредственно из класса
use ShtumiUsefulBundleModelDateRange;
...
$date_format = 'Y-m-d';
$dateRange3 = new DateRange($date_format);
$dateRange3->createToDate(new DateTime(), 'P3D');
3. Осуществляя парсинг строки, содержащей интервал дат.
use ShtumiUsefulBundleModelDateRange;
...
$dateRange4 = new DateRange('m/d/Y');
$dateRange4->parseData('03/27/2012 - 04/05/2012');
Сам объект DateRange, как уже говорилось раньше содержит два главных свойства — дату начала dateStart и окончания dateEnd интервала. Эти свойства являются объектами системного класса DateTime.
echo $dateRange->dateEnd->format('d.m.Y'); //23.03.2012
$x = (string)$dateRange3; // 2012-03-20 - 2012-03-23
Использование же типа поля формы shtumi_daterange опять просто и интуитивно понятно:
$formBuilder
->add('point1', "shtumi_daterange", array('required'=>false
, 'default'=>$dateRange1))
Дополнительные DQL функции
Всем известно, что Symfony2 использует ORM Doctrine, которая имеет свой собственный язык запросов DQL. DQL синтаксисом очень похож на SQL, однако не имеет всех тех функций, которыми обладает MySQL. Мне часто приходится использовать функции IFNULL, ROUND и DATE_DIFF, которых в стандартной поставке Doctrine нет.
ShtumiUsefulBundle добавляет в DQL возможность использования этих функций с тем же синтаксисом, что и в MySQL.
Для использования дополнительных DQL функций необходимо просто добавить их в конфигурацию Doctrine:
doctrine:
...
orm:
entity_managers:
default:
dql:
datetime_functions:
datediff: ShtumiUsefulBundleDQLDateDiff
numeric_functions:
ifnull: ShtumiUsefulBundleDQLIfNull
round: ShtumiUsefulBundleDQLRound
Заключение
Конечно, глобальных задач представленный бандл не решает. Но все же несколько мелких проблем, которые возникают перед разработчиками на симфони были решены и облегчили мне жизнь в работе над несколькими проектами. Надеюсь моя работа покажется интересной еще кому-нибудь и хоть немного облегчит ему жизнь. Естественно, что по мере дальнейшей работы с фреймворком, ShtumiUsefulBundle будет пополняться новыми типами форм, DQL функциями, добавятся расширения TWIG и т. д. Разрабатывая этот бандл я пытался делать решения универсальными и легко конфигурируемыми, старался все подробно описать в документации. Буду очень рад услышать отзывы об его использовании, критику, комментарии и предложения.
Автор: shtumi