- PVSM.RU - https://www.pvsm.ru -
Работая над проектом использующем extjs, для связи extjs <= extdirect => symfony использовал проект NetonDirectBundle.
Этот небольшой bundle предоставляет базовые возможности связи с symfony, но в некоторых местах неоптимален и слишком прост. Всё глубже занимаясь его улучшением, накопилось достаточно много изменений.
К сожалению автор проект забросил, так как последняя активность была год назад, я решил подхватить упавшее знамя и слить свои изменения в форк этого проекта и соответственно поделиться с сообществом.
Изначально я планировал, для начала, написать краткую документацию на русском языке и пост. Увлекшись в написании документации я уже не знаю, чем можно дополнить статью.
Получилось наоборот, достопочтенную публику я не буду утруждать описанием процесса установки bundle, а перейду непосредственно к плюшкам.
Возможно будет многовато кода, но для наглядности.
Этот метод позволяет сохранить совместимость с оригинальным кодом.
Для базового примера использования, рассмотрим задачу извлечения данных, допустим, чтобы заполнить хранилище (Ext.data.Store).
Контроллер (Symfony2)
<?php
namespace AcmeDemoBundleController;
use SymfonyBundleFrameworkBundleControllerController;
class DemoController extends Controller
{
public function getRolesAction()
{
$data = $this->getDoctrine()
->getRepository('AcmeDemoBundle:Role')
->createQueryBuilder('role')
->getQuery()
->getArrayResult();
return $data;
}
}
Модель и хранилище (ExtJS)
Ext.define('ACME.model.Role', {
extend: 'Ext.data.Model',
fields: ['id', 'code', 'name', 'customer_id'],
proxy: {
type: 'direct',
api: {
read: Actions.AcmeDemo_Demo.getRoles
}
}
});
Ext.define('ACME.store.Role', {
extend: 'Ext.data.Store',
model: 'ACME.model.Role',
autoLoad: true
});
Можно обойтись несколько иначе и передать в DirectBundle результат из getQuery() (AbstractQuery)
Контроллер (Symfony2)
<?php
namespace AcmeDemoBundleController;
use SymfonyBundleFrameworkBundleControllerController;
use ExtDirectBundleResponseAbstractQuery;
class DemoController extends Controller
{
public function getCountriesAction()
{
$query = $this->getDoctrine()
->getRepository('AcmeDemoBundle:Country')
->createQueryBuilder('country')
->getQuery();
return $this->get('ext_direct')
->createResponse(new AbstractQuery(), $query);
}
}
Редко когда извлекаются и передаются все данные, без разбора. Обычной задачей является пагинация, фильтрация, сортировка.
Конечно, разбитие на страницы можно реализовать самостоятельно и DirectBundle нисколько в этом не помеха. Но в моём проекте, для этой задачи, используется KnpPaginator.
Контроллер (Symfony2)
<?php
namespace AcmeDemoBundleController;
use AcmeDemoBundleDirectEventListenerCompactCustomerRolesSubscriber;
use SymfonyBundleFrameworkBundleControllerController;
use ExtDirectBundleResponseKnpPaginator;
class DemoController extends Controller
{
public function getCustomersAction($page = 1, $limit = 10, $filter = array(), $sort = array())
{
$query = $this->getDoctrine()
->getEntityManager()
->getRepository('AcmeDemoBundle:Customer')
->findCustomers($filter, $sort);
$paginator = $this->get('knp_paginator')->paginate($query, $page, $limit);
return $this->get('ext_direct')
->createResponse(new KnpPaginator(), $paginator)
->addEventSubscriber(new CompactCustomerRolesSubscriber());
}
}
Рассмотрим внимательно параметры данного метода. Они являются не обязательными, т.к. вызов метода происходит через предварительный ReflectionMethod::getParameters. Это значит, что если параметр определен и его возможно передать, он будет передан.
Пример запроса из ExtJS (JSON)
{
"action":"AcmeDemo_Demo",
"method":"getCustomers",
"data":[{"page":1, "start":0, "limit":28,
"sort":[
{"property":"id","direction":"ASC"}
],
"filter":[
{"property":"roles","value":[4]},
{"property":"country","value":225}
]
}],
"type":"rpc",
"tid":1
}
Соответственно любой ключ из массива data может быть передан как параметр метода.
Существуют еще несколько возможных параметров:
Есть возможность добавить обработку событий. На данный момент обработчик ExtDirectBundleResponseAbstractQuery, и основанный на нем ExtDirectBundleResponseKnpPaginator, поддерживает одно событие — POST_QUERY_EXECUTE.
Ниже приведенный пример изменяет, уже извлеченные данные, перед передачей их в сеть.
Пример события
<?php
namespace AcmeDemoBundleDirectEventListener;
use SymfonyComponentEventDispatcherEventSubscriberInterface;
use ExtDirectBundleEventDirectEvents;
use ExtDirectBundleEventResponseEvent;
class CompactCustomerRolesSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(DirectEvents::POST_QUERY_EXECUTE => 'callbackFunction');
}
public function callbackFunction(ResponseEvent $event)
{
$data = $event->getData();
foreach($data as $n => $customer)
{
if(isset($data[$n]['role_ids']))
$data[$n]['role_ids'] = array();
foreach($customer['roles'] as $role)
{
$data[$n]['role_ids'][] = $role['id'];
}
}
$event->setData($data);
}
}
Рассмотрим задачу обработки submit из Ext.form.Panel.
Конфигурация формы
api: {
submit: Actions.AcmeDemo_Demo.createCustomer
}
paramsAsHash: true
Контроллер (Symfony2)
<?php
namespace AcmeDemoBundleController;
use SymfonyBundleFrameworkBundleControllerController;
use ExtDirectBundleResponseFormError;
use AcmeDemoBundleEntityCustomer;
class DemoController extends Controller
{
public function createCustomerAction($_data)
{
$Customer = new Customer();
$form = $this->createForm($this->get('acme_demo.updatecustomer'), $Customer);
$_data = array_intersect_key($_data, $form->getChildren());
$form->bind($_data);
if($form->isValid())
{
$em = $this->getDoctrine()
->getEntityManager();
$em->persist($Customer);
$em->flush();
} else {
return $this->get('ext_direct')
->createResponse(new FormError(), $form);
}
return $this->get('ext_direct')
->createResponse(new Response())
->setSuccess(true);
}
}
Переданные параметры, кроме служебных, будут переданы в $_data. Этот массив можно прямо передать в $form->bind(), для обработки формы. В примере форма определена как служба. Это необходимо для работы трансформеров.
Если валидация формы прошла успешно, производится ответ передающий success: true.
[
{"type":"rpc",
"tid":"11",
"action":"AcmeDemo_Demo",
"method":"createCustomer",
"result":{"success":true}}
]
В случае наличия ошибок, можно передать ответ содержащий success: false и msg с текстом ошибки.
[
{"type":"rpc",
"tid":"18",
"action":"AcmeDemo_Demo",
"method":"createCustomer",
"result":{"success":false,
"msg":"<ul>n<li>This value should not be blank</li>n<li>This value is not valid</li>n</ul>"}}
]
Существует задача синхронизации хранилища это бывает связано с изменением сразу нескольких строк. Подобную задачу тоже можно решить, используя DirectBundle.
<?php
namespace AcmeDemoBundleController;
use SymfonyBundleFrameworkBundleControllerController;
use ExtDirectBundleResponseResponse;
use ExtDirectBundleResponseValidatorError;
class DemoController extends Controller
{
public function updateCustomerAction(Request $request, $_list)
{
$repository = $this->getDoctrine()
->getRepository('AcmeDemoBundle:Customer');
if($request->getMethod() === "POST")
{
foreach($_list as $customer)
{
if(!isset($customer['id']))
throw new InvalidArgumentException();
$Customer = $repository->findOneById($customer['id']);
$form = $this->createForm($this->get('acme_demo.updatecustomer'), $Customer);
$form->bind(array_intersect_key($customer, $form->getChildren()));
if($form->isValid())
{
$this->getDoctrine()
->getEntityManager()
->flush();
} else {
return $this->get('ext_direct')
->createResponse(new ValidatorError(), $this->get('validator')->validate($Customer));
}
}
return $this->get('ext_direct')
->createResponse(new Response())
->setSuccess(true);
}
return new Response(502);
}
В данном примере специально ошибки извлекаются из сервиса validator, формат ответа будет аналогичен ответу из предыдущего раздела.
Итого
Мне очень хочется, что бы кому то этот bundle был полезен.
Если кто то захочет внести свои улучшения, всегда пожалуйста! Fork & Pull Request! ;)
Автор: GHua
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/extjs-4/11262
Нажмите здесь для печати.