CakePHP Tips – Summer 2013

While CakePHP 2.4 is now just around the corner (already beta), a few tips gathered the last few days.

Don’t mix controller and component pagination

Currently, if you mix controller pagination setup and call the Pagination component then, it can easily backfire as the two different
settings are not always merged as you would expect it to. So don’t mix em.

Either use the controller only:

public $paginate = array(...);
// and
$this->paginate = array(...) + $this->paginate;
$results = $this->paginate();

or the component only:

$this->Paginator->settings(array(...));
$results = $this->Paginator->paginate();

My recommendation: Stick to controller pagination for now (as we all have the last years) as it seems it won’t be deprecated after all. It is a nice
convenience access to the component anyway.

PS: If sticking to controller pagination, add this rejected PR snippet to your AppController to avoid the notice thrown.

Test your code

Even if you don’t assert the outcome.

"But that is time consuming. I dont have time!"

Yeah, sometimes you are in a hurry and can’t properly assert all tests. Still no excuse.
Just be running the code via "dummy call" and having the test not through any notices/warnings/errors you already have a very basic level of testing. And it really doesn’t need time to set such a basic test run up.
As soon as there is time you can then still address the proper result. But until then you at least know that no line of code is suddenly gone missing or variables have
accidentally been renamed etc.
Such a basic "run-through" test is still better than no test at all:

/**
 * Test myMethod()
 * 
 * @return void
 */
public function testMyMethod() {
    $this->MyClass->myMethod();
    //TODO: assert return value etc
}

Return your redirects

I always used to bake my templates with simple redirect() calls:

if ($this->Model->save()) {
    $this->redirect(...);
}

But, especially with testing your code, this might have unexpected results, as there you cannot exit, but need to return early (by overwriting the redirect method in your test case file).

The cookbook now states this, as well:

When testing actions that contain redirect() and other code following the redirect it is generally a good idea to return when redirecting. The reason for this, is that redirect() is mocked in testing, and does not exit like normal. And instead of your code exiting, it will continue to run code following the redirect.

So it’s wiser to cleanly return here, as well:

// in your controller action
if ($this->Model->save()) {
    return $this->redirect(...);
}

Even if for your productive environment the return will be superfluous, the test cases will not return wrong results.

I just recently adjusted my bake templates and it has also been changed in the 2.4 branch of the Cake core. I advice you to update your files, as well. You can use my Upgrade shell task "controllers" to do that as a batch job for your app and plugins:

// for your app
cake Upgrade.Upgrade controllers

// for a plugin FooBar
cake Upgrade.Upgrade controllers -p FooBar

Return your shell errors

Same applies here: Testing does not halt the code. Therefore you also need to return early here:

