CakePHP

快速开发框架

John David Anderson, Cake Documentation Team, cake [at] johndavidanderson [dot] net

Version 0.10.x


目录

读者
自由的 CakePHP
社区
1. CakePHP 简介
CakePHP 是什么?
为何使用 CakePHP ?
CakePHP 的历史
2. 基本概念
MVC 模式
Cake的文件结构一览
3. 安装 CakePHP
要求
服务器的要求
安装 CakePHP
获得最新的稳定版本
解压缩
设置
开发方式安装
生产方式安装
高级设置:可选的安装选项
配置 Apache 和 mod_rewrite
确保 CakePHP 正确工作
4. 配置
数据库配置
全局配置
路径设置Routes Configuration
5. Scaffolding
6. 模型
模型函数
用户定义函数
检索数据
保存数据
模型回调函数
模型变量
Associations
介绍
定义和查询 hasOne
belongsTo 的定义和查询
hasMany 的定义和查询
hasAndBelongsToMany 的定义和查询
Saving hasAndBelongsToMany
7. 控制器Controllers
Controller Functions
和视图交互Interacting with your Views
User Redirection
Controller Callbacks
Other Useful Functions其他有用的方法
Controller Variables控制器的变量
Controller Parameters
8. Views
Views
Layouts
Elements
9. Helpers
Helpers
HTML
AJAX
Javascript
Number
Text
Time
Creating Your Own Helpers
Extending the Cake Helper Class
Including other Helpers
Using your Custom Helper
Contributing
10. Cake's Global Constants And Functions
Global Functions
Global Constants
11. Data Validation
Data Validation
12. Access Control Lists
Understanding How ACL Works
Defining Permissions: Cake's INI-based ACL
Defining Permissions: Cake's Database ACL
Getting Started
Creating Access Request Objects (AROs) and Access Control Objects (ACOs)
Assigning Permissions
Checking Permissions: The ACL Component
13. Data Sanitization
Using Sanitize in Your Application
Making Data Safe for use in SQL and HTML
14. The Cake Session Component
Cake Session Storage Options
Using the Cake Session Component
15. The Request Handler Component
Introduction
Getting Client/Request Information
Stripping Data
Other Useful Functions
16. The Security Component
Introduction
Protecting Controller Actions
Handling Invalid Requests
Advanced Request Authentication
The Cake Blog Tutorial
Introduction
Getting Cake
Creating the Blog Database
Cake Database Configuration
A Note On mod_rewrite
Create a Post Model
Create a Posts Controller
Creating Post Views
Adding Posts
Data Validation
Deleting Posts
Editing Posts
Routes
Conclusion
Cake Naming Conventions
Models
Controllers
Views

表格清单

6.1. Model::recursive 选项

范例清单

3.1. 建议的 httpd.conf
3.2. /app/webroot/index.php (部分内容,注释也被删除了)
4.1. app/config/database.php
4.2. Route Pattern
4.3. 路有举例
4.4. Route Handling in a Controller
4.5. 设置默认路由Setting the Default Route
6.1. 用户模型例子,保存在 /app/models/user.php 中
6.2. 范例模型函数
6.3. findBySQL() 的自定义SQL调用
6.4. /app/models/user.php hasOne
6.5. /app/models/profile.php belongsTo
6.6. /app/models/user.php hasMany
6.7. HABTM Join Tables: Sample models and their join table names
6.8. /app/models/post.php hasAndBelongsToMany
6.9. /app/views/posts/add.thtml Form for creating posts
6.10. /app/views/posts/add.thtml (Tag association code added)
8.1. Calling an Element without parameters
8.2. Calling an Element passing a data array
9.1. Edit Action inside of the NotesController
9.2. Edit View code (edit.thtml) sample
9.3. Concatenating time data before saving a model (excerpt from NotesController)
9.4. AjaxHelper $options Keys
9.5. /app/views/helpers/link.php
9.6. /app/views/helpers/link.php (logic added)
9.7. /app/views/helpers/link.php (using other helpers)
11.1. /app/models/user.php
11.2. Form-handling Action in /app/models/blog_controller.php
11.3. A Form in a View in /app/views/blog/add.thtml
12.1. Initializing your database using acl.php
14.1. core.php Session Configuration
1. /app/models/post.php
2. /app/controllers/posts_controller.php
3. /app/controllers/posts_controller.php (index action added)
4. /app/views/posts/index.thtml
5. /app/controllers/posts_controller.php (view action added)
6. /app/views/posts/view.thtml
7. /app/controllers/posts_controller.php (add action added)
8. /app/views/posts/add.thtml
9. /app/models/post.php (validation array added)
10. /app/controllers/posts_controller.php (delete action only)
11. /app/views/posts/index.thtml (add and delete links added)
12. /app/controllers/posts_controller.php (edit action only)
13. /app/views/posts/edit.thtml
14. /app/views/posts/index.thtml (edit links added)

本章是将简短地介绍 MVC 的概念以及他们如何在Cake中实现的。如果你是初学 MVC (模型-视图-控制器),那么你一定要看这一章。我们将从一个通用的 MVC 概念的讨论开始,并逐渐进入特定的 CakePHP 中的 MVC 应用,最后使用MVC模式演示一些简单的 CakePHP 的例子。

模型-视图-控制器(Model-View-Controller)是一种软件设计模式,它可帮助你有逻辑地划分代码,使代码可服用、可维护,一般来说会更好。模型-视图-控制器最先由Gang of Four的作者小组描述出来。Dean Helman 写到(以下节选自Objective Toolkit Pro白皮书):

“MVC 范例是一种分解应用的方法,或者甚至仅仅是一个应用接口的一小部分,它将应用分解成三个部分:模型、视图和控制器。开发 MVC 最先的目的,是用来将原有的输入、处理、输出的角色映射到 GUI 的领域中”

输入 → 处理 → 输出

控制器 → 模型 → 视图

“用户输入,外部世界的模型和给用户的视觉反馈是被模型,视图端口和控制器对象所分开并处理的。控制器解释用户的鼠标、键盘输入,并将这些用户动作映射到发送到模型和/或视图端口的命令,来实现合适的更改。模型管理一个或多个数据元素,响应对它的状态的查询,并响应更改状态的指令。视图端口管理显示的一个矩形的区域,并负责通过图形和文字给用户展示数据。”

在 Cake 的术语中,模型代表了一个特定的数据库表/记录,以及它和其他表和记录之间的关系。模型也可以包含校验规则,它会在插入或者更新模型数据时应用。视图代表了 Cake 的视图文件,这些是一些普通的嵌入了 PHP 代码的 HTML 文件。Cake 的控制器处理服务器的请求。它获取用户输入(URL 和 POST 数据),应用业务逻辑,使用模型来从数据库和其他数据源读取数据或者写入数据,最后,将数据输出到合适和视图文件。

为了能尽可能更容易地组织应用程序,Cake使用了这个模式不仅仅来管理对象如何在应用之中进行交互,还管理了如何存放文件,这些会在下面详细讲解。

当你在服务器上解压开 Cake 安装包,你会发现有四个文件夹—— app, cake, tmp, 和 vendors。其中cake文件夹是 Cake 的核心库所放的地方, 基本上你不需要动它。

其实它并非一直这样—— 在CakePHP的0.10.0版本发布之前,所有东西都是放在在一个文件夹cake下面的,这种结构被证明有一些问题。

  1. 如果你想部署超过一个的Web应用程序,你需要下载并多次安装Cake,这样核心库就重复了,这不仅对空间是一种浪费,同时也违背了CakePHP试图遵循的DRY原则。

  2. 当 CakePHP 新版本发布时,你必须非常小心以免 app 文件夹下面的文件被新版本中的默认文件所覆盖。

app文件夹是你的程序和文件所放的地方。核心库和应用程序文件夹的分离使得你可以有许多程序目录共享同一个 Cake 库。这也使得更新 CakePHP 变得更加容易:你只需要下载最新版本的Cake并覆盖当前的核心库,而不需要担心是否会覆盖掉你为你的应用所写的东西。

tmp目录可以用于不同的 Cake 操作,比如baking(自动创建新的php文件),缓存和日志记录。

你可以使用vendors目录文件夹来存放第三方库文件。稍后你将更多地了解 vendors。

下面的列表显示了主要的文件夹和它们的基本目的:

  • cake (根目录)

    • app (这里存放你的应用程序逻辑)

      • config (特定于应用的配置文件,比如ACL,core, database connection,routes, paths, 和tags)

      • controllers (这里放置控制器)

        • components (组件,也就可以协助控制逻辑的类,存放的地方)

      • index.php (cake中有三个该文件:它令用户可以用不同的方法部署 Cake 应用。)

      • models (放置模型)

      • plugins (放置插件或者第三方应用)

      • tmp (用于日志、baking等等)

      • views (与视图相关文件)

        • elements (小的经常重复使用的布局零件)

        • errors (存放错误页面)

        • helpers (自定义的助手类文件)

        • layouts (页面布局)

        • pages (PagesController管理的静态内容)

      • webroot (这个目录用作网站的根目录,可以并将公共的文件放在这里)

        • css

        • files

        • img

        • js

    • cake (库文件,最好不要动。)

    • index.php

    • vendors (放第三方库文件)

如果你对配置服务器很熟悉,可以跳过这一部分,直接开始。无论如何,你都应该亲自动手尝试一下。

这一章将描述哪些东西必须安装在服务器上,以及配置服务器的几种不同方法,下载和安装 CakePHP,显示出默认的 CakePHP 页面,以及对于一些可能出现的错误的提示。

第一种安装 CakePHP 的方法一般只推荐开发环境使用,因为它安全性低一些。第二种方法要较为安全,在生产环境中应该使用它。

注意

如果你要使用Cake的日志和缓存功能,你还需要将应用程序中的 tmp目录设置为可以被 Web 服务器写。这个目录在 /app/tmp

注意

一些共享主机的用户可能没有权限通过修改 http.conf 来改变 DocumnentRoot 来指向他们的产品安装。在这种情况下,用户可以安装下面的方式,修改 CakePHP的结构。Cake安装在 /path_to_cake_install,文件目录(不可以修改)指向/public_html

某些情况下,你可能要将 Cake 的目录放在磁盘的其他地方。这可能是由于共享主机的一些限制,或者是你想要让几个应用同时共享同一个 Cake 库。

一个 Cake 应用有三个主要的部分:

  1. 核心 CakePHP 库

    • /cake

  2. 应用程序代码(即控制器、模型、布局和视图)

    • /app

  3. 应用程序Web根目录(即图像、JavaScript和CSS)。

    • /app/webroot

这些目录中的每一个都可以放在文件系统的任何位置,除了 webroot,它必须能被你的 Web 服务器读取。你甚至可以将 webroot 目录移出 app 目录,只要告诉 Cake 你把它放在哪里了。

如果要配置你的Cake安装,需要对 /app/webroot/index.php 进行一些修改 。你需要编辑的是三个主要的常量定义:ROOT、APP_DIR和CAKE_CORE_INCLUDE_PATH。

  • ROOT 应被设置为包含 app 目录的路径。

  • APP_DIR 应被设为你的 app 目录的绝对路径。

  • CAKE_CORE_INCLUDE_PATH 应该被设为你的Cake库的文件夹。

使用一个例子就可以更好地说明它。假设我要让 Cake 按照以下设置运行:

  • 我想让我的 Cake 库和其他应用共享,放在 /usr/lib/cake 中。

  • 我的 Cake 的 webroot 目录是 /var/www/mysite/

  • 我的应用程序文件放在 /home/me/mysite 中。

文件布局如下:

/home
    /me
        /mysite                  <-- 原来是 /cake_install/app
            /config
            /controllers
            /index.php
            /models
            /plugins
            /tmp
            /vendors
            /views
/var                              
    /www
        /mysite                  <-- 原来是 /cake_install/app/webroot
            /.htaccess
            /css
            /css.php
            /favicon.ico
            /files
            /img
            /index.php
            /js
/usr
    /lib
        /cake                    <-- 原来是 /cake_install/cake
            /cake
                /app_controller.php
                /app_model.php
                /basics.php
                /bootstrap.php
                /config
                /dispatcher.php
                /docs
                /libs
                /scripts
            /vendors
 

给出了这个安装形式,要修改webroot中的index.php文件(这个例子中,它应该在 /var/www/mysite/index.php)使之如下:

注意

推荐使用“DS”常量而不是斜杠来分隔文件路径。这防止当使用了错误的分隔符会出现的“missing file”(缺少文件)错误,同时也令你的代码更加有移植性。

if (!defined('ROOT'))
{
    define('ROOT', DS.'home'.DS.'me');
}

if (!defined('APP_DIR'))
{
    define ('APP_DIR', 'mysite');
}

if (!defined('CAKE_CORE_INCLUDE_PATH'))
{
    define('CAKE_CORE_INCLUDE_PATH', DS.'usr'.DS.'lib'.DS.'cake');
}

app/config/database.php文件是数据库配置文件。默认安装后并没有database.php文件,所以你需要复制database.php.defaultdatabase.php。之后,你可以看到如下:

根据你应用程序的数据库连接信息,替换默认提供的配置。

CakePHP支持下面的数据库驱动:

mysql
postgres
sqlite
pear-驱动名 (例如,你可以输入pear-mysql)
adodb-驱动名

$default 连接中的 connect 键允许你指定是否需要连接数据库连接作为持久连接,请仔细阅读database.php.default文件中的注释,以配置你的数据库连接类型。

数据库中的表应遵循下面的命名规则:

  • cake使用的表名应该由英文的复数形式组成,如 users, authors, articles。注意,对应的模型使用是单数形式的名称。

  • 所有的表都必须有一个叫做 “id” 的主键。

  • 如果你需要关联表,使用类似“article_id”的外键 。表名是单数,跟下划线,然后是“id”。,必须是小写。

  • 另外,也可以遵从下面的命名规则来使用某些功能:

    • 包含一个 'created' 列

    • 包含一个 'modified' 或者 'updated' 列

