Tag: cakephp 1.3

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.

Make your CakePHP app ridiculously faster with “view caching”

Big or small, once your app goes to production you’d better start thinking about utilizing view caching ;)

Granted, view caching in a dynamic application can be tricky or impossible(?)…
but with proper usage wherever and whenever needed, it can make your app go off the charts in terms of performance.
If you are not familiar with view caching at all, you should read the CakePHP manual… yet I will give you some basic examples and couple of things that may not be so well known.

1. Enable view caching for 10 minutes for the main (index) page

public function index() {
$this->helpers[] = 'Cache';
$this->cacheAction = '10 minutes';
}

In this little snippet we’ll add the Cache helper, only when required by the given action. No need to load for the entire controller. (Once the page is “visited” for the first time, the given view will be cached… you can see the evidence of this in your app/tmp/cache/views directory).

2. Be mindful of what you are caching, certain things should remain dynamic

Whenever there is dynamic content within the the view, such as a div, which is updated by AJAX, for example, you’d want to utilize the cake:nocache tags, like so:

<cake:nocache><?php echo microtime(); ?></cake:nocache>

… but there are a few “gotchas” to be aware of…

a. Callbacks such as beforeRender() or beforeFilter() do not fire by default.

For larger scale apps this nearly kills the ability to use view caching.
However, not all is lost… In your controller you can setup a property to ensure that callbacks do run for certain actions where they are truly required (as shown in CakePHP manual):

public $cacheAction = array(
	'view' => array('callbacks' => true, 'duration' => 21600),
	'add' => array('callbacks' => true, 'duration' => 36000),
	'index'  => array('callbacks' => true, 'duration' => 48000)
);

Additional option proposed by jrbasso, is to fire-up the necessary logic by adding the following snippet to the layout:

<cake:nocache><?php
if (isset($controller) && is_a($controller, 'Controller')) {
  $controller->constructClasses();
  $controller->startupProcess();
}
?></cake:nocache>

Still a little overhead, but much much much faster overall. Both options achieve the same basic goal and I will leave it up to you to see, which one suits your needs better.

b. Spaces

Another excellent catch by mr. jrbasso is that if you have spaces between your cake:nocache and php tags, the “no-caching features” may not work as expected.
So be sure to always place your dynamic PHP code like so:

<cake:nocache><?php
if($dynamic) {
  echo 'This is a timestamp: ' . microtime();
}
?></cake:nocache>

c. Don’t surround your elements with cake:nocache tags.

Your entire element will not be cached by doing this:

<cake:nocache><?php echo $this->element('my_element'); ?></cake:nocache>

Yep, you have to go inside the element and decide what to cache or what not to cache. Which, in some cases makes sense, depends on the element really.
Alternatively, you can cache an entire element using the 'cache' key:

<?php echo $this->element('my_element', array('cache' => array(
   'time' => '+1 day',
   'key' => 'my_cached_element'
))); ?>

Since the element itself can have dynamic content, the 'key' can be used to create different “versions” of the cached element. Of course in the above example it is a static key, but in other cases you could create a dynamic key based on some conditions or criteria, which would make sense for your application.

Real-time updates vs view caching

It goes without saying that once your view is cached any changes to the underlying model will not be reflected until the cache is invalidated. In some cases it is perfectly fine, in others you might want to clear the cache once the model has been updated.
It is quite easily achieved by adding the following to your model:

public function afterSave() {
  clearCache('related_view*');
}

Granted, on the first hit to the page the cache will be rebuilt and some “extra” weight will be put on the application, yet by using this technique you’ll have the benefit of using the cache and real-time reflection of the updated data.

Don’t let APC get in the way

I am not sure if other PHP opcode engines can cause the same trouble, but at least with APC we’ve found that it can conflict with cake’s view caching. It would be a bit too much to go into details of the issue, but if you use both APC and view caching for your application and notice some strange behavior be sure to run APC with stat “ON” (which is the default behavior).

To learn a little more about this, you can take a look here

In summary I might as well throw this out: when comparing framework performance, using examples such as “Hello world!” don’t forget that any framework or application requires caching. As many other things, ensuring high-availability and excellent performance is quite easy in cake, and beyond that be sure to utilize other tools (APC, memcached, etc.), which are just as necessary in any modern web application as a web server itself.

