RSS
 

Archive for March, 2011

All new CakePHP Tips and Tricks

26 Mar

This is supposed to be a list of useful tricks gathered over many months.

First Templates, then Bake, then Adjustments

The usual workflow for a new project should be

This way you speed up your development while having all the advantages of custom templates. Follow the link to read more about the topic.

Don’t sanitize

Sanitizing is not always bad (see later on). But most of the times we don’t have to sanitize every bit of input. Its overhead and usually makes more harm then good. On save to database nothing is needed as Cake properly escapes data itself. What we DO need is some protection in the view. Use h($var) in the views to make sure all potential dangerous strings are now harmless. This is called “escaping”. Note: You would have to do this with any string that gets in contact with the user. So if you use database content in flash messages, you would have to escape before you call setFlash() – or escape the content in the session flash element (but if you want to be able to use HTML output, the second option is not going to work). Alternatively you could use BBCode for flash messages. That would allow you to use h() and any HTML markup together. To sum it up: Try to be less restrictive on the input but still make sure your site is safe. This makes the user happier and keeps the efforts for security as low as possible but as high as necessary.

BAD Example: Firstname/Lastname input fields validated with regexp or [A-Za-zäöüÄÖÜß] We can see that all major German signs are accepted. But what if a French guy wants to sign up. His name might be Aimé. He would be quite frustrated with the website and leave!

GOOD Example: Simply validate the length and escape the name on output. Every possible name is accepted :)

echo h($user['User']['first_name']);

So follow these tips and you will be fine!

Working with dates

I created some date constants in my bootstrap:

define ('DEFAULT_DATE', '0000-00-00');
define ('DEFAULT_TIME', '00:00:00');
define('FORMAT_DB_DATE','Y-m-d');
define('FORMAT_DB_TIME','H:i:s');
define('FORMAT_DB_DATETIME', FORMAT_DB_DATE . ' ' . FORMAT_DB_TIME);
 
# now I can use it everywhere (controller, models, views, ...)
$today = date(FORMAT_DB_DATE);
$yesterday = date(FORMAT_DB_DATE, time()-DAY);
$exactlySevenDaysAgo = date(FORMAT_DB_DATETIME, time()-7*DAY);
A very clean and readable approach. Always use the SECOND, HOUR, DAY .. constants. Note: Everything above WEEK gets fuzzy (MONTH is always 30 days). So for everything from MONTH up you should use the PHP5 DateTime object to correctly add months/years.

Careful with the super-automatic methods

Methods like updateAll() and deleteAll() are a little bit different from the normal save() or find() methods. They accept expressions and therefore don’t automatically escape the content. You should only use them with own “controlled” input or after sanitizing the data thoroughly. Otherwise your SQL queries might break or can even be used in harmful ways against you and your site.

Those methods like updateAll() can be pretty handy or even necessary if you need atomic DB updates. Example:

$this->Model->updateAll(array('Post.view_count' => 'Post.view_count + 1'), array('Post.id' => $post['Post']['id']));
Even if two users trigger this the exact same moment, it will raise the count twice. If you use find() and saveField() you might end up overriding each other and raising it only once. But in this example the input is not from the user and can therefore be considered safe.

Using debug mode 1

Right now you can usually switch between debug mode 0 (no debug) and 2 (3 is not used anymore). With debug mode 2 you usually display the debug bar or debug plugin etc containing the SQL queries and other stuff. My idea quite some time ago: Why not using 1 as well? 1 could mean debug output is on but no debug bar is displayed. It simply triggers all debug warnings/errors. Simply make sure that your debug level is > 1 at the bottom of your layout:

$debug = (int)Configure::read('debug');
if ($debug > 1 && Configure::read('Debug.helper')) {
    // display debug tabs with SQL queries etc
}
For debug 1 it will still display all debug messages. And with debug 0 it is still debug-free.

 
1 Comment

Posted in CakePHP

 

Working with models

08 Mar

Since there is so much to talk about it here, I will cut right to the chase.

