Pull to refresh

Использование ShtumiUsefulBundle в Symfony2 — несколько полезных вещей в одном бандле

Symfony *
Sandbox
Разрабатывая проекты на базе нового, но уже ставшего очень популярным фреймворка Symfony2 невольно сталкиваешься с кусками кода, которые с минимальными изменениями, а то и вовсе без них кочуют из одного проекта в другой. Собрав несколько таких «кусков» воедино я создал ShtumiUsefulBundle, об использовании которого хочу рассказать.

Найти сам бандл вы можете на GitHub: ShtumiUsefulBundle.

Установка и настройка бандла тривиальны и описаны в Reedme. Останавливаться на этом вопросе я не буду.

Поля формы с авто-подстановкой


В Symfony2 есть замечательный тип полей entity — поле, содержащее выпадающий список Select, заполненный записями определенной таблицы. При чем после отправки формы вы получаете не id выбранной записи, а сам объект. Очень удобно! Удобно, пока количество данных в таблице не превысило разумных пределов. Но когда у вас, к примеру, таблица пользователей содержит десятки тысяч записей и нужно сделать в форме выбор пользователя, данный подход становится неприменимым. Разумным решением в данном случае будет использование текстового поля с авто-подстановкой. ShtumiUsefulBundle позволяет создавать и гибко настраивать такие поля.

image

Для начала необходимо определить в настройках каждое поле с автоподстановкой (в примере их два).

//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.

image

Для начала, как и с предыдущим типом, нам необходимо сконфигурировать используемые поля:

//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 содержит дату начала и окончания периода, формат представления дат а также методы конвертации объекта в строку и обратно.

image

Использование данного типа опять же нужно начинать с настроек, где необходимо указать формат дат и временной интервал по умолчанию:

//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 Shtumi\UsefulBundle\Model\DateRange;
...
$date_format = 'Y-m-d';
$dateRange3 = new DateRange($date_format);
$dateRange3->createToDate(new \DateTime(), 'P3D');


3. Осуществляя парсинг строки, содержащей интервал дат.


use Shtumi\UsefulBundle\Model\DateRange;
...
$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: Shtumi\UsefulBundle\DQL\DateDiff
                    numeric_functions:
                        ifnull: Shtumi\UsefulBundle\DQL\IfNull
                        round: Shtumi\UsefulBundle\DQL\Round


Заключение


Конечно, глобальных задач представленный бандл не решает. Но все же несколько мелких проблем, которые возникают перед разработчиками на симфони были решены и облегчили мне жизнь в работе над несколькими проектами. Надеюсь моя работа покажется интересной еще кому-нибудь и хоть немного облегчит ему жизнь. Естественно, что по мере дальнейшей работы с фреймворком, ShtumiUsefulBundle будет пополняться новыми типами форм, DQL функциями, добавятся расширения TWIG и т. д. Разрабатывая этот бандл я пытался делать решения универсальными и легко конфигурируемыми, старался все подробно описать в документации. Буду очень рад услышать отзывы об его использовании, критику, комментарии и предложения.
Tags:
Hubs:
Total votes 12: ↑10 and ↓2 +8
Views 3K
Comments Comments 3