1. Introduction
  2. Installing CakePHP
  3. Including off-the-shelf libraries
  4. Creating a database
  5. Setting up the models
  6. Admin routing
  7. Static pages
  8. Setting up the controllers
  9. Appendices
    1. Reserved words

In the Kitchen with CakePHP

This guide was last updated on the 23rd of July. (Latest change: adding name search functionality in the admin index URLs in the example code. Yes, I need to write extensive documentation for this now-featureful example code!) Its icons are from the wonderful famfamfam.com site. The latest version of the woefully incomplete (but possibly still helpful to some people) example source code is now available.

1. Introduction

Why I wrote this guide

Considering that CakePHP is an extremely helpful framework written in a language that's already available on most web servers, there seems to be a lack of good documentation for it. Sure, there are brief guides dotted here and there on the web that tell you how to perform the odd specific trick, but I couldn't find anything that gave you a good overview of, and introduction to, the framework - something to set you off in the right direction.

I'm going to attempt to fill that gap with this guide. Its purpose is to get you started - to help you on the way to learning CakePHP to the point where you can get the rest of your information from the official documentation, the API and the helpful people at the CakePHP group.

If you notice that I've done something a particularly strange way, in a way that's now depreciated, or just plain wrong, please tell me so and I'll update this guide. I'm still learning new things about CakePHP every week, especially as it's still in beta at time of writing, so this guide should grow as CakePHP itself grows.

Who this guide is for

This guide is intended to be useful to anyone who's looking for a helping hand delving into CakePHP. Before you read this guide, you should already be reasonably familiar with PHP and an RDBMS such as MySQL. Although CakePHP is specifically designed to work with any PHP based setup, this guide is based on a LAMP setup due to its ubiquity on web servers, but it shouldn't take too much tweaking to get the examples provided working on a different system.

Please note that this guide is specific to CakePHP 1.2. Many of the examples in it will not work with previous versions of CakePHP.

Don't expect an instant website the first time

Don't get too excited about using a framework to dramatically reduce the time it takes you to build a website. To begin with, you'll be equally slowed down by having to learn new ways of doing familiar things. This can seem frustrating, as things that used to be monotonous but simple to do can suddenly only be achieved by approaching them in a new and unfamiliar way. Don't expect your first project to be any faster than usual - it will likely take you a few projects to get to grips with the MVC mindset and take advantage of even half of CakePHP's features. Remember that even if making a website isn't faster at first, the time you do spend will at least be spent learning something new and concentrating on the aspects of the site that make it stand out rather than doing the same old CRUD that you're probably sick of by now. The fact that each site you make will likely come together much faster is best looked at as a nice bonus.

A new way of looking at things

CakePHP is built upon several existing technologies, most notably object oriented PHP and SQL. It helps to understand these other technologies at least fairly well before you delve into your first CakePHP based project, as you will better understand what's going on and why you need to do things in a certain way.

However, thinking just about these underlying technologies can also lead to confusion. Bear in mind that you are talking directly to CakePHP, not anything else, and that it merely relays your requests on to PHP and an SQL based RDBMS. If you take advantage of CakePHP fully, you should find that you don't need to declare an instance of an object manually even once, and you may well not even have a single line of SQL in your code.

In short, try to be mindful of PHP and SQL, but also remember that you only use them indirectly through CakePHP, which can be best thought of as being like a language in its own right. It isn't, of course, but if you act like it is, you'll find yourself working effortlessly with it rather than struggling against it.

Designing for the user

Something else to bear in mind about CakePHP is that it's quite database centric. You should draw an ER diagram before you first touch your keyboard, then create the tables in your RDBMS, then describe the requirements and relationships of those tables to CakePHP in the form of models, and only then make the code for the pages that actually use them. In short, the developer's point of view is from the database, looking outward.

But the chances are you're not just a developer - you need to be a designer too. I know this is beyond the scope of a CakePHP guide, but it's a very good idea to start off thinking about the project from the point of view of a user. Work out which way of presenting the site's information and performable actions is the most intuitive, then start hacking away once you already have a pretty clear idea of both where you're headed and how to get there.

Of course, clients often change their mind half way through a project. Don't worry, with the help of a framework like CakePHP, making those changes will usually be almost as easy as the client assumes it is. Nevertheless, if you start programing with a relatively concrete image already in your head of how the finished site will look and function, you'll find that the project gets finished even sooner, with or without the help of a framework.

A short note on style

Cake has many conventions, which is just another way of saying it has certain ways of doing things. Although it's up to you if you want to follow its conventions or not, your work will be much easier if you do stick with them because CakePHP automatically looks for variables, database tables, and various other things, and only knows to look for names that follow its conventional format.

