ghua/ext-direct-bundle

该软件包已废弃,不再维护。未建议替代软件包。

Symfony2的ExtDirect实现

安装次数: 5,186

依赖项: 0

建议者: 0

安全: 0

星级: 15

关注者: 5

分支: 13

开放问题: 1

类型:symfony-bundle

v2.4.0 2014-02-13 09:11 UTC

This package is not auto-updated.

Last update: 2023-08-16 06:35:10 UTC


README

DirectBundle是symfony2的ExtDirect规范的实现。

Build Status

安装

使用composer

{
    require: {
        "ghua/ext-direct-bundle": "v2.4.0"
    }
}

在AppKernel中注册DirectBundle

<?php
// app/AppKernel.php
public function registerBundles()
{
    $bundles = array(
    // ...
        new Ext\DirectBundle\ExtDirectBundle(),
    // ...
    );

    // ...
    return $bundles;
}

修改app/config/routing.yml

ext_direct:
    resource: "@ExtDirectBundle/Resources/config/routing.yml"

修改app/config/config.yml

ext_direct:
    resource: "%kernel.root_dir%/config/extdirect_routing.yml"

配置示例

  • error_template - 验证错误数组的模板;
  • resource - 路由配置文件,例如:resource: "%kernel.root_dir%/config/extdirect_routing.yml"

extdirect_routing.yml

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 }

此外,您还可以使用控制器注释

testClassLoader:
    resource: "@AcmeTestBundle/Controller/TestController.php"

testDirectoryLoader:
    resource: "@AcmeTestBundle/Controller"
AcmeController.php
namespace Acme\TestBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

use Ext\DirectBundle\Annotation\Route;
use Ext\DirectBundle\Annotation\Reader;
use Ext\DirectBundle\Annotation\Writer;

class TestController extends Controller
{
    /**
     * @Route(name="acmeTest", isWithParams = true)
     */
     public function testAction($_data)
     {
         // code
     }

注释参数

  • Route - 名称,isWithParams,isFormHandler
  • Reader - root,successProperty,totalProperty,type
  • Writer - root,type

添加到模板

    <script type="text/javascript" src="{{ url('ExtDirectBundle_api')}}"></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

您可以稍作不同,将getQuery()(AbstractQuery)的结果传递给DirectBundle

控制器(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
}

因此,可以发送数据数组中的任何键作为方法的参数。

附加参数

存在几个可能的参数

  • 请求 $request - 用于此请求的Symfony\Component\HttpFoundation\Request对象的原件;
  • $_data - 所有发送参数的原数组;
  • $_list - 与$_data相同的数组,但用于批量处理,例如更改网格中的几行,$_list将包含多个$_data的数组。
事件

可以添加事件处理。目前,handler Ext\DirectBundle\Response\AbstractQuery支持:PRE_QUERY_EXECUTE和POST_QUERY_EXECUTE,而基于ot 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);
  }
}
处理表单提交和从表单返回错误

让我们考虑处理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
  }]
});
提交请求(POST)示例
country_id  5
extAction  AcmeDemo_Demo
extMethod	createCustomer
extTID	11
extType	rpc
extUpload	false
id	
name	Admin
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->all());
      $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(),以处理表单。在示例中,表单被定义为服务。这是操作[Transformers](https://symfony.com.cn/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->all()));
    
            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控制器的异常。示例

控制器(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,消息为:内部服务器错误。

开发

测试

composer.phar install
phpunit