public function some_command() {
    if (!$this->foo()) {
        $this->error('Abort');
    }
    ...

becomes

public function some_command() {
    if (!$this->foo()) {
        return $this->error('Abort');
    }
    ...

This is also covered by my Upgrade shell now.

Debug SQL queries by throwing exceptions

Ever worked on an ajax, csv or even a redirect action where there will be no debug SQL bar at the bottom etc?
You can easily display your SQL queries up to a specific point by throwing an exception:

$someRecord = $this->Model->find(...);

 // We just add this snippet here to halt and see the actual SQL query before we continue:
throw new Exception();

if (!$someRecord) {
    return $this->redirect(...);
}
return $this->redirect(...);

This halts the code, shows the error layout with all the queries listed below. Neat, isn’t it? And it is quite fast and works anywhere in your application.

Switch from constants to a more flexible way of configuration

In our case we can leverage the Configure class and its read() and write() methods to do so. Better than constants anyway as they can only be defined once and not changed anymore.
So it’s only reasonable that the 2.4 core deprecated quite a few of those constants like FULL_BASE_URL, DEFAULT LANGUAGE, CAKE_SHELL etc.
Using Configure we can store a default value and overwrite it any time after that with a different value if we need/want to.

In the past I also caught myself using constants where Configure would have sufficed. Partly to make myself believe my app will be faster this way.
The speed advantage is so minimal though that it does not outweigh the reduced flexibility that comes with it.

So instead of using define('FULL_BASE_URL', 'http://example.com') you can now use Configure::write('App.fullBaseURL', 'http://example.com'); and overwrite it again later on.

Same goes for any settings you might want to set. Chose a meaningful namespace to group them, though:

Configure::write('Config.someKey', 'somevalue'); // with Config as namespace

You can read it anywhere in your app as always, of course:

$key = Configure::read('Config.someKey'); // in this case probably boolean (1/0 alias true/false)
if ($key) {
    // some code here
}

Change your default validation error message

It will be changed in 3.x from "This field cannot be left empty" to "The provided value is invalid".
This makes sense as in most cases it would display this error message when a value is provided but does not pass the specific validation rule (like numeric or email).
This is highly confusing then.

'email' => array(
    'notEmpty' => array(
        'rule' => array('email')))

This example would just fallback to "This field cannot be left empty" here – both on empty submit and if you input some invalid email.

So you should already address that in 2.x by always defining the error message for your rules with the above wording. Clearer validation error messages will help your users to correct their mistakes and submit the form properly. Only use "This field cannot be left empty" for actual "notEmpty" cases then:

// in your model
public $validate = array(
    'email' => array(
        'notEmpty' => array(
            'rule' => array('email'),
            'message' => 'The provided value is invalid' // or even 'This is not a valid email address'
        )
    ),
    'postal_code' => array(
        'notEmpty' => array(
            'rule' => array('notEmpty'),
            'message' => 'This field cannot be left empty',
            'last' => true
        ),
        'exists' => array(
            'rule' => array('validatePostalCodeExists'), // custom validation rule
            'message' => 'The provided value is invalid', // or even 'This postal code is not valid'
        )
    )
);

Also try to use the verbose array declaration and don’t forget 'last' => true for consecutive validation rules (as in postal_code above).

Text(area) to paragraphs

Use the new Cake2.4 method TextHelper::autoParagraph() to display your textarea input. It will automatically format single line breaks to <br /> and double ones to <p> blocks.
If you are still on 2.3, you can copy this method over into a TextExt helper, for example, and use aliasing to map it back to "Text" helper alias. When upgrading you won’t have to change your code then a bit 🙂

Important: Do not forget that this method does not escape your output. Thus you need to use h() for (potentially unsafe) user input:

echo $this->Text->autoParagraph(h($text));

h() will effectively remove any XSS dangers as well as defuse any HTML chars like < or > that could otherwise destroy the layout (but are totally valid chars for quoting in some languages).

Read more about output and security in CakePHP here.

Check if you need the hardcoding of your core lib path

By default CakePHP "bake project" for new apps will hardcode the paths in your index.php and test.php – if you don’t have CakePHP in your include_path. Which most don’t, especially when maintaining multiple different Cake versions and apps.
So you might want to (I do anyway) comment those lines out again after baking the project:

define('CAKE_CORE_INCLUDE_PATH', '/some/hardcoded/path');

// to

//define('CAKE_CORE_INCLUDE_PATH', '/some/hardcoded/path');

You will stumple upon that when deploying your scripts. So better take care of it asap.

Check out the 2.4 core

While it is still beta, you can test it already, though. I even use it for new apps (Disclaimer: don’t do that if you are new to CakePHP!).
You can test the new features or how your 2.3 app would run with it. You can realize quite early this way where potential pitfalls are.
In the past I often discovered some "bugs" this way that then could easily be fixed or adjusted before your code actually some day depends on it.
While still in beta you might also be able to add some additions to new features before you will have to wait for 2.5 again or something.
So it’s worth to at least play around with it now.

Also keep an eye on the 2.4 migration guide and maybe even 3.x migration guide as well as 3.x chatter and how your code would be affected or would have to be modified. It might save you some time in the upgrade process if you can already eliminate some of the necessary changes in advance.

A very good example is the removal of the named params in favor of query strings. Don’t start to use those for new apps. Use the future proof query strings even for your 2.x apps.

One example for 3.x, that is not yet officially mentioned yet, is the possible deprecation and removal of Model::read(). It is a method I have always been preaching to avoid. Especially if you are a beginner, but also if you can (and you always do) use find() instead. The cookbook now also states this in a warning.

I never used it once and it seems that there is very high possibility that it will be removed in 3.x. So if you catch yourself using it today, think about this and if you might be able to replace it with the find() wrapper from here on 🙂 To be on the safe side.

2 Comments

  1. In your first tip, the incomplete test, I would add a call to markTestIncomplete() so that you will see there are incomplete tests when running your tests.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.