Prevent debug info from messing with your AJAX responses

Update (05/21/2010): Well, looks like it has been removed from the core completely (good riddance).

Just a little tidbit of code that can help you when doing any AJAX communication…

Generally you’ll notice that cake inserts a timestamp with each response, unless debug = 0 (i.e. production mode).
This, of course, is not a desirable thing if you wish to return a proper JSON object, for example.
At least in 1.3 the SQL debug is moved into a separate element so it does not interfere with the output (you’ll need a little more adjustment for 1.2).

Now, we have to fix-up app/webroot/index.php to avoid timestamp output whenever we have an AJAX response.

Towards the end of the file make the following update:

if (Configure::read() > 0) {
  if (!env('HTTP_X_REQUESTED_WITH') === "XMLHttpRequest") {
    echo "<!-- " . round(getMicrotime() - $TIME_START, 4) . "s -->";
  }
}

Problem solved, enjoy ;)

Bye-bye $cakeDebug…

The infamous $cakeDebug variable, which has been around for a long time and managed to annoy a few people in the process, has been replaced in the recent builds of CakePHP 1.3 with an element.

See the ticket here:
http://cakephp.lighthouseapp.com/projects/42648/tickets/35-the-implementation-of-dbo_sourcephp-logging-needs-changed

The two points to take away are:

  1. Automatic SQL dumps have been removed from DboSource
  2. If you wish to display SQL dumps, as before… replace any occurrence of $cakeDebug in your layouts with echo $this->element('sql_dump');

One of the obvious benefits, is that you no longer have to tweak the core to take control of the SQL debug. As always, simply place sql_dump.ctp in your own app (i.e. app/views/elements) and do what you wish with the output.

Just to test things out let’s throw the SQL debug into the Firebug console, rather than directly into the view.

We’ll need to modify the sql_dump.ctp (which now has been copied into our own app), just a little bit.

Around line 35, let’s replace the default output for SQL dump, for something like this:

//unchanged part of the sql_dump.ctp above this line

//here we are outputting the queries into the Firebug console
foreach($logs as $source => $logInfo) {
  foreach($logInfo['log'] as $key => $query) {
    echo $this->Html->scriptBlock('console.log("' . $query['query'] . '")');
  }
}

For the real world example, this may not be a reasonable thing to do by any means… but it does show how easy it is now to output the SQL debug wherever you need it (logging, debugging, stylizing, parsing, sending to some remote destination… up to your imagination really).

Improved form handling in CakePHP 1.3

Here is a typical, simple form done with cake’s form helpers:

echo $this->Form->create('Article', array('action' => 'test'));

echo $this->Form->input('Article.title');
echo $this->Form->input('Article.body');
echo $this->Form->input('Article.user_id', array('type' => 'hidden'));

echo $this->Form->end('Add Aricle with Tags and Comment');

Which outputs the following HTML:

<form id="ArticleTestForm" method="post" action="/articles/test" accept-charset="utf-8">
    <fieldset style="display:none;">
        <input type="hidden" name="_method" value="POST" />
    </fieldset>
    <div class="input text">
        <label for="ArticleTitle">
            Title
        </label>
        <input name="data[Article][title]" type="text" maxlength="50" value="" id="ArticleTitle" />
    </div>
    <div class="input textarea">
        <label for="ArticleBody">
            Body
        </label>
        <textarea name="data[Article][body]" cols="30" rows="6" id="ArticleBody">
        </textarea>
    </div>
    <input type="hidden" name="data[Article][user_id]" value="" id="ArticleUserId" />
    <div class="submit">
        <input type="submit" value="Add Aricle with Tags and Comment" />
    </div>
</form>

This is fine and all, but one thing worth noting is that default behavior is wrapping each element in a div, and in some cases this may not be desirable. Either a legacy CSS or a front-end developer preference, might require the form to be structured (wrapped) differently.

Let’s see how we can set some defaults for our form with CakePHP 1.3., while simplifying the code a little at the same time by using the inputs() method…

 echo $this->Form->create('Article', array('action' => 'test',
                                              'inputDefaults' => array(
                                                'div' => array('tag' => 'p'),
                                                'before' => '-- goes before the label --',
                                                'after' => '-- goes after the input --')));

