Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Now, in the router configuration can be specified method in the service.

  • Loading branch information...
commit 2046e059e52f1e543659165207417b48bea82bdf 1 parent 27863c3
@ghua authored
View
15 Api/Api.php
@@ -20,6 +20,9 @@ class Api
protected $api = array('actions' => array());
protected $config;
+
+ const Bundle_Action_Regex = '/^([\w]+)Bundle:([\w]+):([\w]+)$/i';
+ const Service_Regex = '/^([\w]+):([\w]+)$/i';
/**
* Initialize the API.
@@ -50,13 +53,15 @@ protected function createApi()
$api = array();
foreach($this->config['router']['rules'] as $rule) {
- if(!preg_match('/^([\w]+)Bundle:([\w]+):([\w]+)$/', $rule['defaults']['_controller'], $match)) {
+ if(preg_match($this::Bundle_Action_Regex, $rule['defaults']['_controller'], $match)) {
+ list($all, $shortBundleName, $controllerName, $methodName) = $match;
+ $key = sprintf('%s_%s', $shortBundleName, $controllerName);
+ } elseif(preg_match($this::Service_Regex, $rule['defaults']['_controller'], $match)) {
+ list($all, $key, $methodName) = $match;
+ } else {
throw new \InvalidArgumentException();
}
- list($all, $shortBundleName, $controllerName, $methodName) = $match;
-
- $key = sprintf('%s_%s', $shortBundleName, $controllerName);
-
+
if(!array_key_exists($key, $api) or !is_array($api[$key]))
$api[$key] = array();
View
14 Controller/ForTestingController.php
@@ -2,6 +2,7 @@
namespace Ext\DirectBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+use Symfony\Component\DependencyInjection\ContainerInterface;
use Ext\DirectBundle\Response\Response;
use Ext\DirectBundle\Response\FormError;
use Ext\DirectBundle\Response\ValidatorError;
@@ -81,4 +82,17 @@ public function testExceptionAction()
throw new \Exception('Exception from testExceptionAction');
}
+ public function __construct($container = null)
+ {
+ if($container instanceof ContainerInterface)
+ $this->container = $container;
+ }
+
+ public function testActionAsService($_data)
+ {
+ return $this->container->get('ext_direct')
+ ->createResponse(new Response(), $_data)
+ ->setSuccess(true);
+ }
+
}
View
2  DependencyInjection/Configuration.php
@@ -63,7 +63,7 @@ private function addRouterSection(ArrayNodeDefinition $rootNode)
->scalarNode('_controller')
->validate()
->ifTrue(function($v) {
- return !preg_match('/^[\w]+:[\w]+:[\w]+$/i', $v);
+ return !preg_match('/^[\w]+(:[\w]+)?:[\w]+$/i', $v);
})
->thenInvalid('This %s format is not supported')
->end()
View
5 Resources/config/services.yml
@@ -2,6 +2,7 @@ parameters:
ext_direct.controller.class: Ext\DirectBundle\Controller\DirectController
ext_direct.response_factory.class: Ext\DirectBundle\Response\ResponseFactory
ext_direct.controller_resolver.class : Ext\DirectBundle\Router\ControllerResolver
+ ext_direct_test_service.class: Ext\DirectBundle\Controller\ForTestingController
services:
ext_direct.controller:
@@ -16,3 +17,7 @@ services:
class: %ext_direct.response_factory.class%
scope: request
arguments: [ @request, @ext_direct.controller_resolver, @service_container ]
+
+ ext_direct_test_service:
+ class: %ext_direct_test_service.class%
+ arguments: [ @service_container ]
View
53 Resources/doc/index.eng.md
@@ -50,7 +50,7 @@ Alternative way, add to deps file:
* error_template - template of validation errors array;
* basic - basic parameters (optional);
* defaults - basic parameters;
- * _controller - NodeName:Controller:method;
+ * _controller - NodeName:Controller:method or или service\_name:methodName;
* params – method has parameters;
* form – formHandler method;
* reader – analogue of store.reader in extjs, it supports:
@@ -79,6 +79,9 @@ Alternative way, add to deps file:
createCustomer:
defaults: { _controller: AcmeDemoBundle:Demo:createCustomer, params: true, form: true }
+
+ chat:
+ defaults: { _controller: chat_service:chat, params: true, form: true }
</pre>
Example of Use
@@ -466,6 +469,48 @@ Similar task can also be solved using DirectBundle.
In this example, the errors are specially retrieved from validator service, the response format will be similar to the response of the previous section.
+##### Exceptions #####
+
+For assist in the development, router can catch exceptions from symfony2 controller.
+Example:
+
+###### Conroller (Symfony2) ######
+
+ public function testExceptionAction()
+ {
+ throw new \Exception('Exception from testExceptionAction');
+ }
+
+###### ExtJS application ######
+
+ Ext.Direct.on('exception', function(e) {
+ Ext.Msg.show({
+ title: 'Exception!',
+ msg: e.message + ' ' + e.where,
+ buttons: Ext.Msg.OK,
+ icon: Ext.MessageBox.ERROR
+ });
+ });
+
+Result of calling testException method will be ejection exception:
+
+ [
+ {
+ "message":"exception 'Exception' with message 'Exception from testExceptionAction'",
+ "where":"in \/home\/gh\/dev\/symfony2sandbox\/vendor\/bundles\/Ext\/DirectBundle\/Controller\/ForTestingController.php: 81",
+ "type":"exception",
+ "tid":3,
+ "action":
+ "ExtDirect_ForTesting",
+ "method":"testException"
+ }
+ ]
+
+ExtJS can display an error message or do something else.
+
+_Warning! This mode can use only in the develop. In production mode, exceptions are handled by symfony.
+By default response is HTTP code 500, with the message: Internal Server Error._
+
Development
---------
@@ -494,6 +539,12 @@ For testing add to config_test.yml:
testFormEntityValidationResponse:
defaults: { _controller: ExtDirectBundle:ForTesting:testFormEntityValidationResponse, params: true, form: true }
+
+ testServiceAction:
+ defaults: { _controller: ext_direct_test_service:testActionAsService, params: true }
+
+ testException:
+ defaults: { _controller: ExtDirectBundle:ForTesting:testException }
Also you should add to the configuration of phpunit.xml the following:
View
580 Resources/doc/index.rus.md
@@ -50,7 +50,7 @@ DirectBundle -- это реализация ExtDirect спецификации
* error_template - шаблон оформления массива ошибок валидации;
* basic - базовые параметры (необязательно);
* defaults - основные параметры;
- * _controller - ИмяУзла:Контроллер:метод;
+ * _controller - ИмяУзла:Контроллер:метод или имя\_сервиса:метод;
* params - метод принимает параметры;
* form - метод formHandler;
* reader - аналог store.reader в extjs, поддерживается:
@@ -79,6 +79,10 @@ DirectBundle -- это реализация ExtDirect спецификации
createCustomer:
defaults: { _controller: AcmeDemoBundle:Demo:createCustomer, params: true, form: true }
+
+ chat:
+ defaults: { _controller: chat_service:chat, params: true, form: true }
+
</pre>
### Подключение к шаблону ###
@@ -351,7 +355,7 @@ _Дополнительную информацию по событиям луч
###### Пример submit запроса (POST) ######
country_id 5
extAction AcmeDemo_Demo
- extMethod createCustomer
+ extMethod createCustomer
extTID 11
extType rpc
extUpload false
@@ -541,7 +545,579 @@ _Внимание! Режим оформления исключений дост
testFormEntityValidationResponse:
defaults: { _controller: ExtDirectBundle:ForTesting:testFormEntityValidationResponse, params: true, form: true }
+
+ DirectBundle
+============
+
+DirectBundle -- это реализация ExtDirect спецификации для symfony2.
+Тестировалось на: symfony 2.0.16, doctrine 2.2.2.
+
+Установка
+---------
+
+##### Добавив субмодуль в ваш текущий проект #####
+Лучший способ установки это добавив субмодуль, в ваш git репозиторий.
+
+ $ git submodule add git://github.com/ghua/DirectBundle.git vendor/bundles/Ext/DirectBundle
+
+##### Через deps файл #####
+Альтернативный способ, добавьте в deps файл:
+
+ [ExtDirectBundle]
+ git=git://github.com/ghua/DirectBundle.git
+ target=/bundles/Ext/DirectBundle
+
+### Добавляем namespace в autoloader ###
+
+ <?php
+ // app/autoload.php
+ $loader->registerNamespaces(array(
+ // ...
+ 'Ext' => __DIR__.'/../vendor/bundles',
+ // ...
+ ));
+
+### Регистрируем DirectBundle в AppKernel ###
+
+ <?php
+ // app/AppKernel.php
+ public function registerBundles()
+ {
+ $bundles = array(
+ // ...
+ new Ext\DirectBundle\ExtDirectBundle(),
+ // ...
+ );
+
+ // ...
+ return $bundles;
+ }
+
+### Пример конфигурации ###
+
+ * error_template - шаблон оформления массива ошибок валидации;
+ * basic - базовые параметры (необязательно);
+ * defaults - основные параметры;
+ * _controller - ИмяУзла:Контроллер:метод или имя\_сервиса:метод;
+ * params - метод принимает параметры;
+ * form - метод formHandler;
+ * reader - аналог store.reader в extjs, поддерживается:
+ * root,
+ * successProperty,
+ * totalProperty.
+
+<pre>
+ ext_direct:
+ basic:
+ error_template: ExtDirectBundle::extjs_errors.html.twig
+ router:
+ rules:
+ getCustomers:
+ defaults: { _controller: AcmeDemoBundle:Demo:getCustomers, params: true }
+ reader: { root: root }
+
+ getCountries:
+ defaults: { _controller: AcmeDemoBundle:Demo:getCountries }
+
+ getRoles:
+ defaults: { _controller: AcmeDemoBundle:Demo:getRoles }
+
+ updateCustomer:
+ defaults: { _controller: AcmeDemoBundle:Demo:updateCustomer, params: true }
+
+ createCustomer:
+ defaults: { _controller: AcmeDemoBundle:Demo:createCustomer, params: true, form: true }
+
+ chat:
+ defaults: { _controller: chat_service:chat, params: true, form: true }
+</pre>
+
+### Подключение к шаблону ###
+
+В вашем базовом шаблоне, добавьте:
+
+ <script type="text/javascript" src="/api.js"></script>
+
+В ExtJS приложении добавьте параметры подключения ExtDirect:
+
+ Ext.direct.Manager.addProvider(Ext.app.REMOTING_API);
+
+Пример использования
+--------------------
+
+#### Простой вариант ####
+
+Для рассмотрения базового примера использования, рассмотрим задачу извлечения данных, допустим, чтобы заполнить хранилище (Ext.data.Store).
+
+###### Контроллер (Symfony2) ######
+
+ <?php
+ namespace Acme\DemoBundle\Controller;
+
+ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+
+ 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
+ });
+
+#### Расширенные варианты #####
+
+##### AbstractQuery #####
+
+Можно обойтись несколько иначе и передать в DirectBundle результат из getQuery() (AbstractQuery)
+
+###### Контроллер (Symfony2) ######
+ <?php
+ namespace Acme\DemoBundle\Controller;
+
+ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+ use Ext\DirectBundle\Response\AbstractQuery;
+ 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);
+ }
+ }
+
+##### KnpPaginator и прием параметров #####
+
+Редко когда извлекаются и передаются все данные, без разбора.
+Обычной задачей является пагинация, фильтрация, сортировка.
+
+Конечно, разбитие на страницы можно реализовать самостоятельно и DirectBundle нисколько в этом не помеха.
+Но в моём проекте, для этой задачи, используется [KnpPaginator](https://github.com/KnpLabs/KnpPaginatorBundle).
+
+###### Контроллер (Symfony2) ######
+ <?php
+ namespace Acme\DemoBundle\Controller;
+
+ use Acme\DemoBundle\Direct\EventListener\CompactCustomerRolesSubscriber;
+ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+ use Ext\DirectBundle\Response\KnpPaginator;
+ 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.
+Это значит, что если параметр определен и его возможно передать, он будет передан.
+
+**_Дополнение!_ Возвращаемый из findCustomers AbstractQuery должен быть с установленным HydrationMode равным HYDRATE_ARRAY.
+Выполняется это путем вызова метода setHydrationMode().**
+
+###### CustomerRepository ######
+ <?php
+ namespace Acme\DemoBundle\Repository;
+
+ use Doctrine\ORM\EntityRepository;
+ use Doctrine\ORM\Query;
+ class CustomerRepository extends EntityRepository
+ {
+ public function findCustomers($filters = array(), $sorts = array())
+ {
+
+ $query = $this->createQueryBuilder('customer')
+ // ...
+ ->getQuery();
+
+ return $query->setHydrationMode(Query::HYDRATE_ARRAY);
+ }
+ }
+
+###### Пример запроса из 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 может быть передан как параметр метода.
+
+###### Дополнительные параметры #######
+Существуют еще несколько возможных параметров:
+
+* Request $request -- оригинал объекта Symfony\Component\HttpFoundation\Request, для данного запроса;
+* $\_data - весь оригинальный массив переданных параметров;
+* $\_list - тот же самый $\_data только для пакетной обработки, к примеру изменение нескольких строк в grid, $_list будет содержать массив из нескольких $\_data.
+
+##### События #####
+Есть возможность добавить обработку событий. На данный момент обработчик Ext\DirectBundle\Response\AbstractQuery поддерживает: PRE\_QUERY\_EXECUTE и POST\_QUERY\_EXECUTE, а основанный на нем Ext\DirectBundle\Response\KnpPaginator, поддерживает только последний.
+_Дополнительную информацию по событиям лучше смотреть непосредственно в исходном коде Ext\DirectBundle\Response\AbstractQuery::execute()._
+
+Ниже приведенный пример изменяет, уже извлеченные данные, перед передачей их в сеть.
+
+###### Пример события #######
+ <?php
+ namespace Acme\DemoBundle\Direct\EventListener;
+
+ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+ use Ext\DirectBundle\Event\DirectEvents;
+ use Ext\DirectBundle\Event\ResponseEvent;
+
+ 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);
+ }
+ }
+
+
+##### Обработка form submit и возврат ошибок из формы #####
+
+Рассмотрим задачу обработки submit из Ext.form.Panel.
+В примере кода, для extjs, определено окно отображения формы и сама форма с элементами.
+
+###### Форма (ExtJS) ######
+ Ext.define('ACME.view.customer.New', {
+ extend: 'Ext.window.Window',
+ alias : 'widget.customernewwindow',
+
+ autoShow: true,
+ title : 'New Customer',
+ layout: 'fit',
+
+ items: [{
+ xtype: 'customerform',
+ api: {
+ submit: Actions.AcmeDemo_Demo.createCustomer
+ },
+ paramsAsHash: true
+ }],
+
+ buttons: [{
+ text: 'Save',
+ action: 'submit'
+ }]
+ });
+
+ Ext.define('ACME.view.customer.Form', {
+ extend: 'Ext.form.Panel',
+ alias : 'widget.customerform',
+
+ layout: 'vbox',
+ frame: true,
+ items: [{
+ xtype: 'textfield',
+ name: 'name',
+ fieldLabel: 'Name',
+ },{
+ xtype: 'combobox',
+ name: 'country_id',
+ fieldLabel: 'Country',
+ valueField: 'id',
+ displayField: 'name',
+ store: 'Country',
+ forceSelection: true
+ },{
+ xtype: 'combobox',
+ name: 'role_ids',
+ fieldLabel: 'Roles',
+ valueField: 'id',
+ displayField: 'name',
+ store: 'Role',
+ multiSelect: true
+ }]
+ });
+
+###### Пример submit запроса (POST) ######
+ country_id 5
+ extAction AcmeDemo_Demo
+ extMethod createCustomer
+ extTID 11
+ extType rpc
+ extUpload false
+ id
+ name Амин
+ role_ids[] 3
+ role_ids[] 1
+
+###### Контроллер (Symfony2) ######
+ <?php
+ namespace Acme\DemoBundle\Controller;
+
+ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+ use Ext\DirectBundle\Response\FormError;
+ use Acme\DemoBundle\Entity\Customer;
+ 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(), для обработки формы.
+В примере форма определена как служба. Это необходимо для работы [трансформеров](http://symfony.com/doc/current/cookbook/form/data_transformers.html).
+
+Если валидация формы прошла успешно, производится ответ передающий 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>"}}
+ ]
+
+##### Синхронизация хранилища и возврат ошибок из сервиса Validator #####
+
+Существует задача синхронизации хранилища это бывает связано с изменением сразу нескольких строк.
+Подобную задачу тоже можно решить, используя DirectBundle.
+
+###### Контроллер (Symfony2) ######
+ <?php
+ namespace Acme\DemoBundle\Controller;
+
+ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+ use Symfony\Component\HttpFoundation\Request;
+ use Ext\DirectBundle\Response\Response;
+ use Ext\DirectBundle\Response\ValidatorError;
+ 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, формат ответа будет аналогичен ответу из предыдущего раздела.
+
+##### Исключения #####
+
+Для помощи в разработке, маршрутизатор запросов ловит исключения, возвращаемые в процессе вызова метода из контроллера, определенного запросом и конфигурацией.
+Рассмотрим пример.
+
+###### Контроллер (Symfony2) ######
+
+ public function testExceptionAction()
+ {
+ throw new \Exception('Exception from testExceptionAction');
+ }
+
+###### ExtJS приложение ######
+
+ Ext.Direct.on('exception', function(e) {
+ Ext.Msg.show({
+ title: 'Exception!',
+ msg: e.message + ' ' + e.where,
+ buttons: Ext.Msg.OK,
+ icon: Ext.MessageBox.ERROR
+ });
+ });
+
+Результатом вызова testException будет выброс исключения. Будет сформирован ответ:
+
+ [
+ {
+ "message":"exception 'Exception' with message 'Exception from testExceptionAction'",
+ "where":"in \/home\/gh\/dev\/symfony2sandbox\/vendor\/bundles\/Ext\/DirectBundle\/Controller\/ForTestingController.php: 81",
+ "type":"exception",
+ "tid":3,
+ "action":
+ "ExtDirect_ForTesting",
+ "method":"testException"
+ }
+ ]
+
+ExtJS на этот ответ среагирует выводом сообщения об ошибке.
+
+_Внимание! Режим оформления исключений доступен только в решиме разработки и тестирования, в "боевом" режиме исключения оформляет symfony.
+По умолчанию реакцией будет HTTP код 500, с сообщением: внутренняя ошибка сервера._
+
+Разработка
+---------
+
+#### Тестирование ####
+Для тестирования добавьте в config_test.yml:
+
+ ext_direct:
+ router:
+ rules:
+ testArrayResponse:
+ defaults: { _controller: ExtDirectBundle:ForTesting:testArrayResponse, params: true }
+
+ testObjectResponse:
+ defaults: { _controller: ExtDirectBundle:ForTesting:testObjectResponse, params: true }
+
+ testResponseWithConfiguredReader:
+ defaults: { _controller: ExtDirectBundle:ForTesting:testResponseWithConfiguredReader, params: true }
+ reader: { root: root, successProperty: successProperty, totalProperty: totalProperty }
+
+ testFormHandlerResponse:
+ defaults: { _controller: ExtDirectBundle:ForTesting:testFormHandlerResponse, params: true, form: true }
+ reader: { root: data }
+
+ testFormValidationResponse:
+ defaults: { _controller: ExtDirectBundle:ForTesting:testFormValidationResponse, params: true, form: true }
+
+ testFormEntityValidationResponse:
+ defaults: { _controller: ExtDirectBundle:ForTesting:testFormEntityValidationResponse, params: true, form: true }
+
+ testServiceAction:
+ defaults: { _controller: ext_direct_test_service:testActionAsService, params: true }
+
+ testException:
+ defaults: { _controller: ExtDirectBundle:ForTesting:testException }
+
+Также в конфигурации phpunit.xml нужно добавить:
+
+ <directory>../vendor/bundles/Ext/DirectBundle/Tests</directory>
+
+Результат запуска тестов должен быть примерно следующим:
+
+ $ phpunit -v
+ PHPUnit 3.6.10 by Sebastian Bergmann.
+
+ Configuration read from symfony2sandbox/app/phpunit.xml
+
+ .......
+
+ Time: 1 second, Memory: 46.75Mb
+
+ OK (7 tests, 78 assertions)
+
+
testException:
defaults: { _controller: ExtDirectBundle:ForTesting:testException }
View
40 Router/ControllerResolver.php
@@ -8,6 +8,7 @@
use Symfony\Component\HttpKernel\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\HttpFoundation\Request as HttpFoundation_Request;
+use Ext\DirectBundle\Api\Api;
/**
* @author Semyon Velichko <semyon@velichko.net>
@@ -79,8 +80,15 @@ public function getControllerFromCall(Call $call)
{
$this->setCall($call);
- list($bundle, $controller) = explode('_', $call->getAction());
- $fullPath = sprintf('%1$sBundle:%2$s:%3$s', $bundle, $controller, $call->getMethod());
+ $explodeResult = explode('_', $call->getAction());
+
+ if(count($explodeResult) <> 2)
+ {
+ $fullPath = sprintf('%1$s:%2$s', $call->getAction(), $call->getMethod());
+ } else {
+ list($bundle, $controller) = $explodeResult;
+ $fullPath = sprintf('%1$sBundle:%2$s:%3$s', $bundle, $controller, $call->getMethod());
+ }
foreach($this->config['router']['rules'] as $key => $rule)
{
@@ -91,21 +99,27 @@ public function getControllerFromCall(Call $call)
if(!$this->getMethodConfigKey())
throw new \BadMethodCallException(sprintf('%1$s does not configured, check config.yml', $fullPath));
- $bundle = $this->kernel->getBundle(sprintf('%sBundle', $bundle));
- $this->setBundle($bundle);
+ try {
+ $controller = $this->container->get($call->getAction());
+ $method = $call->getMethod();
+ } catch(\Exception $e)
+ {
+ $bundle = $this->kernel->getBundle(sprintf('%sBundle', $bundle));
+ $this->setBundle($bundle);
- $controller = sprintf('%s\\Controller\\%sController::%sAction', $bundle->getNamespace(), $controller, $call->getMethod());
-
- if (is_array($controller) || (is_object($controller) && method_exists($controller, '__invoke'))) {
- return $controller;
- }
+ $controller = sprintf('%s\\Controller\\%sController::%sAction', $bundle->getNamespace(), $controller, $call->getMethod());
- if (false === strpos($controller, ':') && method_exists($controller, '__invoke')) {
- return new $controller;
- }
+ if (is_array($controller) || (is_object($controller) && method_exists($controller, '__invoke'))) {
+ return $controller;
+ }
- list($controller, $method) = $this->createController($controller);
+ if (false === strpos($controller, ':') && method_exists($controller, '__invoke')) {
+ return new $controller;
+ }
+ list($controller, $method) = $this->createController($controller);
+ }
+
if (!method_exists($controller, $method)) {
throw new \InvalidArgumentException(sprintf('Method "%s::%s" does not exist.', get_class($controller), $method));
}
View
10 Tests/Api/ApiTest.php
@@ -25,12 +25,16 @@ public function test__toString()
$config['router']['rules'] += array('getFormTest' => array('defaults' =>
array('_controller' => 'ExtDirectBundle:Direct:getFormTest', 'form' => true, 'params' => true)));
+ $config['router']['rules'] += array('getServiceTest' => array('defaults' =>
+ array('_controller' => 'ext_direct_api_test_service:getServiceTest', 'form' => true, 'params' => true)));
+
$api = new Api($config);
$apiString = $api->__toString();
$apiJson = json_decode($apiString);
$this->assertObjectHasAttribute('actions', $apiJson);
$this->assertObjectHasAttribute('ExtDirect_Direct', $apiJson->actions);
+ $this->assertObjectHasAttribute('ext_direct_test_service', $apiJson->actions);
$this->assertObjectHasAttribute('len', $apiJson->actions->ExtDirect_Direct[0]);
$this->assertEquals(0, $apiJson->actions->ExtDirect_Direct[0]->len);
@@ -38,5 +42,11 @@ public function test__toString()
$this->assertObjectHasAttribute('formHandler', $apiJson->actions->ExtDirect_Direct[1]);
$this->assertEquals(1, $apiJson->actions->ExtDirect_Direct[1]->len);
$this->assertEquals(1, $apiJson->actions->ExtDirect_Direct[1]->formHandler);
+
+ $this->assertObjectHasAttribute('len', $apiJson->actions->ext_direct_api_test_service[0]);
+ $this->assertObjectHasAttribute('formHandler', $apiJson->actions->ext_direct_api_test_service[0]);
+ $this->assertEquals(1, $apiJson->actions->ext_direct_api_test_service[0]->len);
+ $this->assertEquals(1, $apiJson->actions->ext_direct_api_test_service[0]->formHandler);
+
}
}
View
36 Tests/GeneralTest.php
@@ -259,5 +259,41 @@ public function testFormEntityValidationResponse()
$this->assertRegExp('/This value should not be blank/', $arrayResult['result']['msg']);
$this->assertRegExp('/This value should be 0 or more/', $arrayResult['result']['msg']);
}
+
+ public function testActionAsService()
+ {
+ $postRawArray = array('action' => 'ext_direct_test_service',
+ 'method' => 'testActionAsService',
+ 'data' => array(array('page' => rand(1,10), 'start' => rand(10,20), 'limit' => rand(100,9999))),
+ 'type' => 'rpc',
+ 'tid' => rand(1, 10));
+ $postRawData = json_encode($postRawArray);
+
+ $client = static::createClient();
+ $crawler = $client->request('POST',
+ $this->get('router')->generate('ExtDirectBundle_route'),
+ array(),
+ array(),
+ array(),
+ $postRawData);
+
+
+ $jsonResult = $client->getResponse()->getContent();
+ $arrayResult = json_decode($jsonResult, true);
+ $this->assertInternalType('array', $arrayResult);
+ $this->assertArrayHasKey(0, $arrayResult);
+ $arrayResult = array_shift($arrayResult);
+
+ $this->assertArrayHasKey('result', $arrayResult);
+
+ $this->assertArrayHasKey('page', $arrayResult['result']);
+ $this->assertEquals($postRawArray['data'][0]['page'], $arrayResult['result']['page']);
+
+ $this->assertArrayHasKey('start', $arrayResult['result']);
+ $this->assertEquals($postRawArray['data'][0]['start'], $arrayResult['result']['start']);
+
+ $this->assertArrayHasKey('limit', $arrayResult['result']);
+ $this->assertEquals($postRawArray['data'][0]['limit'], $arrayResult['result']['limit']);
+ }
}
Please sign in to comment.
Something went wrong with that request. Please try again.