For example, it's a good idea to name your variables using lowerCamelCase because if you use $this->set(compact()) to pass them from your controller to your view, it will convert them to lowerCamelCase anyway. There are several more conventions, but we'll get to those later.

Although it's not technically a CakePHP convention, there is another stylistic choice you can make that will make your life much easier: indenting arrays. CakePHP makes extensive use of multidimensional arrays, and while it doesn't care about white space, consider which is easier for you or your co-workers to read out of this:

echo $form->input('name', array('label' => 'Username', 'error' => array('required' => 'Please enter the username you would like.', 'urlFriendly' => 'Please choose a simpler username.', 'unique' => 'Sorry, that username is already taken. Please choose another one.')));

Or this:

echo $form->input('name', array(
  'label' => 'Username',
  'error' => array(
    'required' => 'Please enter the username you would like.',
    'urlFriendly' => 'Please choose a simpler username.',
    'unique' => 'Sorry, that username is already taken. Please choose another one.'
  )
));

With nicely styled formatting in mind, let's move on to the good part - making the database, then writing some actual code.

2. Installing CakePHP

When you download and extract CakePHP, you should get a directory containing all the files you need. Move the contents of this directory to the root of your website, including the hidden .htaccess file.

If your website isn't located at Apache's DocumentRoot location (if you're using VirtualDocumentRoot or you're on a shared server, this will be the case), then you need to add the line RewriteBase / to your .htaccess file just after RewriteEngine on, so that it looks like this:

/.htaccess

<IfModule mod_rewrite.c> RewriteEngine on RewriteBase / RewriteRule ^$ app/webroot/ [L] RewriteRule (.*) app/webroot/$1 [L] </IfModule>