We all know about “Fat Models, Slim Controllers”. So basically, as much as possible of the code should be in the model. For a good reason: If you need that code in another controller, you can now easily access it. Having it inside a controller does not give you this option. Thats another principle: “Don’t repeat yourself”.

What if I use the same model in different contexts?

We sometimes need the “User” (class name!) Model to be more than just that. Imagine a forum with posts. We could have “User”, “LastUser”, “FirstUser” etc. For all those we have custom belongsTo relations in our models.

If we had something like this in our code, we could get in trouble:

function beforeValidate() {
    if (isset($this->data['User']['title'])) {
        $this->data['User']['title'] = ucfirst($this->data['User']['title']); 
    }
    return true;
}
So always use $this->alias:
function beforeValidate() {
    parent::beforeValidate();
    if (isset($this->data[$this->alias]['title'])) {
        $this->data[$this->alias]['title'] = ucfirst($this->data[$this->alias]['title']);
    }
    return true;
}
Now we can set up any additional relation without breaking anything:
var $belongsTo = array(
    'LastUser' => array(
        'className'    => 'User',
        'foreignKey'   => 'last_user_id',
        'fields' => array('id', 'username')
    )
);
$this->alias is now “LastUser” for this relation.

Always try to outsource code, that repeats itself, into behaviors or the AppModel callbacks. So if the “ucfirst()” code snippet from above is used in pretty much all models, you can either use beforeValidate() of the AppModel:

function beforeValidate() {
    parent::beforeValidate();
    if (isset($this->data[$this->alias][$this->displayField])) {
        $this->data[$this->alias][$this->displayField] = ucfirst($this->data[$this->alias][$this->displayField]);
    }
    return true;
}
or you can create a behavior:
class DoFancyBehavior extends ModelBehavior {
 
    function setup(Model $Model, $config = array()) {
        $this->config[$Model->alias] = $this->default;
        $this->config[$Model->alias] = array_merge($this->config[$Model->alias], $config);
        // [...]
        $this->Model = $Model;
    }
 
    function beforeValidate(Model $Model) {
        $this->_doFancyUcfirst($Model->data);
        return true;
    }
 
    function _doFancyUcfirst(&$data) {
        if (isset($data[$this->Model->alias][$this->Model->displayField])) { 
            $data[$this->Model->alias][$this->Model->displayField] = ucfirst($data[$this->Model->alias][$this->Model->displayField]);
        }
    }
 
}
With the behavior you can define which models will and which won’t “behave” fancy. But of course the same would be possible for the AppModel method using an internal variable ($this->behaveFancy: true/false) and checking on it in our callback.

 
No Comments

Posted in CakePHP

 

Web Development Snippets

06 Mar

A couple of useful snippets for web developing. The list will grow over the years.

Jumping to anchors with JS

If we want to jump to an anchor dynamically (using javascript):

/* dont use this!!! */
//location.href = location.href  + '#searchbox';
/* and not this either: */
//location.href = '#searchbox';
 
/* this is the correct way to go */
window.location.hash = "searchbox";

This has still some flaws if you click the anchor multiple times. This worked for me:

var destinationLink = document.getElementById('targetId');
var destx = destinationLink.offsetLeft;
var desty = destinationLink.offsetTop;
scrollTo(destx, desty);
So the above method should be used to set the hash of the page. The below one for jumping to anchors.

Prevent Right Click

This should be used wisely and only where it makes sense. It it neither waterproof nor convenient in most cases. Simply by displaying the source code the content can still be accessed (Shortcuts like STRG+U).

<body oncontextmenu="return false">
...
</body>
I like the browser solution more than the js solutions out there :)

Get HTML5 ready

<input name="url" placeholder="http://"/>
This is only one example of many. The Google Chrome browser already understands it as well as some beta browsers like FF4 etc. Combine this with a javascript/jquery fallback and you have a nice (placeholder) functionality that will some day run natively in all user browsers.

And some more snippets from different external sources:

Forcing long strings to wrap

biostall.com/forcing-long-strings-and-urls-to-wrap-with-css

Print-Layout

www.alistapart.com/articles/goingtoprint/