gold stars

Bill Hunt Gold Star

Bag o' Tricks

posted by: Nate :: Jan 27th 2007, 14:24

Lately I've been posting quite a bit on new major features of Cake 1.2. However, many of the new features that have gone and are continuing to go into 1.2 are smaller enhancements that, while they aren't earth-shattering, can still collectively improve your coding experience just as much as those that are.

  • 'escape' and 'confirm' keys for link() options - Creating links with HtmlHelper::link() or AjaxHelper::link() that use images has been somewhat of a pain, because of lengthy method definitions which require you to specify $escapeTitle at the very end of the parameter list.

    Now, in both these methods as well as any that wrap them (i.e. Paginator), you can specify these keys, which will override the corresponding parameters.

    Example:
    e($html->link( $html->image("pencil.gif"), array('action' => 'edit'), array(), null, false ));
    becomes:
    e($html->link($html->image("pencil.gif"), array('action' => 'edit'), array('escape' => false)));

  • JavascriptHelper::link() and HtmlHelper::css() now accept arrays of file names - Example:

    Instead of:
    echo $javascript->link('prototype');
    echo $javascript->link('event-selectors');

    you can do:
    echo $javascript->link(array('prototype', 'event-selectors'));

    Same with HtmlHelper::css():
    echo $html->css('admin');
    echo $html->css('default');

    becomes:
    echo $html->css(array('admin', 'default'));

  • $scripts_for_layout and script caching - HtmlHelper::css() and JavascriptHelper::link() now both have an $inline parameter, which can be used to automatically insert the rendered tags into the <head /> of a document. Just add: <?php e($scripts_for_layout); ?> to the appropriate section of your view template. It works just like $title_for_layout and $content_for_layout.

    Tagental to this feature is the ability to cache inline script code to an external file. This has been available for JavaScript events for a while now, but has been made available for any JavaScript code you write: <?php $javascript->codeBlock(); ?> // My JavaScript code goes here <?php $javascript->blockEnd(); ?>Just add the following to your view template: <?php $javascript->cacheEvents(true, true); ?> This will cache all JavaScript code to a file. However, if this view is being loaded via Ajax, none of the JavaScript will work, because the external file won't be loaded. What we need to do in this case is disable caching to an external file for Ajax requests only: <?php $javascript->cacheEvents(!$ajax->isAjax(), true); ?> Now, JavaScript will only be cached to a file on non-Ajax requests. On Ajax requests, inline JavaScript will still be cached, but it'll all be written to one giant <script /> block at the end of the view.

  • HtmlHelper::meta() -This is a new method which was added as part of the new feed APIs, to make it easy to link feeds to pages. For example, let's say you've already added an RSS view template for the index() action of PostsController. You can add the feed link by doing the following: <?php $html->meta('The Feed', '/posts.rss', null, false); ?>

    HtmlHelper::meta() also has an $inline parameter, which is used above to automatically add it to the header of layouts with $scripts_for_layout specified. This method can also be used to wrap <link /> tags for shortcut icons, as follows: <?php e($html->meta('shortcut icon', '/favicon.ico', array('type' => 'icon'))); ?>

  • Components now have a shutdown() callback - Similar to startup(), components can now have a shutdown() method which is triggered immediately before a controller's afterFilter() callback, and works the same way as startup(), in that it receives a reference to the controller as it's only parameter.

    Also new with components is the enabled property. If you set it to false, callbacks will be disabled for that component:function beforeFilter() { $this->RequestHandler->enabled = false; }

  • Data validation with Model::isUnique() - Up till now we haven't really had an efficient way to check the uniqueness of a set of data before inserting a new record. This method accepts a couple of handy syntaxes, including:$valid = $this->User->isUnique(array('email' => 'my@email.server')); Even better, if you've already set some data in the model, you don't have to pass the values to be checked, because they'll be pulled from Model::$data:function beforeValidate() { if ($this->exists()) { return true; } foreach (array('username', 'email') as $field) { if (!$this->isUnique($field)) { $this->invalidate($field); } } return true; } You can even check multiple fields at a time: if (!$this->User->isUnique(array('email', 'username')) { $this->Session->setFlash("This user record is not unique"); } We'll be rolling this into the new validation system soon to make it easier to take advantage of, without having to write callbacks.
  • Persist database defaults to forms with Model::create() - Model::create() will now initialize fields with any non-empty default values you have defined in your database. This can be especially handy for setting defaults in add forms:function add() { if (empty($this->data)) { $this->data = $this->Product->create(); } }

    This method also takes an array parameter which behaves the same as calling Model::set(), which sets the values of the fields after they are initialized. This can be useful for storing valid model data in a session, to be saved on a later page. A good example is a mutli-step registration form: if ($this->User->create($this->data) && $this->User->validates()) { $this->Session->write("UserData", $this->User->data); } else { // ... }

  • Set::extract() - Of all the cool stuff in this framework, the Set class is one of my favorites, simply because it's so darn useful. The extract() method in particular is very handy, and brings us such goodness as Model::generateList(), among other things. When called statically, this method takes two parameters: an array, and a dot-separated string path to the key(s) from which you want to extract the data. The path uses a special {n} symbol to represent a numeric index that extract() will iterate over.

    For a simple demonstration, do an array dump of the metadata of a model: pr($this->Model->loadInfo()); Now try this: pr($this->Model->loadInfo()->extract("{n}.name")); // apologies about the syntax to PHP4 folks

  • Don't forget, Cake 1.2 is still under heavy development, and the magic happens daily: before attempting any of these crazy stunts, always grab the latest nightly build from http://cakephp.org/downloads/index/nightly/1.2.x.x

8 comments

bottom

1. Felix Geisendörfer :: on Jan 29, 2007 Hey nate, thanks for that post. I actually noticed most of this changes in the changset and thought: "somebody has got to blog about those" - seems like you beat me to it ; ).
2. GreyCells :: on Jan 29, 2007 Hi Nate, Thanks for keeping us all up to speed.

I especially like the inline=false functionality of the helpers - it brings one of my two favourite 'addons' into the core where it belongs (HeadHelper + the other being 'model->expects()').

There is a minor typo in the $javascript->link example above - if the 'inline' arg is true (the default if not set) then the 'echo' appears to be required.

In isUnique(), would it be possible for you to include a check for the primary key - i.e. if getId() returns a value, then isUnique() adds 'AND id != getID()' so that it can be re-used for pre update validation?

~GreyCells
3. Nate :: on Jan 30, 2007 Yeah, the inline thing was a long time coming. I was kinda kicking myself for not having gotten it in on our initial 1.0 release.

As far as expects() goes, I've had an idea about what to do with that for a little while. The version that's published on the Bakery is the right idea, but the wrong implementation. But the first step is to rewrite the binding interface.

I like that isUnique enhancement. Open a ticket on it and I'll get it in.
4. GreyCells :: on Feb 01, 2007 Ticket opened as requested (#2032).

Thanks for the help on dbo describe (#2026). I'd discovered the formhelper bit but (wrongly) discounted it as a work-around. Obviously (now...) the form cannot know the data type of a field without a database description.

I look forward to seeing the re-write of the binding interface. Some of my models seem to retrieve absolutely *enormous* relationship maps so they are not all defined in the model as I would like.
5. Huey :: on Feb 06, 2007 Hey Nate, love the blog, chock full of cake goodness.

Anyway, I heard you were the one to talk to about url extensions. Any possibility that you might be able to post some information on it?
6. Nate :: on Feb 09, 2007 Huey:
http://cake.insertdesignhere.com/posts/view/2
You mean like that?

Those features also get a mention towards the end of this presentation:
http://cake.insertdesignhere.com/files/nyphp_presentation.pdf

I also posted a few notes on usage here:
http://cake.insertdesignhere.com/posts/view/8
7. Huey :: on Feb 09, 2007 Yes, my bad, I didn't realize that you had notes on it from so long ago. When I saw mention of it in the 1.2 dev release notes and was told you were the one to talk to I didn't dig deep enough into your blog here to get that information, I only looked at posts since the 1.2 dev release.

I guess the only thing I'm confused about (and this might be a really stupid question, I haven't been playing with cake for too long) I don't understand what you mean when you say add RequestHandler to your controller.

EDIT: nevermind, I found it, finally got to reading the entire manual this weekend. Thanks for the help!
8. Othman Ouahbi :: on Feb 22, 2007 I've tried to do something like $this->Javascript->link(array('files'),false);

in a helper and print $scripts_for_layout in the layout, it looks like it's empty, is it populated before the helpers are loaded in the view class ? noticed something similar with elements.
also caching blocks didn't work for me in helpers.
Regards
EDIT: Nevermind, I called the helper in the layout after $scripts_for_layout so it's normal that it doesn't show, thanks nate for clarifying on irc.

add one

 
 
top