欢迎来到CakePHP的世界!你也许正在看这篇指导文章,因为你想更多地了解CakePHP是如何工作的。我们的目标就是增加程序的生产效率并且让代码更有趣:我们希望当你编写代码的时候会明白这一点。
这篇文章将带你创建一个简单的博客程序。我们将先获得并安装CakePHP,然后配置一个数据库,接着创建一个能够展示、添加、编辑并且删除文章的程序。
下面这些是你所需要的:
下面就让我们开始吧。
首先,让我们获得一个新的CakePHP拷贝。
访问在Cakeforge上的CakePHP项目:http://cakeforge.org/projects/cakephp/下载稳定版本。在这里你需要1.2.x.x
你也可以查看/导出一个新的拷贝在我们的trun上: https://svn.cakephp.org/repo/trunk/cake/1.2.x.x/
不管你是怎么下载它的,把它放到你自己的文档目录里。这些完成后你的目录看起来应该是下面这个样子的:
/path_to_document_root
/app
/cake
/docs
/vendors
.htaccess
index.php
现在正是可以学习下CakePHP的目录结构是如何工作的:查看《CakePHP文件结构》。
接下来,为我们的博客建立一个数据库。现在我们只需要创建一个表来储存我们的文章。作为检测我还将添加一些文章进去。在你的数据库里执行下面的SQL语句:
/* 首先,创建我们的posts表: */
CREATE TABLE posts (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(50),
body TEXT,
created DATETIME DEFAULT NULL,
modified DATETIME DEFAULT NULL
);
/* 然后添加一些文章作为测试: */
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());
表和字段的名字都不是随意取的。如果你遵守CakePHP的数据库和类命名规则(《CakePHP 规则》),你将能使用很多函数功能并且不需要配置。CakePHP能足够灵活的兼容那些糟糕的数据库设计,但是依靠一些约定能节省你的时间。
查看《CakePHP 规则》获得更多信息。将我们的表命名为'posts'是符合规则的,它会自动跟我们的Post模型挂钩,而'modified'和'created'字段也会自动被CakePHP处理。
继续:让我们告诉CakePHP我们数据库在哪里并且如何链接它。大多数情况下这是你第一次也是最后一次配置这一切。
CakePHP的数据库配置文件在/app/config/database.php.default。在同一目录下复制这个文件并且命名为database.php。
配置文件非常简单明了:只需要把$default数组里的值替换成你自己的设置。一个已完成的配置应该看起来是下面这个样子:
var $default = array(
'driver' => 'mysql',
'persistent' => 'false',
'host' => 'localhost',
'port' => '',
'login' => 'cakeBlog',
'password' => 'c4k3-rUl3Z',
'database' => 'cake_blog_tutorial',
'schema' => '',
'prefix' => '',
'encoding' => ''
);
现在,保存database.php文件。你可以打开你的浏览器,并且看到CakePHP的欢迎页面。它会告诉你的数据库配置文件被发现并且CakePHP成功连接了数据库。
另外还有两个项目可以设置。大多数开发者不需要本文就能搞定。一是定义用于安全散列的字符串(或者叫做“salt”),二是设置CakePHP能对它的tmp文件夹执行写操作。
更改/app/config/core.php 153行的值。不管新的值是什么,越长就越不容易被猜到。
<?php
/**
* A random string used in security hashing methods.
*/
Configure::write('Security.salt', 'pl345e-P45s_7h3*S@l7!');
?>
最后的问题是把app/tmp目录设置成能被网页写入。最好的方法是通过运行“<?php echo 'whoami'; ?>”来找到网页服务的所有者,然后把app/tmp目录的所有权设置成该目录。在*nix下的命令可能看起来是这个样子的(我是直接把tmp的权限设为777的,原文这么写我就照翻了)。
$ chown -R www-data app/tmp
如果因为某些原因CakePHP不能写这个目录,你将被在程序运行的时候得到一个警告。
有时候新用户会遇上mod_rewrite的问题,所以在这里我促略的提一下。如果CakePHP的欢迎页面看起来有点怪怪的(没图片或者没有CSS样式),这可能就意味着mod_rewrite并没有在你的系统上起作用。下面一些将帮助你运行它:
如果你不想使用或不能使用mod_rewirte(或者其他一些兼容的模块)在你的服务器上,你需要使用CakePHP构建干净的URL。在/app/config/core.php里,取消下面这行的注释:
Configure::write('App.baseUrl', env('SCRIPT_NAME'));
并且删除.htaccess文件:
/.htaccess
/app/.htaccess
/app/webroot/.htaccess
这将你的URL看起来像“www.example.com/index.php/controllername/actionname/param”而不是“www.example.com/controllername/actionname/param”。
模型好比是CakePHP程序的血肉。通过创建CakePHP模型来于数据库互通,这样我们就有了在适当的位置查看、添加、编辑以及删除的基础。
CakePHP的模型文件被放置在/app/models,我们创建的文件将被保存在/app/models/post.php。完整的文件应该看起来是这个样子的:
<?php
class Post extends AppModel {
var $name = 'Post';
}
?>
在CakePHP里命名规则是很重要的。通过我们的Post模型,CakePHP能自动判断出这个模型将被用在PostsController,并且关联到叫做posts数据库表。
提示:如果CakePHP不能在/app/models下找到相应的文件,它会自动为你创建模型块。这也意味着,不小心错误地命名了你的文件(例如:Post.php或者posts.php)CakePHP将使用默认设置,而忽略你的任何设置。
最好加上$name变量,以用来区别PHP4一些奇怪的类名。
更多关于模型的内容,例如表前缀、回调以及检验,请查看手册里的模型章节。
下面我将为为什么的文章创建一个控制器。当交互发生时控制器将处理所有的post逻辑。我们将在/app/controllers目录下的posts_controller.php文件里新建一个控制器。一个基本的控制器看起来应该是这样子:
<?php
class PostsController extends AppController {
var $name = 'Posts';
}
?>
现在,让我们在控制器里添加一个动作。在程序里一个动作往往是一个单独的方法或者借口。例如,当用户访问www.example.com/posts/index (等同于www.example.com/posts/)是,他们希望得到一个文章的列表。那么动作的代码看起来应该是这个样子:
<?php
class PostsController extends AppController {
var $name = 'Posts';
function index() {
$this->set('posts', $this->Post->find('all'));
}
}
?>
让我来稍微解释下这个动作。通过在我们在PostsController里定义index()方法,当用户访问www.example.com/posts/index时能得到正确逻辑处理。同理,如果我们定义一个foobar()的方法的话,用户就能访问www.example.com/posts/foobar。
You may be tempted to name your controllers and actions a certain way to obtain a certain URL. Resist that temptation. Follow CakePHP conventions (plural controller names, etc.) and create readable, understandable action names. You can map URLs to your code using "routes" covered later on.(这段不不大理解)。
另外说明下动作里使用set()来把控制器的数据传递给视图(我们等下要创建的)。这行将一个视图参数设置为Post模型find('all')函数返回的值。我们的Post模型会自动获得$this->Post的值因为我们遵循了CakePHP的命名规则。
学习更多关于CakePHP控制器的内容,请查看"Controllers"。
现在我们已经有了数据模型、程序逻辑并定义控制器,完成以上这些后让我们再为index动作创建一个视图。
CakePHP的视图只是一些适用于程序页面布局的代码片段。对于大多数程序来说都是HTML跟PHP混合编写的,但是他们也有可能是以XML、CSV亦或两种混合的方式结尾。
Layouts are presentation code that is wrapped around a view, and can be defined and switched between, but for now, let's just use the default.
回忆一下上一章我们是如何通过set()函数把'posts'的值传给视图的。这些传给视图的数据看起来就像这样:
// 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的视图文件被放在/app/views的一个根据它所属的控制器命名的目录里(因此我要创建一个名为'post'的目录)。把文章的数据放在一个漂亮的表格里,我们的视图代码开起来是这样的:
<!-- File: /app/views/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 $html->link($post['Post']['title'],
"/posts/view/".$post['Post']['id']); ?>
</td>
<td><?php echo $post['Post']['created']; ?></td>
</tr>
<?php endforeach; ?>
</table>
希望这些还算简单。
你也许注意到这里使用了一个叫$html的对象。这是一个HtmlHelper类的实例。CakePHP内置例如超链接、表单布局、JavaScript以及AJAX的视图助手(原文CakePHP comes with a set of view helpers that make things like linking, form output, JavaScript and Ajax a snap.翻不好= =!)。你可以在"Built-in Helpers"这节里学到更多关于怎么使用他们,但是在这里需要重点指出的是link()函数将创建一个超链接,在指定标题(第一个参数)和URL(第二个参数)的情况下。当在CakePHP里指定URL的时候,你只需简单的给出一个相对路径。比如,你的URL可能是这样:/controller/action/参数1/参数2。
现在,你可以用你的浏览器访问http://www.example.com/posts/index。你将看到你的视图被正确的显示。
如果当你在这个视图里点击其中一个链接的时候(文章标题的URL /posts/view/some_id),你可能被通知动作未被定义。如果没有,可能是是哪里出错了,或者事实上你已经私下定义了它。
不管怎么说,我们现在将再PostsController里创建它:
<?php
class PostsController extends AppController {
var $name = 'Posts';
function index() {
$this->set('posts', $this->Post->find('all'));
}
function view($id = null) {
$this->Post->id = $id;
$this->set('post', $this->Post->read());
}
}
?>
set()函数看起来是差不多的。注意我们使用read()而不是find('all')因为我只需要一篇文章的信息。
注意view这个动作需要一个参数:文章的ID。这个参数的值是通过URL获得的,如果一个用户请求/posts/view/3,那么ID的值就是3。。
现在让为什么为view动作创建一个新视图在in /app/views/posts/view.ctp。
<!-- File: /app/views/posts/view.ctp -->
<h1><?php echo $post['Post']['title']?></h1>
<p><small>Created: <?php echo $post['Post']['created']?></small></p>
<p><?php echo $post['Post']['body']?></p>
现在在/posts/index上检验一下这些链接或者直接打开/posts/view/1试试。
现在我们已经有了数据模型、程序逻辑并定义控制器,完成以上这些后让我们再为index动作创建一个视图。
CakePHP的视图只是一些适用于程序页面布局的代码片段。对于大多数程序来说都是HTML跟PHP混合编写的,但是他们也有可能是以XML、CSV亦或两种混合的方式结尾。
Layouts are presentation code that is wrapped around a view, and can be defined and switched between, but for now, let's just use the default.
回忆一下上一章我们是如何通过set()函数把'posts'的值传给视图的。这些传给视图的数据看起来就像这样:
// 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的视图文件被放在/app/views的一个根据它所属的控制器命名的目录里(因此我要创建一个名为'post'的目录)。把文章的数据放在一个漂亮的表格里,我们的视图代码开起来是这样的:
<!-- File: /app/views/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 $html->link($post['Post']['title'],
"/posts/view/".$post['Post']['id']); ?>
</td>
<td><?php echo $post['Post']['created']; ?></td>
</tr>
<?php endforeach; ?>
</table>
希望这些还算简单。
你 也许注意到这里使用了一个叫$html的对象。这是一个HtmlHelper类的实例。CakePHP内置例如超链接、表单布局、JavaScript以 及AJAX的视图助手(原文CakePHP comes with a set of view helpers that make things like linking, form output, JavaScript and Ajax a snap.翻不好= =!)。你可以在"Built-in Helpers"这节里学到更多关于怎么使用他们,但是在这里需要重点指出的是link()函数将创建一个超链接,在指定标题(第一个参数)和URL(第 二个参数)的情况下。当在CakePHP里指定URL的时候,你只需简单的给出一个相对路径。比如,你的URL可能是这样:/controller /action/参数1/参数2。
现在,你可以用你的浏览器访问http://www.example.com/posts/index。你将看到你的视图被正确的显示。
如果当你在这个视图里点击其中一个链接的时候(文章标题的URL /posts/view/some_id),你可能被通知动作未被定义。如果没有,可能是是哪里出错了,或者事实上你已经私下定义了它。
不管怎么说,我们现在将再PostsController里创建它:
<?php
class PostsController extends AppController {
var $name = 'Posts';
function index() {
$this->set('posts', $this->Post->find('all'));
}
function view($id = null) {
$this->Post->id = $id;
$this->set('post', $this->Post->read());
}
}
?>
set()函数看起来是差不多的。注意我们使用read()而不是find('all')因为我只需要一篇文章的信息。
注意view这个动作需要一个参数:文章的ID。这个参数的值是通过URL获得的,如果一个用户请求/posts/view/3,那么ID的值就是3。。
现在让为什么为view动作创建一个新视图在in /app/views/posts/view.ctp。
<!-- File: /app/views/posts/view.ctp -->
<h1><?php echo $post['Post']['title']?></h1>
<p><small>Created: <?php echo $post['Post']['created']?></small></p>
<p><?php echo $post['Post']['body']?></p>
现在在/posts/index上检验一下这些链接或者直接打开/posts/view/1试试。
首先,在PostController里添加一个add()动作:
<?php
class PostsController extends AppController {
var $name = 'Posts';
function index() {
$this->set('posts', $this->Post->find('all'));
}
function view($id) {
$this->Post->id = $id;
$this->set('post', $this->Post->read());
}
function add() {
if (!empty($this->data)) {
if ($this->Post->save($this->data)) {
$this->Session->setFlash('Your post has been saved.');
$this->redirect(array('action' => 'index'));
}
}
}
}
?>
这里说一下add()做了些什么:如果提交的数据不是空的,就尝试用Post模型储存数据。如果因为某些原因无法储存则返回index视图。这里我们就可以向用户显示检验后的错误或警告。
当用户通过表单向你的程序POST数据时,所提交的信息被储存在$this->data。如果你想看看这是什么样子的话。你可以用pr()或者debug函数来把他们打印出来。
CakePHP可以让数据校验更加简单快速。你需要在你的视图里使用CakePHP的FormHelper。默认情况下FormHelper会被使用到所有$form部分。
这是添加文章的视图代码:
<!-- File: /app/views/posts/add.ctp -->
<h1>Add Post</h1>
<?php
echo $form->create('Post');
echo $form->input('title');
echo $form->input('body', array('rows' => '3'));
echo $form->end('Save Post');
?>
这里我使用FormHelper生成未关闭的HTML的form标签。这就是$form->create('Post')生成的HTML代码。
<form id="PostAddForm" method="post" action="/posts/add">
如果create()没有指定参数的话,它会默认把数据POST给当前控制器的add()动作(如果在表单数据里包含了id,那么就提交给edit()动作)。
$form->input()函数被用来创建一个同名的元素标签。第一个参数告诉CakePHP这个标签的类型,第二个参数是设置表单的宽高的一个数组。
$form->end()生成一个提交按钮。第一个参数就是按钮的名字。
现在让我们更新下/app/views/posts/index.ctp,在里面添加一个新的“Add Post”链接。在<table>前面,添加如下行:
<?php echo $html->link('Add Post',array('controller' => 'posts', 'action' => 'add'))?>
你也许会想,我改如何告诉CakePHP我的验证规则呢?验证规则默认是被定义在模型里的。让我们回过头看看我们的Post模型,然后做些小调整:
<?php
class Post extends AppModel
{
var $name = 'Post';
var $validate = array(
'title' => array(
'rule' => 'notEmpty'
),
'body' => array(
'rule' => 'notEmpty'
)
);
}
?>
$validate数组告诉了CakePHP当save()函数运行时改如何校验你的数据。这里我指定了title和body都不允许留空。
现在,让我们为用户添加一个删除文中的功能。首先在PostsController下添加一个delete动作:
function delete($id) {
$this->Post->del($id);
$this->Session->setFlash('The post with id: '.$id.' has been deleted.');
$this->redirect(array('action'=>'index'));
}
这个逻辑通过指定的$id删除文章,并且使用$this->Session->setFlash()来向用户显示操作信息当页面被重定向到/posts后。
因为这个动作只是执行一些逻辑和重定向,所以它并没有视图。下面是在主视图添加删除链接:
/app/views/posts/index.ctp
<h1>Blog posts</h1>
<p><?php echo $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 $html->link($post['Post']['title'], array('action' => 'view', 'id' => $post['Post']['id']));?>
</td>
<td>
<?php echo $html->link('Delete', array('action' => 'delete', 'id' => $post['Post']['id']), null, 'Are you sure?' )?>
</td>
<td><?php echo $post['Post']['created']; ?></td>
</tr>
<?php endforeach; ?>
</table>
这里同样使用了HtmlHelper来生成一个JavaScript对话框。
编辑文章:让我们开始吧,你现在已经是CakePHP专家了(= =!老外真会说好听的),所以你应该知道该怎么做了。创建一个动作,然后是视图。下面就是PostsController里的edit()动作:
function edit($id = null) {
$this->Post->id = $id;
if (empty($this->data)) {
$this->data = $this->Post->read();
} else {
if ($this->Post->save($this->data)) {
$this->Session->setFlash('Your post has been updated.');
$this->redirect(array('action' => 'index'));
}
}
}
这个动作首先是检查表单提交的数据。如果什么都有它就会提示错误。如果有数据提交,它就会尝试用Post模型储存数据(或者向用户返回校验后的错误)。
编辑页面的代码:
/app/views/posts/edit.ctp
<h1>Edit Post</h1>
<?php
echo $form->create('Post', array('action' => 'edit'));
echo $form->input('title');
echo $form->input('body', array('rows' => '3'));
echo $form->input('id', array('type'=>'hidden'));
echo $form->end('Save Post');
?>
这个页面显示了编辑表单和一些必需的校验错误信息。
有一点要注意的是:当数据数组里存在'id'的时候,CakePHP就会认为你是在编辑1个模块,反之,如果没有'id'则被认为是在添加一个新的模块。
你现在可以把你的主页面编辑成这样:
/app/views/posts/index.ctp (edit links added)
<h1>Blog posts</h1>
<p><?php echo $html->link("Add Post", array('action'=>'add')); ?>
<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 $html->link($post['Post']['title'],array('action'=>'view', 'id'=>$post['Post']['id']));?>
</td>
<td>
<?php echo $html->link(
'Delete',
array('action'=>'delete', 'id'=>$post['Post']['id']),
null,
'Are you sure?'
)?>
<?php echo $html->link('Edit', array('action'=>'edit', 'id'=>$post['Post']['id']));?>
</td>
<td><?php echo $post['Post']['created']; ?></td>
</tr>
<?php endforeach; ?>
</table>
【本文翻译仅为外语学习及阅读目的,原文作者个人观点与译者及译言网无关】