2017 © Pedro Peláez
 

symfony-bundle ext-direct-bundle

ExtDirect implementation for Symfony2

image

ghua/ext-direct-bundle

ExtDirect implementation for Symfony2

  • Tuesday, July 26, 2016
  • by ghua
  • Repository
  • 4 Watchers
  • 17 Stars
  • 2,745 Installations
  • PHP
  • 0 Dependents
  • 0 Suggesters
  • 15 Forks
  • 1 Open issues
  • 7 Versions
  • 0 % Grown

The README.md

DirectBundle

DirectBundle is an implementation of ExtDirect specification for symfony2., (*1)

Build Status, (*2)

Installation

Using composer

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

Register DirectBundle in AppKernel

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

    // ...
    return $bundles;
}

Modify app/config/routing.yml

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

Modify app/config/config.yml

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

Configuration Example

  • error_template - template of validation errors array;
  • resource - routing configuration file, example: 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 }

In additional, you can use a controller annotation:, (*3)

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
     }

Annotation parameters:, (*4)

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

Add to the template

    <script type="text/javascript" src="{{ url('ExtDirectBundle_api')}}"></script>

Add a extdirect provider in your ExtJS application:, (*5)

    Ext.direct.Manager.addProvider(Ext.app.REMOTING_API);

Example of Use

Simple Version

To consider basic example of use, consider the problem of data extraction, for example, to fill the repository (Ext.data.Store)., (*6)

Controller (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;
  }
}
Model and Repository (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
});

Extended Versions

AbstractQuery

You can do a little differently and transfer to DirectBundle the result from getQuery () (AbstractQuery), (*7)

Controller (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 and receipt of parameters

All data indiscriminately is extracted and transferred rarely. Pagination, filtering, sorting are the common tasks., (*8)

Of course, pagination can be implemented independently and DirectBundle is not a trouble. But in my project [KnpPaginator] is used for this task (https://github.com/KnpLabs/KnpPaginatorBundle)., (*9)

Controller (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());
  }
}

Let’s consider carefully the parameters of this method. They are not mandatory, because method call is carried out via preliminary ReflectionMethod::getParameters. This means that if the parameter is defined and it can be sent, it will be sent., (*10)

Addition! AbstractQuery returned from findCustomers should have HydrationMode equal to HYDRATE_ARRAY. This is done by calling setHydrationMode(). method., (*11)

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);
  }
}
Example of request from 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
}

Accordingly, any key from data array can be sent as a parameter of the method., (*12)

Additional Parameters

There are several possible parameters:, (*13)

  • Request $request – original of Symfony\Component\HttpFoundation\Request object, for this request;
  • $_data – all original array of sent parameters;
  • $_list – the same $_data but for batch processing, for example changing several lines in grid, $_list will have an array from several $_data.
Events

It is possible to add event handling. At this moment handler Ext\DirectBundle\Response\AbstractQuery supports: PRE_QUERY_EXECUTE and POST_QUERY_EXECUTE, and based on ot Ext\DirectBundle\Response\KnpPaginator supports only the latter. See additional information on events in the source code of Ext\DirectBundle\Response\AbstractQuery::execute()., (*14)

The example below changes already extracted data before passing them to the network., (*15)

Event Example
<?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);
  }
}
Handling of form submit and return of errors from the form

Let’s consider the task of handling submit from Ext.form.Panel. In code sample for extjs, a window of form displaying and the form itself with the elements is defined., (*16)

Form (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
  }]
});
Example of submit request (POST)
country_id  5
extAction  AcmeDemo_Demo
extMethod   createCustomer
extTID  11
extType rpc
extUpload   false
id  
name    Admin
role_ids[]  3
role_ids[]  1
Conroller (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);
  }
}

Sent parameters except supporting ones will be sent to $_data. This array can be directly passed to $form-> bind(), to handle the form. The form is defined as a service in the example. This is necessary for operation of [Transformers] (http://symfony.com/doc/current/cookbook/form/data_transformers.html)., (*17)

If form validation is successful, the response is sent: success: true., (*18)

[
  {"type":"rpc",
   "tid":"11",
   "action":"AcmeDemo_Demo",
   "method":"createCustomer",
   "result":{"success":true}}
]

In case of errors, you can send a response containing success: false and msg with the text of error., (*19)

[
  {"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>"}}
]
Storage synchronization and return of errors of Validator service

There is a task of storage synchronization; it is associated with a change of several lines at once. Similar task can also be solved using DirectBundle., (*20)

Controller (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);
}

In this example, the errors are specially retrieved from validator service, the response format will be similar to the response of the previous section., (*21)

Exceptions

For assist in the development, router can catch exceptions from symfony2 controller. Example:, (*22)

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:, (*23)

[
    {
     "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., (*24)

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., (*25)

Development

Testing

composer.phar install
phpunit

The Versions

13/02 2014

v2.4.0

2.4.0.0 https://github.com/ghua/DirectBundle

ExtDirect implementation for Symfony2

  Sources   Download

MIT

The Requires

 

by Semyon Velichko

symfony2 bundle extjs extdirect

22/10 2013

v2.0

2.0.0.0 https://github.com/ghua/DirectBundle

ExtDirect implementation for Symfony2

  Sources   Download

MIT

The Requires

 

by Semyon Velichko

symfony2 bundle extjs extdirect