If you view your new website in a web browser (remember to update /etc/hosts if you're working locally), you should be able to see CakePHP greeting you already. Now all you need to do is override the default salt value (a long, arbitrary number that CakePHP needs) to something original. To do this, edit /app/config/core.php and change the line that looks like this:

/app/config/core.php

Configure::write('Security.salt', 'DYhG93b0qyJfIxfs2guVoUubWwvniR2G0FgaC9mi');

Just write over the top of the forty random characters with any other forty alphanumeric characters, to ensure your website has a unique random string for its security methods to use.

Now all you need to do to finish installing CakePHP is create a database for it to use.

3. Including off-the-shelf libraries

One very quick note before you start properly customising your CakePHP installation: you may often use certain off-the-shelf libraries on several projects, such as Eric Meyer's CSS Reset, jQuery or the famfamfam Silk or Flags icons. The images will have to be put in /app/webroot/img, but any CSS and JavaScript libraries can be placed in a different directory, namely /vendors. Through some nifty programming, files in /vendors/css and /vendors/js get output just as if they were in the actual document root of the site's /css and /js directories, but it's better to put them in this directory instead for two main reasons.

Firstly, it's a lot tidier, so it'll make your project easier to manage. Secondly, if you later decide that you want just a single copy of CakePHP on your server, which is being used by lots of different websites on that server, this will allow you to also have just a single copy of all the libraries you've downloaded. Not only does this save on disk space (admittedly not a major concern with files of this size), but it also allows you to upgrade the libraries as new releases come out without having to hunt down every single copy of them on your server.

4. Creating a database

I'm going to assume that your first CakePHP based project is one that you're starting from scratch, or updating with the ability to rename the database tables and columns. Although you can specify custom table and column names to CakePHP, it's much easier if you stick with its conventions.

Tables should have pluralised, lower case, underscore separated table names such as users, blogs or blog_entries. Primary keys should be called id. Foreign keys should be named after the table they refer to, only singular and with _id suffixed to the end, such as user_id, blog_id or blog_entry_id.

For my example, I'm going to set up a simple blog for a group of people such as a free software team or a small company to use. This is arguably an overused example lately, but it should cover the basics of most modern web sites, so hopefully you can get some useful information out of it. Here's the SQL:

CREATE DATABASE simple_blog;

USE simple_blog;

CREATE TABLE tags (
  id mediumint(8) unsigned NOT NULL auto_increment,
  slug char(64) default NULL,
  name char(64) default NULL,
  PRIMARY KEY (id),
  INDEX (slug)
);

CREATE TABLE comments (
  id mediumint(8) unsigned NOT NULL auto_increment,
  author_id mediumint(8) unsigned default NULL,
  entry_id mediumint(8) unsigned default NULL,
  moderator_id mediumint(8) unsigned default NULL,
  created datetime,
  slug char(64) default NULL,
  content text,
  PRIMARY KEY (id),
  INDEX (entry_id),
  INDEX (created)
);

CREATE TABLE entries (
  id mediumint(8) unsigned NOT NULL auto_increment,
  author_id mediumint(8) unsigned default NULL,
  moderator_id mediumint(8) unsigned default NULL,
  created datetime,
  slug char(64) default NULL,
  name char(64) default NULL,
  content text,
  PRIMARY KEY (id),
  INDEX (created),
  INDEX (slug)
);

CREATE TABLE people (
  id mediumint(8) unsigned NOT NULL auto_increment,
  slug char(64) default NULL,
  name char(64) default NULL,
  password char(64) default NULL,
  email_address char(64) default NULL,
  url char(64) default NULL,
  admin tinyint(1) unsigned,
  moderator tinyint(1) unsigned,
  PRIMARY KEY (id),
  INDEX (slug),
  INDEX (name)
);

INSERT INTO people (slug, name, password, admin, moderator) VALUES ('admin', 'Admin', md5('admin'), 1, 1);

CREATE TABLE entries_tags (
  entry_id mediumint(8) unsigned default NULL,
  tag_id mediumint(8) unsigned default NULL,
  PRIMARY KEY (entry_id, tag_id)
);

GRANT ALL ON simple_blog.* TO 'simple_blog_code'@'localhost' IDENTIFIED BY 'foobar';

Most of these tables are relatively straightforward. Any column named created or updated is automatically filled in as its name implies. This is similar to the timestamp datatype, except that it can store just dates instead of the date and time if you prefer.

It's easy to write methods in CakePHP that will be inherited by all the different models (that is to say, one piece of code that can be reused when talking to different database tables), so I've decided to make a column called name in every table to take advantage of this fact later on.

It's best to avoid the enum datatype if you can, because it's something only MySQL has rather than part of the official SQL specification, and CakePHP tries to stick to the spec. Using enum won't cause a major problem, but for simple boolean values, you'd be much better off using tinyint(1) unsigned, because it will make it much easier to create things like checkboxes for the value later on.

The entries_tags table is just a linking table that links the entries and tags tables together. CakePHP has a special way to handle many-to-many relationships, and its convention for linking tables is to name them after the plural of both of the other tables involved, in alphabetical order.

It's much easier to create all of the tables first, before writing any code. If you're not sure about the exact structure your database will take at this point, and want to leave some tables out for the moment, it's best not to link to them with any foreign keys - they'll only confuse CakePHP into looking for the non-existant tables.

Once you've created your database, you need to tell CakePHP how to connect to it. This is done by editing /app/config/database.php. There should already be an example configuration in /app/config/database.php.default for you to use, but for reference, here's what you need to have in your /app/config/database.php file:

/app/config/database.php

<?php class DATABASE_CONFIG { var $default = array( 'driver' => 'mysql', 'persistent' => false, 'host' => 'localhost', 'port' => '', 'login' => 'simple_blog_code', 'password' => 'foobar', 'database' => 'simple_blog', 'schema' => '', 'prefix' => '', 'encoding' => '' ); } ?>

If you view your website in a web browser again, it should now show lots of happy, green messages. That means that CakePHP is officially installed on your web server, you've given it a database to connect to, and you're ready to write some code!

5. Setting up the models

Now it's time to finally write some real code. Moving from the database out, it's a good idea for you to specify the relationships between all the models first. In the /app/models directory, make some files with singular, lower case, underscore separated names such as user.php or blog_entry.php. I'm going to start with person.php for this example. Here's how you link the models together:

/app/models/person.php

<?php class Person extends AppModel { var $name = 'Person'; var $hasMany = array( 'Comment' => array( 'className' => 'Comment' ), 'Entry' => array( 'className' => 'Entry' ) ); } ?>

As you can see, I'm just specifying the relationship between the tables with a property. You can use properties (the variables at the top of the class) called $belongsTo, $hasMany, and $hasAndBelongsToMany to tell CakePHP how all the models are connected to each other. You're basically just describing all of your ER diagram's connections, crow's feet and so on to CakePHP, so it knows how it's all linked together.

These properties are multidimentional arrays that go two levels deep. They can get more complicated if you want to do complex things, like link a certain model to another model twice (such as linking a message to two different people: the sender and the recipient), but this is a simple blog example so let's not worry about that for now. This is basically just an array which has the names of all the models it's linked to as keys (model names are always UpperCamelCase, no underscores, singular) and another array as the value, this other array having the phrase 'className' as the key, and the name of the model yet again as its value.

At this point, you can pretty much forget about the table names unless your project is particularly complex. From now on, you'll be dealing pretty much exclusively with the tables' model names instead, and they're always UpperCamelCase, no underscores, singular.

As you might have guessed, the comments model file for my simple blog now looks like this:

/app/models/comment.php

<?php class Comment extends AppModel { var $name = 'Comment'; var $belongsTo = array( 'Person' => array( 'className' => 'Person' ), 'Entry' => array( 'className' => 'Entry' ) ); } ?>

Note that I've specified the models that each Comment belongs to in the same order that their foreign keys appear in the comments table.

Now comes the clever bit. It takes a short while to wrap your head around, but it's a real timesaver later on. I want each blog entry to have several tags (ie categories), and each tag to have several entries. That's a many-to-many relationship. You'd probably expect to have to create three models at this point: one for the entries; one for the tags; and one for the linking table, entries_tags. The clever bit is that the linking table doesn't have its own model - entries and tags can be directly linked together using the $hasAndBelongsToMany variable. So I just write the following code:

/app/models/entry.php

<?php class Entry extends AppModel { var $name = 'Entry'; var $belongsTo = array( 'Person' => array( 'className' => 'Person' ) ); var $hasMany = array( 'Comment' => array( 'className' => 'Comment' ) ); var $hasAndBelongsToMany = array( 'Tag' => array( 'className' => 'Tag' ) ); } ?>

/app/models/tag.php

<?php class Tag extends AppModel { var $name = 'Tag'; var $hasAndBelongsToMany = array( 'Entry' => array( 'className' => 'Entry' ) ); } ?>

Let's specify the validation rules we want to set up next, to ensure that people fill in the web forms correctly instead of filling the database up with garbage. Normally you'd have to specify the maximum length of the text inputs to match the chars and varchars in the database, but CakePHP takes care of this for you. For the most part, you'll probably just need to tell CakePHP what is and isn't required to be filled in at all.

Catering for slugs

You've probably noticed that all but one of the tables we've set up have two common column names: slug and name. It's generally a good idea to avoid writing the same lines of code again and again, or even copy and paste them all over the place to apply them to different things. By using the same column names to store the same kind of information in each table, we can write one piece of code once to explain what they're for and how they work. It also makes the database less confusing to co-workers, or even yourself after a few months spent away from it, dealing with other projects.

When you want a single piece of code to apply to different situations, CakePHP makes the task easy with its use of methods such as beforeFilter, files such as /app/app_controller.php and /app/app_model.php, components, helpers and elements.

Here's the code that automatically generates slugs based on the names that users type in, and ensures that each one is unique within the context in which it's used:

/app/app_model.php

<?php class AppModel extends Model { var $validate = array( 'name' => array( 'unique' => array( 'rule' => 'slugUnique' ), 'required' => VALID_NOT_EMPTY ) ); var $recursive = 0; // By default, don't get related models' data function beforeSave() { $modelName = $this->name; // Put the model's name in an easier to read variable if (isset($this->data[$modelName]['name'])) { $this->data[$modelName]['slug'] = $this->makeSlug($this->data[$modelName]['name']); } } function makeSlug($name) { // Turn names such as "It's wrinkled!" into slugs such as "its-wrinkled" $slug = strtolower($name); // Make the string lowercase $slug = str_replace('\'', '', $slug); // Take out apostraphes. CakePHP takes care of escaping strings before putting them into SQL queries. This is purely for aesthetic purposes - changing "that-s-cool" into "thats-cool" $slug = ereg_replace('[^[:alnum:]]+', '-', $slug); // Turn any group of non-alphanumerics into a single hyphen $slug = trim($slug, '-'); // Remove unnecessary hyphens from beginning and end } function slugUnique($model) { // Make sure the model's slug is unique $modelName = $this->name; // Put the model's name in an easier to read variable. $slug = $this->makeSlug($model['name']); if ($this->id == TRUE) { // An ID is set, so we're editing an existing object; the slug must not be already taken by another row. It's OK to already be taken by this row. $id = $this->data[$modelName]['id']; $numberOfMatches = $this->findCount(array( "{$modelName}.slug" => $slug, "{$modelName}.id" => "!= {$id}" ), -1); } else { // No ID is set, so we're adding a new object; the slug must not be already taken by any row. $numberOfMatches = $this->findCount(array( "{$modelName}.slug" => $slug ), -1); } if ($numberOfMatches == 0) { // There are no matches, so the slug is unique. Good. return TRUE; } else { // There was at least one match. (If there was more than one, something went seriously wrong!) Don't let the form validate. return FALSE; } } } ?>

Let's look at what this does. Due to its filename and class name, every model in this project will inherit it, so they'll all have slugs based on thir names without having to repeat any of the slug code.

The $validate property ensures that whenever a user creates or updates any row of any table, the name they type in is converted into its corresponding slug and checked to ensure that it is unique within its own context. (For exaple, entry names only have to be unique for a particular month to avoid annoying the blog's authors. Because the month and slug will both appear in the URL, this is still enough to work out exactly which entry is being requested.)

The beforeSave method is automatically called by CakePHP exactly when its name insinuates. In this instance, we're seeing what name is specified in the form the user's just filled in, and creating its corresponding slug so that they'll both be stored in the database. (Technically, this goes against the ideals of avoiding redundancy in databases because the slug can be inferred by its name, even though the name can't be inferred by its slug. However, making inserts and updates slower in order to make selects quicker is usually a good tradeoff for a blog, and this is the only way to avoid having to tell MySQL how to convert names to slugs, which is just the kind of code repitition we're trying to avoid.)

The makeSlug method does the actual converting from a name to a slug. It's particularly important to ensure that this code isn't repeated all over the place, in case you have to change how it works before finishing the project. Obviously, this will only work if the routine checking a slug's uniqueness and the routine actually storing it actually agree on what the slug will be called, otherwise duplicates could creep in despite all your hard work.

6. Admin routing

It's often a good idea to keep pages that only administrators can access (such as those related to adding, editing and removing entries, in the case of this example blog) separate from pages that regular users and visitors can access (such as just reading the entries). Setting up an /admin directory is simple in CakePHP: Just edit /app/config/core.php and remove the two forward slashes commenting out this line:

/app/config/core.php

Configure::write('Routing.admin', 'admin')

You can replace the 'admin' string literal with whatever you want to prefix admin URLs with. I'm going to stick with admin for this example, to keep things nice and simple. Whenever you want to make a method in one of your controllers for an admin-only page, simply prefix it with admin_. For example, instead of index() we're going to make a method called admin_index().

7. Static pages

If you look at /app/config/routes.php, you can see how to make exceptions to the rules of which URLs map to which pages. In particular, these two lines are worth looking at:

/app/config/routes.php

Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home')); Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display'));

The second line means that any URLs beginning with /pages/ such as /pages/foo and /pages/bar will be mapped to the display method of the pages controller, instead of its foo and bar methods like you'd expect. Although the pages controller seems complex at first, it's just a regular controller included with CakePHP by defualt. You can see it in /cake/libs/controller/pages_controller.php. Because it's used solely for the purpose of displaying static files, it doesn't need a corresponding table in the database. To use it, all you need to do is link to a URL such as /pages/foo and create a file such as /views/pages/foo.ctp.

The first line means that anyone trying to access the site's homepage will see the contents of /views/pages/home.ctp. You can change this to route to any method of any controller you have, but for the time being you may just want to make a handy list of URLs in /views/pages/home.ctp.

8. Setting up the controllers

As with object oriented programming in general, one of the main advantages of a framework such as CakePHP is that you can reuse code a lot. A good example of this is CakePHP's scaffolding, which automatically makes all of your project's CRUD related screens for you. However, scaffolding isn't something you want to leave turned on in a finished project, so the generally accepted method is to "bake" your project instead, executing a one-off script that writes all your PHP for you. Personally, I'm not too happy with this either - partly because I'm finicky over my source code, but mostly because writing the same kind of code again and again is exactly the sort of thing I wanted to avoid by learning CakePHP.

Controllers work by letting you write a method for each action of your web site. The way I recommend to go about handling controllers is to put all your default methods - the pages you want to have for pretty much every entity in your project, which roughly translates to those same CRUD screens - into /app/app_controller.php. Then you can simply extend, or even completely override, those methods with custom ones if and when you need to.

9. Appendices

Reserved words

When naming tables and their corresponding models, you should stick to things that you can collect into a group, such as people, products or blogs. Generally, it's much easier to just pick a simple, self explanatory noun. Be careful, however, to avoid nouns that mean something special to PHP, SQL, MySQL or CakePHP.

Below is a brief list of some of the more popular reserved words to watch out for. It's still a good idea to check the comprehensive lists, as I haven't included every single word that could be conceivably used as a model name. If you're cataloguing your collection of seashells, for instance, beware that I haven't listed shell as a reserved word even though it is one. On the other hand, I have listed model because it's something you're likely to think of using to store products, which is what a lot of people might use their CakePHP based site to do.

Word Alternative Used by
class type PHP
component part, piece CakePHP
condition state MySQL
database database system MySQL
file document CakePHP
folder directory CakePHP
group team MySQL
label tag MySQL
model product CakePHP
permission ability CakePHP
release version MySQL