你可能注意到了 database.php 文件中还有一个 $test 的连接,填上这个配置(或者添加其他格式类似的配置),然后在你使用的时候将下面的代码放入某个模型中:

var $useDbConfig = 'test';

你可以按照这种方式加入任何数量的额外连接设置。

CakePHP的全局配置在/app/config/core.php文件中。尽管我们不喜欢配置文件,但是没有配置确实很难做。在这个文件中你可以进行一些修改,每一项设置的说明都可以在core.php的注释中找到。

DEBUG: 将它设成不同的值可以以不同方式帮你在构建应用程序时进行调试。将其指定为一个非零的数值将强制 Cake 输出 pr( ) 和 debug( ) 函数调用的结果,而且会停止消息页面(flash)的自动转向。将其设为2或更高时会将SQL语句打印在页面的地步。同时,当处于调试模式的时候(也就是 DEBUG 被设为 1 或更高),Cake 会渲染某些生成的错误页面,也就是“Missing Controller”(缺少控制器)、“Missing Action”(缺少动作)等。然而,在生产模式下(DEBUG 被设为0),Cake 将渲染 “Not Found”页面,可以通过 app/views/errors/error404.thtml来覆盖这个默认页面。

CAKE_SESSION_COOKIE:将它改成你想要的会话(Session)数据所使用的 Cookie 的名称。

CAKE_SECURITY:将这个值改成表示你想要的会话检查级别的值。根据你在这里提供的设置,Cake 会令会话超时,生成新的会话ID,并删除旧的会话数据。可能的值是:

  • high: 会话在10分钟无动作后超时,同时会话ID在每一次请求时都会重新生成。

  • medium: 会话在20分钟无动作后超时

  • low: 会话在30分钟无动作后超时

CAKE_SESSION_SAVE: 指定了如何保存会话数据。可能的值是:

  • cake: 会话数据保存在你的 Cake 安装位置下的 tmp/ 目录

  • php: 会话数据按照 php.ini 所定义的进行保存。

  • database: 会话数据保存到由“default”键定义的数据库链接中。

“路由”(Route)‘Routing’是类似于mod_rewrite的 pared-down pure-PHP(机制),可以帮助将URL影射到 controller/action/params. Cake 添加这个可以帮助我们更好的实现URL转化并使得我们可以脱离mod_rewrite的要求。然而使用mod_rewrite,使得我们的address bar显得更加整洁。 "Routing" is a pared-down pure-PHP mod_rewrite-alike that can map URLs to controller/action/params and back. It was added to Cake to make pretty URLs more configurable and to divorce us from the mod_rewrite requirement. Using mod_rewrite, however, will make your address bar look much more tidy.

Routes是映射URLs到特定的controllers和actions的独立规则。Routes被配置在app/config/routes.php文件中,设置形式如下: Routes are individual rules that map matching URLs to specific controllers and actions. Routes are configured in the app/config/routes.php file. They are set-up like this:

在这里 Where:

URL是Cake的URL你想要映射的(URL is the Cake URL you wish to map) URL is the regular expression Cake URL you wish to map,
controllername 是你想要调用的controller的名字 controllername is the name of the controller you wish to invoke,
actionname 是你想要调用controller的action的名字 actionname is the name of the controller's action you wish to invoke,
firstparam是特定的action的第一个参数 and firstparam is the value of the first parameter of the action you've specified.

Any parameters following firstparam will also be passed as parameters to the controller action.

下面的这个例子将/blog下面的所有URL连接到了BlogController. 默认的action是BlogController::index() The following example joins all the urls in /blog to the BlogController. The default action will be BlogController::index().

一个URL 比如 /blog/history/05/june 处理如下: A URL like /blog/history/05/june can then be handled like this:

