Expose Models

Bancha allows you to expose any CakePHP model to the Ext JS/Sencha Touch frontend. To expose a model and define the public CRUD methods follow these steps.

CakePHP Steps:

  1. Add the following code to the model you want to expose:
    public $actsAs = array("Bancha.BanchaRemotable");
    
  2. Setup your model's controller as described below.

Setup your Controller for Bancha

You have two ways to setup your controller, either use CakePHP's bake console or manually adjust your controller. Both options are described in the following.

Bancha will expose any defined and public CRUD method. These functions will not need to be marked with @banchaRemotable. If you have any access restriction you should handle this using the AuthComponent or inside the function. This exactly like with normal CakePHP CRUD methods.

The easy way: Baking your Controller

The easiest way is to just bake a controller using the CakePHP bake
console with Banchas controller template:

Use the Bancha Bake Console to bake your controller

For more details see the CakePHP documentation on code generation.

Augment existing Controllers

The changes to your controller are actually quite easy. You only need to provide a return value for every controller method and make sure the method never redirects.

Below you can see how the default scaffolded controller for the model “User” looks like (without php docs). The modified parts are highlighted with an /*--> */ at the beginning of the line.

<?php
class UsersController extends AppController {

    public function index() {
        $this->User->recursive = 0;
/*--> */    $users = $this->paginate();   // added
/*--> */    $this->set('users', $users);  // modified, original $this->set('users', $this->paginate());
/*--> */    return array_merge($this->request['paging']['User'],array('records'=>$users));  // added
    }

    public function view($id = null) {
        $this->User->id = $id;
        if (!$this->User->exists()) {
            throw new NotFoundException(__('Invalid user'));
        }
        $this->set('user', $this->User->read(null, $id));
/*--> */    return $this->User->data;  // added
    }

    public function add() {
        if ($this->request->is('post')) {
            $this->User->create();

/*--> */        if(isset($this->request->params['isBancha']) && $this->request->params['isBancha']) return $this->User->saveFieldsAndReturn($this->request->data);  // added

            if ($this->User->save($this->request->data)) {
                $this->Session->setFlash(__('The user has been saved'));
                $this->redirect(array('action' => 'index'));
            } else {
                $this->Session->setFlash(__('The user could not be saved. Please, try again.'));
            }
        }
    }

    public function edit($id = null) {
        $this->User->id = $id;
        if (!$this->User->exists()) {
            throw new NotFoundException(__('Invalid user'));
        }

/*--> */    if(isset($this->request->params['isBancha']) && $this->request->params['isBancha']) return $this->User->saveFieldsAndReturn($this->request->data);  // added

        if ($this->request->is('post') || $this->request->is('put')) {
            if ($this->User->save($this->request->data['0']['data'])) {
                $this->Session->setFlash(__('The user has been saved'));
                $this->redirect(array('action' => 'index'));
            } else {
                $this->Session->setFlash(__('The user could not be saved. Please, try again.'));
            }
        } else {
            $this->request->data = $this->User->read(null, $id);
        }
    }

    public function delete($id = null) {
        if (!$this->request->is('post')) {
            throw new MethodNotAllowedException();
        }
        $this->User->id = $id;
        if (!$this->User->exists()) {
            throw new NotFoundException(__('Invalid user'));
        }

/*--> */    if(isset($this->request->params['isBancha']) && $this->request->params['isBancha']) return $this->User->deleteAndReturn();  // added

        if ($this->User->delete()) {
            $this->Session->setFlash(__('User deleted'));
            $this->redirect(array('action'=>'index'));
        }
        $this->Session->setFlash(__('User was not deleted'));
        $this->redirect(array('action' => 'index'));
    }
}

You can see the changes are relatively small and have two aims:

  • always return a result value
  • don’t execute $this->setFlash() and $this->redirect()

Constraints

Currently Bancha has one contraint on your exposed models. The models primary’s key must be called id (cake default).

Using methods with and without Bancha

You might want to use the same method in normal CakePHP requests (e.g. your frontend website) and in Bancha requests (e.g. your backend management area). This is perfectly possible, simply set view data for CakePHP requests (Bancha will ignore those) and provide a return value for Bancha requests.

If you want some specific behavior for Bancha, you can check for the isBancha flag on the request object:

if(isset($this->request->params['isBancha']) && 
	$this->request->params['isBancha']) {
	// Bancha specific behavior...
} else {
	// non-Bancha specific behavior...
}

Exceptions

In can happen that your CakePHP controller method throws an exception. You should not use exceptions as a way of messaging, instead use the success property to tell Sencha Touch/Ext JS that e.g. saving was not completed. Exceptions are only for debugging and handling of unexpected cases. It will always trigger the exception handling on the client side.

If CakePHP is in debug mode (debug level is 2), Bancha will output the whole exception including the exeption type and message. The default Bancha debug error handler will then show an Ext.Msg.alert with the thrown exception.

In production the server sends that an exception occured, but to protect the server against hackers no information about the exception is send. In production mode Bancha will also log the exception to the CakePHP error logs. You can disable this by adding Configure::write('Bancha.logExceptions', false); to your app/Config/core.php.

Keep in mind that the next time a server-request is generated Sencha Touch/Ext JS will try to re-transmit the failed request.

File Handling

Bancha also allows you to upload files. Since this is not possible with an XHR request Bancha requires the CakePHP method to also be marked with @formHandler. These methods can be used in forms as submit functions and will support uploading of files.

The uploaded file is available in the global $_FILES variable under the fieldname. A example usage can be found here:
Bancha Example code on GiHub

Redirects

A Bancha request will execute a CakePHP controller method and then transform the result value into a Ext JS/Sencha Touch response.

Since we require an immediate response there is now consistent way of handling redirects. Bancha requests should never execute an redirect, therefore if a redirect would happen, a BanchaRedirectException is thrown instead. In some cases you will need a redirect for normal CakePHP requests. The easiest way to handle this is to add conditional and provide an alternative behavior for Bancha requests:

if(isset($this->request->params['isBancha']) &&
	$this->request->params['isBancha']) {
	// handle Bancha request
	return $this->MyModel->computeSomeResult();
} else {
	// redirect CakePHP request
	$this->redirect(array('action' => 'index'));
}

Comments

Add a comment