Speed things up with a custom-baked JSON view

Yet another gem of brilliance from Mr. jrbasso.

In a couple of my previous posts regarding CakePHP and JQuery, I’ve shown some basic strategies on setting up the communication between the client and the server.

This post, as one can guess from the title, is going to be more about optimization and making things run smoother and faster while respecting MVC and CakePHP’s methodologies.

Let’s consider a typical scenario… Somewhere in your application’s UI, a user will click an element such as a button, a link or what have ya, which will issue an AJAX request to the server. The response expected by the client (browser) will be a JSON object. (Yep, sounds like this has been done once or twice before…)

In order to respect MVC you’d usually create a controller action to process the AJAX request, perhaps grab some data from the DB and then create a view to output the JSON object.
While this is a proper approach to achieve the task at hand, one can certainly consider the overhead caused by loading the View object, the helpers and other “goodness” that comes with the typical MVC request.
Right away it becomes desirable to just echo out a json_encode()’d string right from your controller. But… whoa… I know that you’d never do something so offensive to the MVC paradigm ;)

Instead, let’s reach a middle ground… we don’t have to break MVC or cake, yet we’ll see how to make our custom JSON view act faster and more robustly.

First, we’ll make up a simple controller action such as the one here:

public function get_json_info() {
    $this->view = 'Json';
    $info = $this->MyModel->getSomeInfo();
    $json = array(
      'variableOne' => $info['some_var'],
      'variableTwo' => $info['another_var']
    );
    $this->set(compact('json'));
  }

As you see this is a totally bogus action, but it works in the same manner as you’d expect many controller actions to work when returning some JSON data.
There are two important things going on here, nonetheless:
1. $this->view = ‘Json'; instructs cake to use our custom JSON view, rather than a standard view class.
2. We’ve created a $json array with some data and set() it to be used by the view.

Now we can finally move on to look at our custom view class.
Create a file called json.php and place it in your app/views/ directory.

This particular implementation comes with a nice, little bonus. If you use Mark Story’s Debug Kit, this custom JSON view will ensure that it is working properly and doesn’t break anything as this tool is quite indispensable for many developers. For those who do not use Debug Kit at all, first you should :), and second you can make the view object even lighter by taking out the Debug Kit specific code (but that is something for your to play with).

Hopefully I didn’t mislead anyone with the title of the post, by making you think that you can “bake” such a view using the console tool. To make up for this, I offer a copy & paste solution instead ;)

Well here’s the little guy in all its glory:

<?php

class JsonView extends View {
  var $content = null;
  var $debugKit = null;

  function __construct(&$controller, $register = true) {
    if (is_object($controller) && isset($controller->viewVars['json'])) {
      if (isset($controller->helpers['DebugKit.Toolbar'])) {
        $this->debugKit = $controller->helpers['DebugKit.Toolbar'];
        parent::__construct($controller, $register);
      }
      $this->content = $controller->viewVars['json'];
    }
    if ($register) {
      ClassRegistry::addObject('view', $this);
    }
    Configure::write('debug', 0);
  }

  function render($action = null, $layout = null, $file = null) {
    if ($this->debugKit !== null) {
      DebugKitDebugger::startTimer('viewRender', __d('debug_kit', 'Rendering View', true));
      $this->loaded = $this->_loadHelpers($this->loaded, array('DebugKit.toolbar' => $this->debugKit, 'DebugKit.simpleGraph', 'html', 'number'));
      $this->_triggerHelpers('beforeRender');
    }

    if ($this->content === null) {
      $data = '';
    } else {
      $data = json_encode($this->content);
    }

    if ($this->debugKit !== null) {
      $this->_triggerHelpers('afterRender');

      DebugKitDebugger::stopTimer('viewRender');
      DebugKitDebugger::stopTimer('controllerRender');
      DebugKitDebugger::setMemoryPoint(__d('debug_kit', 'View render complete', true));

      $backend = $this->loaded['toolbar']->getName();
      $this->loaded['toolbar']->{$backend}->send();
    }

    return $data;
  }
}

