Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android RESTful Web application using Zend Framework

I have written a web application which is based on the Zend Framework (Version 1.11.11) and I want to use the SAME backend code for coding the mobile version of this application (Android). To achieve this, I want to get the response for each of the actions in the controllers in XML and JSON - for mobile-based app.

But the problem I am facing is:

Each of the actions in my controllers will return a view variable which will then be interpreted by the view script. But I want each of the actions to return a JSON array in case of a mobile application and the regular/usual thing (view variables) for the browser based web application.

Can anyone of you give me an example of how it can be achieved for a loginAction() in UsersController.

The URL would look like:

http://{servername}/service/login

To do this, I want some insight and advice on how to do it in the most efficient and CORRECT way. I googled for answers but I did not find any good code samples or implementation samples on how to achieve this. I appreciate any help and guidance.

The way I have done it is: Have an API which is called with parameters which would parse the call and then off load it to the controller. But unsuccessful in coding it.

The code which I have until now:

A UserController with loginAction() (for users logging in):

According to me, I should be using the same logic or rather the same function as the loginAction in UsersController (for web-based and mobile-based app) as follows:

public function loginAction()
  {
// Already logged in
if( Engine_Api::_()->user()->getViewer()->getIdentity() ) {
  $this->view->status = false;
  $this->view->error = Zend_Registry::get('Zend_Translate')->_('You are already signed in.');
  if( null === $this->_helper->contextSwitch->getCurrentContext() ) {
    $this->_helper->redirector->gotoRoute(array(), 'default', true);
  }
  return;
}

// Make form
$this->view->form = $form = new User_Form_Login();
$form->setAction($this->view->url(array('return_url' => null)));
$form->populate(array(
  'return_url' => $this->_getParam('return_url'),
));

// Render
$this->_helper->content
    //->setNoRender()
    ->setEnabled()
    ;

// Not a post
if( !$this->getRequest()->isPost() ) {
  $this->view->status = false;
  $this->view->error = Zend_Registry::get('Zend_Translate')->_('No action taken');
  return;
}

// Form not valid
if( !$form->isValid($this->getRequest()->getPost()) ) {
  $this->view->status = false;
  $this->view->error = Zend_Registry::get('Zend_Translate')->_('Invalid data');
  return;
}

// Check login creds
extract($form->getValues()); // $email, $password, $remember
$user_table = Engine_Api::_()->getDbtable('users', 'user');
$user_select = $user_table->select()
  ->where('email = ?', $email);          // If post exists
$user = $user_table->fetchRow($user_select);

// Get ip address
$db = Engine_Db_Table::getDefaultAdapter();
$ipObj = new Engine_IP();
$ipExpr = new Zend_Db_Expr($db->quoteInto('UNHEX(?)', bin2hex($ipObj->toBinary())));

// Check if user exists
if( empty($user) ) {
  $this->view->status = false;
  $this->view->error = Zend_Registry::get('Zend_Translate')->_('No record of a member with that email was found.');
  $form->addError(Zend_Registry::get('Zend_Translate')->_('No record of a member with that email was found.'));

// Code
  return;
}

// Check if user is verified and enabled
if( !$user->enabled ) {
  if( !$user->verified ) {

   // Code here.
    // End Version 3 authentication

  } else {
    $form->addError('There appears to be a problem logging in. Please reset your password with the Forgot Password link.');

   // Code

    return;
  }
} else { // Normal authentication
  $authResult = Engine_Api::_()->user()->authenticate($email, $password);
  $authCode = $authResult->getCode();
  Engine_Api::_()->user()->setViewer();

  if( $authCode != Zend_Auth_Result::SUCCESS ) {
    $this->view->status = false;
    $this->view->error = Zend_Registry::get('Zend_Translate')->_('Invalid credentials');
    $form->addError(Zend_Registry::get('Zend_Translate')->_('Invalid credentials supplied'));

   //Code
    return;
  }
}

// -- Success! --

// Register login
$loginTable = Engine_Api::_()->getDbtable('logins', 'user');
$loginTable->insert(array(
  'user_id' => $user->getIdentity(),
  'email' => $email,
  'ip' => $ipExpr,
  'timestamp' => new Zend_Db_Expr('NOW()'),
  'state' => 'success',
  'active' => true,
));
$_SESSION['login_id'] = $login_id = $loginTable->getAdapter()->lastInsertId();
$_SESSION['user_id'] = $user->getIdentity();

// Some code.

// Do redirection only if normal context
if( null === $this->_helper->contextSwitch->getCurrentContext() ) {
  // Redirect by form
  $uri = $form->getValue('return_url');
  if( $uri ) {
    if( substr($uri, 0, 3) == '64-' ) {
      $uri = base64_decode(substr($uri, 3));
    }
    if($viewer->is_vendor) {
        return $this->_helper->redirector->gotoRoute(array('module' => 'user' ,'controller' => 'vendors', 'action' => 'mydeals'), 'vendor_mydeals', true);
    } else {
        return $this->_helper->redirector->gotoRoute(array('action' => 'index'), 'user_searchquery', true);
    }
    //return $this->_redirect($uri, array('prependBase' => false));
  }

  return $this->_helper->redirector->gotoRoute(array('action' => 'index'), 'user_searchquery', true);
}

}

