Blog Tutorial
Welcome to CakePHP.
You’re probably checking out this tutorial because you want to learn more about
how CakePHP works. It’s our aim to increase productivity and make coding more
enjoyable: we hope you’ll see this as you dive into the code.
This tutorial will walk
you through the creation of a simple blog application. We’ll be getting and
installing CakePHP, creating and configuring a database, and creating enough
application logic to list, add, edit, and delete blog posts.
Here’s what you’ll need:
1. A running web server. We’re going to assume
you’re using Apache, though the instructions for using other servers should be
very similar. We might have to play a little with the server configuration, but
most folks can get CakePHP up and running without any configuration at all.
Make sure you have PHP 5.2.8 or greater.
2.
A database server. We’re
going to be using MySQL server in this tutorial. You’ll need to know enough
about SQL in order to create a database: CakePHP will be taking the reins from
there. Since we’re using MySQL, also make sure that you have pdo_mysql enabled in PHP.
3. Basic PHP knowledge. The more object-oriented
programming you’ve done, the better: but fear not if you’re a procedural fan.
4.
Finally, you’ll need a
basic knowledge of the MVC programming pattern. A quick overview can be found inUnderstanding Model-View-Controller. Don’t worry, it’s
only a half a page or so.
Let’s get started!
Getting CakePHP
First, let’s get a copy of fresh CakePHP code.
You can also clone the repository using git. git clone git://github.com/cakephp/cakephp.git
Regardless of how you downloaded it, place the code inside of your
DocumentRoot. Once finished, your directory setup should look something like
the following:
/path_to_document_root
/app
/lib
/plugins
/vendors
.htaccess
index.php
README
Now might be a good time to learn a bit about how CakePHP’s
directory structure works: check out the CakePHP Folder Structure section.
Tmp directory permissions
Next we’ll need to make the app/tmp directory writable
by the webserver. The best way to do this is to find out what user your
webserver runs as. You can run <?php echo exec('whoami'); ?> inside any PHP file your webserver can execute. You should
see a username printed. Change the ownership of the app/tmp directory to that user. The final command you run (in *nix)
might look something like this:
$ chown -R www-data app/tmp
If for some reason CakePHP can’t write to that directory, you’ll
see warnings and uncaught exceptions that cache data cannot be written.
Creating the Blog Database
Next, let’s set up the underlying database for our blog. If you
haven’t already done so, create an empty database for use in this tutorial,
with a name of your choice. Right now, we’ll just create a single table to
store our posts. We’ll also throw in a few posts right now to use for testing
purposes. Execute the following SQL statements into your database:
/* First, create our posts
table: */
CREATE TABLE posts (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(50),
body TEXT,
created DATETIME DEFAULT NULL,
modified DATETIME DEFAULT NULL
);
/* Then insert some posts for
testing: */
INSERT INTO posts (title,body,created)
VALUES ('The title', 'This is the post body.', NOW());
INSERT INTO posts (title,body,created)
VALUES ('A title once again', 'And the post body follows.', NOW());
INSERT INTO posts (title,body,created)
VALUES ('Title strikes back', 'This is really exciting! Not.', NOW());
The choices on table and column names are not arbitrary. If you
follow CakePHP’s database naming conventions, and CakePHP’s class naming
conventions (both outlined in CakePHP Conventions), you’ll be able to take advantage of
a lot of free functionality and avoid configuration. CakePHP is flexible enough
to accommodate even the worst legacy database schema, but adhering to
convention will save you time.
Check out CakePHP Conventions for more information, but suffice
it to say that naming our table ‘posts’ automatically hooks it to our Post
model, and having fields called ‘modified’ and ‘created’ will be automagically
managed by CakePHP.
CakePHP Database Configuration
Onward and upward: let’s tell CakePHP where our database is and
how to connect to it. For many, this is the first and last time you configure
anything.
A copy of CakePHP’s database configuration file is found in /app/Config/database.php.default. Make a copy of this file in the same
directory, but name it database.php.
The config file should be pretty straightforward: just replace the
values in the $default array with
those that apply to your setup. A sample completed configuration array might
look something like the following:
public $default = array(
'datasource' => 'Database/Mysql',
'persistent' => false,
'host' => 'localhost',
'port' => '',
'login' => 'cakeBlog',
'password' => 'c4k3-rUl3Z',
'database' => 'cake_blog_tutorial',
'schema' => '',
'prefix' => '',
'encoding' => 'utf8'
);
Once you’ve saved your new database.php file, you
should be able to open your browser and see the CakePHP welcome page. It should
also tell you that your database connection file was found, and that CakePHP
can successfully connect to the database.
Note
Remember that you’ll need to have PDO, and pdo_mysql enabled in
your php.ini.
Optional Configuration
There are a few other items that can be configured. Most
developers complete these laundry-list items, but they’re not required for this
tutorial. One is defining a custom string (or “salt”) for use in security
hashes. The second is defining a custom number (or “seed”) for use in
encryption.
The security salt is used for generating hashes. Change the
default Security.salt value in/app/Config/core.php. The replacement value should be long,
hard to guess and be as random as you can make it:
/**
* A random string used in security hashing
methods.
*/
Configure::write('Security.salt', 'pl345e-P45s_7h3*S@l7!');
The cipher seed is used for encrypt/decrypt strings. Change the
default Security.cipherSeed value by
editing/app/Config/core.php. The replacement
value should be a large random integer:
/**
* A random numeric string (digits only) used
to encrypt/decrypt strings.
*/
Configure::write('Security.cipherSeed', '7485712659625147843639846751');
A Note on mod_rewrite
Occasionally new users will run into mod_rewrite issues. For
example if the CakePHP welcome page looks a little funny (no images or CSS
styles), it probably means mod_rewrite is not functioning on your system.
Please refer to one of the sections below about URL rewriting for your
webserver to get you up and running:
Blog Tutorial - Adding a layer
Create a Post Model
The Model class is the bread and butter of CakePHP applications.
By creating a CakePHP model that will interact with our database, we’ll have
the foundation in place needed to do our view, add, edit, and delete operations
later.
CakePHP’s model class files go in /app/Model, and the file we’ll be creating will be saved to /app/Model/Post.php. The completed file should look like
this:
class Post extends AppModel {
}
Naming conventions are very important in CakePHP. By naming our
model Post, CakePHP can automatically infer that this model will be used in the
PostsController, and will be tied to a database table called posts.
Note
CakePHP will dynamically create a model object for you if it
cannot find a corresponding file in /app/Model. This also means that if you
accidentally name your file wrong (for example, post.php or posts.php instead
of Post.php), CakePHP will not recognize any of your settings and will use the
defaults instead.
For more on models, such as table prefixes, callbacks, and
validation, check out the Models chapter
of the Manual.
Create a Posts Controller
Next, we’ll create a controller for our posts. The controller is
where all the business logic for post interaction will happen. In a nutshell,
it’s the place where you play with the models and get post-related work done.
We’ll place this new controller in a file called PostsController.php inside the /app/Controller directory. Here’s what the basic controller should
look like:
class PostsController extends AppController {
public $helpers = array('Html', 'Form');
}
Now, let’s add an action to our controller. Actions often
represent a single function or interface in an application. For example, when
users request www.example.com/posts/index (which is the same as
www.example.com/posts/), they might expect to see a listing of posts. The code
for that action would look something like this:
class PostsController extends AppController {
public $helpers = array('Html', 'Form');
public function index() {
$this->set('posts', $this->Post->find('all'));
}
}
By defining function index() in our
PostsController, users can access the logic there by requesting
www.example.com/posts/index. Similarly, if we were to define a function
called foobar(), users would be able
to access that at www.example.com/posts/foobar.
Warning
You may be tempted to name your controllers and actions a certain
way to obtain a certain URL. Resist that temptation. Follow CakePHP conventions
(capitalization, plural names, etc.) and create readable, understandable action
names. You can map URLs to your code using “routes” covered later on.
The single instruction in the action uses set() to pass data from the controller to the view (which we’ll
create next). The line sets the view variable called ‘posts’ equal to the
return value of the find('all') method of the
Post model. Our Post model is automatically available at $this->Post because we’ve followed CakePHP’s naming conventions.
To learn more about CakePHP’s controllers, check out the Controllers chapter.
Creating Post Views
Now that we have our data flowing to our model, and our
application logic and flow defined by our controller, let’s create a view for
the index action we created above.
CakePHP views are just presentation-flavored fragments that fit
inside an application’s layout. For most applications, they’re HTML mixed with
PHP, but they may end up as XML, CSV, or even binary data.
A layout is presentation code that is wrapped around a view.
Multiple layouts can be defined, and you can switch between them, but for now,
let’s just use the default.
Remember how in the last section we assigned the ‘posts’ variable
to the view using the set() method? That
would pass data to the view that would look something like this:
// print_r($posts) output:
Array
(
[0] => Array
(
[Post] => Array
(
[id] => 1
[title] => The title
[body] => This is the post body.
[created] => 2008-02-13 18:34:55
[modified] =>
)
)
[1] => Array
(
[Post] => Array
(
[id] => 2
[title] => A title once again
[body] => And the post body follows.
[created] => 2008-02-13 18:34:56
[modified] =>
)
)
[2] => Array
(
[Post] => Array
(
[id] => 3
[title] => Title strikes back
[body] => This is really exciting! Not.
[created] => 2008-02-13 18:34:57
[modified] =>
)
)
)
CakePHP’s view files are stored in /app/View inside a folder named after the controller to which they
correspond. (We’ll have to create a folder named ‘Posts’ in this case.) To
format this post data into a nice table, our view code might look something
like this
<!-- File:
/app/View/Posts/index.ctp -->
<h1>Blog posts</h1>
<table>
<tr>
<th>Id</th>
<th>Title</th>
<th>Created</th>
</tr>
<!-- Here is where we loop through our
$posts array, printing out post info -->
<?php foreach ($posts as $post): ?>
<tr>
<td><?php echo $post['Post']['id']; ?></td>
<td>
<?php echo $this->Html->link($post['Post']['title'],
array('controller' => 'posts', 'action' => 'view', $post['Post']['id'])); ?>
</td>
<td><?php echo $post['Post']['created']; ?></td>
</tr>
<?php endforeach; ?>
<?php unset($post); ?>
</table>
You might have noticed the use of an object called $this->Html. This is an instance of the CakePHP HtmlHelperclass.
CakePHP comes with a set of view helpers that make things like linking, form
output, JavaScript and AJAX a snap. You can learn more about how to use them
in Helpers, but what’s important to note here is that
the link()method will generate
an HTML link with the given title (the first parameter) and URL (the second parameter).
When specifying URLs in CakePHP, it is recommended that you use
the array format. This is explained in more detail in the section on Routes.
Using the array format for URLs allows you to take advantage of CakePHP’s
reverse routing capabilities. You can also specify URLs relative to the base of
the application in the form of /controller/action/param1/param2.
At this point, you should be able to point your browser to http://www.example.com/posts/index. You should see
your view, correctly formatted with the title and table listing of the posts.
If you happened to have clicked on one of the links we created in
this view (which link a post’s title to a URL /posts/view/some_id), you were
probably informed by CakePHP that the action hadn’t yet been defined. If you
were not so informed, either something has gone wrong, or you actually did
define it already, in which case you are very sneaky. Otherwise, we’ll create
it in the PostsController now:
// File: /app/Controller/PostsController.php
class PostsController extends AppController {
public $helpers = array('Html', 'Form');
public function index() {
$this->set('posts', $this->Post->find('all'));
}
public function view($id = null) {
if (!$id) {
throw new NotFoundException(__('Invalid post'));
}
$post = $this->Post->findById($id);
if (!$post) {
throw new NotFoundException(__('Invalid post'));
}
$this->set('post', $post);
}
}
The set() call should
look familiar. Notice we’re using findById() rather
than find('all') because we only
want a single post’s information.
Notice that our view action takes a parameter: the ID of the post
we’d like to see. This parameter is handed to the action through the requested
URL. If a user requests /posts/view/3, then the value ‘3’
is passed as $id.
We also do a bit of error checking to ensure that a user is
actually accessing a record. If a user requests/posts/view, we will throw
a NotFoundException and let the
CakePHP ErrorHandler take over. We also perform a similar check to make sure
the user has accessed a record that exists.
Now let’s create the view for our new ‘view’ action and place it
in /app/View/Posts/view.ctp
<!-- File:
/app/View/Posts/view.ctp -->
<h1><?php echo h($post['Post']['title']); ?></h1>
<p><small>Created: <?php echo $post['Post']['created']; ?></small></p>
<p><?php echo h($post['Post']['body']); ?></p>
Verify that this is working by trying the links at /posts/index or manually requesting a post by accessing/posts/view/1.
Adding Posts
Reading from the database and showing us the posts is a great
start, but let’s allow for adding new posts.
First, start by creating an add() action in the
PostsController:
class PostsController extends AppController {
public $helpers = array('Html', 'Form', 'Session');
public $components = array('Session');
public function index() {
$this->set('posts', $this->Post->find('all'));
}
public function view($id) {
if (!$id) {
throw new NotFoundException(__('Invalid post'));
}
$post = $this->Post->findById($id);
if (!$post) {
throw new NotFoundException(__('Invalid post'));
}
$this->set('post', $post);
}
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.'));
}
}
}
Note
$this->request->is() takes a single
argument, which can be the request METHOD (get, put, post,delete) or some request
identifier (ajax). It is not a way to check for specific posted data. For instance,$this->request->is('book') will not return true if book data
was posted.
Note
You need to include the SessionComponent - and SessionHelper - in
any controller where you will use it. If necessary, include it in your
AppController.
Here’s what the add() action does: if
the HTTP method of the request was POST, it tries to save the data using the
Post model. If for some reason it doesn’t save, it just renders the view. This
gives us a chance to show the user validation errors or other warnings.
Every CakePHP request includes a CakeRequest object which is accessible using $this->request. The request object contains useful
information regarding the request that was just received, and can be used to
control the flow of your application. In this case, we use the CakeRequest::is() method
to check that the request is a HTTP POST request.
When a user uses a form to POST data to your application, that
information is available in $this->request->data. You can use
the pr() or debug() functions
to print it out if you want to see what it looks like.
We use the SessionComponent’s SessionComponent::setFlash() method to set a message to a
session variable to be displayed on the page after redirection. In the layout
we have SessionHelper::flash which
displays the message and clears the corresponding session variable. The
controller’s Controller::redirect function
redirects to another URL. The param array('action' => 'index') translates to
URL /posts (that is, the index action of the posts controller). You can refer
to Router::url() function
on the API to see the formats in which you can specify
a URL for various CakePHP functions.
Calling the save() method will
check for validation errors and abort the save if any occur. We’ll discuss how
those errors are handled in the following sections.
We call the create() method first in
order to reset the model state for saving new information. It does not actually
create a record in the database, but clears Model::$id and sets Model::$data
based on your database field defaults.
Data Validation
CakePHP goes a long way toward taking the monotony out of form
input validation. Everyone hates coding up endless forms and their validation
routines. CakePHP makes it easier and faster.
To take advantage of the validation features, you’ll need to use
CakePHP’s FormHelper in your views. TheFormHelper is
available by default to all views at $this->Form.
Here’s our add view:
<!-- File:
/app/View/Posts/add.ctp -->
<h1>Add Post</h1>
<?php
echo $this->Form->create('Post');
echo $this->Form->input('title');
echo $this->Form->input('body', array('rows' => '3'));
echo $this->Form->end('Save Post');
?>
We use the FormHelper to generate the opening tag for an HTML
form. Here’s the HTML that $this->Form->create() generates:
<form id="PostAddForm" method="post" action="/posts/add">
If create() is called with
no parameters supplied, it assumes you are building a form that submits via
POST to the current controller’s add() action
(or edit() action
when id is included in the form data).
The $this->Form->input() method is used
to create form elements of the same name. The first parameter tells CakePHP
which field they correspond to, and the second parameter allows you to specify
a wide array of options - in this case, the number of rows for the textarea.
There’s a bit of introspection and automagic here: input() will output different form elements based on the model field
specified.
The $this->Form->end() call generates
a submit button and ends the form. If a string is supplied as the first
parameter to end(), the FormHelper
outputs a submit button named accordingly along with the closing form tag.
Again, refer to Helpers for more on helpers.
Now let’s go back and update our /app/View/Posts/index.ctp view to include a new “Add Post”
link. Before the<table>, add the following
line:
<?php echo $this->Html->link(
'Add Post',
array('controller' => 'posts', 'action' => 'add')
); ?>
You may be wondering: how do I tell CakePHP about my validation
requirements? Validation rules are defined in the model. Let’s look back at our
Post model and make a few adjustments:
class Post extends AppModel {
public $validate = array(
'title' => array(
'rule' => 'notEmpty'
),
'body' => array(
'rule' => 'notEmpty'
)
);
}
The $validate array tells
CakePHP how to validate your data when the save() method is
called. Here, I’ve specified that both the body and title fields must not be
empty. CakePHP’s validation engine is strong, with a number of pre-built rules
(credit card numbers, email addresses, etc.) and flexibility for adding your
own validation rules. For more information, check the Data Validation.
Now that you have your validation rules in place, use the app to
try to add a post with an empty title or body to see how it works. Since we’ve
used the FormHelper::input() method
of the FormHelper to create our form elements, our validation error messages
will be shown automatically.
Editing Posts
Post editing: here we go. You’re a CakePHP pro by now, so you
should have picked up a pattern. Make the action, then the view. Here’s what
the edit() action of the
PostsController would look like:
public function edit($id = null) {
if (!$id) {
throw new NotFoundException(__('Invalid post'));
}
$post = $this->Post->findById($id);
if (!$post) {
throw new NotFoundException(__('Invalid post'));
}
if ($this->request->is(array('post', 'put'))) {
$this->Post->id = $id;
if ($this->Post->save($this->request->data)) {
$this->Session->setFlash(__('Your post has been updated.'));
return $this->redirect(array('action' => 'index'));
}
$this->Session->setFlash(__('Unable to update your post.'));
}
if (!$this->request->data) {
$this->request->data = $post;
}
}
This action first ensures that the user has tried to access an
existing record. If they haven’t passed in an $idparameter, or the post does not exist, we throw a NotFoundException for the CakePHP ErrorHandler to
take care of.
Next the action checks whether the request is either a POST or a
PUT request. If it is, then we use the POST data to update our Post record, or
kick back and show the user validation errors.
If there is no data set to $this->request->data, we simply set it to
the previously retrieved post.
The edit view might look something like this:
<!-- File:
/app/View/Posts/edit.ctp -->
<h1>Edit Post</h1>
<?php
echo $this->Form->create('Post');
echo $this->Form->input('title');
echo $this->Form->input('body', array('rows' => '3'));
echo $this->Form->input('id', array('type' => 'hidden'));
echo $this->Form->end('Save Post');
?>
This view outputs the edit form (with the values populated), along
with any necessary validation error messages.
One thing to note here: CakePHP will assume that you are editing a
model if the ‘id’ field is present in the data array. If no ‘id’ is present
(look back at our add view), CakePHP will assume that you are inserting a new
model when save() is called.
You can now update your index view with links to edit specific
posts:
<!-- File:
/app/View/Posts/index.ctp (edit links
added) -->
<h1>Blog posts</h1>
<p><?php echo $this->Html->link("Add Post", array('action' => 'add')); ?></p>
<table>
<tr>
<th>Id</th>
<th>Title</th>
<th>Action</th>
<th>Created</th>
</tr>
<!-- Here's where we loop
through our $posts array, printing out post info -->
<?php foreach ($posts as $post): ?>
<tr>
<td><?php echo $post['Post']['id']; ?></td>
<td>
<?php
echo $this->Html->link(
$post['Post']['title'],
array('action' => 'view', $post['Post']['id'])
);
?>
</td>
<td>
<?php
echo $this->Html->link(
'Edit',
array('action' => 'edit', $post['Post']['id'])
);
?>
</td>
<td>
<?php echo $post['Post']['created']; ?>
</td>
</tr>
<?php endforeach; ?>
</table>
Deleting Posts
Next, let’s make a way for users to delete posts. Start with
a delete() action in the
PostsController:
public function delete($id) {
if ($this->request->is('get')) {
throw new MethodNotAllowedException();
}
if ($this->Post->delete($id)) {
$this->Session->setFlash(
__('The post with id: %s has been
deleted.', h($id))
);
return $this->redirect(array('action' => 'index'));
}
}
This logic deletes the post specified by $id, and uses $this->Session->setFlash() to show the user a confirmation
message after redirecting them on to /posts. If the user
attempts to do a delete using a GET request, we throw an Exception. Uncaught
exceptions are captured by CakePHP’s exception handler, and a nice error page
is displayed. There are many built-in Exceptions that can be used to indicate the various
HTTP errors your application might need to generate.
Because we’re just executing some logic and redirecting, this
action has no view. You might want to update your index view with links that
allow users to delete posts, however:
<!-- File:
/app/View/Posts/index.ctp -->
<h1>Blog posts</h1>
<p><?php echo $this->Html->link('Add Post', array('action' => 'add')); ?></p>
<table>
<tr>
<th>Id</th>
<th>Title</th>
<th>Actions</th>
<th>Created</th>
</tr>
<!-- Here's where we loop
through our $posts array, printing out post info -->
<?php foreach ($posts as $post): ?>
<tr>
<td><?php echo $post['Post']['id']; ?></td>
<td>
<?php
echo $this->Html->link(
$post['Post']['title'],
array('action' => 'view', $post['Post']['id'])
);
?>
</td>
<td>
<?php
echo $this->Form->postLink(
'Delete',
array('action' => 'delete', $post['Post']['id']),
array('confirm' => 'Are you sure?')
);
?>
<?php
echo $this->Html->link(
'Edit', array('action' => 'edit', $post['Post']['id'])
);
?>
</td>
<td>
<?php echo $post['Post']['created']; ?>
</td>
</tr>
<?php endforeach; ?>
</table>
Using postLink() will
create a link that uses JavaScript to do a POST request to delete our post.
Allowing content to be deleted using GET requests is dangerous, as web crawlers
could accidentally delete all your content.
Note
This view code also uses the FormHelper to prompt the user with a
JavaScript confirmation dialog before they attempt to delete a post.
Routes
For some, CakePHP’s default routing works well enough. Developers
who are sensitive to user-friendliness and general search engine compatibility
will appreciate the way that CakePHP’s URLs map to specific actions. So we’ll
just make a quick change to routes in this tutorial.
By default, CakePHP responds to a request for the root of your
site (e.g., http://www.example.com) using its PagesController,
rendering a view called “home”. Instead, we’ll replace this with our
PostsController by creating a routing rule.
CakePHP’s routing is found in /app/Config/routes.php. You’ll want to
comment out or remove the line that defines the default root route. It looks
like this:
Router::connect(
'/',
array('controller' => 'pages', 'action'
=> 'display', 'home')
);
This line connects the URL ‘/’ with the default CakePHP home page.
We want it to connect with our own controller, so replace that line with this
one:
Router::connect('/', array('controller' => 'posts', 'action' => 'index'));
This should connect users requesting ‘/’ to the index() action of
our PostsController.
Note
CakePHP also makes use of ‘reverse routing’. If, with the above
route defined, you passarray('controller' => 'posts', 'action' => 'index') to a function expecting an array, the resulting URL used
will be ‘/’. It’s therefore a good idea to always use arrays for URLs, as this
means your routes define where a URL goes, and also ensures that links point to
the same place.
Conclusion
Creating applications this way will win you peace, honor, love,
and money beyond even your wildest fantasies. Simple, isn’t it? Keep in mind
that this tutorial was very basic. CakePHP has many more features to offer, and is flexible in ways we didn’t
wish to cover here for simplicity’s sake. Use the rest of this manual as a
guide for building more feature-rich applications.
Now that you’ve created a basic CakePHP application, you’re ready
for the real thing. Start your own project and read the rest of the Cookbook and API.
If you need help, there are many ways to get the help you need -
please see the Where to Get Help page. Welcome to CakePHP!
No comments:
Post a Comment