echo $this->Form->inputs(array('Article.title',
                                               'Article.body',
                                               'Article.user_id' => array('type' => 'hidden')),
                                      array('legend' => 'Article submit'));

echo $this->Form->end('Add Aricle');

Which produces:

<form id="ArticleTestForm" method="post" action="/articles/test" accept-charset="utf-8">
    <fieldset style="display:none;">
        <input type="hidden" name="_method" value="POST" />
    </fieldset>
    <fieldset>
        <legend>
            New Article
        </legend>
        <p class="input text">
            -- goes before the label --
            <label for="ArticleTitle">
                Title
            </label>
            <input name="data[Article][title]" type="text" maxlength="50" value="" id="ArticleTitle" />
           -- goes after the input --
        </p>
        <p class="input textarea">
            -- goes before the label --
            <label for="ArticleBody">
                Body
            </label>
            <textarea name="data[Article][body]" cols="30" rows="6" id="ArticleBody">
            </textarea>
           -- goes after the input --
        </p>
        <input type="hidden" name="data[Article][user_id]" value="" id="ArticleUserId" />
    </fieldset>
    <div class="submit">
        <input type="submit" value="Add Aricle" />
    </div>
</form>

Of course for a simple form this may not be a very significant improvement, but having the ability to set defaults for the entire form and relying on some automagic for the rest, certainly makes one’s life easier.

CakePHP 1.3 helps with team-based development workflow…

I do have to say that in 1.2 some of the features described here are available as well, however CakePHP 1.3 takes it slightly to the next level to make team-based development even easier… With SVN or Git it is already easy enough to make team-based development readily accessible, that being said DB (schema and data) management during development process has always been a little bit painful.

Without too much dwelling let’s consider the following scenario. We have our favorite developers Joe and Bob.
(By the way, we are not going to cover any specific version control system here).

Bob starts up the project.
1. He creates the relevant Database with tables, adds a few models, controllers, actions, views.
2. He writes some code and populates the DB in the process with some dummy data.

So far, this should be familiar to most. Generally speaking, once Bob is done with the initial logical point of setup, he would commit all his work into some version control system.

Here’s where the pain of the past came into play…

While Joe can easily grab the application files, which are needed for the project, he is missing the DB structure and some data to continue working on the project.
Since early versions of CakePHP we’ve had a few built-in and third-party tools to help with that process, but they were lacking something… in one way or another. Not to say they didn’t get the job done, just the overall package wasn’t quite there.

Let’s see what to do in cake 1.3..

Bob fires up the command line and…

#cake schema generate

… which creates a php-based, cake-like schema.php file, which holds the information about the table structure.

Next, Bob types…

#cake bake fixture all -records -count 15

(Thanks Mark Story!)

This will bake all fixtures based on the current DB structure… and populate them with maximum of 15 records, which are currently in the database tables.
Fixtures are “nice little things”, but as far as we are concerned here, they simply hold our test data, so that Joe can have easy access to it.

OK, now that Bob was nice enough to do all of the above. He can happily commit all of the application code (including our newly baked schema and fixtures).

Moving on, at this point Joe is ready to get started on the project. He checks out the initial code from the version control repository.

Once the code is on his local development machine, Joe runs:

#cake schema create

Which, as you might have guessed, crates all the tables based on the schema developed and committed by Bob.
(It goes without saying that appropriate CakePHP and ultimately database configuration should already be in place on Joe’s development box).

Now that the tables are created Joe can begin his work, and while it is important to remember the “empty state” of an application, more often than not, some data is needed to get things rolling.

Well, all Joe needs to do at this point is:

#cake fixtures

Which, loads all the data from the fixtures into the newly created tables in Joe’s local database. Nice, eh?
At this point both development environments are fully in-sync and the progress can continue.
The above feature is currently in development and will not be available (or might change) depending on when you are reading this post.

A few things to note…

  1. I rarely find it useful to attempt to alter my schema. If the latest check-in has the required data and updated schema, I can safely drop all the tables, recreate them and insert new data
  2. To address the point above, all of my schema files are in the version control system, so if for whatever reason I need to go back, I simply check out the required revision and rebuild the schema (and add data if needed).
  3. This process can be followed back and forth for as many iterations as necessary.
  4. Human connection is necessary. If there is conflict in the code or schema… check with your fellow developer Bob or Joe and come to an agreement over a beer.

An intro look at jQuery Tools, enhancement for setFlash() and CakePHP 1.3

Actually the main focus of this post will be how to creatively use JavaScript and Session::setFlash() to make your app a little more sexy.
(Slightly tweaked to use CakePHP 1.3, although there is nothing special here that you couldn’t do with other versions of cake).

First, I wanted to point out an awesome little library, called jQuery Tools, which comes with a set of really nice widgets, a whopping 5KB in size and its own CDN for easy loading.

To proceed further let’s consider an Articles Controller, where we are adding a new article.

This is something that you should’ve seen at least a few hundred times…

<?php
class ArticlesController extends AppController {
 public function add() {
    if (!empty($this->data)) {
        if($this->Article->save($this->data)) {
          $this->Session->setFlash('Article saved!', 'modal', array('class' => 'modal success'));
        } else {
          $this->Session->setFlash('Article was not saved!', 'modal', array('class' => 'modal problem'));
        }
     }
  }
}
?>

Here we are using a pretty standard approach to write a message to the session by using $this->Session->setFlash().
What usually happens then, is a redirect to some other place where the message is displayed.
In our case the message will be displayed in the same view, using a nice-looking modal/dialog.
Another point to note, is that once the message is displayed to the user, we’ll automatically hide it (let’s say after three seconds).
To further extend the app you might consider disabling (or converting to plain-text) the form fields, and possibly displaying an “Edit” link… though these features are brewing in my app, I thought it would be too much to cover in one example.

Alright, now that we are looking at our familiar add() action, let’s see what tweaks we should do achieve the desired goals listed above.

In our layout, we’ll need to load both the jQuery and jQuery Tools libraries.

This is a snippet to do so using the existing CDN’s (and in CakePHP 1.3 style):

<?php echo $this->Html->script(array('https://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js',
                                         'http://cdn.jquerytools.org/1.1.2/jquery.tools.min.js')); ?>

$this->Html->script() replaced CakePHP 1.2 approach of using $javascript->link(), the rest should be pretty obvious.

Next, let’s consider the view for our add() action:

<?php $this->Session->flash(); ?>

<?php
    echo $this->Form->create();

    echo $this->Form->input('Article.title');
    echo $this->Form->input('Article.body');

    echo $this->Form->end('Add Aricle');

?>

One thing you’ll notice is the way helpers are referenced in CakePHP 1.3… while $form->input() still works, the approach above is “safer” and more consistent. (Take a look here for more details).

Let’s get back to this line of code for a second…

$this->Session->setFlash('Article saved!', 'modal', array('class' => 'modal success'));

The main difference in CakePHP 1.3, is that modal, the second param, represents an element (unlike a layout in previous versions).
Therefore for this setup to work properly you’ll need to create app/views/elements/modal.ctp

Let’s see what it looks like:

<div id="flash-message" class="<?php echo $class; ?>"> <?php echo $message; ?> </div>

$message is replaced by whatever we place in the first argument to the setFlash() method (which happens to be “Article saved!”, in our case).
As you’ve probably guessed $class gets replaced with the appropriate value from the array of passed params.

Now, if we have successfully saved the article the add.ctp view gets re-displayed with our message showing up and the element rendered. (This all happens automagically by using $this->Session->flash()).
This could be already fine in itself, but it’s just not sexy enough… so we add a little jQuery and jQuery Tools magic to make things a little more fun.

$(document).ready(function() {

   //with this approach you can assign the jQuery Tools API to be used later
  //otherwise you could use a standard one-liner:
  //$('#flash-message').expose().load();
   var flashMessage = $('#flash-message').expose({api: true});

   //now that we have a direct access to the API through the "flashMessage" object
   //we can call the jQuery Tools methods
   //such as load(), which is required to show/expose our modal
   flashMessage.load();

   //now that the modal is displayed we can create a simple way to automatically
   //remove it after 3 seconds
   flashMessage.onLoad(function() {
     setTimeout(function() {
          flashMessage.close();
          $('#flash-message').slideUp(500);
      }, 3000);
   });

});

By including this script we will achieve exactly what our goals are. Display the message in a nice modal/overlay fashion, wait 3 seconds and then auto-hide the message from the user and make our page act as before again.
If you care, please take a look at the comments in the code, to see some nice features of jQuery Tools, i.e. the ability to access any tool’s API. (Please refer to jQuery Tools site for more details).
Overall, we are just relying on the jQuery goodness and overriding our boring flash() behavior, with something quite a bit more interesting.