So I want to use the above loginAction() even for mobile based application.

Next, I have a class called Service_Api with a variety of functions. Below is a function I have now to get user based on id.

private function getUser(array $params)
{
    $userData = array();
    $usersTable = Engine_Api::_()->getDbtable('users', 'user'); 
    $select = $usersTable->select()->where('user_id = ?', $params['user']);

    $user = $usersTable->findOne($params['user']);
    if($user) {
        $userData = $user->exportToArray();
    }

    return Zend_Json_Encoder::encode($userData);
}

Similarly I want to have a loginAction for logging in. How will the loginAction() look and how will I get only JSON vlaues (say user values from db and success/failure for login success/failure) for mobile application.

I want to have a RESTful URL.

So my URLs would look like:

http://{servername}/service/login
http://{servername}/service/groups/list etc.

I have a controller called ServiceController with loginAction as follows:

public function loginAction()
{
    $this->_helper->viewRenderer->setNoRender();
    $this->_helper->layout->disableLayout(true);
    /*
     * Fetch Parameters and Parameter Keys
     * We don't need the controller or action!
     */
    $params = $this->_getAllParams();
    unset($params['controller']);
    unset($params['action']);
    unset($params['module']);
    unset($params['rewrite']);
    $paramKeys = array_keys($params);

    /*
     * Whitelist filter the Parameters
     */
    Zend_Loader::loadClass('Zend_Filter_Input');
    $filterParams = new Zend_Filter_Input($params);

    /*
     * Build a request array, with method name to call
     * on handler class for REST server indexed with
     * 'method' key.
     *
     * Method name is constructed based on valid parameters.
     */
    $paramKeysUc = array();
    foreach($paramKeys as $key)
    {
        $paramKeysUc[] = ucfirst($key);
    }

    $methodName = 'getBy' . implode('', $paramKeysUc);
    $request = array(
        'method'=>$methodName   
    );

    /*
     * Filter parameters as needed and add them all to the
     * $request array if valid.
     */
    foreach($paramKeys as $key)
    {
        switch($key)
        {
            case'tag':
                $request[$key] = $filterParams->testAlnum($key);
                break;
            default:
                $request[$key] = $params[$key];
        }
        if(!$request[$key])
        {
            // need better handling of filter errors for a real webservice…
            throw new Exception($request[$key] . ' contained invalid data');
        }
    }

    /*
     * Setup Zend_Rest_Server
     */
    require_once 'Zend/Rest/Server.php';

    $server = new Zend_Rest_Server;
    $server->setClass('Service_API');
    echo $server->handle($request);
}

But this is using a separate controller action.

Any help is appreciated.

Thanks. Abhilash

like image 966
Abhilash Chiganmi Avatar asked Mar 27 '12 00:03

Abhilash Chiganmi


1 Answers

Disabling layouts works for JSON, but it doesn't allow you to redirect the request to the good controller according to the format requested (XML, JSON, etc.).

From there, how to decides what actions to call according to the requested format?

Ajax Context

Use AjaxContext in your controller _init() method:

$ajaxContext = $this->_helper->getHelper('AjaxContext');
$ajaxContext->addActionContext('login', 'json')
            ->addActionContext('login', 'xml')
            ->initContext();

This will have for effect to redirect your XML request to the same action that your JSON request.

How to make tell which format should be used? Simply add ?format=xml or /format/xml (or json) to the URL parameters. You URL would rather look like this: http://{servername}/service/login/format/json.

From your action, how to know which format has been requested? You don't have anything to do, AjaxContext takes care of everything already.

In case of a JSON request:

JSON. The JSON context sets the 'Content-Type' response header to 'application/json', and the view script suffix to 'json.phtml'.

By default, however, no view script is required. It will simply serialize all view variables, and emit the JSON response immediately.

In case of an XML request:

Change the view suffix to 'xml.phtml' (or, if you use an alternate view suffix, 'xml.[your suffix]').

Note that using AjaxContext, response headers are automatically going to be set according to the response format requested.

Aware of that, you shouldn't need to use Zend_Json_Encoder anymore.

If you want to know more about RESTful API, I've read a very interesting ppt slide written by Matthew Weier O'Phinney (currently Project Lead of ZF), I definitely recommended it.

One more thing, your application doesn't seem to respect the Skinny controller and Fat model convention recommended by Zend Framework, I believed that if you're following this principle it would make things way more clearer to you. And also, your loginAction() would only get a success or failure message from your model, which would be easy to convert to JSON or XML using the method I described above.

RESTful API

In order to know if the request is a GET request or a POST request, use these methods in your controllers:

  • $this->_getAllParams(); or $this->getRequest()->getParams();` will catch all parameters, POST and GET.
  • $this->getRequest()->getPost() retrieves POST parameters.
  • $this->getRequest()->getQuery() retrieves GET parameters.

And to determine the request type, you can uses these methods:

  • isGet()
  • isPost()
  • isPut()
  • isDelete()

More information here in the manual.

like image 58
Liyali Avatar answered Oct 20 '22 04:10

Liyali