Набор инструментов по созданию экземпляра Doctrine QueryBuilder через графический интерфейс с возможностью его сериализации/десериализации.
Входят следующие инструменты:
- Bundle - бандл Symfony, бэкенд конструктора
- client - фронтенд конструктора на React-Redux
- Creator - сервис по созданию экземплярыа QueryBuilder
- Serializer - сервис по сериализации-десериализации QueryBuilder
Symfony 2.8+
# app/AppKernel.php
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = [
...
new FOD\QueryConstructor\Bundle\QueryConstructorBundle(),
];
...
}
...
}
# app/config/routing.yml
...
fod_query_constructor:
resource: "@QueryConstructorBundle/Controller/DefaultController.php"
type: annotation
prefix: /fod
В папке /assets
пакета содержатся готовые скомпилированные js-файлы.
- Создать симлинк на папку с js-файлами из публичной папки с js (путь по умолчанию:
/web/assets/js
)
cd web/assets/js
ln -sfn ../../../vendor/friendsofdoctrine/query-constructor/assets query-constructor
- Отрисовать форму конструктора через шаблонизатор twig
{{ fod_query_constructor()|raw }}
функция fod_query_constructor
Передать опцию scriptPath
со ссылкой на js-файл, который будет использован вместо пути по умолчанию /web/assets/js
{{ fod_query_constructor({'scriptPath' : '/path/to/myfile.js'})|raw }}
Передать опцию htmlId
со строковым значением, которое будет использовано вместо id по умолчанию fod-query-constructor
{{ fod_query_constructor({'htmlId' : 'custom-constructor-id'})|raw }}
Передать опцию prefix
со строковым значением, которое будет использовано в качестве префикса к элементам формы
{{ fod_query_constructor({'prefix' : 'form[query]'})|raw }}
В результате атрибут name
элемента input
получит значение form[query][entity]
вместо entity
Все настройки выполняются через аннотации классов и свойств.
Что сделать
Указать аннотацию класса QC\Entity
.
Результат
- Сущность попадет в конструктор. Название в списке выбора - имя класса.
- Можно аггрегировать и фильтровать по всем свойствам класса, которые отмечены аннотацией
ORM\Column
. - Свойства, отмеченные аннотацией
ORM\ManyToOne
, становятся фильтрами со списками выбора (можно выбрать несколько значений)
Пример
В конструктор должна попасть сущность Room
с фильтрами Id
(integer), Name
(string) и Building
(список выбора)
<?php
// ...
use Doctrine\ORM\Mapping as ORM;
use FOD\QueryConstructor\Mapping\Annotation as QC;
/**
* @QC\Entity()
*/
class Room
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
*/
protected $id;
/**
* @ORM\Column(type="string")
*/
protected $name;
/**
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\Building", inversedBy="rooms")
*/
protected $building;
}
Что сделать
Добавить опцию title
аннотации класса QC\Entity
.
Результат
Сущность будет назваться, как указано в title
. Если опция не задана, используется название свойства в классе.
Пример
Сущность Room должна иметь подпись Помещение
<?php
// ...
use FOD\QueryConstructor\Mapping\Annotation as QC;
/**
* @QC\Entity(title="Помещение")
*/
class Room
{
// ...
}
Что сделать
Добавить аннотацию свойства QC\Property
с опцией title
.
Результат
Свойство будет назваться, как указано в title
. Если опция не задана, используется название класса.
Пример
Фильтр Building
должен иметь подпись Здание
<?php
// ...
use FOD\QueryConstructor\Mapping\Annotation as QC;
/**
* @QC\Entity(title="Помещение")
*/
class Room
{
// ...
/**
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\Building", inversedBy="rooms")
* @QC\Property(title="Здание")
*/
protected $building;
}
Что сделать
Добавить опцию titleField
в аннотацию свойства QC\Property
с указанием названия свойства связанной сущности, откуда будут взяты значения подписей.
Результат
Подписи элементов списка будут взяты из указанного свойства связанной сущности. Если опция titleField
не задана, подписи берутся из первого поля типа string. Если в сущности такого поля нет и опция не задана явно, выбрасывается исключение.
Пример
Вывести подписи из поля Building.address
<?php
// ...
use FOD\QueryConstructor\Mapping\Annotation as QC;
/**
* @QC\Entity(title="Помещение")
*/
class Room
{
// ...
/**
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\Building", inversedBy="rooms")
* @QC\Property(title="Здание", titleField="address")
*/
protected $building;
}
Что сделать
Добавить опцию aggregatable_fields
в аннотацию класса QC\Entity
с указанием названий свойств, к которым применять аггрегирующие функции. Можно задавать как массив, так и строку (если одно поле).
Результат
В списке аггрегации будут присутствовать только указанные свойства. Если не задана, присутствует только первичный ключ
Пример
В список аггрегации должны попасть Id
и Name
<?php
// ...
use Doctrine\ORM\Mapping as ORM;
use FOD\QueryConstructor\Mapping\Annotation as QC;
/**
* @QC\Entity(aggregatable_fields={"id", "name"})
*/
class Room
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
*/
protected $id;
/**
* @ORM\Column(type="string")
*/
protected $name;
/**
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\Building", inversedBy="rooms")
*/
protected $building;
}
Что сделать
Добавить опцию filterable_fields
в аннотацию класса QC\Entity
с указанием названий свойств, по которым можно фильтровать. Можно задавать как массив, так и строку (если одно поле).
Результат
В списках фильтров будут присутствовать только указанные свойства. Если опция не задана, присутствуют все свойства.
Пример
В список фильтрации должен попасть только Name
<?php
// ...
use Doctrine\ORM\Mapping as ORM;
use FOD\QueryConstructor\Mapping\Annotation as QC;
/**
* @QC\Entity(filterable_fields="name")
*/
class Room
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
*/
protected $id;
/**
* @ORM\Column(type="string")
*/
protected $name;
/**
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\Building", inversedBy="rooms")
*/
protected $building;
}
Что сделать
Добавить опцию filterable_fields_except
в аннотацию класса QC\Entity
с указанием названий свойств, которые исключить из фильтрации. Можно задавать как массив, так и строку (если одно поле).
Результат
В списках фильтров указанные свойства будут отсутствовать. Если опция применяется совместно с filterable_fields
, будут выведены только filterable_fields
за исключением свойств, отмеченных в filterable_fields_except
.
Пример
В список фильтрации не должен попасть Id
<?php
// ...
use Doctrine\ORM\Mapping as ORM;
use FOD\QueryConstructor\Mapping\Annotation as QC;
/**
* @QC\Entity(filterable_fields_except="id")
*/
class Room
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
*/
protected $id;
/**
* @ORM\Column(type="string")
*/
protected $name;
/**
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\Building", inversedBy="rooms")
*/
protected $building;
}
Что сделать
Добавить опцию date_between
аннотации класса QC\Entity
. В опции указываются названия двух свойств сущности, содержащие даты "от" и "до".
Результат
К построенному запросу будет добавлено условие AND (:dateReport BETWEEN column1 AND column2)
, где :dateReport
- дата, которая приходит из формы (удобно, если в таблице сущности применен паттерн "Версия реализации").
Пример
К запросу по сущности Room
будет добавлено условие AND (:dateReport BETWEEN fromDate AND toDate)
с параметром dateReport
.
<?php
// ...
use FOD\QueryConstructor\Mapping\Annotation as QC;
/**
* @QC\Entity(date_between={"fromDate", "toDate"})
*/
class Room
{
// ...
/**
* @ORM\Column(type="datetime")
*/
protected $fromDate;
/**
* @ORM\Column(type="datetime")
*/
protected $toDate;
}
Что сделать
Указать аннотацию класса QC\Entity
в связанной сущности (которая отмечена в текущей сущности аннотацией ORM\ManyToOne
.
Результат
При задании фильтра можно будет выбрать связанную сущность и ее свойства, настраиваемые по правилам, указанным выше.
{{ fod_query_constructor()|raw }}
Параметры запроса записываются в скрытый input с именем sqlConstructor
(к имени можно добавить префикс - см. настройку рендера выше)
$queryBuilder = $this->get('query_constructor.creator')->createFromJson($formParams['sqlConstructor']));
$entity->setSqlFilter(addslashes($this->get('query_constructor.serializer')->serialize($queryBuilder)));
Например, сериализованный QueryBuilder возвращается $entity->getSqlFilter()
:
$queryBuilder = $this->get('query_constructor.serializer')->unserialize(stripslashes($entity->getSqlFilter()));
Создаёт экземпляр Doctrine QueryBuilder из JSON
{
"aggregateFunction": "COUNT",
"entity": "MyClass1",
"property": "id"
"conditions": [
{
"type": "NONE",
"entity": "MyClass2",
"property": "name",
"operator": "=",
"value": "John"
}
]
}
Массив conditions[]
может быть пустым.
Все поля, кроме conditions[].entity
- обязательные.
Допустимые значения aggregateFunction
- COUNT
, SUM
, MIN
, MAX
, AVG
.
Допустимые значения conditions[].type
- NONE
, AND
, OR
.
Допустимые entity
, property
определяются из зарегеистрированных провайдеров (см. MetaDataProvider
).
Сериализует свойства экземпляра Doctrine QueryBuilder в виде массива. Обратно десериализует массив в QueryBuilder. Ключи массива:
- dqlParts,
- parameters,
- firstResult,
- maxResults,
- lifetime,
- cacheMode,
- cacheable,
- cacheRegion
Для успешного записи результата сериализации в БД может потребоваться использовать addslashes()
для экранирования 0-символов.
Бэкенд конструктора запросов, регистрация сервисов для Symfony
- fod.query_constructor.index - начальные данные для конструктора
- fod.query_constructor.properties - информация по выбранной сущности (свойства для выборки, фильтров, возможные связи)
Фронтенд-часть конструктора.
Альтернативы использования:
- Скомпилированные файлы - расположены в папке
/assets
проекта (настройка файлов см. выше) - Подключение React-компонента к приложению проекта (см.ниже)
####React-компонент конструктора запросов
- Выполнить
npm install
в корневой папке пакета query-constructor - Добавить в конфигурацию загрузчика babel путь к query-constructor/client
- Создать symlink на query-constructor/client в папке с исходниками React основного проекта
- В компоненте проекта импортировать компонент
QueryConstructor
из QueryConstructor.js - Пересобрать js с новым компонентом сборщиком, используемым на проекте
import QueryConstructor from '../queryConstructor/QueryConstructor'
...
<QueryConstructor prefix="myform[field]" {...this.props.queryConstructorProps} />
...
Требуются следующие параметры:
- aggregateFunctions - required {'имяКласса': 'Название'} - заполняет список аггрегирующих функций для выборки
- entities - required {'имяКласса': 'Название'} - заполняет список сущностей для выборки
- propertiesUrl - required - адрес ресурса, возвращающий свойства сущности в формате Metadata\Registry->get()
- prefix - строка, добавляемая к имени всех элементов input, создаваемых компонентом
Конструктор формирует JSON, готовый для отдачи в Creator - в input
с именем prefix[sqlConstructor]