Well, there you have it, aimed with these tools you are ready to bake sexy CakePHP 1.3 apps ;)

P.S.

Just a little CSS for the modal:

.modal {
   width: 350px;
   padding: 60px;
   margin: 50px 50px;
  }

  .success {
    border: 1px solid #004000;
  }

  .problem {
    border: 1px solid #800000;
  }

Top 10 things to look forward to in CakePHP 1.3

(Update: 8/26/2011… and a few years later we can look forward to CakePHP 2.0).

With CakePHP 1.3 release is just about on the horizon, there are a few developments that I am particularly excited about.
There are some great summaries and previews at code.cakephp.org, but still a few things are somewhat scattered, so here’s my attempt to better highlight some goodies in 1.3. Forgive shameless copy/paste in some parts, but really it goes without saying that all the credit goes to the developers for baking yet another awesome cake.

Maybe, the proper title should be “Top 10 things I’m looking forward to”… but either way, I hope you’ll find something useful and if there are some interesting features/updates that I’ve not included, please do share them in the comments.

So, without “further dudes”…

10. Missing behaviors trigger an error

Using behaviors that do not exist, now triggers a cakeError making missing behaviors easier to find and fix.

9. GROUP BY support in the Containable behavior

Finally, we should be able to do something along the lines of:

'contain' => array(
    'Answer' => array(
        'fields' => array('Answer.value', 'COUNT(*) AS count'),
        'group' => array('Answer.value')
    )
)

Which really makes reporting across multiple models much easier and any previously required hackery is no longer needed.

8. “Missing” model methods will trigger a warning

In the past, when Model::someMethod() was not found, the method itself was attempted to be executed as a query. Now, we should see a warning that such method isn’t there. This does affect some existing code, but is a sound improvement overall.

7. Support for engine type in MySQL DB

The new schema shell will properly recognize the engine type used by your tables (for example MyISAM vs InnoDB).
This was a bit of a pain in the “old days” when running a new schema would always default to MyISAM.

6. Helpers are now referenced by $this->HelperName

This is actually a very nice improvement because it ensures that your helper object will not conflict with some random variable from your view.
For example, if you have an ImageHelper and you also had an $image variable, in the old days the collision between the two would create major problems, especially because the conflict can happen completely unknowingly to the user (consider an app with lots and lots of views). Such an error was too easy to make.

5. Updates to the Form helper

What could be better than the lovely input(s) methods of the Form helper?
How about the suggestions outlined in this ticket

To summarize, we should have greater control over the form element placement therefore having much more flexibility in the way the forms are laid out.

4. Error logging with debug at zero

This has been a long-requested feature and it is finally making it’s way into 1.3
You can use Configure::write(‘log’, $val), to control which errors are logged when debug is off. By default all errors are logged.
The $val is a standard PHP constant such as E_WARNING.

3. New fixture task

Baking fixtures is now another tasty piece of cake. One of the lovely feature is that it properly handles UUID’s.

2. Sweet prefix routing
The old admin routing is gone in the way of proper prefix routing, so if you wish to have:
www.example.com/user/profiles/edit
www.example.com/admin/users/delete
www.example.com/editor/articles/publish

It is now easily accomplished with a simple setup in the core.php:

Configure::write('Routing.prefixes', array('admin', 'editor', 'user));

Of course, you’ll need appropriate “prefixed” action such as editor_publish() in your relevant controllers.

1. Brand new way to handle Javascript and Ajax

This is probably the most anticipated and welcomed change in 1.3. One of the great things about CakePHP has always been the fact that it did not depend on any other libraries or what have you … (well other than PHP). In the old days that paradigm was somewhat “broken” because Ajax helper required the use of the Prototype JS library.
It wasn’t necessarily a huge problem, but for people who preferred to work with other JS frameworks… the helpers were of little help and mostly dead weight in the framework.
Not only that, the the new JS helper and improved HTML helper solve a lot of other little problems, which make CakePHP 1.3 something to really look forward to.

Well… it’s no worth repeating what’s already said at 1.3 wiki regarding the new helpers.

Go cake!