You probably read my last tip sections.
And I started to move some of them to my sandbox app.
But once in a while it might also be nice to publish a few selected tips, here as well.
Oh, and CakePHP 2.5 is out! Get it – now 🙂
See the last chapter of this post about why you really should upgrade ASAP. And it doesn’t matter if you
are on 1.x or 2.x.
URLs in CLI
In CLI and your shells there is no HTTP HOST and therefore usually no absolute URLs.
But in order to properly work with links/urls (e.g. sending batch emails), we need that to work.
So I use Configure::read(‘Config.fullPageUrl’) here.
My configs.php
file then contains:
$config['Config'] = array(
'fullPageUrl' => http://www.myapp.de // The primary URL
)
In case you have a different domain for local development like http://myapp.local
and you want that to be the fullPageUrl, make sure you overwrite the default in your configs_private.php
file:
Configure::write('Config.fullPageUrl', 'http://myapp.local'); // Usually defaults to the (live) primary URL
And in the end of my bootstrap file (after including all config files), I simply do:
if (php_sapi_name() === 'cli') {
Configure::write('App.fullBaseUrl', Configure::read('Config.fullPageUrl'));
}
To test the current domain/fullBaseUrl, you can use my Setup plugin command cake Setup.TestCli router
.
It will output both relative and absolute URL examples generated by the Router class with your current settings.
So on the live server then it will output http://myapp.local
instead of http://localhost
when generating Router::url()
s in your shells.
Careful with running shells in CLI
Most are probably not aware, but running shells in CLI needs to have a proper user management around them in most cases.
Imagine yourself running your apache as www-data (default) and log in as root or any other user not affiliated with that www-data user/role (bad idea).
Once you execute a shell and tmp cache data are (re)built, your www-data user cannot access them anymore, losing the ability to cache and triggering a lot of errors.
So make sure you only log in with a user that shares the role of www-data at least, so that both can access each others’ generated files.
A popular example is the ClearCache shell which re-builds your cache dirs in debug 0 (when changing files or db schema makes this necessary).
PS: Of course you could also switch to another cache system than the default File engine. But most probably didn’t do that yet, either.
Merging arrays
Ever wondered what Hash::merge(), array_merge and array_merge_recursive have in common – or don’t have in common?
Check out these merge comparison examples.
See what the requirements are – and use the appropriate merge method then.
There is also the +
operator, which is quite useful when merging flat arrays and string key based options. This is quite commonly used in the core to merge
options and defaults:
$defaults = array(
'escape' => true
):
$options += $defaults;
In this case the $defaults are merged on top of $options, but only if that key has not been specified yet.
This kind of merge is really efficient and fast (4-5x faster than array_merge() itself) – but should really only be used if all keys are definitely strings.
Paginating and merging
A propos merging: When setting up paginate settings in your controllers, try to prevent
public function index() {
$this->paginate = array(...);
}
This way you kill all defaults you might have set via public $paginate
or in your extending controllers (AppController’s beforeFilter() callback for example).
So it is better to use:
$this->paginate = array_merge($this->paginate, array(...));
// or
$this->paginate = array(...) + $this->paginate;
In my 2.x code for example I have this snippet in all my AppControllers to have query strings for paginations:
public function beforeFilter() {
parent::beforeFilter();
$this->paginate['paramType'] = 'querystring';
}
This will only work with proper merging of defaults and custom settings.
I prefer the latter because the settings are string based and here the + operator is the fastest and easiest way of doing things.
Once the key is already set in your method, the default will be ignored right away (with array_merge() and nullish values this can be different/unexpected).
And remember to not mix controller and component pagination.
Pagination and sort default order
Adjust your bake templates so that some fields like created/modified are ordered DESC instead of ASC per default in the index actions.
For those fields the first click on the header row should display them DESC right away as one would then most likely be interested in the
latest changes. Same goes for most date fields like "published" as well as fields like "priority", "rating", …
That’s how the baked code (or manually adjusted one if done afterwards) could then look like (index.ctp):
<th><?php echo $this->Paginator->sort('name');?></th>
<th><?php echo $this->Paginator->sort('amount');?></th>
<th><?php echo $this->Paginator->sort('priority', null, array('direction' => 'desc'));?></th>
<th><?php echo $this->Paginator->sort('status', null, array('direction' => 'desc'));?></th>
<th><?php echo $this->Paginator->sort('publish_date', null, array('direction' => 'desc'));?></th>
<th><?php echo $this->Paginator->sort('modified', null, array('direction' => 'desc'));?></th>
<th><?php echo $this->Paginator->sort('created', null, array('direction' => 'desc'));?></th>
Using modified model data in the form again
Some of you might have had the wish of posted data that was modified in the model due to beforeValidate/beforeSave to appear modified in the view again (so
that the reason for validation errors might be more clear etc).
So let’s say you have a beforeValidate callback to clean the input of a textarea:
public function beforeValidate($options = array() {
if (isset($this->data[$this->alias]['comment']) {
$this->data[$this->alias]['comment'] = $this->_clean($this->data[$this->alias]['comment']);
}
return true;
}
So in this case it could easily be that _clean() removes some invalid content and thus the minLength rule is suddenly triggered.
Which is weird, since we posted at least twice the length of text.
To clarify to the user what is going on, one could adjust the error message – but one could additionally return the modified (ready to save) data instead of the
actually posted data.
public function add() {
if ($this->request->is('post')) {
$this->{$this->modelClass}->create();
if ($this->{$this->modelClass}->save($this->request->data)) {
$this->Session->setFlash(...);
return $this->redirect(array('action' => 'index'));
} else {
// Here we assign the modified model data back to the request
// object and therefore to the view/form
$this->request->data = $this->{$this->modelClass}->data;
$this->Session->setFlash(...);
}
} else {
// Default values for the form
$this->request->data[$this->modelClass]['status'] = true;
}
}
The input field will now contain the content that was served to beforeValidate(). And combined with a good error message this will probably clear things up.
PS: You can also directly use the model’s name instead of {$this->modelClass}, e.g. ‘Comment’.
MySQL – MyISAM vs InnoDB
InnoDB is a little bit more robust as it allows transactions. Especially with CakePHP and "multiple queries" most of the time (per default) this can be quite helpful in keeping the DB in a valid state.
Also read myisam-versus-innodb on pros/cons for each.
One additional problem with InnoDB, though: Per default it creates a file that always increases in size, never decreases again. This can soon be a nightmare with backuping when it becomes >> xx GB of size.
See how-to-shrink-purge-ibdata1-file-in-mysql how to avoid that by not using that file, and instead using innodb_file_per_table
.
Testing
Testing Controllers
I stumbled upon a few issues with testing controllers – especially plugin controllers.
For plugin controllers to be testable unfortunately you always need to use $uses, even if it wasn’t necessary due to conventions.
So for your Tools.TinyUrls Controller you would need
public $uses = array('Tools.TinyUrl');
Otherwise it would always try to look for the model/fixture in your app dir, which eventually always fails.
Do not forget to use --stderr
when testing in CLI. Otherwise all tests that use the session (and which work fine in webtest runner) will fail:
cake test app AllController --stderr
Test Coverage
If you want to generate a HTML overview of all your locale test coverage:
cake test app AllApp --stderr --log-junit tmp/coverage/unitreport.xml --coverage-html tmp/coverage --coverage-clover tmp/coverage/coverage.xml
The report index.html will be in your /tmp/coverage folder.
More on testing – especially controller testing – can be found on the corresponding Tools plugin Wiki page.
Upgrade (deprecated) code
It is always good practice to upgrade to the current master. Not only for CakePHP, but there especially. It will save you a lot of time in the long run, as migration will be easier and faster in small steps instead of one huge step. It will also make it easier to use the new features and more importantly will also come with a lot of fixes and method corrections that the older versions didn’t get anymore. Those outdated versions usually only get security-bugs fixed. So if you look hours for an error that is already fixed in the current master, it was just a huge waste of time. I have seen that a thousands times – on IRC and elsewhere.
So in case you are not using the current master (2.5), do it now. Internally, upgrading 2.x is a "piece of cake".
Upgrading from 1.x is also not that big of a deal – just needs a little bit more manual adjustments. For most things you can use the core UpgradeShell as well as my Upgrade plugin.
In case you are already upgraded to 2.5, you can and should also remove deprecated functionality in favor of the right one.
Those deprecated things will only add to the file of upgrades for the next 2.x release or 3.x. And using the upgrade shell it is usually just one single command to execute.
So for 2.5, you should have removed all the "easy" stuff that will clearly be switched with a different way of doing things as it is mentioned in the migration guide, e.g.
- loggedIn() in favor of Auth::user(‘id’)
- CakeRequest::onlyAllow() in favor of CakeRequest::allowMethod()
- Use first argument (string|array) instead of var args for CakeRequest::allowMethod(), AuthComponent::allow(), etc
- $title_for_layout in favor of $this->fetch(‘title’) and all other deprecated view vars
From 2.4 and below there are also a few left overs that could easily be corrected:
- FULL_BASE_URL, DEFAULT_LANGUAGE, IMAGES_URL, JS_URL, CSS_URL to Config variables
- Remove CAKEPHP_SHELL, IMAGES, JS, CSS usage
- Simplify HtmlHelper::css()
- Remove Sanitize class usage in favor of a more sane approach
- Simplify CakeRequest and PaginatorHelper usage with param() if applicable
- Don’t use request shortcuts anymore – like
$this->action
in favor of$this->request->action
etc - Get rid of Model::read() calls – in 3.x this will be get() – I use my Tools plugin MyModel::get() method here already for years
- Use the new Model::save() syntax for options
- Completey get rid of named params in favor query strings (Use my Tools plugin CommonComponent and Configure value
App.warnAboutNamedParams
to detect left-overs) - Replace all Set calls with Hash calls, as Set is deprecated. Make sure it is tested thoroughly (as the functionality of Hash might be slightly different).
- Prevent using Validation::range() in favor of a custom validation rule for inclusive ranges to easier migrate to 3.x – or simply use my Tools plugin MyModel::rangeInclusive() method.
- Further deprecations in favor of the 2.5+ way to do things
and so on.
For some details see Tips-Upgrading-to-CakePHP-2.x.
My Tools plugin also contains a few further tweaks that can help ease migration from 2.x to 3.x See the full list on the corresponding Wiki page.
This will help making sure any future upgrade is less painful.
Think about it: When you do that now the remaining TODO list will only be half the size and look a lot less intimidating. When the time comes to upgrade to 3.x it will look quite more doable.
My codez is now officially all 2.5 – and as 3.0 ready as it can get 😛
And what always helps a lot is to code clean and structured. A code mess will always be difficult to maintain. So use coding standards and enforce them. Use best practice approaches. Refactor once in a while to prevent a mess from happening.
Happy coding 🙂
hey , I’m debutante in cakephp 2.6, it’s been a while I search how to use your paging plugin . please help us