URL中的’history’被匹配到 Blog route中的:action.( The 'history' from the URL was matched by :action from the Blog's route.) URL中被*匹配的元素被传递到活动的controller的处理方法,这里是$year和$month。比如这个URL /blog/history/05, 仅仅传递一个参数05给history() The 'history' from the URL was matched by :action from the Blog's route. URL elements matched by * are passed to the active controller's handling method as parameters, hence the $year and $month. Called with URL /blog/history/05, history() would only be passed one parameter, 05.

接下来的例子是默认的CakePHP route,指向 PagesController::display(‘home’).其中home是一个view,位于/app/views/pages/home.thtml The following example is a default CakePHP route used to set up a route for PagesController::display('home'). Home is a view which can be overridden by creating the file /app/views/pages/home.thtml.

注意

Scaffolding是一个很棒的途径,使得早期开发的部分web应用能够运行起来。早期的数据库模式是不稳定的,很容易变化。Scaffolding有个下降趋势:web程序员憎恨创建以后可能根本用不到的forms。为了减少程序员的这种重复劳动, Cake中包含了Scaffolding。Scaffolding分析数据库,创建一些标准的使用add、delete、和edit按钮的lists,创建输入的forms,以及查看数据库中一个item的标准views。为了在程序中的controller中添加Scaffolding,需要添加$scaffold变量: Cake's scaffolding is pretty cool. So cool that you'll want to use it in production apps. Now, we think its cool, too, but please realize that scaffolding is... well... just scaffolding. Its a bunch of stuff you throw up real quick during the beginning of a project in order to get started. It isn't meant to be completely flexible. So, if you find yourself really wanting to customize your logic and your views, its time to pull your scaffolding down in order to write some code.

Scaffolding is a great way of getting the early parts of developing a web application started. Early database schemas are volatile and subject to change, which is perfectly normal in the early part of the design process. This has a downside: a web developer hates creating forms that never will see real use. To reduce the strain on the developer, scaffolding has been included in Cake. Scaffolding analyzes your database tables and creates standard lists with add, delete and edit buttons, standard forms for editing and standard views for inspecting a single item in the database. To add scaffolding for your application, in the controller, add the $scaffold variable:

class CategoriesController extends AppController
{
    var $scaffold;
}

有关Scaffold,要注意一个重要的问题: Scaffold期望每个以_id结尾的filed name是一个外键并且指向一个table,table的名称和_id前方的一样(只不过是小写的)。所以,举个例子来说,如果你嵌套了分类,你最好有个列叫做parent_id。在这个版本中,最好能够命名为parentid.同样,在表中有一个外键(比如,titles table有个category_id),并且你已经合适的联结到models(查看6.2理解联结),在show/edit/newd的views中,选择的表将会和外键的表(category)一起自动的表现出来(原文:a select box will be automatically populated with the rows from the foreign table (category) in the show/edit/new views.)。在foreign model中设置$displayField来决定foreign中哪些field会被显示。继续我们的例子,category有个标题One important thing to note about scaffold: it expects that any field name that ends with _id is a foreign key to a table which has a name that precedes the underscore. So, for example, if you have nested categories, you'd probably have a column called parent_id. With this release, it would be best to call this parentid. Also, when you have a foreign key in your table (e.g. titles table has category_id), and you have associated your models appropriately (see Understanding Associations, 6.2), a select box will be automatically populated with the rows from the foreign table (category) in the show/edit/new views. To set which field in the foreign table is shown, set the $displayField variable in the foreign model. To continue our example of a category having a title:

class Title extends AppModel 
{
    var $name = 'Title';

    var $displayField = 'title';
}

模型是什么?它其实就是 MVC 模式中的 M。

模型做些什么。它将领域逻辑和表现形式分离,并独立了应用逻辑。

一个模型一般来说就是数据库的一个访问点,更具体地说,就是数据库中特定的表。默认情况下,每个模型都使用以自身名字的复数形式为名称的表,比如,User 模型使用 users 表。模型也可以包含数据校验的规则,关联信息以及特定于它使用的表的方法。下面是一个简单的用户模型的例子:

从PHP的观点看,模型都是扩展了 AppModel 类的类。类 AppModel 最初是定义在 cake/ 目录下,但如果你要创建自己的,可以将其放在 app/app_model.php。它应该包含会在两个或多个模型之间共享的方法。它本身是扩展了 Model 类,这时一个标准的 Cake 库,定义在 libs/model.php 中。

重要

本节介绍的是 Cake 的 Model 中常用的方法,最好使用 http://api.cakephp.org 来查看全部的参考。

下面是使用模型来得到数据的一些标准的途径:

  • findAll ($conditions, $fields, $order, $limit, $page, $recursive)

    • 返回指定的字段最多 $limit (默认是50)条的满足 $conditions(如果有)的记录,列出从第 $page(默认为第一页)页开始。$conditions 内容应该像 SQL 语句中的一样:例如,$conditions = "race = 'wookie' AND thermal_detonators > 3"。

    • 当 $recursive 选项被设置成 1 到 3 之间的整数时,findAll() 操作将会返回在联结到该模型中发现的所有相关的记录。递归寻找可以最深 3 层。 When the $recursive option is set to a integer value between 1 and 3, the findAll() operation will make an effort to return the models associated to the ones found by the findAll(). The recursive find can go up to three levels deep. If your property has many owners who in turn have many contracts, a recursive findAll() on your Property model will return up to three levels deep of associated models.

  • find ($conditions, $fields, $order, $recursive)

    • 返回匹配 $conditions 的第一条记录中指定的字段(如果没有指定则返回全部)。

    • $recursive 作用同上When the $recursive option is set to a integer value between 1 and 3, the find() operation will make an effort to return the models associated to the ones found by the find(). The recursive find can go up to three levels deep. If your property has many owners who in turn have many contracts, a recursive find() on your Property model will return up to three levels deep of associated models.

  • findAllBy<FieldName>($value) and findBy<fieldName>($value)

    • 这些魔法函数可以用来根据给定字段和特定的值可以快速查找表中的某一行。,奇妙的方法可以用于指定特定的field和特定的value快速查找行,你要做的就是把你的field用驼峰表达法添加在后面。举个例子(用于controller中)These magic functions can be used as a shortcut to search your tables for a row given a certain field, and a certain value. Just tack on the name of the field you wish to search, and CamelCase it. Examples (as used in a Controller) might be:

      $this->Post->findByTitle('My First Blog Post');
      $this->Author->findByLastName('Rogers');
      $this->Property->findAllByState('AZ');
      $this->Specimen->findAllByKingdom('Animalia');

      返回的结果是一个数组,它和 find() 以及 findAll() 返回的结果的格式是一样的。

  • findNeighbours($conditions = null, $field, $value)

    • 返回包含相邻记录(由 $field 和 $value 指定中间的记录)的数组(只包含特定字段),通过 $conditions 作为 SQL 条件进行筛选。

      当你要生成“前一条”和“后一条”链接来帮助用户按照某种顺序在条目中逐个查看的时候,它就十分有用。它只能按照数值型或者日期字段。

      class ImagesController extends AppController
      {
          function view($id)
          {
              // 我们要展示图像……
      
              $this->set('image', $this->Image->find("id = $id");
      
              // 不过我们还要前一个和后一个图像……
      
              $this->set('neighbours', $this->Image->findNeighbours(null, 'id', $id);
          
          }
      }

      这将给我们的视图传递一格完整的 $image['Image'] 数组,同时还有 $neighbours['prev']['Image']['id'] 和 $neighbours['next']['Image']['id'] 。

  • field($name, $conditions, $order)

    • 从第一个按照 $order 排序满足条件 $conditions 的第一条记录中的字段$name 的值。

  • findCount($conditions)

    • 返回匹配给定条件 $conditions 的记录的个数。

  • generateList ($conditions=null, $order=null, $limit=null, $keyPath=null, $valuePath=null)

    • 这个函数是从模型的记录中获取“键-值”对的数组的一个快捷方式——尤其是对从模型记录中创建一个 <select> 列表十分有用。$conditions、$order 和 $limit参数使用方法和 findAll() 请求相同。$keyPath 和 $valuePath 是你告诉模型要从哪里去找所生成的列表的键和值。举个例子,如果你想根据 Role 模型生成一个角色的列表,列表由角色的数值ID作为键,那么完整的调用应该类似:

      $this->set(
          'Roles',
          $this->Role->generateList(null, 'role_name ASC', null, 'id', 'role_name')
      );
      
      // 这会返回类似以下的东西:
      array(
          '1' => 'Account Manager',
          '2' => 'Account Viewer',
          '3' => 'System Manager',
          '4' => 'Site Visitor'
      );
  • read($fields=null, $id=null)

    • 使用这个方法从当前已载入的记录,或者由 $id 指定的记录中获取指定字段和值。

      请注意,不管模型中的 $recursive 值是多少,read() 操作都只抓取关联模型的第一层。如果要取得额外的级别,请使用 find() 或者 findAll()。

自定义的的 SQL 调用可以通过模型的 findBySql() 方法来完成。例如,在一个 blog 应用中,假设我要存储一个发贴者的姓到一个不是我的 Cake 策略(简化的例子)中的表时。我可以在模型中放一个自定义的函数,然后来获取数据。返回值是一个多位数组。

当然也有 query() 方法,如果执行成功返回true,失败返回false。因为查询语句并不是都返回结果集,像“DELETE FROM problems WHERE solved = true”。

要将数据保存到模型中,必须向模型提供要保存的数据。这些提交给 save() 方法的数据应该按照下面的形式:

Array
(
    [modelname] => Array
        (
            [fieldname1] => value
            [fieldname2] => value
        )
)

为了能让你的数据按照这种形式 POST 发送到控制器中,只要使用 HTML 助手类就可以很方便地完成,因为它创建的表单元素的命名是 Cake 所期望的。如果你不要使用它:只要确保元素的名称类似 data[模型名][字段名]

从表单提交的数据会自动按照这种格式并放在控制器中的 $this->params['data'],这样通过web的表单直接保存数据就很快。一个特性控制器的编辑方法可能会像下面这样显示:

function edit($id) 
{

   //注意:特性模型已经自动为我们载入了,在 $this->Property 中。
     
   // 检查我们是否已经获取了表单数据……
   if (isset($this->params['form']['data']['property'])) 
   {  
      // 这里我们尝试保存数据……
      if ($this->property->save($this->params['data'])) 
      {
         // 告诉用户他的数据已经被保存了……

         $this->flash('Your information has been saved.',
                     '/properties/edit/'.$this->params['data']['property']['id'], 2);
         exit();
      }
      else
      {
         // 如果数据不能通过验证,显示验证错误
         // 并重新将表单字段填上被提交的数据……

         $this->set('form', $this->params['data']);
         $this->validateErrors($this->property);
         $this->render();
      }      
   }
   
   // 如果没有提交表单数据,那么就渲染编辑视图……
   $this->render();
}

注意 save 操作是如何放置在一个条件语句中:如果你试图保存数据到model中,cake自动尝试确数据正确(根据你提供的规则)。可以查看第十章了解更多关于正确规则(validation).如果不想查看正确性直接保存数据,使用save($data,false); Notice how the save operation is placed inside a conditional: when you try to save data to your model, Cake automatically attempts to validate your data using the rules you've provided. To learn more about data validation, see Chapter 10. If you do not want save() to try to validate your data, use save($data, false).

其他有用的保存方法Other useful save functions:

  • del($id = null, $cascade = true)

    • 删除模型中由$id指定的记录,或者当前的记录。

      如果这个模型还与其他模型关联,同时在关系数组中设置了“dependant”键,那么当 $cascade 为 true 时,这个方法还会删除相关的模型。

      当成功的时候返回 true。

  • saveField($name, $value)

    • 保存一个单独的字段的值。

  • getLastInsertID()

    • 返回最后创建记录的ID。

在我们接近0.10.x最终版的时候,我们添加了一些模型回调函数,它们允许用户能够逻辑上在模型的操作前或操作后执行。要在应用中使用这种功能,使用提供的参数并在你的 Cake 模型中覆盖这些函数。

  • beforeFind($conditions)

    • beforeFind() 回调函数在一个查询操作开始之前执行。将查询前的操作逻辑放到这个方法里面。当你在自己的模型中覆盖了这个方法后,如果你要继续执行 find 操作的话,返回true,若要中止查询继续,返回false

  • afterFind($results)

    • 使用这个回调函数可以修改从 find 操作中返回的结果,或者进行其他任何的 find 后逻辑。该函数的参数是模型的 find 操作的结果,返回的是修改后的结果。

  • beforeValidate()

    • 使用这个回调函数可以在模型数据被验证之前,对数据进行修改。通过使用Model::invalidate(),它可以用于额外的、更加复杂的验证规则。在这个环境中,模型数据是通过$this->data来访问的。这个函数返回true,save( )操作才会继续执行,否则会中断。

  • beforeSave()

    • 将任何保存前的逻辑放在这个函数中。在模型数据通过验证之后,保存数据之前就立即执行这个函数(如果没有通过验证,那么save( )调用就会终止,这个回调函数也不会被执行)。如果你要继续保存操作,那么也要返回true,如果要终止,那么返回false

      一种 beforeSave 的用法是格式化时间数据来存储到数据库中:

      // 通过 HTML 帮助类来创建 日期/时间 字段:
      // 这段代码要放在视图中
      
      $html->dayOptionTag('Event/start');
      $html->monthOptionTag('Event/start');
      $html->yearOptionTag('Event/start');
      $html->hourOptionTag('Event/start');
      $html->minuteOptionTag('Event/start');
      
      /*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
      
      // 用于重新组合日期数据用于数据存储的模型回调函数
      // 这段代码应该在 Event 模型中:
      
      function beforeSave()
      {
          $this->data['Event']['start'] = $this->_getDate('Event', 'start');
       
          return true;
      }
       
      function _getDate($model, $field)
      {
          return date('Y-m-d H:i:s', mktime(
              intval($this->data[$model][$field . '_hour']),
              intval($this->data[$model][$field . '_min']),
              null,
              intval($this->data[$model][$field . '_month']),
              intval($this->data[$model][$field . '_day']),
              intval($this->data[$model][$field . '_year'])));
      }
  • afterSave()

    • 将你想要在保存后执行的代码放在这个回调函数中。

  • beforeDelete()

    • 将删除前的逻辑代码放在这个函数中。如果你要让删除操作继续,那么返回true,否则如果要终止,返回false

  • afterDelete()

    • 将你想要在删除后执行的代码放在这个回调函数中。

CakePHP 的一个最强大的功能是由模型提供的关系映射。在 CakePHP 中,表之间的链接是通过关联(association)进行处理的。关联是两个相关的逻辑单元之间的凝胶。

在 CakePHP 中存在四种类型的联结:

  • hasOne

  • hasMany

  • belongsTo

  • hasAndBelongsToMany

当定义了模型之间的关系之后,Cake 将自动抓取和你目前正在使用的模型相关的模型。例如,如果一个 Post 模型和一个 Author 模型使用一个 hasMany 关联相联系,那么在控制器中调用 $this->Post->findAll() 会抓取 Post 的记录,同时所有和这些记录相关的 Author 记录。

要正确使用关联,最好遵守 CakePHP 的命名约定(见附录 B)。如果你使用了 CakePHP 的命名约定的话,你就可以使用脚手架功能来立刻使应用的数据从页面上可见,因为脚手架功能可以探测和使用模型之间的关联。当然你也可以自定义模型之间的关联来绕开 CakePHP 的命名约定,但这些技巧我们以后再提。现在,还是让我们坚持这些约定。在这里我们关心的命名约定是外键、模型名称和表格名称。

下面列出了对上面提到的三种东西,Cake 所期望的名称:(关于命名的更多信息参见附录 B)

  • 外键:

    • [模型名称单数形式]_id。例如,在“authors”表中有一个外键指向指定 Author 所属的 Post,则这个外键应命名为“post_id”。

  • 表名称:

    • [对象名称复数形式]。由于我们要存储关于日志帖子和作者的信息,所以相应要有“posts”和“authors”两张表。

  • 模型名称:

    • [表格名称的骆驼写法、单数形式]。对应“posts”表的模型的名称应为“Post”,同时对应“authors”表的模型的名称是“Post”。

为了进一步举例说明这些关联是如何用的,我们还继续使用 Blog 应用为例。假设我们要为 Blog 创建一个简单的用户管理系统。我们要跟踪用户的信息,要让每个用户与一个档案(Profile)关联(一个用户有一个档案——User hasOne Profile)。用户也要能创建评论,同时与它们保持关联(用户有多个评论——User hasMany Cooments)。我们让这个用户系统运行起来之后,我们就可以让帖子和标签(Tag)对象使用 hasAndBelongsToMany 来建立关系(Post hasAndBelongsToMany Tags)。

为了描述这个关联,假设你已经创建了 User 和 Profile 模型。要定义他们之间的 hasOne 关联,我们要给模型添加一个数组来告诉 Cake 他们是什么关系。如下:

Cake 使用 $hasOne 数组来在 User 和 Profile 模型之间构建关联。数组中的每一个建都可以让你进一步配置这个关联:

  • className (必须):要关联的模型的类名

    • 针对我们的例子,我们要指定“Profile”模型的类名。

  • conditions: 定义关系的SQL 条件的片断

    • 比如,我们可以使用它来告诉 Cake 只能关联有一个绿色抬头的 Profile。要这么做的话,你只要指定一个SQL条件部分作为这个键的值:"Profile.header_color = 'green'".

  • order: 相关联的模型的排序方式

    • 如果你想令被关联的模型按照某个特定的顺序进行排列,将该键的值设成一个SQL顺序的谓词,例如:“Profile.name ASC”。

  • dependent:如果将其设置为 true,那么当某个记录被删除时,相关联的模型就会被销毁。

    • 例如,如果“Cool Blue”档案是与“Bob”相关联的,且我删除了用户“Bob”,那么档案“Cool Blue”也将被删除。

  • foreignKey:指向相关联的模型的外键的名称。

    • 万一你所处理的数据库并不遵守 Cake 的命名约定,那么可以修改它。

现在,当我们对 User 模型执行 find() 或者 findAll() 时,我们也将获得相关联的 Profile 模型:

$user = $this->User->findById('25');
print_r($user);

//output:

Array
(
    [User] => Array
        (
            [id] => 25
            [first_name] => John
            [last_name] => Anderson
            [username] => psychic
            [pasword] => c4k3roxx
        )

    [Profile] => Array
        (
            [id] => 4
            [name] => Cool Blue
            [header_color] => aquamarine
            [user_id] = 25
        )
)

现在一个User可以看到它对应的 Profile,我们还需要定义一个关联以便 Profile 可以看到它的 User。这可以通过使用 belongsTo 关联来完成。在 Profile 模型中,我们可以如下:

Cake 将使用 $belongsTo 数组来建立 User 和 Profile 模型之间的关联。数组中的每一个键都可以让你对这个关联进行进一步的配置:

  • className (必须):要关联的模型的类名

    • 针对我们的例子,我们要指定“User”模型的类名。 class name.

  • conditions:定义关系的SQL 条件的片断

    • 比如,我们可以使用它来告诉 Cake 只能关联活动着的用户。要这么做的话,你只要指定一个SQL条件部分作为这个键的值:"User.active = '1'"。

  • order: 相关联的模型的排序方式

    • 如果你想令被关联的模型按照某个特定的顺序进行排列,将该键的值设成一个SQL顺序的谓词,例如:“User.last_name ASC”。

  • foreignKey: 指向相关联的模型的外键的名称。

    • 万一你所处理的数据库并不遵守 Cake 的命名约定,那么可以修改它。

现在,当我们对 Profile 模型执行 find() 或者 findAll() 时,我们也将获得相关联的 User 模型:

$profile = $this->Profile->findById('4');
print_r($profile);

//output:

Array
(

    [Profile] => Array
        (
            [id] => 4
            [name] => Cool Blue
            [header_color] => aquamarine
            [user_id] = 25
        )

    [User] => Array
        (
            [id] => 25
            [first_name] => John
            [last_name] => Anderson
            [username] => psychic
            [pasword] => c4k3roxx
        )
)

现在 User 和 Profile 已经相互关联了并且运行正常,我们继续构建系统,让用户记录可以和多条评论相关。可以在 User 模型中这样做:

Cake 使用 $hasMany 数组来构建 User 和 Comment 模型之间的关联。数组中的每一个键都可以让你对这个关联进行进一步的配置:

  • className (必须):要关联的模型的类名

    • 针对我们的例子,我们指定“Comment”模型的类名。

  • conditions:定义关系的SQL 条件的片断

    • 比如,我们可以使用它来告诉 Cake 只能关联那些被审批的的评论。要这么做的话,你只要指定一个SQL条件部分作为这个键的值:"Comment.moderated = 1"。

  • order: 相关联的模型的排序方式

    • 如果你想令被关联的模型按照某个特定的顺序进行排列,将该键的值设成一个SQL顺序的谓词,例如:“Comment.created DESC”。

  • limit: 你要让 Cake 获取的关联模型的数量上限。

    • 对于这个例子,我们并不想抓取用户所有的评论,只要头五条。

  • foreignKey: 指向相关联的模型的外键的名称。

    • 万一你所处理的数据库并不遵守 Cake 的命名约定,那么可以修改它。

  • dependent:如果将其设置为 true,那么当某个记录被删除时,相关联的模型就会被销毁。

    • 例如,如果评论“Cool Blue”是与“Bob”相关联的,且我删除了用户“Bob”,那么评论“Cool Blue”也将被删除。

  • exclusive: 如果将其设置为 true,那么所有相关联的对象都只用一句 SQL 语句来删除,而不执行它们的 beforeDestroy 回调函数。

    • 对于简单的关联十分有效,因为它更快。

  • finderSql: 指定完整的用于获取关联的 SQL 语句。

    • 对于需要依赖多个表的复杂关联,这是一个很好的办法。如果 Cake 的自动生成的关联不能正常工作,那么,你可以在这里进行自定义。

现在,当我们对 User 模型执行 find() 或者 findAll() 的话,我们应该能看到相关联的 Comment 模型了:

$user = $this->User->findById('25');
print_r($user);

//output:

Array
(
    [User] => Array
        (
            [id] => 25
            [first_name] => John
            [last_name] => Anderson
            [username] => psychic
            [pasword] => c4k3roxx
        )

    [Profile] => Array
        (
            [id] => 4
            [name] => Cool Blue
            [header_color] => aquamarine
            [user_id] = 25
        )

    [Comment] => Array
        (
            [0] => Array
                (
                    [id] => 247
                    [user_id] => 25
                    [body] => The hasMany assocation is nice to have.
                )

            [1] => Array
                (
                    [id] => 256
                    [user_id] => 25
                    [body] => The hasMany assocation is really nice to have.
                )

            [2] => Array
                (
                    [id] => 269
                    [user_id] => 25
                    [body] => The hasMany assocation is really, really nice to have.
                )

            [3] => Array
                (
                    [id] => 285
                    [user_id] => 25
                    [body] => The hasMany assocation is extremely nice to have.
                )

            [4] => Array
                (
                    [id] => 286
                    [user_id] => 25
                    [body] => The hasMany assocation is super nice to have.
                )

        )
)

注意

最好同样定义一下“Comment belongsTo User”关联,不过在此我们就不再赘述。这么做可以让两个模型都能够看到对象。否则,在进行搭建脚手架功能的时候,常常会有一些麻烦。

现在你已经掌握了简单的关联,让我们继续学会最后一种关联:hasAndBelongsToMany(简称 HABTM)。这是最后一个也是最难理解的一个,但它也是最有用的。当你想让两个模型通过一个中间表来建立链接,那么 HABTM 关联是很有用的。中间表中保存了和两个模型有关系的例。

hasMany 和 hasAndBelongsToMany 之间的区别是,使用了 hasMany,被关联的模型就不能被共享。如果一个用户有很多评论(User hasMany Comments),那么它是唯一和这些评论相关联的用户。但使用HABTM,被关联的模型可以被共享。这对我们接下去要做的东西是意义巨大的:将 Post 模型和 Tag 模型相关联。当The difference between hasMany and hasAndBelongsToMany is that with hasMany, the associated model is not shared. If a User hasMany Comments, it is the *only* user associated to those comments. With HABTM, the associated models are shared. This is great for what we're about to do next: associate Post models to Tag models. When a Tag belongs to a Post, we don't want it to be 'used up', we want to continue to associate it to other Posts as well.

In order to do this, we'll need to set up the correct tables for this association. Of course you'll need a "tags" table for you Tag model, and a "posts" table for your posts, but you'll also need to create a join table for this association. The naming convention for HABTM join tables is [plural model name1]_[plural model name2], where the model names are in alphabetical order:

HABTM 中间表需要至少包含两个外键。例如,“post_id”和“tag_id”。join tables need to at least consist of the two foreign keys of the models they link. For our example, "post_id" and "tag_id" is all we'll need.

下面是Post HABTM Tag 例子的 SQL 导出:

-- 
-- Table structure for table `posts`
-- 

CREATE TABLE `posts` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `user_id` int(10) default NULL,
  `title` varchar(50) default NULL,
  `body` text,
  `created` datetime default NULL,
  `modified` datetime default NULL,
  `status` tinyint(1) NOT NULL default '0',
  PRIMARY KEY  (`id`)
) TYPE=MyISAM;

-- --------------------------------------------------------

-- 
-- Table structure for table `posts_tags`
-- 

CREATE TABLE `posts_tags` (
  `post_id` int(10) unsigned NOT NULL default '0',
  `tag_id` int(10) unsigned NOT NULL default '0',
  PRIMARY KEY  (`post_id`,`tag_id`)
) TYPE=MyISAM;

-- --------------------------------------------------------

-- 
-- Table structure for table `tags`
-- 

CREATE TABLE `tags` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `tag` varchar(100) default NULL,
  PRIMARY KEY  (`id`)
) TYPE=MyISAM;

设置好了表格之后,我们就要在 Post 模型中定义关联:

The $hasAndBelongsToMany array is what Cake uses to build the association between the Post and Tag models. Each key in the array allows you to further configure the association:

  • className (required): the classname of the model you'd like to associate

    • For our example, we want to specify the 'User' model class name.

  • joinTable: this is here for a database that doesn't adhere to Cake's naming conventions. If your table doesn't look like [plural model1]_[plural model2] in lexical order, you can specify the name of your table here.

  • foreignKey: the name of the foreign key that points to the associated model.

    • This is here in case you're working with a database that doesn't follow Cake's naming conventions.

  • associationForeignKey: the name of the foreign key in the join table that points to the current model.

  • conditions: SQL condition fragments that define the relationship

    • We could use this to tell Cake to only associate a Comment that has been moderated. You would do this by setting the value of the key to be "Comment.moderated = 1", or something similar.

  • order: the ordering of the associated models

    • IF you'd like your associated models in a specific order, set the value for this key using an SQL order predicate: "Comment.created DESC", for example.

  • limit: the maximum number of associated models you'd like Cake to fetch.

    • For this example, we didn't want to fetch *all* of the user's comments, just five.

  • uniq: If set to true, duplicate associated objects will be ignored by accessors and query methods.

    • Basically, if the associations are distinct, set this to true. That way the Tag "Awesomeness" can only be assigned to the Post "Cake Model Associations" once, and will only show up once in result arrays.

  • finderSql: Specify a complete SQL statement to fetch the association.

    • This is a good way to go for complex associations that depends on multiple tables. If Cake's automatic assocations aren't working for you, here's where you customize it.

  • deleteQuery: A complete SQL statement to be used to remove assocations between HABTM models.

    • If you don't like the way Cake is performing deletes, or your setup is customized in some way, you can change the way deletion works by supplying your own query here.

Now, when we execute find() or findAll() calls using the Post model, we should see our associated Tag models there as well:

$post = $this->Post->findById('2');
print_r($user);

//output:

Array
(
    [Post] => Array
        (
            [id] => 2
            [user_id] => 25
            [title] => Cake Model Associations
            [body] => Time saving, easy, and powerful.
            [created] => 2006-04-15 09:33:24
            [modified] => 2006-04-15 09:33:24
            [status] => 1
        )

    [Tag] => Array
        (
            [0] => Array
                (
                    [id] => 247
                    [tag] => CakePHP
                )

            [1] => Array
                (
                    [id] => 256
                    [tag] => Powerful Software
                )
        )
)

Saving models that are associated by hasOne, belongsTo, and hasMany is pretty simple: you just populate the foreign key field with the ID of the associated model. Once that's done, you just call the save() method on the model, and everything gets linked up correctly.

With hasAndBelongsToMany, its a bit trickier, but we've gone out of our way to make it as simple as possible. In keeping along with our example, we'll need to make some sort of form that relates Tags to Posts. Let's now create a form that creates posts, and associates them to an existing list of Tags.

You might actually like to create a form that creates new tags and associates them on the fly - but for simplicity's sake, we'll just show you how to associate them and let you take it from there.

When you're saving a model on its own in Cake, the tag name (if you're using the Html Helper) looks like 'Model/field_name'. Let's just start out with the part of the form that creates our post:

The form as it stands now will just create Post records. Let's add some code to allow us to bind a given Post to one or many Tags:

In order for a call to $this->Post->save() in the controller to save the links between this new Post and its associated Tags, the name of the field must be in the form "Tag/Tag". The submitted data must be a single ID, or an array of IDs of linked records. Because we're using a multiple select here, the submitted data for Tag/Tag will be an array of IDs.

The $tags variable here is just an array where the keys are the IDs of the possible Tags, and the values are the displayed names of the Tags in the multi-select element.

一个Controller是用来管理你应用某个方面的逻辑。大多数来说,controllers用来管理一个model的逻辑。比如,你正在建设一个站点来管理一个video的collection,你可能有一个VideoController以及一个RentalControlle来管理你的视频和租金。分别的,cake中每个controller的名字通常是复数。 A controller is used to manage the logic for a certain section of your application. Most commonly, controllers are used to manage the logic for a single model. For example, if you were building a site that manages a video collection, you might have a VideoController and a RentalController managing your videos and rentals, respectively. In Cake, controller names are always plural.

程序的Controllers都是从AppController类继承来的类,AppController又是从核心的类Controller继承来的。Controller类可以包含人以数量的action:各种显示Views的方法 Your application's controllers are classes that extend the Cake AppController class, which in turn extends a core Controller class. Controllers can include any number of actions: functions used in your web application to display views.

AppController类可以在/app/app_controller.php中第一,它里面包含了许多可以被更多controllers共享的方法,它本身是从核心类Controller继承来的。 AppController class can be defined in /app/app_controller.php and it should contain methods that are shared between two or more controllers. It itself extends the Controller class which is a standard Cake library.

一个Action是controller中的一个单独的方法,这个方法可以被Dispathcher自动的运行,运行的根据是从routes配置解析的页面请求。回到我们的 video collection的例子,VideoController类可以包含actions比如view(), rent(), search()等等,这个controller可以在/app/controllers/video_controller.php找到,并且文件内容为: An action is a single functionality of a controller. It is run automatically by the Dispatcher if an incoming page request specifies it in routes configuration. Returning to our video collection example, our VideoController might contain the view(), rent(), and search() actions. The controller would be found in /app/controllers/videos_controller.php and contain:

class VideosController extends AppController
{
    function view($id)
    {
        //action logic goes here..
    }

    function rent($customer_id, $video_id)
    {
        //action logic goes here..
    }

    function search($query)
    {
        //action logic goes here..
    }
}

你可以通过使用下面的URLs来调用这些方法: You would be able to access these actions using the following example URLs:

http://www.example.com/videos/view/253
http://www.example.com/videos/rent/5124/0-235253
http://www.example.com/videos/search/hudsucker+proxy

但是这些页面将如何显示?你需要为每个action定义一个view,你可以在下一章查看具体内容。但是本章中,你会掌握Cake的controller,并且能够更好的使用。具体的说,你将学到如何用controller将你的数据传递给view,使用户转向以及其他更多功能。 But how would these pages look? You would need to define a view for each of these actions - check it out in the next chapter, but stay with me: the following sections will show you how to harness the power of the Cake controller and use it to your advantage. Specifically, you'll learn how to have your controller hand data to the view, redirect the user, and much more.

重要

尽管本章中介绍了许多Cake中常用的方法,更多的手册内容请参考http://api.cake.org While this section will treat most of the oft-used functions in Cake's Model, its important to remember to use http://api.cakephp.org for a full reference.

  • set($var, $value)

    • 这个方法是将controller中的数据传递给view的主要方法。你可以使用这个方法控制任何值:single values, 整个数组等等。一旦你使用了set(),view中也可以使用这个变量:在controller中使用set(‘color’,’blue’)可以使得$color变量在view中使用This function is the main way to get data from your controller to your view. You can use it to hand over anything: single values,whole arrays, etc. Once you've used set(), the variable can be accessed in your view: doing set('color', 'blue') in your controller makes $color available in the view.

  • validate()

    • 通过收集错误的保存,返回错误的个数Returns the number of errors generated by an unsuccessful save.

  • validateErrors()

    • 根据model中第一的正确性规则,检查一个model的数据是否正确,更多信息参考第十章Validates a model data according to the model's defined validation rules. For more on validation, see Chapter 10.

  • render($action = null, $layout = null, $file = null)

    • 你并非时刻需要这个方法,因为在每个controller方法的最后,render都被自动调用,并且调用后定义(或者使用)相应的viewYou may not often need this function, because render is automatically called for you at the end of each controller action, and the view named after your action is rendered. Alternatively, you can call this function to render the view at any point in the controller logic.

尽管这些方法是在Cake的Object类中,他们在Controller中也可以使用While these are functions part of Cake's Object class, they are also available inside the Controller:

  • generateFieldNames($data = null, $doCreateOptions = true)

  • requestAction($url, $extra = array())

    • 这个方法根据任何地方(location)调用controller的action,然后返回已经显示的view.$url是Cake规范的URL(/controllername/actionname/params),如果$extra数组包含了一个键‘render’,controller action将自动设置AutoRenderThis function calls a controller's action from any location and returns the rendered view. The $url is a Cake URL (/controllername/actionname/params). If the $extra array includes a 'return' key, AutoRender is automatically set to true for the controller action.

      You can use requestAction to get data from another controller action, or get a fully rendered view from a controller.

      First, getting data from a controller is simple. You just use request action in the view you need the data.

      // Here is our simple controller:
      
      class UsersController extends AppController
      {
          function getUserList()
          {
              $this->User->findAll();
          }
      }

      Imagine that we needed to create a simple table showing the users in the system. Instead of duplicating code in another controller, we can get the data from UsersController::getUserList() instead by using requestAction().

      class ProductsController extends AppController
      {
          function showUserProducts()
          {
              $this->set('users', $this->requestAction('/users/getUserList'));
      
              // Now the $users variable in the view will have the data from
              // UsersController::getUserList().
          }
      }
      

      If you have an oft used element in your application that is not static, you might want to use requestAction() to inject it into your views. Let's say that rather than just passing the data from UsersController::getUserList, we actually wanted to render that action's view (which might consist of a table), inside another controller. This saves you from duplicating view code.

      class ProgramsController extends AppController
      {
          function viewAll()
          {
              $this->set('userTable', $this->requestAction('/users/getUserList', array('return')));
      
              // Now, we can echo out $userTable in this action's view to
              // see the rendered view that is also available at /users/getUserList.
          }
      }

      Please note that actions called using requestAction() are rendered using an empty layout - this way you don't have to worry about layouts getting rendered inside of layouts.

      The requestAction() function is also useful in AJAX situations where a small element of a view needs to be populated before or during and AJAX update.

  • log($msg, $type = LOG_ERROR)

    • 你可以使用这个方法在你的程序应中记录发生的任何事件。记录文件Logs放在/tmp文件目录下You can use this function to log different events that happen within your web application. Logs can be found inside Cake's /tmp directory.

      If the $type is equal to the PHP constant LOG_DEBUG, the message is written to the log as a debug message. Any other type is written to the log as an error.

      // Inside a controller, you can use log() to write entries:
      
      $this->log('Mayday! Mayday!');
      
      //Log entry:
      
      06-03-28 08:06:22 Error: Mayday! Mayday!
      
      $this->log("Look like {$_SESSION['user']} just logged in.", LOG_DEBUG);
      
      //Log entry:
      
      06-03-28 08:06:22 Debug: Looks like Bobby just logged in.
  • postConditions($dtaa)

    • A method to which you can pass $this->params['data'], and it will pass back an array formatted as a model conditions array.

      For example, if I have a person search form:

      // app/views/people/search.thtml:
      
      <?php echo $html->input('Person/last_name'); ?>

      Submitting the form with this element would result in the following $this->params['data'] array:

      Array
      (
          [Person] => Array
              (
                  [last_name] => Anderson
              )
      )

      At this point, we can use postConditions() to format this data to use in model:

      // app/controllers/people_controller.php:
      
      $conditions = $this->postConditions($this->params['data']);
      
      // Yields an array looking like this:
      
      Array
      (
          [Person.last_name] => Anderson
      )
      
      // Which can be used in model find operations:
      
      $this->Person->findAll($conditions);

数量使用controller中定义的变量可以使你更好的使用cake的某些附加功能Manipulating a few special variables inside of your controller allows you to take advantage of some extra Cake functionality:

  • $name

    • PHP4 并不能以驼峰表示法将当前类的名称提供给我们。如果你遇到问题,使用这个变量将你的类名设置成正确的驼峰表示法PHP 4 doesn't like to give us the name of the current class in CamelCase. Use this variable to set the correct CamelCased name of your class if you're running into problems.

  • $uses

    • 你的controller是否使用了很多的model?比如你的Fragglescontroller会自动调用$this->fraggle,但是你也想同时能够调用$this->smurf,那就将下面的语句加入到你的controller中Does your controller use more than one model? Your FragglesController will automatically load $this->fraggle, but if you want $this->smurf as well, try adding something like the following to your controller:

      var $uses = array('Fraggle','Smurf');
  • $helpers

    • 使用这个变量使得你的controller装在helpers到它的views. 这个HTML helper会自动装载,但是你可以使用这个变量来指定其他的Use this variable to have your controller load helpers into its views. The HTML helper is automatically loaded, but you can use this variable to specify a few others:

      var $helpers = array('html','ajax','javascript');
  • $layout

    • 将这个变量设置成为你在这个controller中想使用的layoutSet this variable to the name of the layout you would like to use for this controller.

  • $autoRender

    • 将这个变量设置成为false将会使得actions停止自动显示(rendering)Setting this to false will stop your actions from automatically rendering.

  • $beforeFilter

    • 如果你需要在某一个或者任何一个action调用之前都要运行一小段代码,使用$beforefilter.这个函数处理access control十分有用,你可以在任何action处理之前检查用户的权限。你需要做的就是将这个变量设置成为一个数组,数组中包含了你想运行的controller的action,如下所示 If you'd like a bit of code run every time an action is called (and before any of that action code runs), use $beforeFilter. This functionality is really nice for access control - you can check to see a user's permissions before any action takes place. Just set this variable using an array containing the controller action(s) you'd like to run:

      class ProductsController extends AppController
      {
          var $beforeFilter = array('checkAccess');
      
          function checkAccess()
          {
              //Logic to check user identity and access would go here....
          }
      
          function index()
          {
              //When this action is called, checkAccess() is called first.
          }
      }
  • $components

    • 和$helpers和$uses类似,这个变量用来装载你需要的components var $components = array('acl');Just like $helpers and $uses, this variable is used to load up components you will need:

      var $components = array('acl');

Controller parameters are available at $this->params in your Cake controller. This variable is used to get data into the controller and provide access to information about the current request. The most common usage of $this->params is to get access to information that has been handed to the controller via POST or GET operations.

  • $this->params['data']

    • Used to handle POST data sent from HTML Helper forms to the controller.

      // A HTML Helper is used to create a form element
      
      $html->input('User/first_name');
      
      // When rendered in the HTML would look something like:
      
      <input name="data[User][first_name]" value="" type="text" />
      
      // And when submitted to the controller via POST, 
      // shows up in $this->params['data']['User']['first_name']
      
      Array
      (
          [data] => Array
              (
                  [User] => Array
                      (
                          [username] => mrrogers
                          [password] => myn3ighb0r
                          [first_name] => Mister
                          [last_name] => Rogers
                      )
      
              )
      )
      
  • $this->params['form']

    • Any POST data from any form is stored here, including information also found in $_FILES.

  • $this->params['bare']

    • Stores '1' if the current layout is bare, '0' if not.

  • $this->params['ajax']

    • Stores '1' if the current layout is ajax, '0' if not.

  • $this->params['controller']

    • Stores the name of the current controller handling the request. For example, if the URL /posts/view/1 was called, $this->params['controller'] would equal "posts".

  • $this->params['action']

    • Stores the name of the current action handling the request. For example, if the URL /posts/view/1 was called, $this->params['action'] would equal "view".

  • $this->params['pass']

    • Stores the GET query string passed with the current request. For example, if the URL /posts/view/?var1=3&var2=4 was called, $this->params['pass'] would equal "?var1=3&var2=4".

  • $this->params['url']

    • Stores the current URL requested, along with key-value pairs of get variables. For example, if the URL /posts/view/?var1=3&var2=4 was called, $this->params['pass'] would look like this:

      [url] => Array
              (
                  [url] => posts/view
                  [var1] => 3
                  [var2] => 4
              )

一个View就是一个页面的模版,经常以action的名字来命名。举例来说,Postscontroller::add()方法的view存放在/app/views/posts/add.thtml。Cake的views文件都是简单的PHP文件,所以你可以在里面使用任何PHP代码,几乎所有的view文件都包含HTML,一个view可以使特定数据集的任何体现,包括XML,图片等等A view is a page template, usually named after an action. For example, the view for PostsController::add() would be found at /app/views/posts/add.thtml. Cake views are quite simply PHP files, so you can use any PHP code inside them. Although most of your view files will contain HTML, a view could be any perspective on a certain set of data, be it XML, and image, etc.

在view的模版文件中,你可以从对应的Model中使用数据,这个数据以数组$data的形式传递过来,你在controller中使用set()传递过来的任何数据在view中都可以使用In the view template file, you can use the data from the corresponding Model. This data is passed as an array called $data. Any data that you've handed to the view using set() in the controller is also now available in your view.

注意

HTML helper默认情况下在任何一个view都是可用的。而且是views中使用最多的。它创建forms包括scripts和media、链接以及数据正确性检查中都非常有用。参考第九章第1.1节可以了解HTML helper更多的内容The HTML helper is available in every view by default, and is by far the most commonly used helper in views. It is very helpful in creating forms, including scripts and media, linking and aiding in data validation. Please see section 1.1 in Chapter 9 for a discussion on the HTML helper.

大部分视图中可用的函数都是由助手类(Helper)提供的。Cake提供了很多的助手类(参考第九章),你也可以引用自己定义的。因为views不应该包含过多的逻辑,所以views类中并没有很多public的方法。其中一个有用的方法就是renderElement(),这个方法我们在1.2节中讨论 Most of the functions available in the views are provided by Helpers. Cake comes with a great set of helpers (discussed in Chapter 9), and you can also include your own. Because views shouldn't contain much logic, there aren't many well used public functions in the view class. One that is helpful is renderElement(), which will be discussed in section 1.2.

一个布局(layout)包括了围绕view的所有代码。你想在view中见到的所有东西都应该被放在你的layout中。 布局文件位于/app/views/layouts。A layout contains all the presentational code that wraps around a view. Anything you want to see in all of your views should be placed in your layout.

Cake的默认layout可以在/app/views/layouts/default.thtml被新的默认layout重写。一旦一个新的默认layout创建,controller view的代码将在页面被显示的时候替换到默认的layout Layout files are placed in /app/views/layouts. Cake's default layout can be overridden by placing a new default layout at /app/views/layouts/default.thtml. Once a new default layout has been created, controller view code is placed inside of the default layout when the page is rendered.

当你常见一个layout的时候,你需要告诉Cake你的controller view的代码位置:为了 达到这个目的,一定要确保layout包含$content_for_layout(有$title_for_layout更好)下面是一 个默认的layout的例子 When you create a layout, you need to tell Cake where to place your controller view code: to do so, make sure your layout includes a place for $content_for_layout (and optionally, $title_for_layout). Here's an example of what a default layout might look like:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><?php echo $title_for_layout?></title>
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
<body>

<!-- IF you'd like some sort of menu to show up on all of your views, include it here -->
<div id="header">
    <div id="menu">...</div>
</div>

<!-- Here's where I want my views to be displayed -->
<?php echo $content_for_layout ?>

<!-- Add a footer to each displayed page -->
<div id="footer">...</div>

</body>
</html>

To set the title for the layout, its easiest to do so in the controller, using the $pageTitle controller variable.

class UsersController extends AppController
{
    function viewActive()
    {
        $this->pageTitle = 'View Active Users';
    }
}

You can create as many layouts as you wish for your Cake site, just place them in the app/views/layouts directory, and switch between them inside of your controller actions using the controller's $layout variable, or setLayout() function.

For example, if a section of my site included a smaller ad banner space, I might create a new layout with the smaller advertising space and specify it as the layout for all controller's actions using something like:

var $layout = 'default_small_ad';

Helpers are meant to provide functions that are commonly needed in views to format and present data in useful ways.

The HTML helper is one of Cake's ways of making development less monotonous and more rapid. The HTML helper has two main goals: to aid in the insertion of oft-repeated sections of HTML code, and to aid in the quick-and-easy creation of web forms. The following sections will walk you through the highlight functions in the helper, but remember http://api.cakephp.org should always be used as a final reference.

Many of the functions in the HTML helper make use of a HTML tag definition file called tags.ini.php. Cake's core configuration contains a tags.ini.php, but if you'd like to make some changes, create a copy of /cake/config/tags.ini.php and place it in your /app/config/ folder. The HTML helper uses the tag definitions in this file to generate tags you request. Using the HTML helper to create some of your view code can be helpful, as a change to the tags.ini.php file will result in a site-wide cascading change.

Additionally, if AUTO_OUTPUT is set to true in your core config file for your application (/app/config/core.php), the helper will automatically output the the tag, rather than returning the value. This has been done in an effort to assuage those who dislike short tags (<?= ?>) or lots of echo() calls in their view code. Functions that include the $return parameter allow you to force-override the settings in your core config. Set $return to true if you'd like the HTML helper to return the HTML code regardless of any AUTO_OUTPUT settings.

HTML helper functions also include a $htmlAttributes parameter, that allow you to tack on any extra attributes on your tags. For example, if you had a tag you'd like to add a class attribute to, you'd pass this as the $htmlAttribute value:

array('class'=>'someClass')

If you'd like to use Cake to insert well-formed and oft-repeated elements in your HTML code, the HTML helper is great at doing that. There are functions in this helper that insert media, aid with tables, and there's even guiListTree which creates an unordered list based on a PHP array.

  • charset ($charset, $return)

    • This is used to generate a charset META-tag.

  • css ($path, $rel= 'stylesheet', $htmlAttributes=null, $return=false)

    • Creates a link to a CSS stylesheet. The $rel parameter allows you to provide a rel= value for the tag.

  • image ($path, $htmlAttributes=null, $return=false)

    • Renders an image tag. The code returned from this function can be used as an input for the link() function to automatically create linked images.

  • link ($title, $url=null, $htmlAttributes=null, $confirmMessage=false, $escapeTitle=true, $return=false)

    • Use this function to create links in your view. $confirmMessage is used when you need a JavaScript confirmation message to appear once the link is clicked. For example, a link that deletes an object should probably have a "Are you sure?" type message to confirm the action before the link is activated. Set $escapeTitle to true if you'd like to have the HTML helper escape the data you handed it in the $title variable.

  • tableHeaders ($names, $tr_options=null, $th_options=null)

    • Used to create a formatted table header.

  • tableCells ($data, $odd_tr_options=null, $even_tr_options=null)

    • Used to create a formatted set of table cells.

  • guiListTree ($data, $htmlAttributes=null, $bodyKey= 'body', $childrenKey='children', $return=false)

    • Generates a nested unordered list tree from an array.

The HTML helper really shines when it comes to quickening your form code in your views. It generates all your form tags, automatically fills values back in during error situations, and spits out error messages. To help illustrate, let's walk through a quick example. Imagine for a moment that your application has a Note model, and you want to create controller logic and a view to add and edit Note objects. In your NotesController, you would have an edit action that might look something like the following:

Once we've got our controller set up, lets look at the view code (which would be found in app/views/notes/edit.thtml). Our Note model is pretty simple at this point 鈥?it only contains an id, a submitter's id and a body. This view code is meant to display Note data and allow the user to enter new values and save that data to the model.

The HTML helper is available in all views by default, and can be accessed using $html.

Specifically, let's just look at the table where the guts of the form are found:

Most of the form tag generating functions (along with tagErrorMsg) require you to supply a $fieldName. This $fieldName lets Cake know what data you are passing so that it can save and validate the data correclty. The string passed in the $fieldName parameter is in the form "modelname/fieldname." If you were going to add a new title field to our Note, you might add something to the view that looked like this:

<?php echo $html->input('note/title', $note['note']['title']) ?>
<?php echo $html->tagErrorMsg('note/title', 'Please supply a title for this note.')?>

Error messages displayed by the tagErrorMsg() function are wrapped in <div class="error_message"></div> for easy CSS styling.

Here are the form tags the HTML helper can generate (most of them are straightforward):

  • formTag ($target=null, $type='post', $htmlAttributes=null)

    • Generates an opening form tag. Use $target to specify the form's action. Remember, if your form is submitting a file, you need to supply the $htmlAttributes parameter with the proper enctype information.

  • submit ($caption= 'Submit', $htmlAttributes=null, $return=false)

    • Supply an alternate button title using $caption.

  • password ($fieldName, $htmlAttributes=null, $return=false)

  • textarea ($fieldName, $htmlAttributes=null, $return=false)

  • checkbox ($fieldName, $title=null, $htmlAttributes=null, $return=false)

  • file ($fieldName, $htmlAttributes=null, $return=false)

  • hidden ($fieldName, $htmlAttributes=null, $return=false)

  • input ($fieldName, $htmlAttributes=null, $return=false)

  • radio ($fieldName, $options, $inbetween=null, $htmlAttributes=null, $return=false)

  • tagErrorMsg ($field, $text)

The HTML helper also includes a set of functions that aid in creating date-related option tags. The $tagName parameter should be handled in the same way that the $fieldName parameter; Just provide the name of the field this date option tag is relevant to. Once the data is processed, you'll see it in your controller with the part of the date it handles concatenated to the end of the field name. For example, if my Note had a deadline field that was a date, and my dayOptionTag $tagName parameter was set to 'note/deadline', the day data would show up in the $params variable once the form has been submitted to a controller action:

$this->params['form']['data']['note']['deadline_day']

You can then use this information to concatenate the time data in a format that is friendly to your current database configuration. This code would be placed just before you attempt to save the data, and saved in the $data array used to save the information to the model.

  • dayOptionTag ($tagName, $value=null, $selected=null, $optionAttr=null)

  • yearOptionTag ($tagName, $value=null, $minYear=null, $maxYear=null, $selected=null, $optionAttr=null)

  • monthOptionTag ($tagName, $value=null, $selected=null, $optionAttr=null)

  • hourOptionTag ($tagName, $value=null, $format24Hours=false, $selected=null, $optionAttr=null)

  • minuteOptionTag ($tagName, $value=null, $selected=null, $optionAttr=null)

  • meridianOptionTag ($tagName, $value=null, $selected=null, $optionAttr=null)

  • dateTimeOptionTag ($tagName, $dateFormat= 'DMY', $timeFormat= '12', $selected=null, $optionAttr=null)

The Cake Ajax helper utilizes the ever-popular Prototype and script.aculo.us libraries for Ajax operations and client side effects. In order to use this helper, you must have a current version of the JavaScript libraries from http://script.aculo.us placed in /app/webroot/js/. In addition, any views that plan to use the Ajax Helper will need to include those libraries.

Most of the functions in this helper expect a special $options array as a parameter. This array is used to specify different things about your Ajax operation. Here are the different values you can specify:

例 9.4. AjaxHelper $options Keys

/* General Options */

$options['url']         // The URL for the action you want to be called.

$options['frequency']   // The number of seconds between remoteTimer() or 
                        // observeField() checks are made.

$options['update']      // The DOM ID of the element you wish you update with 
                        // the resuls of a Ajax operation.

$options['with']        // The DOM ID of the form element you wish to serialize
                        // and send with an Ajax form submission.

$options['type']        // Either 'asynchronous' (default), or 'synchronous'. 
                        // Allows you to pick between operation types.

/* Callbacks : JS code to be executed at certain 
   times during the XMLHttpRequest process */

$options['loading']     // JS code to be executed when the remote document 
                        // is being loaded with data by the browser.

$options['loaded']      // JS code to be executed when the browser has finished
                        // loading the remote document.

$options['interactive'] // JS code to be executed when the user can interact
                        // with the remote document, even though it has not 
                        // finished loading.

$options['complete']    // JS code to be called when the XMLHttpRequest is 
                        // complete.

$options['confirm']     // Text to be displayed in a confirmation dialog before
                        // a XMLHttpRequest action begins. 

$optoins['condition']   // JS condition to be met before the XMLHttpRequest 
                        // is initiated.

$options['before']      // JS code to be called before request is initiated.

$options['after']       // JS code to be called immediately after request was
                        // initiated and before 'loading'.

Here are the helper's functions for making Ajax in Cake quick and easy:

  • link ($title, $href, $options=null, $confirm = null, $escapeTitle = true)

    • Displays linked text $title, which retrieves the remote document at $options['url'] and updates the DOM element $options['update']. Callbacks can be used with this function.

  • remoteFunction ($options=null)

    • This function creates the JavaScript needed to make a remote call it is primarily used as a helper for linkToRemote. This isn't often used unless you need to generate some custom scripting.

  • remoteTimer ($options=null)

    • Periodically calls the specified action at $options['url'], every $options['frequency'] seconds (default is 10). Usually used to update a specified div (specified by $options['update']) with the results of the remote call. Callbacks can be used with this function.

  • form ($params = null, $type = 'post', $options = array()))

    • Returns a form tag that will submit to the action at $params using XMLHttpRequest in the background instead of the regular reload-required POST submission. The form data from this form will act just as a normal form data would (i.e. it will be available in $this->params['form']). The DOM element specified by $options['update'] will be updated with the resulting remote document. Callbacks can be used with this function.

  • observeField ($field_id, $options=null)

    • Observes the field with the DOM ID specified by $field_id (every $options['frequency'] seconds) and calls the action at $options['url'] when its contents have changed. You can update a DOM element with ID $options['update'] or specify a form element using $options['with'] as well. Callbacks can be used with this function.

  • observeForm ($form_id, $options=array())

    • Works the same as observeField(), only this observes all the elements in a given form.

  • autoComplete ($field, $url="", $options=array())

    • Renders a text field with ID $field with autocomplete. The action at $url should be able to return the autocomplete terms: basically, your action needs to spit out a unordered list (<ul></ul>) with list items that are the auto complete terms. If you wanted an autocomplete field that retrieved the subjects of your blog posts, your controller action might look something like:

      function autocomplete () {
          $this->set('posts', 
              $this->Post->findAll(
                  "subject LIKE '{$this->params['form']['autocomplete_field_name_goes_here']}'")
              );
          $this->layout = "ajax";
      }

      And your view for the autocomplete() action above would look something like:

      <ul>
      <?php foreach($posts as $post): ?>
      <li><?php echo $post['Post']['subject']; ?></li>
      <?php endforeach; ?>
      </ul>

      The actual auto-complete field as it would look in a view would look like this:

      <form action="/users/index" method="POST">
          <?=$ajax->autoComplete('Post/subject', '/posts/autoComplete')?>
          <?=$html->submit('View Post')?>
      </form>

      The autoComplete() function will use this information to render a text field, and some divs that will be used to show the autocomplete terms supplied by your action. You might also want to style the view with something like the following:

      <style type="text/css">
      
      div.auto_complete {
          position         :absolute;
          width            :250px;
          background-color :white;
          border           :1px solid #888;
          margin           :0px;
          padding          :0px;
      }
      
      li.selected { background-color: #ffb; }
      
      </style>
  • drag ($id, $options=array())

    • Makes the DOM element with ID $id draggable. There are some additional things you can specify using $options:

      // (The version numbers refer to script.aculo.us versions)
      
      $options['handle']     // (v1.0) Sets whether the element should only be 
                             // draggable by an embedded handle. The value must be 
                             // an element reference or element id.
      
      $options['handle']     // (V1.5) As above, except now the value may be a 
                             // string referencing a CSS class value. The first 
                             // child/grandchild/etc. element found within the 
                             // element that has this CSS class value will be used 
                             // as the handle.
      
      $options['revert']     // (V1.0) If set to true, the element returns to its 
                             // original position when the drags ends.
      
      $options['revert']     // (V1.5) Revert can also be an arbitrary function 
                             // reference, called when the drag ends.
      
      $options['constraint'] // If set to 鈥榟orizontal鈥?or 鈥榲ertical鈥?the drag will 
                             // be constrained to take place only horizontally or 
                             // vertically.
  • drop ($id, $options=array())

    • Makes the DOM element with ID $id drop-able. There are some additional things you can specify using $options:

      $options['accept']      // Set accept to a string or a JavaScript array of 
                              // strings describing CSS classes. The Droppable will 
                              // only accept Draggables that have one or more of 
                              // these CSS classes.
      
      $options['containment'] // The droppable element will only accept the 
                              // draggable element if it is contained in the given 
                              // elements (or element ids). Can be a single element
                              // or a JS array of elements.
      
      $options['overlap']     //If set to 鈥榟orizontal鈥?or 鈥榲ertical鈥?the droppable 
                              // will only react to a draggable element if its 
                              //overlapping by more than 50% in the given direction.
  • dropRemote ($id, $options=array(), $ajaxOptions=array())

    • Used to create a drop target that initiates a XMLHttpRequest when a draggable element is dropped on it. The $options are the same as in drop(), and the $ajaxOptions are the same as in link().

  • sortable ($id, $options=array())

    • Makes a list or group of floated objects (specified by DOM element ID $id) sortable. The $options array can configure your sorting as follows:

      $options['tag']         // Sets the kind of tag (of the child elements of the
                              // container) that will be made sortable. For UL and 
                              // OL containers, this is 鈥楲I鈥? you have to provide 
                              // the tag kind for other sorts of child tags. 
                              // Defaults to 'li'.
      
      $options['only']        // Further restricts the selection of child elements 
                              // to only encompass elements with the given CSS class
                              // (or, if you provide an array of strings, on any of
                              // the classes).
      
      $options['overlap']     // Either 鈥榲ertical鈥?(default) or 鈥榟orizontal鈥? For 
                              // floating sortables or horizontal lists, choose 
                              // 鈥榟orizontal鈥? Vertical lists should use 鈥榲ertical鈥?
      
      $options['constraint']  // Restricts the movement of draggable elements, 
                              // 'vertical' or 'horizontal'.
      
      $options['containment'] // Enables dragging and dropping between Sortables. 
                              // Takes an array of elements or element-ids (of the 
                              // containers).
      
      $options['handle']      // Makes the created draggable elemetns use handles, 
                              // see the handle option on drag().
      

The Time Helper provides methods that a developer may need for outputting Unix timestamps and/or datetime strings into more understandable phrases to the browser.

Dates can be provided to all functions as either valid PHP datetime strings or Unix timestamps.

  • fromString ($date_string)

    • Returns a UNIX timestamp, given either a UNIX timestamp or a valid strtotime() date string.

  • nice ($date_string=null, $return=false)

    • Returns a nicely formatted date string. Dates are formatted as "D, M jS Y, H:i", or 'Mon, Jan 1st 2005, 12:00'.

  • niceShort ($date_string=null, $return=false)

    • Formats date strings as specified in nice(), but ouputs "Today, 12:00" if the date string is today, or "Yesterday, 12:00" if the date string was yesterday.

  • isToday ($date_string)

    • Returns true if given datetime string is today.

  • daysAsSql ($begin, $end, $field_name, $return=false)

    • Returns a partial SQL string to search for all records between two dates.

  • dayAsSql ($date_string, $field_name, $return=false)

    • Returns a partial SQL string to search for all records between two times occurring on the same day.

  • isThisYear ($date_string, $return=false)

    • Returns true if given datetime string is within current year.

  • wasYesterday ($date_string, $return=false)

    • Returns true if given datetime string was yesterday.

  • isTomorrow ($date_string, $return=false)

    • Returns true if given datetime string is tomorrow.

  • toUnix ($date_string, $return=false)

    • Returns a UNIX timestamp from a textual datetime description. Wrapper for PHP function strtotime().

  • toAtom ($date_string, $return=false)

    • Returns a date formatted for Atom RSS feeds.

  • toRSS ($date_string, $return=false)

    • Formats date for RSS feeds

  • timeAgoInWords ($datetime_string, $return=false)

    • Returns either a relative date or a formatted date depending on the difference between the current time and given datetime. $datetime should be in a strtotime-parsable format like MySQL datetime.

  • relativeTime ($datetime_string, $return=false)

    • Works much like timeAgoInWords(), but includes the ability to create output for timestamps in the future as well (i.e. "Yesterday, 10:33", "Today, 9:42", and also "Tomorrow, 4:34").

  • wasWithinLast ($timeInterval, $date_string, $return=false)

    • Returns true if specified datetime was within the interval specified, else false. The time interval should be specifed with the number as well as the units: '6 hours', '2 days', etc.

Have the need for some help with your view code? If you find yourself needing a specific bit of view logic over and over, you can make your own view helper.

Here are some globally available constants and functions that you might find useful as you build your application with Cake.

Here are Cake's globally available functions. Many of them are convenience wrappers for long-named PHP functions, but some of them (like vendor() and uses()) can be used to include code, or perform other useful functions. Chances are if you're wanting a nice little function to do something annoying over and over, it's here.

  • config()

    • Loads Cake's core configuration file. Returns true on success.

  • uses($lib1, $lib2, $lib3...)

    • Used to load Cake's core libaries (found in cake/libs/). Supply the name of the lib filename without the '.php' extension.

      uses('sanitize', 'security');
  • vendor($lib1, $lib2, $lib3...)

    • Used to load external libraries found in the /vendors directory. Supply the name of the lib filename without the '.php' extension.

      vendor('myWebService', 'nusoap');
  • debug($var, $showHTML = false)

    • If the application's DEBUG level is non-zero, the $var is printed out. If $showHTML is true, the data is rendered to be browser-friendly.

  • a()

    • Returns an array of the parameters used to call the wrapping function.

      function someFunction() 
      {
          echo print_r(a('foo', 'bar'));
      } 
       
      someFunction();
      
      // output:
      
      array(
          [0] => 'foo', 
          [1] => 'bar'
      )
  • aa()

    • Used to create associative arrays formed from the parameters used to call the wrapping function.

      echo aa('a','b');
      
      // output:
      
      array(
          'a' => 'b'
      ) 
  • e($text)

    • Convenience wrapper for echo().

  • low()

    • Convenience wrapper for strtolower().

  • up()

    • Convenience wrapper for strtoupper().

  • r($search, $replace, $subject)

    • Convenience wrapper for str_replace().

  • pr($data)

    • Convenience function equivalent to:

      echo "<pre>" . print_r($data) . "</pre>";

      Only prints out information if DEBUG is non-zero.

  • am($array1, $array2, $array3...)

    • Merges and returns the arrays supplied in the parameters.

  • env($key)

    • Gets an environment variable from available sources. Used as a backup if $_SERVER or $_ENV are disabled.

      This function also emulates PHP_SELF and DOCUMENT_ROOT on unsupporting servers. In fact, it's a good idea to always use env() instead of $_SERVER or getenv() (especially if you plan to distribute the code), since it's a full emulation wrapper.

  • cache($path, $data, $expires, $target = 'cache')

    • Writes the data in $data to the path in /app/tmp specified by $path as a cache. The expiration time specified by $expires must be a valid strtotime() string. The $target of the cached data can either be 'cache' or 'public'.

  • clearCache($search, $path, $ext)

    • Used to delete files in the cache directories, or clear contents of cache directories.

      If $search is a string, matching cache directory or file names will be removed from the cache. The $search parameter can also be passed as an array of names of files/directories to be cleared. If empty, all files in /app/tmp/cache/views will be cleared.

      The $path parameter can be used to specify which directory inside of /tmp/cache is to be cleared. Defaults to 'views'.

      Teh $ext param is used to specify files with a certain file extention you wish to clear.

  • stripslashes_deep($array)

    • Recursively strips slashes from all values in an array.

  • countdim($array)

    • Returns the number of dimensions in the supplied array.

  • fileExistsInPath($file)

    • Searches the current include path for a given filename. Returns the path to that file if found, false if not found.

  • convertSlash($string)

    • Converts forward slashes to underscores and removes first and last underscores in a string.

Creating custom validation rules can help to make sure the data in a Model conforms to the business rules of the application, such as passwords can only be eight characters long, user names can only have letters, etc.

The first step to data validation is creating the validation rules in the Model. To do that, use the Model::validate array in the Model definition, for example:

Validations are defined using Perl-compatibile regular expressions, some of which are pre-defined in /libs/validators.php. These are:

VALID_NOT_EMPTY
VALID_NUMBER
VALID_EMAIL
VALID_YEAR

If there are any validations present in the model definition (i.e. in the $validate array), they will be parsed and checked during saves (i.e. in the Model::save() method). To validate the data directly use the Model::validates() (returns false if data is incorrect) and Model::invalidFields() (which returns an array of error messages).

But usually the data is implicit in the controller code. The following example demonstrates how to create a form-handling action:

The view used by this action can look like this:

The Controller::validates($model[, $model...]) is used to check any custom validation added in the model. The Controller::validationErrors() method returns any error messages thrown in the model so they can be displayed by tagErrorMsg() in the view.

If you'd like to perform some custom validation apart from the regex based Cake validation, you can use the invalidate() function of your model to flag a field as erroneous. Imagine that you wanted to show an error on a form when a user tries to create a username that already exists in the system. Because you can't just ask Cake to find that out using regex, you'll need to do your own validation, and flag the field as invalid to invoke Cake's normal form invalidation process.

The controller might look something like this:

<?php

class UsersController extends AppController
{
    function create()
    {
        // Check to see if form data has been submitted
        if ($this->params['data']['User'])
        {
            //See if a user with that username exists
            $user = $this->User->findByUsername($this->params['data']['User']['username']);

            // Invalidate the field to trigger the HTML Helper's error messages
            if (!empty($user['User']['username']))
            {
                $this->User->invalidate('username');
            }

            //Try to save as normal, shouldn't work if the field was invalidated.
            if($this->User->save($this->params['data']))
            {
                $this->redirect('/users/index/saved');
            }
            else
            {
                 $this->render();
            }
            
        }
    }
}

?>

Most important, powerful things require some sort of access control. Access control lists are a way to manage application permissions in a fine-grained, yet easily maintainable and manageable way. Access control lists, or ACL, handle two main things: things that want stuff, and things that are wanted. In ACL lingo, things (most often users) that want to use stuff are called access request objects, or AROs. Things in the system that are wanted (most often actions or data) are called access control objects, or ACOs. The entities are called 'objects' because sometimes the requesting object isn't a person - sometimes you might want to limit the access certain Cake controllers have to initiate logic in other parts of your application. ACOs could be anything you want to control, from a controller action, to a web service, to a line on your grandma's online diary.

To use all the acronyms at once: ACL is what is used to decide when an ARO can have access to an ACO.

Now, in order to help you understand this, let's use a practial example. Imagine, for a moment, a computer system used by a group of adventurers. The leader of the group wants to forge ahead on their quest while maintaining a healthy amount of privacy and security for the other members of the party. The AROs involved are as following:

Gandalf
Aragorn
Bilbo
Frodo
Gollum
Legolas
Gimli
Pippin
Merry

These are the entities in the system that will be requesting things (the ACOs) from the system. It should be noted that ACL is *not* a system that is meant to authenticate users. You should already have a way to store user information and be able to verify that user's identity when they enter the system. Once you know who a user is, that's where ACL really shines. Okay - back to our adventure.

The next thing Gandalf needs to do is make an initial list of things, or ACOs, the system will handle. His list might look something like:

Weapons
The One Ring
Salted Pork
Diplomacy
Ale

Traditionally, systems were managed using a sort of matrix, that showed a basic set of users and permissions relating to objects. If this information were stored in a table, it might look like the following, with X's indicating denied access, and O's indicating allowed access.

         Weapons    The One Ring    Salted Pork      Diplomacy       Ale
Gandalf     X             X              O               O            O
Aragorn     O             X              O               O            O
Bilbo       X             X              X               X            O
Frodo       X             O              X               X            O
Gollum      X             X              O               X            X
Legolas     O             X              O               O            O       
Gimli       O             X              O               X            X
Pippin      X             X              X               O            O
Merry       X             X              X               X            O

At first glance, it seems that this sort of system could work rather well. Assignments can be made to protect security (only Frodo can access the ring) and protect against accidents (keeping the hobbits out of the salted pork). It seems fine grained enough, and easy enough to read, right?

For a small system like this, maybe a matrix setup would work. But for a growing system, or a system with a large amount of resources (ACOs) and users (AROs), a table can become unwieldy rather quickly. Imagine trying to control access to the hundreds of war encampments and trying to manage them by unit, for example. Another drawback to matrices is that you can't really logically group sections of users, or make cascading permissions changes to groups of users based on those logical groupings. For example, it would sure be nice to automatically allow the hobbits access to the ale and pork once the battle is over: Doing it on an indivudual user basis would be tedious and error prone, while making a cascading permissions change to all 'hobbits' would be easy.

ACL is most usually implemented in a tree structure. There is usually a tree of AROs and a tree of ACOs. By organizing your objects in trees, permissions can still be dealt out in a granular fashion, while still maintaining a good grip on the big picture. Being the wise leader he is, Gandalf elects to use ACL in his new system, and organizes his objects along the following lines:

Fellowship of the Ring:
Warriors
    Aragorn
    Legolas
    Gimli
Wizards
    Gandalf
Hobbits
    Frodo
    Bilbo
    Merry
    Pippin
Vistors
    Gollum

By structuring our party this way, we can define access controls to the tree, and apply those permissions to any children. The default permission is to deny access to everything. As you work your way down the tree, you pick up permissions and apply them. The last permission applied (that applies to the ACO you're wondering about) is the one you keep. So, using our ARO tree, Gandalf can hang on a few permissions:

Fellowship of the Ring: [Deny: ALL]
    Warriors                [Allow: Weapons, Ale, Elven Rations, Salted Pork]
        Aragorn
        Legolas
        Gimli
    Wizards                 [Allow: Salted Pork, Diplomacy, Ale]
        Gandalf
    Hobbits                 [Allow: Ale]
        Frodo
        Bilbo
        Merry
        Pippin
    Vistors                 [Allow: Salted Pork]
        Gollum

If we wanted to use ACL to see if the Pippin was allowed to access the ale, we'd first get his path in the tree, which is Fellowship->Hobbits->Pippin. Then we see the different permissions that reside at each of those points, and use the most specific permission relating to Pippin and the Ale.

  • Fellowship = DENY Ale, so deny (because it is set to deny all ACOs)

  • Hobbits = ALLOW Ale, so allow

  • Pippin = ?; There really isn't any ale-specific information so we stick with ALLOW.

  • Final Result: allow the ale.

The tree also allows us to make finer adjustments for more granular control - while still keeping the ability to make sweeping changes to groups of AROs:

Fellowship of the Ring: [Deny: ALL]
    Warriors                [Allow: Weapons, Ale, Elven Rations, Salted Pork]
        Aragorn             [Allow: Diplomacy]
        Legolas
        Gimli
    Wizards                 [Allow: Salted Pork, Diplomacy, Ale]
        Gandalf
    Hobbits                 [Allow: Ale]
        Frodo               [Allow: Ring]
        Bilbo
        Merry               [Deny: Ale]
        Pippin              [Allow: Diplomacy]
    Vistors                 [Allow: Salted Pork]
        Gollum

You can see this because the Aragorn ARO maintains is permissions just like others in the Warriors ARO group, but you can still make fine-tuned adjustments and special cases when you see fit. Again, permissions default to DENY, and only change as the traversal down the tree forces an ALLOW. To see if Merry can access the Ale, we'd find his path in the tree: Fellowship->Hobbits->Merry and work our way down, keeping track of ale-related permissions:

  • Fellowship = DENY (because it is set to deny all), so deny the ale.

  • Hobbits = ALLOW: ale, so allow the ale

  • Merry = DENY ale, so deny the ale

  • Final Result: deny the ale.

The default ACL permissions implementation is database stored. Database ACL, or dbACL consists of a set of core models, and a command-line script that comes with your Cake installation. The models are used by Cake to interact with your database in order to store and retrieve nodes the ACL trees. The command-line script is used to help you get started and be able to interact with your trees.

To get started, first you'll need to make sure your /app/config/database.php is present and correctly configured. For a new Cake installation, the easiest way to tell that this is so is to bring up the installation directory using a web browser. Near the top of the page, you should see the messages "Your database configuration file is present." and "Cake is able to connect to the database." if you've done it correctly. See section 4.1 for more information on database configuration.

Next, use the the ACL command-line script to initialize your database to store ACL information. The script found at /cake/scripts/acl.php will help you accomplish this. Initialize the your database for ACL by executing the following command (from your /cake/scripts/ directory):

At this point, you should be able to check your project's database to see the new tables. If you're curious about how Cake stores tree information in these tables, read up on modified database tree traversal. Basically, it stores nodes, and their place in the tree. The acos and aros tables store the nodes for their respective trees, and the aros_acos table is used to link your AROs to the ACOs they can access.

Now, you should be able to start creating your ARO and ACO trees.

There are two ways of referring to AROs/ACOs. One is by giving them an numeric id, which is usually just the primary key of the table they belong to. The other way is by giving them a string alias. The two are not mutually exclusive.

The way to create a new ARO is by using the methods defined the the Aro Cake model. The create() method of the Aro class takes three parameters: $link_id, $parent_id, and $alias. This method creates a new ACL object under the parent specified by a parent_id - or as a root object if the $parent_id passed is null. The $link_id allows you to link a current user object to Cake's ACL structures. The alias parameter allows you address your object using a non-integer ID.

Before we can create our ACOs and AROs, we'll need to load up those classes. The easiest way to do this is to include Cake's ACL Component in your controller using the $components array:

var $components = array('Acl');

Once we've got that done, let's see what some examples of creating these objects might look like. The following code could be placed in a controller action somewhere:

$aro = new Aro();

// First, set up a few AROs.
// These objects will have no parent initially.

$aro->create( 1, null, 'Bob Marley' );
$aro->create( 2, null, 'Jimi Hendrix');
$aro->create( 3, null, 'George Washington');
$aro->create( 4, null, 'Abraham Lincoln');

// Now, we can make groups to organize these users:
// Notice that the IDs for these objects are 0, because 
//     they will never tie to users in our system

$aro->create(0, null, 'Presidents');
$aro->create(0, null, 'Artists');

//Now, hook AROs to their respective groups:

$aro->setParent('Presidents', 'George Washington');
$aro->setParent('Presidents', 'Abraham Lincoln');
$aro->setParent('Artists', 'Jimi Hendrix');
$aro->setParent('Artists', 'Bob Marley');

//In short, here is how to create an ARO:
$aro = new Aro(); 
$aro->create($user_id, $parent_id, $alias);

You can also create AROs using the command line script using $acl.php create aro <link_id> <parent_id> <alias>.

Creating an ACO is done in a similar manner:

$aco = new Aco();

//Create some access control objects:
$aco->create(1, null, 'Electric Guitar');
$aco->create(2, null, 'United States Army');
$aco->create(3, null, 'Fans');

// I suppose we could create groups for these
// objects using setParent(), but we'll skip that
// for this particular example

//So, to create an ACO:
$aco = new Aco();
$aco->create($id, $parent, $alias);

The corresponding command line script command would be: $acl.php create aco <link_id> <parent_id> <alias>.

After creating our ACOs and AROs, we can finally assign permission between the two groups. This is done using Cake's core Acl component. Let's continue on with our example:

// First, in a controller, we'll need access 
// to Cake's ACL component:

class SomethingsController extends AppController
{
    // You might want to place this in the AppController
    // instead, but here works great too.

    var $components = array('Acl');

    // Remember: ACL will always deny something
    // it doesn't have information on. If any 
    // checks were made on anything, it would
    // be denied. Let's allow an ARO access to an ACO.

    function someAction()
    {
        //ALLOW
    
        // Here is how you grant an ARO full access to an ACO
        $this->Acl->allow('Jimi Hendrix', 'Electric Guitar');
        $this->Acl->allow('Bob Marley',   'Electric Guitar');

        // We can also assign permissions to groups, remember?
        $this->Acl->Allow('Presidents', 'United States Army');
        
        // The allow() method has a third parameter, $action.
        // You can specify partial access using this parameter.
        // $action can be set to create, read, update or delete.
        // If no action is specified, full access is assumed.

        // Look, don't touch, gentlemen:
        $this->Acl->allow('George Washington', 'Electric Guitar', 'read');
        $this->Acl->allow('Abraham Lincoln',   'Electric Guitar', 'read');

        //DENY

        //Denies work in the same manner:

        //When his term is up...
        $this->Acl->deny('Abraham Lincoln', 'United States Army');


    }
}

This particular controller isn't especially useful, but it is mostly meant to show you how the process works. Using the Acl component in connection with your user management controller would be the best usage. Once a user has been created on the system, her ARO could be created and placed at the right point of the tree, and permissions could be assigned to specific ACO or ACO groups based on her identity.

Permissions can also be assigned using the command line script packaged with Cake. The syntax is similar to the model functions, and can be viewed by executing $php acl.php help.

This section explains how to use some of the functions that Sanitize offers.

  • paranoid($string, $allowed = array())

    • This function strips anything out of the target $string that is not a plain-jane alphanumeric character. You can, however, let it overlook certain characters by passing them along inside the $allowed array.

      $badString = ";:<script><html><   // >@@#";
      
      echo $mrClean->paranoid($badString);
      
      // output: scripthtml
      
      echo $mrClean->paranoid($badString, array(' ', '@'));
      
      // output: scripthtml    @@
  • html($string, $remove = false)

    • This method helps you get user submitted data ready for display inside an existing HTML layout. This is especially useful if you don't want users to be able to break your layouts or insert images or scripts inside of blog comments, forum posts, and the like. If the $remove option is set to true, any HTML is removed rather than rendered as HTML entities.

      $badString = '<font size="99" color="#FF0000">HEY</font><script>...</script>';
      
      echo $mrClean->html($badString);
      
      // output: &lt;font size=&quot;99&quot; color=&quot;#FF0000&quot;&gt;HEY&lt;/font&gt;&lt;script&gt;...&lt;/script&gt;
      
      echo $mrClean->html($badString, true);
      
      // output: font size=99 color=#FF0000 HEY fontscript...script
  • sql($string)

    • Used to escape SQL statements by adding slashes, depending on the system's current magic_quotes_gpc setting.

  • cleanArray(&$toClean)

    • This function is an industrial strength, multi-purpose cleaner, meant to be used on entire arrays (like $this->params['form'], for example). The function takes an array and cleans it: nothing is returned because the array is passed by reference. The following cleaning operations are performed on each element in the array (recursively):

      • Odd spaces (including 0xCA) are replaced with regular spaces.

      • HTML is replaced by its corresponding HTML entities (including \n to <br>).

      • Double-check special chars and remove carriage returns for increased SQL security.

      • Add slashes for SQL (just calls the sql function outlined above).

      • Swap user-inputted backslashes with trusted backslashes.

The Cake session component is used to interact with session information. It includes basic session reading and writing, but also contains features for using sessions for error and reciept messages (i.e. "Your data has been saved"). The Session Component is available in all Cake controllers by default.

Here are some of the functions you'll use most:

  • check($name)

    • Checks to see if the current key specified by $name has been set in the session.

  • del($name) and delete($name)

    • Deletes the session variable specified by $name.

  • error()

    • Returns the last error created by the CakeSession component. Mostly used for debugging.

  • flash($key = 'flash')

    • Returns the last message set in the session using setFlash(). If $key has been set, the message returned is the most recent stored under that key.

  • read($name = null)

    • Returns the session variable specified by $name.

  • renew()

    • Renews the currently active session by creating a new session ID, deleting the old one, and passing the old session data to the new one.

  • setFlash($flashMessage, $layout = 'default', $params = array(), $key = 'flash')

    • Writes the message specified by $flashMessage into the session (to be later retrieved by flash()).

      If $layout is set to 'default', the message is stored as '<div class="message">'.$flashMessage.'</div>'. If $default is set to '', the message is stored just as it has been passed. If any other value is passed, the message is stored inside the Cake view specified by $layout.

      Params has been placed in this function for future usage. Check back for more info.

      The $key variable allows you to store flash messages under keys. See flash() for retreiving a flash message based off of a key.

  • valid()

    • Returns true if the session is valid. Best used before read() operations to make sure that the session data you are trying to access is in fact valid.

  • write($name, $value)

    • Writes the variable specified by $name and $value into the active session.

Let's just dive in:

  • accepts($type)

    • Returns information about the content-types that the client accepts, depending on the value of $type. If null or no value is passed, it will return an array of content-types the client accepts. If a string is passed, it returns true if the client accepts the given type, by checking $type against the content-type map (see setContent()). If $type is an array, each string is evaluated individually, and accepts() will return true if just one of them matches an accepted content-type. For example:

      class PostsController extends AppController
      {
          var $components = array('RequestHandler');
      
          function beforeFilter ()
          {
              if ($this->RequestHandler->accepts('html'))
              {
                  // Execute code only if client accepts an HTML (text/html) response
              }
              elseif ($this->RequestHandler->accepts('rss'))
              {
                  // Execute RSS-only code
              }
              elseif ($this->RequestHandler->accepts('atom'))
              {
                  // Execute Atom-only code
              }
              elseif ($this->RequestHandler->accepts('xml'))
              {
                  // Execute XML-only code
              }
      
              if ($this->RequestHandler->accepts(array('xml', 'rss', 'atom')))
              {
                  // Executes if the client accepts any of the above: XML, RSS or Atom
              }
          }
      }
  • getAjaxVersion()

    • If you are using the Prototype JS libraries, you can fetch a special header it sets on AJAX requests. This function returns the Prototype version used.

  • getClientIP ()

    • Returns the remote client's IP address.

  • getReferrer ()

    • Returns the server name from which the request originated.

  • isAjax()

    • Returns true if the current request was an XMLHttpRequest.

  • isAtom()

    • Returns true if the client accepts Atom feed content (application/atom+xml).

  • isDelete()

    • Returns true if the current request was via DELETE.

  • isGet()

    • Returns true if the current request was via GET.

  • isMobile()

    • Returns true if the user agent string matches a mobile web browser.

  • isPost()

    • Returns true if the current request was via POST.

  • isPut()

    • Returns true if the current request was via PUT.

  • isRss()

    • Returns true if the clients accepts RSS feed content (application/rss+xml).

  • isXml()

    • Returns true if the client accepts XML content (application/xml or text/xml).

  • setContent($name, $type)

    • Adds a content-type alias mapping, for use with accepts() and prefers(), where $name is the name of the mapping (string), and $type is either a string or an array of strings, each of which is a MIME type. The built-in type mappings are as follows:

      // Name     => Type
        'js'      => 'text/javascript',
        'css'     => 'text/css',
        'html'    => 'text/html',
        'form'    => 'application/x-www-form-urlencoded',
        'file'    => 'multipart/form-data',
        'xhtml'   => array('application/xhtml+xml', 'application/xhtml', 'text/xhtml'),
        'xml'     => array('application/xml', 'text/xml'),
        'rss'     => 'application/rss+xml',
        'atom'    => 'application/atom+xml'
      

The Security component contains two primary methods for restricting access to controller actions:

  • requirePost($action1, $action2, ...$actionN);

    • In order for the specified actions to execute, they must be requested via POST.

  • requireAuth($action1, $action2, ...$actionN);

    • Ensures that a request is coming from within the application by checking an authentication key in the POST'ed form data against an authentication key stored in the user's session. If they match, the action is allowed to execute. Be aware, however, that for reasons of flexibility, this check is only run if form data has actually been posted. If the action is called with a regular GET request, requireAuth() will do nothing. For maximum security, you should use requirePost() and requireAuth() on actions that you want fully protected. Learn more about how the authentication key is generated, and how it ends up where it should in Section 4 below.

But first, let's take a look at a simple example:

class ThingsController extends AppController
{
    var $components = array('Security');

    function beforeFilter()
    {
        $this->Security->requirePost('delete');
    }

    function delete($id)
    {
        // This will only happen if the action is called via an HTTP POST request
        $this->Thing->del($id);
    }
}

Here, we're telling the Security component that the 'delete' action requires a POST request. The beforeFilter() method is usually where you'll want to tell Security (and most other components) what to do with themselves. It will then go do what it's told right after beforeFilter() is called, but right before the action itself is called.

And that's about all there is to it. You can test this by typing the URL for the action into your browser and seeing what happens.

The requireAuth() method allows you to be very detailed in specifying how and from where an action can be accessed, but it comes with certain usage stipulations, which become clear when you understand how this method of authentication works. As stated above, requireAuth() works by comparing an authentication key in the POST data to the key stored in the user's session data. Therefore, the Security component must be included in both the controller recieveing the request, as well as the controller making the request.

For example, if I have an action in PostsController with a view containing a form that POSTs to an action in CommentsController, then the Security component must be included in both CommentsController (which is receiving the request, and actually protecting the action), as well as PostsController (from which the request will be made).

Every time the Security component is loaded, even if it is not being used to protect an action, it does the following things: First, it generates an authentication key using the core Security class. Then, it writes this key to the session, along with an expiration date and some additional information (the expiration date is determined by your session security setting in /app/config/core.php). Next, it sets the key in your controller, to be referenced later.

Then in your view files, any form tag you generate using $html->formTag() will also contain a hidden input field with the authentication key. That way, when the form is POSTed, the Security component can compare that value to the value in the session on the receiving end of the request. After that, the authentication key is regenerated, and the session is updated for the next request.

Occasionally a new user will run in to mod_rewrite issues, so I'll mention them marginally here. If the Cake welcome page looks a little funny (no images or css styles), it probably means mod_rewrite isn't functioning on your system. Here are some tips to help get you up and running:

  • Make sure that an .htaccess override is allowed: in your httpd.conf, you should have a section that defines a section for each Directory on your server. Make sure the AllowOverride is set to All for the correct Directory.

  • Make sure you are editing the system httpd.conf rather than a user- or site-specific httpd.conf.

  • For some reason or another, you might have obtained a copy of CakePHP without the needed .htaccess files. This sometimes happens because some operating systems treat files that start with '.' as hidden, and don't copy them. Make sure your copy of CakePHP is from the downloads section of the site or our SVN repository.

  • Make sure you are loading up mod_rewrite correctly! You should see something like LoadModule rewrite_module libexec/httpd/mod_rewrite.so and AddModule mod_rewrite.c in your httpd.conf.

If you don't want or can't get mod_rewrite (or some other compatible module) up and running on your server, you'll need to use Cake's built in pretty URLs. In /app/config/core.php, uncomment the line that looks like:

define ('BASE_URL', env('SCRIPT_NAME'));

This will make your URLs look like www.example.com/index.php/controllername/actionname/param rather than www.example.com/controllername/actionname/param.

Next we'll create a controller for our posts. The controller is where all the logic for post interaction will happen, and its also where all the actions for this model will be found. You should place this new controller in a file called posts_controller.php inside your /app/controllers directory. Here's what the basic controller should look like:

Now, lets add an action to our controller. When users request www.example.com/posts, this is the same as requesting www.example.com/posts/index. Since we want our readers to view a list of posts when they access that URL, the index action would look something like this:

Now that we have our database connected using our model, and our application logic and flow defined by our controller, let's create a view for the index action we defined above.

Cake views are just HTML and PHP flavored fragments that fit inside an application's layout. Layouts can be defined and switched between, but for now, let's just use the default.

Remember in the last section how we assigned the 'posts' variable to the view using the set() method? That would hand down 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] => 2006-03-08 14:42:22
                    [modified] =>
                )
         )
    [1] => Array
        (
            [Post] => Array
                (
                    [id] => 2
                    [title] => A title once again
                    [body] => And the post body follows.
                    [created] => 2006-03-08 14:42:23
                    [modified] =>
                )
        )
    [2] => Array
        (
            [Post] => Array
                (
                    [id] => 3
                    [title] => Title strikes back
                    [body] => This is really exciting! Not.
                    [created] => 2006-03-08 14:42:24
                    [modified] =>
                )
         )
)