As you see, our JsonView is a child of CakePHP’s core View class. It implements and overrides a couple of methods and does so in a much lighter fashion.

Just a few things to point out:

  • $this->content = $controller->viewVars['json'];
  • remember our bogus action where we’ve set() $json var for the view? Well, this is exactly what the view is going to expect to transform into the JSON object.

  • It does so quite easily: $data = json_encode($this->content);

And there you have it… light and speedy, not too greedy ;) Perfectly optimized for JSON output.
Enjoy.

  • Pingback: Tweets that mention Speed things up with a custom-baked JSON view | nuts and bolts of cakephp -- Topsy.com

  • Pingback: Speed things up with a custom-baked JSON view | DEEP in PHP

  • http://www.hitwebdesign.com/careers/ Michael Clark

    It would seem to me that hand crafting the output JSON is less likely to result in information leakage useful to an attacker; though you can limit the fields returned from a CakePHP query (which you should be doing anyway) it becomes easy to just dump the whole response object to the world. This could leak information you didn’t intend to, such as the user’s password field (hashed with secret key, sure, but why risk leaking it?). If you have a boolean admin field and don’t use form protections or pass a whitelist to save() (which you should be doing anyway in both cases), both quite possible if making use of AJAX, an attacker might be able to set that value at will on their own account by injecting an appropriately named field.

  • teknoid

    @Michael Clark

    Very good points, one should be very cautious both on the output of AJAX data as well as saving anything submitted by AJAX.

    Using Security component in conjunction with white-listing of fields would keep your form data much more secure (that being said, in the past Security component and AJAX did not play very well together… hopefully it has been fixed-up in the recent builds of cake).

  • berclausen

    Also, a nice thing that I should do here is setting some headers right after returning $data, for example:

    header(“Pragma: no-cache”);
    header(“Cache-Control: no-store, no-cache, max-age=0, must-revalidate”);
    return $data

  • Eugenio

    Hi, do you know if this view is compatible with the view cache helper?

    Thanks!

  • http://www.afekenholm.se Alexander Wallin

    Thank, man. Saved me an hour or two!

  • fuz

    I update the app_controller.php for extjs with
    public function get_json_info() {
    $this->view = ‘Json';
    $info = $this->{$this->modelClass}->find(‘all’);

    $extJson = array();
    foreach ($info as $object)
    {
    $extJson[] = $object[$this->modelClass];
    }
    $json = array(
    ‘Success’ => true,
    $this->name => $extJson
    );
    $this->set(compact(‘json’));
    }

    function update_json()
    {
    $this->view = ‘json';

    $postdata = file_get_contents(“php://input”);
    $obj = json_decode($postdata);
    $objArray = (array) $obj;
    $saveData = array(“$this->modelClass” => $objArray);

    if($this->User->save($saveData)){
    $json = array(
    ‘Success’ => true,
    $this->name => $objArray
    );
    } else {
    $json = array(‘Success’ => false);
    }
    $this->set(compact(‘json’));
    }

  • fly2279

    If you want to use this with 2.0 you need to change the controller code:

    $this->view = ‘Json'; to $this->viewClass = ‘Json';

    Also, I was getting an error with the debugkit code that it couldn’t find the class DebugKitDebugger on line 23 so I removed it until someone can fix it.

  • fly2279

    Something broke in between 2.0.2 and 2.0.3. Fatal error: Class ‘JsonView’ not found.

    teknoid, do you know what happened that this doesn’t work any more by setting the view like $this->viewClass = ‘Json’ in the controller?

  • Reuben

    @fly2279, Juan Basso is maintaining the JsonView plugin at https://github.com/jrbasso/json_plugin, currently at CakePHP 2.0.X.

  • Tim

    I have a problem with the returned type text/html. I want to have with Cake PHP 2.0 that the type will be application/json. How can I do this?

    Putting
    header(“Content-Type: application/json; charset=utf-8″);
    in front of the return $data; does not work :-(