CakePHP is an amazing PHP framework, but it has a steep learning curve! It requires a good amount of research and training to become an expert.
I have been fortunate to use CakePHP for over 7 years now, and in that time I’ve had the honor of working with many members of the CakePHP community.
In this CakePHP tutorial I’d like to describe few bad practices which I have seen over the years, and propose the correct approach to avoid these mistakes. This isn’t to say my code is perfect, but as a programmer we are always learning, so it is important to follow the best practices and adjust as you learn!
Content of this article is inspired by a post from CakeCoded. If you would like to learn more about CakePHP please visit our learning section here.
Common Mistake #1: Not Following CakePHP Coding Conventions
CakePHP coding conventions can be viewed here. I will highlight a few things which I often notice when viewing other programmers’ code.
Control structures. So often you see programmers get this wrong, and in some cases bring practices from other coding languages. CakePHP expects the following syntax:
if ((expr_1) || (expr_2)) {
// action_1;
} elseif (!(expr_3) && (expr_4)) {
// action_2;
} else {
// default_action;
}
There should be 1 (one) space before the first parenthesis and 1 (one) space between the last parenthesis and the opening bracket. So this means that the following is incorrect:
if($this->request->data){
}
Note the spacing between the if
and (
, and between )
and {
Always use curly brackets in control structures, even if they are not needed. They increase the readability of the code, and they give you fewer logical errors.
So, for example, the following is incorrect:
if ($foo)
$bar = true
This should be formatted like this:
if ($foo) {
$bar = true
}
Finally, watch where you place the brackets. Opening brackets shouldn’t start a new line. And make sure all your brackets line up so that each new bracket is in line with the closing bracket.
Here are some incorrect examples:
if ($foo)
{
$bar = true;
}
This is incorrect, the opening bracket should be in the first line:
if ($foo) {
$bar = true;
if ($action) {
$to = false;
}
}
The indentation needs to line up correctly.
I often hear programmers say, “But I am too busy to make the code neat….” My response is, “Trust me, neat code will stand the test of time”. Writing CakePHP code which isn’t readable will be a nightmare to come back to if you need to make a change in a few months.
Common Mistake #2: Improper Use of Containable Behaviors and Recursive Levels in ORM
I was fortunate recently to have an informal discussion with a database developer from Facebook. We started talking about CakePHP and he said to me, “Oh, that uses ORM doesn’t it? That can be scary.” I asked him what he meant, and he commented that with Object-relational mapping (ORM) it is easy for SQL queries to become unnecessarily large.
He is right in a way. Part of CakePHP’s magic is in its use of ORM and the way that it groups different database table relations together. By default, CakePHP automatically selects any related ‘Belongs To’, ‘Has One’ and ‘Has Many’ data, and this can lead to very large SQL queries. These queries may not be a concern when you are initially developing an application, but after six months of collecting live data, you may find the application becomes very slow, and in some cases crashes if the queries aren’t optimized.
I look out for two things when auditing an existing website. Firstly, has the default recursive level been changed? By default, CakePHP sets the recursive level to 1, which in my opinion is too high. I always set it to -1 and then use the containable behavior to get any related models.
That leads to the second thing I look for – has the Containable behavior been used? I often have new clients come to me and say that CakePHP is slow. The reason is almost always because Containable hasn’t been used! A good CakePHP programmer will optimize their SQL queries regardless of how much “auto-magic” is done behind the scenes.
The containable behavior wasn’t added until CakePHP 1.2, but boy has it made a difference?! Be sure to use containable as much as possible, as it is such an effective way to optimize your SQL. For more information on how to implement and use the Containable behavior, click here.
Common Mistake #3: Keeping Business Logic in Controllers Instead of Models
Good CakePHP code will have the logic in the model files. This takes a bit to get used to, but once mastered there is no looking back! A controller file should be used for what it is intended for in the MVC pattern – controlling! So use your controller file to handle user actions, while letting the business logic go in the model file.
A good example of this would be a simple CRUD – an everyday action! Let’s take the add posts function from a blog tutorial as an example. The default add function is as follows:
public function add() {
if ($this->request->is('post')) {
$this->Post->create();
if ($this->Post->save($this->request->data)) {
$this->Session->setFlash(__('Your post has been saved.'));
return $this->redirect(array('action' => 'index'));
}
$this->Session->setFlash(__('Unable to add your post.'));
}
}
This controller action is fine for a simple add, but what would happen if you wanted to do things such as send an email to the admin when a post was added, or update another model association when a post was added. This is additional logic, but this logic shouldn’t go into our controller file.
Instead we would write a function for this in our Post.php
model. Perhaps something like this:
public function addPost($data = array(), $emailAdmin = true) {
$this->create();
$this->save($data);
// update any other tables
// send the email to the admin user
if ($emailAdmin) {
}
// if all is successful
return true;
}
This would then result in a small change to the controller action as follows:
public function add() {
if ($this->request->is('post')) {
if ($this->Post->addPost($this->request->data)) {
$this->Session->setFlash(__('Your post has been saved.'));
return $this->redirect(array('action' => 'index'));
}
$this->Session->setFlash(__('Unable to add your post.'));
}
}
As you can see, the new action is actually one less line, because the $this->Post->create()
has been moved to the model file.
This is a perfect, everyday example of where moving logic to the model file is a good idea – and it certainly makes for a much cleaner code base!
Common Mistake #4: Adding Too Much Complexity to the Code, Instead of Returning Often and Early
This is always a bit of an ongoing debate, but returning often, and returning early certainly does make for much cleaner looking code. This applies to the model methods more than anything else.
But what exactly do I mean? Well, let’s take a look at the method we added in the CakePHP tutorial above:
public function addPost($data = array(), $emailAdmin = true) {
$this->create();
$this->save($data);
// update any other tables
// send the email to the admin user
if ($emailAdmin) {
}
// if all is successful
return true;
}
To return often, and return early means that as we run through our function, we check to make sure the everything is OK on a regular basis. If it isn’t, then we return false, or return a CakePHP error.
It might be easiest to show this with an example. There are two ways the above function could be written:
public function addPost($data = array(), $emailAdmin = true) {
if ($data) {
$this->create();
$result = $this->save($data);
if ($result) {
// update any other tables
// send the email to the admin user
if ($emailAdmin) {
// send the admin email
}
} else {
// problem saving the data
return false;
}
// if all is successful
return true;
} else {
// no data submitted
return false;
}
}
See how the code quickly becomes unreadable? There are if
s and else
s all over the place, and the function quickly becomes one big indentation. Don’t get me wrong, I love clean indentation, but watch how the function looks if it written with the return often, return early principle.
public function addPost($data = array(), $emailAdmin = true) {
if (!$data) {
// no data submitted
return false;
}
$this->create();
$result = $this->save($data);
if (!$result) {
// problem saving the data
return false;
}
// update any other tables
// send the email to the admin user
if ($emailAdmin) {
// send the admin email
}
// if all is successful
return true;
}
Straight away, in this small example, you can see the code only has a single indentation and is much more readable. The logic actually makes more sense – let the logic run through line by line, and if there is any issue along the way, return the error and don’t proceed to the next line.
This allows a CakePHP programmer to write the same way that we read – reading code from left to right, top to bottom, rather than in different blocks, which can quickly get confusing!
Common Mistake #5: Not Using the DRY Principle
DRY stands for Don’t Repeat Yourself, and it is a philosophy which should be followed when coding in CakePHP. With object-oriented code, there is no excuse for repeating the same block of code twice!
Here are few CakePHP tips to ensure you don’t repeat yourself:
- As mentioned above, aim to put logic in model files so you can share the logic.
- In your view files, if you are repeating views, create the view code as an Element, or even a custom helper.
- Set up some configuration settings – the
app/Config/bootstrap.php
file is a great place for this. This helps make sure you aren’t hard coding things like the application name and the main email address. The last thing you want to do is go through hundreds of files just because the client has asked to update an email address in an application. - Always ask yourself, “If I am repeating code, is there a better way to write this code, and am I putting this code in the right place?” Chances are, if you need to repeat code, it could be written better.
Common Mistake #6: Not Commenting the Code
The last point I will make is in regards to comments. Firstly, doc blocking. A doc block is when you document a method or an action. It takes only a minute to record a little about what a function is doing, but it makes such a difference in terms of readability of code.
CakePHP Doc Blocks need to go against the left margin of the page. So a simple example using the code from above.
/**
* Adds & saves a post as well as emails the admin to let them know the post has been added.
* Also performs some saving to another table
*
* @param array $data The post data
* @param bool $emailAdmin If set to true, will email the website admin
* @return bool Returns true if successful
*/
public function addPost($data = array(), $emailAdmin = true) {
As you will see, it doesn’t take long to write a doc block, but it makes a huge difference in terms of longevity of the code. Ultimately, it means the code can live on past you as the developer.
Likewise with in-line comments. Don’t be scared to explain what your code is doing and why! It makes it a lot easier in the long run to understand your code, especially if another developer is looking at it!
Wrap-up
CakePHP is an extensive, full-featured framework. Given it follows convention over configuration, CakePHP is more strict than other PHP based frameworks, in the sense that a user is “forced” to follow a certain way of laying out the code. This can be controversial, but in my experience it leads to a code base which is more consistent, readable and understandable – rather than letting a developer “choose” how the code should be written, a development team will write consistent code by following Cake’s conventions.
By following this CakePHP tutorial and ensuring your code is well written, applications can stand the test of time. Code should always be written for tomorrow – so that if another developer is looking at a particular code block years later, he will understand the code, and stick to the expected standards. CakePHP is no different and hopefully this guide will help correct some bad habits.