Cake's view files are stored in /app/views inside a folder named after the controller they correspond to (we'll have to create a folder named 'posts' in this case). To format this post data in a nice table, our view code might look something like this:

Hopefully this should look somewhat simple.

You might have noticed the use of an object called $html. This is an instance of the HtmlHelper class. Cake 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 Chapter 9, 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 URL's in Cake, you simply give a path relative to the base of the application, and Cake fills in the rest. As such, your URL's will typically take the form of /controller/action/id.

Now 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 (that link a post's title to a URL /posts/view/some_id), you were probably informed by Cake that the action hasn'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 now:

The set() call should look familiar. Notice we're using read() rather than findAll() because we only really want a single post's information.

Notice that our view action takes a parameter. This parameter is handed to the action by the URL called. If a user requests /posts/view/3, then the value '3' is passed as $id.

Now let's create the view for our new 'view' action and place it in /app/views/posts/view.thtml.

Verify that this is working by trying the links at /posts/index or manually requesting a post by accessing /posts/view/1.

reading from the database and showing us the posts is fine and dandy, but let's allow for the adding of new posts.

First, start with the add() action in the PostsController:

Cake goes a long way in taking the monotany out of form input validation. Everyone hates coding up endless forms and their validation routines, and Cake makes it easier and faster.

To take advantage of the validation features, you'll need to use Cake's HtmlHelper in your views. The HtmlHelper is available by default to all views at $html.

Here's our add view:

As with $html->link(), $html->formTag() will generate a proper URL from the controller and action we have given it. By default, it prints out a POST form tag, but this can be modified by the second parameter. The $html->input() and $html->textarea() functions spit out form elements of the same name. The first parameter tells Cake which model/field they correspond to, and the second param is for extra HTML attributes (like the size of the input field). Again, refer to Chapter 9 for more on helpers.

The tagErrorMsg() function calls will output the error messages in case there is a validation problem.

If you'd like, you can update your /app/views/posts/index.thtml view to include a new "Add Post" link that points to www.example.com/posts/add.

That seems cool enough, but how do I tell Cake about my validation requirements? This is where we come back to the model.

The $validate array tells Cake how to validate your data when the save() method is called. The values for those keys are just constants set by Cake that translate to regex matches (see /cake/libs/validators.php). Right now Cake's validation is regex based, but look for a more robust solution in the future.

Now that you have your validation in place, use the app to try to add a post without a title or body to see how it works.

So... post editing: here we go. You're a Cake 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 Posts Controller would look like:

This checks for submitted form data. If nothing was submitted, go find the Post and hand it to the view. If some data has been submitted, try to save the Post model (or kick back and show the user the validation errors).

The edit view might look something like this:

This view ouputs the edit form (with the values populated), and the necessary error messages (if present). One thing to note here: Cake will assume that you are edititing a model if the 'id' field is present and exists in a currently stored model. If no 'id' is present (look back at our add view), Cake 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: