Академический Документы
Профессиональный Документы
Культура Документы
Release 3.x
Contents
CakePHP at a Glance
Conventions Over Configuration
The Model Layer . . . . . . . .
The View Layer . . . . . . . . .
The Controller Layer . . . . . .
CakePHP Request Cycle . . . .
Just the Start . . . . . . . . . . .
Additional Reading . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
1
1
2
2
3
3
5
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
29
29
29
29
30
30
30
30
31
31
31
31
31
32
33
34
35
i
Log . . . . . . . . . .
Routing . . . . . . . .
Network . . . . . . . .
Sessions . . . . . . . .
Network\Http . . . . .
Network\Email . . . .
Controller . . . . . . .
Controller\Components
Model . . . . . . . . .
TestSuite . . . . . . . .
View . . . . . . . . . .
View\Helper . . . . . .
I18n . . . . . . . . . .
L10n . . . . . . . . . .
Testing . . . . . . . . .
Utility . . . . . . . . .
4
ii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
35
36
38
38
39
39
39
41
43
44
45
47
51
52
52
53
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
57
57
65
72
76
88
95
Contributing
Documentation . . . . . . . . .
Tickets . . . . . . . . . . . . . .
Code . . . . . . . . . . . . . . .
Coding Standards . . . . . . . .
Backwards Compatibility Guide
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
105
105
114
115
118
129
Installation
Requirements . . .
Installing CakePHP
Permissions . . . .
Development Server
Production . . . . .
Fire It Up . . . . .
URL Rewriting . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
133
133
134
135
136
136
137
137
.
.
.
.
.
.
143
143
145
146
147
148
150
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Configuration
Configuring your Application . . . . . . .
Additional Class Paths . . . . . . . . . .
Inflection Configuration . . . . . . . . . .
Configure Class . . . . . . . . . . . . . .
Reading and writing configuration files . .
Creating your Own Configuration Engines
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Routing
Quick Tour . . . . . . . . . . . . . .
Connecting Routes . . . . . . . . . .
Creating RESTful Routes . . . . . . .
Passed Arguments . . . . . . . . . . .
Generating URLs . . . . . . . . . . .
Redirect Routing . . . . . . . . . . .
Custom Route Classes . . . . . . . .
Creating Persistent URL Parameters .
Handling Named Parameters in URLs
.
.
.
.
.
.
.
.
.
155
155
157
167
170
171
172
173
174
175
181
181
188
194
10 Controllers
The App Controller . . . . . . . .
Request Flow . . . . . . . . . . .
Controller Actions . . . . . . . . .
Interacting with Views . . . . . .
Redirecting to Other Pages . . . .
Loading Additional Models . . . .
Paginating a Model . . . . . . . .
Configuring Components to Load .
Configuring Helpers to Load . . .
Request Life-cycle Callbacks . . .
More on Controllers . . . . . . . .
11 Views
The App View . . . . . . . . . .
View Templates . . . . . . . . .
Using View Blocks . . . . . . .
Layouts . . . . . . . . . . . . .
Elements . . . . . . . . . . . . .
View Events . . . . . . . . . . .
Creating Your Own View Classes
More About Views . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
197
197
198
198
199
202
203
204
204
205
205
206
.
.
.
.
.
.
.
.
253
253
254
257
260
262
265
265
266
14 Caching
Configuring Cache Class . . . . . . . . . . .
Writing to a Cache . . . . . . . . . . . . . .
Reading From a Cache . . . . . . . . . . . .
Deleting From a Cache . . . . . . . . . . . .
Clearing Cached Data . . . . . . . . . . . . .
Using Cache to Store Counters . . . . . . . .
Using Cache to Store Common Query Results
Using Groups . . . . . . . . . . . . . . . . .
Globally Enable or Disable Cache . . . . . .
Creating a Storage Engine for Cache . . . . .
15 Shells, Tasks & Console Tools
The CakePHP Console . . . . . . . . . .
Creating a Shell . . . . . . . . . . . . . .
Shell Tasks . . . . . . . . . . . . . . . .
Shell Helpers . . . . . . . . . . . . . . .
Invoking Other Shells from Your Shell . .
Getting User Input . . . . . . . . . . . . .
Creating Files . . . . . . . . . . . . . . .
Console Output . . . . . . . . . . . . . .
Stopping Shell Execution . . . . . . . . .
Hook Methods . . . . . . . . . . . . . . .
Configuring Options and Generating Help
Routing in Shells / CLI . . . . . . . . . .
More Topics . . . . . . . . . . . . . . . .
16 Debugging
Basic Debugging . . . . . . .
Using the Debugger Class . . .
Outputting Values . . . . . . .
Logging With Stack Traces . .
Generating Stack Traces . . .
Getting an Excerpt From a File
Using Logging to Debug . . .
Debug Kit . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
533
534
535
537
537
538
538
539
539
540
541
.
.
.
.
.
.
.
.
.
.
.
.
.
543
543
545
547
548
548
549
550
550
553
553
554
563
564
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
575
575
576
576
576
577
577
578
578
17 Deployment
Moving files . . . . . . . . . . . . . . . .
Adjust config/app.php . . . . . . . . . . .
Check Your Security . . . . . . . . . . .
Set Document Root . . . . . . . . . . . .
Improve Your Applications Performance
Deploying an update . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
579
579
579
580
580
580
581
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
18 Email
583
Basic Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 583
Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 584
iv
Setting Headers . . . . . .
Sending Templated Emails
Sending Attachments . . .
Using Transports . . . . .
Sending Messages Quickly
Sending Emails from CLI .
Creating Reusable Emails .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
587
587
588
589
590
591
591
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
595
595
596
596
597
597
600
601
601
602
20 Events System
Example Event Usage . . .
Accessing Event Managers
Core Events . . . . . . . .
Registering Listeners . . .
Dispatching Events . . . .
Conclusion . . . . . . . .
Additional Reading . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
605
605
606
607
608
611
614
614
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
615
615
617
621
625
625
.
.
.
.
.
.
.
.
.
627
627
629
630
630
630
631
633
634
634
.
.
.
.
.
.
.
22 Logging
Logging Configuration . . .
Error and Exception Logging
Interacting with Log Streams
Using the FileLog Adapter .
Logging to Syslog . . . . . .
Writing to Logs . . . . . . .
Log API . . . . . . . . . . .
Logging Trait . . . . . . . .
Using Monolog . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
23 Modelless Forms
637
Creating a Form . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 637
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
638
639
640
640
640
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
643
643
644
645
646
647
648
649
651
651
652
653
653
25 REST
655
The Simple Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 655
Accepting Input in Other Formats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 658
RESTful Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 658
26 Security
659
Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 659
27 Sessions
Session Configuration . . . . . . . . . . .
Built-in Session Handlers & Configuration
Setting ini directives . . . . . . . . . . . .
Creating a Custom Session Handler . . .
Accessing the Session Object . . . . . . .
Reading & Writing Session Data . . . . .
Destroying the Session . . . . . . . . . .
Rotating Session Identifiers . . . . . . . .
Flash Messages . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
663
663
664
666
667
668
669
670
670
670
28 Testing
Installing PHPUnit . . . . . .
Test Database Setup . . . . . .
Checking the Test Setup . . . .
Test Case Conventions . . . .
Creating Your First Test Case .
Running Tests . . . . . . . . .
Test Case Lifecycle Callbacks
Fixtures . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
671
671
672
672
673
673
675
677
677
vi
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
683
685
694
694
696
697
699
700
701
701
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
705
705
712
713
714
30 App Class
Finding Classes . . . . . . .
Finding Paths to Namespaces
Locating Plugins . . . . . .
Locating Themes . . . . . .
Loading Vendor Files . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
715
715
715
716
716
716
31 Collections
Quick Example . . . .
List of Methods . . . .
Iterating . . . . . . . .
Filtering . . . . . . . .
Aggregation . . . . . .
Sorting . . . . . . . . .
Working with Tree Data
Other Methods . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
719
719
720
720
724
725
729
730
732
741
741
742
746
29 Validation
Creating Validators .
Validating Data . . .
Validating Entities . .
Core Validation Rules
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
33 Hash
749
Hash Path Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 749
34 Http Client
Doing Requests . . . . . . . . . . . .
Creating Multipart Requests with Files
Sending Request Bodies . . . . . . .
Request Method Options . . . . . . .
Authentication . . . . . . . . . . . . .
Creating Scoped Clients . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
765
765
766
767
768
768
770
vii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
775
775
776
776
776
777
777
777
36 Number
Formatting Currency Values . . . . . .
Setting the Default Currency . . . . . .
Formatting Floating Point Numbers . .
Formatting Percentages . . . . . . . . .
Interacting with Human Readable Values
Formatting Numbers . . . . . . . . . .
Format Differences . . . . . . . . . . .
Configure formatters . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
779
779
780
780
781
781
782
783
784
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
37 Registry Objects
785
Loading Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 785
Triggering Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 785
Disabling Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 786
38 Text
Convert Strings into ASCII . . . . . .
Creating URL Safe Strings . . . . . .
Generating UUIDs . . . . . . . . . .
Simple String Parsing . . . . . . . . .
Formatting Strings . . . . . . . . . .
Wrapping Text . . . . . . . . . . . . .
Highlighting Substrings . . . . . . . .
Removing Links . . . . . . . . . . . .
Truncating Text . . . . . . . . . . . .
Truncating the Tail of a String . . . .
Extracting an Excerpt . . . . . . . . .
Converting an Array to Sentence Form
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
787
788
788
789
789
789
790
791
791
791
792
793
794
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
795
796
796
797
800
801
801
viii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Dates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 801
Immutable Dates and Times . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 802
Accepting Localized Request Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 803
40 Xml
805
Importing Data to Xml Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805
Transforming a XML String in Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 806
Transforming an Array into a String of XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 806
41 Constants & Functions
809
Global Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 809
Core Definition Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 811
Timing Definition Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 812
42 Debug Kit
Installation . . . . . . . . .
DebugKit Storage . . . . . .
Toolbar Usage . . . . . . . .
Using the History Panel . . .
Developing Your Own Panels
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
813
813
813
814
815
815
43 Migrations
Installation . . . . . . . . . . . . . . . . . . . .
Overview . . . . . . . . . . . . . . . . . . . . .
Creating Migrations . . . . . . . . . . . . . . . .
Generating migrations from an existing database .
Generating a diff between two database states . .
The commands . . . . . . . . . . . . . . . . . .
Using Migrations In Plugins . . . . . . . . . . .
Running Migrations in a non-shell environment .
Tips and tricks . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
819
819
820
821
826
827
828
832
832
834
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
44 Appendices
837
3.x Migration Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 837
General Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 860
PHP Namespace Index
863
Index
865
ix
CHAPTER 1
CakePHP at a Glance
CakePHP is designed to make common web-development tasks simple, and easy. By providing an all-in-one
toolbox to get you started the various parts of CakePHP work well together or separately.
The goal of this overview is to introduce the general concepts in CakePHP, and give you a quick overview
of how those concepts are implemented in CakePHP. If you are itching to get started on a project, you can
start with the tutorial, or dive into the docs.
echo $row->username;
}
You may notice that we didnt have to write any code before we could start working with our data. By using
conventions, CakePHP will use standard classes for table and entity classes that have not yet been defined.
If we wanted to make a new user and save it (with validation) we would do something like:
use Cake\ORM\TableRegistry;
$users = TableRegistry::get('Users');
$user = $users->newEntity(['email' => 'mark@example.com']);
$users->save($user);
The View layer provides a number of extension points like View Templates, Elements and View Cells to let
you re-use your presentation logic.
The View layer is not only limited to HTML or text representation of the data. It can be used to deliver
common data formats like JSON, XML, and through a pluggable architecture any other format you may
need, such as CSV.
You may notice that we never explicitly rendered a view. CakePHPs conventions will take care of selecting
the right view and rendering it with the view data we prepared with set().
A caching framework that integrates with Memcached, Redis and other backends.
Powerful code generation tools so you can start immediately.
Integrated testing framework so you can ensure your code works perfectly.
The next obvious steps are to download CakePHP, read the tutorial and build something awesome.
Additional Reading
Where to Get Help
The Official CakePHP website
http://www.cakephp.org
The Official CakePHP website is always a great place to visit. It features links to oft-used developer tools,
screencasts, donation opportunities, and downloads.
The Cookbook
http://book.cakephp.org
This manual should probably be the first place you go to get answers. As with many other open source
projects, we get new folks regularly. Try your best to answer your questions on your own first. Answers
may come slower, but will remain longer and youll also be lightening our support load. Both the manual
and the API have an online component.
The Bakery
http://bakery.cakephp.org
The CakePHP Bakery is a clearing house for all things regarding CakePHP. Check it out for tutorials, case
studies, and code examples. Once youre acquainted with CakePHP, log on and share your knowledge with
the community and gain instant fame and fortune.
The API
http://api.cakephp.org/
Straight to the point and straight from the core developers, the CakePHP API (Application Programming
Interface) is the most comprehensive documentation around for all the nitty gritty details of the internal
workings of the framework. Its a straight forward code reference, so bring your propeller hat.
Additional Reading
https://github.com/cakephp?tab=members
http://discourse.cakephp.org
http://stackoverflow.com/questions/tagged/cakephp/
http://cakephp-br.org
Danish
Danish CakePHP Slack Channel8
French
French CakePHP Community9
German
German CakePHP Slack Channel10
German CakePHP Facebook Group11
Iranian
Iranian CakePHP Community12
Dutch
Dutch CakePHP Slack Channel13
Japanese
Japanese CakePHP Slack Channel14
Japanese CakePHP Facebook Group15
Portuguese
Portuguese CakePHP Google Group16
8
9
10
11
12
13
14
15
16
https://cakesf.slack.com/messages/denmark/
http://cakephp-fr.org
https://cakesf.slack.com/messages/german/
https://www.facebook.com/groups/146324018754907/
http://cakephp.ir
https://cakesf.slack.com/messages/netherlands/
https://cakesf.slack.com/messages/japanese/
https://www.facebook.com/groups/304490963004377/
http://groups.google.com/group/cakephp-pt
Additional Reading
Spanish
Spanish CakePHP Slack Channel17
Spanish CakePHP IRC Channel
Spanish CakePHP Google Group18
CakePHP Conventions
We are big fans of convention over configuration. While it takes a bit of time to learn CakePHPs conventions, you save time in the long run. By following conventions, you get free functionality, and you liberate
yourself from the maintenance nightmare of tracking config files. Conventions also make for a very uniform
development experience, allowing other developers to jump in and help.
Controller Conventions
Controller class names are plural, CamelCased, and end in Controller. PeopleController and
LatestArticlesController are both examples of conventional controller names.
Public methods on Controllers are often exposed as actions accessible through a web browser. For example
the /articles/view maps to the view() method of the ArticlesController out of the box.
Protected or private methods cannot be accessed with routing.
URL Considerations for Controller Names
As youve just seen, single word controllers map to a simple lower case URL path. For example,
ApplesController (which would be defined in the file name ApplesController.php) is accessed from
http://example.com/apples.
Multiple word controllers can be any inflected form which equals the controller name so:
/redApples
/RedApples
/Red_apples
/red_apples
Will all resolve to the index of the RedApples controller. However, the convention is that your URLs are
lowercase and dashed using the DashedRoute class, therefore /red-apples/go-pick is the correct
form to access the RedApplesController::goPick() action.
When you create links using this->Html->link(), you can use the following conventions for the url
array:
17
18
https://cakesf.slack.com/messages/spanish/
http://groups.google.com/group/cakephp-esp
$this->Html->link('link-title', [
'prefix' => 'MyPrefix' // CamelCased
'plugin' => 'MyPlugin', // CamelCased
'controller' => 'ControllerName', // CamelCased
'action' => 'actionName' // camelBacked
]
For more information on CakePHP URLs and parameter handling, see Connecting Routes.
File and Class Name Conventions
In general, filenames match the class names, and follow the PSR-0 or PSR-4 standards for autoloading. The
following are some examples of class names and their filenames:
The Controller class KissesAndHugsController would be found in a file named KissesAndHugsController.php
The Component class MyHandyComponent would be found in a file named MyHandyComponent.php
The Table class OptionValuesTable would be found in a file named OptionValuesTable.php.
The Entity class OptionValue would be found in a file named OptionValue.php.
The Behavior class EspeciallyFunkableBehavior would be found in a file named EspeciallyFunkableBehavior.php
The View class SuperSimpleView would be found in a file named SuperSimpleView.php
The Helper class BestEverHelper would be found in a file named BestEverHelper.php
Each file would be located in the appropriate folder/namespace in your app folder.
Model and Database Conventions
Table class names are plural and CamelCased. People, BigPeople, and ReallyBigPeople are all examples of
conventional model names.
Table names corresponding to CakePHP models are plural and underscored. The underlying tables for the
above mentioned models would be people, big_people, and really_big_people, respectively.
The convention is to use English words for table and column names. If you use words in another language,
CakePHP might not be able to process the right inflections (from singular to plural and vice-versa). For
some reason, if you need to add your own language rules for some words, you can use the utility class
Cake\Utility\Inflector. Besides defining those custom inflection rules, this class also allows you
to check that CakePHP understands your custom syntax for plurals and singulars words. See the documentation about Inflector for more information.
Field names with two or more words are underscored: first_name.
Foreign keys in hasMany, belongsTo or hasOne relationships are recognized by default as the (singular)
name of the related table followed by _id. So if Bakers hasMany Cakes, the cakes table will refer to the
Additional Reading
bakers table via a baker_id foreign key. For a table like category_types whose name contains multiple words,
the foreign key would be category_type_id.
Join tables, used in BelongsToMany relationships between models, should be named after the model tables
they will join, arranged in alphabetical order (apples_zebras rather than zebras_apples).
Rather than using an auto-increment key as the primary key, you may also use char(36). CakePHP will then
use a unique 36 character UUID (Text::uuid) whenever you save a new record using the Table::save()
method.
View Conventions
View template files are named after the controller functions they display, in an underscored
form. The getReady() function of the PeopleController class will look for a view template in
src/Template/People/get_ready.ctp.
The basic pattern is src/Template/Controller/underscored_function_name.ctp.
By naming the pieces of your application using CakePHP conventions, you gain functionality without the
hassle and maintenance tethers of configuration. Heres a final example that ties the conventions together:
Database table: people
Table class: PeopleTable, found at src/Model/Table/PeopleTable.php
Entity class: Person, found at src/Model/Entity/Person.php
Controller class: PeopleController, found at src/Controller/PeopleController.php
View template, found at src/Template/People/index.ctp
Using these conventions, CakePHP knows that a request to http://example.com/people/ maps to a call on
the index() function of the PeopleController, where the Person model is automatically available (and
automatically tied to the people table in the database), and renders to a file. None of these relationships
have been configured by any means other than by creating classes and files that youd need to create anyway.
Now that youve been introduced to CakePHPs fundamentals, you might try a run through the Bookmarker
Tutorial to see how things fit together.
tmp
vendor
webroot
.htaccess
composer.json
index.php
README.md
Youll notice a few top level folders:
The bin folder holds the Cake console executables.
The config folder holds the (few) Configuration files CakePHP uses. Database connection details,
bootstrapping, core configuration files and more should be stored here.
The plugins folder is where the Plugins your application uses are stored.
The logs folder normally contains your log files, depending on your log configuration.
The src folder will be where you work your magic: its where your applications files will be placed.
The tests folder will be where you put the test cases for your application.
The tmp folder is where CakePHP stores temporary data. The actual data it stores depends on how you
have CakePHP configured, but this folder is usually used to store model descriptions and sometimes
session information.
The vendor folder is where CakePHP and other application dependencies will be installed. Make a
personal commitment not to edit files in this folder. We cant help you if youve modified the core.
The webroot directory is the public document root of your application. It contains all the files you
want to be publically reachable.
Make sure that the tmp and logs folders exist and are writable, otherwise the performance of your
application will be severely impacted. In debug mode, CakePHP will warn you, if it is not the case.
The src Folder
CakePHPs src folder is where you will do most of your application development. Lets look a little closer
at the folders inside src.
Console Contains the console commands and console tasks for your application. For more information see
Shells, Tasks & Console Tools.
Controller Contains your applications controllers and their components.
Locale Stores string files for internationalization.
Model Contains your applications tables, entities and behaviors.
View Presentational classes are placed here: cells, helpers, and template files.
Additional Reading
11
Template Presentational files are placed here: elements, error pages, layouts, and view template files.
12
CHAPTER 2
The best way to experience and learn CakePHP is to sit down and build something. To start off well build
a simple bookmarking application.
Bookmarker Tutorial
This tutorial will walk you through the creation of a simple bookmarking application (bookmarker). To start
with, well be installing CakePHP, creating our database, and using the tools CakePHP provides to get our
application up fast.
Heres what youll need:
1. A database server. Were going to be using MySQL server in this tutorial. Youll need to know enough
about SQL in order to create a database: CakePHP will be taking the reins from there. Since were
using MySQL, also make sure that you have pdo_mysql enabled in PHP.
2. Basic PHP knowledge.
Before starting you should make sure that you have got an up to date PHP version:
php -v
You should at least have got installed PHP 5.5.9 (CLI) or higher. Your webservers PHP version must also
be of 5.5.9 or higher, and should best be the same version your command line interface (CLI) PHP version
is of. If youd like to see the completed application, checkout cakephp/bookmarker19 . Lets get started!
Getting CakePHP
The easiest way to install CakePHP is to use Composer. Composer is a simple way of installing CakePHP
from your terminal or command line prompt. First, youll need to download and install Composer if you
havent done so already. If you have cURL installed, its as easy as running the following:
19
https://github.com/cakephp/bookmarker-tutorial
13
If you downloaded and ran the Composer Windows Installer21 , then type the following line in your terminal
from your installation directory (ie. C:\wamp\www\dev\cakephp3):
composer self-update && composer create-project --prefer-dist cakephp/app
bookmarker
The advantage to using Composer is that it will automatically complete some important set up tasks, such
as setting the correct file permissions and creating your config/app.php file for you.
There are other ways to install CakePHP. If you cannot or dont want to use Composer, check out the
Installation section.
Regardless of how you downloaded and installed CakePHP, once your set up is completed, your directory
setup should look something like the following:
/bookmarker
/bin
/config
/logs
/plugins
/src
/tests
/tmp
/vendor
/webroot
.editorconfig
.gitignore
.htaccess
.travis.yml
composer.json
index.php
phpunit.xml.dist
README.md
Now might be a good time to learn a bit about how CakePHPs directory structure works: check out the
CakePHP Folder Structure section.
20
21
14
https://getcomposer.org/download/
https://getcomposer.org/Composer-Setup.exe
Note: For Windows, the command needs to be bin\cake server (note the backslash).
This will start PHPs built-in webserver on port 8765. Open up http://localhost:8765 in your web browser to
see the welcome page. All the bullet points should be checkmarks other than CakePHP being able to connect
to your database. If not, you may need to install additional PHP extensions, or set directory permissions.
Bookmarker Tutorial
15
You may have noticed that the bookmarks_tags table used a composite primary key. CakePHP supports
composite primary keys almost everywhere, making it easier to build multi-tenanted applications.
The table and column names we used were not arbitrary. By using CakePHPs naming conventions, we
can leverage CakePHP better and avoid having to configure the framework. CakePHP is flexible enough
to accommodate even inconsistent legacy database schemas, but adhering to the conventions will save you
time.
Database Configuration
Next, lets tell CakePHP where our database is and how to connect to it. For many, this will be the first and
last time you will need to configure anything.
The configuration should be pretty straightforward:
just replace the values in the
Datasources.default array in the config/app.php file with those that apply to your setup. A
sample completed configuration array might look something like the following:
return [
// More configuration above.
'Datasources' => [
'default' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Mysql',
'persistent' => false,
'host' => 'localhost',
'username' => 'cakephp',
'password' => 'AngelF00dC4k3~',
'database' => 'cake_bookmarks',
'encoding' => 'utf8',
'timezone' => 'UTC',
'cacheMetadata' => true,
],
],
// More configuration below.
];
Once youve saved your config/app.php file, you should see that CakePHP is able to connect to the
database section have a checkmark.
Note: A copy of CakePHPs default configuration file is found in config/app.default.php.
16
This will generate the controllers, models, views, their corresponding test cases, and fixtures for
our users, bookmarks and tags resources. If youve stopped your server, restart it and go to
http://localhost:8765/bookmarks.
You should see a basic but functional application providing data access to your applications database tables.
Once youre at the list of bookmarks, add a few users, bookmarks, and tags.
Note: If you see a Not Found (404) page, confirm that the Apache mod_rewrite module is loaded.
Bookmarker Tutorial
17
Now update one of the users you created earlier, if you change their password, you should see a hashed
password instead of the original value on the list or view pages. CakePHP hashes passwords with bcrypt22
by default. You can also use sha1 or md5 if youre working with an existing database.
The above defines a new route which connects the /bookmarks/tagged/ path, to
BookmarksController::tags(). By defining routes, you can isolate how your URLs look,
22
18
http://codahale.com/how-to-safely-store-a-password/
To access other parts of the request data, consult the Request section.
Creating the Finder Method
In CakePHP we like to keep our controller actions slim, and put most of our applications logic in the models.
If you were to visit the /bookmarks/tagged URL now you would see an error that the findTagged()
method has not been implemented yet, so lets do that. In src/Model/Table/BookmarksTable.php add the
following:
// The $query argument is a query builder instance.
// The $options array will contain the 'tags' option we passed
// to find('tagged') in our controller action.
public function findTagged(Query $query, array $options)
{
return $this->find()
->distinct(['Bookmarks.id'])
->matching('Tags', function ($q) use ($options) {
if (empty($options['tags'])) {
return $q->where(['Tags.title IS' => null]);
}
return $q->where(['Tags.title IN' => $options['tags']]);
});
}
We just implemented a custom finder method. This is a very powerful concept in CakePHP that allows you to
package up re-usable queries. Finder methods always get a Query Builder object and an array of options as
parameters. Finders can manipulate the query and add any required conditions or criteria. When complete,
finder methods must return a modified query object. In our finder weve leveraged the distinct()
and matching() methods which allow us to find distinct bookmarks that have a matching tag. The
Bookmarker Tutorial
19
matching() method accepts an anonymous function23 that receives a query builder as its argument. Inside
the callback we use the query builder to define conditions that will filter bookmarks that have specific tags.
Creating the View
Now if you visit the /bookmarks/tagged URL, CakePHP will show an error letting you know that
you have not made a view file. Next, lets build the view file for our tags() action. In
src/Template/Bookmarks/tags.ctp put the following content:
<h1>
Bookmarks tagged with
<?= $this->Text->toList($tags) ?>
</h1>
<section>
<?php foreach ($bookmarks as $bookmark): ?>
<article>
<!-- Use the HtmlHelper to create a link -->
<h4><?= $this->Html->link($bookmark->title, $bookmark->url) ?></h4>
<small><?= h($bookmark->url) ?></small>
<!-- Use the TextHelper to format text -->
<?= $this->Text->autoParagraph($bookmark->description) ?>
</article>
<?php endforeach; ?>
</section>
In the above code we use the Html and Text helpers to assist in generating our view output. We also use
the h shortcut function to HTML encode output. You should remember to always use h() when outputting
user data to prevent HTML injection issues.
The tags.ctp file we just created follows the CakePHP conventions for view template files. The convention
is to have the template use the lower case and underscored version of the controller action name.
You may notice that we were able to use the $tags and $bookmarks variables in our view. When we use
the set() method in our controller, we set specific variables to be sent to the view. The view will make all
passed variables available in the templates as local variables.
You should now be able to visit the /bookmarks/tagged/funny URL and see all the bookmarks tagged with
funny.
So far, weve created a basic application to manage bookmarks, tags and users. However, everyone can see
everyone elses tags. In the next chapter, well implement authentication and restrict the visible bookmarks
to only those that belong to the current user.
Now continue to Bookmarker Tutorial Part 2 to continue building your application or dive into the documentation to learn more about what CakePHP can do for you.
23
20
http://php.net/manual/en/functions.anonymous.php
Adding Login
In CakePHP, authentication is handled by Components. Components can be thought of as ways to create
reusable chunks of controller code related to a specific feature or concept. Components can also hook into
the controllers event life-cycle and interact with your application that way. To get started, well add the
AuthComponent to our application. Well pretty much want every method to require authentication, so well
add AuthComponent in our AppController:
// In src/Controller/AppController.php
namespace App\Controller;
use Cake\Controller\Controller;
class AppController extends Controller
{
public function initialize()
{
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
]
]);
// Allow the display action so our pages controller
// continues to work.
$this->Auth->allow(['display']);
}
}
Weve just told CakePHP that we want to load the Flash and Auth components. In addition, weve
customized the configuration of AuthComponent, as our users table uses email as the username. Now, if
you go to any URL youll be kicked to /users/login, which will show an error page as we have not written
that code yet. So lets create the login action:
21
// In src/Controller/UsersController.php
public function login()
{
if ($this->request->is('post')) {
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
return $this->redirect($this->Auth->redirectUrl());
}
$this->Flash->error('Your username or password is incorrect.');
}
}
Now that we have a simple login form, we should be able to log in with one of the users that has a hashed
password.
Note: If none of your users have hashed passwords, comment the loadComponent('Auth') line.
Then go and edit the user, saving a new password for them.
You should now be able to log in. If not, make sure you are using a user that has a hashed password.
Adding Logout
Now that people can log in, youll probably want to provide a way to log out as well. Again, in the
UsersController, add the following code:
public function initialize()
{
parent::initialize();
$this->Auth->allow(['logout']);
}
public function logout()
{
$this->Flash->success('You are now logged out.');
return $this->redirect($this->Auth->logout());
}
This code whitelists the logout action as a public action, and implements the logout method. Now you
can visit /users/logout to log out. You should then be sent to the login page.
22
Enabling Registrations
If you arent logged in and you try to visit /users/add you will be kicked to the login page. We should
fix that as we want to allow people to sign up for our application. In the UsersController add the
following:
public function initialize()
{
parent::initialize();
// Add logout to the allowed actions list.
$this->Auth->allow(['logout', 'add']);
}
The above tells AuthComponent that the add() action does not require authentication or authorization.
You may want to take the time to clean up the Users/add.ctp and remove the misleading links, or continue
on to the next section. We wont be building out user editing, viewing or listing in this tutorial so they will
not work as AuthComponent will deny you access to those controller actions.
Also, add the following to the configuration for Auth in your AppController:
'authorize' => 'Controller',
23
Well default to denying access, and incrementally grant access where it makes sense. First, well add the
authorization logic for bookmarks. In your BookmarksController add the following:
public function isAuthorized($user)
{
$action = $this->request->params['action'];
// The add and index actions are always allowed.
if (in_array($action, ['index', 'add', 'tags'])) {
return true;
}
// All other actions require an id.
if (empty($this->request->params['pass'][0])) {
return false;
}
// Check that the bookmark belongs to the current user.
$id = $this->request->params['pass'][0];
$bookmark = $this->Bookmarks->get($id);
if ($bookmark->user_id == $user['id']) {
return true;
}
return parent::isAuthorized($user);
}
Now if you try to view, edit or delete a bookmark that does not belong to you, you should be redirected back
to the page you came from. However, there is no error message being displayed, so lets rectify that next:
// In src/Template/Layout/default.ctp
// Under the existing flash message.
<?= $this->Flash->render('auth') ?>
24
By setting the entity property with the session data, we remove any possibility of the user modifying which
user a bookmark is for. Well do the same for the edit form and action. Your edit() action from
src/Controller/BookmarksController.php should look like:
public function edit($id = null)
{
$bookmark = $this->Bookmarks->get($id, [
'contain' => ['Tags']
]);
if ($this->request->is(['patch', 'post', 'put'])) {
$bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->
data);
$bookmark->user_id = $this->Auth->user('id');
if ($this->Bookmarks->save($bookmark)) {
$this->Flash->success('The bookmark has been saved.');
return $this->redirect(['action' => 'index']);
}
$this->Flash->error('The bookmark could not be saved. Please, try
again.');
}
$tags = $this->Bookmarks->Tags->find('list');
$this->set(compact('bookmark', 'tags'));
$this->set('_serialize', ['bookmark']);
}
25
List View
Now, we only need to show bookmarks for the currently logged in user. We can do that by updating the call
to paginate(). Make your index() action from src/Controller/BookmarksController.php look like:
public function index()
{
$this->paginate = [
'conditions' => [
'Bookmarks.user_id' => $this->Auth->user('id'),
]
];
$this->set('bookmarks', $this->paginate($this->Bookmarks));
$this->set('_serialize', ['bookmarks']);
}
We should also update the tags() action and the related finder method, but well leave that as an exercise
you can complete on your own.
This will let us access the $bookmark->tag_string computed property. Well use this property in
inputs later on. Remember to add the tag_string property to the _accessible list in your entity, as
well want to save it later on.
26
27
unset($newTags[$index]);
}
}
// Add existing tags.
foreach ($query as $tag) {
$out[] = $tag;
}
// Add new tags.
foreach ($newTags as $tag) {
$out[] = $this->Tags->newEntity(['title' => $tag]);
}
return $out;
}
While this code is a bit more complicated than what weve done so far, it helps to showcase how powerful
the ORM in CakePHP is. You can manipulate query results using the Collections methods, and handle
scenarios where you are creating entities on the fly with ease.
Wrapping Up
Weve expanded our bookmarking application to handle authentication and basic authorization/access control scenarios. Weve also added some nice UX improvements by leveraging the FormHelper and ORM
capabilities.
Thanks for taking the time to explore CakePHP. Next, you can complete the Blog Tutorial, learn more about
the Database Access & ORM, or you can peruse the /topics.
28
CHAPTER 3
This page summarizes the changes from CakePHP 2.x that will assist in migrating a project to 3.0, as well
as a reference to get up to date with the changes made to the core since the CakePHP 2.x branch. Be sure to
read the other pages in this guide for all the new features and API changes.
Requirements
CakePHP 3.x supports PHP Version 5.4.16 and above.
CakePHP 3.x requires the mbstring extension.
CakePHP 3.x requires the intl extension.
Warning: CakePHP 3.0 will not work if you do not meet the above requirements.
Upgrade Tool
While this document covers all the breaking changes and improvements made in CakePHP 3.0, weve also
created a console application to help you complete some of the time consuming mechanical changes. You
can get the upgrade tool from github24 .
https://github.com/cakephp/upgrade
http://www.php-fig.org/psr/psr-4/
https://github.com/cakephp/app
29
Namespaces
All of CakePHPs core classes are now namespaced and follow PSR-4 autoloading specifications. For
example src/Cache/Cache.php is namespaced as Cake\Cache\Cache. Global constants and helper
methods like __() and debug() are not namespaced for convenience sake.
Removed Constants
The following deprecated constants have been removed:
IMAGES
CSS
JS
IMAGES_URL
JS_URL
CSS_URL
DEFAULT_LANGUAGE
Configuration
Configuration in CakePHP 3.0 is significantly different than in previous versions. You should read the
Configuration documentation for how configuration is done in 3.0.
You can no longer use App::build() to configure additional class paths. Instead you should map additional paths using your applications autoloader. See the section on Additional Class Paths for more
information.
Three new configure variables provide the path configuration for plugins, views and locale files. You can
add multiple paths to App.paths.templates, App.paths.plugins, App.paths.locales to
configure multiple paths for templates, plugins and locale files respectively.
The config key www_root has been changed to wwwRoot for consistency. Please adjust your app.php
config file as well as any usage of Configure::read('App.wwwRoot').
27
30
http://getcomposer.org
New ORM
CakePHP 3.0 features a new ORM that has been re-built from the ground up. The new ORM is significantly different and incompatible with the previous one. Upgrading to the new ORM will require extensive
changes in any application that is being upgraded. See the new Database Access & ORM documentation for
information on how to use the new ORM.
Basics
LogError() was removed, it provided no benefit and is rarely/never used.
The following global functions have been removed: config(), cache(), clearCache(),
convertSlashes(), am(), fileExistsInPath(), sortByKey().
Debugging
Configure::write('debug',$bool) does not support 0/1/2 anymore. A simple boolean is
used instead to switch debug mode on or off.
Object settings/configuration
Objects used in CakePHP now have a consistent instance-configuration storage/retrieval system. Code
which previously accessed for example: $object->settings should instead be updated to use
$object->config().
Cache
Memcache engine has been removed, use Cake\Cache\Cache\Engine\Memcached instead.
Cache engines are now lazy loaded upon first use.
Cake\Cache\Cache::engine() has been added.
Cake\Cache\Cache::enabled() has been added. This replaced the Cache.disable configure option.
Cake\Cache\Cache::enable() has been added.
Cake\Cache\Cache::disable() has been added.
Cache configurations are now immutable. If you need to change configuration you must first drop the
configuration and then re-create it. This prevents synchronization issues with configuration options.
Cache::set() has been removed. It is recommended that you create multiple cache configurations
to replace runtime configuration tweaks previously possible with Cache::set().
New ORM
31
and
All Cake\Cache\Cache\CacheEngine methods now honor/are responsible for handling the configured key prefix. The Cake\Cache\CacheEngine::write() no longer permits setting the duration
on write - the duration is taken from the cache engines runtime config. Calling a cache method with an
empty key will now throw an InvalidArgumentException, instead of returning false.
Core
App
App::pluginPath() has been removed. Use CakePlugin::path() instead.
App::build() has been removed.
App::location() has been removed.
App::paths() has been removed.
App::load() has been removed.
App::objects() has been removed.
App::RESET has been removed.
App::APPEND has been removed.
App::PREPEND has been removed.
App::REGISTER has been removed.
Plugin
Cake\Core\Plugin::load() does not setup an autoloader unless you set the autoload option to true.
When loading plugins you can no longer provide a callable.
When loading plugins you can no longer provide an array of config files to load.
Configure
Cake\Configure\PhpReader renamed to Cake\Core\Configure\EnginePhpConfig
Cake\Configure\IniReader renamed to Cake\Core\Configure\EngineIniConfig
Cake\Configure\ConfigReaderInterface
Cake\Core\Configure\ConfigEngineInterface
renamed
to
Cake\Core\Configure::load() now expects the file name without extension suffix as this
can be derived from the engine. E.g. using PhpConfig use app to load app.php.
Setting
a
$config
variable
in
PHP
config
file
is
deprecated.
Cake\Core\Configure\EnginePhpConfig now expects the config file to return an
array.
A new config engine Cake\Core\Configure\EngineJsonConfig has been added.
Object
The Object class has been removed. It formerly contained a grab bag of methods that were
used in various places across the framework. The most useful of these methods have been extracted into traits. You can use the Cake\Log\LogTrait to access the log() method. The
Cake\Routing\RequestActionTrait provides requestAction().
Console
The cake executable has been moved from the app/Console directory to the bin directory within the
application skeleton. You can now invoke CakePHPs console with bin/cake.
TaskCollection Replaced
This class has been renamed to Cake\Console\TaskRegistry. See the section on Registry Objects for more information on the features provided by the new class. You can use the cake upgrade
rename_collections to assist in upgrading your code. Tasks no longer have access to callbacks, as
there were never any callbacks to use.
Shell
Shell::__construct() has
Cake\Console\ConsoleIo.
changed.
It
now
takes
an
instance
of
ConsoleOptionParser
ConsoleOptionParser::merge() has been added to merge parsers.
Console
33
ConsoleInputArgument
ConsoleInputArgument::isEqualTo() has been added to compare two arguments.
Shell / Task
Shells and Tasks have been moved from Console/Command and Console/Command/Task to Shell
and Shell/Task.
ApiShell Removed
The ApiShell was removed as it didnt provide any benefit over the file source itself and the online documentation/API28 .
SchemaShell Removed
The SchemaShell was removed as it was never a complete database migration implementation and better
tools such as Phinx29 have emerged. It has been replaced by the CakePHP Migrations Plugin30 which acts
as a wrapper between CakePHP and Phinx31 .
ExtractTask
bin/cake i18n extract no longer includes untranslated validation messages. If you want
translated validation messages you should wrap those messages in __() calls like any other content.
BakeShell / TemplateTask
Bake is no longer part of the core source and is superseded by CakePHP Bake Plugin32
Bake templates have been moved under src/Template/Bake.
The syntax of Bake templates now uses erb-style tags (<% %>) to denote templating logic, allowing
php code to be treated as plain text.
The bake view command has been renamed bake template.
28
29
30
31
32
34
http://api.cakephp.org/
https://phinx.org/
https://github.com/cakephp/migrations
https://phinx.org/
https://github.com/cakephp/bake
Event
The getEventManager() method, was removed on all objects that had it. An eventManager()
method is now provided by the EventManagerTrait. The EventManagerTrait contains the logic
of instantiating and keeping a reference to a local event manager.
The Event subsystem has had a number of optional features removed. When dispatching events you can no
longer use the following options:
passParams This option is now enabled always implicitly. You cannot turn it off.
break This option has been removed. You must now stop events.
breakOn This option has been removed. You must now stop events.
Log
Log configurations are now immutable. If you need to change configuration you must first drop the
configuration and then re-create it. This prevents synchronization issues with configuration options.
Log engines are now lazily loaded upon the first write to the logs.
Cake\Log\Log::engine() has been added.
The following methods have been removed from Cake\Log\Log :: defaultLevels(),
enabled(), enable(), disable().
You can no longer create custom levels using Log::levels().
When configuring loggers you should use 'levels' instead of 'types'.
You can no longer specify custom log levels. You must use the default set of log levels. You should use
logging scopes to create custom log files or specific handling for different sections of your application.
Using a non-standard log level will now throw an exception.
Cake\Log\LogTrait was added. You can use this trait in your classes to add the log() method.
The logging scope passed to Cake\Log\Log::write() is now forwarded to the log engines
write() method in order to provide better context to the engines.
Log engines are now required to implement Psr\Log\LogInterface instead of Cakes own
LogInterface. In general, if you extended Cake\Log\Engine\BaseEngine you just need
to rename the write() method to log().
Cake\Log\Engine\FileLog now writes files in ROOT/logs instead of ROOT/tmp/logs.
Event
35
Routing
Named Parameters
Named parameters were removed in 3.0. Named parameters were added in 1.2.0 as a pretty version of
query string parameters. While the visual benefit is arguable, the problems named parameters created are
not.
Named parameters required special handling in CakePHP as well as any PHP or JavaScript library that
needed to interact with them, as named parameters are not implemented or understood by any library except
CakePHP. The additional complexity and code required to support named parameters did not justify their
existence, and they have been removed. In their place you should use standard query string parameters or
passed arguments. By default Router will treat any additional parameters to Router::url() as query
string arguments.
Since many applications will still need to parse incoming URLs containing named parameters.
Cake\Routing\Router::parseNamedParams() has been added to allow backwards compatibility with existing URLs.
RequestActionTrait
Cake\Routing\RequestActionTrait::requestAction() has had some of the extra options changed:
options[url] is now options[query].
options[data] is now options[post].
Named parameters are no longer supported.
Router
Named parameters have been removed, see above for more information.
The full_base option has been replaced with the _full option.
The ext option has been replaced with the _ext option.
_scheme, _port, _host, _base, _full, _ext options added.
String URLs are no longer modified by adding the plugin/controller/prefix names.
The default fallback route handling was removed. If no routes match a parameter set / will be returned.
Route classes are responsible for all URL generation including query string parameters. This makes
routes far more powerful and flexible.
Persistent
parameters
were
removed.
They
were
replaced
with
Cake\Routing\Router::urlFilter() which allows a more flexible way to mutate
URLs being reverse routed.
36
Router::parseExtensions()
has
been
removed.
Use
Cake\Routing\Router::extensions() instead. This method must be called before
routes are connected. It wont modify existing routes.
Router::setExtensions() has been removed. Use Cake\Routing\Router::extensions()
instead.
Router::resourceMap() has been removed.
The [method] option has been renamed to _method.
The ability to match arbitrary headers with [] style parameters has been removed. If you need to
parse/match on arbitrary conditions consider using custom route classes.
Router::promote() has been removed.
Router::parse() will now raise an exception when a URL cannot be handled by any route.
Router::url() will now raise an exception when no route matches a set of parameters.
Routing scopes have been introduced. Routing scopes allow you to keep your routes file DRY and
give Router hints on how to optimize parsing & reverse routing URLs.
Route
CakeRoute was re-named to Route.
The signature of match() has changed to match($url,$context = [])
Cake\Routing\Route::match() for information on the new signature.
See
FilterAssetFilter
Plugin & theme assets handled by the AssetFilter are no longer read via include instead they are
treated as plain text files. This fixes a number of issues with JavaScript libraries like TinyMCE and
environments with short_tags enabled.
Support for the Asset.filter configuration and hooks were removed. This feature should be
replaced with a plugin or dispatcher filter.
Routing
37
Network
Request
CakeRequest has been renamed to Cake\Network\Request.
Cake\Network\Request::port() was added.
Cake\Network\Request::scheme() was added.
Cake\Network\Request::cookie() was added.
Cake\Network\Request::$trustProxy was added. This makes it easier to put CakePHP
applications behind load balancers.
Cake\Network\Request::$data is no longer merged with the prefixed data key, as that prefix
has been removed.
Cake\Network\Request::env() was added.
Cake\Network\Request::acceptLanguage() was changed from static method to nonstatic.
Request detector for mobile has been removed from the core. Instead the app template adds detectors for mobile and tablet using MobileDetect lib.
The method onlyAllow() has been renamed to allowMethod() and no longer accepts var
args. All method names need to be passed as first argument, either as string or array of strings.
Response
The mapping of mimetype text/plain to extension csv has been removed. As a consequence Cake\Controller\Component\RequestHandlerComponent doesnt set extension to csv if Accept header contains mimetype text/plain which was a common annoyance
when receiving a jQuery XHR request.
Sessions
The session class is no longer static, instead the session can be accessed through the request object. See the
Sessions documentation for using the session object.
Cake\Network\Session and related session classes have been moved under the
Cake\Network namespace.
SessionHandlerInterface has been removed in favor of the one provided by PHP itself.
The property Session::$requestCountdown has been removed.
The session checkAgent feature has been removed. It caused a number of bugs when chrome frame,
and flash player are involved.
The conventional sessions database table name is now sessions instead of cake_sessions.
38
The session cookie timeout is automatically updated in tandem with the timeout in the session data.
The path for session cookie now defaults to apps base path instead of /. A new configuration
variable Session.cookiePath has been added to customize the cookie path.
A new convenience method Cake\Network\Session::consume() has been added to allow
reading and deleting session data in a single step.
The default value of Cake\Network\Session::clear()s argument $renew has been
changed from true to false.
Network\Http
HttpSocket is now Cake\Network\Http\Client.
HttpClient has been re-written from the ground up. It has a simpler/easier to use API, support for
new authentication systems like OAuth, and file uploads. It uses PHPs stream APIs so there is no
requirement for cURL. See the Http Client documentation for more information.
Network\Email
Cake\Network\Email\Email::config() is now used to define configuration profiles. This
replaces the EmailConfig classes in previous versions.
Cake\Network\Email\Email::profile() replaces config() as the way to modify per
instance configuration options.
Cake\Network\Email\Email::drop() has been added to allow the removal of email configuration.
Cake\Network\Email\Email::configTransport() has been added to allow the definition of transport configurations. This change removes transport options from delivery profiles and
allows you to re-use transports across email profiles.
Cake\Network\Email\Email::dropTransport() has been added to allow the removal of
transport configuration.
Controller
Controller
The $helpers, $components properties are now merged with all parent classes not just
AppController and the plugin AppController. The properties are merged differently now as well.
Instead of all settings in all classes being merged together, the configuration defined in the child class
will be used. This means that if you have some configuration defined in your AppController, and
some configuration defined in a subclass, only the configuration in the subclass will be used.
Network\Http
39
removed,
use
Controller::flash() has been removed. This method was rarely used in real applications and
served no purpose anymore.
Controller::validate() and Controller::validationErrors() have been removed. They were left over methods from the 1.x days where the concerns of models + controllers
were far more intertwined.
Controller::loadModel() now loads table objects.
The Controller::$scaffold property has been removed. Dynamic scaffolding has been removed from CakePHP core. An improved scaffolding plugin, named CRUD, can be found here:
https://github.com/FriendsOfCake/crud
The Controller::$ext property has been removed. You now have to extend and override the
View::$_ext property if you want to use a non-default view file extension.
The Controller::$methods property has been removed.
You should now use
Controller::isAction() to determine whether or not a method name is an action. This
change was made to allow easier customization of what is and is not counted as an action.
The Controller::$Components property has been removed and replaced with
_components.
If you need to load components at runtime you should use
$this->loadComponent() on your controller.
The signature of Cake\Controller\Controller::redirect() has been changed to
Controller::redirect(string|array $url,int $status = null). The 3rd argument $exit has been dropped. The method can no longer send response and exit script, instead it
returns a Response instance with appropriate headers set.
The base, webroot, here, data, action, and params magic properties have been removed.
You should access all of these properties on $this->request instead.
Underscore prefixed controller methods like _someMethod() are no longer treated as private methods. Use proper visibility keywords instead. Only public methods can be used as controller actions.
Scaffold Removed
The dynamic scaffolding in CakePHP has been removed from CakePHP core. It was infrequently used,
and never intended for production use. An improved scaffolding plugin, named CRUD, can be found here:
https://github.com/FriendsOfCake/crud
ComponentCollection Replaced
This class has been renamed to Cake\Controller\ComponentRegistry. See the section on Registry Objects for more information on the features provided by the new class. You can use the cake
upgrade rename_collections to assist in upgrading your code.
40
Component
The _Collection property is now _registry.
Cake\Controller\ComponentRegistry now.
It
contains
an
instance
of
All components should now use the config() method to get/set configuration.
Default configuration for components should be defined in the $_defaultConfig property. This
property is automatically merged with any configuration provided to the constructor.
Configuration options are no longer set as public properties.
The Component::initialize() method is no longer an event listener. Instead, it is
a post-constructor hook like Table::initialize() and Controller::initialize().
The new Component::beforeFilter() method is bound to the same event that
Component::initialize() used to be. The initialize method should have the following signature initialize(array $config).
Controller\Components
CookieComponent
Uses Cake\Network\Request::cookie() to read cookie data, this eases testing, and allows
for ControllerTestCase to set cookies.
Cookies encrypted in previous versions of CakePHP using the cipher() method are now unreadable because Security::cipher() has been removed. You will need to re-encrypt cookies
with the rijndael() or aes() method before upgrading.
CookieComponent::type() has been removed and replaced with configuration data accessed
through config().
write() no longer takes encryption or expires parameters. Both of these are now managed
through config data. See Cookie for more information.
The path for cookies now defaults to apps base path instead of /.
AuthComponent
Default is now the default password hasher used by authentication classes. It uses exclusively
the bcrypt hashing algorithm. If you want to continue using SHA1 hashing used in 2.x use
'passwordHasher' => 'Weak' in your authenticator configuration.
A new FallbackPasswordHasher was added to help users migrate old passwords from one
algorithm to another. Check AuthComponents documentation for more info.
BlowfishAuthenticate class has been removed. Just use FormAuthenticate
BlowfishPasswordHasher class has been removed. Use DefaultPasswordHasher instead.
Controller\Components
41
RequestHandlerComponent
The following methods have been removed from RequestHandler component:: isAjax(),
isFlash(), isSSL(), isPut(), isPost(), isGet(), isDelete().
Use the
Cake\Network\Request::is() method instead with relevant argument.
RequestHandler::setContent() was removed, use Cake\Network\Response::type()
instead.
RequestHandler::getReferer() was removed, use Cake\Network\Request::referer()
instead.
RequestHandler::getClientIP() was removed, use Cake\Network\Request::clientIp()
instead.
RequestHandler::getAjaxVersion() was removed.
RequestHandler::mapType() was removed, use Cake\Network\Response::mapType()
instead.
Configuration options are no longer set as public properties.
SecurityComponent
The following methods and their related properties have been removed from Security component: requirePost(), requireGet(), requirePut(), requireDelete(). Use the
Cake\Network\Request::allowMethod() instead.
42
SecurityComponent::$disabledFields()
SecurityComponent::$unlockedFields().
has
been
removed,
use
The CSRF related features in SecurityComponent have been extracted and moved into a separate
CsrfComponent. This allows you to use CSRF protection without having to use form tampering
prevention.
Configuration options are no longer set as public properties.
The methods requireAuth() and requireSecure() no longer accept var args. All method
names need to be passed as first argument, either as string or array of strings.
SessionComponent
SessionComponent::setFlash() is deprecated. You should use Flash instead.
Error
Custom ExceptionRenderers are now expected to either return a Cake\Network\Response object or
string when rendering errors. This means that any methods handling specific exceptions must return a
response or string value.
Model
The Model layer in 2.x has been entirely re-written and replaced. You should review the New ORM Upgrade
Guide for information on how to use the new ORM.
The Model class has been removed.
The BehaviorCollection class has been removed.
The DboSource class has been removed.
The Datasource class has been removed.
The various datasource classes have been removed.
ConnectionManager
ConnectionManager has been moved to the Cake\Datasource namespace.
ConnectionManager has had the following methods removed:
sourceList
getSourceName
loadDataSource
enumConnectionObjects
Model
43
removed.
It
can
be
replaced
by
Behaviors
Underscore prefixed behavior methods like _someMethod() are no longer treated as private methods. Use proper visibility keywords instead.
TreeBehavior
The TreeBehavior was completely re-written to use the new ORM. Although it works the same as in 2.x, a
few methods were renamed or removed:
TreeBehavior::children() is now a custom finder find('children').
TreeBehavior::generateTreeList() is now a custom finder find('treeList').
TreeBehavior::getParentNode() was removed.
TreeBehavior::getPath() is now a custom finder find('path').
TreeBehavior::reorder() was removed.
TreeBehavior::verify() was removed.
TestSuite
TestCase
_normalizePath() has been added to allow path comparison tests to run across all operation
systems regarding their DS settings (\ in Windows vs / in UNIX, for example).
The following assertion methods have been removed as they have long been deprecated and replaced by
their new PHPUnit counterpart:
assertEqual() in favor of assertEquals()
assertNotEqual() in favor of assertNotEquals()
assertIdentical() in favor of assertSame()
assertNotIdentical() in favor of assertNotSame()
44
View
Themes are now Basic Plugins
Having themes and plugins as ways to create modular application components has proven to be limited, and
confusing. In CakePHP 3.0, themes no longer reside inside the application. Instead they are standalone
plugins. This solves a few problems with themes:
You could not put themes in plugins.
Themes could not provide helpers, or custom view classes.
Both these issues are solved by converting themes into plugins.
View
45
HelperCollection Replaced
This class has been renamed to Cake\View\HelperRegistry. See the section on Registry Objects for more information on the features provided by the new class. You can use the cake upgrade
rename_collections to assist in upgrading your code.
View Class
The
plugin
key
has
been
Cake\View\View::element().
SomePlugin.element_name instead.
removed
from
$options
argument
Specify
the
element
name
of
as
ViewBlock
ViewBlock::append() has been removed, use Cake\ViewViewBlock::concat() instead. However, View::append() still exists.
JsonView
By default JSON data will have HTML entities encoded now. This prevents possible XSS issues when
JSON view content is embedded in HTML files.
Cake\View\JsonView now supports the _jsonOptions view variable. This allows you to
configure the bit-mask options used when generating JSON.
46
XmlView
Cake\View\XmlView now supports the _xmlOptions view variable. This allows you to configure the options used when generating XML.
View\Helper
The $settings property is now called $_config and should be accessed through the config()
method.
Configuration options are no longer set as public properties.
Helper::clean() was removed. It was never robust enough to fully prevent XSS. instead you
should escape content with h or use a dedicated library like htmlPurifier.
Helper::output() was removed. This method was deprecated in 2.x.
Methods
Helper::webroot(),
Helper::url(),
Helper::assetUrl(),
Helper::assetTimestamp() have been moved to new Cake\View\Helper\UrlHelper
helper. Helper::url() is now available as Cake\View\Helper\UrlHelper::build().
Magic accessors to deprecated properties have been removed. The following properties now need to
be accessed from the request object:
base
here
webroot
data
action
params
Helper
Helper has had the following methods removed:
Helper::setEntity()
Helper::entity()
Helper::model()
Helper::field()
Helper::value()
Helper::_name()
Helper::_initInputField()
Helper::_selectedArray()
View\Helper
47
These methods were part used only by FormHelper, and part of the persistent field features that have proven
to be problematic over time. FormHelper no longer relies on these methods and the complexity they provide
is not necessary anymore.
The following methods have been removed:
Helper::_parseAttributes()
Helper::_formatAttribute()
These methods can now be found on the StringTemplate class that helpers frequently use. See the
StringTemplateTrait for an easy way to integrate string templates into your own helpers.
FormHelper
FormHelper has been entirely rewritten for 3.0. It features a few large changes:
FormHelper works with the new ORM. But has an extensible system for integrating with other ORMs
or datasources.
FormHelper features an extensible widget system that allows you to create new custom input widgets
and augment the built-in ones.
String templates are the foundation of the helper. Instead of munging arrays together everywhere,
most of the HTML FormHelper generates can be customized in one central place using template sets.
In addition to these larger changes, some smaller breaking changes have been made as well. These changes
should help streamline the HTML FormHelper generates and reduce the problems people had in the past:
The data[ prefix was removed from all generated inputs. The prefix serves no real purpose anymore.
The various standalone input methods like text(), select() and others no longer generate id
attributes.
The inputDefaults option has been removed from create().
Options default and onsubmit of create() have been removed. Instead one should use
JavaScript event binding or set all required js code for onsubmit.
end() can no longer make buttons. You should create buttons with button() or submit().
FormHelper::tagIsInvalid() has been removed. Use isFieldError() instead.
FormHelper::inputDefaults() has been removed. You can use templates() to define/augment the templates FormHelper uses.
The wrap and class options have been removed from the error() method.
The showParents option has been removed from select().
The div, before, after, between and errorMessage options have been removed from
input(). You can use templates to update the wrapping HTML. The templates option allows
you to override the loaded templates for one input.
The separator, between, and legend options have been removed from radio(). You can
use templates to change the wrapping HTML now.
48
The format24Hours parameter has been removed from hour(). It has been replaced with the
format option.
The minYear, and maxYear parameters have been removed from year(). Both of these parameters can now be provided as options.
The dateFormat and timeFormat parameters have been removed from datetime(). You can
use the template to define the order the inputs should be displayed in.
The submit() has had the div, before and after options removed. You can customize the
submitContainer template to modify this content.
The inputs() method no longer accepts legend and fieldset in the $fields parameter,
you must use the $options parameter. It now also requires $fields parameter to be an array.
The $blacklist parameter has been removed, the functionality has been replaced by specifying
'field' => false in the $fields parameter.
The inline parameter has been removed from postLink() method. You should use the block
option instead. Setting block => true will emulate the previous behavior.
The timeFormat parameter for hour(), time() and dateTime() now defaults to 24, complying with ISO 8601.
The $confirmMessage argument of Cake\View\Helper\FormHelper::postLink()
has been removed. You should now use key confirm in $options to specify the message.
Checkbox and radio input types are now rendered inside of label elements by default. This helps
increase compatibility with popular CSS libraries like Bootstrap33 and Foundation34 .
Templates tags are now all camelBacked. Pre-3.0 tags formstart, formend, hiddenblock
and inputsubmit are now formStart, formEnd, hiddenBlock and inputSubmit. Make
sure you change them if they are customized in your app.
It is recommended that you review the Form documentation for more details on how to use the FormHelper
in 3.0.
HtmlHelper
HtmlHelper::useTag() has been removed, use tag() instead.
HtmlHelper::loadConfig() has been removed. Customizing the tags can now be done using
templates() or the templates setting.
The second parameter $options for HtmlHelper::css() now always requires an array as
documented.
The first parameter $data for HtmlHelper::style() now always requires an array as documented.
The inline parameter has been removed from meta(), css(), script(), scriptBlock() methods. You
should use the block option instead. Setting block => true will emulate the previous behavior.
33
34
http://getbootstrap.com/
http://foundation.zurb.com/
View\Helper
49
PaginatorHelper
link() has been removed. It was no longer used by the helper internally. It had low usage in user
land code, and no longer fit the goals of the helper.
next() no longer has class, or tag options. It no longer has disabled arguments. Instead templates
are used.
prev() no longer has class, or tag options. It no longer has disabled arguments. Instead templates
are used.
first() no longer has after, ellipsis, separator, class, or tag options.
last() no longer has after, ellipsis, separator, class, or tag options.
numbers() no longer has separator, tag, currentTag, currentClass, class, tag, ellipsis options. These options are now facilitated through templates. It also requires the $options parameter
to be an array now.
The
%page%
style
placeholders
have
Cake\View\Helper\PaginatorHelper::counter().
holders instead.
been
removed
from
Use {{page}} style place-
url() has been renamed to generateUrl() to avoid method declaration clashes with
Helper::url().
By default all links and inactive texts are wrapped in <li> elements. This helps make CSS easier to write,
and improves compatibility with popular CSS frameworks.
Instead of the various options in each method, you should use the templates feature. See the PaginatorHelper
Templates documentation for information on how to use templates.
TimeHelper
TimeHelper::__set(), TimeHelper::__get(), and TimeHelper::__isset() were
removed. These were magic methods for deprecated attributes.
TimeHelper::serverOffset() has been removed. It promoted incorrect time math practices.
TimeHelper::niceShort() has been removed.
50
NumberHelper
NumberHelper::format() now requires $options to be an array.
SessionHelper
The SessionHelper has been deprecated. You can use $this->request->session() directly, and the flash message functionality has been moved into Flash instead.
JsHelper
JsHelper and all associated engines have been removed. It could only generate a very small subset
of JavaScript code for selected library and hence trying to generate all JavaScript code using just the
helper often became an impediment. Its now recommended to directly use JavaScript library of your
choice.
CacheHelper Removed
CacheHelper has been removed. The caching functionality it provided was non-standard, limited and incompatible with non-HTML layouts and data views. These limitations meant a full rebuild would be necessary.
Edge Side Includes have become a standardized way to implement the functionality CacheHelper used to
provide. However, implementing Edge Side Includes35 in PHP has a number of limitations and edge cases.
Instead of building a sub-par solution, we recommend that developers needing full response caching use
Varnish36 or Squid37 instead.
I18n
The I18n subsystem was completely rewritten. In general, you can expect the same behavior as in previous
versions, specifically if you are using the __() family of functions.
Internally, the I18n class uses Aura\Intl, and appropriate methods are exposed to access the specific
features of this library. For this reason most methods inside I18n were removed or renamed.
Due to the use of ext/intl, the L10n class was completely removed. It provided outdated and incomplete
data in comparison to the data available from the Locale class in PHP.
The default application language will no longer be changed automatically by the browser accepted language
nor by having the Config.language value set in the browser session. You can, however, use a dispatcher
filter to get automatic language switching from the Accept-Language header sent by the browser:
// In config/bootstrap.php
DispatcherFactory::addFilter('LocaleSelector');
35
36
37
http://en.wikipedia.org/wiki/Edge_Side_Includes
http://varnish-cache.org
http://squid-cache.org
I18n
51
There is no built-in replacement for automatically selecting the language by setting a value in the user
session.
The default formatting function for translated messages is no longer sprintf, but the more advanced and
feature rich MessageFormatter class. In general you can rewrite placeholders in messages as follows:
// Before:
__('Today is a %s day in %s', 'Sunny', 'Spain');
// After:
__('Today is a {0} day in {1}', 'Sunny', 'Spain');
You can avoid rewriting your messages by using the old sprintf formatter:
I18n::defaultFormatter('sprintf');
Additionally, the Config.language value was removed and it can no longer be used to control the
current language of the application. Instead, you can use the I18n class:
// Before
Configure::write('Config.language', 'fr_FR');
// Now
I18n::locale('en_US');
to
Since CakePHP now requires the mbstring extension, the Multibyte class has been removed.
Error messages throughout CakePHP are no longer passed through I18n functions. This was done to
simplify the internals of CakePHP and reduce overhead. The developer facing messages are rarely, if
ever, actually translated - so the additional overhead reaps very little benefit.
L10n
Cake\I18n\L10n s constructor now takes a Cake\Network\Request instance as argument.
Testing
The TestShell has been removed. CakePHP, the application skeleton and newly baked plugins all
use phpunit to run tests.
52
The webrunner (webroot/test.php) has been removed. CLI adoption has greatly increased since the
initial release of 2.x. Additionaly, CLI runners offer superior integration with IDEs and other automated tooling.
If you find yourself in need of a way to run tests from a browser you should checkout VisualPHPUnit38 . It offers many additional features over the old webrunner.
ControllerTestCase is deprecated and will be removed for CakePHP 3.0.0. You should use the
new Controller Integration Testing features instead.
Fixtures should now be referenced using their plural form:
// Instead of
$fixtures = ['app.article'];
// You should use
$fixtures = ['app.articles'];
Utility
Set Class Removed
The Set class has been removed, you should use the Hash class instead now.
Inflector
The default value for $replacement argument of Cake\Utility\Inflector::slug() has
been changed from underscore (_) to dash (-). Using dashes to separate words in URLs is the popular
choice and also recommended by Google.
Transliterations for Cake\Utility\Inflector::slug() have changed. If you use custom
transliterations you will need to update your code. Instead of regular expressions, transliterations use
simple string replacement. This yielded significant performance improvements:
// Instead of
Inflector::rules('transliteration', [
'/|/' => 'ae',
'//' => 'aa'
]);
38
https://github.com/NSinopoli/VisualPHPUnit
Utility
53
Separate set of uninflected and irregular rules for pluralization and singularization
have been removed.
Instead we now have a common list for each.
When using
Cake\Utility\Inflector::rules() with type singular and plural you can no longer
use keys like uninflected, irregular in $rules argument array.
You can add / overwrite the list of uninflected and irregular rules using
Cake\Utility\Inflector::rules() by using values uninflected and irregular for
$type argument.
Sanitize
Sanitize class has been removed.
Security
Security::cipher() has been removed. It is insecure and promoted bad cryptographic practices. You should use Security::encrypt() instead.
The Configure value Security.cipherSeed is no longer required.
Security::cipher() it serves no use.
Backwards compatibility in Cake\Utility\Security::rijndael() for values encrypted prior to CakePHP 2.3.1 has been removed.
You should re-encrypt values using
Security::encrypt() and a recent version of CakePHP 2.x before migrating.
The ability to generate a blowfish hash has been removed. You can no longer use type blowfish
for Security::hash(). One should just use PHPs password_hash() and password_verify() to
generate and verify blowfish hashes. The compability library ircmaxell/password-compat39 which is
installed along with CakePHP provides these functions for PHP < 5.5.
OpenSSL is now used over mcrypt when encrypting/decrypting data. This change provides better
performance and future proofs CakePHP against distros dropping support for mcrypt.
Security::rijndael() is deprecated and only available when using mcrypt.
Warning: Data encrypted with Security::encrypt() in previous versions is not compatible with the
openssl implementation. You should set the implementation to mcrypt when upgrading.
39
54
https://packagist.org/packages/ircmaxell/password-compat
Time
CakeTime has been renamed to Cake\I18n\Time.
CakeTime::serverOffset() has been removed. It promoted incorrect time math practises.
CakeTime::niceShort() has been removed.
CakeTime::convert() has been removed.
CakeTime::convertSpecifiers() has been removed.
CakeTime::dayAsSql() has been removed.
CakeTime::daysAsSql() has been removed.
CakeTime::fromString() has been removed.
CakeTime::gmt() has been removed.
CakeTime::toATOM() has been renamed to toAtomString.
CakeTime::toRSS() has been renamed to toRssString.
CakeTime::toUnix() has been renamed to toUnixString.
CakeTime::wasYesterday() has been renamed to isYesterday to match the rest of the
method naming.
CakeTime::format() Does not use sprintf format strings anymore, you can use
i18nFormat instead.
Time::timeAgoInWords() now requires $options to be an array.
Time is not a collection of static methods anymore, it extends DateTime to inherit all its methods and adds
location aware formatting functions with the help of the intl extension.
In general, expressions looking like this:
CakeTime::aMethod($date);
Number
The Number library was rewritten to internally use the NumberFormatter class.
CakeNumber has been renamed to Cake\I18n\Number.
Number::format() now requires $options to be an array.
Number::addFormat() was removed.
Number::fromReadableSize() has been moved to Cake\Utility\Text::parseFileSize().
Utility
55
Validation
The range for Validation::range() now is inclusive if $lower and $upper are provided.
Validation::ssn() has been removed.
Xml
Xml::build() now requires $options to be an array.
Xml::build() no longer accepts a URL. If you need to create an XML document from a URL,
use Http\Client.
56
CHAPTER 4
In this section, you can walk through typical CakePHP applications to see how all of the pieces come
together.
Alternatively, you can refer to the non-official CakePHP plugin repository CakePackages40 and the Bakery41
for existing applications and components.
Bookmarker Tutorial
This tutorial will walk you through the creation of a simple bookmarking application (bookmarker). To start
with, well be installing CakePHP, creating our database, and using the tools CakePHP provides to get our
application up fast.
Heres what youll need:
1. A database server. Were going to be using MySQL server in this tutorial. Youll need to know enough
about SQL in order to create a database: CakePHP will be taking the reins from there. Since were
using MySQL, also make sure that you have pdo_mysql enabled in PHP.
2. Basic PHP knowledge.
Before starting you should make sure that you have got an up to date PHP version:
php -v
You should at least have got installed PHP 5.5.9 (CLI) or higher. Your webservers PHP version must also
be of 5.5.9 or higher, and should best be the same version your command line interface (CLI) PHP version
is of. If youd like to see the completed application, checkout cakephp/bookmarker42 . Lets get started!
40
41
42
http://plugins.cakephp.org/
http://bakery.cakephp.org/
https://github.com/cakephp/bookmarker-tutorial
57
Getting CakePHP
The easiest way to install CakePHP is to use Composer. Composer is a simple way of installing CakePHP
from your terminal or command line prompt. First, youll need to download and install Composer if you
havent done so already. If you have cURL installed, its as easy as running the following:
curl -s https://getcomposer.org/installer | php
If you downloaded and ran the Composer Windows Installer44 , then type the following line in your terminal
from your installation directory (ie. C:\wamp\www\dev\cakephp3):
composer self-update && composer create-project --prefer-dist cakephp/app
bookmarker
The advantage to using Composer is that it will automatically complete some important set up tasks, such
as setting the correct file permissions and creating your config/app.php file for you.
There are other ways to install CakePHP. If you cannot or dont want to use Composer, check out the
Installation section.
Regardless of how you downloaded and installed CakePHP, once your set up is completed, your directory
setup should look something like the following:
/bookmarker
/bin
/config
/logs
/plugins
/src
/tests
/tmp
/vendor
/webroot
.editorconfig
.gitignore
.htaccess
.travis.yml
composer.json
index.php
phpunit.xml.dist
README.md
Now might be a good time to learn a bit about how CakePHPs directory structure works: check out the
CakePHP Folder Structure section.
43
44
58
https://getcomposer.org/download/
https://getcomposer.org/Composer-Setup.exe
Note: For Windows, the command needs to be bin\cake server (note the backslash).
This will start PHPs built-in webserver on port 8765. Open up http://localhost:8765 in your web browser to
see the welcome page. All the bullet points should be checkmarks other than CakePHP being able to connect
to your database. If not, you may need to install additional PHP extensions, or set directory permissions.
Bookmarker Tutorial
59
You may have noticed that the bookmarks_tags table used a composite primary key. CakePHP supports
composite primary keys almost everywhere, making it easier to build multi-tenanted applications.
The table and column names we used were not arbitrary. By using CakePHPs naming conventions, we
can leverage CakePHP better and avoid having to configure the framework. CakePHP is flexible enough
to accommodate even inconsistent legacy database schemas, but adhering to the conventions will save you
time.
Database Configuration
Next, lets tell CakePHP where our database is and how to connect to it. For many, this will be the first and
last time you will need to configure anything.
The configuration should be pretty straightforward:
just replace the values in the
Datasources.default array in the config/app.php file with those that apply to your setup. A
sample completed configuration array might look something like the following:
return [
// More configuration above.
'Datasources' => [
'default' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Mysql',
'persistent' => false,
'host' => 'localhost',
'username' => 'cakephp',
'password' => 'AngelF00dC4k3~',
'database' => 'cake_bookmarks',
'encoding' => 'utf8',
'timezone' => 'UTC',
'cacheMetadata' => true,
],
],
// More configuration below.
];
Once youve saved your config/app.php file, you should see that CakePHP is able to connect to the
database section have a checkmark.
Note: A copy of CakePHPs default configuration file is found in config/app.default.php.
60
This will generate the controllers, models, views, their corresponding test cases, and fixtures for
our users, bookmarks and tags resources. If youve stopped your server, restart it and go to
http://localhost:8765/bookmarks.
You should see a basic but functional application providing data access to your applications database tables.
Once youre at the list of bookmarks, add a few users, bookmarks, and tags.
Note: If you see a Not Found (404) page, confirm that the Apache mod_rewrite module is loaded.
Bookmarker Tutorial
61
Now update one of the users you created earlier, if you change their password, you should see a hashed
password instead of the original value on the list or view pages. CakePHP hashes passwords with bcrypt45
by default. You can also use sha1 or md5 if youre working with an existing database.
The above defines a new route which connects the /bookmarks/tagged/ path, to
BookmarksController::tags(). By defining routes, you can isolate how your URLs look,
45
62
http://codahale.com/how-to-safely-store-a-password/
To access other parts of the request data, consult the Request section.
Creating the Finder Method
In CakePHP we like to keep our controller actions slim, and put most of our applications logic in the models.
If you were to visit the /bookmarks/tagged URL now you would see an error that the findTagged()
method has not been implemented yet, so lets do that. In src/Model/Table/BookmarksTable.php add the
following:
// The $query argument is a query builder instance.
// The $options array will contain the 'tags' option we passed
// to find('tagged') in our controller action.
public function findTagged(Query $query, array $options)
{
return $this->find()
->distinct(['Bookmarks.id'])
->matching('Tags', function ($q) use ($options) {
if (empty($options['tags'])) {
return $q->where(['Tags.title IS' => null]);
}
return $q->where(['Tags.title IN' => $options['tags']]);
});
}
We just implemented a custom finder method. This is a very powerful concept in CakePHP that allows you to
package up re-usable queries. Finder methods always get a Query Builder object and an array of options as
parameters. Finders can manipulate the query and add any required conditions or criteria. When complete,
finder methods must return a modified query object. In our finder weve leveraged the distinct()
and matching() methods which allow us to find distinct bookmarks that have a matching tag. The
Bookmarker Tutorial
63
matching() method accepts an anonymous function46 that receives a query builder as its argument. Inside
the callback we use the query builder to define conditions that will filter bookmarks that have specific tags.
Creating the View
Now if you visit the /bookmarks/tagged URL, CakePHP will show an error letting you know that
you have not made a view file. Next, lets build the view file for our tags() action. In
src/Template/Bookmarks/tags.ctp put the following content:
<h1>
Bookmarks tagged with
<?= $this->Text->toList($tags) ?>
</h1>
<section>
<?php foreach ($bookmarks as $bookmark): ?>
<article>
<!-- Use the HtmlHelper to create a link -->
<h4><?= $this->Html->link($bookmark->title, $bookmark->url) ?></h4>
<small><?= h($bookmark->url) ?></small>
<!-- Use the TextHelper to format text -->
<?= $this->Text->autoParagraph($bookmark->description) ?>
</article>
<?php endforeach; ?>
</section>
In the above code we use the Html and Text helpers to assist in generating our view output. We also use
the h shortcut function to HTML encode output. You should remember to always use h() when outputting
user data to prevent HTML injection issues.
The tags.ctp file we just created follows the CakePHP conventions for view template files. The convention
is to have the template use the lower case and underscored version of the controller action name.
You may notice that we were able to use the $tags and $bookmarks variables in our view. When we use
the set() method in our controller, we set specific variables to be sent to the view. The view will make all
passed variables available in the templates as local variables.
You should now be able to visit the /bookmarks/tagged/funny URL and see all the bookmarks tagged with
funny.
So far, weve created a basic application to manage bookmarks, tags and users. However, everyone can see
everyone elses tags. In the next chapter, well implement authentication and restrict the visible bookmarks
to only those that belong to the current user.
Now continue to Bookmarker Tutorial Part 2 to continue building your application or dive into the documentation to learn more about what CakePHP can do for you.
46
64
http://php.net/manual/en/functions.anonymous.php
Adding Login
In CakePHP, authentication is handled by Components. Components can be thought of as ways to create
reusable chunks of controller code related to a specific feature or concept. Components can also hook into
the controllers event life-cycle and interact with your application that way. To get started, well add the
AuthComponent to our application. Well pretty much want every method to require authentication, so well
add AuthComponent in our AppController:
// In src/Controller/AppController.php
namespace App\Controller;
use Cake\Controller\Controller;
class AppController extends Controller
{
public function initialize()
{
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
]
]);
// Allow the display action so our pages controller
// continues to work.
$this->Auth->allow(['display']);
}
}
Weve just told CakePHP that we want to load the Flash and Auth components. In addition, weve
customized the configuration of AuthComponent, as our users table uses email as the username. Now, if
you go to any URL youll be kicked to /users/login, which will show an error page as we have not written
that code yet. So lets create the login action:
65
// In src/Controller/UsersController.php
public function login()
{
if ($this->request->is('post')) {
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
return $this->redirect($this->Auth->redirectUrl());
}
$this->Flash->error('Your username or password is incorrect.');
}
}
Now that we have a simple login form, we should be able to log in with one of the users that has a hashed
password.
Note: If none of your users have hashed passwords, comment the loadComponent('Auth') line.
Then go and edit the user, saving a new password for them.
You should now be able to log in. If not, make sure you are using a user that has a hashed password.
Adding Logout
Now that people can log in, youll probably want to provide a way to log out as well. Again, in the
UsersController, add the following code:
public function initialize()
{
parent::initialize();
$this->Auth->allow(['logout']);
}
public function logout()
{
$this->Flash->success('You are now logged out.');
return $this->redirect($this->Auth->logout());
}
This code whitelists the logout action as a public action, and implements the logout method. Now you
can visit /users/logout to log out. You should then be sent to the login page.
66
Enabling Registrations
If you arent logged in and you try to visit /users/add you will be kicked to the login page. We should
fix that as we want to allow people to sign up for our application. In the UsersController add the
following:
public function initialize()
{
parent::initialize();
// Add logout to the allowed actions list.
$this->Auth->allow(['logout', 'add']);
}
The above tells AuthComponent that the add() action does not require authentication or authorization.
You may want to take the time to clean up the Users/add.ctp and remove the misleading links, or continue
on to the next section. We wont be building out user editing, viewing or listing in this tutorial so they will
not work as AuthComponent will deny you access to those controller actions.
Also, add the following to the configuration for Auth in your AppController:
'authorize' => 'Controller',
67
Well default to denying access, and incrementally grant access where it makes sense. First, well add the
authorization logic for bookmarks. In your BookmarksController add the following:
public function isAuthorized($user)
{
$action = $this->request->params['action'];
// The add and index actions are always allowed.
if (in_array($action, ['index', 'add', 'tags'])) {
return true;
}
// All other actions require an id.
if (empty($this->request->params['pass'][0])) {
return false;
}
// Check that the bookmark belongs to the current user.
$id = $this->request->params['pass'][0];
$bookmark = $this->Bookmarks->get($id);
if ($bookmark->user_id == $user['id']) {
return true;
}
return parent::isAuthorized($user);
}
Now if you try to view, edit or delete a bookmark that does not belong to you, you should be redirected back
to the page you came from. However, there is no error message being displayed, so lets rectify that next:
// In src/Template/Layout/default.ctp
// Under the existing flash message.
<?= $this->Flash->render('auth') ?>
68
By setting the entity property with the session data, we remove any possibility of the user modifying which
user a bookmark is for. Well do the same for the edit form and action. Your edit() action from
src/Controller/BookmarksController.php should look like:
public function edit($id = null)
{
$bookmark = $this->Bookmarks->get($id, [
'contain' => ['Tags']
]);
if ($this->request->is(['patch', 'post', 'put'])) {
$bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->
data);
$bookmark->user_id = $this->Auth->user('id');
if ($this->Bookmarks->save($bookmark)) {
$this->Flash->success('The bookmark has been saved.');
return $this->redirect(['action' => 'index']);
}
$this->Flash->error('The bookmark could not be saved. Please, try
again.');
}
$tags = $this->Bookmarks->Tags->find('list');
$this->set(compact('bookmark', 'tags'));
$this->set('_serialize', ['bookmark']);
}
69
List View
Now, we only need to show bookmarks for the currently logged in user. We can do that by updating the call
to paginate(). Make your index() action from src/Controller/BookmarksController.php look like:
public function index()
{
$this->paginate = [
'conditions' => [
'Bookmarks.user_id' => $this->Auth->user('id'),
]
];
$this->set('bookmarks', $this->paginate($this->Bookmarks));
$this->set('_serialize', ['bookmarks']);
}
We should also update the tags() action and the related finder method, but well leave that as an exercise
you can complete on your own.
This will let us access the $bookmark->tag_string computed property. Well use this property in
inputs later on. Remember to add the tag_string property to the _accessible list in your entity, as
well want to save it later on.
70
71
unset($newTags[$index]);
}
}
// Add existing tags.
foreach ($query as $tag) {
$out[] = $tag;
}
// Add new tags.
foreach ($newTags as $tag) {
$out[] = $this->Tags->newEntity(['title' => $tag]);
}
return $out;
}
While this code is a bit more complicated than what weve done so far, it helps to showcase how powerful
the ORM in CakePHP is. You can manipulate query results using the Collections methods, and handle
scenarios where you are creating entities on the fly with ease.
Wrapping Up
Weve expanded our bookmarking application to handle authentication and basic authorization/access control scenarios. Weve also added some nice UX improvements by leveraging the FormHelper and ORM
capabilities.
Thanks for taking the time to explore CakePHP. Next, you can complete the Blog Tutorial, learn more about
the Database Access & ORM, or you can peruse the /topics.
Blog Tutorial
This tutorial will walk you through the creation of a simple blog application. Well be installing CakePHP,
creating a database, and creating enough application logic to list, add, edit, and delete blog articles.
Heres what youll need:
1. A running web server. Were going to assume youre using Apache, though the instructions for using
other servers should be very similar. We might have to play a little with the server configuration, but
most folks can get CakePHP up and running without any configuration at all. Make sure you have
PHP 5.5.9 or greater, and that the mbstring and intl extensions are enabled in PHP.
2. A database server. Were going to be using MySQL server in this tutorial. Youll need to know enough
about SQL in order to create a database: CakePHP will be taking the reins from there. Since were
using MySQL, also make sure that you have pdo_mysql enabled in PHP.
3. Basic PHP knowledge.
Lets get started!
72
Getting CakePHP
The easiest way to install CakePHP is to use Composer. Composer is a simple way of installing CakePHP
from your terminal or command line prompt. First, youll need to download and install Composer if you
havent done so already. If you have cURL installed, its as easy as running the following:
curl -s https://getcomposer.org/installer | php
In case youve already got composer installed globally, you may instead type:
composer self-update && composer create-project --prefer-dist cakephp/app
[app_name]
The advantage to using Composer is that it will automatically complete some important set up tasks, such
as setting the correct file permissions and creating your config/app.php file for you.
There are other ways to install CakePHP. If you cannot or dont want to use Composer, check out the
Installation section.
Regardless of how you downloaded and installed CakePHP, once your set up is completed, your directory
setup should look something like the following:
/cake_install
/bin
/config
/logs
/plugins
/src
/tests
/tmp
/vendor
/webroot
.editorconfig
.gitignore
.htaccess
.travis.yml
composer.json
index.php
phpunit.xml.dist
README.md
Now might be a good time to learn a bit about how CakePHPs directory structure works: check out the
CakePHP Folder Structure section.
47
https://getcomposer.org/download/
Blog Tutorial
73
If for some reason CakePHP cant write to these directories, youll be informed by a warning while not in
production mode.
While not recommended, if you are unable to set the permissions to the same as your webserver, you can
simply set write permissions on the folder by running a command such as:
chmod 777 -R tmp
chmod 777 -R logs
The choices on table and column names are not arbitrary. If you follow CakePHPs database naming conventions, and CakePHPs class naming conventions (both outlined in CakePHP Conventions), youll be
able to take advantage of a lot of free functionality and avoid configuration. CakePHP is flexible enough
to accommodate even inconsistent legacy database schemas, but adhering to the conventions will save you
time.
74
Check out CakePHP Conventions for more information, but its suffice to say that naming our table articles automatically hooks it to our Articles model, and having fields called modified and created will be
automatically managed by CakePHP.
Database Configuration
Next, lets tell CakePHP where our database is and how to connect to it. For many, this will be the first and
last time you will need to configure anything.
The configuration should be pretty straightforward:
just replace the values in the
Datasources.default array in the config/app.php file with those that apply to your setup. A
sample completed configuration array might look something like the following:
return [
// More configuration above.
'Datasources' => [
'default' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Mysql',
'persistent' => false,
'host' => 'localhost',
'username' => 'cake_blog',
'password' => 'AngelF00dC4k3~',
'database' => 'cake_blog',
'encoding' => 'utf8',
'timezone' => 'UTC'
],
],
// More configuration below.
];
Once youve saved your config/app.php file, you should be able to open your browser and see the CakePHP
welcome page. It should also tell you that your database connection file was found, and that CakePHP can
successfully connect to the database.
Note: A copy of CakePHPs default configuration file is found in config/app.default.php.
Optional Configuration
There are a few other items that can be configured. Most developers complete these laundry-list items, but
theyre not required for this tutorial. One is defining a custom string (or salt) for use in security hashes.
The security salt is used for generating hashes. If you used Composer this too is taken care of for you during
the install. Else youd need to change the default salt value by editing config/app.php. It doesnt matter
much what the new value is, as long as its not guessable:
Blog Tutorial
75
'Security' => [
'salt' => 'something long and containing lots of different values.',
],
A Note on mod_rewrite
Occasionally new users will run into mod_rewrite issues. For example if the CakePHP welcome page looks
a little funny (no images or CSS styles). This probably means mod_rewrite is not functioning on your
system. Please refer to the URL Rewriting section to help resolve any issues you are having.
Now continue to Blog Tutorial - Part 2 to start building your first CakePHP application.
Naming conventions are very important in CakePHP. By naming our Table object ArticlesTable,
CakePHP can automatically infer that this Table object will be used in the ArticlesController, and
will be tied to a database table called articles.
Note: CakePHP will dynamically create a model object for you if it cannot find a corresponding file in
src/Model/Table. This also means that if you accidentally name your file wrong (i.e. articlestable.php
or ArticleTable.php), CakePHP will not recognize any of your settings and will use the generated model
instead.
76
For more on models, such as callbacks, and validation, check out the Database Access & ORM chapter of
the Manual.
Note: If you completed Part 1 of the Blog Tutorial and created the articles table in our Blog
database you can leverage CakePHPs bake console and its code generation capabilities to create the
ArticlesTable model:
bin/cake bake model Articles
For more on bake and its code generation features please visit Code Generation with Bake.
Now, lets add an action to our controller. Actions often represent a single function or interface in an
application. For example, when users request www.example.com/articles/index (which is also the same as
www.example.com/articles/), they might expect to see a listing of articles. The code for that action would
look like this:
// src/Controller/ArticlesController.php
namespace App\Controller;
class ArticlesController extends AppController
{
public function index()
{
$articles = $this->Articles->find('all');
$this->set(compact('articles'));
}
}
By defining function index() in our ArticlesController, users can now access the logic there by
requesting www.example.com/articles/index. Similarly, if we were to define a function called foobar(),
users would be able to access that at www.example.com/articles/foobar.
77
Warning: You may be tempted to name your controllers and actions a certain way to obtain a certain
URL. Resist that temptation. Follow CakePHP Conventions (capitalization, plural names, etc.) and
create readable, understandable action names. You can map URLs to your code using Routing covered
later on.
The single instruction in the action uses set() to pass data from the controller to the view (which well
create next). The line sets the view variable called articles equal to the return value of the find('all')
method of the ArticlesTable object.
Note: If you completed Part 1 of the Blog Tutorial and created the articles table in your Blog database
you can leverage CakePHPs bake console and its code generation capabilities to create the ArticlesController class:
bin/cake bake controller Articles
For more on bake and its code generation features please visit Code Generation with Bake.
To learn more about CakePHPs controllers, check out the Controllers chapter.
78
79
}
}
The set() call should look familiar. Notice were using get() rather than find('all') because we
only really want a single articles information.
Notice that our view action takes a parameter: the ID of the article wed like to see. This parameter is handed
to the action through the requested URL. If a user requests /articles/view/3, then the value 3 is
passed as $id.
We also do a bit of error checking to ensure a user is actually accessing a record. By using the get() function in the Articles table, we make sure the user has accessed a record that exists. In case the requested article
is not present in the database, or the id is false the get() function will throw a NotFoundException.
Now lets create the view for our new view action and place it in src/Template/Articles/view.ctp
<!-- File: src/Template/Articles/view.ctp -->
<h1><?= h($article->title) ?></h1>
<p><?= h($article->body) ?></p>
<p><small>Created: <?= $article->created->format(DATE_RFC850) ?></small></p>
Verify that this is working by trying the links at /articles/index or manually requesting an article by
accessing /articles/view/{id}, replacing {id} by an article id.
Adding Articles
Reading from the database and showing us the articles is a great start, but lets allow for the adding of new
articles.
First, start by creating an add() action in the ArticlesController:
// src/Controller/ArticlesController.php
namespace App\Controller;
use App\Controller\AppController;
class ArticlesController extends AppController
{
public function initialize()
{
parent::initialize();
$this->loadComponent('Flash'); // Include the FlashComponent
}
public function index()
{
$this->set('articles', $this->Articles->find('all'));
}
80
Note: You need to include the Flash component in any controller where you will use it. If necessary,
include it in your AppController.
Heres what the add() action does: if the HTTP method of the request was POST, try to save the data using
the Articles model. If for some reason it doesnt save, just render the view. This gives us a chance to show
the user validation errors or other warnings.
Every CakePHP request includes a Request object which is accessible using $this->request. The
request object contains useful information regarding the request that was just received, and can be used to
control the flow of your application. In this case, we use the Cake\Network\Request::is() method
to check that the request is a HTTP POST request.
When a user uses a form to POST data to your application, that information is available in
$this->request->data. You can use the pr() or debug() functions to print it out if you want
to see what it looks like.
We use FlashComponents success() and error() methods to set a message to a session variable. These methods are provided using PHPs magic method features48 . Flash messages will be
displayed on the page after redirection. In the layout we have <?= $this->Flash->render()
?> which displays the message and clears the corresponding session variable.
The controllers Cake\Controller\Controller::redirect function redirects to another URL. The
param ['action' => 'index'] translates to URL /articles i.e the index action of the
ArticlesController. You can refer to Cake\Routing\Router::url() function on the API49
to see the formats in which you can specify a URL for various CakePHP functions.
48
49
http://php.net/manual/en/language.oop5.overloading.php#object.call
http://api.cakephp.org
81
Calling the save() method will check for validation errors and abort the save if any occur. Well discuss
how those errors are handled in the following sections.
Data Validation
CakePHP goes a long way toward taking the monotony out of form input validation. Everyone hates coding
up endless forms and their validation routines. CakePHP makes it easier and faster.
To take advantage of the validation features, youll need to use CakePHPs Form helper in your views. The
Cake\View\Helper\FormHelper is available by default to all views at $this->Form.
Heres our add view:
<!-- File: src/Template/Articles/add.ctp -->
<h1>Add Article</h1>
<?php
echo $this->Form->create($article);
echo $this->Form->input('title');
echo $this->Form->input('body', ['rows' => '3']);
echo $this->Form->button(__('Save Article'));
echo $this->Form->end();
?>
We use the FormHelper to generate the opening tag for an HTML form.
$this->Form->create() generates:
If create() is called with no parameters supplied, it assumes you are building a form that submits via
POST to the current controllers add() action (or edit() action when id is included in the form data).
The $this->Form->input() method is used to create form elements of the same name. The first
parameter tells CakePHP which field they correspond to, and the second parameter allows you to specify a
wide array of options - in this case, the number of rows for the textarea. Theres a bit of introspection and
automagic here: input() will output different form elements based on the model field specified.
The $this->Form->end() call ends the form. Outputting hidden inputs if CSRF/Form Tampering
prevention is enabled.
Now lets go back and update our src/Template/Articles/index.ctp view to include a new Add Article
link. Before the <table>, add the following line:
<?= $this->Html->link('Add Article', ['action' => 'add']) ?>
You may be wondering: how do I tell CakePHP about my validation requirements? Validation rules are
defined in the model. Lets look back at our Articles model and make a few adjustments:
// src/Model/Table/ArticlesTable.php
namespace App\Model\Table;
82
use Cake\ORM\Table;
use Cake\Validation\Validator;
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Timestamp');
}
public function validationDefault(Validator $validator)
{
$validator
->notEmpty('title')
->requirePresence('title')
->notEmpty('body')
->requirePresence('body');
return $validator;
}
}
The validationDefault() method tells CakePHP how to validate your data when the save()
method is called. Here, weve specified that both the body and title fields must not be empty, and are
required for both create and update operations. CakePHPs validation engine is strong, with a number of
pre-built rules (credit card numbers, email addresses, etc.) and flexibility for adding your own validation
rules. For more information on that setup, check the Validation documentation.
Now that your validation rules are in place, use the app to try to add an article with an empty title or body
to see how it works. Since weve used the Cake\View\Helper\FormHelper::input() method of
the FormHelper to create our form elements, our validation error messages will be shown automatically.
Editing Articles
Post editing: here we go. Youre a CakePHP pro by now, so you should have picked up a pattern. Make the
action, then the view. Heres what the edit() action of the ArticlesController would look like:
// src/Controller/ArticlesController.php
public function edit($id = null)
{
$article = $this->Articles->get($id);
if ($this->request->is(['post', 'put'])) {
$this->Articles->patchEntity($article, $this->request->data);
if ($this->Articles->save($article)) {
$this->Flash->success(__('Your article has been updated.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('Unable to update your article.'));
}
83
$this->set('article', $article);
}
This action first ensures that the user has tried to access an existing record. If they havent passed in an $id
parameter, or the article does not exist, we throw a NotFoundException for the CakePHP ErrorHandler
to take care of.
Next the action checks whether the request is either a POST or a PUT request. If it is, then we use the POST
data to update our article entity by using the patchEntity() method. Finally we use the table object to
save the entity back or kick back and show the user validation errors.
The edit view might look something like this:
<!-- File: src/Template/Articles/edit.ctp -->
<h1>Edit
<?php
echo
echo
echo
echo
echo
?>
Article</h1>
$this->Form->create($article);
$this->Form->input('title');
$this->Form->input('body', ['rows' => '3']);
$this->Form->button(__('Save Article'));
$this->Form->end();
This view outputs the edit form (with the values populated), along with any necessary validation error
messages.
CakePHP will determine whether a save() generates an insert or an update statement based on the state
of the entity.
You can now update your index view with links to edit specific articles:
<!-- File: src/Template/Articles/index.ctp
<h1>Blog articles</h1>
<p><?= $this->Html->link("Add Article", ['action' => 'add']) ?></p>
<table>
<tr>
<th>Id</th>
<th>Title</th>
<th>Created</th>
<th>Action</th>
</tr>
<!-- Here's where we iterate through our $articles query object, printing out
article info -->
<?php foreach ($articles as $article): ?>
<tr>
<td><?= $article->id ?></td>
<td>
<?= $this->Html->link($article->title, ['action' => 'view',
$article->id]) ?>
84
</td>
<td>
<?= $article->created->format(DATE_RFC850) ?>
</td>
<td>
<?= $this->Html->link('Edit', ['action' => 'edit', $article->id])
?>
</td>
</tr>
<?php endforeach; ?>
</table>
Deleting Articles
Next, lets make a way for users to delete articles.
ArticlesController:
// src/Controller/ArticlesController.php
public function delete($id)
{
$this->request->allowMethod(['post', 'delete']);
$article = $this->Articles->get($id);
if ($this->Articles->delete($article)) {
$this->Flash->success(__('The article with id: {0} has been deleted.
', h($id)));
return $this->redirect(['action' => 'index']);
}
}
This logic deletes the article specified by $id, and uses $this->Flash->success() to show the
user a confirmation message after redirecting them on to /articles. If the user attempts to do a delete
using a GET request, the allowMethod() will throw an Exception. Uncaught exceptions are captured by
CakePHPs exception handler, and a nice error page is displayed. There are many built-in Exceptions that
can be used to indicate the various HTTP errors your application might need to generate.
Because were just executing some logic and redirecting, this action has no view. You might want to update
your index view with links that allow users to delete articles, however:
<!-- File: src/Template/Articles/index.ctp (delete links added) -->
<h1>Blog articles</h1>
<p><?= $this->Html->link('Add Article', ['action' => 'add']) ?></p>
<table>
<tr>
<th>Id</th>
<th>Title</th>
<th>Created</th>
<th>Actions</th>
85
</tr>
<!-- Here's where we loop through our $articles query object, printing out
article info -->
<?php foreach ($articles as $article): ?>
<tr>
<td><?= $article->id ?></td>
<td>
<?= $this->Html->link($article->title, ['action' => 'view',
$article->id]) ?>
</td>
<td>
<?= $article->created->format(DATE_RFC850) ?>
</td>
<td>
<?= $this->Form->postLink(
'Delete',
['action' => 'delete', $article->id],
['confirm' => 'Are you sure?'])
?>
<?= $this->Html->link('Edit', ['action' => 'edit', $article->id])
?>
</td>
</tr>
<?php endforeach; ?>
</table>
Note: This view code also uses the FormHelper to prompt the user with a JavaScript confirmation dialog
before they attempt to delete an article.
Routes
For some, CakePHPs default routing works well enough. Developers who are sensitive to user-friendliness
and general search engine compatibility will appreciate the way that CakePHPs URLs map to specific
actions. So well just make a quick change to routes in this tutorial.
For more information on advanced routing techniques, see Connecting Routes.
By default, CakePHP responds to a request for the root of your site (e.g., http://www.example.com) using
86
its PagesController, rendering a view called home. Instead, well replace this with our ArticlesController by creating a routing rule.
CakePHPs routing is found in config/routes.php. Youll want to comment out or remove the line that
defines the default root route. It looks like this:
$routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home
']);
This line connects the URL / with the default CakePHP home page. We want it to connect with our own
controller, so replace that line with this one:
$routes->connect('/', ['controller' => 'Articles', 'action' => 'index']);
This should connect users requesting / to the index() action of our ArticlesController.
Note:
CakePHP also makes use of reverse routing. If, with the above route defined, you pass
['controller' => 'Articles','action' => 'index'] to a function expecting an array,
the resulting URL used will be /. Its therefore a good idea to always use arrays for URLs as this means
your routes define where a URL goes, and also ensures that links point to the same place.
Conclusion
Creating applications this way will win you peace, honor, love, and money beyond even your wildest fantasies. Simple, isnt it? Keep in mind that this tutorial was very basic. CakePHP has many more features to
offer, and is flexible in ways we didnt wish to cover here for simplicitys sake. Use the rest of this manual
as a guide for building more feature-rich applications.
Now that youve created a basic CakePHP application, you can either continue to Blog Tutorial - Part 3, or
start your own project. You can also peruse the /topics or API <http://api.cakephp.org/3.0> to learn more
about CakePHP.
If you need help, there are many ways to get the help you need - please see the Where to Get Help page.
Welcome to CakePHP!
Suggested Follow-up Reading
These are common tasks people learning CakePHP usually want to study next:
1. Layouts: Customizing your website layout
2. Elements: Including and reusing view snippets
3. Code Generation with Bake: Generating basic CRUD code
4. Blog Tutorial - Authentication and Authorization: User authentication and authorization tutorial
87
Migrations Plugin
We will use the migrations plugin50 to create a table in our database. If you already have an articles table in
your database, erase it.
Now open your applications composer.json file. Normally you would see that the migrations plugin is
already under require. If not, add it by executing:
composer require cakephp/migrations:~1.0
Also,
add
Once the plugin is loaded, run the following command to create a migration file:
bin/cake bake migration CreateArticles title:string body:text category_id:
integer created modified
A migration file will be generated in the /config/Migrations folder with the following:
<?php
use Migrations\AbstractMigration;
class CreateArticles extends AbstractMigration
{
public function change()
{
$table = $this->table('articles');
$table->addColumn('title', 'string', [
'default' => null,
'limit' => 255,
'null' => false,
]);
$table->addColumn('body', 'text', [
'default' => null,
'null' => false,
]);
$table->addColumn('category_id', 'integer', [
'default' => null,
'limit' => 11,
50
88
https://github.com/cakephp/migrations
Run another command to create a categories table. If you need to specify a field length, you can do it
within brackets in the field type, ie:
bin/cake bake migration CreateCategories parent_id:integer lft:integer[10]
rght:integer[10] name:string[100] description:string created modified
89
Now that the migration files are created, you can edit them before creating your tables. We need to change
the 'null' => false for the parent_id field with 'null' => true because a top-level category
has a null parent_id.
Run the following command to create your tables:
bin/cake migrations migrate
tables
together.
Open
the
// src/Model/Table/ArticlesTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Timestamp');
// Just add the belongsTo relation with CategoriesTable
$this->belongsTo('Categories', [
'foreignKey' => 'category_id',
]);
}
}
90
The bake tool has created all your files in a snap. You can give them a quick read if you want re-familiarize
yourself with how CakePHP works.
Note: If you are on Windows remember to use \ instead of /.
Youll
need
to
edit
the
src/Template/Categories/edit.ctp:
following
in
src/Template/Categories/add.ctp
and
echo $this->Form->input('parent_id', [
'options' => $parentCategories,
'empty' => 'No parent category'
]);
With the TreeBehavior attached youll be able to access some features like reordering the categories. Well
see that in a moment.
But for now, you have to remove the following inputs in your Categories add and edit template files:
echo $this->Form->input('lft');
echo $this->Form->input('rght');
In addition you should disable or remove the requirePresence from the validator for both the lft and rght
columns in your CategoriesTable model:
51
http://www.sitepoint.com/hierarchical-data-database-2/
91
These fields are automatically managed by the TreeBehavior when a category is saved.
Using your web browser, add some new categories using the /yoursite/categories/add controller
action.
92
93
94
When you go to the address /yoursite/articles/add you should see a list of categories to choose.
We have adhered to the CakePHP conventions in naming tables, but were also taking advantage of another
convention: By using the username and password columns in a users table, CakePHP will be able to autoconfigure most things for us when implementing the user login.
Next step is to create our UsersTable class, responsible for finding, saving and validating any user data:
// src/Model/Table/UsersTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
95
Lets also create our UsersController. The following content corresponds to parts of a basic baked UsersController class using the code generation utilities bundled with CakePHP:
// src/Controller/UsersController.php
namespace App\Controller;
use App\Controller\AppController;
use Cake\Event\Event;
class UsersController extends AppController
{
public function beforeFilter(Event $event)
{
parent::beforeFilter($event);
$this->Auth->allow('add');
}
public function index()
{
$this->set('users', $this->Users->find('all'));
}
public function view($id)
{
$user = $this->Users->get($id);
$this->set(compact('user'));
}
public function add()
{
$user = $this->Users->newEntity();
if ($this->request->is('post')) {
$user = $this->Users->patchEntity($user, $this->request->data);
96
if ($this->Users->save($user)) {
$this->Flash->success(__('The user has been saved.'));
return $this->redirect(['action' => 'add']);
}
$this->Flash->error(__('Unable to add the user.'));
}
$this->set('user', $user);
}
}
In the same way we created the views for our articles by using the code generation tool, we can implement
the user views. For the purpose of this tutorial, we will show just the add.ctp:
<!-- src/Template/Users/add.ctp -->
<div class="users form">
<?= $this->Form->create($user) ?>
<fieldset>
<legend><?= __('Add User') ?></legend>
<?= $this->Form->input('username') ?>
<?= $this->Form->input('password') ?>
<?= $this->Form->input('role', [
'options' => ['admin' => 'Admin', 'author' => 'Author']
]) ?>
</fieldset>
<?= $this->Form->button(__('Submit')); ?>
<?= $this->Form->end() ?>
</div>
97
There is not much to configure, as we used the conventions for the users table. We just set up the URLs
that will be loaded after the login and logout actions is performed, in our case to /articles/ and /
respectively.
What we did in the beforeFilter() function was to tell the AuthComponent to not require a login for
all index() and view() actions, in every controller. We want our visitors to be able to read and list the
entries without registering in the site.
Now, we need to be able to register new users, save their username and password, and more importantly,
hash their password so it is not stored as plain text in our database. Lets tell the AuthComponent to let
un-authenticated users access the users add function and implement the login and logout action:
// src/Controller/UsersController.php
namespace App\Controller;
use App\Controller\AppController;
use Cake\Event\Event;
class UsersController extends AppController
{
// Other methods..
public function beforeFilter(Event $event)
{
parent::beforeFilter($event);
// Allow users to register and logout.
// You should not add the "login" action to allow list. Doing so would
// cause problems with normal functioning of AuthComponent.
$this->Auth->allow(['add', 'logout']);
}
98
Password hashing is not done yet, we need an Entity class for our User in order to handle its own specific
logic. Create the src/Model/Entity/User.php entity file and add the following:
// src/Model/Entity/User.php
namespace App\Model\Entity;
use Cake\Auth\DefaultPasswordHasher;
use Cake\ORM\Entity;
class User extends Entity
{
// Make all fields mass assignable except for primary key field "id".
protected $_accessible = [
'*' => true,
'id' => false
];
// ...
protected function _setPassword($password)
{
return (new DefaultPasswordHasher)->hash($password);
}
// ...
}
Now every time the password property is assigned to the user it will be hashed using the
DefaultPasswordHasher class. Were just missing a template view file for the login function. Open
up your src/Template/Users/login.ctp file and add the following lines:
99
You can now register a new user by accessing the /users/add URL and log in with the newly created
credentials by going to /users/login URL. Also, try to access any other URL that was not explicitly
allowed such as /articles/add, you will see that the application automatically redirects you to the login
page.
And thats it! It looks too simple to be true. Lets go back a bit to explain what happened. The
beforeFilter() function is telling the AuthComponent to not require a login for the add() action in addition to the index() and view() actions that were already allowed in the AppControllers
beforeFilter() function.
The login() action calls the $this->Auth->identify() function in the AuthComponent, and it
works without any further config because we are following conventions as mentioned earlier. That is, having
a Users table with a username and a password column, and use a form posted to a controller with the user
data. This function returns whether the login was successful or not, and in the case it succeeds, then we
redirect the user to the configured redirection URL that we used when adding the AuthComponent to our
application.
The logout works by just accessing the /users/logout URL and will redirect the user to the configured
logoutUrl formerly described. This URL is the result of the AuthComponent::logout() function on
success.
Also, a small change in the ArticlesController is required to store the currently logged in user as a reference
for the created article:
// src/Controller/ArticlesController.php
public function add()
{
$article = $this->Articles->newEntity();
100
if ($this->request->is('post')) {
$article = $this->Articles->patchEntity($article, $this->request->
data);
// Added this line
$article->user_id = $this->Auth->user('id');
// You could also do the following
//$newData = ['user_id' => $this->Auth->user('id')];
//$article = $this->Articles->patchEntity($article, $newData);
if ($this->Articles->save($article)) {
$this->Flash->success(__('Your article has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('Unable to add your article.'));
}
$this->set('article', $article);
// Just added the categories list to be able to choose
// one category for an article
$categories = $this->Articles->Categories->find('treeList');
$this->set(compact('categories'));
}
The user() function provided by the component returns any column from the currently logged in user.
We used this method to add the data into the request info that is saved.
Lets secure our app to prevent some authors from editing or deleting the others articles. Basic rules for
our app are that admin users can access every URL, while normal users (the author role) can only access the
permitted actions. Again, open the AppController class and add a few more options to the Auth config:
// src/Controller/AppController.php
public function initialize()
{
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authorize' => ['Controller'], // Added this line
'loginRedirect' => [
'controller' => 'Articles',
'action' => 'index'
],
'logoutRedirect' => [
'controller' => 'Pages',
'action' => 'display',
'home'
]
]);
}
public function isAuthorized($user)
{
// Admin can access every action
if (isset($user['role']) && $user['role'] === 'admin') {
return true;
101
}
// Default deny
return false;
}
We just created a simple authorization mechanism. Users with the admin role will be able to access any
URL in the site when logged-in. All other users those with the author role will have the same access
as users who arent logged-in.
This is not exactly what we want. We need to supply more rules to our isAuthorized() method. However instead of doing it in AppController, well delegate supplying those extra rules to each individual controller. The rules were going to add to ArticlesController should permit authors to create articles but prevent
authors from editing articles they do not own. Add the following content to your ArticlesController.php:
// src/Controller/ArticlesController.php
public function isAuthorized($user)
{
// All registered users can add articles
if ($this->request->action === 'add') {
return true;
}
// The owner of an article can edit and delete it
if (in_array($this->request->action, ['edit', 'delete'])) {
$articleId = (int)$this->request->params['pass'][0];
if ($this->Articles->isOwnedBy($articleId, $user['id'])) {
return true;
}
}
return parent::isAuthorized($user);
}
Were now overriding the AppControllers isAuthorized() call and internally checking if the parent
class is already authorizing the user. If he isnt, then just allow him to access the add action, and conditionally
access edit and delete. One final thing has not been implemented. To tell whether or not the user is authorized
to edit the article, were calling a isOwnedBy() function in the Articles table. Lets then implement that
function:
// src/Model/Table/ArticlesTable.php
public function isOwnedBy($articleId, $userId)
{
return $this->exists(['id' => $articleId, 'user_id' => $userId]);
}
This concludes our simple authentication and authorization tutorial. For securing the UsersController you
can follow the same technique we did for ArticlesController. You could also be more creative and code
something more general in AppController based on your own rules.
102
Should you need more control, we suggest you read the complete Auth guide in the Authentication section
where you will find more about configuring the component, creating custom Authorization classes, and
much more.
Suggested Follow-up Reading
1. Code Generation with Bake Generating basic CRUD code
2. Authentication: User registration and login
103
104
CHAPTER 5
Contributing
There are a number of ways you can contribute to CakePHP. The following sections cover the various ways
you can contribute to CakePHP:
Documentation
Contributing to the documentation is simple. The files are hosted on https://github.com/cakephp/docs. Feel
free to fork the repo, add your changes/improvements/translations and give back by issuing a pull request.
You can even edit the docs online with GitHub, without ever downloading the files the Improve this Doc
button on any given page will direct you to GitHubs online editor for that page.
CakePHP documentation is continuously integrated52 , so you can check the status of the various builds53 on
the Jenkins server at any time.
Translations
Email the docs team (docs at cakephp dot org) or hop on IRC (#cakephp on freenode) to discuss any translation efforts you would like to participate in.
New Translation Language
We want to provide translations that are as complete as possible. However, there may be times where
a translation file is not up-to-date. You should always consider the English version as the authoritative
version.
If your language is not in the current languages, please contact us through Github and we will consider
creating a skeleton folder for it. The following sections are the first one you should consider translating as
these files dont change often:
52
53
http://en.wikipedia.org/wiki/Continuous_integration
http://ci.cakephp.org
105
index.rst
intro.rst
quickstart.rst
installation.rst
/intro folder
/tutorials-and-examples folder
Reminder for Docs Administrators
The structure of all language folders should mirror the English folder structure. If the structure changes for
the English version, we should apply those changes in the other languages.
For example, if a new English file is created in en/file.rst, we should:
Add the file in all other languages : fr/file.rst, zh/file.rst, ...
Delete the content, but keeping the title, meta information and eventual toc-tree elements.
The following note will be added while nobody has translated the file:
File Title
##########
.. note::
The documentation is not currently supported in XX language for this
page.
Please feel free to send us a pull request on
`Github <https://github.com/cakephp/docs>`_ or use the **Improve
This Doc**
button to directly propose your changes.
You can refer to the English version in the select top menu to have
information about this page's topic.
// If toc-tree elements are in the English version
.. toctree::
:maxdepth: 1
one-toc-file
other-toc-file
.. meta::
:title lang=xx: File Title
:keywords lang=xx: title, description,...
106
Chapter 5. Contributing
Translator tips
Browse and edit in the language you want the content to be translated to - otherwise you wont see
what has already been translated.
Feel free to dive right in if your chosen language already exists on the book.
Use Informal Form54 .
Translate both the content and the title at the same time.
Do compare to the English content before submitting a correction (if you correct something, but dont
integrate an upstream change your submission wont be accepted).
If you need to write an English term, wrap it in <em> tags. E.g. asdf asdf Controller asdf or asdf
asdf Kontroller (Controller) asfd as appropriate.
Do not submit partial translations.
Do not edit a section with a pending change.
Do not use HTML entities55 for accented characters, the book uses UTF-8.
Do not significantly change the markup (HTML) or add new content.
If the original content is missing some info, submit an edit for that first.
http://en.wikipedia.org/wiki/Register_(linguistics)
http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references
http://en.wikipedia.org/wiki/ReStructuredText
Documentation
107
``true``,
``id``,
``PagesController``,
If asterisks or backquotes appear in running text and could be confused with inline markup delimiters, they
have to be escaped with a backslash.
Inline markup has a few restrictions:
It may not be nested.
Content may not start or end with whitespace: * text* is wrong.
Content must be separated from surrounding text by non-word characters. Use a backslash escaped
space to work around that: onelong\ *bolded*\ word.
Lists
List markup is very similar to markdown. Unordered lists are indicated by starting a line with a single
asterisk and a space. Numbered lists can be created with either numerals, or # for auto numbering:
* This is a bullet
* So is this. But this line
has two lines.
1. First line
2. Second line
108
Chapter 5. Contributing
#. Automatic numbering
#. Will save you some time.
Indented lists can also be created, by indenting sections and separating them with an empty line:
* First line
* Second line
* Going deeper
* Whoah
* Back to the first level.
Terms cannot be more than one line, but definitions can be multi-line and all lines should be indented
consistently.
Links
There are several kinds of links, each with their own uses.
External Links
Links to external documents can be done with the following:
`External Link to php.net <http://php.net>`_
The resulting link would look like this: External Link to php.net57
Links to Other Pages
:doc:
Other pages in the documentation can be linked to using the :doc: role. You can link to the specified
document using either an absolute or relative path reference. You should omit the .rst extension.
For example, if the reference :doc:`form` appears in the document core-helpers/html,
then the link references core-helpers/form. If the reference was :doc:`/core-helpers`,
it would always reference /core-helpers regardless of where it was used.
57
http://php.net
Documentation
109
Elsewhere you could reference the above section using :ref:`label-name`. The links text
would be the title that the link preceded. You can also provide custom link text using :ref:`Link
text <label-name>`.
Prevent Sphinx to Output Warnings
Sphinx will output warnings if a file is not referenced in a toc-tree. Its a great way to ensure that all files
have a link directed to them, but sometimes, you dont need to insert a link for a file, eg. for our epubcontents and pdf-contents files. In those cases, you can add :orphan: at the top of the file, to suppress
warnings that the file is not in the toc-tree.
Describing Classes and their Contents
The CakePHP documentation uses the phpdomain58 to provide custom directives for describing PHP objects
and constructs. Using these directives and roles is required to give proper indexing and cross referencing
features.
Describing Classes and Constructs
Each directive populates the index, and or the namespace index.
.. php:global:: name
This directive declares a new PHP global variable.
.. php:function:: name(signature)
Defines a new global function outside of a class.
.. php:const:: name
This directive declares a new PHP constant, you can also use it nested inside a class directive to create
class constants.
58
110
http://pypi.python.org/pypi/sphinxcontrib-phpdomain
Chapter 5. Contributing
.. php:exception:: name
This directive declares a new Exception in the current namespace. The signature can include constructor arguments.
.. php:class:: name
Describes a class. Methods, attributes, and constants belonging to the class should be inside this
directives body:
.. php:class:: MyClass
Class description
.. php:method:: method($argument)
Method description
Attributes, methods and constants dont need to be nested. They can also just follow the class declaration:
.. php:class:: MyClass
Text about the class
.. php:method:: methodName()
Text about the method
See also:
php:method, php:attr, php:const
.. php:method:: name(signature)
Describe a class method, its arguments, return value, and exceptions:
.. php:method:: instanceMethod($one, $two)
:param string $one: The first parameter.
:param string $two: The second parameter.
:returns: An array of stuff.
:throws: InvalidArgumentException
This is an instance method.
.. php:staticmethod:: ClassName::methodName(signature)
Describe a static method, its arguments, return value and exceptions, see php:method for options.
.. php:attr:: name
Describe an property/attribute on a class.
Documentation
111
Cross Referencing
The following roles refer to PHP objects and links are generated if a matching directive is found:
:php:func:
Reference a PHP function.
:php:global:
Reference a global variable whose name has $ prefix.
:php:const:
Reference either a global constant, or a class constant. Class constants should be preceded by the
owning class:
DateTime has an :php:const:`DateTime::ATOM` constant.
:php:class:
Reference a class by name:
:php:class:`ClassName`
:php:meth:
Reference a method of a class. This role supports both kinds of methods:
:php:meth:`DateTime::setDate`
:php:meth:`Classname::staticMethod`
:php:attr:
Reference a property on an object:
:php:attr:`ClassName::$propertyName`
:php:exc:
Reference an exception.
112
Chapter 5. Contributing
Source Code
Literal code blocks are created by ending a paragraph with ::. The literal block must be indented, and like
all paragraphs be separated by single lines:
This is a paragraph::
while ($i--) {
doStuff()
}
This is regular text again.
Literal text is not modified or formatted, save that one level of indentation is removed.
Notes and Warnings
There are often times when you want to inform the reader of an important tip, special note or a potential
hazard. Admonitions in sphinx are used for just that. There are fives kinds of admonitions.
.. tip:: Tips are used to document or re-iterate interesting or important information. The content of the directive should be written in complete sentences and include all appropriate punctuation.
.. note:: Notes are used to document an especially important piece of information. The content
of the directive should be written in complete sentences and include all appropriate punctuation.
.. warning:: Warnings are used to document potential stumbling blocks, or information pertaining to security. The content of the directive should be written in complete sentences and include
all appropriate punctuation.
.. versionadded:: X.Y.Z Version added admonitions are used to display notes specific
to new features added at a specific version, X.Y.Z being the version on which the said feature was
added.
.. deprecated:: X.Y.Z As opposed to version added admonitions, deprecated admonition are used to notify of a deprecated feature, X.Y.Z being the version on which the said feature
was deprecated.
All admonitions are made the same:
.. note::
Indented and preceded and followed by a blank line. Just like a
paragraph.
This text is not part of the note.
Samples
Documentation
113
Tickets
Getting feedback and help from the community in the form of tickets is an extremely important part of the
CakePHP development process. All of CakePHPs tickets are hosted on GitHub59 .
Reporting Bugs
Well written bug reports are very helpful. There are a few steps to help create the best bug report possible:
Do: Please search60 for a similar existing ticket, and ensure someone hasnt already reported your
issue, or that it hasnt already been fixed in the repository.
Do: Please include detailed instructions on how to reproduce the bug. This could be in the form of
a test-case or a snippet of code that demonstrates the issue. Not having a way to reproduce an issue
means its less likely to get fixed.
Do: Please give as many details as possible about your environment: (OS, PHP version, CakePHP
version).
Dont: Please dont use the ticket system to ask support questions. The #cakephp IRC channel on
Freenode61 has many developers available to help answer your questions. Also have a look at Stack
Overflow62 .
114
https://github.com/cakephp/cakephp/issues
https://github.com/cakephp/cakephp/search?q=it+is+broken&ref=cmdform&type=Issues
https://webchat.freenode.net
https://stackoverflow.com/questions/tagged/cakephp
Chapter 5. Contributing
For each report, we try to first confirm the vulnerability. Once confirmed, the CakePHP team will take the
following actions:
Acknowledge to the reporter that weve received the issue, and are working on a fix. We ask that the
reporter keep the issue confidential until we announce it.
Get a fix/patch prepared.
Prepare a post describing the vulnerability, and the possible exploits.
Release new versions of all affected versions.
Prominently feature the problem in the release announcement.
Code
Patches and pull requests are a great way to contribute code back to CakePHP. Pull requests can be created
in GitHub, and are preferred over patch files in ticket comments.
Initial Setup
Before working on patches for CakePHP, its a good idea to get your environment setup. Youll need the
following software:
Git
PHP 5.5.9 or greater
PHPUnit 3.7.0 or greater
Set up your user information with your name/handle and working email address:
git config --global user.name 'Bob Barker'
git config --global user.email 'bob.barker@example.com'
Note: If you are new to Git, we highly recommend you to read the excellent and free ProGit63 book.
Get a clone of the CakePHP source code from GitHub:
If you dont have a GitHub64 account, create one.
Fork the CakePHP repository65 by clicking the Fork button.
After your fork is made, clone your fork to your local machine:
git clone git@github.com:YOURNAME/cakephp.git
63
64
65
http://git-scm.com/book/
http://github.com
http://github.com/cakephp/cakephp
Code
115
Add the original CakePHP repository as a remote repository. Youll use this later to fetch changes from the
CakePHP repository. This will let you stay up to date with CakePHP:
cd cakephp
git remote add upstream git://github.com/cakephp/cakephp.git
Now that you have CakePHP setup you should be able to define a $test database connection, and run all
the tests.
Working on a Patch
Each time you want to work on a bug, feature or enhancement create a topic branch.
The branch you create should be based on the version that your fix/enhancement is for. For example if you
are fixing a bug in 3.x you would want to use the master branch as the base for your branch. If your
change is a bug fix for the 2.x release series, you should use the 2.x branch. This makes merging your
changes in later much simpler, as Github does not let you edit the target branch:
# fixing a bug on 3.x
git fetch upstream
git checkout -b ticket-1234 upstream/master
# fixing a bug on 2.x
git fetch upstream
git checkout -b ticket-1234 upstream/2.x
Tip: Use a descriptive name for your branch, referencing the ticket or feature name is a good convention.
e.g. ticket-1234, feature-awesome
The above will create a local branch based on the upstream (CakePHP) 2.x branch. Work on your fix, and
make as many commits as you need; but keep in mind the following:
Follow the Coding Standards.
Add a test case to show the bug is fixed, or that the new feature works.
Keep your commits logical, and write good clear and concise commit messages.
116
Chapter 5. Contributing
This will fetch + merge in any changes that have happened in CakePHP since you started. It will then rebase
- or replay your changes on top of the current code. You might encounter a conflict during the rebase. If
the rebase quits early you can see which files are conflicted/un-merged with git status. Resolve each
conflict, and then continue the rebase:
git add <filename> # do this for each conflicted file.
git rebase --continue
Check that all your tests continue to pass. Then push your branch to your fork:
git push origin <branch-name>
If youve rebased after pushing your branch, youll need to use force push:
git push --force origin <branch-name>
Once your branch is on GitHub, you can submit a pull request on GitHub.
Choosing Where Your Changes will be Merged Into
When making pull requests you should make sure you select the correct base branch, as you cannot edit it
once the pull request is created.
If your change is a bugfix and doesnt introduce new functionality and only corrects existing behavior
that is present in the current release. Then choose master as your merge target.
If your change is a new feature or an addition to the framework, then you should choose the branch
with the next version number. For example if the current stable release is 3.2.10, the branch accepting new features will be 3.next.
If your change is a breaks existing functionality, or APIs then youll have to choose then next major
release. For example, if the current release is 3.2.2 then the next time existing behavior can be
broken will be in 4.x so you should target that branch.
Note: Remember that all code you contribute to CakePHP will be licensed under the MIT License, and the
Cake Software Foundation66 will become the owner of any contributed code. Contributors should follow
the CakePHP Community Guidelines67 .
All bug fixes merged into a maintenance branch will also be merged into upcoming releases periodically by
the core team.
66
67
http://cakefoundation.org/pages/about
http://community.cakephp.org/guidelines
Code
117
Coding Standards
CakePHP developers will use the PSR-2 coding style guide68 in addition to the following rules as coding
standards.
It is recommended that others developing CakeIngredients follow the same standards.
You can use the CakePHP Code Sniffer69 to check that your code follows required standards.
IDE Setup
Please make sure your IDE is set up to trim right on whitespaces. There should be no trailing spaces per
line.
Most modern IDEs also support an .editorconfig file. The CakePHP app skeleton ships with it by
default. It already contains best practise defaults.
Indentation
Four spaces will be used for indentation.
So, indentation should look like this:
// base level
// level 1
// level 2
// level 1
// base level
Or:
$booleanVariable = true;
$stringVariable = 'moose';
if ($booleanVariable) {
echo 'Boolean value is true';
if ($stringVariable === 'moose') {
echo 'We have encountered a moose';
}
}
In cases where youre using a multi-line function call use the following guidelines:
68
69
118
http://www.php-fig.org/psr/psr-2/
https://github.com/cakephp/cakephp-codesniffer
Chapter 5. Contributing
Opening parenthesis of a multi-line function call must be the last content on the line.
Only one argument is allowed per line in a multi-line function call.
Closing parenthesis of a multi-line function call must be on a line by itself.
As an example, instead of using the following formatting:
$matches = array_intersect_key($this->_listeners,
array_flip(preg_grep($matchPattern,
array_keys($this->_listeners), 0)));
Line Length
It is recommended to keep lines at approximately 100 characters long for better code readability. Lines must
not be longer than 120 characters.
In short:
100 characters is the soft limit.
120 characters is the hard limit.
Control Structures
Control structures are for example if, for, foreach, while, switch etc. Below, an example with if:
if ((expr_1) || (expr_2)) {
// action_1;
} elseif (!(expr_3) && (expr_4)) {
// action_2;
} else {
// default_action;
}
In the control structures there should be 1 (one) space before the first parenthesis and 1 (one) space
between the last parenthesis and the opening bracket.
Always use curly brackets in control structures, even if they are not needed. They increase the readability of the code, and they give you fewer logical errors.
Coding Standards
119
Opening curly brackets should be placed on the same line as the control structure. Closing curly
brackets should be placed on new lines, and they should have same indentation level as the control
structure. The statement included in curly brackets should begin on a new line, and code contained
within it should gain a new level of indentation.
Inline assignments should not be used inside of the control structures.
// wrong = no brackets, badly placed statement
if (expr) statement;
// wrong = no brackets
if (expr)
statement;
// good
if (expr) {
statement;
}
// wrong = inline assignment
if ($variable = Class::function()) {
statement;
}
// good
$variable = Class::function();
if ($variable) {
statement;
}
Ternary Operator
Ternary operators are permissible when the entire ternary operation fits on one line. Longer ternaries should
be split into if else statements. Ternary operators should not ever be nested. Optionally parentheses can
be used around the condition check of the ternary for clarity:
// Good, simple and readable
$variable = isset($options['variable']) ? $options['variable'] : true;
// Nested ternaries are bad
$variable = isset($options['variable']) ? isset($options['othervar']) ? true :
false : false;
Template Files
In template files (.ctp files) developers should use keyword control structures. Keyword control structures
are easier to read in complex template files. Control structures can either be contained in a larger PHP block,
or in separate PHP tags:
120
Chapter 5. Contributing
<?php
if ($isAdmin):
echo '<p>You are the admin user.</p>';
endif;
?>
<p>The following is also acceptable:</p>
<?php if ($isAdmin): ?>
<p>You are the admin user.</p>
<?php endif; ?>
Comparison
Always try to be as strict as possible. If a non-strict test is deliberate it might be wise to comment it as such
to avoid confusing it for a mistake.
For testing if a variable is null, it is recommended to use a strict check:
if ($value === null) {
// ...
}
Function Calls
Functions should be called without space between functions name and starting parenthesis. There should
be one space between every parameter of a function call:
$var = foo($bar, $bar2, $bar3);
As you can see above there should be one space on both sides of equals sign (=).
Method Definition
Example of a method definition:
Coding Standards
121
Parameters with a default value, should be placed last in function definition. Try to make your functions
return something, at least true or false, so it can be determined whether the function call was successful:
public function connection($dns, $persistent = false)
{
if (is_array($dns)) {
$dnsInfo = $dns;
} else {
$dnsInfo = BD::parseDNS($dns);
}
if (!($dnsInfo) || !($dnsInfo['phpType'])) {
return $this->addError();
}
return true;
}
Here $table must be an instance of \Cake\ORM\Table, $array must be an array and $callback
must be of type callable (a valid callback).
Note that if you want to allow $array to be also an instance of \ArrayObject you should not typehint
as array accepts only the primitive type:
122
Chapter 5. Contributing
/**
* Some method description.
*
* @param array|\ArrayObject $array Some array value.
*/
public function foo($array)
{
}
Method Chaining
Method chaining should have multiple methods spread across separate lines, and indented with four spaces:
$email->from('foo@example.com')
->to('bar@example.com')
->subject('A great message')
->send();
Commenting Code
All comments should be written in English, and should in a clear way describe the commented block of
code.
Comments can include the following phpDocumentor71 tags:
@author72
@copyright73
@deprecated74 Using the @version <vector> <description> format, where version
and description are mandatory.
@example75
70
71
72
73
74
75
http://www.php-fig.org/psr/psr-2/
http://phpdoc.org
http://phpdoc.org/docs/latest/references/phpdoc/tags/author.html
http://phpdoc.org/docs/latest/references/phpdoc/tags/copyright.html
http://phpdoc.org/docs/latest/references/phpdoc/tags/deprecated.html
http://phpdoc.org/docs/latest/references/phpdoc/tags/example.html
Coding Standards
123
@ignore76
@internal77
@link78
@see79
@since80
@version81
PhpDoc tags are very much like JavaDoc tags in Java. Tags are only processed if they are the first thing in a
DocBlock line, for example:
/**
* Tag example.
*
* @author this tag is parsed, but this @version is ignored
* @version 1.0 this tag is also parsed
*/
/**
* Example of inline phpDoc tags.
*
* This function works hard with foo() to rule the world.
*
* @return void
*/
function bar()
{
}
/**
* Foo function.
*
* @return void
*/
function foo()
{
}
Comment blocks, with the exception of the first block in a file, should always be preceded by a newline.
Variable Types
Variable types for use in DocBlocks:
76
77
78
79
80
81
124
http://phpdoc.org/docs/latest/references/phpdoc/tags/ignore.html
http://phpdoc.org/docs/latest/references/phpdoc/tags/internal.html
http://phpdoc.org/docs/latest/references/phpdoc/tags/link.html
http://phpdoc.org/docs/latest/references/phpdoc/tags/see.html
http://phpdoc.org/docs/latest/references/phpdoc/tags/since.html
http://phpdoc.org/docs/latest/references/phpdoc/tags/version.html
Chapter 5. Contributing
Type Description
mixed A variable with undefined (or multiple) type.
int Integer type variable (whole number).
float Float type (point number).
bool Logical type (true or false).
string String type (any value in or ).
null Null type. Usually used in conjunction with another type.
array Array type.
object Object type. A specific class name should be used if possible.
resource Resource type (returned by for example mysql_connect()). Remember that when you specify the
type as mixed, you should indicate whether it is unknown, or what the possible types are.
callable Callable function.
You can also combine types using the pipe char:
int|bool
For more than two types it is usually best to just use mixed.
When returning the object itself, e.g. for chaining, one should use $this instead:
/**
* Foo function.
*
* @return $this
*/
public function foo()
{
return $this;
}
Including Files
include, require, include_once and require_once do not have parentheses:
// wrong = parentheses
require_once('ClassFileName.php');
require_once ($class);
// good = no parentheses
require_once 'ClassFileName.php';
require_once $class;
When including files with classes or libraries, use only and always the require_once82 function.
82
http://php.net/require_once
Coding Standards
125
PHP Tags
Always use long tags (<?php ?>) instead of short tags (<?
template files (.ctp) where appropriate.
Short Echo
The short echo should be used in template files in place of <?php echo. It should be immediately followed
by a single space, the variable or function value to echo, a single space, and the php closing tag:
// wrong = semicolon, no spaces
<td><?=$name;?></td>
// good = spaces, no semicolon
<td><?= $name ?></td>
As of PHP 5.4 the short echo tag (<?=) is no longer to be consider a short tag is always available regardless
of the short_open_tag ini directive.
Naming Convention
Functions
Write all functions in camelBack:
function longFunctionName()
{
}
Classes
Class names should be written in CamelCase, for example:
class ExampleClass
{
}
Variables
Variable names should be as descriptive as possible, but also as short as possible. All variables should start
with a lowercase letter, and should be written in camelBack in case of multiple words. Variables referencing
objects should in some way associate to the class the variable is an object of. Example:
$user = 'John';
$users = ['John', 'Hans', 'Arne'];
$dispatcher = new Dispatcher();
126
Chapter 5. Contributing
Member Visibility
Use PHP5s private and protected keywords for methods and variables. Additionally, non-public method or
variable names start with a single underscore (_). Example:
class A
{
protected $_iAmAProtectedVariable;
protected function _iAmAProtectedMethod()
{
/* ... */
}
private $_iAmAPrivateVariable;
private function _iAmAPrivateMethod()
{
/* ... */
}
}
Example Addresses
For all example URL and mail addresses use example.com, example.org and example.net, for example:
Email: someone@example.com
WWW: http://www.example.com
FTP: ftp://ftp.example.com
The example.com domain name has been reserved for this (see RFC 260683 ) and is recommended for use
in documentation or as examples.
Files
File names which do not contain classes should be lowercased and underscored, for example:
long_file_name.php
Casting
For casting we use:
83
https://tools.ietf.org/html/rfc2606.html
Coding Standards
127
Type Description
(bool) Cast to boolean.
(int) Cast to integer.
(float) Cast to float.
(string) Cast to string.
(array) Cast to array.
(object) Cast to object.
Please use (int)$var instead of intval($var) and (float)$var instead of floatval($var)
when applicable.
Constants
Constants should be defined in capital letters:
define('CONSTANT', 1);
If a constant name consists of multiple words, they should be separated by an underscore character, for
example:
define('LONG_NAMED_CONSTANT', 2);
When dealing with defined properties you should favour null checks over empty()/isset() checks:
128
Chapter 5. Contributing
class Thing
{
private $property; // Defined
public function readProperty()
{
// Not recommended as the property is defined in the class
if (!isset($this->property)) {
// ...
}
// Recommended
if ($this->property === null) {
}
}
}
When working with arrays, it is better to merge in defaults over using empty() checks. By merging in
defaults, you can ensure that required keys are defined:
function doWork(array $array)
{
// Merge defaults to remove need for empty checks.
$array += [
'key' => null,
];
// Not recommended, the key is already set
if (isset($array['key'])) {
// ...
}
// Recommended
if ($array['key'] !== null) {
// ...
}
}
http://semver.org/
129
Note: CakePHP started following semantic versioning in 2.0.0. These rules do not apply to 1.x.
To clarify what changes you can expect in each release tier we have more detailed information for developers
using CakePHP, and for developers working on CakePHP that helps set expectations of what can be done in
minor releases. Major releases can have as many breaking changes as required.
Migration Guides
For each major and minor release, the CakePHP team will provide a migration guide. These guides explain
the new features and any breaking changes that are in each release. They can be found in the Appendices
section of the cookbook.
Using CakePHP
If you are building your application with CakePHP, the following guidelines explain the stability you can
expect.
Interfaces
Outside of major releases, interfaces provided by CakePHP will not have any existing methods changed.
New methods may be added, but no existing methods will be changed.
Classes
Classes provided by CakePHP can be constructed and have their public methods and properties used by
application code and outside of major releases backwards compatibility is ensured.
Note: Some classes in CakePHP are marked with the @internal API doc tag. These classes are not
stable and do not have any backwards compatibility promises.
In minor releases, new methods may be added to classes, and existing methods may have new arguments
added. Any new arguments will have default values, but if youve overridden methods with a differing
signature you may see fatal errors. Methods that have new arguments added will be documented in the
migration guide for that release.
The following table outlines several use cases and what compatibility you can expect from CakePHP:
130
Chapter 5. Contributing
If you...
Typehint against the class
Create a new instance
Extend the class
Access a public property
Call a public method
Extend a class and...
Override a public property
Access a protected property
Override a protected property
Override a protected method
Call a protected method
Add a public property
Add a public method
Add an argument to an overridden method
Add a default argument value to an existing method argument
Backwards compatibility?
Yes
Yes
Yes
Yes
Yes
Yes
No 1
No 1
No 1
No 1
No
No
No 1
Yes
Working on CakePHP
If you are helping make CakePHP even better please keep the following guidelines in mind when
adding/changing functionality:
In a minor release you can:
1
Your code may be broken by minor releases. Check the migration guide for details.
131
No
No
No
No
No
Yes 2
Yes
No
Yes
Yes 3
Yes
No
Yes
Yes
Yes 3
No
Yes 2
Yes
No
No
You can change a class/method name as long as the old name remains available. This is generally avoided unless renaming
has significant benefit.
3
Avoid whenever possible. Any removals need to be documented in the migration guide.
132
Chapter 5. Contributing
CHAPTER 6
Installation
CakePHP is simple and easy to install. The minimum requirements are a web server and a copy of CakePHP,
thats it! While this chapter focuses primarily on setting up on Apache (because its simple to install and
setup), CakePHP will run on a variety of web servers such as nginx, LightHTTPD, or Microsoft IIS.
Requirements
HTTP Server. For example: Apache. Having mod_rewrite is preferred, but by no means required.
PHP 5.5.9 or greater (including PHP 7).
mbstring PHP extension
intl PHP extension
Note: In both XAMPP and WAMP, the mbstring extension is working by default.
In XAMPP, intl extension is included but you have to uncomment extension=php_intl.dll in
php.ini and restart the server through the XAMPP Control Panel.
In WAMP, the intl extension is activated by default but not working. To make it work you have to go to
php folder (by default) C:\wamp\bin\php\php{version}, copy all the files that looks like icu*.dll and paste
them into the apache bin directory C:\wamp\bin\apache\apache{version}\bin. Then restart all services
and it should be OK.
While a database engine isnt required, we imagine that most applications will utilize one. CakePHP supports a variety of database storage engines:
MySQL (5.1.10 or greater)
PostgreSQL
Microsoft SQL Server (2008 or higher)
SQLite 3
133
Note: All built-in drivers require PDO. You should make sure you have the correct PDO extensions installed.
Installing CakePHP
Before starting you should make sure that you have got an up to date PHP version:
php -v
You should at least have got installed PHP 5.5.9 (CLI) or higher. Your webservers PHP version must also
be of 5.5.9 or higher, and should best be the same version your command line interface (CLI) PHP version
is of.
Installing Composer
CakePHP uses Composer85 , a dependency management tool, as the officially supported method for installation.
Installing Composer on Linux and Mac OS X
1. Run the installer script as described in the official Composer documentation86 and follow the
instructions to install Composer.
2. Execute the following command to move the composer.phar to a directory that is in your path:
mv composer.phar /usr/local/bin/composer
134
http://getcomposer.org
https://getcomposer.org/download/
https://github.com/composer/windows-setup/releases/
https://github.com/composer/windows-setup
Chapter 6. Installation
Once Composer finishes downloading the application skeleton and the core CakePHP library, you should
have a functioning CakePHP application installed via Composer. Be sure to keep the composer.json and
composer.lock files with the rest of your source code.
You can now visit the path to where you installed your CakePHP application and see the default home page.
To change the content of this page, edit src/Template/Pages/home.ctp.
Although composer is the recommended installation method, there are pre-installed downloads available on
Github89 . Those downloads contain the app skeleton with all vendor packages installed. Also it includes the
composer.phar so you have everything you need for further use.
Each time you run php composer.phar update you will receive the latest stable releases when using
the default version constraint ~3.2. Only bugfix and minor version releases of 3.x will be used when
updating.
If you want to keep current with the latest unreleased changes in CakePHP designate dev-master as the
package version in your applications composer.json:
"require": {
"cakephp/cakephp": "dev-master"
}
Be aware that is not recommended, as your application can break when the next major version is being
released. Additionally composer does not cache development branches, so it slows down consecutive composer installs/updates.
Permissions
CakePHP uses the tmp directory for a number of different operations. Model descriptions, cached views,
and session information are just a few examples. The logs directory is used to write log files by the default
FileLog engine.
As such, make sure the directories logs, tmp and all its subdirectories in your CakePHP installation are
writable by the web server user. Composers installation process makes tmp and its subfolders globally
writeable to get things up and running quickly but you can update the permissions for better security and
keep them writable only for the web server user.
89
https://github.com/cakephp/cakephp/tags
Permissions
135
One common issue is that logs and tmp directories and subdirectories must be writable both by the web
server and the command line user. On a UNIX system, if your web server user is different from your
command line user, you can run the following commands from your application directory just once in your
project to ensure that permissions will be setup properly:
HTTPDUSER=`ps aux | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' |
grep -v root | head -1 | cut -d\
-f1`
setfacl -R -m u:${HTTPDUSER}:rwx tmp
setfacl -R -d -m u:${HTTPDUSER}:rwx tmp
setfacl -R -m u:${HTTPDUSER}:rwx logs
setfacl -R -d -m u:${HTTPDUSER}:rwx logs
Development Server
A development installation is the fastest method to setup CakePHP. In this example, we will be using CakePHPs console to run PHPs built-in web server which will make your application available at
http://host:port. From the app directory, execute:
bin/cake server
By default, without any arguments provided, this will serve your application at http://localhost:8765/.
If you have something conflicting with localhost or port 8765, you can tell the CakePHP console to run the
web server on a specific host and/or port utilizing the following arguments:
bin/cake server -H 192.168.13.37 -p 5673
Production
A production installation is a more flexible way to setup CakePHP. Using this method allows an entire
domain to act as a single CakePHP application. This example will help you install CakePHP anywhere on
your filesystem and make it available at http://www.example.com. Note that this installation may require
the rights to change the DocumentRoot on Apache webservers.
136
Chapter 6. Installation
After installing your application using one of the methods above into the directory of your choosing - well
assume you chose /cake_install - your production setup will look like this on the file system:
/cake_install/
bin/
config/
logs/
plugins/
src/
tests/
tmp/
vendor/
webroot/ (this directory is set as DocumentRoot)
.gitignore
.htaccess
.travis.yml
composer.json
index.php
phpunit.xml.dist
README.md
Developers using Apache should set the DocumentRoot directive for the domain to:
DocumentRoot /cake_install/webroot
If your web server is configured correctly, you should now find your CakePHP application accessible at
http://www.example.com.
Fire It Up
Alright, lets see CakePHP in action. Depending on which setup you used, you should point your browser
to http://example.com/ or http://localhost:8765/. At this point, youll be presented with CakePHPs default
home, and a message that tells you the status of your current database connection.
Congratulations! You are ready to create your first CakePHP application.
URL Rewriting
Apache
While CakePHP is built to work with mod_rewrite out of the boxand usually doesweve noticed that a
few users struggle with getting everything to play nicely on their systems.
Here are a few things you might try to get it running correctly. First look at your httpd.conf. (Make sure you
are editing the system httpd.conf rather than a user- or site-specific httpd.conf.)
These files can vary between different distributions and Apache versions. You may also take a look at
http://wiki.apache.org/httpd/DistrosDefaultLayout for further information.
Fire It Up
137
1. Make sure that an .htaccess override is allowed and that AllowOverride is set to All for the correct
DocumentRoot. You should see something similar to:
# Each directory to which Apache has access can be configured with
respect
# to which services and features are allowed and/or disabled in that
# directory (and its subdirectories).
#
# First, we configure the "default" to be a very restrictive set of
# features.
<Directory />
Options FollowSymLinks
AllowOverride All
#
Order deny,allow
#
Deny from all
</Directory>
2. Make sure you are loading mod_rewrite correctly. You should see something like:
LoadModule rewrite_module libexec/apache2/mod_rewrite.so
In many systems these will be commented out by default, so you may just need to remove the leading
# symbols.
After you make changes, restart Apache to make sure the settings are active.
Verify that your .htaccess files are actually in the right directories. Some operating systems treat files
that start with . as hidden and therefore wont copy them.
3. Make sure your copy of CakePHP comes from the downloads section of the site or our Git repository,
and has been unpacked correctly, by checking for .htaccess files.
CakePHP app directory (will be copied to the top directory of your application by bake):
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule
^$
webroot/
RewriteRule
(.*) webroot/$1
</IfModule>
[L]
[L]
CakePHP webroot directory (will be copied to your applications web root by bake):
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>
If your CakePHP site still has problems with mod_rewrite, you might want to try modifying
settings for Virtual Hosts. On Ubuntu, edit the file /etc/apache2/sites-available/default (location is distribution-dependent). In this file, ensure that AllowOverride None is changed to
AllowOverride All, so you have:
138
Chapter 6. Installation
<Directory />
Options FollowSymLinks
AllowOverride All
</Directory>
<Directory /var/www>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Order Allow,Deny
Allow from all
</Directory>
On Mac OSX, another solution is to use the tool virtualhostx90 to make a Virtual Host to point to your
folder.
For many hosting services (GoDaddy, 1and1), your web server is actually being served from a
user directory that already uses mod_rewrite. If you are installing CakePHP into a user directory (http://example.com/~username/cakephp/), or any other URL structure that already utilizes
mod_rewrite, youll need to add RewriteBase statements to the .htaccess files CakePHP uses (.htaccess, webroot/.htaccess).
This can be added to the same section with the RewriteEngine directive, so for example, your webroot
.htaccess file would look like:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /path/to/app
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>
The details of those changes will depend on your setup, and can include additional things that are not
related to CakePHP. Please refer to Apaches online documentation for more information.
4. (Optional) To improve production setup, you should prevent invalid assets from being parsed by
CakePHP. Modify your webroot .htaccess to something like:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /path/to/app/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !^/(webroot/)?(img|css|js)/(.*)$
RewriteRule ^ index.php [L]
</IfModule>
The above will simply prevent incorrect assets from being sent to index.php and instead display your
web servers 404 page.
Additionally you can create a matching HTML 404 page, or use the default built-in CakePHP 404 by
adding an ErrorDocument directive:
90
http://clickontyler.com/virtualhostx/
URL Rewriting
139
nginx
nginx does not make use of .htaccess files like Apache, so it is necessary to create those rewritten URLs in the site-available configuration.
This is usually found in
/etc/nginx/sites-available/your_virtual_host_conf_file.
Depending upon
your setup, you will have to modify this, but at the very least, you will need PHP running as a FastCGI
instance:
server {
listen
80;
server_name www.example.com;
rewrite ^(.*) http://example.com$1 permanent;
}
server {
listen
80;
server_name example.com;
# root directive should be global
root
/var/www/example.com/public/webroot/;
index index.php;
access_log /var/www/example.com/log/access.log;
error_log /var/www/example.com/log/error.log;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass
127.0.0.1:9000;
fastcgi_index
index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
On some servers (Like Ubuntu 14.04) the above configuration wont work out of the box, and the nginx docs
recommend a different approach anyway (http://nginx.org/en/docs/http/converting_rewrite_rules.html). You
might try the following (youll notice this is also just one server {} block, rather than two, although if you
want example.com to resolve to your CakePHP application in addition to www.example.com consult the
nginx link above):
server {
listen
80;
server_name www.example.com;
rewrite 301 http://www.example.com$request_uri permanent;
140
Chapter 6. Installation
http://www.microsoft.com/web/downloads/platform.aspx
http://www.iis.net/downloads/microsoft/url-rewrite
http://www.microsoft.com/en-us/download/details.aspx?id=5747
http://www.microsoft.com/en-us/download/details.aspx?id=7435
URL Rewriting
141
Once the web.config file is created with the correct IIS-friendly rewrite rules, CakePHPs links, CSS,
JavaScript, and rerouting should work correctly.
142
Chapter 6. Installation
CHAPTER 7
Configuration
While conventions remove the need to configure all of CakePHP, youll still need to configure a few things
like your database credentials.
Additionally, there are optional configuration options that allow you to swap out default values & implementations with ones tailored to your application.
You can also use additional configuration files to provide environment specific overrides. Each file loaded
after app.php can redefine previously declared values allowing you to customize configuration for development or staging environments.
143
General Configuration
Below is a description of the variables and how they affect your CakePHP application.
debug Changes CakePHP debugging output. false = Production mode. No error messages, errors, or
warnings shown. true = Errors and warnings shown.
App.namespace The namespace to find app classes under.
Note: When changing the namespace in your configuration, you will also need to update your
composer.json file to use this namespace as well. Additionally, create a new autoloader by running
php composer.phar dumpautoload.
App.baseUrl Un-comment this definition if you dont plan to use Apaches mod_rewrite with CakePHP.
Dont forget to remove your .htaccess files too.
App.base The base directory the app resides in. If false this will be auto detected. If not false, ensure
your string starts with a / and does NOT end with a /. E.g., /basedir is a valid App.base. Otherwise,
the AuthComponent will not work properly.
App.encoding Define what encoding your application uses. This encoding is used to generate the charset
in the layout, and encode entities. It should match the encoding values specified for your database.
App.webroot The webroot directory.
App.wwwRoot The file path to webroot.
App.fullBaseUrl The fully qualified domain name (including protocol) to your applications root. This
is used when generating absolute URLs. By default this value is generated using the $_SERVER
environment. However, you should define it manually to optimize performance or if you are concerned
about people manipulating the Host header. In a CLI context (from shells) the fullBaseUrl cannot be
read from $_SERVER, as there is no webserver involved. You do need to specify it yourself if you do
need to generate URLs from a shell (e.g. when sending emails).
App.imageBaseUrl Web path to the public images directory under webroot. If you are using a CDN you
should set this value to the CDNs location.
App.cssBaseUrl Web path to the public css directory under webroot. If you are using a CDN you should
set this value to the CDNs location.
App.jsBaseUrl Web path to the public js directory under webroot. If you are using a CDN you should set
this value to the CDNs location.
App.paths Configure paths for non class based resources. Supports the plugins, templates,
locales subkeys, which allow the definition of paths for plugins, view templates and locale files
respectively.
Security.salt A random string used in hashing. This value is also used as the HMAC salt when doing
symetric encryption.
Asset.timestamp Appends a timestamp which is last modified time of the particular file at the end of asset
files URLs (CSS, JavaScript, Image) when using proper helpers. Valid values:
144
Chapter 7. Configuration
Database Configuration
See the Database Configuration for information on configuring your database connections.
Caching Configuration
See the Caching Configuration for information on configuring caching in CakePHP.
Logging Configuration
See the Logging Configuration for information on configuring logging in CakePHP.
Email Configuration
See the Email Configuration for information on configuring email presets in CakePHP.
Session Configuration
See the Session Configuration for information on configuring session handling in CakePHP.
Routing configuration
See the Routes Configuration for more information on configuring routing and creating routes for your
application.
145
"autoload": {
"psr-4": {
"App\\Controller\\": "/path/to/directory/with/controller/folders",
"App\": "src"
}
}
The above would setup paths for both the App and App\Controller namespace. The first key will be
searched, and if that path does not contain the class/file the second key will be searched. You can also map
a single namespace to multiple directories with the following:
"autoload": {
"psr-4": {
"App\": ["src", "/path/to/directory"]
}
}
Paths should end with a directory separator, or they will not work properly.
Inflection Configuration
See the Inflection Configuration docs for more information.
146
Chapter 7. Configuration
Configure Class
class Cake\Core\Configure
CakePHPs Configure class can be used to store and retrieve application or runtime specific values. Be
careful, this class allows you to store anything in it, then use it in any other part of your code: a sure
temptation to break the MVC pattern CakePHP was designed for. The main goal of Configure class is to
keep centralized variables that can be shared between many objects. Remember to try to live by convention
over configuration and you wont end up breaking the MVC structure weve set in place.
You can access Configure from anywhere in your application:
Configure::read('debug');
Note: The dot notation used in the $key parameter can be used to organize your configuration settings
into logical groups.
The above example could also be written in a single call:
Configure::write('Company', [
'name' => 'Pizza, Inc.',
'slogan' => 'Pizza for your body and soul'
]);
You can use Configure::write('debug',$bool) to switch between debug and production modes
on the fly. This is especially handy for JSON interactions where debugging information can cause parsing
problems.
Configure Class
147
Configure::read('Company');
// Yields:
['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul'];
Chapter 7. Configuration
You can have multiple engines attached to Configure, each reading different kinds or sources of configuration
files. You can interact with attached engines using a few other methods on Configure. To check which engine
aliases are attached you can use Configure::configured():
// Get the array of aliases for attached engines.
Configure::configured();
// Check if a specific engine is attached
Configure::configured('default');
static Cake\Core\Configure::drop($name)
You can also remove attached engines. Configure::drop('default') would remove the default
engine alias. Any future attempts to load configuration files with that engine would fail:
Configure::drop('default');
Loaded configuration files merge their data with the existing runtime configuration in Configure. This allows
you to overwrite and add new values into the existing runtime configuration. By setting $merge to true,
values will not ever overwrite the existing configuration.
http://php.net/parse_ini_file
149
Dumps all or some of the data in Configure into a file or storage system supported by a config engine. The
serialization format is decided by the config engine attached as $config. For example, if the default engine
is a Cake\Core\Configure\Engine\PhpConfig, the generated file will be a PHP configuration
file loadable by the Cake\Core\Configure\Engine\PhpConfig
Given that the default engine is an instance of PhpConfig.
my_config.php:
Configure::dump('my_config', 'default');
Configure::dump() can be used to either modify or overwrite configuration files that are readable with
Configure::load()
Stored configuration data is persisted in the named cache configuration. See the Caching documentation for
more information on caching.
When restoring configuration information its important to restore it with the same key, and cache configuration as was used to store it. Restored information is merged on top of the existing runtime configuration.
Chapter 7. Configuration
In your config/bootstrap.php you could attach this engine and use it:
use App\Configure\Engine\XmlConfig;
Configure::config('xml', new XmlConfig());
...
Configure::load('my_xml', 'xml');
The read() method of a config engine, must return an array of the configuration information that the
resource named $key contains.
interface Cake\Core\Configure\ConfigEngineInterface
Defines the interface used by classes that read configuration data and store it in Configure
Cake\Core\Configure\ConfigEngineInterface::read($key)
Parameters
$key (string) The key name or identifier to load.
151
This method should load/parse the configuration data identified by $key and return an array of data
in the file.
Cake\Core\Configure\ConfigEngineInterface::dump($key)
Parameters
$key (string) The identifier to write to.
$data (array) The data to dump.
This method should dump/store the provided configuration data to a key identified by $key.
152
Chapter 7. Configuration
debug = 0
[Security]
salt = its-secret
[App]
namespace = App
The above ini file, would result in the same end configuration data as the PHP example above. Array
structures can be created either through dot separated values, or sections. Sections can contain dot separated
keys for deeper nesting.
Bootstrapping CakePHP
If you have any additional configuration needs, you should add them to your applications config/bootstrap.php file. This file is included before each request, and CLI command.
This file is ideal for a number of common bootstrapping tasks:
Defining convenience functions.
Declaring constants.
Creating cache configurations.
Configuring inflections.
Loading configuration files.
Be careful to maintain the MVC software design pattern when you add things to the bootstrap file: it might
be tempting to place formatting functions there in order to use them in your controllers. As youll see in the
Controllers and Views sections there are better ways you add custom logic to your application.
Bootstrapping CakePHP
153
Environment Variables
Some of the modern cloud providers, like Heroku, let you define environment variables. By defining environment variables, you can configure your CakePHP app as an 12factor app. Following the 12factor app
instructions96 is a good way to create a stateless app, and to ease the deployment of your app. This means for
example, that if you need to change your database, youll just need to modify a DATABASE_URL variable
on your host configuration without the need to change it in your source code.
As you can see in your app.php, the following variables are concerned:
DEBUG (0 or 1)
APP_ENCODING (ie UTF-8)
APP_DEFAULT_LOCALE (ie en_US)
SECURITY_SALT
96
97
154
http://12factor.net/
https://github.com/josegonzalez/php-dotenv
Chapter 7. Configuration
CHAPTER 8
Routing
class Cake\Routing\Router
Routing provides you tools that map URLs to controller actions. By defining routes, you can separate how
your application is implemented from how its URLs are structured.
Routing in CakePHP also encompasses the idea of reverse routing, where an array of parameters can be
transformed into a URL string. By using reverse routing, you can re-factor your applications URL structure
without having to update all your code.
Quick Tour
This section will teach you by example the most common uses of the CakePHP Router. Typically you want
to display something as a landing page, so you add this to your routes.php file:
use Cake\Routing\Router;
// Using the scoped route builder.
Router::scope('/', function ($routes) {
$routes->connect('/', ['controller' => 'Articles', 'action' => 'index']);
});
// Using the static method.
Router::connect('/', ['controller' => 'Articles', 'action' => 'index']);
Router provides two interfaces for connecting routes. The static method is a backwards compatible interface, while the scoped builders offer more terse syntax when building multiple routes, and better performance.
This will execute the index method in the ArticlesController when the homepage of your site is
visited. Sometimes you need dynamic routes that will accept multiple parameters, this would be the case,
for example of a route for viewing an articles content:
155
The above route will accept any URL looking like /articles/15 and invoke the method view(15)
in the ArticlesController. This will not, though, prevent people from trying to access URLs looking like /articles/foobar. If you wish, you can restring some parameters to conform to a regular
expression:
Router::connect(
'/articles/:id',
['controller' => 'Articles', 'action' => 'view'],
['id' => '\d+', 'pass' => ['id']]
);
The previous example changed the star matcher by a new placeholder :id. Using placeholders allows us to
validate parts of the URL, in this case we used the \d+ regular expression so that only digits are matched.
Finally, we told the Router to treat the id placeholder as a function argument to the view() function by
specifying the pass option. More on using this option later.
The CakePHP Router can also reverse match routes. That means that from an array containing matching
parameters, it is capable of generating a URL string:
use Cake\Routing\Router;
echo Router::url(['controller' => 'Articles', 'action' => 'view', 'id' =>
15]);
// Will output
/articles/15
Routes can also be labelled with a unique name, this allows you to quickly reference them when building
links instead of specifying each of the routing parameters:
use Cake\Routing\Router;
Router::connect(
'/login',
['controller' => 'Users', 'action' => 'login'],
['_name' => 'login']
);
echo Router::url(['_name' => 'login']);
// Will output
/login
To help keep your routing code DRY, the Router has the concept of scopes. A scope defines a common path
segment, and optionally route defaults. Any routes connected inside a scope will inherit the path/defaults
from their wrapping scopes:
Router::scope('/blog', ['plugin' => 'Blog'], function ($routes) {
$routes->connect('/', ['controller' => 'Articles']);
});
156
Chapter 8. Routing
Connecting Routes
static Cake\Routing\Router::connect($route, $defaults =[], $options =[])
To keep your code DRY you should use routing scopes. Routing scopes not only let you keep your code
DRY, they also help Router optimize its operation. As seen above you can also use Router::connect()
to connect routes. This method defaults to the / scope. To create a scope and connect some routes well use
the scope() method:
// In config/routes.php
use Cake\Routing\Route\DashedRoute;
Router::scope('/', function ($routes) {
$routes->fallbacks(DashedRoute::class);
});
The connect() method takes up to three parameters: the URL template you wish to match, the default
values for your route elements, and the options for the route. Options frequently include regular expression
rules to help the router match elements in the URL.
The basic format for a route definition is:
$routes->connect(
'URL template',
['default' => 'defaultValue'],
['option' => 'matchingRegex']
);
The first parameter is used to tell the router what sort of URL youre trying to control. The URL is a normal
slash delimited string, but can also contain a wildcard (*) or Route Elements. Using a wildcard tells the
router that you are willing to accept any additional arguments supplied. Routes without a * only match the
exact template pattern supplied.
Once youve specified a URL, you use the last two parameters of connect() to tell CakePHP what to
do with a request once it has been matched. The second parameter is an associative array. The keys of the
array should be named after the route elements the URL template represents. The values in the array are the
default values for those keys. Lets look at some basic examples before we start using the third parameter of
connect():
$routes->connect(
'/pages/*',
['controller' => 'Pages', 'action' => 'display']
);
This route is found in the routes.php file distributed with CakePHP. It matches any URL starting
with /pages/ and hands it to the display() action of the PagesController. A request to
Connecting Routes
157
The incoming URL of /pages/the-example-/-and-proof would result in a single passed argument of the-example-/-and-proof.
You can use the second parameter of connect() to provide any routing parameters that are composed of
the default values of the route:
$routes->connect(
'/government',
['controller' => 'Pages', 'action' => 'display', 5]
);
This example shows how you can use the second parameter of connect() to define default parameters.
If you built a site that features products for different categories of customers, you might consider creating a
route. This allows you to link to /government rather than /pages/display/5.
Another common use for the Router is to define an alias for a controller. Lets say that instead
of accessing our regular URL at /users/some_action/5, wed like to be able to access it by
/cooks/some_action/5. The following route takes care of that:
$routes->connect(
'/cooks/:action/*', ['controller' => 'Users']
);
This is telling the Router that any URL beginning with /cooks/ should be sent to the users controller. The
action called will depend on the value of the :action parameter. By using Route Elements, you can create
variable routes, that accept user input or variables. The above route also uses the greedy star. The greedy
star indicates to Router that this route should accept any additional positional arguments given. These
arguments will be made available in the Passed Arguments array.
When generating URLs, routes are used too. Using ['controller' => 'Users','action' =>
'some_action',5] as a URL will output /cooks/some_action/5 if the above route is the first
match found.
Route Elements
You can specify your own route elements and doing so gives you the power to define places in the URL
where parameters for controller actions should lie. When a request is made, the values for these route
elements are found in $this->request->params in the controller. When you define a custom route
element, you can optionally specify a regular expression - this tells CakePHP how to know if the URL is
158
Chapter 8. Routing
correctly formed or not. If you choose to not provide a regular expression, any non / character will be
treated as part of the parameter:
$routes->connect(
'/:controller/:id',
['action' => 'view'],
['id' => '[0-9]+']
);
The above example illustrates how to create a quick way to view models from any controller by crafting a
URL that looks like /controllername/:id. The URL provided to connect() specifies two route
elements: :controller and :id. The :controller element is a CakePHP default route element, so
the router knows how to match and identify controller names in URLs. The :id element is a custom route
element, and must be further clarified by specifying a matching regular expression in the third parameter of
connect().
CakePHP does not automatically produce lowercased and dashed URLs when using the :controller
parameter. If you need this, the above example could be rewritten like so:
use Cake\Routing\Route\DashedRoute;
$routes->connect(
'/:controller/:id',
['action' => 'view'],
['id' => '[0-9]+', 'routeClass' => DashedRoute::class]
);
The DashedRoute class will make sure that the :controller and :plugin parameters are correctly
lowercased and dashed.
If you need lowercased and underscored URLs while migrating from a CakePHP 2.x application, you can
instead use the InflectedRoute class.
Note: Patterns used for route elements must not contain any capturing groups. If they do, Router will not
function correctly.
Once this route has been defined, requesting /apples/5 would call the view() method of
the ApplesController.
Inside the view() method, you would need to access the passed ID at
$this->request->params['id'].
If you have a single controller in your application and you do not want the controller name to appear in the
URL, you can map all URLs to actions in your controller. For example, to map all URLs to actions of the
home controller, e.g have URLs like /demo instead of /home/demo, you can do the following:
$routes->connect('/:action', ['controller' => 'Home']);
If you would like to provide a case insensitive URL, you can use regular expression inline modifiers:
$routes->connect(
'/:userShortcut',
['controller' => 'Teachers', 'action' => 'profile', 1],
Connecting Routes
159
This is rather involved, but shows how powerful routes can be. The URL supplied has four route elements.
The first is familiar to us: its a default route element that tells CakePHP to expect a controller name.
Next, we specify some default values. Regardless of the controller, we want the index() action to be called.
Finally, we specify some regular expressions that will match years, months and days in numerical form. Note
that parenthesis (grouping) are not supported in the regular expressions. You can still specify alternates, as
above, but not grouped with parenthesis.
Once defined, this route will match /articles/2007/02/01, /articles/2004/11/16, handing the requests to the index() actions of their respective controllers, with the date parameters in
$this->request->params.
There are several route elements that have special meaning in CakePHP, and should not be used unless you
want the special meaning
controller Used to name the controller for a route.
action Used to name the controller action for a route.
plugin Used to name the plugin a controller is located in.
prefix Used for Prefix Routing
_ext Used for File extentions routing.
_base Set to false to remove the base path from the generated URL. If your application is not in
the root directory, this can be used to generate URLs that are cake relative.
_scheme Set to create links on different schemes like webcal or ftp. Defaults to the current scheme.
_host Set the host to use for the link. Defaults to the current host.
_port Set the port if you need to create links on non-standard ports.
_full If true the FULL_BASE_URL constant will be prepended to generated URLs.
# Allows you to set URL hash fragments.
_ssl Set to true to convert the generated URL to https or false to force http.
_method Define the HTTP verb/method to use. Useful when working with Creating RESTful Routes.
160
Chapter 8. Routing
_name Name of route. If you have setup named routes, you can use this key to specify it.
Now thanks to the reverse routing capabilities, you can pass in the URL array like below and CakePHP will
know how to form the URL as defined in the routes:
// view.ctp
// This will return a link to /blog/3-CakePHP_Rocks
echo $this->Html->link('CakePHP Rocks', [
'controller' => 'Blog',
'action' => 'view',
'id' => 3,
'slug' => 'CakePHP_Rocks'
]);
// You can also used numerically indexed parameters.
echo $this->Html->link('CakePHP Rocks', [
'controller' => 'Blog',
'action' => 'view',
3,
'CakePHP_Rocks'
]);
Connecting Routes
161
If your route template contains any route elements like :controller youll need to supply those as part
of the options to Router::url().
Note: Route names must be unique across your entire application. The same _name cannot be used twice,
even if the names occur inside a different routing scope.
When building named routes, you will probably want to stick to some conventions for the route names.
CakePHP makes building up route names easier by allowing you to define name prefixes in each scope:
Router::scope('/api', ['_namePrefix' => 'api:'], function ($routes) {
// This route's name will be `api:ping`
$routes->connect('/ping', ['controller' => 'Pings'], ['_name' => 'ping']);
});
// Generate a URL for the ping route
Router::url(['_name' => 'api:ping']);
// Use namePrefix with plugin()
Router::plugin('Contacts', ['_namePrefix' => 'contacts:'], function ($routes)
{
// Connect routes.
});
// Or with prefix()
Router::prefix('Admin', ['_namePrefix' => 'admin:'], function ($routes) {
// Connect routes.
});
You can also use the _namePrefix option inside nested scopes and it works as youd expect:
162
Chapter 8. Routing
Routes connected in named scopes will only have names added if the route is also named. Nameless routes
will not have the _namePrefix applied to them.
New in version 3.1: The _namePrefix option was added in 3.1
Prefix Routing
static Cake\Routing\Router::prefix($name, $callback)
Many applications require an administration section where privileged users can make changes. This is
often done through a special URL such as /admin/users/edit/5. In CakePHP, prefix routing can be
enabled by using the prefix scope method:
use Cake\Routing\Route\DashedRoute;
Router::prefix('admin', function ($routes) {
// All routes here will be prefixed with `/admin`
// And have the prefix => admin route element added.
$routes->fallbacks(DashedRoute::class);
});
Prefixes are mapped to sub-namespaces in your applications Controller namespace. By having prefixes
as separate controllers you can create smaller and simpler controllers. Behavior that is common to the
prefixed and non-prefixed controllers can be encapsulated using inheritance, Components, or traits. Using
our users example, accessing the URL /admin/users/edit/5 would call the edit() method of our
src/Controller/Admin/UsersController.php passing 5 as the first parameter. The view file used would be
src/Template/Admin/Users/edit.ctp
You can map the URL /admin to your index() action of pages controller using following route:
Router::prefix('admin', function ($routes) {
// Because you are in the admin scope,
// you do not need to include the /admin prefix
// or the admin route element.
$routes->connect('/', ['controller' => 'Pages', 'action' => 'index']);
});
When creating prefix routes, you can set additional route parameters using the $options argument:
Connecting Routes
163
The above would create a route template like /debug_kit/admin/:controller. The connected
route would have the plugin and prefix route elements set.
When defining prefixes, you can nest multiple prefixes if necessary:
Router::prefix('manager', function ($routes) {
$routes->prefix('admin', function ($routes) {
$routes->connect('/:controller');
});
});
The above would create a route template like /manager/admin/:controller. The connected route
would have the prefix route element set to manager/admin.
The
current
prefix
will
be
available
$this->request->params['prefix']
from
the
controller
methods
through
When using prefix routes its important to set the prefix option. Heres how to build this link using the
HTML helper:
// Go into a prefixed route.
echo $this->Html->link(
'Manage articles',
['prefix' => 'manager', 'controller' => 'Articles', 'action' => 'add']
);
// Leave a prefix
echo $this->Html->link(
'View Post',
['prefix' => false, 'controller' => 'Articles', 'action' => 'view', 5]
);
Note: You should connect prefix routes before you connect fallback routes.
164
Chapter 8. Routing
Plugin Routing
static Cake\Routing\Router::plugin($name, $options =[], $callback)
Routes for Plugins should be created using the plugin() method. This method creates a new routing
scope for the plugins routes:
Router::plugin('DebugKit', function ($routes) {
// Routes connected here are prefixed with '/debug_kit' and
// have the plugin route element set to 'DebugKit'.
$routes->connect('/:controller');
});
When creating plugin scopes, you can customize the path element used with the path option:
Router::plugin('DebugKit', ['path' => '/debugger'], function ($routes) {
// Routes connected here are prefixed with '/debugger' and
// have the plugin route element set to 'DebugKit'.
$routes->connect('/:controller');
});
When using scopes you can nest plugin scopes within prefix scopes:
Router::prefix('admin', function ($routes) {
$routes->plugin('DebugKit', function ($routes) {
$routes->connect('/:controller');
});
});
The above would create a route that looks like /admin/debug_kit/:controller. It would have the
prefix, and plugin route elements set.
You can create links that point to a plugin, by adding the plugin key to your URL array:
echo $this->Html->link(
'New todo',
['plugin' => 'Todo', 'controller' => 'TodoItems', 'action' => 'create']
);
Conversely if the active request is a plugin request and you want to create a link that has no plugin you can
do the following:
echo $this->Html->link(
'New todo',
['plugin' => null, 'controller' => 'Users', 'action' => 'profile']
);
By setting plugin => null you tell the Router that you want to create a link that is not part of a plugin.
Connecting Routes
165
SEO-Friendly Routing
Some developers prefer to use dashes in URLs, as its perceived to give better search engine rankings.
The DashedRoute class can be used in your application with the ability to route plugin, controller, and
camelized action names to a dashed URL.
For example, if we had a ToDo plugin, with a TodoItems controller, and a showItems() action, it
could be accessed at /to-do/todo-items/show-items with the following router connection:
use Cake\Routing\Route\DashedRoute;
Router::plugin('ToDo', ['path' => 'to-do'], function ($routes) {
$routes->fallbacks(DashedRoute::class);
});
This will enable the named extensions for all routes connected after the extensions() call. Any routes
connected prior to it will not inherit the extensions.
Note: Setting the extensions should be the first thing you do in a scope, as the extensions will only be
applied to routes connected after the extensions are set.
By using extensions, you tell the router to remove any matching file extensions, and then parse what remains.
If you want to create a URL such as /page/title-of-page.html you would create your route using:
Router::scope('/page', function ($routes) {
$routes->extensions(['json', 'xml', 'html']);
$routes->connect(
'/:title',
['controller' => 'Pages', 'action' => 'view'],
[
'pass' => ['title']
]
);
});
Then to create links which map back to the routes simply use:
166
Chapter 8. Routing
$this->Html->link(
'Link title',
['controller' => 'Pages', 'action' => 'view', 'title' => 'super-article',
'_ext' => 'html']
);
File extensions are used by Request Handling to do automatic view switching based on content types.
The first line sets up a number of default routes for easy REST access where method specifies the desired
result format (e.g. xml, json, rss). These routes are HTTP Request Method sensitive.
HTTP format
GET
GET
POST
PUT
PATCH
DELETE
URL.format
/recipes.format
/recipes/123.format
/recipes.format
/recipes/123.format
/recipes/123.format
/recipes/123.format
CakePHPs Router class uses a number of different indicators to detect the HTTP method being used. Here
they are in order of preference:
1. The _method POST variable
2. The X_HTTP_METHOD_OVERRIDE
3. The REQUEST_METHOD header
The _method POST variable is helpful in using a browser as a REST client (or anything else that can do
POST). Just set the value of _method to the name of the HTTP request method you wish to emulate.
167
Will generate resource routes for both articles and comments. The comments routes will look like:
/api/articles/:article_id/comments
/api/articles/:article_id/comments/:id
By default resource routes map to the same prefix as the containing scope. If you have both nested and
non-nested resource controllers you can use a different controller in each context by using prefixes:
Router::scope('/api', function ($routes) {
$routes->resources('Articles', function ($routes) {
$routes->resources('Comments', ['prefix' => 'articles']);
});
});
Would create read only resource routes. The route names are create, update, view, index, and
delete.
168
Chapter 8. Routing
The above would use put() for the edit() action, and create() instead of add().
In addition to the default routes, this would also connect a route for /articles/delete_all. By default the path
segment will match the key name. You can use the path key inside the resource definition to customize the
path name:
$routes->resources('Articles', [
'map' => [
'updateAll' => [
'action' => 'updateAll',
'method' => 'DELETE',
'path' => '/update_many'
],
]
]);
// This would connect /articles/update_many
If you define only and map, make sure that your mapped methods are also in the only list.
169
Passed Arguments
Passed arguments are additional arguments or path segments that are used when making a request. They are
often used to pass parameters to your controller methods.
http://localhost/calendars/view/recent/mark
In
the
above
example,
both
recent
and
mark
are
passed
arguments
to
CalendarsController::view(). Passed arguments are given to your controllers in three
ways.
First as arguments to the action method called, and secondly they are available in
$this->request->params['pass'] as a numerically indexed array.
When using custom
routes you can force particular parameters to go into the passed arguments as well.
If you were to visit the previously mentioned URL, and you had a controller action that looked like:
class CalendarsController extends AppController
{
public function view($arg1, $arg2)
{
debug(func_get_args());
}
}
170
Chapter 8. Routing
When generating URLs, using a routing array you add passed arguments as values without string keys in
the array:
['controller' => 'Articles', 'action' => 'view', 5]
Generating URLs
static Cake\Routing\Router::url($url = null, $full = false)
Generating URLs or Reverse routing is a feature in CakePHP that is used to allow you to change your URL
structure without having to modify all your code. By using routing arrays to define your URLs, you can
later configure routes and the generated URLs will automatically update.
If you create URLs using strings like:
$this->Html->link('View', '/articles/view/' . $id);
And then later decide that /articles should really be called articles instead, you would have to go
through your entire application renaming URLs. However, if you defined your link like:
$this->Html->link(
'View',
['controller' => 'Articles', 'action' => 'view', $id]
);
Then when you decided to change your URLs, you could do so by defining a route. This would change both
the incoming URL mapping, as well as the generated URLs.
Generating URLs
171
When using array URLs, you can define both query string parameters and document fragments using special
keys:
Router::url([
'controller' => 'Articles',
'action' => 'index',
'?' => ['page' => 1],
'#' => 'top'
]);
// Will generate a URL like.
/articles/index?page=1#top
Router will also convert any unknown parameters in a routing array to querystring parameters. The ? is
offered for backwards compatibility with older versions of CakePHP.
You can also use any of the special route elements when generating URLs:
_ext Used for Routing File Extensions routing.
_base Set to false to remove the base path from the generated URL. If your application is not in
the root directory, this can be used to generate URLs that are cake relative.
_scheme Set to create links on different schemes like webcal or ftp. Defaults to the current
scheme.
_host Set the host to use for the link. Defaults to the current host.
_port Set the port if you need to create links on non-standard ports.
_full If true the FULL_BASE_URL constant will be prepended to generated URLs.
_ssl Set to true to convert the generated URL to https or false to force http.
_name Name of route. If you have setup named routes, you can use this key to specify it.
Redirect Routing
Redirect routing allows you to issue HTTP status 30x redirects for incoming routes, and point them at
different URLs. This is useful when you want to inform client applications that a resource has moved and
you dont want to expose two URLs for the same content.
Redirection routes are different from normal routes as they perform an actual header redirection if a match
is found. The redirection can occur to a destination within your application or an outside location:
Router::scope('/', function ($routes) {
$routes->redirect(
'/home/*',
['controller' => 'Articles', 'action' => 'view'],
['persist' => true]
// Or ['persist'=>['id']] for default routing where the
// view action expects $id as an argument.
);
})
172
Chapter 8. Routing
This route would create an instance of SlugRoute and allow you to implement custom parameter handling.
You can use plugin route classes using standard plugin syntax.
173
use Cake\Routing\Route\InflectedRoute;
Router::defaultRouteClass(InflectedRoute::class);
will cause all routes connected after this to use the InflectedRoute route class. Calling the method
without an argument will return current default route class.
Fallbacks Method
Cake\Routing\Router::fallbacks($routeClass = null)
The fallbacks method is a simple shortcut for defining default routes.
The method uses the
passed routing class for the defined rules or if no class is provided the class returned by
Router::defaultRouteClass() is used.
Calling fallbacks like so:
use Cake\Routing\Route\DashedRoute;
$routes->fallbacks(DashedRoute::class);
Note:
Using the default route class (Route) with fallbacks, or any route with :plugin and/or
:controller route elements will result in inconsistent URL case.
174
Chapter 8. Routing
into this:
Router::url(['plugin' => 'MyPlugin', 'controller' => 'Locations', 'action' =>
'index', 'language' => 'es']);
175
This will populate $this->request->params['named'] with any named parameters found in the
passed arguments. Any passed argument that was interpreted as a named parameter, will be removed from
the list of passed arguments.
Dispatcher Filters
Deprecated since version 3.3.0: As of 3.3.0 Dispatcher Filters are deprecated. You should use /controllers/middleware instead now.
There are several reasons to want a piece of code to be run before any controller code is executed or right
before the response is sent to the client, such as response caching, header tuning, special authentication or
just to provide access to a mission-critical API response in lesser time than a complete request dispatching
cycle would take.
CakePHP provides a clean interface for attaching filters to the dispatch cycle. It is similar to a middleware
layer, but re-uses the existing event subsystem used in other parts of CakePHP. Since they do not work
exactly like traditional middleware, we refer to them as Dispatcher Filters.
Built-in Filters
CakePHP comes with several dispatcher filters built-in. They handle common features that all applications
are likely to need. The built-in filters are:
AssetFilter checks whether the request is referring to a theme or plugin asset file, such as a
CSS, JavaScript or image file stored in either a plugins webroot folder or the corresponding one for a
Theme. It will serve the file accordingly if found, stopping the rest of the dispatching cycle:
// Use options to set cacheTime for your static assets
// If not set, this defaults to +1 hour
DispatcherFactory::add('Asset', ['cacheTime' => '+24 hours']);
rules
to
the
request
URL.
Populates
ControllerFactory uses $request->params to locate the controller that will handle the
current request.
LocaleSelector enables automatic language switching from the Accept-Language header
sent by the browser.
Using Filters
Filters are usually enabled in your applications bootstrap.php file, but you could load them
any time before the request is dispatched.
Adding and removing filters is done through
Cake\Routing\DispatcherFactory. By default, the CakePHP application template comes with
a couple filter classes already enabled for all requests; lets take a look at how they are added:
176
Chapter 8. Routing
DispatcherFactory::add('Routing');
DispatcherFactory::add('ControllerFactory');
// Plugin syntax is also possible
DispatcherFactory::add('PluginName.DispatcherName');
// Use options to set priority
DispatcherFactory::add('Asset', ['priority' => 1]);
Dispatcher filters with higher priority (lower numbers) - will be executed first. Priority defaults to 10.
While using the string name is convenient, you can also pass instances into add():
use Cake\Routing\Filter\RoutingFilter;
DispatcherFactory::add(new RoutingFilter());
The higher the priority the later this filter will be invoked.
Conditionally Applying Filters
If you dont want to run a filter on every request, you can use conditions to only apply it some of the
time. You can apply conditions using the for and when options. The for option lets you match on URL
substrings, while the when option allows you to run a callable:
// Only runs on requests starting with `/blog`
DispatcherFactory::add('BlogHeader', ['for' => '/blog']);
// Only run on GET requests.
DispatcherFactory::add('Cache', [
'when' => function ($request, $response) {
return $request->is('get');
}
]);
The callable provided to when should return true when the filter should run. The callable can expect to
get the current request and response as arguments.
177
Building a Filter
To create a filter, define a class in src/Routing/Filter. In this example, well be making a filter that adds a
tracking cookie for the first landing page. First, create the file. Its contents should look like:
namespace App\Routing\Filter;
use Cake\Event\Event;
use Cake\Routing\DispatcherFilter;
class TrackingCookieFilter extends DispatcherFilter
{
public function beforeDispatch(Event $event)
{
$request = $event->data['request'];
$response = $event->data['response'];
if (!$request->cookie('landing_page')) {
$response->cookie([
'name' => 'landing_page',
'value' => $request->here(),
'expire' => '+ 1 year',
]);
}
}
}
Save this file into src/Routing/Filter/TrackingCookieFilter.php. As you can see, like other classes in
CakePHP, dispatcher filters have a few conventions:
Class names end in Filter.
Classes are in the Routing\Filter namespace. For example, App\Routing\Filter.
Generally filters extend Cake\Routing\DispatcherFilter.
DispatcherFilter exposes two methods that can be overridden in subclasses, they are
beforeDispatch() and afterDispatch(). These methods are executed before or after any controller is executed respectively. Both methods receive a Cake\Event\Event object containing the
Request and Response objects (Cake\Network\Request and Cake\Network\Response instances) inside the $data property.
While our filter was pretty simple, there are a few other interesting things we can do in filter methods. By returning an Response object, you can short-circuit the dispatch process and prevent
the controller from being called. When returning a response, you should also remember to call
$event->stopPropagation() so other filters are not called.
Note: When a beforeDispatch method returns a response, the controller, and afterDispatch event will not
be invoked.
Lets now create another filter for altering response headers in any public page, in our case it would be
anything served from the PagesController:
178
Chapter 8. Routing
namespace App\Routing\Filter;
use Cake\Event\Event;
use Cake\Routing\DispatcherFilter;
class HttpCacheFilter extends DispatcherFilter
{
public function afterDispatch(Event $event)
{
$request = $event->data['request'];
$response = $event->data['response'];
if ($response->statusCode() === 200) {
$response->sharable(true);
$response->expires(strtotime('+1 day'));
}
}
}
// In our bootstrap.php
DispatcherFactory::add('HttpCache', ['for' => '/pages'])
This filter will send a expiration header to 1 day in the future for all responses produced by the pages
controller. You could of course do the same in the controller, this is just an example of what could be done
with filters. For instance, instead of altering the response, you could cache it using Cake\Cache\Cache
and serve the response from the beforeDispatch() callback.
While powerful, dispatcher filters have the potential to make your application more difficult to maintain.
Filters are an extremely powerful tool when used wisely and adding response handlers for each URL in
your app is not a good use for them. Keep in mind that not everything needs to be a filter; Controllers and
Components are usually a more accurate choice for adding any request handling code to your app.
179
180
Chapter 8. Routing
CHAPTER 9
The request and response objects provide an abstraction around HTTP requests and responses. The request
object in CakePHP allows you to introspect an incoming request, while the response object allows you to
effortlessly create HTTP responses from your controllers.
Request
class Cake\Network\Request
Request is the default request object used in CakePHP. It centralizes a number of features for interrogating and interacting with request data. On each request one Request is created and then passed by reference to the various layers of an application that use request data. By default the request is assigned to
$this->request, and is available in Controllers, Cells, Views and Helpers. You can also access it in
Components using the controller reference. Some of the duties Request performs include:
Processing the GET, POST, and FILES arrays into the data structures you are familiar with.
Providing environment introspection pertaining to the request. Information like the headers sent, the
clients IP address, and the subdomain/domain names the server your application is running on.
Providing access to request parameters both as array indexes and object properties.
Request Parameters
Request exposes several interfaces for accessing request parameters:
$this->request->params['controller'];
$this->request->param('controller');
All of the above will access the same value. All Route Elements are accessed through this interface.
In addition to Route Elements, you also often need access to Passed Arguments. These are both available on
the request object as well:
181
// Passed arguments
$this->request->pass;
$this->request['pass'];
$this->request->params['pass'];
Will all provide you access to the passed arguments. There are several important/useful parameters that
CakePHP uses internally, these are also all found in the request parameters:
plugin The plugin handling the request. Will be null when there is no plugin.
controller The controller handling the current request.
action The action handling the current request.
prefix The prefix for the current action. See Prefix Routing for more information.
You can either directly access the query property, or you can use query() method to read the URL query
array in an error-free manner. Any keys that do not exist will return null:
$foo = $this->request->query('value_that_does_not_exist');
// $foo === null
182
Some deserializing methods require additional parameters when called, such as the as array
parameter on json_decode.
If you want XML converted into a DOMDocument object,
Network\Request::input() supports passing in additional parameters as well:
// Get Xml encoded data submitted to a PUT/POST action
$data = $this->request->input('Cake\Utility\Xml::build', ['return' =>
'domdocument']);
Request
183
Path Information
The request object also provides useful information about the paths in your application.
$request->base and $request->webroot are useful for generating URLs, and determining
whether or not your application is in a subdirectory. The various properties you can use are:
// Assume the current request URL is /subdir/articles/edit/1?page=1
// Holds /subdir/articles/edit/1?page=1
$request->here;
// Holds /subdir
$request->base;
// Holds /subdir/
$request->webroot;
You
can
also
extend
the
request
detectors
that
are
available,
by
using
Cake\Network\Request::addDetector() to create new kinds of detectors. There are four
different types of detectors that you can create:
Environment value comparison - Compares a value fetched from env() for equality with the provided value.
Pattern value comparison - Pattern value comparison allows you to compare a value fetched from
env() to a regular expression.
Option based comparison - Option based comparisons use a list of options to create a regular expression. Subsequent calls to add an already defined options detector will merge the options.
Callback detectors - Callback detectors allow you to provide a callback type to handle the check.
The callback will receive the request object as its only parameter.
Cake\Network\Request::addDetector($name, $options)
Some examples would be:
// Add an environment detector.
$this->request->addDetector(
'post',
['env' => 'REQUEST_METHOD', 'value' => 'POST']
);
184
Request
also
includes
methods
like
Cake\Network\Request::domain(),
Cake\Network\Request::subdomains() and Cake\Network\Request::host() to
help applications with subdomains, have a slightly easier life.
There are several built-in detectors that you can use:
is('get') Check to see whether the current request is a GET.
is('put') Check to see whether the current request is a PUT.
is('patch') Check to see whether the current request is a PATCH.
is('post') Check to see whether the current request is a POST.
is('delete') Check to see whether the current request is a DELETE.
is('head') Check to see whether the current request is HEAD.
is('options') Check to see whether the current request is OPTIONS.
is('ajax') Check to see whether the current request came with X-Requested-With = XMLHttpRequest.
is('ssl') Check to see whether the request is via SSL.
is('flash') Check to see whether the request has a User-Agent of Flash.
Request
185
is('requested') Check to see whether the request has a query param requested with value 1.
is('json') Check to see whether the request has json extension and accept application/json
mimetype.
is('xml') Check to see whether the request has xml extension and accept application/xml or
text/xml mimetype.
New in version 3.3.0: Detectors can take additional parameters as of 3.3.0.
Session Data
To access the session for a given request use the session() method:
$this->request->session()->read('Auth.User.name');
For more information, see the Sessions documentation for how to use the session object.
Cake\Network\Request::subdomains($tldLength = 1)
Returns the subdomains your application is running on as an array:
// Returns ['my', 'dev'] for 'my.dev.example.org'
$request->subdomains();
Cake\Network\Request::host()
Returns the host your application is on:
// Prints 'my.dev.example.org'
echo $request->host();
Cake\Network\Request::allowMethod($methods)
186
Set allowed HTTP methods. If not matched, will throw MethodNotAllowedException. The 405 response
will include the required Allow header with the passed methods
Cake\Network\Request::header($name)
Allows you to access any of the HTTP_* headers that were used for the request. For example:
$this->request->header('User-Agent');
Cake\Network\Request::referer($local = false)
Returns the referring address for the request.
Cake\Network\Request::clientIp()
Returns the current visitors IP address.
Request
187
Cake\Network\Request::acceptLanguage($language = null)
Get all the languages accepted by the client, or check whether a specific language is accepted.
Get the list of accepted languages:
$this->request->acceptLanguage();
Response
class Cake\Network\Response
Cake\Network\Response is the default response class in CakePHP. It encapsulates a number of features and functionality for generating HTTP responses in your application. It also assists in testing, as it can
be mocked/stubbed allowing you to inspect headers that will be sent. Like Cake\Network\Request,
Cake\Network\Response consolidates a number of methods previously found on Controller,
RequestHandlerComponent and Dispatcher. The old methods are deprecated in favour of using Cake\Network\Response.
Response provides an interface to wrap the common response-related tasks such as:
Sending headers for redirects.
Sending content type headers.
Sending any header.
Sending the response body.
You
can
control
the
Content-Type
of
your
applications
responses
with
Cake\Network\Response::type(). If your application needs to deal with content types that
are not built into Response, you can map them with type() as well:
// Add a vCard type
$this->response->type(['vcf' => 'text/v-card']);
// Set the response Content-Type to vcard.
$this->response->type('vcf');
Usually, youll want to map additional content types in your controllers beforeFilter() callback,
so you can leverage the automatic view switching features of RequestHandlerComponent if you are
using it.
Sending Files
Cake\Network\Response::file($path, $options =[])
There are times when you want to send files as responses for your requests. You can accomplish that by
using Cake\Network\Response::file():
public function sendFile($id)
{
$file = $this->Attachments->getFile($id);
$this->response->file($file['path']);
// Return response object to prevent controller from trying to render
// a view.
return $this->response;
}
As shown in the above example, you must pass the file path to the method. CakePHP will send
a proper content type header if its a known file type listed in Cake\Network\Reponse::$_mimeTypes.
You can add new types prior to calling Cake\Network\Response::file() by using the
Cake\Network\Response::type() method.
If you want, you can also force a file to be downloaded instead of displayed in the browser by specifying the
options:
$this->response->file(
$file['path'],
['download' => true, 'name' => 'foo']
);
Response
189
Streaming Resources
You can use a callable with body() to convert resource streams into responses:
$file = fopen('/some/file.png', 'r');
$this->response->body(function () use ($file) {
rewind($file);
fpassthru($file);
fclose($file);
});
Setting Headers
Cake\Network\Response::header($header = null, $value = null)
Setting headers is done with the Cake\Network\Response::header() method. It can be called
with a few different parameter configurations:
190
Setting the same header() multiple times will result in overwriting the previous values, just as regular
header calls. Headers are not sent when Cake\Network\Response::header() is called; instead
they are buffered until the response is actually sent.
You can now use the convenience method Cake\Network\Response::location() to directly set
or get the redirect location header.
Warning: Using disableCache() with downloads from SSL domains while trying to send files to Internet Explorer can result in errors.
Cake\Network\Response::cache($since, $time = +1 day)
You can also tell clients that you
Cake\Network\Response::cache():
want
them
to
cache
responses.
By
using
Response
191
The above would tell clients to cache the resulting response for 5 days, hopefully speeding up your visitors experience. CakeResponse::cache() sets the Last-Modified value to the first argument.
Expires header and the max-age directive are set based on the second parameter. Cache-Controls
public directive is set as well.
Response class helps you set this header with some utility methods that will produce a final valid
Cache-Control header. The first is the Cake\Network\Response::sharable() method, which
indicates whether a response is to be considered sharable across different users or clients. This method actually controls the public or private part of this header. Setting a response as private indicates that all
or part of it is intended for a single user. To take advantage of shared caches, the control directive must be
set as public.
The second parameter of this method is used to specify a max-age for the cache, which is the number of
seconds after which the response is no longer considered fresh:
public function view()
{
// ...
// Set the Cache-Control as public for 3600 seconds
$this->response->sharable(true, 3600);
}
public function my_data()
{
// ...
// Set the Cache-Control as private for 3600 seconds
$this->response->sharable(false, 3600);
}
Response exposes separate methods for setting each of the directives in the Cache-Control header.
192
This method also accepts a DateTime instance or any string that can be parsed by the DateTime class.
The Etag Header
Cake\Network\Response::etag($tag = null, $weak = false)
Cache validation in HTTP is often used when content is constantly changing, and asks the application to
only generate the response contents if the cache is no longer fresh. Under this model, the client continues to
store pages in the cache, but it asks the application every time whether the resource has changed, instead of
using it directly. This is commonly used with static resources such as images and other assets.
The etag() method (called entity tag) is a string that uniquely identifies the requested resource, as a
checksum does for a file, in order to determine whether it matches a cached resource.
To take advantage of this header, you must either call the Cake\Network\Response::checkNotModified()
method manually or include the RequestHandlerComponent in your controller:
public function index()
{
$articles = $this->Articles->find('all');
$this->response->etag($this->Articles->generateHash($articles));
if ($this->response->checkNotModified($this->request)) {
return $this->response;
}
// ...
}
Note: Most proxy users should probably consider using the Last Modified Header instead of Etags for
performance and compatibility reasons.
193
To take advantage of this header, you must either call the Cake\Network\Response::checkNotModified()
method or include the RequestHandlerComponent in your controller:
public function view()
{
$article = $this->Articles->find()->first();
$this->response->modified($article->modified);
if ($this->response->checkNotModified($this->request)) {
return $this->response;
}
// ...
}
194
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
->allowHeaders(['X-CSRF-Token'])
->allowCredentials()
->exposeHeaders(['Link'])
->maxAge(300)
->build();
CORS related headers will only be applied to the response if the following criteria are met:
1. The request has an Origin header.
2. The requests Origin value matches one of the allowed Origin values.
New in version 3.2: The CorsBuilder was added in 3.2
195
196
CHAPTER 10
Controllers
class Cake\Controller\Controller
Controllers are the C in MVC. After routing has been applied and the correct controller has been found,
your controllers action is called. Your controller should handle interpreting the request data, making sure
the correct models are called, and the right response or view is rendered. Controllers can be thought of as
middle layer between the Model and View. You want to keep your controllers thin, and your models fat.
This will help you reuse your code and makes your code easier to test.
Commonly, a controller is used to manage the logic around a single model. For example, if you were
building a site for an online bakery, you might have a RecipesController managing your recipes and an
IngredientsController managing your ingredients. However, its also possible to have controllers work with
more than one model. In CakePHP, a controller is named after the primary model it handles.
Your applications controllers extend the AppController class, which in turn extends the core
Controller class. The AppController class can be defined in src/Controller/AppController.php
and it should contain methods that are shared between all of your applications controllers.
Controllers provide a number of methods that handle requests. These are called actions. By default, each
public method in a controller is an action, and is accessible from a URL. An action is responsible for
interpreting the request and creating the response. Usually responses are in the form of a rendered view, but
there are other ways to create responses as well.
197
{
}
Controller attributes and methods created in your AppController will be available in all controllers that
extend it. Components (which youll learn about later) are best used for code that is used in many (but not
necessarily all) controllers.
You can use your AppController to load components that will be used in every controller in your
application. CakePHP provides a initialize() method that is invoked at the end of a Controllers
constructor for this kind of use:
namespace App\Controller;
use Cake\Controller\Controller;
class AppController extends Controller
{
public function initialize()
{
// Always enable the CSRF component.
$this->loadComponent('Csrf');
}
}
In addition to the initialize() method, the older $components property will also allow you to
declare which components should be loaded. While normal object-oriented inheritance rules apply, the
components and helpers used by a controller are treated specially. In these cases, AppController property values are merged with child controller class arrays. The values in the child class will always override
those in AppController.
Request Flow
When a request is made to a CakePHP application, CakePHPs Cake\Routing\Router and
Cake\Routing\Dispatcher classes use Connecting Routes to find and create the correct controller
instance. The request data is encapsulated in a request object. CakePHP puts all of the important request
information into the $this->request property. See the section on Request for more information on the
CakePHP request object.
Controller Actions
Controller actions are responsible for converting the request parameters into a response for the browser/user
making the request. CakePHP uses conventions to automate this process and remove some boilerplate code
you would otherwise need to write.
By convention, CakePHP renders a view with an inflected version of the action name. Returning to our
198
online bakery example, our RecipesController might contain the view(), share(), and search()
actions. The controller would be found in src/Controller/RecipesController.php and contain:
// src/Controller/RecipesController.php
class RecipesController extends AppController
{
public function view($id)
{
// Action logic goes here.
}
public function share($customerId, $recipeId)
{
// Action logic goes here.
}
public function search($query)
{
// Action logic goes here.
}
}
The
template
files
for
these
actions
would
be
src/Template/Recipes/view.ctp,
src/Template/Recipes/share.ctp, and src/Template/Recipes/search.ctp. The conventional view file
name is the lowercased and underscored version of the action name.
Controller actions generally use Controller::set() to create a context that View uses to render the
view layer. Because of the conventions that CakePHP uses, you dont need to create and render the view
manually. Instead, once a controller action has completed, CakePHP will handle rendering and delivering
the View.
If for some reason youd like to skip the default behavior, you can return a Cake\Network\Response
object from the action with the fully created response.
In order for you to use a controller effectively in your own application, well cover some of the core attributes
and methods provided by CakePHPs controllers.
199
The Controller::set() method also takes an associative array as its first parameter. This can often
be a quick way to assign a set of information to the view:
$data = [
'color' => 'pink',
'type' => 'sugar',
'base_price' => 23.95
];
// Make $color, $type, and $base_price
// available to the view:
$this->set($data);
The above shows how you can load custom helpers, set the theme and use a custom view class.
New in version 3.1: ViewBuilder was added in 3.1
Rendering a View
Cake\Controller\Controller::render(string $view, string $layout)
The Controller::render() method is automatically called at the end of each requested controller action. This method performs all the view logic (using the data youve submitted using the
Controller::set() method), places the view inside its View::$layout, and serves it back to the
end user.
The default view file used by render is determined by convention. If the search() action of the
RecipesController is requested, the view file in src/Template/Recipes/search.ctp will be rendered:
200
namespace App\Controller;
class RecipesController extends AppController
{
// ...
public function search()
{
// Render the view in src/Template/Recipes/search.ctp
$this->render();
}
// ...
}
Although CakePHP will automatically call it after every actions logic (unless youve set
$this->autoRender to false), you can use it to specify an alternate view file by specifying a view
file name as first argument of Controller::render() method.
If $view starts with /, it is assumed to be a view or element file relative to the src/Template folder. This
allows direct rendering of elements, very useful in AJAX calls:
// Render the element in src/Template/Element/ajaxreturn.ctp
$this->render('/Element/ajaxreturn');
The second parameter $layout of Controller::render() allows you to specify the layout with
which the view is rendered.
Rendering a Specific Template
In your controller, you may want to render a different view than the conventional one. You can do this
by calling Controller::render() directly. Once you have called Controller::render(),
CakePHP will not try to re-render the view:
namespace App\Controller;
class PostsController extends AppController
{
public function my_action()
{
$this->render('custom_file');
}
}
201
The method will return the response instance with appropriate headers set. You should return the response
instance from your action to prevent view rendering and let the dispatcher handle actual redirection.
You can also use a relative or absolute URL as the $url argument:
return $this->redirect('/orders/thanks');
return $this->redirect('http://www.example.com');
The second parameter of Controller::redirect() allows you to define an HTTP status code to
accompany the redirect. You may want to use 301 (moved permanently) or 303 (see other), depending on
the nature of the redirect.
If you need to redirect to the referer page you can use:
return $this->redirect($this->referer());
202
return $this->redirect([
'controller' => 'Orders',
'action' => 'confirm',
'?' => [
'product' => 'pizza',
'quantity' => 5
],
'#' => 'top'
]);
If you are using a table provider other than the built-in ORM you can link that table system into CakePHPs
controllers by connecting its factory method:
// In a controller method.
$this->modelFactory(
'ElasticIndex',
['ElasticIndexes', 'factory']
);
203
After registering a table factory, you can use loadModel to load instances:
// In a controller method.
$this->loadModel('Locations', 'ElasticIndex');
Note: The built-in ORMs TableRegistry is connected by default as the Table provider.
Paginating a Model
Cake\Controller\Controller::paginate()
This method is used for paginating results fetched by your models. You can specify page sizes, model find
conditions and more. See the pagination section for more details on how to use paginate()
The paginate attribute gives you an easy way to customize how paginate() behaves:
class ArticlesController extends AppController
{
public $paginate = [
'Articles' => [
'conditions' => ['published' => 1]
]
];
}
property Cake\Controller\Controller::$components
The $components property on your controllers allows you to configure components. Configured components and their dependencies will be created by CakePHP for you. Read the Configuring Components
section for more information. As mentioned earlier the $components property will be merged with the
property defined in each of you controllers parent classes.
204
Each of these variables are merged with their inherited values, therefore it is not necessary (for example) to
redeclare the FormHelper, or anything that is declared in your AppController.
Deprecated since version 3.0: Loading Helpers from the controller is provided for backwards compatibility
reasons. You should see Configuring Helpers for how to load helpers.
Event List
Controller.initialize
Controller.startup
Controller.beforeRedirect
Controller.beforeRender
Controller.shutdown
205
Cake\Controller\Controller::beforeRender(Event $event)
Called during the Controller.beforeRender event which occurs after controller action logic,
but before the view is rendered. This callback is not used often, but may be needed if you are calling
Controller\Controller::render() manually before the end of a given action.
Cake\Controller\Controller::afterFilter(Event $event)
Called during the Controller.shutdown event which is triggered after every controller action,
and after rendering is complete. This is the last controller method to run.
In addition to controller life-cycle callbacks, Components also provide a similar set of callbacks.
Remember to call AppControllers callbacks within child controller callbacks for best results:
public function beforeFilter(Event $event)
{
parent::beforeFilter($event);
}
More on Controllers
The Pages Controller
CakePHPs official skeleton app ships with a default controller PagesController.php. This is a simple
and optional controller for serving up static content. The home page you see after installation is generated using this controller and the view file src/Template/Pages/home.ctp. If you make the view file
src/Template/Pages/about_us.ctp you can access it using the URL http://example.com/pages/about_us.
You are free to modify the Pages Controller to meet your needs.
When you bake an app using Composer the Pages Controller is created in your src/Controller/ folder.
Components
Components are packages of logic that are shared between controllers. CakePHP comes with a fantastic set
of core components you can use to aid in various common tasks. You can also create your own components.
If you find yourself wanting to copy and paste things between controllers, you should consider creating your
own component to contain the functionality. Creating components keeps controller code clean and allows
you to reuse code between different controllers.
For more information on the components included in CakePHP, check out the chapter for each component:
Authentication
class AuthComponent(ComponentCollection $collection, array $config =[])
Identifying, authenticating, and authorizing users is a common part of almost every web application. In
CakePHP AuthComponent provides a pluggable way to do these tasks. AuthComponent allows you to
combine authentication objects and authorization objects to create flexible ways of identifying and checking
user authorization.
206
More on Controllers
207
// Pass settings in
$this->Auth->config('authenticate', [
'Basic' => ['userModel' => 'Members'],
'Form' => ['userModel' => 'Members']
]);
In the second example youll notice that we had to declare the userModel key twice. To help you keep
your code DRY, you can use the all key. This special key allows you to set settings that are passed to every
attached object. The all key is also exposed as AuthComponent::ALL:
// Pass settings in using 'all'
$this->Auth->config('authenticate', [
AuthComponent::ALL => ['userModel' => 'Members'],
'Basic',
'Form'
]);
In the above example, both Form and Basic will get the settings defined for the all key. Any settings
passed to a specific authentication object will override the matching key in the all key. The core authentication objects support the following configuration keys.
fields The fields to use to identify a user by. You can use keys username and password to
specify your username and password fields respectively.
userModel The model name of the users table; defaults to Users.
finder The finder method to use to fetch a user record. Defaults to all.
passwordHasher Password hasher class; Defaults to Default.
The scope and contain options have been deprecated as of 3.1. Use a custom finder instead to
modify the query to fetch a user record.
The userFields option has been deprecated as of 3.1. Use select() in your custom finder.
To configure different fields for user in your initialize() method:
public function initialize()
{
parent::initialize();
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'fields' => ['username' => 'email', 'password' => 'passwd']
]
]
]);
}
Do not put other Auth configuration keys, such as authError, loginAction, etc., within the
authenticate or Form element. They should be at the same level as the authenticate key. The setup
above with other Auth configuration should look like:
208
In addition to the common configuration, Basic authentication supports the following keys:
realm The realm being authenticated. Defaults to env('SERVER_NAME').
In addition to the common configuration Digest authentication supports the following keys:
realm The realm authentication is for. Defaults to the servername.
nonce A nonce used for authentication. Defaults to uniqid().
qop Defaults to auth; no other values are supported at this time.
opaque A string that must
md5($config['realm']).
be
returned
unchanged
by
clients.
Defaults
to
This will require your UsersTable to have finder method findAuth(). In the example shown below
the query is modified to fetch only required fields and add condition. You must ensure that you select the
More on Controllers
209
Note: finder option is available since 3.1. Prior to that you can use scope and contain options to
modify query.
The above code will attempt to first identify a user by using the POST data. If successful we set the user info
to the session so that it persists across requests and then redirect to either the last page they were visiting or
a URL specified in the loginRedirect config. If the login is unsuccessful, a flash message is set.
Warning: $this->Auth->setUser($data) will log the user in with whatever data is passed to
the method. It wont actually check the credentials against an authentication class.
210
On each request, these values, PHP_AUTH_USER and PHP_AUTH_PW, are used to re-identify the user and
ensure they are valid user. As with authentication objects authenticate() method, the getUser()
method should return an array of user information on success or false on failure.
More on Controllers
211
The above is how you could implement getUser method for HTTP basic authentication.
The
_findUser() method is part of BaseAuthenticate and identifies a user based on a username and
password.
Using Basic Authentication
Basic authentication allows you to create a stateless authentication that can be used in intranet applications
or for simple API scenarios. Basic authentication credentials will be rechecked on each request.
Warning: Basic authentication transmits credentials in plain-text. You should use HTTPS when using
Basic authentication.
To use basic authentication, youll need to configure AuthComponent:
$this->loadComponent('Auth', [
'authenticate' => [
'Basic' => [
'fields' => ['username' => 'username', 'password' => 'api_key'],
'userModel' => 'Users'
],
],
'storage' => 'Memory',
'unauthorizedRedirect' => false
]);
Here were using username + API key as our fields, and use the Users model.
Creating API Keys for Basic Authentication
Because basic HTTP sends credentials in plain-text, it is unwise to have users send their login password.
Instead an opaque API key is generally used. You can generate these API tokens randomly using libraries
from CakePHP:
namespace App\Model\Table;
use Cake\Auth\DefaultPasswordHasher;
212
use Cake\Utility\Text;
use Cake\Event\Event;
use Cake\ORM\Table;
class UsersTable extends Table
{
public function beforeSave(Event $event)
{
$entity = $event->data['entity'];
if ($entity->isNew()) {
$hasher = new DefaultPasswordHasher();
// Generate an API 'token'
$entity->api_key_plain = sha1(Text::uuid());
// Bcrypt the token so BasicAuthenticate can check
// it during login.
$entity->api_key = $hasher->hash($entity->api_key_plain);
}
return true;
}
}
The above generates a random hash for each user as they are saved. The above code assumes you have two
columns api_key - to store the hashed API key, and api_key_plain - to the plaintext version of the
API key, so we can display it to the user later on. Using a key instead of a password, means that even over
plain HTTP, your users can use an opaque token instead of their original password. It is also wise to include
logic allowing API keys to be regenerated at a users request.
Using Digest Authentication
Digest authentication offers an improved security model over basic authentication, as the users credentials
are never sent in the request header. Instead a hash is sent.
To use digest authentication, youll need to configure AuthComponent:
$this->loadComponent('Auth', [
'authenticate' => [
'Digest' => [
'fields' => ['username' => 'username', 'password' => 'digest_hash
'],
'userModel' => 'Users'
],
],
'storage' => 'Memory',
'unauthorizedRedirect' => false
]);
Here were using username + digest_hash as our fields, and use the Users model.
More on Controllers
213
Passwords for digest authentication need a bit more information than other password hashes, based on the
RFC for digest authentication.
Note:
The third parameter of DigestAuthenticate::password() must match the realm config value
defined when DigestAuthentication was configured in AuthComponent::$authenticate. This defaults to
env('SCRIPT_NAME'). You may wish to use a static string if you want consistent hashes in multiple
environments.
214
use Cake\Network\Response;
class OpenidAuthenticate extends BaseAuthenticate
{
public function authenticate(Request $request, Response $response)
{
// Do things for OpenID here.
// Return an array of user if they could authenticate the user,
// return false if not.
}
}
Authentication objects should return false if they cannot identify the user and an array of user information
if they can. Its not required that you extend BaseAuthenticate, only that your authentication object
implements Cake\Event\EventListenerInterface. The BaseAuthenticate class provides
a number of helpful methods that are commonly used. You can also implement a getUser() method if
your authentication object needs to support stateless or cookie-less authentication. See the sections on basic
and digest authentication below for more information.
AuthComponent triggers two events, Auth.afterIdentify and Auth.logout, after a user has
been identified and before a user is logged out respectively. You can set callback functions for these events
by returning a mapping array from implementedEvents() method of your authenticate class:
public function implementedEvents()
{
return [
'Auth.afterIdentify' => 'afterIdentify',
'Auth.logout' => 'logout'
];
}
Note: Note that when using simple notation theres no Authenticate word when initiating the authentication object. Instead, if using namespaces, youll need to set the full namespace of the class, including the
Authenticate word.
More on Controllers
215
You can customize the error messages and flash settings AuthComponent uses. Using flash config you
can configure the parameters AuthComponent uses for setting flash messages. The available keys are
key - The key to use, defaults to auth.
params - The array of additional params to use, defaults to [].
In addition to the flash message settings you can customize other error messages AuthComponent uses. In
your controllers beforeFilter, or component settings you can use authError to customize the error used
for when authorization fails:
$this->Auth->config('authError', "Woopsie, you are not authorized to access
this area.");
Sometimes, you want to display the authorization error only after the user has already logged-in. You can
suppress this message by setting its value to boolean false.
In your controllers beforeFilter() or component settings:
if (!$this->Auth->user()) {
$this->Auth->config('authError', false);
}
Hashing Passwords
You are responsible for hashing the passwords before they are persisted to the database, the easiest way is
to use a setter function in your User entity:
namespace App\Model\Entity;
use Cake\Auth\DefaultPasswordHasher;
216
use Cake\ORM\Entity;
class User extends Entity
{
// ...
protected function _setPassword($password)
{
if (strlen($password) > 0) {
return (new DefaultPasswordHasher)->hash($password);
}
}
// ...
}
Then you are required to configure the AuthComponent to use your own password hasher:
More on Controllers
217
Supporting legacy systems is a good idea, but it is even better to keep your database with the latest security
advancements. The following section will explain how to migrate from one hashing algorithm to CakePHPs
default.
Changing Hashing Algorithms
CakePHP provides a clean way to migrate your users passwords from one algorithm to another, this is
achieved through the FallbackPasswordHasher class. Assuming you are migrating your app from
CakePHP 2.x which uses sha1 password hashes, you can configure the AuthComponent as follows:
public function initialize()
{
parent::initialize();
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'passwordHasher' => [
'className' => 'Fallback',
'hashers' => [
'Default',
'Weak' => ['hashType' => 'sha1']
]
]
]
]
]);
}
The first name appearing in the hashers key indicates which of the classes is the preferred one, but it will
fallback to the others in the list if the check was unsuccessful.
When using the WeakPasswordHasher you will need to set the Security.salt configure value to
ensure passwords are salted.
In order to update old users passwords on the fly, you can change the login function accordingly:
218
As you can see we are just setting the plain password again so the setter function in the entity will hash the
password as shown in the previous example and then save the entity.
Manually Logging Users In
AuthComponent::setUser(array $user)
Sometimes the need arises where you need to manually log a user in, such as just after they registered for
your application. You can do this by calling $this->Auth->setUser() with the user data you want
to login:
public function register()
{
$user = $this->Users->newEntity($this->request->data);
if ($this->Users->save($user)) {
$this->Auth->setUser($user->toArray());
return $this->redirect([
'controller' => 'Users',
'action' => 'home'
]);
}
}
Warning: Be sure to manually add the new User id to the array passed to the setUser() method.
Otherwise you wont have the user id available.
More on Controllers
219
Once a user is logged in, you will often need some particular information about the current user. You can
access the currently logged in user using AuthComponent::user():
// From inside a controller or other component.
$this->Auth->user('id');
If the current user is not logged in or the key doesnt exist, null will be returned.
Logging Users Out
AuthComponent::logout()
Eventually youll want a quick way to de-authenticate someone and redirect them to where they need to
go. This method is also useful if you want to provide a Log me out link inside a members area of your
application:
public function logout()
{
return $this->redirect($this->Auth->logout());
}
Logging out users that logged in with Digest or Basic auth is difficult to accomplish for all clients. Most
browsers will retain credentials for the duration they are still open. Some clients can be forced to logout
by sending a 401 status code. Changing the authentication realm is another solution that works for some
clients.
Deciding When to run Authentication
In some cases you may want to use $this->Auth->user() in the beforeFilter(Event
$event) method. This is achievable by using the checkAuthIn config key. The following changes
which event for which initial authentication checks should be done:
//Set up AuthComponent to authenticate in initialize()
$this->Auth->config('checkAuthIn', 'Controller.initialize');
220
Note: The ActionsAuthorize & CrudAuthorize adapter available in CakePHP 2.x have now been
moved to a separate plugin cakephp/acl99 .
Much like authenticate, authorize, helps you keep your code DRY, by using the all key. This
special key allows you to set settings that are passed to every attached object. The all key is also exposed
as AuthComponent::ALL:
// Pass settings in using 'all'
$this->Auth->config('authorize', [
AuthComponent::ALL => ['actionPath' => 'controllers/'],
'Actions',
'Controller'
]);
In the above example, both the Actions and Controller will get the settings defined for the all key.
Any settings passed to a specific authorization object will override the matching key in the all key.
If an authenticated user tries to go to a URL hes not authorized to access, hes redirected back to the
referrer. If you do not want such redirection (mostly needed when using stateless authentication adapter)
you can set config option unauthorizedRedirect to false. This causes AuthComponent to throw a
ForbiddenException instead of redirecting.
99
https://github.com/cakephp/acl
More on Controllers
221
Authorize objects should return false if the user is denied access, or if the object is unable to perform a
check. If the object is able to verify the users access, true should be returned. Its not required that you
extend BaseAuthorize, only that your authorize object implements an authorize() method. The
BaseAuthorize class provides a number of helpful methods that are commonly used.
Using Custom Authorize Objects
Once youve created your custom authorize object, you can use them by including them in your AuthComponents authorize array:
$this->Auth->config('authorize', [
'Ldap', // app authorize object.
'AuthBag.Combo', // plugin authorize object.
]);
Using No Authorization
If youd like to not use any of the built-in authorization objects and want to handle things entirely outside of
AuthComponent, you can set $this->Auth->config('authorize',false);. By default AuthComponent starts off with authorize set to false. If you dont use an authorization scheme, make sure
to check authorization yourself in your controllers beforeFilter or with another component.
Making Actions Public
AuthComponent::allow($actions = null)
There are often times controller actions that you wish to remain entirely public or that dont require users to
be logged in. AuthComponent is pessimistic and defaults to denying access. You can mark actions as public
222
By calling it empty you allow all actions to be public. For a single action you can provide the action name
as string. Otherwise use an array.
Note: You should not add the login action of your UsersController to allow list. Doing so would
cause problems with normal functioning of AuthComponent.
By calling it empty you deny all actions. For a single action you can provide the action name as string.
Otherwise use an array.
Using ControllerAuthorize
ControllerAuthorize allows you to handle authorization checks in a controller callback. This is ideal when
you have very simple authorization or you need to use a combination of models and components to do your
authorization and dont want to create a custom authorize object.
The callback is always called isAuthorized() and it should return a boolean as to whether or not the
user is allowed to access resources in the request. The callback is passed the active user so it can be checked:
class AppController extends Controller
{
More on Controllers
223
The above callback would provide a very simple authorization system where only users with role = admin
could access actions that were in the admin prefix.
Configuration options
The following settings can all be defined either in your controllers initialize() method or using
$this->Auth->config() in your beforeFilter():
ajaxLogin The name of an optional view element to render when an AJAX request is made with an invalid
or expired session.
allowedActions Controller actions for which user validation is not required.
authenticate Set to an array of Authentication objects you want to use when logging users in. There are
several core authentication objects; see the section on Suggested Reading Before Continuing.
authError Error to display when user attempts to access an object or action to which they do not have
access.
You can suppress authError message from being displayed by setting this value to boolean false.
authorize Set to an array of Authorization objects you want to use when authorizing users on each request;
see the section on Authorization.
flash Settings to use when Auth needs to do a flash message with FlashComponent::set(). Available
keys are:
element - The element to use; defaults to default.
224
This is useful if you want to redirect an user to the login route for example. Without a parameter, the full
configuration will be returned.
Testing Actions Protected By AuthComponent
See the Testing Actions That Require Authentication section for tips on how to test controller actions that
are protected by AuthComponent.
Cookie
class Cake\Controller\Component\CookieComponent(ComponentRegistry $collection,
array $config =[])
The CookieComponent is a wrapper around the native PHP setcookie() method. It makes it easier to
manipulate cookies, and automatically encrypt cookie data.
More on Controllers
225
Configuring Cookies
Cookies can be configured either globally or per top-level name. The global configuration data will be
merged with the top-level configuration. So only need to override the parts that are different. To configure
the global settings use the config() method:
$this->Cookie->config('path', '/');
$this->Cookie->config([
'expires' => '+10 days',
'httpOnly' => true
]);
226
You can also group your variables by using dot notation in the key parameter:
$this->Cookie->write('User.name', 'Larry');
$this->Cookie->write('User.role', 'Lead');
If you want to write more than one value to the cookie at a time, you can pass an array:
$this->Cookie->write('User',
['name' => 'Larry', 'role' => 'Lead']
);
All values in the cookie are encrypted with AES by default. If you want to store the values as plain
text, be sure to configure the key space:
$this->Cookie->configKey('User', 'encryption', false);
Warning: CookieComponent cannot interact with bare strings values that contain ,. The component will attempt to interpret these values as arrays, leading to incorrect results. Instead you
should use $request->cookie().
Cake\Controller\Component\CookieComponent::check($key)
Parameters
$key (string) The key to check.
Used to check whether a key/path exists and has a non-null value.
Cake\Controller\Component\CookieComponent::delete(mixed $key)
Deletes a cookie variable of the name in $key. Works with dot notation:
// Delete a variable
$this->Cookie->delete('bar');
// Delete the cookie variable bar, but not everything under foo
$this->Cookie->delete('foo.bar');
More on Controllers
227
Settings can be passed into the component through your components settings. The available configuration
options are:
cookieName The name of the cookie to send. Defaults to csrfToken.
expiry How long the CSRF token should last. Defaults to browser session. Accepts strtotime
values as of 3.1
secure Whether or not the cookie will be set with the Secure flag. That is, the cookie will only be
set on a HTTPS connection and any attempt over normal HTTP will fail. Defaults to false.
field The form field to check. Defaults to _csrfToken. Changing this will also require configuring FormHelper.
When enabled, you can access the current CSRF token on the request object:
$token = $this->request->param('_csrfToken');
100
228
http://en.wikipedia.org/wiki/Cross-site_request_forgery
Flash
class Cake\Controller\Component\FlashComponent(ComponentCollection
$collection, array $config =[])
FlashComponent provides a way to set one-time notification messages to be displayed after processing a
form or acknowledging data. CakePHP refers to these messages as flash messages. FlashComponent
writes flash messages to $_SESSION, to be rendered in a View using FlashHelper.
Setting Flash Messages
FlashComponent provides two ways to set flash messages: its __call() magic method and its set()
method. To furnish your application with verbosity, FlashComponents __call() magic method allows
you use a method name that maps to an element located under the src/Template/Element/Flash
directory. By convention, camelcased methods will map to the lowercased and underscored element name:
// Uses src/Template/Element/Flash/success.ctp
$this->Flash->success('This was successful');
More on Controllers
229
// Uses src/Template/Element/Flash/great_success.ctp
$this->Flash->greatSuccess('This was greatly successful');
Alternatively, to set a plain-text message without rendering an element, you can use the set() method:
$this->Flash->set('This is a message');
New in version 3.1: Flash messages now stack. Successive calls to set() or __call() with the same
key will append the messages in the $_SESSION. If you want to keep the old behavior (one message even
after consecutive calls), set the clear parameter to true when configuring the Component.
FlashComponents __call() and set() methods optionally take a second parameter, an array of options:
key Defaults to flash. The array key found under the Flash key in the session.
element Defaults to null, but will automatically be set when using the __call() magic method.
The element name to use for rendering.
params An optional array of keys/values to make available as variables within an element.
New in version 3.1: A new key clear was added. This key expects a bool and allows you to delete all
messages in the current stack and start a new one.
An example of using these options:
// In your Controller
$this->Flash->success('The user has been saved', [
'key' => 'positive',
'params' => [
'name' => $user->name,
'email' => $user->email
]
]);
// In your View
<?= $this->Flash->render('positive') ?>
<!-- In src/Template/Element/Flash/success.ctp -->
<div id="flash-<?= h($key) ?>" class="message-info success">
<?= h($message) ?>: <?= h($params['name']) ?>, <?= h($params['email']) ?>.
</div>
Note that the parameter element will be always overridden while using __call(). In order to retrieve a
specific element from a plugin, you should set the plugin parameter. For example:
// In your Controller
$this->Flash->warning('My message', ['plugin' => 'PluginName']);
The code above will use the warning.ctp element under plugins/PluginName/src/Template/Element/Flas
for rendering the flash message.
Note: By default, CakePHP escapes the content in flash messages for security reasons. If you are using any
request or user data in your flash messages those are escaped and therefore safe to be printed. If you want
230
to output HTML, you need to pass in an escape param and also adjust the templates to allow disabling
escaping when such a param is passed.
Make sure that you escape the input manually, then. In the above example $highlight and $message
are non-HTML input and therefore escaped.
For more information about rendering your flash messages, please refer to the FlashHelper section.
Security
class SecurityComponent(ComponentCollection $collection, array $config =[])
The Security Component creates an easy way to integrate tighter security in your application. It provides
methods for various tasks like:
Restricting which HTTP methods your application accepts.
Form tampering protection
Requiring that SSL be used.
Limiting cross controller communication.
Like all components it is configured through several configurable parameters. All of these properties can be
set directly or through setter methods of the same name in your controllers beforeFilter.
By using the Security Component you automatically get form tampering protection. Hidden token fields
will automatically be inserted into forms and checked by the Security component.
If you are using Security components form protection features and other components that process form
data in their startup() callbacks, be sure to place Security Component before those components in your
initialize() method.
Note: When using the Security Component you must use the FormHelper to create your forms. In
addition, you must not override any of the fields name attributes. The Security Component looks
for certain indicators that are created and managed by the FormHelper (especially those created in
View\Helper\FormHelper::create() and View\Helper\FormHelper::end()). Dynamically altering the fields that are submitted in a POST request (e.g. disabling, deleting or creating new fields
via JavaScript) is likely to cause the request to be send to the blackhole callback. See the $validatePost
or $disabledFields configuration parameters.
More on Controllers
231
You should always verify the HTTP method being used before executing side-effects. You should check
the HTTP method or use Cake\Network\Request::allowMethod() to ensure the correct HTTP
method is used.
232
More on Controllers
233
use App\Controller\AppController;
use Cake\Event\Event;
class WidgetsController extends AppController
{
public function initialize()
{
parent::initialize();
$this->loadComponent('Security');
}
public function beforeFilter(Event $event)
{
if (isset($this->request->params['admin'])) {
$this->Security->requireSecure();
}
}
}
The above example would force all actions that had admin routing to require secure SSL requests:
namespace App\Controller;
use App\Controller\AppController;
use Cake\Event\Event;
class WidgetsController extends AppController
{
public function initialize()
{
parent::initialize();
$this->loadComponent('Security', ['blackHoleCallback' => 'forceSSL']);
}
public function beforeFilter(Event $event)
{
if (isset($this->params['admin'])) {
$this->Security->requireSecure();
}
}
public function forceSSL()
{
return $this->redirect('https://' . env('SERVER_NAME') . $this->
request->here);
}
}
This example would force all actions that had admin routing to require secure SSL requests. When the
request is black holed, it will call the nominated forceSSL() callback which will redirect non-secure
requests to secure requests automatically.
234
CSRF Protection
CSRF or Cross Site Request Forgery is a common vulnerability in web applications. It allows an attacker to
capture and replay a previous request, and sometimes submit data requests using image tags or resources on
other domains. To enable CSRF protection features use the Cross Site Request Forgery.
Disabling Security Component for Specific Actions
There may be cases where you want to disable all security checks for an action (ex. AJAX requests). You may unlock these actions by listing them in $this->Security->unlockedActions
in your beforeFilter(). The unlockedActions property will not affect other features of
SecurityComponent:
namespace App\Controller;
use App\Controller\AppController;
use Cake\Event\Event;
class WidgetController extends AppController
{
public function initialize()
{
parent::initialize();
$this->loadComponent('Security');
}
public function beforeFilter(Event $event)
{
$this->Security->config('unlockedActions', ['edit']);
}
}
This example would disable all security checks for the edit action.
Pagination
class Cake\Controller\Component\PaginatorComponent
One of the main obstacles of creating flexible and user-friendly web applications is designing an intuitive
user interface. Many applications tend to grow in size and complexity quickly, and designers and programmers alike find they are unable to cope with displaying hundreds or thousands of records. Refactoring takes
time, and performance and user satisfaction can suffer.
Displaying a reasonable number of records per page has always been a critical part of every application and
used to cause many headaches for developers. CakePHP eases the burden on the developer by providing a
quick, easy way to paginate data.
Pagination in CakePHP is offered by a Component in the controller, to make building paginated queries
easier. In the View View\Helper\PaginatorHelper is used to make the generation of pagination
More on Controllers
235
You can also include any of the options supported by ORM\Table::find(), such as fields:
class ArticlesController extends AppController
{
public $paginate = [
'fields' => ['Articles.id', 'Articles.created'],
'limit' => 25,
'order' => [
'Articles.title' => 'asc'
]
];
public function initialize()
{
parent::initialize();
$this->loadComponent('Paginator');
}
}
While you can pass most of the query options from the paginate property it is often cleaner and simpler to
bundle up your pagination options into a Custom Finder Methods. You can define the finder pagination uses
by setting the finder option:
236
Because custom finder methods can also take in options, this is how you pass in options into a custom finder
method within the paginate property:
class ArticlesController extends AppController
{
// find articles by tag
public function tags()
{
$tags = $this->request->params['pass'];
$customFinderOptions = [
'tags' => $tags
];
// the custom finder method is called findTagged inside ArticlesTable.
php
// it should look like this:
// public function findTagged(Query $query, array $options) {
// hence you use tagged as the key
$this->paginate = [
'finder' => [
'tagged' => $customFinderOptions
]
];
$articles = $this->paginate($this->Articles);
$this->set(compact('articles', 'tags'));
}
In addition to defining general pagination values, you can define more than one set of pagination defaults in
the controller, you just name the keys of the array after the model you wish to configure:
class ArticlesController extends AppController
{
public $paginate = [
'Articles' => [],
'Authors' => [],
];
}
The values of the Articles and Authors keys could contain all the properties that a model/key less
$paginate array could.
More on Controllers
237
Once
the
$paginate
property
has
been
defined,
we
can
use
the
Controller\Controller::paginate() method to create the pagination data, and add the
PaginatorHelper if it hasnt already been added. The controllers paginate method will return
the result set of the paginated query, and set pagination metadata to the request. You can access the
pagination metadata at $this->request->params['paging']. A more complete example of using
paginate() would be:
class ArticlesController extends AppController
{
public function index()
{
$this->set('articles', $this->paginate());
}
}
By default the paginate() method will use the default model for a controller. You can also pass the
resulting query of a find method:
public function index()
{
$query = $this->Articles->find('popular')->where(['author_id' => 1]);
$this->set('articles', $this->paginate($query));
}
If you want to paginate a different model you can provide a query for it, the table object itself, or its name:
// Using a query
$comments = $this->paginate($commentsTable->find());
// Using the model name.
$comments = $this->paginate('Comments');
// Using a table object.
$comments = $this->paginate($commentTable);
The first parameter should be the query object from a find on table object you wish to paginate results
from. Optionally, you can pass the table object and let the query be constructed for you. The second
parameter should be the array of settings to use for pagination. This array should have the same structure
238
as the $paginate property on a controller. When paginating a Query object, the finder option will be
ignored. It is assumed that you are passing in the query you want paginated.
Paginating Multiple Queries
You can paginate multiple models in a single controller action, using the scope option:
// In a controller action
$articles = $this->paginate($this->Articles, ['scope' => 'article']);
$tags = $this->paginate($this->Tags, ['scope' => 'tag']);
$this->set(compact('articles', 'tags'));
The scope option will result in PaginatorComponent looking in scoped query string parameters. For
example, the following URL could be used to paginate both tags and articles at the same time:
/dashboard?article[page]=1&tag[page]=3
See the Paginating Multiple Results section for how to generate scoped HTML elements and URLs for
pagination.
New in version 3.3.0: Multiple Pagination was added in 3.3.0
Control which Fields Used for Ordering
By default sorting can be done on any non-virtual column a table has. This is sometimes undesirable as it
allows users to sort on un-indexed columns that can be expensive to order by. You can set the whitelist of
fields that can be sorted using the sortWhitelist option. This option is required when you want to sort
on any associated data, or computed fields that may be part of your pagination query:
public $paginate = [
'sortWhitelist' => [
'id', 'title', 'Users.username', 'created'
]
];
Any requests that attempt to sort on fields not in the whitelist will be ignored.
Limit the Maximum Number of Rows that can be Fetched
The number of results that are fetched is exposed to the user as the limit parameter. It is generally
undesirable to allow users to fetch all rows in a paginated set. By default CakePHP limits the maximum
number of rows that can be fetched to 100. If this default is not appropriate for your application, you can
adjust it as part of the pagination options:
public $paginate = [
// Other keys here.
'maxLimit' => 10
];
More on Controllers
239
If the requests limit param is greater than this value, it will be reduced to the maxLimit value.
Joining Additional Associations
Additional associations can be loaded to the paginated table by using the contain parameter:
public function index()
{
$this->paginate = [
'contain' => ['Authors', 'Comments']
];
$this->set('articles', $this->paginate($this->Articles));
}
240
gain additional insight into content types that the client accepts and automatically changes to the appropriate
layout when file extensions are enabled.
By default RequestHandler will automatically detect AJAX requests based on the HTTP-XRequested-With header that many JavaScript libraries use.
When used in conjunction with
Cake\Routing\Router::extensions(), RequestHandler will automatically switch the layout
and template files to those that match the requested type. Furthermore, if a helper with the same
name as the requested extension exists, it will be added to the Controllers Helper array. Lastly, if
XML/JSON data is POSTed to your Controllers, it will be parsed into an array which is assigned to
$this->request->data, and can then be saved as model data. In order to make use of RequestHandler
it must be included in your initialize() method:
class WidgetsController extends AppController
{
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler');
}
// Rest of controller
}
More on Controllers
241
Symbian
UP.Browser
webOS
Windows CE
Windows Phone OS
Xiino
RequestHandlerComponent::isWap()
Returns true if the client accepts WAP content.
All of the above request detection methods can be used in a similar fashion to filter functionality intended
for specific content types. For example when responding to AJAX requests, you often will want to disable
browser caching, and change the debug level. However, you want to allow caching for non-AJAX requests.
The following would accomplish that:
if ($this->request->is('ajax')) {
$this->response->disableCache();
}
// Continue Controller action
More on Controllers
243
You can use any callable101 for the handling function. You can also pass additional arguments to the callback,
this is useful for callbacks like json_decode:
$this->RequestHandler->addInputType('json', ['json_decode', true]);
// After 3.1.0 you should use
$this->RequestHandler->config('inputTypeMap.json', ['json_decode', true]);
The above will make $this->request->data an array of the JSON input data, without the additional
true youd get a set of StdClass objects.
Deprecated since version 3.1.0: As of 3.1.0 the addInputType() method is deprecated. You should use
config() to add input types at runtime.
Checking Content-Type Preferences
RequestHandlerComponent::prefers($type = null)
Determines which content-types the client prefers. If no parameter is given the most likely content type
is returned. If $type is an array the first type the client accepts will be returned. Preference is determined
primarily by the file extension parsed by Router if one has been provided, and secondly by the list of contenttypes in HTTP\_ACCEPT:
$this->RequestHandler->prefers('json');
Responding To Requests
RequestHandlerComponent::renderAs($controller, $type)
Change the render mode of a controller to the specified type. Will also append the appropriate helper to the
controllers helper array if available and not already in the array:
// Force the controller to render an xml response.
$this->RequestHandler->renderAs($this, 'xml');
This method will also attempt to add a helper that matches your current content type. For example if you
render as rss, the RssHelper will be added.
RequestHandlerComponent::respondAs($type, $options)
Sets the response header based on content-type map names. This method lets you set a number of response
properties at once:
$this->RequestHandler->respondAs('xml', [
// Force download
'attachment' => true,
'charset' => 'UTF-8'
]);
101
244
http://php.net/callback
RequestHandlerComponent::responseType()
Returns the current response type Content-type header or null if one has yet to be set.
Taking Advantage of HTTP Cache Validation
The HTTP cache validation model is one of the processes used for cache gateways, also known as reverse
proxies, to determine if they can serve a stored copy of a response to the client. Under this model, you
mostly save bandwidth, but when used correctly you can also save some CPU processing, reducing this way
response times.
Enabling the RequestHandlerComponent in your controller automatically activates a check done before
rendering the view. This check compares the response object against the original request to determine
whether the response was not modified since the last time the client asked for it.
If response is evaluated as not modified, then the view rendering process is stopped, saving processing time,
saving bandwidth and no content is returned to the client. The response status code is then set to 304 Not
Modified.
You can opt-out this automatic checking by setting the checkHttpCache setting to false:
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler', [
'checkHttpCache' => false
]);
}
Deprecated since version 3.1.0: As of 3.1.0 the viewClassMap() method is deprecated. You should use
config() to change the viewClassMap at runtime.
More on Controllers
245
Configuring Components
Many of the core components require configuration. Some examples of components requiring configuration are Authentication and Cookie. Configuration for these components, and for components in general, is usually done via loadComponent() in your Controllers initialize() method or via the
$components array:
class PostsController extends AppController
{
public function initialize()
{
parent::initialize();
$this->loadComponent('Auth', [
'authorize' => 'Controller',
'loginAction' => ['controller' => 'Users', 'action' => 'login']
]);
$this->loadComponent('Cookie', ['expiry' => '1 day']);
}
}
You can configure components at runtime using the config() method. Often, this is done in your controllers beforeFilter() method. The above could also be expressed as:
public function beforeFilter(Event $event)
{
$this->Auth->config('authorize', ['controller']);
$this->Auth->config('loginAction', ['controller' => 'Users', 'action' =>
'login']);
$this->Cookie->config('name', 'CookieMonster');
}
Like helpers, components implement a config() method that is used to get and set any configuration data
for a component:
// Read config data.
$this->Auth->config('loginAction');
// Set config
$this->Csrf->config('cookieName', 'token');
As with helpers, components will automatically merge their $_defaultConfig property with constructor configuration to create the $_config property which is accessible with config().
Aliasing Components
One common setting to use is the className option, which allows you to alias components. This feature
is useful when you want to replace $this->Auth or another common Component reference with a custom
implementation:
246
// src/Controller/PostsController.php
class PostsController extends AppController
{
public function initialize()
{
$this->loadComponent('Auth', [
'className' => 'MyAuth'
]);
}
}
// src/Controller/Component/MyAuthComponent.php
use Cake\Controller\Component\AuthComponent;
class MyAuthComponent extends AuthComponent
{
// Add your code to override the core AuthComponent
}
Note: Keep in mind that components loaded on the fly will not have missed callbacks called. If you rely on
the beforeFilter or startup callbacks being called, you may need to call them manually depending
on when you load your component.
Using Components
Once youve included some components in your controller, using them is pretty simple.
Each component you use is exposed as a property on your controller.
If you
had
loaded
up
the
Cake\Controller\Component\FlashComponent
and
the
Cake\Controller\Component\CookieComponent in your controller, you could access them like
so:
More on Controllers
247
Note: Since both Models and Components are added to Controllers as properties they share the same
namespace. Be sure to not give a component and a model the same name.
Creating a Component
Suppose our application needs to perform a complex mathematical operation in many different parts of the
application. We could create a component to house this shared logic for use in many different controllers.
The first step is to create a new component file and class.
Create the file in
src/Controller/Component/MathComponent.php.
The basic structure for the component would
look something like this:
namespace App\Controller\Component;
use Cake\Controller\Component;
class MathComponent extends Component
{
public function doComplexOperation($amount1, $amount2)
{
return $amount1 + $amount2;
}
}
Note: All components must extend Cake\Controller\Component. Failing to do this will trigger an
exception.
248
When including Components in a Controller you can also declare a set of parameters that will be passed on
to the Components constructor. These parameters can then be handled by the Component:
// In your controller.
public function initialize()
{
parent::initialize();
$this->loadComponent('Math', [
'precision' => 2,
'randomGenerator' => 'srand'
]);
$this->loadComponent('Csrf');
}
and
randomGenerator
to
More on Controllers
249
You can access the controller in any callback method from the event object:
$controller = $event->subject();
Component Callbacks
Components also offer a few request life-cycle callbacks that allow them to augment the request cycle.
beforeFilter(Event $event)
Is called before the controllers beforeFilter method, but after the controllers initialize() method.
startup(Event $event)
Is called after the controllers beforeFilter method but before the controller executes the current action
handler.
250
beforeRender(Event $event)
Is called after the controller executes the requested actions logic, but before the controller renders
views and layout.
shutdown(Event $event)
Is called before output is sent to the browser.
beforeRedirect(Event $event, $url, Response $response)
Is invoked when the controllers redirect method is called but before any further action. If this method
returns false the controller will not continue on to redirect the request. The $url, and $response
parameters allow you to inspect and modify the location or any other headers in the response.
More on Controllers
251
252
CHAPTER 11
Views
class Cake\View\View
Views are the V in MVC. Views are responsible for generating the specific output required for the request.
Often this is in the form of HTML, XML, or JSON, but streaming files and creating PDFs that users can
download are also responsibilities of the View Layer.
CakePHP comes with a few built-in View classes for handling the most common rendering scenarios:
To create XML or JSON webservices you can use the JSON and XML views.
To serve protected files, or dynamically generated files, you can use Sending Files.
To create multiple themed views, you can use Themes.
You can use your AppView to load helpers that will be used for every view rendered in your application.
CakePHP provides an initialize() method that is invoked at the end of a Views constructor for this
kind of use:
<?php
namespace App\View;
253
use Cake\View\View;
class AppView extends View
{
public function initialize()
{
// Always enable the MyUtils Helper
$this->loadHelper('MyUtils');
}
}
View Templates
The view layer of CakePHP is how you speak to your users. Most of the time your views will be rendering
HTML/XHTML documents to browsers, but you might also need to reply to a remote application via JSON,
or output a CSV file for a user.
CakePHP template files have a default extension of .ctp (CakePHP Template) and utilize the alternative
PHP syntax102 for control structures and output. These files contain the logic necessary to prepare the data
received from the controller into a presentation format that is ready for your audience.
Alternative Echos
Echo, or print a variable in your template:
<?php echo $variable; ?>
254
http://php.net/manual/en/control-structures.alternative-syntax.php
<ul>
<?php foreach ($todo as $item): ?>
<li><?= $item ?></li>
<?php endforeach; ?>
</ul>
If youd prefer using a templating language like Twig103 , a subclass of View will bridge your templating
language and CakePHP.
Template files are stored in src/Template/, in a folder named after the controller that uses the files, and
named after the action it corresponds to. For example, the view file for the Products controllers view()
action, would normally be found in src/Template/Products/view.ctp.
The view layer in CakePHP can be made up of a number of different parts. Each part has different uses, and
will be covered in this chapter:
views: Templates are the part of the page that is unique to the action being run. They form the meat
of your applications response.
elements: small, reusable bits of view code. Elements are usually rendered inside views.
layouts: template files that contain presentational code that wraps many interfaces in your application.
Most views are rendered inside a layout.
helpers: these classes encapsulate view logic that is needed in many places in the view layer. Among
other things, helpers in CakePHP can help you build forms, build AJAX functionality, paginate model
data, or serve RSS feeds.
cells: these classes provide miniature controller-like features for creating self contained UI components. See the View Cells documentation for more information.
View Variables
Any variables you set in your controller with set() will be available in both the view and the layout your
action renders. In addition, any set variables will also be available in any element. If you need to pass
additional variables from the view to the layout you can either call set() in the view template, or use a
Using View Blocks.
You should remember to always escape any user data before outputting it as CakePHP does not automatically escape output. You can escape user content with the h() function:
103
http://twig.sensiolabs.org
View Templates
255
Then, in your layout, the $activeMenuButton variable will be available and contain the value posts.
Extending Views
View extending allows you to wrap one view in another. Combining this with view blocks gives you a
powerful way to keep your views DRY. For example, your application has a sidebar that needs to change
depending on the specific view being rendered. By extending a common view file, you can avoid repeating
the common markup for your sidebar, and only define the parts that change:
<!-- src/Template/Common/view.ctp -->
<h1><?= $this->fetch('title') ?></h1>
<?= $this->fetch('content') ?>
<div class="actions">
<h3>Related actions</h3>
<ul>
<?= $this->fetch('sidebar') ?>
</ul>
</div>
The above view file could be used as a parent view. It expects that the view extending it will define the
sidebar and title blocks. The content block is a special block that CakePHP creates. It will contain
all the uncaptured content from the extending view. Assuming our view file has a $post variable with the
data about our post, the view could look like:
<!-- src/Template/Posts/view.ctp -->
<?php
$this->extend('/Common/view');
$this->assign('title', $post);
$this->start('sidebar');
?>
<li>
<?php
256
echo $this->Html->link('edit', [
'action' => 'edit',
$post->id
]); ?>
</li>
<?php $this->end(); ?>
// The remaining content will be available as the 'content' block
// In the parent view.
<?= h($post->body) ?>
The post view above shows how you can extend a view, and populate a set of blocks. Any content not already
in a defined block will be captured and put into a special block named content. When a view contains
a call to extend(), execution continues to the bottom of the current view file. Once it is complete, the
extended view will be rendered. Calling extend() more than once in a view file will override the parent
view that will be processed next:
$this->extend('/Common/view');
$this->extend('/Common/index');
The above will result in /Common/index.ctp being rendered as the parent view to the current view.
You can nest extended views as many times as necessary. Each view can extend another view if desired.
Each parent view will get the previous views content as the content block.
Note: You should avoid using content as a block name in your application. CakePHP uses this for
uncaptured content in extended views.
You can get the list of all populated blocks using the blocks() method:
$list = $this->blocks();
257
echo $this->fetch('sidebar');
echo $this->element('sidebar/popular_topics');
$this->end();
If you need to clear or overwrite a block there are a couple of alternatives. The reset() method will clear
or overwrite a block at any time. The assign() method with an empty content string can also be used to
clear the specified block.:
// Clear the previous content from the sidebar block.
$this->reset('sidebar');
// Assigning an empty string will also clear the sidebar block.
$this->assign('sidebar', '');
Note: You should avoid using content as a block name. This is used by CakePHP internally for extended
views, and view content in the layout.
Displaying Blocks
You can display blocks using the fetch() method. fetch() will output a block, returning if a block
does not exist:
<?= $this->fetch('sidebar') ?>
You can also use fetch to conditionally show content that should surround a block should it exist. This is
helpful in layouts, or extended views where you want to conditionally show headings or other markup:
258
// In src/Template/Layout/default.ctp
<?php if ($this->fetch('menu')): ?>
<div class="menu">
<h3>Menu options</h3>
<?= $this->fetch('menu') ?>
</div>
<?php endif; ?>
You can also provide a default value for a block if it does not exist. This allows you to add placeholder
content when a block does not exist. You can provide a default value using the second argument:
<div class="shopping-cart">
<h3>Your Cart</h3>
<?= $this->fetch('cart', 'Your cart is empty') ?>
</div>
The Cake\View\Helper\HtmlHelper also allows you to control which block the scripts and CSS go
to:
// In your view
$this->Html->script('carousel', ['block' => 'scriptBottom']);
// In your layout
<?= $this->fetch('scriptBottom') ?>
259
Layouts
A layout contains presentation code that wraps around a view. Anything you want to see in all of your views
should be placed in a layout.
CakePHPs default layout is located at src/Template/Layout/default.ctp. If you want to change the overall
look of your application, then this is the right place to start, because controller-rendered view code is placed
inside of the default layout when the page is rendered.
Other layout files should be placed in src/Template/Layout. When you create a layout, you need to tell
CakePHP where to place the output of your views. To do so, make sure your layout includes a place for
$this->fetch('content') Heres an example of what a default layout might look like:
<!DOCTYPE html>
<html lang="en">
<head>
<title><?= h($this->fetch('title')) ?></title>
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
<!-- Include external files and scripts here (See HTML helper for more info.)
-->
<?php
echo $this->fetch('meta');
echo $this->fetch('css');
echo $this->fetch('script');
?>
</head>
<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 -->
<?= $this->fetch('content') ?>
<!-- Add a footer to each displayed page -->
<div id="footer">...</div>
</body>
</html>
The script, css and meta blocks contain any content defined in the views using the built-in HTML
helper. Useful for including JavaScript and CSS files from views.
Note: When using HtmlHelper::css() or HtmlHelper::script() in template files, specify
'block' => true to place the HTML source in a block with the same name. (See API for more details
on usage).
The content block contains the contents of the rendered view.
260
You can set the title block content from inside your view file:
$this->assign('title', 'View Active Users');
You can create as many layouts as you wish: just place them in the src/Template/Layout directory, and
switch between them inside of your controller actions using the controller or views $layout property:
// From a controller
public function admin_view()
{
// Set the layout.
$this->viewBuilder()->layout('admin');
// Before 3.1
$this->layout = 'admin';
}
// From a view file
$this->layout = 'loggedin';
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 controllers actions using something like:
namespace App\Controller;
class UsersController extends AppController
{
public function view_active()
{
$this->set('title', 'View Active Users');
$this->viewBuilder()->layout('default_small_ad');
// or the following before 3.1
$this->layout = 'default_small_ad';
}
public function view_image()
{
$this->viewBuilder()->layout('image');
// or the following before 3.1
$this->layout = 'image';
// Output user image
}
}
Besides a default layout CakePHPs official skeleton app also has an ajax layout. The Ajax layout is handy
for crafting AJAX responses - its an empty layout. (Most AJAX calls only require a bit of markup in return,
rather than a fully-rendered interface.)
The skeleton app also has a default layout to help generate RSS.
Layouts
261
Elements
Cake\View\View::element(string $elementPath, array $data, array $options =[])
Many applications have small blocks of presentation code that need to be repeated from page to page,
sometimes in different places in the layout. CakePHP can help you repeat parts of your website that need to
be reused. These reusable parts are called Elements. Ads, help boxes, navigational controls, extra menus,
login forms, and callouts are often implemented in CakePHP as elements. An element is basically a miniview that can be included in other views, in layouts, and even within other elements. Elements can be used
to make a view more readable, placing the rendering of repeating elements in its own file. They can also
help you re-use content fragments in your application.
Elements live in the src/Template/Element/ folder, and have the .ctp filename extension. They are output
using the element method of the view:
echo $this->element('helpbox');
Inside the element file, all the passed variables are available as members of the parameter array (in the same
way that Controller::set() in the controller works with template files). In the above example, the
src/Template/Element/helpbox.ctp file can use the $helptext variable:
// Inside src/Template/Element/helpbox.ctp
echo $helptext; // Outputs "Oh, this text is very helpful."
262
The View::element() method also supports options for the element. The options supported are cache
and callbacks. An example:
echo $this->element('helpbox', [
"helptext" => "This is passed to the element as $helptext",
"foobar" => "This is passed to the element as $foobar",
],
[
// uses the "long_view" cache configuration
"cache" => "long_view",
// set to true to have before/afterRender called for the element
"callbacks" => true
]
);
Element caching is facilitated through the Cache class. You can configure elements to be stored in any
Cache configuration youve set up. This gives you a great amount of flexibility to decide where and for
how long elements are stored. To cache different versions of the same element in an application, provide a
unique cache key value using the following format:
$this->element('helpbox', [], [
"cache" => ['config' => 'short', 'key' => 'unique value']
]
);
If you need more logic in your element, such as dynamic data from a datasource, consider using a View Cell
instead of an element. Find out more about View Cells.
Caching Elements
You can take advantage of CakePHP view caching if you supply a cache parameter. If set to true, it will
cache the element in the default Cache configuration. Otherwise, you can set which cache configuration
should be used. See Caching for more information on configuring Cache. A simple example of caching an
element would be:
echo $this->element('helpbox', [], ['cache' => true]);
If you render the same element more than once in a view and have caching enabled, be sure to set the
key parameter to a different name each time. This will prevent each successive call from overwriting the
previous element() calls cached result. For example:
echo $this->element(
'helpbox',
['var' => $var],
['cache' => ['key' => 'first_use', 'config' => 'view_long']]
);
echo $this->element(
'helpbox',
['var' => $differenVar],
Elements
263
The above will ensure that both element results are cached separately. If you want all element caching to
use the same cache configuration, you can avoid some repetition by setting View::$elementCache to
the cache configuration you want to use. CakePHP will use this configuration when none is given.
If your view is a part of a plugin, you can omit the plugin name. For example, if you are in the
ContactsController of the Contacts plugin, the following:
echo $this->element('helpbox');
// and
echo $this->element('Contacts.helpbox');
are equivalent and will result in the same element being rendered.
For elements inside subfolder of a plugin (e.g., plugins/Contacts/Template/Element/sidebar/helpbox.ctp),
use the following:
echo $this->element('Contacts.sidebar/helpbox');
The element first be looked for in src/Template/Admin/Element/. If such a file does not exist, it will be
looked for in the default location.
264
Sometimes generating a section of your view output can be expensive because of rendered View Cells or
expensive helper operations. To help make your application run faster CakePHP provides a way to cache
view sections:
// Assuming some local variables
echo $this->cache(function () use ($user, $article) {
echo $this->cell('UserProfile', [$user]);
echo $this->cell('ArticleFull', [$article]);
}, ['key' => 'my_view_key']);
By default cached view content will go into the View::$elementCache cache config, but you can use
the config option to change this.
View Events
Like Controller, view trigger several events/callbacks that you can use to insert logic around the rendering
life-cycle:
Event List
View.beforeRender
View.beforeRenderFile
View.afterRenderFile
View.afterRender
View.beforeLayout
View.afterLayout
You can attach application event listeners to these events or use Helper Callbacks.
For example:
View Events
265
// In src/View/PdfView.php
namespace App\View;
use Cake\View\View;
class PdfView extends View
{
public function render($view = null, $layout = null)
{
// Custom logic here.
}
}
Replacing the render method lets you take full control over how your content is rendered.
266
https://github.com/apotonick/cells
Save this file into src/View/Cell/InboxCell.php. As you can see, like other classes in CakePHP, Cells have
a few conventions:
Cells live in the App\View\Cell namespace. If you are making a cell in a plugin, the namespace
would be PluginName\View\Cell.
Class names should end in Cell.
Classes should inherit from Cake\View\Cell.
We added an empty display() method to our cell; this is the conventional default method when
rendering a cell. Well cover how to use other methods later in the docs. Now, create the file
src/Template/Cell/Inbox/display.ctp. This will be our template for our new cell.
You can generate this stub code quickly using bake:
bin/cake bake cell Inbox
Because Cells use the ModelAwareTrait and ViewVarsTrait, they behave very much like a controller would. We can use the loadModel() and set() methods just like we would in a controller. In
our template file, add the following:
<!-- src/Template/Cell/Inbox/display.ctp -->
<div class="notification-icon">
You have <?= $unread_count ?> unread messages.
</div>
267
Note: Cell templates have an isolated scope that does not share the same View instance as the one used to
render template and layout for the current controller action or other cells. Hence they are unaware of any
helper calls made or blocks set in the actions template / layout and vice versa.
Loading Cells
Cells can be loaded from views using the cell() method and works the same in both contexts:
// Load an application cell
$cell = $this->cell('Inbox');
// Load a plugin cell
$cell = $this->cell('Messaging.Inbox');
The above will load the named cell class and execute the display() method. You can execute other
methods using the following:
// Run the expanded() method on the Inbox cell
$cell = $this->cell('Inbox::expanded');
If you need controller logic to decide which cells to load in a request, you can use the CellTrait in your
controller to enable the cell() method there:
namespace App\Controller;
use App\Controller\AppController;
use Cake\View\CellTrait;
class DashboardsController extends AppController
{
use CellTrait;
// More code.
}
268
Rendering a Cell
Once a cell has been loaded and executed, youll probably want to render it. The easiest way to render a cell
is to echo it:
<?= $cell ?>
This will render the template matching the lowercased and underscored version of our action name, e.g.
display.ctp.
Because cells use View to render templates, you can load additional cells within a cell template if required.
Note: Echoing a cell uses the PHP __toString() magic method which prevents PHP from showing the
filename and line number for any fatal errors raised. To obtain a meanful error message, it is recommended
to use the Cell::render() method, for example <?= $cell->render() ?>.
269
If a key is generated the underscored version of the cell class and template name will be used.
Note: A new View instance is used to render each cell and these new objects do not share context with the
main template / layout. Each cell is self-contained and only has access to variables passed as arguments to
the View::cell() call.
Themes
You can take advantage of themes, making it easy to switch the look and feel of your page quickly. Themes
in CakePHP are simply plugins that focus on providing template files. In addition to template files, they can
also provide helpers and cells if your theming requires that. When using cells and helpers from your theme,
you will need to continue using the plugin syntax.
To use themes, set the theme name in your controllers action or beforeRender() callback:
class ExamplesController extends AppController
{
// For CakePHP before 3.1
public $theme = 'Modern';
public function beforeRender(\Cake\Event\Event $event)
{
$this->viewBuilder()->theme('Modern');
}
}
Theme template files need to be within a plugin with the same name. For example, the above theme would
be found in plugins/Modern/src/Template. Its important to remember that CakePHP expects CamelCase
plugin/theme names. Beyond that, the folder structure within the plugins/Modern/src/Template folder is
exactly the same as src/Template/.
For example, the view file for an edit action of a Posts controller would reside
at plugins/Modern/src/Template/Posts/edit.ctp.
Layout files would reside in plugins/Modern/src/Template/Layout/. You can provide customized templates for plugins with a theme
as well. If you had a plugin named Cms, that contained a TagsController, the Modern theme could provide
plugins/Modern/src/Template/Plugin/Cms/Tags/edit.ctp to replace the edit template in the plugin.
If a view file cant be found in the theme, CakePHP will try to locate the view file in the src/Template/
folder. This way, you can create master template files and simply override them on a case-by-case basis
within your theme folder.
270
Theme Assets
Because themes are standard CakePHP plugins, they can include any necessary assets in their webroot
directory. This allows for easy packaging and distribution of themes. Whilst in development, requests for
theme assets will be handled by Cake\Routing\Dispatcher. To improve performance for production
environments, its recommended that you Improve Your Applications Performance.
All of CakePHPs built-in helpers are aware of themes and will create the correct paths automatically. Like
template files, if a file isnt in the theme folder, it will default to the main webroot folder:
// When in a theme with the name of 'purple_cupcake'
$this->Html->css('main.css');
// creates a path like
/purple_cupcake/css/main.css
// and links to
plugins/PurpleCupcake/webroot/css/main.css
load
the
This can be done in your AppController and will enable automatic view class switching on content types.
You can also set the component up with the viewClassMap setting, to map types to your custom classes
and/or map other data types.
You can optionally enable the json and or xml extensions with Routing File Extensions. This will allow you
to access the JSON, XML or any other special format views by using a custom URL ending with the name
of the response type as a file extension such as http://example.com/articles.json.
271
By default, when not enabling Routing File Extensions, the request the Accept header is used for selecting
which type of format should be rendered to the user. An example Accept format that is used to render
JSON responses is application/json.
Using Data Views with the Serialize Key
The _serialize key is a special view variable that indicates which other view variable(s) should be
serialized when using a data view. This lets you skip defining template files for your controller actions if
you dont need to do any custom formatting before your data is converted into json/xml.
If you need to do any formatting or manipulation of your view variables before generating the response, you
should use template files. The value of _serialize can be either a string or an array of view variables to
serialize:
namespace App\Controller;
class ArticlesController extends AppController
{
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler');
}
public function index()
{
// Set the view vars that have to be serialized.
$this->set('articles', $this->paginate());
// Specify which view vars JsonView should serialize.
$this->set('_serialize', ['articles']);
}
}
272
Defining _serialize as an array has the added benefit of automatically appending a top-level
<response> element when using XmlView. If you use a string value for _serialize and XmlView,
make sure that your view variable has a single top-level element. Without a single top-level element the Xml
will fail to generate.
New in version 3.1.0: You can also set _serialize to true to serialize all view variables instead of
explicitly specifying them.
Using a Data View with Template Files
You should use template files if you need to do some manipulation of your view content before creating
the final output. For example if we had articles, that had a field containing generated HTML, we would
probably want to omit that from a JSON response. This is a situation where a view file would be useful:
// Controller code
class ArticlesController extends AppController
{
public function index()
{
$articles = $this->paginate('Articles');
$this->set(compact('articles'));
}
}
// View code - src/Template/Articles/json/index.ctp
foreach ($articles as &$$article) {
unset($article->generated_html);
}
echo json_encode(compact('articles'));
You can do more complex manipulations, or use helpers to do formatting as well. The data view classes
dont support layouts. They assume that the view file will output the serialized content.
Note: As of 3.1.0 AppController, in the application skeleton automatically adds '_serialize' =>
true to all XML/JSON requests. You will need to remove this code from the beforeRender callback if you
want to use view files.
273
The XmlView class supports the _xmlOptions variable that allows you to customize the options used to
generate XML, e.g. tags vs attributes.
Creating JSON Views
class JsonView
The JsonView class supports the _jsonOptions variable that allows you to customize the bit-mask used
to generate JSON. See the json_encode105 documentation for the valid values of this option.
JSONP Responses
When using JsonView you can use the special view variable _jsonp to enable returning a JSONP response. Setting it to true makes the view class check if query string parameter named callback is set
and if so wrap the json response in the function name provided. If you want to use a custom query string
parameter name instead of callback set _jsonp to required name instead of true.
Helpers
Helpers are the component-like classes for the presentation layer of your application. They contain presentational logic that is shared between many views, elements, or layouts. This chapter will show you how to
configure helpers. How to load helpers and use those helpers, and outline the simple steps for creating your
own custom helpers.
CakePHP includes a number of helpers that aid in view creation. They assist in creating well-formed markup
(including forms), aid in formatting text, times and numbers, and can even speed up AJAX functionality.
For more information on the helpers included in CakePHP, check out the chapter for each helper:
Flash
class Cake\View\Helper\FlashHelper(View $view, array $config =[])
FlashHelper provides a way to render flash messages that were set in $_SESSION by FlashComponent.
FlashComponent and FlashHelper primarily use elements to render flash messages. Flash elements are
found under the src/Template/Element/Flash directory. Youll notice that CakePHPs App template comes
with two flash elements: success.ctp and error.ctp.
Rendering Flash Messages
To render a flash message, you can simply use FlashHelpers render() method:
<?= $this->Flash->render() ?>
105
274
http://php.net/json_encode
By default, CakePHP uses a flash key for flash messages in a session. But, if youve specified a key when
setting the flash message in FlashComponent, you can specify which flash key to render:
<?= $this->Flash->render('other') ?>
You can also override any of the options that were set in FlashComponent:
// In your Controller
$this->Flash->set('The user has been saved.', [
'element' => 'success'
]);
// In your View: Will use great_success.ctp instead of succcess.ctp
<?= $this->Flash->render('flash', [
'element' => 'great_success'
]);
Note: By default, CakePHP does not escape the HTML in flash messages. If you are using any request or
user data in your flash messages, you should escape it with h when formatting your messages.
New in version 3.1: The FlashComponent now stacks messages. If you set multiple flash messages, when
you call render(), each message will be rendered in its own elements, in the order they were set.
For more information about the available array options, please refer to the FlashComponent section.
Routing Prefix and Flash Messages
New in version 3.0.1.
If you have a Routing prefix configured, you can now have your Flash elements stored in
src/Template/{Prefix}/Element/Flash. This way, you can have specific messages layouts for each part
of your application (for instance, have different layouts for you front-end and your admin side).
Flash Messages and Themes
The FlashHelper uses normal elements to render the messages and will therefore obey any theme you might
have specified. So when your theme has a src/Template/Element/Flash/error.ctp file it will be used, just
as with any Elements and Views.
Form
class Cake\View\Helper\FormHelper(View $view, array $config =[])
The FormHelper does most of the heavy lifting in form creation. The FormHelper focuses on creating forms
quickly, in a way that will streamline validation, re-population and layout. The FormHelper is also flexible
- it will do almost everything for you using conventions, or you can use specific methods to get only what
you need.
More About Views
275
Starting a Form
Cake\View\Helper\FormHelper::create(mixed $model = null, array $options =[])
The first method youll need to use in order to take advantage of the FormHelper is create(). This
method outputs an opening form tag.
All parameters are optional. If create() is called with no parameters supplied, it assumes you are building
a form that submits to the current controller, via the current URL. The default method for form submission is
POST. If you were to call create() inside the view for UsersController::add(), you would see something
like the following output in the rendered view:
<form method="post" action="/users/add">
The $model argument is used as the forms context. There are several built-in form contexts and you can
add your own, which well cover in the next section. The built-in providers map to the following values of
$model:
An Entity instance or, an iterator map to the EntityContext, this context allows FormHelper
to work with results from the built-in ORM.
An array containing the schema key, maps to ArrayContext which allows you to create simple
data structures to build forms against.
null and false map to the NullContext, this context class simply satisifies the interface
FormHelper requires. This context is useful if you want to build a short form that doesnt require
ORM persistence.
All contexts classes also have access to the request data, making it simpler to build forms.
Once a form has been created with a context, all inputs you create will use the active context. In the case of
an ORM backed form, FormHelper can access associated data, validation errors and schema metadata. You
can close the active context using the end() method, or by calling create() again. To create a form for
an entity, do the following:
// If you are on /articles/add
// $article should be an empty Article entity.
echo $this->Form->create($article);
Output:
<form method="post" action="/articles/add">
This will POST the form data to the add() action of ArticlesController. However, you can also use the
same logic to create an edit form. The FormHelper uses the Entity object to automatically detect whether
to create an add or edit form. If the provided entity is not new, the form will be created as an edit form.
For example, if we browse to http://example.org/articles/edit/5, we could do the following:
// src/Controller/ArticlesController.php:
public function edit($id = null)
{
if (empty($id)) {
throw new NotFoundException;
276
}
$article = $this->Articles->get($id);
// Save logic goes here
$this->set('article', $article);
}
// View/Articles/edit.ctp:
// Since $article->isNew() is false, we will get an edit form
<?= $this->Form->create($article) ?>
Output:
<form method="post" action="/articles/edit/5">
<input type="hidden" name="_method" value="PUT" />
Note: Since this is an edit form, a hidden input field is generated to override the default HTTP method.
The $options array is where most of the form configuration happens. This special array can contain a
number of different key-value pairs that affect the way the form tag is generated.
Changing the HTTP Method for a Form
By using the type option you can change the HTTP method a form will use:
echo $this->Form->create($article, ['type' => 'get']);
Output:
<form method="get" action="/articles/edit/5">
Specifying file changes the form submission method to post, and includes an enctype of multipart/formdata on the form tag. This is to be used if there are any file elements inside the form. The absence of the
proper enctype attribute will cause the file uploads not to function:
echo $this->Form->create($article, ['type' => 'file']);
Output:
<form enctype="multipart/form-data" method="post" action="/articles/add">
When using put, patch or delete, your form will be functionally equivalent to a post form, but when
submitted, the HTTP request method will be overridden with PUT, PATCH or DELETE, respectively.
This allows CakePHP to emulate proper REST support in web browsers.
277
Output:
<form method="post" action="/users/login">
If the desired form action isnt in the current controller, you can specify a complete URL for the form action.
The supplied URL can be relative to your CakePHP application:
echo $this->Form->create(null, [
'url' => ['controller' => 'Articles', 'action' => 'publish']
]);
Output:
<form method="post" action="/articles/publish">
Output:
<form method="get" action="http://www.google.com/search">
Use 'url' => false if you dont want to output a URL as the form action.
Using Custom Validators
Often models will have multiple validation sets, and you will want FormHelper to mark fields required based
on a the specific validation rules your controller action is going to apply. For example, your Users table has
specific validation rules that only apply when an account is being registered:
echo $this->Form->create($user, [
'context' => ['validator' => 'register']
]);
The above will use the register validator for the $user and all related associations. If you are creating
a form for associated entities, you can define validation rules for each association by using an array:
278
echo $this->Form->create($user, [
'context' => [
'validator' => [
'Users' => 'register',
'Comments' => 'default'
]
]
]);
The above would use register for the user, and default for the users comments.
Creating context classes
While the built-in context classes are intended to cover the basic cases youll encounter you may need to
build a new context class if you are using a different ORM. In these situations you need to implement the
Cake\View\Form\ContextInterface106 . Once you have implemented this interface you can wire your new
context into the FormHelper. It is often best to do this in a View.beforeRender event listener, or in an
application view class:
$this->Form->addContextProvider('myprovider', function ($request, $data) {
if ($data['entity'] instanceof MyOrmClass) {
return new MyProvider($request, $data);
}
});
Context factory functions are where you can add logic for checking the form options for the correct type of
entity. If matching input data is found you can return an object. If there is no match return null.
Creating Form Inputs
Cake\View\Helper\FormHelper::input(string $fieldName, array $options =[])
The input() method lets you to generate complete form inputs. These inputs will include a wrapping
div, label, input widget, and validation error if necessary. By using the metadata in the form context, this
method will choose an appropriate input type for each field. Internally input() uses the other methods of
FormHelper.
The type of input created depends on the column datatype:
Column Type Resulting Form Field
string, uuid (char, varchar, etc.) text
boolean, tinyint(1) checkbox
decimal number
float number
106
http://api.cakephp.org/3.0/class-Cake.View.Form.ContextInterface.html
279
integer number
text textarea
text, with name of password, passwd password
text, with name of email email
text, with name of tel, telephone, or phone tel
date day, month, and year selects
datetime, timestamp day, month, year, hour, minute, and meridian selects
time hour, minute, and meridian selects
binary file
The $options parameter allows you to choose a specific input type if you need to:
echo $this->Form->input('published', ['type' => 'checkbox']);
The wrapping div will have a required class name appended if the validation rules for the models field
indicate that it is required and not allowed to be empty. You can disable automatic required flagging using
the required option:
echo $this->Form->input('title', ['required' => false]);
To skip browser validation triggering for the whole form you can set option 'formnovalidate'
=> true for the input button you generate using View\Helper\FormHelper::submit() or set
'novalidate' => true in options for View\Helper\FormHelper::create().
For example, lets assume that your User model includes fields for a username (varchar), password (varchar), approved (datetime) and quote (text). You can use the input() method of the FormHelper to create
appropriate inputs for all of these form fields:
echo $this->Form->create($user);
// Text
echo $this->Form->input('username');
// Password
echo $this->Form->input('password');
// Day, month, year, hour, minute, meridian
echo $this->Form->input('approved');
// Textarea
echo $this->Form->input('quote');
echo $this->Form->button('Add');
echo $this->Form->end();
280
Besides the specific options for input() found below, you can specify any option for the input type & any
HTML attribute (for instance onfocus).
If you want to create a select field while using a belongsTo - or hasOne - Relation, you can add the following
to your Users-controller (assuming your User belongsTo Group):
$this->set('groups', $this->Users->Groups->find('list'));
To make a select box for a belongsToMany Groups association you can add the following to your UsersController:
$this->set('groups', $this->Users->Groups->find('list'));
If your model name consists of two or more words, e.g., UserGroup, when passing the data using set()
you should name your data in a pluralised and camelCased format as follows:
$this->set('userGroups', $this->UserGroups->find('list'));
Note:
You should not use FormHelper::input() to generate submit buttons.
View\Helper\FormHelper::submit() instead.
Use
or
arbitrary
models
by
passing
in
echo $this->Form->input('association.fieldname');
Any dots in your field names will be converted into nested request data. For example, if you created a field
with a name 0.comments.body you would get a name attribute that looks like 0[comments][body].
This convention makes it easy to save data with the ORM. Details for the various association types can be
found in the Creating Inputs for Associated Data section.
281
When creating datetime related inputs, FormHelper will append a field-suffix. You may notice additional
fields named year, month, day, hour, minute, or meridian being added. These fields will be
automatically converted into DateTime objects when entities are marshalled.
Options
FormHelper::input() supports a large number of options. In addition to its own options input()
accepts options for the generated input types, as well as HTML attributes. The following will cover the
options specific to FormHelper::input().
$options['type'] You can force the type of an input, overriding model introspection, by specifying a type. In addition to the field types found in the Creating Form Inputs, you can also create
file, password, and any type supported by HTML5:
echo $this->Form->input('field', ['type' => 'file']);
echo $this->Form->input('email', ['type' => 'email']);
Output:
<div class="input file">
<label for="field">Field</label>
<input type="file" name="field" value="" id="field" />
</div>
<div class="input email">
<label for="email">Email</label>
<input type="email" name="email" value="" id="email" />
</div>
$options['label'] Set this key to the string you would like to be displayed within the label that
usually accompanies the input:
echo $this->Form->input('name', [
'label' => 'The User Alias'
]);
Output:
<div class="input">
<label for="name">The User Alias</label>
<input name="name" type="text" value="" id="name" />
</div>
Alternatively, set this key to false to disable the output of the label:
echo $this->Form->input('name', ['label' => false]);
Output:
<div class="input">
<input name="name" type="text" value="" id="name" />
</div>
282
Set this to an array to provide additional options for the label element. If you do this, you can use
a text key in the array to customize the label text:
echo $this->Form->input('name', [
'label' => [
'class' => 'thingy',
'text' => 'The User Alias'
]
]);
Output:
<div class="input">
<label for="name" class="thingy">The User Alias</label>
<input name="name" type="text" value="" id="name" />
</div>
$options['error'] Using this key allows you to override the default model error messages and
can be used, for example, to set i18n messages.
To disable error message output & field classes set the error key to false:
echo $this->Form->input('name', ['error' => false]);
To override the model error messages use an array with the keys matching the original validation error
messages:
$this->Form->input('name', [
'error' => ['Not long enough' => __('This is not long enough')]
]);
As seen above you can set the error message for each validation rule you have in your models. In
addition you can provide i18n messages for your forms.
Generating Specific Types of Inputs
In addition to the generic input() method, FormHelper has specific methods for generating a number of different types of inputs. These can be used to generate just the input widget itself, and combined with other methods like View\Helper\FormHelper::label() and
View\Helper\FormHelper::error() to generate fully custom form layouts.
Common Options
Many of the various input element methods support a common set of options. All of these options are
also supported by input(). To reduce repetition the common options shared by all input methods are as
follows:
$options['id'] Set this key to force the value of the DOM id for the input. This will override
the idPrefix that may be set.
283
$options['default'] Used to set a default value for the input field. The value is used if the
data passed to the form does not contain a value for the field (or if no data is passed at all). An explicit
default value will override any default values defined in the schema.
Example usage:
echo $this->Form->text('ingredient', ['default' => 'Sugar']);
Note:
You cannot use default to check a checkbox - instead you might set the value in
$this->request->data in your controller, or set the input option checked to true.
Beware of using false to assign a default value. A false value is used to disable/exclude options of
an input field, so 'default' => false would not set any value at all. Instead use 'default'
=> 0.
$options['value'] Used to set a specific value for the input field. This will override any value
that may else be injected from the context, such as Form, Entity or request->data etc.
Note: If you want to set a field to not render its value fetched from context or valuesSource you will
need to set $options['value'] to '' (instead of setting it to null).
In addition to the above options, you can mixin any HTML attribute you wish to use. Any non-special
option name will be treated as an HTML attribute, and applied to the generated HTML input element.
Changed in version 3.3.0: As of 3.3.0, FormHelper will automatically use any default values defined in your
database schema. You can disable this behavior by setting the schemaDefault option to false.
Options for Select, Checkbox and Radio Inputs
$options['value'] may also be used in combination with a select-type input (i.e. For types
select, date, time, datetime). Set value to the value of the item you wish to be selected by default
when the input is rendered:
echo $this->Form->time('close_time', [
'value' => '13:30:00'
]);
Note: The value key for date and datetime inputs may also be a UNIX timestamp, or a DateTime
object.
284
For select input where you set the multiple attribute to true, you can use an array of the values you
want to select by default:
echo $this->Form->select('rooms', [
'multiple' => true,
// options with values 1 and 3 will be selected as default
'default' => [1, 3]
]);
Output:
<select name="field">
<option value="">(choose one)</option>
<option value="0">1</option>
<option value="1">2</option>
<option value="2">3</option>
<option value="3">4</option>
<option value="4">5</option>
</select>
Which outputs:
<input type="checkbox" name="published" value="1">
If you want to create multiple blocks of inputs on a form that are all grouped together, you should use
this parameter on all inputs except the first. If the hidden input is on the page in multiple places, only
the last group of inputs values will be saved
In this example, only the tertiary colors would be passed, and the primary colors would be overridden:
More About Views
285
<h2>Primary Colors</h2>
<input type="hidden" name="color" value="0" />
<label for="color-red">
<input type="checkbox" name="color[]" value="5" id="color-red" />
Red
</label>
<label for="color-blue">
<input type="checkbox" name="color[]" value="5" id="color-blue" />
Blue
</label>
<label for="color-yellow">
<input type="checkbox" name="color[]" value="5" id="color-yellow" />
Yellow
</label>
<h2>Tertiary Colors</h2>
<input type="hidden" name="color" value="0" />
<label for="color-green">
<input type="checkbox" name="color[]" value="5" id="color-green" />
Green
</label>
<label for="color-purple">
<input type="checkbox" name="color[]" value="5" id="color-purple" />
Purple
</label>
<label for="color-orange">
<input type="checkbox" name="color[]" value="5" id="color-orange" />
Orange
</label>
Disabling the 'hiddenField' on the second input group would prevent this behavior.
You can set a different hidden field value other than 0 such as N:
echo $this->Form->checkbox('published', [
'value' => 'Y',
'hiddenField' => 'N',
]);
Datetime Options
$options['timeFormat'] Used to specify the format of the select inputs for a time-related set
of inputs. Valid values include 12, 24, and null.
$options['minYear'],$options['maxYear'] Used in combination with a date/datetime
input. Defines the lower and/or upper end of values shown in the years select field.
$options['orderYear'] Used in combination with a date/datetime input. Defines the order in
which the year values will be set. Valid values include asc, desc. The default value is desc.
286
$options['interval'] This option specifies the number of minutes between each option in the
minutes select box:
echo $this->Form->input('time', [
'type' => 'time',
'interval' => 15
]);
Would create 4 options in the minute select. One for each 15 minutes.
$options['round'] Can be set to up or down to force rounding in either direction. Defaults to
null which rounds half up according to interval.
$options['monthNames'] If false, 2 digit numbers will be used instead of text. If it is given
an array like ['01' => 'Jan','02' => 'Feb',...] then the given array will be used.
Creating Input Elements
Creating Text Inputs
Cake\View\Helper\FormHelper::text(string $name, array $options)
The rest of the methods available in the FormHelper are for creating specific form elements. Many of these
methods also make use of a special $options parameter. In this case, however, $options is used primarily to
specify HTML tag attributes (such as the value or DOM id of an element in the form):
echo $this->Form->text('username', ['class' => 'users']);
Will output:
<input name="username" type="text" class="users">
Will output:
<input name="password" value="" type="password">
287
Will output:
<input name="id" value="10" type="hidden" />
Creating Textareas
Cake\View\Helper\FormHelper::textarea(string $fieldName, array $options)
Creates a textarea input field.
echo $this->Form->textarea('notes');
Will output:
<textarea name="notes"></textarea>
If the form is edited (that is, the array $this->request->data will contain the information saved
for the User model), the value corresponding to notes field will automatically be added to the HTML
generated. Example:
<textarea name="notes" id="notes">
This text is to be edited.
</textarea>
Note: The textarea input type allows for the $options attribute of 'escape' which determines
whether or not the contents of the textarea should be escaped. Defaults to true.
Options
In addition to the Common Options, textarea() supports a few specific options:
$options['rows'],$options['cols'] These two keys specify the number of rows and
columns:
echo $this->Form->textarea('textarea', ['rows' => '5', 'cols' => '5']);
Output:
<textarea name="textarea" cols="5" rows="5">
</textarea>
288
Creating Checkboxes
Cake\View\Helper\FormHelper::checkbox(string $fieldName, array $options)
Creates a checkbox form element. This method also generates an associated hidden form input to force the
submission of data for the specified field.
echo $this->Form->checkbox('done');
Will output:
<input type="hidden" name="done" value="0">
<input type="checkbox" name="done" value="1">
It is possible to specify the value of the checkbox by using the $options array:
echo $this->Form->checkbox('done', ['value' => 555]);
Will output:
<input type="hidden" name="done" value="0">
<input type="checkbox" name="done" value="555">
Will output:
<input type="checkbox" name="done" value="1">
289
Generally $options is a simple key => value pair. However, if you need to put custom attributes on your
radio buttons you can use an expanded format:
echo $this->Form->radio(
'favorite_color',
[
['value' => 'r', 'text' => 'Red', 'style' => 'color:red;'],
['value' => 'u', 'text' => 'Blue', 'style' => 'color:blue;'],
['value' => 'g', 'text' => 'Green', 'style' => 'color:green;'],
]
);
// Will output
<input type="hidden" name="favorite_color" value="">
<label for="favorite-color-r">
<input type="radio" name="favorite_color" value="r" style="color:red;" id=
"favorite-color-r">
Red
</label>
<label for="favorite-color-u">
<input type="radio" name="favorite_color" value="u" style="color:blue;"
id="favorite-color-u">
Blue
</label>
<label for="favorite-color-g">
<input type="radio" name="favorite_color" value="g" style="color:green;"
id="favorite-color-g">
Green
</label>
Will output:
<select name="gender">
<option value=""></option>
<option value="M">Male</option>
<option value="F">Female</option>
</select>
The select input type allows for a special $option attribute called 'escape' which accepts a bool
and determines whether to HTML entity encode the contents of the select options. Defaults to true:
290
$attributes['options'] This key allows you to manually specify options for a select input,
or for a radio group. Unless the type is specified as radio, the FormHelper will assume that the
target output is a select input:
echo $this->Form->select('field', [1,2,3,4,5]);
Output:
<select name="field">
<option value="0">1</option>
<option value="1">2</option>
<option value="2">3</option>
<option value="3">4</option>
<option value="4">5</option>
</select>
Output:
<select name="field">
<option value="Value 1">Label 1</option>
<option value="Value 2">Label 2</option>
<option value="Value 3">Label 3</option>
</select>
If you would like to generate a select with optgroups, just pass data in hierarchical format. This works
on multiple checkboxes and radio buttons too, but instead of optgroups wraps elements in fieldsets:
$options = [
'Group 1' => [
'Value 1' => 'Label 1',
'Value 2' => 'Label 2'
],
'Group 2' => [
'Value 3' => 'Label 3'
]
];
echo $this->Form->select('field', $options);
Output:
291
<select name="field">
<optgroup label="Group 1">
<option value="Value 1">Label 1</option>
<option value="Value 2">Label 2</option>
</optgroup>
<optgroup label="Group 2">
<option value="Value 3">Label 3</option>
</optgroup>
</select>
Output:
<select name="field">
<option value="value 1" attr_name="attr_value 1">Description 1</option>
<option value="value 2" attr_name="attr_value 2">Description 2</option>
<option value="value 3" other_attr_name="other_attr_value">Description 3</
option>
</select>
$attributes['multiple'] If multiple has been set to true for an input that outputs a select,
the select will allow multiple selections:
echo $this->Form->select('field', $options, ['multiple' => true]);
Output:
<input name="field" value="" type="hidden">
<div class="checkbox">
<label for="field-1">
<input name="field[]" value="Value 1" id="field-1" type="checkbox">
292
Label 1
</label>
</div>
<div class="checkbox">
<label for="field-2">
<input name="field[]" value="Value 2" id="field-2" type="checkbox">
Label 2
</label>
</div>
$attributes['disabled'] When creating checkboxes, this option can be set to disable all or
some checkboxes. To disable all checkboxes set disabled to true:
$options = [
'Value 1' => 'Label 1',
'Value 2' => 'Label 2'
];
echo $this->Form->select('field', $options, [
'multiple' => 'checkbox',
'disabled' => ['Value 1']
]);
Output:
<input name="field" value="" type="hidden">
<div class="checkbox">
<label for="field-1">
<input name="field[]" disabled="disabled" value="Value 1" type=
"checkbox">
Label 1
</label>
</div>
<div class="checkbox">
<label for="field-2">
<input name="field[]" value="Value 2" id="field-2" type="checkbox">
Label 2
</label>
</div>
Next add either of the two lines to your form view file:
More About Views
293
echo $this->Form->input('submittedfile', [
'type' => 'file'
]);
// OR
echo $this->Form->file('submittedfile');
Due to the limitations of HTML itself, it is not possible to put default values into input fields of type file.
Each time the form is displayed, the value inside will be empty.
Upon submission, file fields provide an expanded data array to the script receiving the form data.
For the example above, the values in the submitted data array would be organized as follows, if the CakePHP
was installed on a Windows server. tmp_name will have a different path in a Unix environment:
$this->request->data['submittedfile'] = [
'name' => 'conference_schedule.pdf',
'type' => 'application/pdf',
'tmp_name' => 'C:/WINDOWS/TEMP/php1EE.tmp',
'error' => 0, // On Windows this can be a string.
'size' => 41737,
];
This array is generated by PHP itself, so for more detail on the way PHP handles data passed via file fields
read the PHP manual section on file uploads107 .
Note: When using $this->Form->file(), remember to set the form encoding-type, by setting the
type option to file in $this->Form->create().
294
http://php.net/features.file-upload
default The default value to be used by the input. A value in $this->request->data matching the field name will override this value. If no default is provided time() will be used.
timeFormat The time format to use, either 12 or 24.
second Set to true to enable seconds drop down.
To control the order of inputs, and any elements/content between the inputs you can override the
dateWidget template. By default the dateWidget template is:
{{year}}{{month}}{{day}}{{hour}}{{minute}}{{second}}{{meridian}}
To create a datetime inputs with custom classes/attributes on a specific select box, you can use the options
in each component:
echo $this->Form->datetime('released', [
'year' => [
'class' => 'year-classname',
],
'month' => [
'class' => 'month-class',
'data-type' => 'month',
],
]);
295
296
Will output:
<select name="mob[month]">
<option value=""></option>
<option value="01">January</option>
<option value="02">February</option>
<option value="03">March</option>
<option value="04">April</option>
<option value="05">May</option>
<option value="06">June</option>
<option value="07">July</option>
<option value="08">August</option>
<option value="09">September</option>
<option value="10">October</option>
<option value="11">November</option>
<option value="12">December</option>
</select>
297
You can pass in your own array of months to be used by setting the monthNames attribute, or have months
displayed as numbers by passing false. (Note: the default months can be localized with CakePHP Internationalization & Localization features.):
echo $this->Form->month('mob', ['monthNames' => false]);
Will output:
<select name="created[day]">
<option value=""></option>
<option value="01">1</option>
<option value="02">2</option>
<option value="03">3</option>
...
<option value="31">31</option>
</select>
298
Creates a select element populated with the minutes of the hour. You can create a select that only contains
specific values using the interval option. For example, if you wanted 10 minute increments you would
do the following:
echo $this->Form->minute('created', [
'interval' => 10
]);
Output:
<label for="user-name">Name</label>
<label for="user-name">Your username</label>
$options can either be an array of HTML attributes, or a string that will be used as a class name:
echo $this->Form->label('User.name', null, ['id' => 'user-label']);
echo $this->Form->label('User.name', 'Your username', 'highlight');
Output:
<label for="user-name" id="user-label">Name</label>
<label for="user-name" class="highlight">Your username</label>
299
Cake\View\Helper\FormHelper::isFieldError(string $fieldName)
Returns true if the supplied $fieldName has an active validation error.
if ($this->Form->isFieldError('gender')) {
echo $this->Form->error('gender');
}
Will output:
<div class="submit"><input value="Submit" type="submit"></div>
You can pass a relative or absolute URL to an image for the caption parameter instead of caption text:
echo $this->Form->submit('ok.png');
Will output:
<div class="submit"><input type="image" src="/img/ok.png"></div>
Submit inputs are useful when you only need basic text or images. If you need more complex button content
you should use button().
Creating Button Elements
Cake\View\Helper\FormHelper::button(string $title, array $options =[])
Creates an HTML button with the specified title and a default type of button.
$options['type'] will output one of the three possible button types:
Setting
300
echo
echo
echo
echo
$this->Form->button('A Button');
$this->Form->button('Another Button', ['type' => 'button']);
$this->Form->button('Reset the Form', ['type' => 'reset']);
$this->Form->button('Submit Form', ['type' => 'submit']);
Will output:
<button
<button
<button
<button
type="submit">A Button</button>
type="button">Another Button</button>
type="reset">Reset the Form</button>
type="submit">Submit Form</button>
The button input type supports the escape option, which accepts a boolean and defaults to false. It
determines whether to HTML encode the $title of the button:
// Will render escaped HTML.
echo $this->Form->button('<em>Submit Form</em>', [
'type' => 'submit',
'escape' => true
]);
The $secureAttributes parameter allows you to pass additional HTML attributes to the hidden inputs
that are generated when your application is using SecurityComponent. If you need to add additional
attributes to the generated hidden inputs you can use the $secureAttributes argument:
echo $this->Form->end(['data-type' => 'hidden']);
Will output:
<div style="display:none;">
<input type="hidden" name="_Token[fields]" data-type="hidden"
value="2981c38990f3f6ba935e6561dc77277966fabd6d%3AAddresses.id">
<input type="hidden" name="_Token[unlocked]" data-type="hidden"
value="address%7Cfirst_name">
</div>
301
then
or
Note: Be careful to not put a postLink inside an open form. Instead use the block option to buffer
the form into a Using View Blocks
This would load the tags in config/app_form.php. This file should contain an array of templates indexed
by name:
302
// in config/app_form.php
return [
'inputContainer' => '<div class="form-control">{{content}}</div>',
];
Any templates you define will replace the default ones included in the helper. Templates that are not replaced, will continue to use the default values. You can also change the templates at runtime using the
templates() method:
$myTemplates = [
'inputContainer' => '<div class="form-control">{{content}}</div>',
];
$this->Form->templates($myTemplates);
Warning: Template strings containing a percentage sign (%) need special attention, you should prefix
this character with another percentage so it looks like %%. The reason is that internally templates are compiled to be used with sprintf(). Example: <div style=width:{{size}}%%>{{content}}</div>
List of Templates
The list of default templates, their default format and the variables they expect can be found at the
FormHelper API documentation108 .
In addition to these templates, the input() method will attempt to use distinct templates for each input
container. For example, when creating a datetime input the datetimeContainer will be used if it is
present. If that container is missing the inputContainer template will be used. For example:
// Add custom radio wrapping HTML
$this->Form->templates([
'radioContainer' => '<div class="form-radio">{{content}}</div>'
]);
// Create a radio set with our custom wrapping div.
echo $this->Form->radio('User.email_notifications', ['y', 'n']);
Similar to input containers, the input() method will also attempt to use distinct templates for each
form group. A form group is a combo of label and input. For example, when creating a radio input the
radioFormGroup will be used if it is present. If that template is missing by default each set of label &
input is rendered using the formGroup template. For example:
// Add custom radio form group
$this->Form->templates([
'radioFormGroup' => '<div class="radio">{{label}}{{input}}</div>'
]);
108
http://api.cakephp.org/3.2/class-Cake.View.Helper.FormHelper.html#%24_defaultConfig
303
This will make radio buttons and checkboxes render outside of their labels.
Generating Entire Forms
Cake\View\Helper\FormHelper::inputs(array $fields =[], $options =[])
Generates a set of inputs for the given context wrapped in a fieldset. You can specify the generated fields by
including them:
echo $this->Form->inputs([
'name' => ['label' => 'custom label']
]);
You can customize the generated inputs by defining additional options in the $fields parameter:
304
echo $this->Form->inputs([
'name' => ['label' => 'custom label']
]);
When customizing, fields, you can use the $options parameter to control the generated legend/fieldset.
fieldset Set to false to disable the fieldset. You can also pass an array of parameters to be
applied as HTML attributes to the fieldset tag. If you pass an empty array, the fieldset will be displayed
without attributes.
legend Set to false to disable the legend for the generated input set. Or supply a string to customize the legend text.
For example:
echo $this->Form->allInputs(
[
'name' => ['label' => 'custom label']
],
null,
['legend' => 'Update your post']
);
305
echo $this->Form->input('title');
// Author inputs (belongsTo)
echo $this->Form->input('author.id');
echo $this->Form->input('author.first_name');
echo $this->Form->input('author.last_name');
// Author profile (belongsTo + hasOne)
echo $this->Form->input('author.profile.id');
echo $this->Form->input('author.profile.username');
// Tags inputs (belongsToMany)
echo $this->Form->input('tags.0.id');
echo $this->Form->input('tags.0.name');
echo $this->Form->input('tags.1.id');
echo $this->Form->input('tags.1.name');
// Multiple select element for belongsToMany
echo $this->Form->input('tags._ids', [
'type' => 'select',
'multiple' => true,
'options' => $tagList,
]);
// Inputs for the joint table (articles_tags)
echo $this->Form->input('tags.0._joinData.starred');
echo $this->Form->input('tags.1._joinData.starred');
// Comments inputs (hasMany)
echo $this->Form->input('comments.0.id');
echo $this->Form->input('comments.0.comment');
echo $this->Form->input('comments.1.id');
echo $this->Form->input('comments.1.comment');
The above inputs could then be marshalled into a completed entity graph using the following code in your
controller:
$article = $this->Articles->patchEntity($article, $this->request->data, [
'associated' => [
'Authors',
'Authors.Profiles',
'Tags',
'Comments'
]
]);
Obviously, this is a very simple example, but it demonstrates how a custom widget could be built.
Using Widgets
You can load custom widgets when loading FormHelper or by using the addWidget() method. When
loading FormHelper, widgets are defined as a setting:
307
// In View class
$this->loadHelper('Form', [
'widgets' => [
'autocomplete' => ['Autocomplete']
]
]);
If your widget requires other widgets, you can have FormHelper populate those dependencies by declaring
them:
$this->loadHelper('Form', [
'widgets' => [
'autocomplete' => [
'App\View\Widget\AutocompleteWidget',
'text',
'label'
]
]
]);
In the above example, the autocomplete widget would depend on the text and label widgets. If your
widget needs access to the View, you should use the _view widget. When the autocomplete widget is
created, it will be passed the widget objects that are related to the text and label names. To add widgets
using the addWidget() method would look like:
// Using a classname.
$this->Form->addWidget(
'autocomplete',
['Autocomplete', 'text', 'label']
);
// Using an instance - requires you to resolve dependencies.
$autocomplete = new AutocompleteWidget(
$this->Form->getTemplater(),
$this->Form->widgetRegistry()->get('text'),
$this->Form->widgetRegistry()->get('label'),
);
$this->Form->addWidget('autocomplete', $autocomplete);
This will create the custom widget with a label and wrapping div just like input() always does. Alternatively, you can create just the input widget using the magic method:
308
309
Will output:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
Alternatively,
echo $this->Html->charset('ISO-8859-1');
Will output:
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
Will output:
<link rel="stylesheet" href="/css/forms.css" />
Will output:
<link rel="stylesheet" href="/css/forms.css" />
<link rel="stylesheet" href="/css/tables.css" />
<link rel="stylesheet" href="/css/menu.css" />
310
You can include CSS files from any loaded plugin using plugin syntax.
ins/DebugKit/webroot/css/toolbar.css you could use the following:
To include plug-
echo $this->Html->css('DebugKit.toolbar.css');
If you want to include a CSS file which shares a name with a loaded plugin you can do the following. For
example if you had a Blog plugin, and also wanted to include webroot/css/Blog.common.css, you would:
echo $this->Html->css('Blog.common.css', ['plugin' => false]);
Will output:
background:#633; border-bottom:1px solid #000; padding:10px;
translated value
text/html
application/rss+xml
application/atom+xml
image/x-icon
<?= $this->Html->meta(
'favicon.ico',
'/favicon.ico',
['type' => 'icon']
);
?>
311
This method can also be used to add the meta keywords and descriptions. Example:
<?= $this->Html->meta(
'keywords',
'enter any meta keyword here'
);
?>
// Output
<meta name="keywords" content="enter any meta keyword here" />
<?= $this->Html->meta(
'description',
'enter any meta description here'
);
?>
// Output
<meta name="description" content="enter any meta description here" />
In addition to making predefined meta tags, you can create link elements:
<?= $this->Html->meta([
'link' => 'http://example.com/manifest',
'rel' => 'manifest'
]);
?>
// Output
<link href="http://example.com/manifest" rel="manifest"/>
Any attributes provided to meta() when called this way will be added to the generated link tag.
312
translated value
HTML4 Strict
HTML4 Transitional
HTML4 Frameset
HTML5
XHTML1 Strict
XHTML1 Transitional
XHTML1 Frameset
XHTML1.1
echo $this->Html->docType();
// Outputs: <!DOCTYPE html>
echo $this->Html->docType('html4-trans');
// Outputs:
// <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
//
"http://www.w3.org/TR/html4/loose.dtd">
Linking to Images
Cake\View\Helper\HtmlHelper::image(string $path, array $options =[])
Creates a formatted image tag. The path supplied should be relative to webroot/img/.
echo $this->Html->image('cake_logo.png', ['alt' => 'CakePHP']);
Will output:
<img src="/img/cake_logo.png" alt="CakePHP" />
To create an image link specify the link destination using the url option in $attributes.
echo $this->Html->image("recipes/6.jpg", [
"alt" => "Brownies",
'url' => ['controller' => 'Recipes', 'action' => 'view', 6]
]);
Will output:
<a href="/recipes/view/6">
<img src="/img/recipes/6.jpg" alt="Brownies" />
</a>
If you are creating images in emails, or want absolute paths to images you can use the fullBase option:
313
Will output:
<img src="http://example.com/img/logo.jpg" alt="" />
You can include image files from any loaded plugin using plugin syntax.
ins/DebugKit/webroot/img/icon.png You could use the following:
To include plug-
echo $this->Html->image('DebugKit.icon.png');
If you want to include an image file which shares a name with a loaded plugin you can do the following. For
example if you had a Blog plugin, and also wanted to include webroot/img/Blog.icon.png, you
would:
echo $this->Html->image('Blog.icon.png', ['plugin' => false]);
Creating Links
Cake\View\Helper\HtmlHelper::link(string $title, mixed $url = null, array $options =[
])
General purpose method for creating HTML links. Use $options to specify attributes for the element and
whether or not the $title should be escaped.
echo $this->Html->link(
'Enter',
'/pages/home',
['class' => 'button', 'target' => '_blank']
);
Will output:
<a href="/pages/home" class="button" target="_blank">Enter</a>
Will output:
<a href="http://www.yourdomain.com/dashboards/index">Dashboard</a>
314
Will output:
<a href="/recipes/delete/6"
onclick="return confirm(
'Are you sure you wish to delete this recipe?'
);">
Delete
</a>
Will output:
<a href="/images/view/1?height=400&width=500">View image</a>
HTML special characters in $title will be converted to HTML entities. To disable this conversion, set
the escape option to false in the $options array.
echo $this->Html->link(
$this->Html->image("recipes/6.jpg", ["alt" => "Brownies"]),
"recipes/view/6",
['escape' => false]
);
Will output:
<a href="/recipes/view/6">
<img src="/img/recipes/6.jpg" alt="Brownies" />
</a>
Setting escape to false will also disable escaping of attributes of the link. You can use the option
escapeTitle to disable just escaping of title and not the attributes.
echo $this->Html->link(
$this->Html->image('recipes/6.jpg', ['alt' => 'Brownies']),
'recipes/view/6',
['escapeTitle' => false, 'title' => 'hi "howdy"']
);
Will output:
315
316
Will output:
<script src="/js/scripts.js"></script>
You can link to files with absolute paths as well to link files that are not in webroot/js:
echo $this->Html->script('/otherdir/script_file');
Will output:
<script src="http://code.jquery.com/jquery.min.js"></script>
Will output:
<script src="/js/jquery.js"></script>
<script src="/js/wysiwyg.js"></script>
<script src="/js/scripts.js"></script>
You can append the script tag to a specific block using the block option:
echo $this->Html->script('wysiwyg', ['block' => 'scriptBottom']);
In your layout you can output all the script tags added to scriptBottom:
More About Views
317
echo $this->fetch('scriptBottom');
To include plug-
You can include script files from any loaded plugin using plugin syntax.
ins/DebugKit/webroot/js/toolbar.js You could use the following:
echo $this->Html->script('DebugKit.toolbar.js');
If you want to include a script file which shares a name with a loaded plugin you can do the following. For
example if you had a Blog plugin, and also wanted to include webroot/js/Blog.plugins.js, you
would:
echo $this->Html->script('Blog.plugins.js', ['plugin' => false]);
Cake\View\Helper\HtmlHelper::scriptStart($options =[])
Cake\View\Helper\HtmlHelper::scriptEnd()
You can use the scriptStart() method to create a capturing block that will output into a <script>
tag. Captured script snippets can be output inline, or buffered into a block:
// Append into the 'script' block.
$this->Html->scriptStart(['block' => true]);
echo "alert('I am in the JavaScript');";
$this->Html->scriptEnd();
Once you have buffered javascript, you can output it as you would any other View Block:
// In your layout
echo $this->fetch('script');
318
$list = [
'Languages' => [
'English' => [
'American',
'Canadian',
'British',
],
'Spanish',
'German',
]
];
echo $this->Html->nestedList($list);
Output:
// Output (minus the whitespace)
<ul>
<li>Languages
<ul>
<li>English
<ul>
<li>American</li>
<li>Canadian</li>
<li>British</li>
</ul>
</li>
<li>Spanish</li>
<li>German</li>
</ul>
</li>
</ul>
Output:
<tr>
<th>Date</th>
<th>Title</th>
<th>Active</th>
</tr>
echo $this->Html->tableHeaders(
['Date','Title','Active'],
319
Output:
<tr class="status">
<th class="product_table">Date</th>
<th class="product_table">Title</th>
<th class="product_table">Active</th>
</tr>
You can set attributes per column, these are used instead of the defaults provided in the $thOptions:
echo $this->Html->tableHeaders([
'id',
['Name' => ['class' => 'highlight']],
['Date' => ['class' => 'sortable']]
]);
Output:
<tr>
<th>id</th>
<th class="highlight">Name</th>
<th class="sortable">Date</th>
</tr>
Output:
<tr><td>Jul 7th, 2007</td><td>Best Brownies</td><td>Yes</td></tr>
<tr><td>Jun 21st, 2007</td><td>Smart Cookies</td><td>Yes</td></tr>
<tr><td>Aug 1st, 2006</td><td>Anti-Java Cake</td><td>No</td></tr>
320
echo $this->Html->tableCells([
['Jul 7th, 2007', ['Best Brownies', ['class' => 'highlight']] , 'Yes'],
['Jun 21st, 2007', 'Smart Cookies', 'Yes'],
['Aug 1st, 2006', 'Anti-Java Cake', ['No', ['id' => 'special']]],
]);
Output:
<tr>
<td>
Jul 7th, 2007
</td>
<td class="highlight">
Best Brownies
</td>
<td>
Yes
</td>
</tr>
<tr>
<td>
Jun 21st, 2007
</td>
<td>
Smart Cookies
</td>
<td>
Yes
</td>
</tr>
<tr>
<td>
Aug 1st, 2006
</td>
<td>
Anti-Java Cake
</td>
<td id="special">
No
</td>
</tr>
echo $this->Html->tableCells(
[
['Red', 'Apple'],
['Orange', 'Orange'],
['Yellow', 'Banana'],
],
['class' => 'darker']
);
Output:
321
<tr class="darker"><td>Red</td><td>Apple</td></tr>
<tr><td>Orange</td><td>Orange</td></tr>
<tr class="darker"><td>Yellow</td><td>Banana</td></tr>
Warning: Template strings containing a percentage sign (%) need special attention, you should prefix
this character with another percentage so it looks like %%. The reason is that internally templates are compiled to be used with sprintf(). Example: <div style=width:{{size}}%%>{{content}}</div>
322
The $startText option can also accept an array. This gives more control over the generated first link:
echo $this->Html->getCrumbs(' > ', [
'text' => $this->Html->image('home.png'),
'url' => ['controller' => 'Pages', 'action' => 'display', 'home'],
'escape' => false
]);
Any keys that are not text or url will be passed to link() as the $options parameter.
Now, in your view youll want to add the following to start the breadcrumb trails on each of the pages:
$this->Html->addCrumb('Users', '/users');
$this->Html->addCrumb('Add User', ['controller' => 'Users', 'action' => 'add
']);
This will add the output of Home > Users > Add User in your layout where getCrumbs was added.
You can also fetch the crumbs formatted inside an HTML list:
echo $this->Html->getCrumbList();
As options you can use regular HTML parameter that fits in the <ul> (Unordered List) such as class and
for the specific options, you have: separator (will be between the <li> elements), firstClass and
lastClass like:
echo $this->Html->getCrumbList(
[
'firstClass' => false,
'lastClass' => 'active',
'class' => 'breadcrumb'
],
'Home'
);
323
The first parameter, $value, should be a floating point number that represents the amount of money you
are expressing. The second parameter is a string used to choose a predefined currency formatting scheme:
$currency
EUR
GBP
USD
The third parameter is an array of options for further defining the output. The following options are available:
Option
before
after
zero
places
precision
locale
fractionSymbol
fractionPosition
pattern
useIntlCode
Description
Text to display before the rendered number.
Text to display after the rendered number.
The text to use for zero values; can be a string or a number. ie. 0, Free!.
Number of decimal places to use, ie. 2
Maximal number of decimal places to use, ie. 2
The locale name to use for formatting number, ie. fr_FR.
String to use for fraction numbers, ie. cents.
Either before or after to place the fraction symbol.
An ICU number pattern to use for formatting the number ie. #,###.00
Set to true to replace the currency symbol with the international currency code.
If
$currency
value
is
null,
the
default
Cake\I18n\Number::defaultCurrency()
currency
will
be
retrieved
from
324
Formatting Percentages
Cake\View\Helper\NumberHelper::toPercentage(mixed $value, int $precision = 2,
array $options =[])
Option
multiply
Description
Boolean to indicate whether the value has to be multiplied by 100. Useful for decimal
percentages.
325
// Called as NumberHelper
echo $this->Number->toReadableSize(0); // 0 Byte
echo $this->Number->toReadableSize(1024); // 1 KB
echo $this->Number->toReadableSize(1321205.76); // 1.26 MB
echo $this->Number->toReadableSize(5368709120); // 5 GB
// Called as Number
echo Number::toReadableSize(0); // 0 Byte
echo Number::toReadableSize(1024); // 1 KB
echo Number::toReadableSize(1321205.76); // 1.26 MB
echo Number::toReadableSize(5368709120); // 5 GB
Formatting Numbers
Cake\View\Helper\NumberHelper::format(mixed $value, array $options =[])
This method gives you much more control over the formatting of numbers for use in your views (and is used
as the main method by most of the other NumberHelper methods). Using this method might looks like:
// Called as NumberHelper
$this->Number->format($value, $options);
// Called as Number
Number::format($value, $options);
The $value parameter is the number that you are planning on formatting for output. With no $options
supplied, the number 1236.334 would output as 1,236. Note that the default precision is zero decimal places.
The $options parameter is where the real magic for this method resides.
If you pass an integer then this becomes the amount of precision or places for the function.
If you pass an associated array, you can use the following keys:
Option
places
precision
pattern
locale
before
after
Description
Number of decimal places to use, ie. 2
Maximum number of decimal places to use, ie. 2
An ICU number pattern to use for formatting the number ie. #,###.00
The locale name to use for formatting number, ie. fr_FR.
Text to display before the rendered number.
Text to display after the rendered number.
Example:
// Called as NumberHelper
echo $this->Number->format('123456.7890', [
'places' => 2,
'before' => ' ',
'after' => ' !'
]);
// Output ' 123,456.79 !'
326
echo $this->Number->format('123456.7890', [
'locale' => 'fr_FR'
]);
// Output '123 456,79 !'
// Called as Number
echo Number::format('123456.7890', [
'places' => 2,
'before' => ' ',
'after' => ' !'
]);
// Output ' 123,456.79 !'
echo Number::format('123456.7890', [
'locale' => 'fr_FR'
]);
// Output '123 456,79 !'
Format Differences
Cake\View\Helper\NumberHelper::formatDelta(mixed $value, array $options =[])
This method displays differences in value as a signed number:
// Called as NumberHelper
$this->Number->formatDelta($value, $options);
// Called as Number
Number::formatDelta($value, $options);
The $value parameter is the number that you are planning on formatting for output. With no $options
supplied, the number 1236.334 would output as 1,236. Note that the default precision is zero decimal places.
More About Views
327
Description
Number of decimal places to use, ie. 2
Maximum number of decimal places to use, ie. 2
The locale name to use for formatting number, ie. fr_FR.
Text to display before the rendered number.
Text to display after the rendered number.
Example:
// Called as NumberHelper
echo $this->Number->formatDelta('123456.7890', [
'places' => 2,
'before' => '[',
'after' => ']'
]);
// Output '[+123,456.79]'
// Called as Number
echo Number::formatDelta('123456.7890', [
'places' => 2,
'before' => '[',
'after' => ']'
]);
// Output '[+123,456.79]'
Paginator
class Cake\View\Helper\PaginatorHelper(View $view, array $config =[])
The Paginator helper is used to output pagination controls such as page numbers and next/previous links. It
works in tandem with PaginatorComponent.
See also Pagination for information on how to create paginated datasets and do paginated queries.
PaginatorHelper Templates
Internally PaginatorHelper uses a series of simple HTML templates to generate markup. You can modify
these templates to customize the HTML generated by the PaginatorHelper.
Templates use {{var}} style placeholders. It is important to not add any spaces around the {{}} or the
replacements will not work.
328
This will load the file located at config/paginator-templates.php. See the example below for how the file
should look like. You can also load templates from a plugin using plugin syntax:
// In your AppView.php
public function initialize()
{
...
$this->loadHelper('Paginator', ['templates' => 'MyPlugin.paginatortemplates']);
}
Whether your templates are in the primary application or a plugin, your templates file should look something
like:
return [
'number' => '<a href="{{url}}">{{text}}</a>',
];
Warning: Template strings containing a percentage sign (%) need special attention, you should prefix
this character with another percentage so it looks like %%. The reason is that internally templates are compiled to be used with sprintf(). Example: <div style=width:{{size}}%%>{{content}}</div>
329
Template Names
PaginatorHelper uses the following templates:
nextActive The active state for a link generated by next().
nextDisabled The disabled state for next().
prevActive The active state for a link generated by prev().
prevDisabled The disabled state for prev()
counterRange The template counter() uses when format == range.
counterPages The template counter() uses when format == pages.
first The template used for a link generated by first().
last The template used for a link generated by last()
number The template used for a link generated by numbers().
current The template used for the current page.
ellipsis The template used for ellipses generated by numbers().
sort The template for a sort link with no direction.
sortAsc The template for a sort link with an ascending direction.
sortDesc The template for a sort link with a descending direction.
Creating Sort Links
Cake\View\Helper\PaginatorHelper::sort($key, $title = null, $options =[])
Parameters
$key (string) The name of the column that the recordset should be sorted.
$title (string) Title for the link. If $title is null $key will be used for the
title and will be generated by inflection.
$options (array) Options for sorting link.
Generates a sorting link. Sets querystring parameters for the sort and direction. Links will default to sorting
by asc. After the first click, links generated with sort() will handle direction switching automatically. If
the resultset is sorted asc by the specified key the returned link will sort by desc.
Accepted keys for $options:
escape Whether you want the contents HTML entity encoded, defaults to true.
model The model to use, defaults to PaginatorHelper::defaultModel().
direction The default direction to use when this link isnt active.
lock Lock direction. Will only use the default direction then, defaults to false.
330
Assuming you are paginating some posts, and are on page one:
echo $this->Paginator->sort('user_id');
Output:
<a href="/posts/index?page=1&sort=user_id&direction=asc">User Id</a>
You can use the title parameter to create custom text for your link:
echo $this->Paginator->sort('user_id', 'User account');
Output:
<a href="/posts/index?page=1&sort=user_id&direction=asc">User account
</a>
If you are using HTML like images in your links remember to set escaping off:
echo $this->Paginator->sort(
'user_id',
'<em>User account</em>',
['escape' => false]
);
Output:
<a href="/posts/index?page=1&sort=user_id&direction=asc"><em>User
account</em></a>
The direction option can be used to set the default direction for a link. Once a link is active, it will automatically switch directions like normal:
echo $this->Paginator->sort('user_id', null, ['direction' => 'desc']);
Output:
<a href="/posts/index?page=1&sort=user_id&direction=desc">User Id</a>
The lock option can be used to lock sorting into the specified direction:
echo $this->Paginator->sort('user_id', null, ['direction' => 'asc', 'lock' =>
true]);
331
last Whether you want last links generated, set to an integer to define the number of last links to
generate. Defaults to false. Follows the same logic as the first option. There is a last()`
method to be used separately as well if you wish.
While this method allows a lot of customization for its output. It is also ok to just call the method without
any params.
echo $this->Paginator->numbers();
Using the first and last options you can create links to the beginning and end of the page set. The following
would create a set of page links that include links to the first 2 and last 2 pages in the paged results:
echo $this->Paginator->numbers(['first' => 2, 'last' => 2]);
escape Whether you want the contents HTML entity encoded, defaults to true.
model The model to use, defaults to PaginatorHelper::defaultModel().
disabledTitle The text to use when the link is disabled. Defaults to the $title parameter.
A simple example would be:
echo $this->Paginator->prev(' << ' . __('previous'));
If you were currently on the second page of posts, you would get the following:
<li class="prev">
<a rel="prev" href="/posts/index?page=1&sort=title&order=desc
">
<< previous
</a>
</li>
The above creates a single link for the first page. Will output nothing if you are on the first page. You
can also use an integer to indicate how many first paging links you want generated:
echo $this->Paginator->first(3);
The above will create links for the first 3 pages, once you get to the third or greater page. Prior to that
nothing will be output.
The options parameter accepts the following:
model The model to use defaults to PaginatorHelper::defaultModel()
escape Whether or not the text should be escaped. Set to false if your content contains
HTML.
Cake\View\Helper\PaginatorHelper::last($last = last >>, $options =[])
This method works very much like the first() method. It has a few differences though. It will not
generate any links if you are on the last page for a string values of $last. For an integer value of
$last no links will be generated once the user is inside the range of last pages.
333
334
model
The
name
of
the
model
being
paginated,
defaults
to
PaginatorHelper::defaultModel(). This is used in conjunction with the custom
string on format option.
Configuring Pagination Options
Cake\View\Helper\PaginatorHelper::options($options =[])
Sets all the options for the Paginator Helper. Supported options are:
url The URL of the paginating action. url has a few sub options as well:
sort The key that the records are sorted by.
direction The direction of the sorting. Defaults to ASC.
page The page number to display.
The above mentioned options can be used to force particular pages/directions. You can also append
additional URL content into all URLs generated in the helper:
$this->Paginator->options([
'url' => [
'sort' => 'email',
'direction' => 'desc',
'page' => 6,
'lang' => 'en'
]
]);
The above adds the en route parameter to all links the helper will generate. It will also create links
with specific sort, direction and page values. By default PaginatorHelper will merge in all of the
current passed arguments and query string parameters.
escape Defines if the title field for links should be HTML escaped. Defaults to true.
model
The
name
of
the
model
PaginatorHelper::defaultModel().
being
paginated,
defaults
to
Example Usage
Its up to you to decide how to show records to the user, but most often this will be done inside HTML
tables. The examples below assume a tabular layout, but the PaginatorHelper available in views doesnt
always need to be restricted as such.
335
See the details on PaginatorHelper109 in the API. As mentioned, the PaginatorHelper also offers sorting
features which can be integrated into your table column headers:
<!-- src/Template/Posts/index.ctp -->
<table>
<tr>
<th><?= $this->Paginator->sort('id', 'ID') ?></th>
<th><?= $this->Paginator->sort('title', 'Title') ?></th>
</tr>
<?php foreach ($recipes as $recipe): ?>
<tr>
<td><?= $recipe->id ?> </td>
<td><?= h($recipe->title) ?> </td>
</tr>
<?php endforeach; ?>
</table>
The links output from the sort() method of the PaginatorHelper allow users to click on table headers
to toggle the sorting of the data by a given field.
It is also possible to sort a column based on associations:
<table>
<tr>
<th><?= $this->Paginator->sort('title', 'Title') ?></th>
<th><?= $this->Paginator->sort('Authors.name', 'Author') ?></th>
</tr>
<?php foreach ($recipes as $recipe): ?>
<tr>
<td><?= h($recipe->title) ?> </td>
<td><?= h($recipe->name) ?> </td>
</tr>
<?php endforeach; ?>
</table>
The final ingredient to pagination display in views is the addition of page navigation, also supplied by the
PaginationHelper:
// Shows the page numbers
<?= $this->Paginator->numbers() ?>
// Shows the next and previous links
<?= $this->Paginator->prev(' Previous') ?>
<?= $this->Paginator->next('Next ') ?>
// Prints X of Y, where X is current page and Y is number of pages
<?= $this->Paginator->counter() ?>
The wording output by the counter() method can also be customized using special markers:
109
336
http://api.cakephp.org/3.0/class-Cake.View.Helper.PaginatorHelper.html
<?= $this->Paginator->counter([
'format' => 'Page {{page}} of {{pages}}, showing {{current}} records out
of
{{count}} total, starting on record {{start}}, ending on {{end}}'
]) ?>
By using the model option, PaginatorHelper will automatically use the scope defined in when the
query was paginated.
New in version 3.3.0: Multiple Pagination was added in 3.3.0
Rss
class Cake\View\Helper\RssHelper(View $view, array $config =[])
The RSS helper makes generating XML for RSS feeds110 easy.
Creating an RSS Feed with the RssHelper
This example assumes you have a Articles Controller, Articles Table and an Article Entity already created
and want to make an alternative view for RSS.
110
https://en.wikipedia.org/wiki/RSS
337
Creating an XML/RSS version of articles/index is a snap with CakePHP. After a few simple steps you can simply append the desired extension .rss to articles/index making your URL
articles/index.rss. Before we jump too far ahead trying to get our webservice up and running
we need to do a few things. First extensions parsing needs to be activated, this is done in config/routes.php:
Router::extensions('rss');
In
the
call
above
weve
activated
the
.rss
extension.
When
using
Cake\Routing\Router::extensions() you can pass a string or an array of extensions as
first argument. This will activate each extension/content-type for use in your application. Now when the
address articles/index.rss is requested you will get an XML version of your articles/index.
However, first we need to edit the controller to add in the rss-specific code.
Controller Code
It is a good idea to add RequestHandler to your ArticlesControllers initialize() method. This will
allow a lot of automagic to occur:
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler');
}
Before we can make an RSS version of our articles/index we need to get a few things in order. It may be tempting to put the channel metadata in the controller action and pass it to your view
using the Cake\Controller\Controller::set() method but this is inappropriate. That information can also go in the view. That will come later though, for now if you have a different
set of logic for the data used to make the RSS feed and the data for the HTML view you can use
the Cake\Controller\Component\RequestHandler::isRss() method, otherwise your controller can stay the same:
// Modify the Posts Controller action that corresponds to
// the action which deliver the rss feed, which is the
// Index action in our example.
public function index()
{
if ($this->RequestHandler->isRss() ) {
$articles = $this->Articles
->find()
->limit(20)
->order(['created' => 'desc']);
$this->set(compact('articles'));
} else {
// this is not an Rss request, so deliver
// data used by website's interface.
$this->paginate = [
'order' => ['created' => 'desc'],
'limit' => 10
338
];
$this->set('articles', $this->paginate($this->Articles));
$this->set('_serialize', ['articles']);
}
}
With all the View variables set we need to create an rss layout.
Layout
An Rss layout is very simple, put the following contents in src/Template/Layout/rss/default.ctp:
if (!isset($documentData)) {
$documentData = [];
}
if (!isset($channelData)) {
$channelData = [];
}
if (!isset($channelData['title'])) {
$channelData['title'] = $this->fetch('title');
}
$channel = $this->Rss->channel([], $channelData, $this->fetch('content'));
echo $this->Rss->document($documentData, $channel);
It doesnt look like much but thanks to the power in the RssHelper its doing a lot of lifting for us. We
havent set $documentData or $channelData in the controller, however in CakePHP your views can
pass variables back to the layout. Which is where our $channelData array will come from setting all of
the meta data for our feed.
Next up is view file for my articles/index. Much like the layout file we created, we need to create a
src/Template/Posts/rss/ directory and create a new index.ctp inside that folder. The contents of the file
are below.
View
Our view, located at src/Template/Posts/rss/index.ctp, begins by setting the $documentData
and $channelData variables for the layout, these contain all the metadata for our RSS feed.
This is done by using the Cake\View\View::set() method which is analogous to the
Cake\Controller\Controller::set() method. Here though we are passing the channels metadata back to the layout:
$this->set('channelData', [
'title' => __("Most Recent Posts"),
'link' => $this->Url->build('/', true),
'description' => __("Most recent posts."),
'language' => 'en-us'
]);
339
The second part of the view generates the elements for the actual records of the feed. This is accomplished
by looping through the data that has been passed to the view ($items) and using the RssHelper::item()
method. The other method you can use, RssHelper::items() which takes a callback and an array of
items for the feed. The callback method is usually called transformRss(). There is one downfall to this
method, which is that you cannot use any of the other helper classes to prepare your data inside the callback
method because the scope inside the method does not include anything that is not passed inside, thus not
giving access to the TimeHelper or any other helper that you may need. The RssHelper::item()
transforms the associative array into an element for each key value pair.
Note: You will need to modify the $link variable as appropriate to your application. You might also want
to use a virtual property in your Entity.
$this->Rss->item([], [
'title' => $article->title,
'link' => $link,
'guid' => ['url' => $link, 'isPermaLink' => 'true'],
'description' => $body,
'pubDate' => $article->created
]);
}
You can see above that we can use the loop to prepare the data to be transformed into XML elements. It is
important to filter out any non-plain text characters out of the description, especially if you are using a rich
text editor for the body of your blog. In the code above we used strip_tags() and h() to remove/escape
any XML special characters from the content, as they could cause validation errors. Once we have set up the
data for the feed, we can then use the RssHelper::item() method to create the XML in RSS format.
Once you have all this setup, you can test your RSS feed by going to your site /posts/index.rss
and you will see your new feed. It is always important that you validate your RSS feed before making it
live. This can be done by visiting sites that validate the XML such as Feed Validator or the w3c site at
http://validator.w3.org/feed/.
340
Note: You may need to set the value of debug in your core configuration to false to get a valid feed,
because of the various debug information added automagically under higher debug settings that break XML
syntax or feed validation rules.
Session
class Cake\View\Helper\SessionHelper(View $view, array $config =[])
Deprecated since version 3.0.0: The SessionHelper is deprecated in 3.x. Instead you should use either the
FlashHelper or access the session via the request.
As a natural counterpart to the Session object, the Session Helper replicates most of the objects functionality
and makes it available in your view.
The major difference between the Session Helper and the Session object is that the helper does not have the
ability to write to the session.
As with the session object, data is read by using dot notation array structures:
['User' => [
'username' => 'super@example.com'
]];
Given the previous array structure, the node would be accessed by User.username, with the dot indicating the nested array. This notation is used for all Session helper methods wherever a $key is used.
Cake\View\Helper\SessionHelper::read(string $key)
Return type mixed
Read from the Session. Returns a string or array depending on the contents of the session.
Cake\View\Helper\SessionHelper::check(string $key)
Return type boolean
Check to see whether a key is in the Session. Returns a boolean representing the keys existence.
Text
class Cake\View\Helper\TextHelper(View $view, array $config =[])
The TextHelper contains methods to make text more usable and friendly in your views. It aids in enabling
links, formatting URLs, creating excerpts of text around chosen words or phrases, highlighting key words
in blocks of text, and gracefully truncating long stretches of text.
Linking Email addresses
Cake\View\Helper\TextHelper::autoLinkEmails(string $text, array $options =[])
More About Views
341
Adds links to the well-formed email addresses in $text, according to any options defined in $options (see
HtmlHelper::link()).
$myText = 'For more information regarding our world-famous ' .
'pastries and desserts, contact info@example.com';
$linkedText = $this->Text->autoLinkEmails($myText);
Output:
For more information regarding our world-famous pastries and desserts,
contact <a href="mailto:info@example.com">info@example.com</a>
This method automatically escapes its input. Use the escape option to disable this if necessary.
Linking URLs
Cake\View\Helper\TextHelper::autoLinkUrls(string $text, array $options =[])
Same as autoLinkEmails(), only this method searches for strings that start with https, http, ftp, or nntp
and links them appropriately.
This method automatically escapes its input. Use the escape option to disable this if necessary.
Linking Both URLs and Email Addresses
Cake\View\Helper\TextHelper::autoLink(string $text, array $options =[])
Performs the functionality in both autoLinkUrls() and autoLinkEmails() on the supplied
$text. All URLs and emails are linked appropriately given the supplied $options.
This method automatically escapes its input. Use the escape option to disable this if necessary.
Converting Text into Paragraphs
Cake\View\Helper\TextHelper::autoParagraph(string $text)
Adds proper <p> around text where double-line returns are found, and <br> where single-line returns are
found.
$myText = 'For more information
regarding our world-famous pastries and desserts.
contact info@example.com';
$formattedText = $this->Text->autoParagraph($myText);
Output:
<p>For more information<br />
regarding our world-famous pastries and desserts.<p>
<p>contact info@example.com</p>
342
Highlighting Substrings
Cake\View\Helper\TextHelper::highlight(string $haystack, string $needle, array
$options =[])
Highlights $needle in $haystack using the $options['format'] string specified or a default
string.
Options:
format string - The piece of HTML with the phrase that will be highlighted
html bool - If true, will ignore any HTML tags, ensuring that only the correct text is highlighted
Example:
// Called as TextHelper
echo $this->Text->highlight(
$lastSentence,
'using',
['format' => '<span class="highlight">\1</span>']
);
// Called as Text
use Cake\Utility\Text;
echo Text::highlight(
$lastSentence,
'using',
['format' => '<span class="highlight">\1</span>']
);
Output:
Highlights $needle in $haystack <span class="highlight">using</span> the
$options['format'] string specified or a default string.
Removing Links
Cake\View\Helper\TextHelper::stripLinks($text)
Strips the supplied $text of any HTML links.
Truncating Text
Cake\View\Helper\TextHelper::truncate(string $text, int $length = 100, array $options)
If $text is longer than $length, this method truncates it at $length and adds a prefix consisting of
'ellipsis', if defined. If 'exact' is passed as false, the truncation will occur at the first whitespace
343
after the point at which $length is exceeded. If 'html' is passed as true, HTML tags will be respected
and will not be cut off.
$options is used to pass all extra parameters, and has the following possible keys by default, all of which
are optional:
[
'ellipsis' => '...',
'exact' => true,
'html' => false
]
Example:
// Called as TextHelper
echo $this->Text->truncate(
'The killer crept forward and tripped on the rug.',
22,
[
'ellipsis' => '...',
'exact' => false
]
);
// Called as Text
use Cake\Utility\Text;
echo Text::truncate(
'The killer crept forward and tripped on the rug.',
22,
[
'ellipsis' => '...',
'exact' => false
]
);
Output:
The killer crept...
344
[
'ellipsis' => '...',
'exact' => true
]
Example:
$sampleText = 'I packed my bag and in it I put a PSP, a PS3, a TV, ' .
'a C# program that can divide by zero, death metal t-shirts'
// Called as TextHelper
echo $this->Text->tail(
$sampleText,
70,
[
'ellipsis' => '...',
'exact' => false
]
);
// Called as Text
use Cake\Utility\Text;
echo Text::tail(
$sampleText,
70,
[
'ellipsis' => '...',
'exact' => false
]
);
Output:
...a TV, a C# program that can divide by zero, death metal t-shirts
Extracting an Excerpt
Cake\View\Helper\TextHelper::excerpt(string $haystack, string $needle, integer $radius=100, string $ellipsis=...)
Extracts an excerpt from $haystack surrounding the $needle with a number of characters on each side
determined by $radius, and prefix/suffix with $ellipsis. This method is especially handy for search
results. The query string or keywords can be shown within the resulting document.
// Called as TextHelper
echo $this->Text->excerpt($lastParagraph, 'method', 50, '...');
// Called as Text
use Cake\Utility\Text;
345
Output:
... by $radius, and prefix/suffix with $ellipsis. This method is especially
handy for search results. The query...
Output:
red, orange, yellow, green, blue, indigo and violet
Time
class Cake\View\Helper\TimeHelper(View $view, array $config =[])
The Time Helper does what it says on the tin: saves you time. It allows for the quick processing of time
related information. The Time Helper has two main tasks that it can perform:
1. It can format time strings.
2. It can test time (but cannot bend time, sorry).
Using the Helper
A common use of the Time Helper is to offset the date and time to match a users time zone. Lets use a
forum as an example. Your forum has many users who may post messages at any time from any part of the
world. An easy way to manage the time is to save all dates and times as GMT+0 or UTC. Uncomment the
line date_default_timezone_set('UTC'); in config/bootstrap.php to ensure your applications
time zone is set to GMT+0.
Next add a time zone field to your users table and make the necessary modifications to allow your users to
set their time zone. Now that we know the time zone of the logged in user we can correct the date and time
on our posts using the Time Helper:
346
echo $this->Time->format(
$post->created,
\IntlDateFormatter::FULL,
null,
$user->time_zone
);
// Will display 'Saturday, August 22, 2011 at 11:53:00 PM GMT'
// for a user in GMT+0. While displaying,
// 'Saturday, August 22, 2011 at 03:53 PM GMT-8:00'
// for a user in GMT-8
Most of TimeHelpers features are intended as backwards compatible interfaces for applications that are
upgrading from older versions of CakePHP. Because the ORM returns Cake\I18n\Time instances for
every timestamp and datetime column, you can use the methods there to do most tasks. E.g. to read
about the accepted formatting strings take a look at the Cake\I18n\Time::i18nFormat()111 method.
Url
class Cake\View\Helper\UrlHelper(View $view, array $config =[])
The UrlHelper makes it easy for you to generate URLs from your other helpers. It also gives you a single
place to customize how URLs are generated by overriding the core helper with an application one. See the
Aliasing Helpers section for how to do this.
Generating URLs
Cake\View\Helper\UrlHelper::build(mixed $url = null, boolean $full = false)
Returns a URL pointing to a combination of controller and action. If $url is empty, it returns the
REQUEST\_URI, otherwise it generates the URL for the controller and action combo. If full is true,
the full base URL will be prepended to the result:
echo $this->Url->build([
"controller" => "Posts",
"action" => "view",
"bar"
]);
// Output
/posts/view/bar
http://api.cakephp.org/3.0/class-Cake.I18n.Time.html#_i18nFormat
347
The above example uses the ? key which is useful when you want to be explicit about the query string
parameters you are using, or if you want a query string parameter that shares a name with one of your route
placeholders.
URL for named route:
echo $this->Url->build(['_name' => 'product-page', 'slug' => 'i-m-slug']);
// Assuming route is setup like:
// $router->connect(
//
'/products/:slug',
//
[
//
'controller' => 'Products',
//
'action' => 'view'
//
],
//
[
//
'_name' => 'product-page'
//
]
// );
/products/i-m-slug
If you are generating URLs for CSS, Javascript or image files there are helper methods for each of these
asset types:
// Outputs /img/icon.png
$this->Url->image('icon.png');
// Outputs /js/app.js
348
$this->Url->script('app.js');
// Outputs /css/app.css
$this->Url->css('app.css');
New in version 3.2.4: The asset helper methods were added in 3.2.4.
For further information check Router::url112 in the API.
Configuring Helpers
You load helpers in CakePHP by declaring them in a view class. An AppView class comes with every
CakePHP application and is the ideal place to load helpers:
class AppView extends View
{
public function initialize()
{
parent::initialize();
$this->loadHelper('Html');
$this->loadHelper('Form');
$this->loadHelper('Flash');
}
}
To load helpers from plugins use the plugin syntax used elsewhere in CakePHP:
$this->loadHelper('Blog.Comment');
You dont have to explicitly load Helpers that come from CakePHP or your application. These helpers can
be lazily loaded upon first use. For example:
// Loads the FormHelper if it has not already been loaded.
$this->Form->create($article);
From within a plugins views, plugin helpers can also be lazily loaded. For example, view templates in the
Blog plugin, can lazily load helpers from the same plugin.
Conditionally Loading Helpers
You can use the current action name to conditionally load helpers:
class AppView extends View
{
public function initialize()
{
parent::initialize();
if ($this->request->action === 'index') {
112
http://api.cakephp.org/3.0/class-Cake.Routing.Router.html#_url
349
$this->loadHelper('ListPage');
}
}
}
You can also use your controllers beforeRender method to load helpers:
class ArticlesController extends AppController
{
public function beforeRender(Event $event)
{
parent::beforeRender($event);
$this->viewBuilder()->helpers(['MyHelper']);
}
}
Configuration options
You can pass configuration options to helpers. These options can be used to set attribute values or modify
the behavior of a helper:
namespace App\View\Helper;
use Cake\View\Helper;
use Cake\View\View;
class AwesomeHelper extends Helper
{
// initialize() hook is available since 3.2. For prior versions you can
// override the constructor if required.
public function initialize(array $config)
{
debug($config);
}
}
By default all configuration options will be merged with the $_defaultConfig property. This property
should define the default values of any configuration your helper requires. For example:
350
namespace App\View\Helper;
use Cake\View\Helper;
use Cake\View\StringTemplateTrait;
class AwesomeHelper extends Helper
{
use StringTemplateTrait;
protected $_defaultConfig = [
'errorClass' => 'error',
'templates' => [
'label' => '<label for="{{for}}">{{content}}</label>',
],
];
}
Any configuration provided to your helpers constructor will be merged with the default values during construction and the merged data will be set to _config. You can use the config() method to read runtime
configuration:
// Read the errorClass config option.
$class = $this->Awesome->config('errorClass');
Using helper configuration allows you to declaratively configure your helpers and keep configuration logic
out of your controller actions. If you have configuration options that cannot be included as part of a class
declaration, you can set those in your controllers beforeRender callback:
class PostsController extends AppController
{
public function beforeRender(Event $event)
{
parent::beforeRender($event);
$builder = $this->viewBuilder();
$builder->helpers([
'CustomStuff' => $this->_getCustomStuffConfig(),
]);
}
}
Aliasing Helpers
One common setting to use is the className option, which allows you to create aliased helpers in your
views. This feature is useful when you want to replace $this->Html or another common Helper reference
with a custom implementation:
// src/View/AppView.php
class AppView extends View
{
351
Using Helpers
Once youve configured which helpers you want to use in your controller, each helper is exposed as a public
property in the view. For example, if you were using the HtmlHelper you would be able to access it by
doing the following:
echo $this->Html->css('styles');
The above would call the css() method on the HtmlHelper. You can access any loaded helper using
$this->{$helperName}.
Loading Helpers On The Fly
There may be situations where you need to dynamically load a helper from inside a view. You can use the
views Cake\View\HelperRegistry to do this:
// Either one works.
$mediaHelper = $this->loadHelper('Media', $mediaConfig);
$mediaHelper = $this->helpers()->load('Media', $mediaConfig);
The HelperRegistry is a registry and supports the registry API used elsewhere in CakePHP.
352
Callback Methods
Helpers feature several callbacks that allow you to augment the view rendering process. See the Helper
Class and the Events System documentation for more information.
Creating Helpers
You can create custom helper classes for use in your application or plugins. Like most components of
CakePHP, helper classes have a few conventions:
Helper class files should be put in src/View/Helper. For example: src/View/Helper/LinkHelper.php
Helper classes should be suffixed with Helper. For example: LinkHelper.
When referencing helper class names you should omit the Helper suffix.
$this->loadHelper('Link');.
For example:
353
Once your helper has been loaded, you can use it in your views by accessing the matching view property:
<!-- make a link using the new helper -->
<?= $this->Link->makeEdit('Change this Recipe', '/recipes/edit/5') ?>
Note: The HelperRegistry will attempt to lazy load any helpers not specifically identified in your
Controller.
354
Helper Class
class Helper
Callbacks
By implementing a callback method in a helper, CakePHP will automatically subscribe your helper to the
relevant event. Unlike previous versions of CakePHP you should not call parent in your callbacks, as the
base Helper class does not implement any of the callback methods.
Helper::beforeRenderFile(Event $event, $viewFile)
Is called before each view file is rendered. This includes elements, views, parent views and layouts.
Helper::afterRenderFile(Event $event, $viewFile, $content)
Is called after each view file is rendered. This includes elements, views, parent views and layouts. A
callback can modify and return $content to change how the rendered content will be displayed in
the browser.
Helper::beforeRender(Event $event, $viewFile)
The beforeRender method is called after the controllers beforeRender method but before the controller renders view and layout. Receives the file being rendered as an argument.
Helper::afterRender(Event $event, $viewFile)
Is called after the view has been rendered but before layout rendering has started.
Helper::beforeLayout(Event $event, $layoutFile)
Is called before layout rendering starts. Receives the layout filename as an argument.
Helper::afterLayout(Event $event, $layoutFile)
Is called after layout rendering is complete. Receives the layout filename as an argument.
355
356
CHAPTER 12
In CakePHP working with data through the database is done with two primary object types. The first are
repositories or table objects. These objects provide access to collections of data. They allow you to save
new records, modify/delete existing ones, define relations, and perform bulk operations. The second type of
objects are entities. Entities represent individual records and allow you to define row/record level behavior
& functionality.
These two classes are usually responsible for managing almost everything that happens regarding your data,
its validity, interactions and evolution of the information workflow in your domain of work.
CakePHPs built-in ORM specializes in relational databases, but can be extended to support alternative
datasources.
The CakePHP ORM borrows ideas and concepts from both ActiveRecord and Datamapper patterns. It aims
to create a hybrid implementation that combines aspects of both patterns to create a fast, simple to use ORM.
Before we get started exploring the ORM, make sure you configure your database connections.
Note: If you are familiar with previous versions of CakePHP, you should read the New ORM Upgrade
Guide for important differences between CakePHP 3.0 and older versions of CakePHP.
Quick Example
To get started you dont have to write any code. If youve followed the CakePHP conventions for your
database tables you can just start using the ORM. For example if we wanted to load some data from our
articles table we could do:
use Cake\ORM\TableRegistry;
$articles = TableRegistry::get('Articles');
$query = $articles->find();
357
Note that we didnt have to create any code or wire any configuration up. The conventions in CakePHP
allow us to skip some boilerplate code and allow the framework to insert base classes when your application
has not created a concrete class. If we wanted to customize our ArticlesTable class adding some associations
or defining some additional methods we would add the following to src/Model/Table/ArticlesTable.php
after the <?php opening tag:
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table
{
}
Table classes use the CamelCased version of the table name with the Table suffix as the class name. Once
your class has been created you get a reference to it using the ORM\TableRegistry as before:
use Cake\ORM\TableRegistry;
// Now $articles is an instance of our ArticlesTable class.
$articles = TableRegistry::get('Articles');
Now that we have a concrete table class, well probably want to use a concrete entity class. Entity classes let
you define accessor and mutator methods, define custom logic for individual records and much more. Well
start off by adding the following to src/Model/Entity/Article.php after the <?php opening tag:
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Article extends Entity
{
}
Entities use the singular CamelCase version of the table name as their class name by default. Now that we
have created our entity class, when we load entities from the database well get instances of our new Article
class:
use Cake\ORM\TableRegistry;
// Now an instance of ArticlesTable.
$articles = TableRegistry::get('Articles');
$query = $articles->find();
foreach ($query as $row) {
358
CakePHP uses naming conventions to link the Table and Entity class together. If you need to customize
which entity a table uses you can use the entityClass() method to set a specific classname.
See the chapters on Table Objects and Entities for more information on how to use table objects and entities
in your application.
More Information
Database Basics
The CakePHP database access layer abstracts and provides help with most aspects of dealing with relational
databases such as, keeping connections to the server, building queries, preventing SQL injections, inspecting
and altering schemas, and with debugging and profiling queries sent to the database.
Quick Tour
The functions described in this chapter illustrate what is possible to do with the lower-level database access
API. If instead you want to learn more about the complete ORM, you can read the Query Builder and Table
Objects sections.
The easiest way to create a database connection is using a DSN string:
use Cake\Datasource\ConnectionManager;
$dsn = 'mysql://root:password@localhost/my_database';
ConnectionManager::config('default', ['url' => $dsn]);
Once created, you can access the connection object to start using it:
$connection = ConnectionManager::get('default');
Supported Databases
CakePHP supports the following relational database servers:
MySQL 5.1+
SQLite 3
PostgreSQL 8+
SQLServer 2008+
Oracle (through a community plugin)
More Information
359
You will need the correct PDO extension installed for each of the above database drivers. Procedural APIs
are not supported.
The Oracle database is supported through the Driver for Oracle Database113 community plugin.
Running Select Statements
Running raw SQL queries is a breeze:
use Cake\Datasource\ConnectionManager;
$connection = ConnectionManager::get('default');
$results = $connection->execute('SELECT * FROM articles')->fetchAll('assoc');
Instead of writing the SQL manually, you can use the query builder:
$results = $connection
->newQuery()
->select('*')
->from('articles')
->where(['created >' => new DateTime('1 day ago'), ['created' => 'datetime
']])
->order(['title' => 'DESC'])
->execute()
->fetchAll('assoc');
113
360
https://github.com/CakeDC/cakephp-oracle-driver
$connection = ConnectionManager::get('default');
$connection->insert('articles', [
'title' => 'A New Article',
'created' => new DateTime('now')
], ['created' => 'datetime']);
Configuration
By convention database connections are configured in config/app.php. The connection information defined
in this file is fed into Cake\Datasource\ConnectionManager creating the connection configuration your application will be using. Sample connection information can be found in config/app.default.php.
A sample connection configuration would look like:
'Datasources' => [
'default' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Mysql',
'persistent' => false,
'host' => 'localhost',
'username' => 'my_app',
'password' => 'sekret',
'database' => 'my_app',
'encoding' => 'utf8',
'timezone' => 'UTC',
'cacheMetadata' => true,
]
],
More Information
361
The above will create a default connection, with the provided parameters. You can define as many connections as you want in your configuration file. You can also define additional connections at runtime using
Cake\Datasource\ConnectionManager::config(). An example of that would be:
use Cake\Datasource\ConnectionManager;
ConnectionManager::config('default', [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Mysql',
'persistent' => false,
'host' => 'localhost',
'username' => 'my_app',
'password' => 'sekret',
'database' => 'my_app',
'encoding' => 'utf8',
'timezone' => 'UTC',
'cacheMetadata' => true,
]);
Configuration options can also be provided as a DSN string. This is useful when working with environment
variables or PaaS providers:
ConnectionManager::config('default', [
'url' => 'mysql://my_app:sekret@localhost/my_app?encoding=utf8&
timezone=UTC&cacheMetadata=true',
]);
When using a DSN string you can define any additional parameters/options as query string arguments.
By default, all Table objects will use the default connection. To use a non-default connection, see
Configuring Connections.
There are a number of keys supported in database configuration. A full list is as follows:
className The fully namespaced class name of the class that represents the connection to a database server.
This class is responsible for loading the database driver, providing SQL transaction mechanisms and
preparing SQL statements among other things.
driver The class name of the driver used to implements all specificities for a database engine. This can
either be a short classname using plugin syntax, a fully namespaced name, or a constructed driver
instance. Examples of short classnames are Mysql, Sqlite, Postgres, and Sqlserver.
persistent Whether or not to use a persistent connection to the database.
host The database servers hostname (or IP address).
username The username for the account.
password The password for the account.
database The name of the database for this connection to use. Avoid using . in your database name.
Because of how it complicates identifier quoting CakePHP does not support . in database names.
port (optional) The TCP port or Unix socket used to connect to the server.
362
encoding Indicates the character set to use when sending SQL statements to the server. This defaults to the
databases default encoding for all databases other than DB2. If you wish to use UTF-8 encoding with
MySQL connections you must use utf8 without the hyphen.
timezone Server timezone to set.
schema Used in PostgreSQL database setups to specify which schema to use.
unix_socket Used by drivers that support it to connect via Unix socket files. If you are using PostgreSQL
and want to use Unix sockets, leave the host key blank.
ssl_key The file path to the SSL key file. (Only supported by MySQL).
ssl_cert The file path to the SSL certificate file. (Only supported by MySQL).
ssl_ca The file path to the SSL certificate authority. (Only supported by MySQL).
init A list of queries that should be sent to the database server as when the connection is created.
log Set to true to enable query logging. When enabled queries will be logged at a debug level with the
queriesLog scope.
quoteIdentifiers Set to true if you are using reserved words or special characters in your table or column
names. Enabling this setting will result in queries built using the Query Builder having identifiers
quoted when creating SQL. It should be noted that this decreases performance because each query
needs to be traversed and manipulated before being executed.
flags An associative array of PDO constants that should be passed to the underlying PDO instance. See the
PDO documentation for the flags supported by the driver you are using.
cacheMetadata Either boolean true, or a string containing the cache configuration to store meta data in.
Having metadata caching disable is not advised and can result in very poor performance. See the
Metadata Caching section for more information.
At this point, you might want to take a look at the CakePHP Conventions. The correct naming for your tables
(and the addition of some columns) can score you some free functionality and help you avoid configuration.
For example, if you name your database table big_boxes, your table BigBoxesTable, and your controller
BigBoxesController, everything will work together automatically. By convention, use underscores, lower
case, and plural forms for your database table names - for example: bakers, pastry_stores, and savory_cakes.
Managing Connections
class Cake\Datasource\ConnectionManager
The ConnectionManager class acts as a registry to access database connections your application has. It
provides a place that other objects can get references to existing connections.
Accessing Connections
static Cake\Datasource\ConnectionManager::get($name)
More Information
363
See the Configuration for more information on the configuration data used when creating connections.
Data Types
class Cake\Database\Type
Since not every database vendor includes the same set of data types, or the same names for similar data
types, CakePHP provides a set of abstracted data types for use with the database layer. The types CakePHP
supports are:
string Generally backed by CHAR or VARCHAR columns. Using the fixed option will force a CHAR
column. In SQL Server, NCHAR and NVARCHAR types are used.
text Maps to TEXT types.
uuid Maps to the UUID type if a database provides one, otherwise this will generate a CHAR(36) field.
integer Maps to the INTEGER type provided by the database. BIT is not yet supported at this moment.
biginteger Maps to the BIGINT type provided by the database.
float Maps to either DOUBLE or FLOAT depending on the database. The precision option can be used
to define the precision used.
decimal Maps to the DECIMAL type. Supports the length and precision options.
boolean Maps to BOOLEAN except in MySQL, where TINYINT(1) is used to represent booleans. BIT(1)
is not yet supported at this moment.
binary Maps to the BLOB or BYTEA type provided by the database.
date Maps to a timezone naive DATE column type. The return value of this column type is
Cake\I18n\Date which extends the native DateTime class.
364
datetime Maps to a timezone naive DATETIME column type. In PostgreSQL, and SQL Server this turns
into a TIMESTAMP type. The default return value of this column type is Cake\I18n\Time which
extends the built-in DateTime class and Chronos114 .
timestamp Maps to the TIMESTAMP type.
time Maps to a TIME type in all databases.
json Maps to a JSON type if its available, otherwise it maps to TEXT. The json type was added in 3.3.0
These types are used in both the schema reflection features that CakePHP provides, and schema generation
features CakePHP uses when using test fixtures.
Each type can also provide translation functions between PHP and SQL representations. These methods
are invoked based on the type hints provided when doing queries. For example a column that is marked
as datetime will automatically convert input parameters from DateTime instances into a timestamp or
formatted datestrings. Likewise, binary columns will accept file handles, and generate file handles when
reading data.
Adding Custom Types
static Cake\Database\Type::map($name, $class)
If you need to use vendor specific types that are not built into CakePHP you can add additional new types to
CakePHPs type system. Type classes are expected to implement the following methods:
toPHP
toDatabase
toStatement
marshal
An easy way to fulfill the basic interface is to extend Cake\Database\Type. For example if we wanted
to add a JSON type, we could make the following type class:
// in src/Database/Type/JsonType.php
namespace App\Database\Type;
use Cake\Database\Driver;
use Cake\Database\Type;
use PDO;
class JsonType extends Type
{
public function toPHP($value, Driver $driver)
{
if ($value === null) {
return null;
114
https://github.com/cakephp/chronos
More Information
365
}
return json_decode($value, true);
}
public function marshal($value)
{
if (is_array($value) || $value === null) {
return $value;
}
return json_decode($value, true);
}
public function toDatabase($value, Driver $driver)
{
return json_encode($value);
}
public function toStatement($value, Driver $driver)
{
if ($value === null) {
return PDO::PARAM_NULL;
}
return PDO::PARAM_STR;
}
}
By default the toStatement() method will treat values as strings which will work for our new type.
Once weve created our new type, we need to add it into the type mapping. During our application bootstrap
we should do the following:
use Cake\Database\Type;
Type::map('json', 'App\Database\Type\JsonType');
We can then overload the reflected schema data to use our new type, and CakePHPs database layer will
automatically convert our JSON data when creating queries. You can use the custom types youve created
by mapping the types in your Tables _initializeSchema() method:
use Cake\Database\Schema\Table as Schema;
class WidgetsTable extends Table
{
protected function _initializeSchema(Schema $schema)
{
$schema->columnType('widget_prefs', 'json');
return $schema;
}
}
366
With our value object created, well need a Type class to map data into this value object and into SQL
expressions:
namespace App\Database\Type;
use App\Database\Point;
use Cake\Database\Expression\FunctionExpression;
More Information
367
368
Because Date/Time objects are easily mutated in place, CakePHP allows you to enable immutable value
objects. This is best done in your applications config/bootstrap.php file:
Type::build('datetime')->useImmutable();
Type::build('date')->useImmutable();
Type::build('time')->useImmutable();
Connection Classes
class Cake\Database\Connection
Connection classes provide a simple interface to interact with database connections in a consistent way.
They are intended as a more abstract interface to the driver layer and provide features for executing queries,
logging queries, and doing transactional operations.
Executing Queries
Cake\Database\Connection::query($sql)
Once youve gotten a connection object, youll probably want to issue some queries with it. CakePHPs
database abstraction layer provides wrapper features on top of PDO and native drivers. These wrappers
provide a similar interface to PDO. There are a few different ways you can run queries depending on the
type of query you need to run and what kind of results you need back. The most basic method is query()
which allows you to run already completed SQL queries:
$stmt = $conn->query('UPDATE articles SET published = 1 WHERE id = 2');
Without any type hinting information, execute will assume all placeholders are string values. If you need
to bind specific types of data, you can use their abstract type names when creating a query:
$stmt = $conn->execute(
'UPDATE articles SET published_date = ? WHERE id = ?',
[new DateTime('now'), 2],
['date', 'integer']
);
More Information
369
Cake\Database\Connection::newQuery()
This allows you to use rich data types in your applications and properly convert them into SQL statements.
The last and most flexible way of creating queries is to use the Query Builder. This approach allows you to
build complex and expressive queries without having to use platform specific SQL:
$query = $conn->newQuery();
$query->update('articles')
->set(['published' => true])
->where(['id' => 2]);
$stmt = $query->execute();
When using the query builder, no SQL will be sent to the database server until the execute() method is
called, or the query is iterated. Iterating a query will first execute it and then start iterating over the result
set:
$query = $conn->newQuery();
$query->select('*')
->from('articles')
->where(['published' => true]);
foreach ($query as $row) {
// Do something with the row.
}
Note: When you have an instance of Cake\ORM\Query you can use all() to get the result set for
SELECT queries.
Using Transactions
The connection objects provide you a few simple ways you do database transactions. The most basic way of
doing transactions is through the begin(), commit() and rollback() methods, which map to their
SQL equivalents:
$conn->begin();
$conn->execute('UPDATE articles SET published = ? WHERE id = ?', [true, 2]);
$conn->execute('UPDATE articles SET published = ? WHERE id = ?', [false, 4]);
$conn->commit();
Cake\Database\Connection::transactional(callable $callback)
In addition to this interface connection instances also provide the transactional() method which
makes handling the begin/commit/rollback calls much simpler:
$conn->transactional(function ($conn) {
$conn->execute('UPDATE articles SET published = ? WHERE id = ?', [true,
2]);
$conn->execute('UPDATE articles SET published = ? WHERE id = ?', [false,
4]);
370
});
In addition to basic queries, you can execute more complex queries using either the Query Builder or Table
Objects. The transactional method will do the following:
Call begin.
Call the provided closure.
If the closure raises an exception, a rollback will be issued. The original exception will be re-thrown.
If the closure returns false, a rollback will be issued.
If the closure executes successfully, the transaction will be committed.
Interacting with Statements
When using the lower level database API, you will often encounter statement objects. These objects allow
you to manipulate the underlying prepared statement from the driver. After creating and executing a query
object, or using execute() you will have a StatementDecorator instance. It wraps the underlying
basic statement object and provides a few additional features.
Preparing a Statement
You can create a statement object using execute(), or prepare(). The execute() method returns
a statement with the provided values bound to it. While prepare() returns an incomplete statement:
// Statements from execute will have values bound to them already.
$stmt = $conn->execute(
'SELECT * FROM articles WHERE published = ?',
[true]
);
// Statements from prepare will be parameters for placeholders.
// You need to bind parameters before attempting to execute it.
$stmt = $conn->prepare('SELECT * FROM articles WHERE published = ?');
Once youve prepared a statement you can bind additional data and execute it.
Binding Values
Once youve created a prepared statement, you may need to bind additional data. You can bind multiple
values at once using the bind() method, or bind individual elements using bindValue:
$stmt = $conn->prepare(
'SELECT * FROM articles WHERE published = ? AND created > ?'
);
// Bind multiple values
More Information
371
$stmt->bind(
[true, new DateTime('2013-01-01')],
['boolean', 'date']
);
// Bind a single value
$stmt->bindValue(1, true, 'boolean');
$stmt->bindValue(2, new DateTime('2013-01-01'), 'date');
When creating statements you can also use named array keys instead of positional ones:
$stmt = $conn->prepare(
'SELECT * FROM articles WHERE published = :published AND created > :
created'
);
// Bind multiple values
$stmt->bind(
['published' => true, 'created' => new DateTime('2013-01-01')],
['published' => 'boolean', 'created' => 'date']
);
// Bind a single value
$stmt->bindValue('published', true, 'boolean');
$stmt->bindValue('created', new DateTime('2013-01-01'), 'date');
Warning: You cannot mix positional and named array keys in the same statement.
372
Note: Reading rows through iteration will fetch rows in both mode. This means you will get both the
numerically indexed and associatively indexed results.
Query Logging
Query logging can be enabled when configuring your connection by setting the log option to true. You
can also toggle query logging at runtime, using logQueries:
// Turn query logging on.
$conn->logQueries(true);
// Turn query logging off
$conn->logQueries(false);
When query logging is enabled, queries will be logged to Cake\Log\Log using the debug level, and the
queriesLog scope. You will need to have a logger configured to capture this level & scope. Logging to
stderr can be useful when working on unit tests, and logging to files/syslog can be useful when working
with web requests:
use Cake\Log\Log;
// Console logging
Log::config('queries', [
'className' => 'Console',
'stream' => 'php://stderr',
'scopes' => ['queriesLog']
]);
// File logging
Log::config('queries', [
More Information
373
Note: Query logging is only intended for debugging/development uses. You should never leave query
logging on in production as it will negatively impact the performance of your application.
Identifier Quoting
By default CakePHP does not quote identifiers in generated SQL queries. The reason for this is identifier
quoting has a few drawbacks:
Performance overhead - Quoting identifiers is much slower and complex than not doing it.
Not necessary in most cases - In non-legacy databases that follow CakePHPs conventions there is no
reason to quote identifiers.
If you are using a legacy schema that requires identifier quoting you can enable it using the
quoteIdentifiers setting in your Configuration. You can also enable this feature at runtime:
$conn->driver()->autoQuoting(true);
When enabled, identifier quoting will cause additional query traversal that converts all identifiers into
IdentifierExpression objects.
Note: SQL snippets contained in QueryExpression objects will not be modified.
Metadata Caching
CakePHPs ORM uses database reflection to determine the schema, indexes and foreign keys your application contains. Because this metadata changes infrequently and can be expensive to access, it is typically
cached. By default, metadata is stored in the _cake_model_ cache configuration. You can define a
custom cache configuration using the cacheMetatdata option in your datasource configuration:
'Datasources' => [
'default' => [
// Other keys go here.
// Use the 'orm_metadata' cache config for metadata.
'cacheMetadata' => 'orm_metadata',
]
],
You can also configure the metadata caching at runtime with the cacheMetadata() method:
374
CakePHP also includes a CLI tool for managing metadata caches. See the ORM Cache Shell chapter for
more information.
Creating Databases
If you want to create a connection without selecting a database you can omit the database name:
$dsn = 'mysql://root:password@localhost/';
You can now use your connection object to execute queries that create/modify databases. For example to
create a database:
$connection->query("CREATE DATABASE IF NOT EXISTS my_database");
Note: When creating a database it is a good idea to set the character set and collation parameters. If these
values are missing, the database will set whatever system default values it uses.
Query Builder
class Cake\ORM\Query
The ORMs query builder provides a simple to use fluent interface for creating and running queries. By
composing queries together, you can create advanced queries using unions and subqueries with ease.
Underneath the covers, the query builder uses PDO prepared statements which protect against SQL injection
attacks.
The Query Object
The easiest way to create a Query object is to use find() from a Table object. This method will return
an incomplete query ready to be modified. You can also use a tables connection object to access the lower
level Query builder that does not include ORM features, if necessary. See the Executing Queries section for
more information:
use Cake\ORM\TableRegistry;
$articles = TableRegistry::get('Articles');
More Information
375
When inside a controller, you can use the automatic table variable that is created using the conventions
system:
// Inside ArticlesController.php
$query = $this->Articles->find();
For the remaining examples, assume that $articles is a ORM\Table. When inside controllers, you can
use $this->Articles instead of $articles.
Almost every method in a Query object will return the same query, this means that Query objects are lazy,
and will not be executed unless you tell them to:
$query->where(['id' => 1]); // Return the same query object
$query->order(['title' => 'DESC']); // Still same object, no SQL executed
You can of course chain the methods you call on Query objects:
$query = $articles
->find()
->select(['id', 'name'])
->where(['id !=' => 1])
->order(['created' => 'DESC']);
foreach ($query as $article) {
debug($article->created);
}
If you try to call debug() on a Query object, you will see its internal state and the SQL that will be
executed in the database:
debug($articles->find()->where(['id' => 1]));
//
//
//
//
Outputs
...
'sql' => 'SELECT * FROM articles where id = ?'
...
376
You can execute a query directly without having to use foreach on it. The easiest way is to either call the
all() or toArray() methods:
$resultsIteratorObject = $articles
->find()
->where(['id >' => 1])
->all();
foreach ($resultsIteratorObject as $article) {
debug($article->id);
}
$resultsArray = $articles
->find()
->where(['id >' => 1])
->toArray();
foreach ($resultsArray as $article) {
debug($article->id);
}
debug($resultsArray[0]->title);
More Information
377
echo $title;
}
For more information on how to customize the fields used for populating the list refer to Finding Key/Value
Pairs section.
Queries Are Collection Objects
Once you get familiar with the Query object methods, it is strongly encouraged that you visit the Collection
section to improve your skills in efficiently traversing the data. In short, it is important to remember that
anything you can call on a Collection object, you can also do in a Query object:
// Use the combine() method from the collections library
// This is equivalent to find('list')
$keyValueList = $articles->find()->combine('id', 'title');
// An advanced example
$results = $articles->find()
->where(['id >' => 1])
->order(['title' => 'DESC'])
->map(function ($row) { // map() is a collection method, it executes the
query
$row->trimmedTitle = trim($row->title);
return $row;
})
->combine('id', 'trimmedTitle') // combine() is another collection method
->toArray(); // Also a collections library method
foreach ($results as $id => $trimmedTitle) {
echo "$id : $trimmedTitle";
}
378
The querys first() method is called. This will return the first result in the set built by SELECT (it
adds LIMIT 1 to the query).
The querys all() method is called. This will return the result set and can only be used with SELECT
statements.
The querys toArray() method is called.
Until one of these conditions are met, the query can be modified without additional SQL being sent to the
database. It also means that if a Query hasnt been evaluated, no SQL is ever sent to the database. Once
executed, modifying and re-evaluating a query will result in additional SQL being run.
If you want to take a look at what SQL CakePHP is generating, you can turn database query logging on.
The following sections will show you everything there is to know about using and combining the Query
object methods to construct SQL statements and extract data.
Selecting Data
Most web applications make heavy use of SELECT queries. CakePHP makes building them a snap. To limit
the fields fetched, you can use the select() method:
$query = $articles->find();
$query->select(['id', 'title', 'body']);
foreach ($query as $row) {
debug($row->title);
}
You can set aliases for fields by providing fields as an associative array:
// Results in SELECT id AS pk, title AS aliased_title, body ...
$query = $articles->find();
$query->select(['pk' => 'id', 'aliased_title' => 'title', 'body']);
To set some basic conditions you can use the where() method:
// Conditions are combined with AND
$query = $articles->find();
$query->where(['title' => 'First Post', 'published' => true]);
// You can call where() multiple times
$query = $articles->find();
$query->where(['title' => 'First Post'])
->where(['published' => true]);
More Information
379
See the Advanced Conditions section to find out how to construct more complex WHERE conditions. To
apply ordering, you can use the order method:
$query = $articles->find()
->order(['title' => 'ASC', 'id' => 'ASC']);
New in version 3.0.12: In addition to order, the orderAsc and orderDesc methods can be used when
you need to sort on complex expressions:
$query = $articles->find();
$concat = $query->func()->concat([
'title' => 'identifier',
'synopsis' => 'identifier'
]);
$query->orderAsc($concat);
To limit the number of rows or set the row offset you can use the limit() and page() methods:
// Fetch rows 50 to 100
$query = $articles->find()
->limit(50)
->page(2);
As you can see from the examples above, all the methods that modify the query provide a fluent interface,
allowing you to build a query through chained method calls.
Selecting All Fields From a Table
By default a query will select all fields from a table, the exception is when you call the select() function
yourself and pass certain fields:
// Only select id and title from the articles table
$articles->find()->select(['id', 'title']);
If you wish to still select all fields from a table after having called select($fields), you can pass the
table instance to select() for this purpose:
// Only all fields from the articles table including
// a calculated slug field.
$query = $articlesTable->find();
$query
->select(['slug' => $query->func()->concat(['title', '-', 'id'])])
->select($articlesTable); // Select all fields from articles
New in version 3.1: Passing a table object to select() was added in 3.1.
Using SQL Functions
CakePHPs ORM offers abstraction for some commonly used SQL functions. Using the abstraction allows
the ORM to select the platform specific implementation of the function you want. For example, concat is
380
implemented differently in MySQL, PostgreSQL and SQL Server. Using the abstraction allows your code
to be portable:
// Results in SELECT COUNT(*) count FROM ...
$query = $articles->find();
$query->select(['count' => $query->func()->count('*')]);
A number of commonly used functions can be created with the func() method:
sum() Calculate a sum. The arguments will be treated as literal values.
avg() Calculate an average. The arguments will be treated as literal values.
min() Calculate the min of a column. The arguments will be treated as literal values.
max() Calculate the max of a column. The arguments will be treated as literal values.
count() Calculate the count. The arguments will be treated as literal values.
concat() Concatenate two values together. The arguments are treated as bound parameters unless
marked as literal.
coalesce() Coalesce values. The arguments are treated as bound parameters unless marked as
literal.
dateDiff() Get the difference between two dates/times. The arguments are treated as bound
parameters unless marked as literal.
now() Take either time or date as an argument allowing you to get either the current time, or
current date.
extract() Returns the specified date part from the SQL expression.
dateAdd() Add the time unit to the date expression.
dayOfWeek() Returns a FunctionExpression representing a call to SQL WEEKDAY function.
New in version 3.1: extract(), dateAdd() and dayOfWeek() methods have been added.
When providing arguments for SQL functions, there are two kinds of parameters you can use, literal arguments and bound parameters. Identifier/Literal parameters allow you to reference columns or other SQL
literals. Bound parameters can be used to safely add user data to SQL functions. For example:
$query = $articles->find()->innerJoinWith('Categories');
$concat = $query->func()->concat([
'Articles.title' => 'identifier',
' - CAT: ',
'Categories.name' => 'identifier',
' - Age: ',
'(DATEDIFF(NOW(), Articles.created))' => 'literal',
]);
$query->select(['link_title' => $concat]);
By making arguments with a value of literal, the ORM will know that the key should be treated as a
literal SQL value. By making arguments with a value of identifier, the ORM will know that the key
should be treated as a field identifier. The above would generate the following SQL on MySQL:
More Information
381
The :c0 value will have the ' -CAT:' text bound when the query is executed.
In addition to the above functions, the func() method can be used to create any generic SQL function
such as year, date_format, convert, etc. For example:
$query = $articles->find();
$year = $query->func()->year([
'created' => 'identifier'
]);
$time = $query->func()->date_format([
'created' => 'identifier',
"'%H:%i'" => 'literal'
]);
$query->select([
'yearCreated' => $year,
'timeCreated' => $time
]);
You should remember to use the function builder whenever you need to put untrusted data into SQL functions
or stored procedures:
// Use a stored procedure
$query = $articles->find();
$lev = $query->func()->levenshtein([$search, 'LOWER(title)' => 'literal']);
$query->where(function ($exp) use ($lev) {
return $exp->between($lev, 0, $tolerance);
});
// Generated SQL would be
WHERE levenshtein(:c0, lower(street)) BETWEEN :c1 AND :c2
382
Case statements
The ORM also offers the SQL case expression. The case expression allows for implementing if ...
then ... else logic inside your SQL. This can be useful for reporting on data where you need to
conditionally sum or count data, or where you need to specific data based on a condition.
If we wished to know how many published articles are in our database, wed need to generate the following
SQL:
SELECT
SUM(CASE published = 'Y' THEN 1 ELSE 0) AS number_published,
SUM(CASE published = 'N' THEN 1 ELSE 0) AS number_unpublished
FROM articles GROUP BY published
To do this with the query builder, wed use the following code:
$query = $articles->find();
$publishedCase = $query->newExpr()
->addCase(
$query->newExpr()->add(['published' => 'Y']),
1,
'integer'
);
$notPublishedCase = $query->newExpr()
->addCase(
$query->newExpr()->add(['published' => 'N']),
1,
'integer'
);
$query->select([
'number_published' => $query->func()->sum($publishedCase),
'number_unpublished' => $query->func()->sum($unpublishedCase)
])
->group('published');
The addCase function can also chain together multiple statements to create if ..
[elseif .. then .. ] [ .. else ] logic inside your SQL.
then ..
If we wanted to classify cities into SMALL, MEDIUM, or LARGE based on population size, we could do
the following:
$query = $cities->find()
->where(function ($exp, $q) {
return $exp->addCase(
[
$q->newExpr()->lt('population', 100000),
$q->newExpr()->between('population', 100000, 999000),
$q->newExpr()->gte('population', 999001),
],
More Information
383
#
#
#
#
#
);
});
WHERE CASE
WHEN population < 100000 THEN 'SMALL'
WHEN population BETWEEN 100000 AND 999000 THEN 'MEDIUM'
WHEN population >= 999001 THEN 'LARGE'
END
Any time there are fewer case conditions than values, addCase will automatically produce an if ..
then .. else statement:
$query = $cities->find()
->where(function ($exp, $q) {
return $exp->addCase(
[
$q->newExpr()->eq('population', 0),
],
['DESERTED', 'INHABITED'], # values matching conditions
['string', 'string'] # type of each value
);
});
# WHERE CASE
#
WHEN population = 0 THEN 'DESERTED' ELSE 'INHABITED' END
After executing those lines, your result should look similar to this:
[
['id' => 1, 'title' => 'First Article', 'body' => 'Article 1 body' ...],
['id' => 2, 'title' => 'Second Article', 'body' => 'Article 2 body' ...],
...
]
384
sets. If you need more control over the process, or want to reduce results you should use the Map/Reduce
feature instead. If you were querying a list of people, you could calculate their age with a result formatter:
// Assuming we have built the fields, conditions and containments.
$query->formatResults(function (\Cake\Datasource\ResultSetInterface $results)
{
return $results->map(function ($row) {
$row['age'] = $row['birth_date']->diff(new \DateTime)->y;
return $row;
});
});
As you can see in the example above, formatting callbacks will get a ResultSetDecorator as their first
argument. The second argument will be the Query instance the formatter was attached to. The $results
argument can be traversed and modified as necessary.
Result formatters are required to return an iterator object, which will be used as the return value for the query.
Formatter functions are applied after all the Map/Reduce routines have been executed. Result formatters
can be applied from within contained associations as well. CakePHP will ensure that your formatters are
properly scoped. For example, doing the following would work as you may expect:
// In a method in the Articles table
$query->contain(['Authors' => function ($q) {
return $q->formatResults(function ($authors) {
return $authors->map(function ($author) {
$author['age'] = $author['birth_date']->diff(new \DateTime)->y;
return $author;
});
});
});
// Get results
$results = $query->all();
// Outputs 29
echo $results->first()->author->age;
As seen above, the formatters attached to associated query builders are scoped to operate only on the data in
the association. CakePHP will ensure that computed values are inserted into the correct entity.
Advanced Conditions
The query builder makes it simple to build complex where clauses. Grouped conditions can be expressed
by providing combining where(), andWhere() and orWhere(). The where() method works similar
to the conditions arrays in previous versions of CakePHP:
$query = $articles->find()
->where([
'author_id' => 3,
'OR' => [['view_count' => 2], ['view_count' => 3]],
]);
More Information
385
If youd prefer to avoid deeply nested arrays, you can use the orWhere() and andWhere() methods
to build your queries. Each method sets the combining operator used between the current and previous
condition. For example:
$query = $articles->find()
->where(['author_id' => 2])
->orWhere(['author_id' => 3]);
By combining orWhere() and andWhere(), you can express complex conditions that use a mixture of
operators:
$query = $articles->find()
->where(['author_id' => 2])
->orWhere(['author_id' => 3])
->andWhere([
'published' => true,
'view_count >' => 10
])
->orWhere(['promoted' => true]);
By using functions as the parameters to orWhere() and andWhere(), you can compose conditions
together with the expression objects:
$query = $articles->find()
->where(['title LIKE' => '%First%'])
->andWhere(function ($exp) {
return $exp->or_([
'author_id' => 2,
'is_highlighted' => true
]);
});
386
SELECT *
FROM articles
WHERE ((author_id = 2 OR is_highlighted = 1)
AND title LIKE '%First%')
The expression object that is passed into where() functions has two kinds of methods. The first type of
methods are combinators. The and_() and or_() methods create new expression objects that change
how conditions are combined. The second type of methods are conditions. Conditions are added into an
expression where they are combined with the current combinator.
For example, calling $exp->and_(...) will create a new Expression object that combines all conditions it contains with AND. While $exp->or_() will create a new Expression object that combines
all conditions added to it with OR. An example of adding conditions with an Expression object would
be:
$query = $articles->find()
->where(function ($exp) {
return $exp
->eq('author_id', 2)
->eq('published', true)
->notEq('spam', true)
->gt('view_count', 10);
});
Since we started off using where(), we dont need to call and_(), as that happens implicitly. Much like
how we would not need to call or_(), had we started our query with orWhere(). The above shows a
few new condition methods being combined with AND. The resulting SQL would look like:
SELECT *
FROM articles
WHERE (
author_id = 2
AND published = 1
AND spam != 1
AND view_count > 10)
However, if we wanted to use both AND & OR conditions we could do the following:
$query = $articles->find()
->where(function ($exp) {
$orConditions = $exp->or_(['author_id' => 2])
->eq('author_id', 5);
return $exp
->add($orConditions)
->eq('published', true)
->gte('view_count', 10);
});
More Information
387
WHERE (
(author_id = 2 OR author_id = 5)
AND published = 1
AND view_count >= 10)
The or_() and and_() methods also allow you to use functions as their parameters. This is often easier
to read than method chaining:
$query = $articles->find()
->where(function ($exp) {
$orConditions = $exp->or_(function ($or) {
return $or->eq('author_id', 2)
->eq('author_id', 5);
});
return $exp
->not($orConditions)
->lte('view_count', 10);
});
388
SELECT *
FROM articles
WHERE (
YEAR(created) >= 2014
AND published = 1
)
When using the expression objects you can use the following methods to create conditions:
eq() Creates an equality condition:
$query = $cities->find()
->where(function ($exp, $q) {
return $exp->eq('population', '10000');
});
# WHERE population = 10000
More Information
389
$query = $cities->find()
->where(function ($exp, $q) {
return $exp->notIn('country_id', ['AFG', 'USA', 'EST']);
});
# WHERE country_id NOT IN ('AFG', 'USA', 'EST')
390
In situations when you cant get, or dont want to use the builder methods to create the conditions you want
you can also use snippets of SQL in where clauses:
// Compare two fields to each other
$query->where(['Categories.parent_id != Parents.id']);
Warning: The field names used in expressions, and SQL snippets should never contain untrusted
content. See the Using SQL Functions section for how to safely include unsafe data into function calls.
More Information
391
The above will automatically create id IN (...) instead of id = ?. This can be useful when you
do not know whether you will get a scalar or array of parameters. The [] suffix on any data type name
indicates to the query builder that you want the data handled as an array. If the data is not an array, it will
first be cast to an array. After that, each value in the array will be cast using the type system. This works
with complex types as well. For example, you could take a list of DateTime objects using:
$query = $articles->find()
->where(['post_date' => $dates], ['post_date' => 'date[]']);
The above will create parent_id` = :c1 or parent_id IS NULL depending on the type of
$parentId
Automatic IS NOT NULL Creation
When a condition value is expected not to be null or any other value, you can use the IS NOT operator
to automatically create the correct expression:
$query = $categories->find()
->where(['parent_id IS NOT' => $parentId]);
The above will create parent_id` != :c1 or parent_id IS NOT NULL depending on the type of
$parentId
392
Raw Expressions
When you cannot construct the SQL you need using the query builder, you can use expression objects to
add snippets of SQL to your queries:
$query = $articles->find();
$expr = $query->newExpr()->add('1 + 1');
$query->select(['two' => $expr]);
Expression objects can be used with any query builder methods like where(), limit(), group(),
select() and many other methods.
Warning: Using expression objects leaves you vulnerable to SQL injection. You should avoid interpolating user data into expressions.
Getting Results
Once youve made your query, youll want to retrieve rows from it. There are a few ways of doing this:
// Iterate the query
foreach ($query as $row) {
// Do stuff.
}
// Get the results
$results = $query->all();
You can use any of the collection methods on your query objects to pre-process or transform the results:
// Use one of the collection methods.
$ids = $query->map(function ($row) {
return $row->id;
});
$maxAge = $query->max(function ($row) {
return $max->age;
});
You can use first or firstOrFail to retrieve a single record. These methods will alter the query
adding a LIMIT 1 clause:
// Get just the first row
$row = $query->first();
// Get the first row or an exception.
$row = $query->firstOrFail();
More Information
393
The count() method will ignore the limit, offset and page clauses, thus the following will return
the same result:
$total = $articles->find()->where(['is_active' => true])->limit(10)->count();
This is useful when you need to know the total result set size in advance, without having to construct
another Query object. Likewise, all result formatting and map-reduce routines are ignored when using the
count() method.
Moreover, it is possible to return the total count for a query containing group by clauses without having to
rewrite the query in any way. For example, consider this query for retrieving article ids and their comments
count:
$query = $articles->find();
$query->select(['Articles.id', $query->func()->count('Comments.id')])
->matching('Comments')
->group(['Articles.id']);
$total = $query->count();
After counting, the query can still be used for fetching the associated records:
$list = $query->all();
Sometimes, you may want to provide an alternate method for counting the total records of a query. One
common use case for this is providing a cached value or an estimate of the total rows, or to alter the query
to remove expensive unneeded parts such as left joins. This becomes particularly handy when using the
CakePHP built-in pagination system which calls the count() method:
$query = $query->where(['is_active' => true])->counter(function ($query) {
return 100000;
});
$query->count(); // Returns 100000
In the example above, when the pagination component calls the count method, it will receive the estimated
hard-coded number of rows.
Caching Loaded Results
When fetching entities that dont change often you may want to cache the results. The Query class makes
this simple:
$query->cache('recent_articles');
394
Will enable caching on the querys result set. If only one argument is provided to cache() then the
default cache configuration will be used. You can control which caching configuration is used with the
second parameter:
// String config name.
$query->cache('recent_articles', 'dbResults');
// Instance of CacheEngine
$query->cache('recent_articles', $memcache);
In addition to supporting static keys, the cache() method accepts a function to generate the key. The
function you give it will receive the query as an argument. You can then read aspects of the query to
dynamically generate the cache key:
// Generate a key based on a simple checksum
// of the query's where clause
$query->cache(function ($q) {
return 'articles-' . md5(serialize($q->clause('where')));
});
The cache method makes it simple to add cached results to your custom finders or through event listeners.
When the results for a cached query are fetched the following happens:
1. The Model.beforeFind event is triggered.
2. If the query has results set, those will be returned.
3. The cache key will be resolved and cache data will be read. If the cache data is not empty, those
results will be returned.
4. If the cache misses, the query will be executed and a new ResultSet will be created. This
ResultSet will be written to the cache and returned.
Note: You cannot cache a streaming query result.
Loading Associations
The builder can help you retrieve data from multiple tables at the same time with the minimum amount of
queries possible. To be able to fetch associated data, you first need to setup associations between the tables
as described in the Associations - Linking Tables Together section. This technique of combining queries to
fetch associated data from other tables is called eager loading.
Eager loading helps avoid many of the potential performance problems surrounding lazy-loading in an
ORM. The queries generated by eager loading can better leverage joins, allowing more efficient queries to
be made. In CakePHP you define eager loaded associations using the contain method:
// In a controller or table method.
// As an option to find()
$query = $articles->find('all', ['contain' => ['Authors', 'Comments']]);
More Information
395
The above will load the related author and comments for each article in the result set. You can load nested
associations using nested arrays to define the associations to be loaded:
$query = $articles->find()->contain([
'Authors' => ['Addresses'], 'Comments' => ['Authors']
]);
Alternatively, you can express nested associations using the dot notation:
$query = $articles->find()->contain([
'Authors.Addresses',
'Comments.Authors'
]);
If you need to reset the containments on a query you can set the second argument to true:
$query = $articles->find();
$query->contain(['Authors', 'Comments'], true);
396
Note: When you limit the fields that are fetched from an association, you must ensure that the foreign key
columns are selected. Failing to select foreign key fields will cause associated data to not be present in the
final result.
It is also possible to restrict deeply-nested associations using the dot notation:
$query = $articles->find()->contain([
'Comments',
'Authors.Profiles' => function ($q) {
return $q->where(['Profiles.is_published' => true]);
}
]);
If you have defined some custom finder methods in your associated table, you can use them inside
contain():
// Bring all articles, but only bring the comments that are approved and
// popular.
$query = $articles->find()->contain([
'Comments' => function ($q) {
return $q->find('approved')->find('popular');
}
]);
Note: For BelongsTo and HasOne associations only the where and select clauses are used when
loading the associated records. For the rest of the association types you can use every clause that the query
object provides.
If you need full control over the query that is generated, you can tell contain() to not append
the foreignKey constraints to the generated query. In that case you should use an array passing
foreignKey and queryBuilder:
$query = $articles->find()->contain([
'Authors' => [
'foreignKey' => false,
'queryBuilder' => function ($q) {
return $q->where(...); // Full conditions for filtering
}
]
]);
If you have limited the fields you are loading with select() but also want to load fields off of contained
associations, you can pass the association object to select():
More Information
397
// Select id & title from articles, but all fields off of Users.
$query = $articles->find()
->select(['id', 'title'])
->select($articlesTable->Users)
->contain(['Users']);
New in version 3.1: Selecting columns via an association object was added in 3.1
Sorting Contained Associations
When loading HasMany and BelongsToMany associations, you can use the sort option to sort the data in
those associations:
$query->contain([
'Comments' => [
'sort' => ['Comment.created' => 'DESC']
]
]);
You can apply this strategy to HasMany associations as well. For example if Authors HasMany Articles,
you could find all the authors with recently published articles using the following:
$query = $authors->find();
$query->matching('Articles', function ($q) {
398
Filtering by deep associations is surprisingly easy, and the syntax should be already familiar to you:
// In a controller or table method.
$query = $products->find()->matching(
'Shops.Cities.Countries', function ($q) {
return $q->where(['Countries.name' => 'Japan']);
}
);
// Bring unique articles that were commented by 'markstory' using passed
variable
// Dotted matching paths should be used over nested matching() calls
$username = 'markstory';
$query = $articles->find()->matching('Comments.Users', function ($q) use (
$username) {
return $q->where(['username' => $username]);
});
Note: As this function will create an INNER JOIN, you might want to consider calling distinct on
the find query as you might get duplicate rows if your conditions dont exclude them already. This might be
the case, for example, when the same users comments more than once on a single article.
The data from the association that is matched will be available on the _matchingData property of entities. If you both match and contain the same association, you can expect to get both the _matchingData
and standard association properties in your results.
Using innerJoinWith
Using the matching() function, as we saw already, will create an INNER JOIN with the specified
association and will also load the fields into the result set.
There may be cases where you want to use matching() but are not interested in loading the fields into
the result set. For this purpose, you can use innerJoinWith():
$query = $articles->find();
$query->innerJoinWith('Tags', function ($q) {
return $q->where(['Tags.name' => 'CakePHP']);
});
The innerJoinWith() method works the same as matching(), that means that you can use dot
notation to join deeply nested associations:
$query = $products->find()->innerJoinWith(
'Shops.Cities.Countries', function ($q) {
return $q->where(['Countries.name' => 'Japan']);
More Information
399
}
);
Again, the only difference is that no additional columns will be added to the result set, and no
_matchingData property will be set.
New in version 3.1: Query::innerJoinWith() was added in 3.1
Using notMatching
The opposite of matching() is notMatching(). This function will change the query so that it filters
results that have no relation to the specified association:
// In a controller or table method.
$query = $articlesTable
->find()
->notMatching('Tags', function ($q) {
return $q->where(['Tags.name' => 'boring']);
});
The above example will find all articles that were not tagged with the word boring. You can apply this
method to HasMany associations as well. You could, for example, find all the authors with no published
articles in the last 10 days:
$query = $authorsTable
->find()
->notMatching('Articles', function ($q) {
return $q->where(['Articles.created >=' => new \DateTime('-10 days
')]);
});
It is also possible to use this method for filtering out records not matching deep associations. For example,
you could find articles that have not been commented on by a certain user:
$query = $articlesTable
->find()
->notMatching('Comments.Users', function ($q) {
return $q->where(['username' => 'jose']);
});
Since articles with no comments at all also satisfy the condition above, you may want to combine
matching() and notMatching() in the same query. The following example will find articles having at least one comment, but not commented by a certain user:
$query = $articlesTable
->find()
->notMatching('Comments.Users', function ($q) {
return $q->where(['username' => 'jose']);
})
->matching('Comments');
400
Note: As notMatching() will create a LEFT JOIN, you might want to consider calling distinct
on the find query as you can get duplicate rows otherwise.
Keep in mind that contrary to the matching() function, notMatching() will not add any data to the
_matchingData property in the results.
New in version 3.1: Query::notMatching() was added in 3.1
Using leftJoinWith
On certain occasions you may want to calculate a result based on an association, without having to load all
the records for it. For example, if you wanted to load the total number of comments an article has along
with all the article data, you can use the leftJoinWith() function:
$query = $articlesTable->find();
$query->select(['total_comments' => $query->func()->count('Comments.id')])
->leftJoinWith('Comments')
->group(['Articles.id'])
->autoFields(true);
The results for the above query will contain the article data and the total_comments property for each
of them.
leftJoinWith() can also be used with deeply nested associations. This is useful, for example, for
bringing the count of articles tagged with a certain word, per author:
$query = $authorsTable
->find()
->select(['total_articles' => $query->func()->count('Articles.id')])
->leftJoinWith('Articles.Tags', function ($q) {
return $q->where(['Tags.name' => 'awesome']);
})
->group(['Authors.id'])
->autoFields(true);
This function will not load any columns from the specified associations into the result set.
New in version 3.1: Query::leftJoinWith() was added in 3.1
Adding Joins
In addition to loading related data with contain(), you can also add additional joins with the query
builder:
$query = $articles->find()
->hydrate(false)
->join([
More Information
401
You can append multiple joins at the same time by passing an associative array with multiple joins:
$query = $articles->find()
->hydrate(false)
->join([
'c' => [
'table' => 'comments',
'type' => 'LEFT',
'conditions' => 'c.article_id = articles.id',
],
'u' => [
'table' => 'users',
'type' => 'INNER',
'conditions' => 'u.id = articles.user_id',
]
]);
As seen above, when adding joins the alias can be the outer array key. Join conditions can also be expressed
as an array of conditions:
$query = $articles->find()
->hydrate(false)
->join([
'c' => [
'table' => 'comments',
'type' => 'LEFT',
'conditions' => [
'c.created >' => new DateTime('-5 days'),
'c.moderated' => true,
'c.article_id = articles.id'
]
],
], ['c.created' => 'datetime', 'c.moderated' => 'boolean']);
When creating joins by hand and using array based conditions, you need to provide the datatypes for each
column in the join conditions. By providing datatypes for the join conditions, the ORM can correctly
convert data types into SQL. In addition to join() you can use rightJoin(), leftJoin() and
innerJoin() to create joins:
// Join with an alias and string conditions
$query = $articles->find();
$query->leftJoin(
['Authors' => 'authors'],
['Authors.id = Articles.author_id']);
// Join with an alias, array conditions, and types
402
$query = $articles->find();
$query->innerJoin(
['Authors' => 'authors'],
[
'Authors.promoted' => true,
'Authors.created' => new DateTime('-5 days'),
'Authors.id = Articles.author_id'
],
['Authors.promoted' => 'boolean', 'Authors.created' => 'datetime']);
It should be noted that if you set the quoteIdentifiers option to true when defining your
Connection, join conditions between table fields should be set as follow:
$query = $articles->find()
->join([
'c' => [
'table' => 'comments',
'type' => 'LEFT',
'conditions' => [
'c.article_id' => new
\Cake\Database\Expression\IdentifierExpression('articles.id')
]
],
]);
This ensures that all of your identifiers will be quoted across the Query, avoiding errors with some database
Drivers (PostgreSQL notably)
Inserting Data
Unlike earlier examples, you should not use find() to create insert queries. Instead, create a new Query
object using query():
$query = $articles->query();
$query->insert(['title', 'body'])
->values([
'title' => 'First post',
'body' => 'Some body text'
])
->execute();
Generally, it is easier to insert data using entities and ORM\Table::save(). By composing a SELECT
and INSERT query together, you can create INSERT INTO ... SELECT style queries:
$select = $articles->find()
->select(['title', 'body', 'published'])
->where(['id' => 3]);
$query = $articles->query()
->insert(['title', 'body', 'published'])
More Information
403
->values($select)
->execute();
Note: Inserting records with the query builder will not trigger events such as Model.afterSave. Instead
you should use the ORM to save data.
Updating Data
As with insert queries, you should not use find() to create update queries. Instead, create new a Query
object using query():
$query = $articles->query();
$query->update()
->set(['published' => true])
->where(['id' => $id])
->execute();
Deleting Data
As with insert queries, you should not use find() to create delete queries. Instead, create new a query
object using query():
$query = $articles->query();
$query->delete()
->where(['id' => $id])
->execute();
404
When building function expressions, function names should never contain user data:
// Not safe.
$query->func()->{$userData}($arg1);
// Also not safe to use an array of
// user data in a function expression
$query->func()->coalesce($userData);
You can create UNION ALL queries using the unionAll() method:
$inReview = $articles->find()
->where(['need_review' => true]);
$unpublished = $articles->find()
->where(['published' => false]);
$unpublished->unionAll($inReview);
Subqueries
Subqueries are a powerful feature in relational databases and building them in CakePHP is fairly intuitive.
By composing queries together, you can make subqueries:
$matchingComment = $articles->association('Comments')->find()
->select(['article_id'])
->distinct()
More Information
405
Subqueries are accepted anywhere a query expression can be used. For example, in the select() and
join() methods.
Adding Locking Statements
Most relational database vendors support taking out locks when doing select operations. You can use the
epilog() method for this:
// In MySQL
$query->epilog('FOR UPDATE');
The epilog() method allows you to append raw SQL to the end of queries. You should never put raw
user data into epilog().
Executing Complex Queries
While the query builder makes it easy to build most queries, very complex queries can be tedious and
complicated to build. You may want to execute the desired SQL directly.
Executing SQL directly allows you to fine tune the query that will be run. However, doing so doesnt let
you use contain or other higher level ORM features.
Table Objects
class Cake\ORM\Table
Table objects provide access to the collection of entities stored in a specific table. Each table in your
application should have an associated Table class which is used to interact with a given table. If you do not
need to customize the behavior of a given table CakePHP will generate a Table instance for you to use.
Before trying to use Table objects and the ORM, you should ensure that you have configured your database
connection.
Basic Usage
To get started, create a Table class. These classes live in src/Model/Table. Tables are a type model collection
specific to relational databases, and the main interface to your database in CakePHPs ORM. The most basic
table class would look like:
// src/Model/Table/ArticlesTable.php
namespace App\Model\Table;
406
use Cake\ORM\Table;
class ArticlesTable extends Table
{
}
Note that we did not tell the ORM which table to use for our class. By convention table objects will use
a table that matches the lower cased and underscored version of the class name. In the above example the
articles table will be used. If our table class was named BlogPosts your table should be named
blog_posts. You can specify the table to using the table() method:
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->table('my_table');
}
}
No inflection conventions will be applied when specifying a table. By convention the ORM also expects each table to have a primary key with the name of id. If you need to modify this you can use
the primaryKey() method:
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->primaryKey('my_id');
}
}
More Information
407
As seen in the examples above Table objects have an initialize() method which is called at the end of
the constructor. It is recommended that you use this method to do initialization logic instead of overriding
the constructor.
Getting Instances of a Table Class
Before you can query a table, youll need to get an instance of the table. You can do this by using the
TableRegistry class:
// In a controller or table method.
use Cake\ORM\TableRegistry;
$articles = TableRegistry::get('Articles');
The TableRegistry class provides the various dependencies for constructing a table, and maintains a registry
of all the constructed table instances making it easier to build relations and configure the ORM. See Using
the TableRegistry for more information.
If your table class is in a plugin, be sure to use the correct name for your table class. Failing to do so can
result in validation rules, or callbacks not being triggered as a default class is used instead of your actual
class. To correctly load plugin table classes use the following:
// Plugin table
$articlesTable = TableRegistry::get('PluginName.Articles');
// Vendor prefixed plugin table
$articlesTable = TableRegistry::get('VendorName/PluginName.Articles');
Lifecycle Callbacks
As you have seen above table objects trigger a number of events. Events are useful if you want to hook into
the ORM and add logic in without subclassing or overriding methods. Event listeners can be defined in table
or behavior classes. You can also use a tables event manager to bind listeners in.
When using callback methods behaviors attached in the initialize() method will have their listeners
fired before the table callback methods are triggered. This follows the same sequencing as controllers &
components.
To add an event listener to a Table class or Behavior simply implement the method signatures as described
below. See the Events System for more detail on how to use the events subsystem.
408
Event List
Model.initialize
Model.beforeMarshal
Model.beforeFind
Model.buildValidator
Model.buildRules
Model.beforeRules
Model.afterRules
Model.beforeSave
Model.afterSave
Model.afterSaveCommit
Model.beforeDelete
Model.afterDelete
Model.afterDeleteCommit
initialize
Cake\ORM\Table::initialize(Event $event, ArrayObject $data, ArrayObject $options)
The Model.initialize event is fired after the constructor and initialize methods are called. The Table
classes do not listen to this event by default, and instead use the initialize hook method.
To respond to the Model.initialize event you can create a listener class which implements
EventListenerInterface:
use Cake\Event\EventListenerInterface;
class ModelInitializeListener implements EventListenerInterface
{
public function implementedEvents()
{
return array(
'Model.initialize' => 'initializeEvent',
);
}
public function initializeEvent($event)
{
$table = $event->subject();
// do something here
}
}
More Information
409
use Cake\Event\EventManager;
$listener = new ModelInitializeListener();
EventManager::instance()->attach($listener);
This will call the initializeEvent when any Table class is constructed.
beforeMarshal
Cake\ORM\Table::beforeMarshal(Event $event, ArrayObject $data, ArrayObject $options)
The Model.beforeMarshal event is fired before request data is converted into entities. See the Modifying Request Data Before Building Entities documentation for more information.
beforeFind
Cake\ORM\Table::beforeFind(Event $event, Query $query, ArrayObject $options, $primary)
The Model.beforeFind event is fired before each find operation. By stopping the event and supplying
a return value you can bypass the find operation entirely. Any changes done to the $query instance will be
retained for the rest of the find. The $primary parameter indicates whether or not this is the root query,
or an associated query. All associations participating in a query will have a Model.beforeFind event
triggered. For associations that use joins, a dummy query will be provided. In your event listener you can
set additional fields, conditions, joins or result formatters. These options/features will be copied onto the
root query.
You might use this callback to restrict find operations based on a users role, or make caching decisions
based on the current load.
In previous versions of CakePHP there was an afterFind callback, this has been replaced with the Modifying Results with Map/Reduce features and entity constructors.
buildValidator
Cake\ORM\Table::buildValidator(Event $event, Validator $validator, $name)
The Model.buildValidator event is fired when $name validator is created. Behaviors, can use this
hook to add in validation methods.
buildRules
Cake\ORM\Table::buildRules(Event $event, RulesChecker $rules)
The Model.buildRules event is fired after a rules instance has been created and after the tables
buildRules() method has been called.
410
beforeRules
Cake\ORM\Table::beforeRules(Event $event, EntityInterface $entity, ArrayObject $options, $operation)
The Model.beforeRules event is fired before an entity has had rules applied. By stopping this event,
you can halt the rules checking and set the result of applying rules.
afterRules
Cake\ORM\Table::afterRules(Event $event, EntityInterface $entity, ArrayObject $options,
$result, $operation)
The Model.afterRules event is fired after an entity has rules applied. By stopping this event, you can
return the final value of the rules checking operation.
beforeSave
Cake\ORM\Table::beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)
The Model.beforeSave event is fired before each entity is saved. Stopping this event will abort the save
operation. When the event is stopped the result of the event will be returned.
afterSave
Cake\ORM\Table::afterSave(Event $event, EntityInterface $entity, ArrayObject $options)
The Model.afterSave event is fired after an entity is saved.
afterSaveCommit
Cake\ORM\Table::afterSaveCommit(Event $event, EntityInterface $entity, ArrayObject
$options)
The Model.afterSaveCommit event is fired after the transaction in which the save operation is
wrapped has been committed. Its also triggered for non atomic saves where database operations are implicitly committed. The event is triggered only for the primary table on which save() is directly called. The
event is not triggered if a transaction is started before calling save.
beforeDelete
Cake\ORM\Table::beforeDelete(Event $event, EntityInterface $entity, ArrayObject $options)
The Model.beforeDelete event is fired before an entity is deleted. By stopping this event you will
abort the delete operation.
More Information
411
afterDelete
Cake\ORM\Table::afterDelete(Event $event, EntityInterface $entity, ArrayObject $options)
The Model.afterDelete event is fired after an entity has been deleted.
afterDeleteCommit
Cake\ORM\Table::afterDeleteCommit(Event $event, EntityInterface $entity, ArrayObject
$options)
The Model.afterDeleteCommit event is fired after the transaction in which the delete operation is
wrapped has been is committed. Its also triggered for non atomic deletes where database operations are
implicitly committed. The event is triggered only for the primary table on which delete() is directly
called. The event is not triggered if a transaction is started before calling delete.
Behaviors
Cake\ORM\Table::addBehavior($name, array $options =[])
Behaviors provide an easy way to create horizontally re-usable pieces of logic related to table classes. You
may be wondering why behaviors are regular classes and not traits. The primary reason for this is event
listeners. While traits would allow for re-usable pieces of logic, they would complicate binding events.
To add a behavior to your table you can call the addBehavior() method. Generally the best place to do
this is in the initialize() method:
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Timestamp');
}
}
As with associations, you can use plugin syntax and provide additional configuration options:
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Timestamp', [
412
'events' => [
'Model.beforeSave' => [
'created_at' => 'new',
'modified_at' => 'always'
]
]
]);
}
}
You can find out more about behaviors, including the behaviors provided by CakePHP in the chapter on
Behaviors.
Configuring Connections
By default all table instances use the default database connection. If your application uses multiple database connections you will want to configure which tables use which connections. This is the
defaultConnectionName() method:
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table
{
public static function defaultConnectionName() {
return 'replica_db';
}
}
More Information
413
$articles = TableRegistry::get('Articles', [
'className' => 'App\Custom\ArticlesTable',
'table' => 'my_articles',
'connection' => $connectionObject,
'schema' => $schemaObject,
'entityClass' => 'Custom\EntityClass',
'eventManager' => $eventManager,
'behaviors' => $behaviorRegistry
]);
Pay attention to the connection and schema configuration settings, they arent string values but
objects.
The connection will take an object of Cake\Database\Connection and schema
Cake\Database\Schema\Collection.
Note: If your table also does additional configuration in its initialize() method, those values will
overwrite the ones provided to the registry.
You can also pre-configure the registry using the config() method. Configuration data is stored per alias,
and can be overridden by an objects initialize() method:
TableRegistry::config('Users', ['table' => 'my_users']);
Note: You can only configure a table before or during the first time you access that alias. Doing it after the
registry is populated will have no effect.
414
/Entity
/Table
Entities
class Cake\ORM\Entity
While Table Objects represent and provide access to a collection of objects, entities represent individual rows
or domain objects in your application. Entities contain persistent properties and methods to manipulate and
access the data they contain.
Entities are created for you by CakePHP each time you use find() on a table object.
Creating Entity Classes
You dont need to create entity classes to get started with the ORM in CakePHP. However, if you want
to have custom logic in your entities you will need to create classes. By convention entity classes live in
src/Model/Entity/. If our application had an articles table we could create the following entity:
// src/Model/Entity/Article.php
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Article extends Entity
{
}
Right now this entity doesnt do very much. However, when we load data from our articles table, well get
instances of this class.
Note: If you dont define an entity class CakePHP will use the basic Entity class.
Creating Entities
Entities can be directly instantiated:
use App\Model\Entity\Article;
$article = new Article();
When instantiating an entity you can pass the properties with the data you want to store in them:
More Information
415
use App\Model\Entity\Article;
$article = new Article([
'id' => 1,
'title' => 'New Article',
'created' => new DateTime('now')
]);
Another way of getting new entities is using the newEntity() method from the Table objects:
use Cake\ORM\TableRegistry;
$article = TableRegistry::get('Articles')->newEntity();
$article = TableRegistry::get('Articles')->newEntity([
'id' => 1,
'title' => 'New Article',
'created' => new DateTime('now')
]);
When using set() you can update multiple properties at once using an array:
$article->set([
'title' => 'My first post',
'body' => 'It is the best ever!'
]);
Warning: When updating entities with request data you should whitelist which fields can be set with
mass assignment.
In addition to the simple get/set interface, entities allow you to provide accessors and mutator methods.
These methods let you customize how properties are read or set. For example:
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Article extends Entity
{
protected function _getTitle($title)
{
return ucwords($title);
}
}
Accessors use the convention of _get followed by the CamelCased version of the field name. They receive
the basic value stored in the _properties array as their only argument. Accessors will be used when
saving entities, so be careful when defining methods that format data, as the formatted data will be persisted.
You can customize how properties get set by defining a mutator:
namespace App\Model\Entity;
use Cake\ORM\Entity;
use Cake\Utility\Inflector;
class Article extends Entity
{
protected function _setTitle($title)
{
$this->set('slug', Inflector::slug($title));
return $title;
}
}
Mutator methods should always return the value that should be stored in the property. As you can see above,
you can also use mutators to set other calculated properties. When doing this, be careful to not introduce
any loops, as CakePHP will not prevent infinitely looping mutator methods. Mutators allow you to convert
properties as they are set, or create calculated data. Mutators and accessors are applied when properties are
read using object notation, or using get() and set().
Creating Virtual Fields
By defining accessors you can provide access to fields/properties that do not actually exist. For example if
your users table has first_name and last_name you could create a method for the full name:
namespace App\Model\Entity;
use Cake\ORM\Entity;
More Information
417
' .
You can access virtual fields as if they existed on the entity. The property name will be the lower case and
underscored version of the method:
echo $user->full_name;
You can also flag fields as being modified. This is handy when appending into array properties:
// Add a comment and mark the field as changed.
$article->comments[] = $newComment;
$article->dirty('comments', true);
In addition you can also base your conditional code on the original property values by using the
getOriginal() method. This method will either return the original value of the property if it has been
modified or its actual value.
You can also check for changes to any property in the entity:
// See if the entity has changed
$article->dirty();
To remove the dirty mark from fields in an entity, you can use the clean() method:
$article->clean();
When creating a new entity, you can avoid the fields from being marked as dirty by passing an extra option:
$article = new Article(['title' => 'New Article'], ['markClean' => true]);
418
Validation Errors
Cake\ORM\Entity::errors($field = null, $errors = null)
After you save an entity any validation errors will be stored on the entity itself. You can access any validation
errors using the errors() method:
// Get all the errors
$errors = $user->errors();
// Get the errors for a single field.
$errors = $user->errors('password');
The errors() method can also be used to set the errors on an entity, making it easier to test code that
works with error messages:
$user->errors('password', ['Password is required.']);
Mass Assignment
While setting properties to entities in bulk is simple and convenient, it can create significant security issues.
Bulk assigning user data from the request into an entity allows the user to modify any and all columns.
When using anonymous entity classes or creating the entity class with the Bake Console CakePHP does not
protect against mass-assignment.
The _accessible property allows you to provide a map of properties and whether or not they can be
mass-assigned. The values true and false indicate whether a field can or cannot be mass-assigned:
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Article extends Entity
{
protected $_accessible = [
'title' => true,
'body' => true
];
}
In addition to concrete fields there is a special * field which defines the fallback behavior if a field is not
specifically named:
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Article extends Entity
{
protected $_accessible = [
'title' => true,
More Information
419
Note: Modifying accessible fields effects only the instance the method is called on.
When using the newEntity() and patchEntity() methods in the Table objects you can customize
mass assignment protection with options. Please refer to the Changing Accessible Fields section for more
information.
Bypassing Field Guarding
There are some situations when you want to allow mass-assignment to guarded fields:
$article->set($properties, ['guard' => false]);
By setting the guard option to false, you can ignore the accessible field list for a single call to set().
420
If you are certain that an entity has already been persisted, you can use isNew() as a setter:
$article->isNew(false);
$article->isNew(true);
https://github.com/jeremyharris/cakephp-lazyload
More Information
421
For example if we had SoftDeletable plugin, it could provide a trait. This trait could give methods for
marking entities as deleted, the method softDelete could be provided by a trait:
// SoftDelete/Model/Entity/SoftDeleteTrait.php
namespace SoftDelete\Model\Entity;
trait SoftDeleteTrait
{
public function softDelete()
{
$this->set('deleted', true);
}
}
You could then use this trait in your entity class by importing it and including it:
namespace App\Model\Entity;
use Cake\ORM\Entity;
use SoftDelete\Model\Entity\SoftDeleteTrait;
class Article extends Entity
{
use SoftDeleteTrait;
}
Converting to Arrays/JSON
When building APIs, you may often need to convert entities into arrays or JSON data. CakePHP makes this
simple:
// Get an array.
// Associations will be converted with toArray() as well.
$array = $user->toArray();
// Convert to JSON
// Associations will be converted with jsonSerialize hook as well.
$json = json_encode($user);
When converting an entity to an JSON the virtual & hidden field lists are applied. Entities are recursively
converted to JSON as well. This means that if you eager loaded entities and their associations CakePHP will
correctly handle converting the associated data into the correct format.
Exposing Virtual Properties
By default virtual properties are not exported when converting entities to arrays or JSON. In order to expose
virtual properties you need to make them visible. When defining your entity class you can provide a list of
422
Hiding Properties
There are often fields you do not want exported in JSON or array formats. For example it is often unwise
to expose password hashes or account recovery questions. When defining an entity class, define which
properties should be hidden:
namespace App\Model\Entity;
use Cake\ORM\Entity;
class User extends Entity
{
protected $_hidden = ['password'];
}
More Information
423
While table objects provide an abstraction around a repository or collection of objects, when you query
for individual records you get entity objects. While this section discusses the different ways you can find
and load entities, you should read the Entities section for more information on entities.
Debugging Queries and ResultSets
Since the ORM now returns Collections and Entities, debugging these objects can be more complicated than
in previous CakePHP versions. There are now various ways to inspect the data returned by the ORM.
debug($query) Shows the SQL and bound params, does not show results.
debug($query->all()) Shows the ResultSet properties (not the results).
debug($query->toArray()) An easy way to show each of the results.
debug(json_encode($query,JSON_PRETTY_PRINT)) More human readable results.
debug($query->first()) Show the properties of a single entity.
debug((string)$query->first()) Show the properties of a single entity as JSON.
Getting a Single Entity by Primary Key
Cake\ORM\Table::get($id, $options =[])
It is often convenient to load a single entity from the database when editing or view entities and their related
data. You can do this by using get():
// In a controller or table method.
// Get a single article
$article = $articles->get($id);
// Get a single article, and related comments
$article = $articles->get($id, [
'contain' => ['Comments']
]);
424
Optionally you can get() an entity using Custom Finder Methods. For example you may want to get all
translations for an entity. You can achieve that by using the finder option:
$article = $articles->get($id, [
'finder' => 'translations',
]);
The return value of any find() method is always a Cake\ORM\Query object. The Query class allows
you to further refine a query after creating it. Query objects are evaluated lazily, and do not execute until
you start fetching rows, convert it to an array, or when the all() method is called:
// In a controller or table method.
// Find all the articles.
// At this point the query has not run.
$query = $articles->find('all');
// Iteration will execute the query.
foreach ($query as $row) {
}
// Calling all() will execute the query
// and return the result set.
$results = $query->all();
// Once we have a result set we can get all the rows
$data = $results->toArray();
// Converting the query to an array will execute it.
$results = $query->toArray();
More Information
425
Note: Once youve started a query you can use the Query Builder interface to build more complex queries,
adding additional conditions, limits, or include associations using the fluent interface.
You can also provide many commonly used options to find(). This can help with testing as there are
fewer methods to mock:
// In a controller or table method.
$query = $articles->find('all', [
'conditions' => ['Articles.created >' => new DateTime('-10 days')],
'contain' => ['Authors', 'Comments'],
'limit' => 10
]);
426
This approach replaces find('first') in previous versions of CakePHP. You may also want to use the
get() method if you are loading entities by primary key.
Note: The first() method will return null if no results are found.
See Returning the Total Count of Records for additional usage of the count() method.
Finding Key/Value Pairs
It is often useful to generate an associative array of data from your applications data. For example, this is
very useful when creating <select> elements. CakePHP provides a simple to use method for generating
lists of data:
// In a controller or table method.
$query = $articles->find('list');
$data = $query->toArray();
// Data now looks like
$data = [
1 => 'First post',
2 => 'Second article I wrote',
];
With no additional options the keys of $data will be the primary key of your table, while the values will be
the displayField of the table. You can use the displayField() method on a table object to configure
the display field of a table:
More Information
427
When calling list you can configure the fields used for the key and value with the keyField and
valueField options respectively:
// In a controller or table method.
$query = $articles->find('list', [
'keyField' => 'slug',
'valueField' => 'title'
]);
$data = $query->toArray();
// Data now looks like
$data = [
'first-post' => 'First post',
'second-article-i-wrote' => 'Second article I wrote',
];
Results can be grouped into nested sets. This is useful when you want bucketed sets, or want to build
<optgroup> elements with FormHelper:
// In a controller or table method.
$query = $articles->find('list', [
'keyField' => 'slug',
'valueField' => 'title',
'groupField' => 'author_id'
]);
$data = $query->toArray();
// Data now looks like
$data = [
1 => [
'first-post' => 'First post',
'second-article-i-wrote' => 'Second article I wrote',
],
2 => [
// More data.
]
];
You can also create list data from associations that can be reached with joins:
$query = $articles->find('list', [
'keyField' => 'id',
'valueField' => 'author.name'
])->contain(['Authors']);
428
Lastly it is possible to use closures to access entity mutator methods in your list finds. This example shows
using the _getFullName() mutator method from the Author entity.
$query = $articles->find('list', [
'keyField' => 'id',
'valueField' => function ($e) {
return $e->author->get('full_name');
}
]);
You can also fetch the full name in the list directly using.
$this->displayField('full_name');
$query = $authors->find('list');
The parentField and keyField keys can be used to define the fields that threading will occur on.
Tip: If you need to manage more advanced trees of data, consider using Tree instead.
429
use Cake\ORM\Query;
use Cake\ORM\Table;
class ArticlesTable extends Table
{
public function findOwnedBy(Query $query, array $options)
{
$user = $options['user'];
return $query->where(['author_id' => $user->id]);
}
}
// In a controller or table method.
$articles = TableRegistry::get('Articles');
$query = $articles->find('ownedBy', ['user' => $userEntity]);
Finder methods can modify the query as required, or use the $options to customize the finder operation
with relevant application logic. You can also stack finders, allowing you to express complex queries
effortlessly. Assuming you have both the published and recent finders, you could do the following:
// In a controller or table method.
$articles = TableRegistry::get('Articles');
$query = $articles->find('published')->find('recent');
While all the examples so far have show finder methods on table classes, finder methods can also be defined
on Behaviors.
If you need to modify the results after they have been fetched you should use a Modifying Results with
Map/Reduce function to modify the results. The map reduce features replace the afterFind callback found
in previous versions of CakePHP.
Dynamic Finders
CakePHPs ORM provides dynamically constructed finder methods which allow you to express simple
queries with no additional code. For example if you wanted to find a user by username you could do:
// In a controller
// The following two calls are equal.
$query = $this->Users->findByUsername('joebob');
$query = $this->Users->findAllByUsername('joebob');
// In a table method
$users = TableRegistry::get('Users');
// The following two calls are equal.
$query = $users->findByUsername('joebob');
$query = $users->findAllByUsername('joebob');
430
While you can use either OR or AND conditions, you cannot combine the two in a single dynamic finder.
Other query options like contain are also not supported with dynamic finders. You should use Custom
Finder Methods to encapsulate more complex queries. Lastly, you can also combine dynamic finders with
custom finders:
$query = $users->findTrollsByUsername('bro');
Once you have a query object from a dynamic finder, youll need to call first() if you want the first
result.
Note: While dynamic finders make it simple to express queries, they come with some additional performance overhead.
431
Eager loading helps avoid many of the potential performance problems surrounding lazy-loading in an
ORM. The queries generated by eager loading can better leverage joins, allowing more efficient queries to
be made. In CakePHP you define eager loaded associations using the contain method:
// In a controller or table method.
// As an option to find()
$query = $articles->find('all', ['contain' => ['Authors', 'Comments']]);
// As a method on the query object
$query = $articles->find('all');
$query->contain(['Authors', 'Comments']);
The above will load the related author and comments for each article in the result set. You can load nested
associations using nested arrays to define the associations to be loaded:
$query = $articles->find()->contain([
'Authors' => ['Addresses'], 'Comments' => ['Authors']
]);
Alternatively, you can express nested associations using the dot notation:
$query = $articles->find()->contain([
'Authors.Addresses',
'Comments.Authors'
]);
If you need to reset the containments on a query you can set the second argument to true:
$query = $articles->find();
$query->contain(['Authors', 'Comments'], true);
432
}
]);
Note: When you limit the fields that are fetched from an association, you must ensure that the foreign key
columns are selected. Failing to select foreign key fields will cause associated data to not be present in the
final result.
It is also possible to restrict deeply-nested associations using the dot notation:
$query = $articles->find()->contain([
'Comments',
'Authors.Profiles' => function ($q) {
return $q->where(['Profiles.is_published' => true]);
}
]);
If you have defined some custom finder methods in your associated table, you can use them inside
contain():
// Bring all articles, but only bring the comments that are approved and
// popular.
$query = $articles->find()->contain([
'Comments' => function ($q) {
return $q->find('approved')->find('popular');
}
]);
Note: For BelongsTo and HasOne associations only the where and select clauses are used when
loading the associated records. For the rest of the association types you can use every clause that the query
object provides.
If you need full control over the query that is generated, you can tell contain() to not append
the foreignKey constraints to the generated query. In that case you should use an array passing
foreignKey and queryBuilder:
$query = $articles->find()->contain([
'Authors' => [
'foreignKey' => false,
'queryBuilder' => function ($q) {
More Information
433
If you have limited the fields you are loading with select() but also want to load fields off of contained
associations, you can pass the association object to select():
// Select id & title from articles, but all fields off of Users.
$query = $articles->find()
->select(['id', 'title'])
->select($articlesTable->Users)
->contain(['Users']);
New in version 3.1: Selecting columns via an association object was added in 3.1
Sorting Contained Associations
When loading HasMany and BelongsToMany associations, you can use the sort option to sort the data in
those associations:
$query->contain([
'Comments' => [
'sort' => ['Comment.created' => 'DESC']
]
]);
434
You can apply this strategy to HasMany associations as well. For example if Authors HasMany Articles,
you could find all the authors with recently published articles using the following:
$query = $authors->find();
$query->matching('Articles', function ($q) {
return $q->where(['Articles.created >=' => new DateTime('-10 days')]);
});
Filtering by deep associations is surprisingly easy, and the syntax should be already familiar to you:
// In a controller or table method.
$query = $products->find()->matching(
'Shops.Cities.Countries', function ($q) {
return $q->where(['Countries.name' => 'Japan']);
}
);
// Bring unique articles that were commented by 'markstory' using passed
variable
// Dotted matching paths should be used over nested matching() calls
$username = 'markstory';
$query = $articles->find()->matching('Comments.Users', function ($q) use (
$username) {
return $q->where(['username' => $username]);
});
Note: As this function will create an INNER JOIN, you might want to consider calling distinct on
the find query as you might get duplicate rows if your conditions dont exclude them already. This might be
the case, for example, when the same users comments more than once on a single article.
The data from the association that is matched will be available on the _matchingData property of entities. If you both match and contain the same association, you can expect to get both the _matchingData
and standard association properties in your results.
Using innerJoinWith
Using the matching() function, as we saw already, will create an INNER JOIN with the specified
association and will also load the fields into the result set.
There may be cases where you want to use matching() but are not interested in loading the fields into
the result set. For this purpose, you can use innerJoinWith():
$query = $articles->find();
$query->innerJoinWith('Tags', function ($q) {
return $q->where(['Tags.name' => 'CakePHP']);
});
More Information
435
The innerJoinWith() method works the same as matching(), that means that you can use dot
notation to join deeply nested associations:
$query = $products->find()->innerJoinWith(
'Shops.Cities.Countries', function ($q) {
return $q->where(['Countries.name' => 'Japan']);
}
);
Again, the only difference is that no additional columns will be added to the result set, and no
_matchingData property will be set.
New in version 3.1: Query::innerJoinWith() was added in 3.1
Using notMatching
The opposite of matching() is notMatching(). This function will change the query so that it filters
results that have no relation to the specified association:
// In a controller or table method.
$query = $articlesTable
->find()
->notMatching('Tags', function ($q) {
return $q->where(['Tags.name' => 'boring']);
});
The above example will find all articles that were not tagged with the word boring. You can apply this
method to HasMany associations as well. You could, for example, find all the authors with no published
articles in the last 10 days:
$query = $authorsTable
->find()
->notMatching('Articles', function ($q) {
return $q->where(['Articles.created >=' => new \DateTime('-10 days
')]);
});
It is also possible to use this method for filtering out records not matching deep associations. For example,
you could find articles that have not been commented on by a certain user:
$query = $articlesTable
->find()
->notMatching('Comments.Users', function ($q) {
return $q->where(['username' => 'jose']);
});
Since articles with no comments at all also satisfy the condition above, you may want to combine
matching() and notMatching() in the same query. The following example will find articles hav-
436
Note: As notMatching() will create a LEFT JOIN, you might want to consider calling distinct
on the find query as you can get duplicate rows otherwise.
Keep in mind that contrary to the matching() function, notMatching() will not add any data to the
_matchingData property in the results.
New in version 3.1: Query::notMatching() was added in 3.1
Using leftJoinWith
On certain occasions you may want to calculate a result based on an association, without having to load all
the records for it. For example, if you wanted to load the total number of comments an article has along
with all the article data, you can use the leftJoinWith() function:
$query = $articlesTable->find();
$query->select(['total_comments' => $query->func()->count('Comments.id')])
->leftJoinWith('Comments')
->group(['Articles.id'])
->autoFields(true);
The results for the above query will contain the article data and the total_comments property for each
of them.
leftJoinWith() can also be used with deeply nested associations. This is useful, for example, for
bringing the count of articles tagged with a certain word, per author:
$query = $authorsTable
->find()
->select(['total_articles' => $query->func()->count('Articles.id')])
->leftJoinWith('Articles.Tags', function ($q) {
return $q->where(['Tags.name' => 'awesome']);
})
->group(['Authors.id'])
->autoFields(true);
This function will not load any columns from the specified associations into the result set.
New in version 3.1: Query::leftJoinWith() was added in 3.1
More Information
437
In order to correctly fetch the data from this association, we will need to tell the query to use the select
strategy, since we want order by a particular column:
$query = $articles->find()->contain([
'FirstComment' => [
'strategy' => 'select',
'queryBuilder' => function ($q) {
return $q->order(['FirstComment.created' =>'ASC'])->limit(1);
}
]
]);
Dynamically changing the strategy in this way will only apply to a specific query. If you want to make the
strategy change permanent you can do:
$articles->FirstComment->strategy('select');
Using the select strategy is also a great way of making associations with tables in another database, since
it would not be possible to fetch records using joins.
Fetching With The Subquery Strategy
As your tables grow in size, fetching associations from them can become slower, especially if you
are querying big batches at once. A good way of optimizing association loading for hasMany and
belongsToMany associations is by using the subquery strategy:
$query = $articles->find()->contain([
'Comments' => [
'strategy' => 'subquery',
'queryBuilder' => function ($q) {
return $q->where(['Comments.approved' => true]);
}
]
]);
438
The result will remain the same as with using the default strategy, but this can greatly improve the query and
fetching time in some databases, in particular it will allow to fetch big chunks of data at the same time in
databases that limit the amount of bound parameters per query, such as Microsoft SQL Server.
You can also make the strategy permanent for the association by doing:
$articles->Comments->strategy('subquery');
More Information
439
// Json
$json = json_encode($results);
Both serializing and JSON encoding result sets work as you would expect. The serialized data can be
unserialized into a working result set. Converting to JSON respects hidden & virtual field settings on all
entity objects within a result set.
In addition to making serialization easy, result sets are a Collection object and support the same methods
that collection objects do. For example, you can extract a list of unique tags on a collection of articles by
running:
// In a controller or table method.
$articles = TableRegistry::get('Articles');
$query = $articles->find()->contain(['Tags']);
$reducer = function ($output, $value) {
if (!in_array($value, $output)) {
$output[] = $value;
}
return $output;
};
$uniqueTags = $query->all()
->extract('tags.name')
->reduce($reducer, []);
Some other examples of the collection methods being used with result sets are:
// Filter the rows by a calculated property
$filtered = $results->filter(function ($row) {
return $row->is_recent;
});
// Create an associative array from result properties
$articles = TableRegistry::get('Articles');
$results = $articles->find()->contain(['Authors'])->all();
$authorList = $results->combine('id', 'author.name');
The Collections chapter has more detail on what can be done with result sets using the collections features.
The Adding Calculated Fields section show how you can add calculated fields, or replace the result set.
Getting the First & Last Record From a ResultSet
You can use the first() and last() methods to get the respective records from a result set:
$result = $articles->find('all')->all();
// Get the first and/or last result.
$row = $result->first();
$row = $result->last();
440
You can eager load additional data into a single entity, or a collection of entities.
Modifying Results with Map/Reduce
More often than not, find operations require post-processing the data that is found in the database. While
entities getter methods can take care of most of the virtual property generation or special data formatting,
sometimes you need to change the data structure in a more fundamental way.
For those cases, the Query object offers the mapReduce() method, which is a way of processing results
once they are fetched from the database.
A common example of changing the data structure is grouping results together based on certain conditions.
For this task we can use the mapReduce() function. We need two callable functions the $mapper and
the $reducer. The $mapper callable receives the current result from the database as first argument, the
iteration key as second argument and finally it receives an instance of the MapReduce routine it is running:
$mapper = function ($article, $key, $mapReduce) {
$status = 'published';
More Information
441
if ($article->isDraft() || $article->isInReview()) {
$status = 'unpublished';
}
$mapReduce->emitIntermediate($article, $status);
};
In the above example $mapper is calculating the status of an article, either published or unpublished, then
it calls emitIntermediate() on the MapReduce instance. This method stores the article in the list of
articles labelled as either published or unpublished.
The next step in the map-reduce process is to consolidate the final results. For each status created in the
mapper, the $reducer function will be called so you can do any extra processing. This function will
receive the list of articles in a particular bucket as the first parameter, the name of the bucket it needs to
process as the second parameter, and again, as in the mapper() function, the instance of the MapReduce
routine as the third parameter. In our example, we did not have to do any extra processing, so we just
emit() the final results:
$reducer = function ($articles, $status, $mapReduce) {
$mapReduce->emit($articles, $status);
};
Of course, this is a simplistic example that could actually be solved in another way without the help of a
map-reduce process. Now, lets take a look at another example in which the reducer function will be needed
to do something more than just emitting the results.
Calculating the most commonly mentioned words, where the articles contain information about CakePHP,
as usual we need a mapper function:
$mapper = function ($article, $key, $mapReduce) {
if (stripos('cakephp', $article['body']) === false) {
return;
}
$words = array_map('strtolower', explode(' ', $article['body']));
foreach ($words as $word) {
$mapReduce->emitIntermediate($article['id'], $word);
442
}
};
It first checks for whether the cakephp word is in the articles body, and then breaks the body into individual words. Each word will create its own bucket where each article id will be stored. Now lets reduce
our results to only extract the count:
$reducer = function ($occurrences, $word, $mapReduce) {
$mapReduce->emit(count($occurrences), $word);
}
This could return a very large array if we dont clean stop words, but it could look something like this:
[
'cakephp' => 100,
'awesome' => 39,
'impressive' => 57,
'outstanding' => 10,
'mind-blowing' => 83
]
One last example and you will be a map-reduce expert. Imagine you have a friends table and you want
to find fake friends in our database, or better said, people who do not follow each other. Lets start with
our mapper() function:
$mapper = function ($rel, $key, $mr) {
$mr->emitIntermediate($rel['source_user_id'], $rel['target_user_id']);
$mr->emitIntermediate($rel['target_user_id'], $rel['source_target_id']);
};
We just duplicated our data to have a list of users each other user follows. Now its time to reduce it. For
each call to the reducer, it will receive a list of followers per user:
// $friends list will look like
// repeated numbers mean that the relationship existed in both directions
[2, 5, 100, 2, 4]
$reducer = function ($friendsList, $user, $mr) {
$friends = array_count_values($friendsList);
foreach ($friends as $friend => $count) {
if ($count < 2) {
$mr->emit($friend, $user);
}
More Information
443
}
}
The resulting array means, for example, that user with id 1 follows users 2 and 4, but those do not follow 1
back.
Stacking Multiple Operations
Using mapReduce in a query will not execute it immediately. The operation will be registered to be run as
soon as the first result is attempted to be fetched. This allows you to keep chaining additional methods and
filters to the query even after adding a map-reduce routine:
$query = $articles->find()
->where(['published' => true])
->mapReduce($mapper, $reducer);
// At a later point in your app:
$query->where(['created >=' => new DateTime('1 day ago')]);
This is particularly useful for building custom finder methods as described in the Custom Finder Methods
section:
public function findPublished(Query $query, array $options)
{
return $query->where(['published' => true]);
}
public function findRecent(Query $query, array $options)
{
return $query->where(['created >=' => new DateTime('1 day ago')]);
}
public function findCommonWords(Query $query, array $options)
{
// Same as in the common words example in the previous section
444
$mapper = ...;
$reducer = ...;
return $query->mapReduce($mapper, $reducer);
}
$commonWords = $articles
->find('commonWords')
->find('published')
->find('recent');
Moreover, it is also possible to stack more than one mapReduce operation for a single query. For example,
if we wanted to have the most commonly used words for articles, but then filter it to only return words that
were mentioned more than 20 times across all articles:
$mapper = function ($count, $word, $mr) {
if ($count > 20) {
$mr->emit($count, $word);
}
};
$articles->find('commonWords')->mapReduce($mapper);
Validating Data
Before you save your data you will probably want to ensure the data is correct and consistent. In CakePHP
we have two stages of validation:
1. Before request data is converted into entities, validation rules around data types and formatting can be
applied.
2. Before data is saved, domain or application rules can be applied. These rules help ensure that your
applications data remains consistent.
Validating Data Before Building Entities
When marshalling data into entities, you can validate data. Validating data allows you to check the type,
shape and size of data. By default request data will be validated before it is converted into entities. If any
validation rules fail, the returned entity will contain errors. The fields with errors will not be present in the
returned entity:
More Information
445
$article = $articles->newEntity($this->request->data);
if ($article->errors()) {
// Entity failed validation.
}
446
$validator
->allowEmpty('link')
->add('link', 'valid-url', ['rule' => 'url']);
...
return $validator;
}
}
The available validation methods and rules come from the Validator class and are documented in the
Creating Validators section.
Note: Validation objects are intended primarily for validating user input, i.e. forms and any other posted
request data.
The above would call the validationUpdate() method on the table instance to build the required rules.
By default the validationDefault() method will be used. An example validator for our articles table
would be:
class ArticlesTable extends Table
{
public function validationUpdate($validator)
{
$validator
->add('title', 'notEmpty', [
'rule' => 'notEmpty',
'message' => __('You need to provide a title'),
])
->add('body', 'notEmpty', [
'rule' => 'notEmpty',
'message' => __('A body is required')
]);
return $validator;
}
}
You can have as many validation sets as necessary. See the validation chapter for more information on
building validation rule-sets.
More Information
447
Combining Validators
Because of how validator objects are built, it is easy to break their construction process into multiple reusable
steps:
// UsersTable.php
public function validationDefault(Validator $validator)
{
$validator->notEmpty('username');
$validator->notEmpty('password');
$validator->add('email', 'valid-email', ['rule' => 'email']);
...
return $validator;
}
public function validationHardened(Validator $validator)
{
$validator = $this->validationDefault($validator);
448
Given the above setup, when using the hardened validation set, it will also contain the validation rules
declared in the default set.
Validation Providers
Validation rules can use functions defined on any known providers. By default CakePHP sets up a few
providers:
1. Methods on the table class or its behaviors are available on the table provider.
2. The core Validation\Validation class is setup as the default provider.
When a validation rule is created you can name the provider of that rule. For example, if your table has an
isValidRole method you can use it as a validation rule:
use Cake\ORM\Table;
use Cake\Validation\Validator;
class UsersTable extends Table
{
public function validationDefault(Validator $validator)
{
$validator
->add('role', 'validRole', [
'rule' => 'isValidRole',
'message' => __('You need to provide a valid role'),
'provider' => 'table',
]);
return $validator;
}
public function isValidRole($value, array $context)
{
return in_array($value, ['admin', 'editor', 'author'], true);
}
}
Validation methods can return error messages when they fail. This is a simple way to make error messages
dynamic based on the provided value.
More Information
449
450
{
// Add a rule that is applied for create and update operations
$rules->add(function ($entity, $options) {
// Return a boolean to indicate pass/failure
}, 'ruleName');
// Add a rule for create.
$rules->addCreate(function ($entity, $options) {
// Return a boolean to indicate pass/failure
}, 'ruleName');
// Add a rule for update
$rules->addUpdate(function ($entity, $options) {
// Return a boolean to indicate pass/failure
}, 'ruleName');
// Add a rule for the deleting.
$rules->addDelete(function ($entity, $options) {
// Return a boolean to indicate pass/failure
}, 'ruleName');
return $rules;
}
Your rules functions can expect to get the Entity being checked and an array of options. The options array will contain errorField, message, and repository. The repository option will contain
the table class the rules are attached to. Because rules accept any callable, you can also use instance
functions:
$rules->addCreate([$this, 'uniqueEmail'], 'uniqueEmail');
or callable classes:
$rules->addCreate(new IsUnique(['email']), 'uniqueEmail');
When adding rules you can define the field the rule is for and the error message as options:
$rules->add([$this, 'isValidState'], 'validState', [
'errorField' => 'status',
'message' => 'This invoice cannot be moved to that status.'
]);
The error will be visible when calling the errors() method on the entity:
$entity->errors(); // Contains the domain rules error messages
451
use Cake\ORM\Rule\IsUnique;
// A single field.
$rules->add($rules->isUnique(['email']));
// A list of fields
$rules->add($rules->isUnique(['username', 'account_id']));
When setting rules on foreign key fields it is important to remember, that only the fields listed are used in
the rule. This means that setting $user->account->id will not trigger the above rule.
Foreign Key Rules
While you could rely on database errors to enforce constraints, using rules code can help provide a nicer
user experience. Because of this CakePHP includes an ExistsIn rule class:
// A single field.
$rules->add($rules->existsIn('article_id', 'articles'));
// Multiple keys, useful for composite primary keys.
$rules->add($rules->existsIn(['site_id', 'article_id'], 'articles'));
The fields to check existence against in the related table must be part of the primary key.
You can enforce existsIn to pass when nullable parts of your composite foreign key are null:
// Example: A composite primary key within NodesTable is (id, site_id).
// A Node may reference a parent Node but does not need to. In latter case,
parent_id is null.
// Allow this rule to pass, even if fields that are nullable, like parent_id,
are null:
$rules->add($rules->existsIn(
['parent_id', 'site_id'], // Schema: parent_id NULL, site_id NOT NULL
'ParentNodes',
['allowNullableNulls' => true]
);
// A Node however should in addition also always reference a Site.
$rules->add($rules->existsIn(['site_id'], 'Sites'));
452
When defining count based rules, the third parameter lets you define the comparison operator to use. ==,
>=, <=, >, <, and != are the accepted operators. To ensure a propertys count is within a range, use two
rules:
// Between 3 and 5 tags
$rules->add($rules->validCount('tags', 3, '>=', 'You must have at least 3 tags
'));
$rules->add($rules->validCount('tags', 5, '<=', 'You must have at most 5 tags
'));
See the core rules for examples on how to create such rules.
Creating Custom Rule Objects
If your application has rules that are commonly reused, it is helpful to package those rules into re-usable
classes:
// in src/Model/Rule/CustomRule.php
namespace App\Model\Rule;
use Cake\Datasource\EntityInterface;
More Information
453
class CustomRule
{
public function __invoke(EntityInterface $entity, array $options)
{
// Do work
return false;
}
}
By creating custom rule classes you can keep your code DRY and make your domain rules easy to test.
Disabling Rules
When saving an entity, you can disable the rules if necessary:
$articles->save($article, ['checkRules' => false]);
$articlesTable->newEntity(
'customName']
$articlesTable->patchEntity(
'customName']
454
custom
validator,
which
is
defined
using
the
Validation assumes strings or array are passed since that is what is received from any request:
// In src/Model/Table/UsersTable.php
public function validatePasswords($validator)
{
$validator->add('confirm_password', 'no-misspelling', [
'rule' => ['compareWith', 'password'],
'message' => 'Passwords are not equal',
]);
...
return $validator;
}
In the above example the entity will be saved as validation is only triggered for the newEntity() and
patchEntity() methods. The second level of validation is meant to address this situation.
Application rules as explained above will be checked whenever save() or delete() are called:
// In src/Model/Table/UsersTable.php
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->isUnique('email'));
return $rules;
}
// Elsewhere in your application code
$userEntity->email = 'a@duplicated.email';
$usersTable->save($userEntity); // Returns false
While Validation is meant for direct user input, application rules are specific for data transitions generated
inside your application:
// In src/Model/Table/OrdersTable.php
public function buildRules(RulesChecker $rules)
{
$check = function($order) {
return $order->price < 100 && $order->shipping_mode === 'free';
};
$rules->add($check, [
'errorField' => 'shipping_mode',
'message' => 'No free shipping for orders under 100!'
More Information
455
]);
return $rules;
}
// Elsewhere in application code
$order->price = 50;
$order->shipping_mode = 'free';
$ordersTable->save($order); // Returns false
When executed the save will fail thanks to the new application rule that was added:
$userEntity->email = 'not an email!!!';
$usersTable->save($userEntity);
$userEntity->errors('email'); // Invalid email
456
Saving Data
class Cake\ORM\Table
After you have loaded your data you will probably want to update and save the changes.
A Glance Over Saving Data
Applications will usually have a couple of ways in which data is saved. The first one is obviously through
web forms and the other is by directly generating or changing data in the code to be sent to the database.
Inserting Data
The easiest way to insert data in the database is by creating a new entity and passing it to the save()
method in the Table class:
use Cake\ORM\TableRegistry;
$articlesTable = TableRegistry::get('Articles');
$article = $articlesTable->newEntity();
$article->title = 'A New Article';
$article->body = 'This is the body of the article';
if ($articlesTable->save($article)) {
// The $article entity contains the id now
$id = $article->id;
}
Updating Data
Updating your data is equally easy, and the save() method is also used for that purpose:
use Cake\ORM\TableRegistry;
$articlesTable = TableRegistry::get('Articles');
$article = $articlesTable->get(12); // Return article with id 12
$article->title = 'CakePHP is THE best PHP framework!';
$articlesTable->save($article);
CakePHP will know whether to perform an insert or an update based on the return value of the isNew()
method. Entities that were retrieved with get() or find() will always return false when isNew() is
called on them.
More Information
457
The save() method is also able to create new records for associations:
$firstComment = $articlesTable->Comments->newEntity();
$firstComment->body = 'The CakePHP features are outstanding';
$secondComment = $articlesTable->Comments->newEntity();
$secondComment->body = 'CakePHP performance is terrific!';
$tag1 = $articlesTable->Tags->findByName('cakephp')->first();
$tag2 = $articlesTable->Tags->newEntity();
$tag2->name = 'awesome';
$article = $articlesTable->get(12);
$article->comments = [$firstComment, $secondComment];
$article->tags = [$tag1, $tag2];
$articlesTable->save($article);
You can also create/update join table information when using newEntity() or patchEntity(). Your
POST data should look like:
$data = [
'title' => 'My great blog post',
'body' => 'Some content that goes on for a bit.',
'tags' => [
[
'id' => 10,
'_joinData' => [
'tagComment' => 'Great article!',
]
],
]
];
$articlesTable->newEntity($data, ['associated' => ['Tags']]);
When modifying records by directly setting or changing the properties no validation happens, which is a
problem when accepting form data. The following sections will demonstrate how to efficiently convert form
data into entities so that they can be validated and saved.
More Information
459
The request data should follow the structure of your entities. For example if you have an article, which
belonged to a user, and had many comments, your request data should resemble:
$data = [
'title' => 'CakePHP For the Win',
'body' => 'Baking with CakePHP makes web development fun!',
'user_id' => 1,
'user' => [
'username' => 'mark'
],
'comments' => [
['body' => 'The CakePHP features are outstanding'],
['body' => 'CakePHP performance is terrific!'],
]
];
By default, the newEntity() method validates the data that gets passed to it, as explained in the Validating
Data Before Building Entities section. If you wish to bypass data validation pass the 'validate' =>
false option:
$entity = $articles->newEntity($data, ['validate' => false]);
When building forms that save nested associations, you need to define which associations should be marshalled:
// In a controller
$articles = TableRegistry::get('Articles');
// New entity with nested associations
$entity = $articles->newEntity($this->request->data(), [
'associated' => [
'Tags', 'Comments' => ['associated' => ['Users']]
]
]);
The above indicates that the Tags, Comments and Users for the Comments should be marshalled.
Alternatively, you can use dot notation for brevity:
// In a controller
$articles = TableRegistry::get('Articles');
460
Associated data is also validated by default unless told otherwise. You may also change the validation set to
be used per association:
// In a controller
$articles = TableRegistry::get('Articles');
// Bypass validation on Tags association and
// Designate 'signup' validation set for Comments.Users
$entity = $articles->newEntity($this->request->data(), [
'associated' => [
'Tags' => ['validate' => false],
'Comments.Users' => ['validate' => 'signup']
]
]);
The following diagram gives an overview of what happens inside the newEntity() or patchEntity()
method:
You
can
always
count
on
getting
an
entity
back
from
newEntity().
If
validation
fails
your
entity
will
contain
errors,
More Information
461
and
any
invalid
fields
will
not
be
populated
in
the
created entity.
Converting BelongsToMany Data
If you are saving belongsToMany associations you can either use a list of entity data or a list of ids. When
using a list of entity data your request data should look like:
$data = [
'title' => 'My title',
'body' => 'The text',
'user_id' => 1,
'tags' => [
['tag' => 'CakePHP'],
['tag' => 'Internet'],
]
];
The above will create 2 new tags. If you want to link an article with existing tags you can use a list of ids.
Your request data should look like:
$data = [
'title' => 'My title',
'body' => 'The text',
'user_id' => 1,
'tags' => [
'_ids' => [1, 2, 3, 4]
]
];
If you need to link against some existing belongsToMany records, and create new ones at the same time you
can use an expanded format:
$data = [
'title' => 'My title',
'body' => 'The text',
'user_id' => 1,
'tags' => [
['name' => 'A new tag'],
462
When the above data is converted into entities, you will have 4 tags. The first two will be new objects, and
the second two will be references to existing records.
When converting belongsToMany data, you can disable the new entity creation, by using the onlyIds
option. When enabled, this option restricts belongsToMany marshalling to only use the _ids key and
ignore all other data.
New in version 3.1.0: The onlyIds option was added in 3.1.0
Converting HasMany Data
If you are saving hasMany associations and want to link existing records to a new parent record you can use
the _ids format:
$data = [
'title' => 'My new article',
'body' => 'The text',
'user_id' => 1,
'comments' => [
'_ids' => [1, 2, 3, 4]
]
];
When converting hasMany data, you can disable the new entity creation, by using the onlyIds option.
When enabled, this option restricts hasMany marshalling to only use the _ids key and ignore all other
data.
New in version 3.1.0: The onlyIds option was added in 3.1.0
Converting Multiple Records
When creating forms that create/update multiple records at once you can use newEntities():
// In a controller.
$articles = TableRegistry::get('Articles');
$entities = $articles->newEntities($this->request->data());
In this situation, the request data for multiple articles should look like:
$data = [
[
'title' => 'First post',
'published' => 1
],
More Information
463
[
'title' => 'Second post',
'published' => 1
],
];
Once youve converted request data into entities you can save() or delete() them:
// In a controller.
foreach ($entities as $entity) {
// Save entity
$articles->save($entity);
// Delete entity
$articles->delete($entity);
}
The above will run a separate transaction for each entity saved. If youd like to process all the entities as a
single transaction you can use transactional():
// In a controller.
$articles->connection()->transactional(function () use ($articles, $entities)
{
foreach ($entities as $entity) {
$articles->save($entity, ['atomic' => false]);
}
});
The above will keep the association unchanged between Comments and Users for the concerned entity.
464
Note: If you are using newEntity() and the resulting entities are missing some or all of the data they were
passed, double check that the columns you want to set are listed in the $_accessible property of your
entity.
You may also change the validation set used for the entity or any of the associations:
$articles->patchEntity($article, $this->request->data(), [
'validate' => 'custom',
'associated' => ['Tags', 'Comments.Users' => ['validate' => 'signup']]
]);
More Information
465
Associations are merged by matching the primary key field in the source entities to the corresponding fields
in the data array. Associations will construct new entities if no previous entity is found for the associations
target property.
For example give some request data like the following:
$data = [
'title' => 'My title',
'user' => [
'username' => 'mark'
]
];
Trying to patch an entity without an entity in the user property will create a new user entity:
// In a controller.
$entity = $articles->patchEntity(new Article, $data);
echo $entity->user->username; // Echoes 'mark'
The same can be said about hasMany and belongsToMany associations, with an important caveat:
Note: For belongsToMany associations, ensure the relevant entity has a property accessible for the associated entity.
If a Product belongsToMany Tag:
// in the
protected
// ..
'tags'
];
Product Entity
$_accessible = [
other properties
=> true,
Note: For hasMany and belongsToMany associations, if there were any entities that could not be matched
by primary key to any record in the data array, then those records will be discarded from the resulting entity.
Remember that using either patchEntity() or patchEntities() does not persist the data, it just
edits (or creates) the given entities. In order to save the entity you will have to call the tables save()
method.
For example, consider the following case:
$data = [
'title' => 'My title',
466
At the end, if the entity is converted back to an array you will obtain the following result:
[
'title' => 'My title',
'body' => 'The text',
'comments' => [
['body' => 'Changed comment', 'id' => 1],
['body' => 'A new comment'],
]
];
As you can see, the comment with id 2 is no longer there, as it could not be matched to anything in the
$newData array. This happens because CakePHP is reflecting the new state described in the request data.
Some additional advantages of this approach is that it reduces the number of operations to be executed when
persisting the entity again.
Please note that this does not mean that the comment with id 2 was removed from the database, if you wish
to remove the comments for that article that are not present in the entity, you can collect the primary keys
and execute a batch delete for those not in the list:
// In a controller.
$comments = TableRegistry::get('Comments');
$present = (new Collection($entity->comments))->extract('id')->filter()->
toArray();
$comments->deleteAll([
'article_id' => $article->id,
'id NOT IN' => $present
]);
As you can see, this also helps creating solutions where an association needs to be implemented like a single
set.
You can also patch multiple entities at once. The consideration made for patching hasMany and belongsToMany associations apply for patching multiple entities: Matches are done by the primary key field
More Information
467
value and missing matches in the original entities array will be removed and not present in the result:
// In a controller.
$articles = TableRegistry::get('Articles');
$list = $articles->find('popular')->toArray();
$patched = $articles->patchEntities($list, $this->request->data());
foreach ($patched as $entity) {
$articles->save($entity);
}
Similarly to using patchEntity(), you can use the third argument for controlling the associations that
will be merged in each of the entities in the array:
// In a controller.
$patched = $articles->patchEntities(
$list,
$this->request->data(),
['associated' => ['Tags', 'Comments.Users']]
);
The $data parameter is an ArrayObject instance, so you dont have to return it to change the data used
to create entities.
The main purpose of beforeMarshal is to assist the users to pass the validation process when simple
mistakes can be automatically resolved, or when data needs to be restructured so it can be put into the right
fields.
The Model.beforeMarshal event is triggered just at the start of the validation process, one of the
reasons is that beforeMarshal is allowed to change the validation rules and the saving options, such as
the field whitelist. Validation is triggered just after this event is finished. A common example of changing
the data before it is validated is trimming all fields before saving:
468
Because of how the marshalling process works, if a field does not pass validation it will automatically be
removed from the data array and not be copied into the entity. This is to prevent inconsistent data from
entering the entity object.
Moreover, the data in beforeMarshal is a copy of the passed data. This is because it is important to
preserve the original user input, as it may be used elsewhere.
Validating Data Before Building Entities
The Validating Data chapter has more information on how to use the validation features of CakePHP to
ensure your data stays correct and consistent.
Avoiding Property Mass Assignment Attacks
When creating or merging entities from request data you need to be careful of what you allow your users to
change or add in the entities. For example, by sending an array in the request containing the user_id an
attacker could change the owner of an article, causing undesirable effects:
// Contains ['user_id' => 100, 'title' => 'Hacked!'];
$data = $this->request->data;
$entity = $this->patchEntity($entity, $data);
$this->save($entity);
There are two ways of protecting you against this problem. The first one is by setting the default columns
that can be safely set from a request using the Mass Assignment feature in the entities.
The second way is by using the fieldList option when creating or merging data into an entity:
// Contains ['user_id' => 100, 'title' => 'Hacked!'];
$data = $this->request->data;
// Only allow title to be changed
$entity = $this->patchEntity($entity, $data, [
'fieldList' => ['title']
More Information
469
]);
$this->save($entity);
You can also control which properties can be assigned for associations:
// Only allow changing the title and tags
// and the tag name is the only column that can be set
$entity = $this->patchEntity($entity, $data, [
'fieldList' => ['title', 'tags'],
'associated' => ['Tags' => ['fieldList' => ['name']]]
]);
$this->save($entity);
Using this feature is handy when you have many different functions your users can access and you want to
let your users edit different data based on their privileges.
The fieldList options is also accepted by the newEntity(),
patchEntities() methods.
newEntities() and
Saving Entities
Cake\ORM\Table::save(Entity $entity, array $options =[])
When saving request data to your database you need to first hydrate a new entity using newEntity() for
passing into save(). For example:
// In a controller
$articles = TableRegistry::get('Articles');
$article = $articles->newEntity($this->request->data);
if ($articles->save($article)) {
// ...
}
The ORM uses the isNew() method on an entity to determine whether or not an insert or update should be
performed. If the isNew() method returns true and the entity has a primary key value, an exists query
will be issued. The exists query can be suppressed by passing 'checkExisting' => false in the
$options argument:
$articles->save($article, ['checkExisting' => false]);
Once youve loaded some entities youll probably want to modify them and update your database. This is a
pretty simple exercise in CakePHP:
$articles = TableRegistry::get('Articles');
$article = $articles->find('all')->where(['id' => 2])->first();
$article->title = 'My new title';
$articles->save($article);
When saving, CakePHP will apply your rules, and wrap the save operation in a database transaction. It will
also only update properties that have changed. The above save() call would generate SQL like:
470
following
diagram
illustrates
the
above
process:
See
the
Applying
Application Rules
section for
more information on
creating and
using rules.
Warning:
If
no
changes
are
made
to
the
entity
More Information
471
when it
is saved,
the callbacks
will
not fire
because
no save
is performed.
The
save()
method will
return the
modified
entity
on
success, and
false on
failure. You
can disable
rules and/or
transactions
using
the
$options
argument
for save:
472
Saving
Associations
When you
are saving
an
entity,
you
can
also elect to
save some
or all of the
associated
entities. By
default all
first
level
entities will
be
saved.
For example
saving
an
Article,
will
also
automatically update
any
dirty
entities that
are directly
related
to
articles
table.
You can fine tune which associations are saved by using the associated option:
// In a controller.
// Only save the comments association
$articles->save($entity, ['associated' => ['Comments']]);
You can define save distant or deeply nested associations by using dot notation:
// Save the company, the employees and related addresses for each of them.
$companies->save($entity, ['associated' => ['Employees.Addresses']]);
Moreover, you can combine the dot notation for associations with the options array:
$companies->save($entity, [
'associated' => [
'Employees',
More Information
473
'Employees.Addresses'
]
]);
Your entities should be structured in the same way as they are when loaded from the database. See the form
helper documentation for how to build inputs for associations.
If you are building or modifying association data after building your entities you will have to mark the
association property as modified with dirty():
$company->author->name = 'Master Chef';
$company->dirty('author', true);
474
]);
$users->save($user);
When saving hasMany associations, associated records will either be updated, or inserted. For the case that
the record already has associated records in the database, you have the choice between two saving strategies:
append Associated records are updated in the database or, if not matching any existing record, inserted.
replace Any existing records that do not match the records provided will be deleted from the database.
Only provided records will remain (or be inserted).
By default the append strategy is used.
Whenever you add new records into an existing association you should always mark the association property
as dirty. This lets the ORM know that the association property has to be persisted:
$article->comments[] = $comment;
$article->dirty('comments', true);
Without the call to dirty() the updated comments will not be saved.
Saving BelongsToMany Associations
When saving belongsToMany associations, the ORM expects an array of entities at the plural, underscored
version of the association name. For example:
// In a controller.
$data = [
'title' => 'First Post',
'tags' => [
['tag' => 'CakePHP'],
More Information
475
When converting request data into entities, the newEntity() and newEntities() methods will handle
both arrays of properties, as well as a list of ids at the _ids key. Using the _ids key makes it easy to build
a select box or checkbox based form controls for belongs to many associations. See the Converting Request
Data into Entities section for more information.
When saving belongsToMany associations, you have the choice between two saving strategies:
append Only new links will be created between each side of this association. This strategy will not destroy
existing links even though they may not be present in the array of entities to be saved.
replace When saving, existing links will be removed and new links will be created in the junction table. If
there are existing link in the database to some of the entities intended to be saved, those links will be
updated, not deleted and then re-saved.
By default the replace strategy is used. Whenever you add new records into an existing association you
should always mark the association property as dirty. This lets the ORM know that the association property
has to be persisted:
$article->tags[] = $tag;
$article->dirty('tags', true);
Without the call to dirty() the updated tags will not be saved.
Often youll find yourself wanting to make an association between two existing entities, eg. a user coauthoring an article. This is done by using the method link(), like this:
$article = $this->Articles->get($articleId);
$user = $this->Users->get($userId);
$this->Articles->Users->link($article, [$user]);
When saving belongsToMany Associations, it can be relevant to save some additional data to the junction
Table. In the previous example of tags, it could be the vote_type of person who voted on that article. The
vote_type can be either upvote or downvote and is represented by a string. The relation is between
Users and Articles.
Saving that association, and the vote_type is done by first adding some data to _joinData and then
saving the association with link(), example:
$article = $this->Articles->get($articleId);
$user = $this->Users->get($userId);
$user->_joinData = new Entity(['vote_type' => $voteType], ['markNew' =>
true]);
476
$this->Articles->Users->link($article, [$user]);
When saving data you can populate the additional columns on the junction table by setting data to the
_joinData property:
$student->courses[0]->_joinData->grade = 80.12;
$student->courses[0]->_joinData->days_attended = 30;
$studentsTable->save($student);
The _joinData property can be either an entity, or an array of data if you are saving entities built from
request data. When saving junction table data from request data your POST data should look like:
$data = [
'first_name' => 'Sally',
'last_name' => 'Parker',
'courses' => [
[
'id' => 10,
'_joinData' => [
'grade' => 80.12,
'days_attended' => 30
]
],
// Other courses.
]
];
$student = $this->Students->newEntity($data, [
'associated' => ['Courses._joinData']
]);
See the Creating Inputs for Associated Data documentation for how to build inputs with FormHelper
correctly.
Saving Complex Types
Tables are capable of storing data represented in basic types, like strings, integers, floats, booleans, etc. But
It can also be extended to accept more complex types such as arrays or objects and serialize this data into
More Information
477
The code above maps the preferences column to the json custom type. This means that when retrieving data for that column, it will be unserialized from a JSON string in the database and put into an entity as
an array.
Likewise, when saved, the array will be transformed back into its JSON representation:
$user = new User([
'preferences' => [
'sports' => ['football', 'baseball'],
'books' => ['Mastering PHP', 'Hamlet']
]
]);
$usersTable->save($user);
When using complex types it is important to validate that the data you are receiving from the end user is
the correct type. Failing to correctly handle complex data could result in malicious users being able to store
data they would not normally be able to.
Saving Multiple Entities
Cake\ORM\Table::saveMany($entities, $options =[])
Using this method you can save multiple entities atomically. $entites can be an array of entities created
using newEntities() / patchEntities(). $options can have the same options as accepted by
save():
$data = [
[
'title' => 'First post',
478
'published' => 1
],
[
'title' => 'Second post',
'published' => 1
],
];
$articles = TableRegistry::get('Articles');
$entities = $articles->newEntities($data);
$result = $articles->saveMany($entities);
If you need to do bulk updates and use SQL expressions, you will need to use an expression object as
updateAll() uses prepared statements under the hood:
use Cake\Database\Expression\QueryExpression;
...
function incrementCounters()
{
$expression = new QueryExpression('view_count = view_count + 1');
$this->updateAll([$expression], ['published' => true]);
}
More Information
479
Deleting Data
class Cake\ORM\Table
Cake\ORM\Table::delete(Entity $entity, $options =[])
Once youve loaded an entity you can delete it by calling the originating tables delete method:
// In a controller.
$entity = $this->Articles->get(2);
$result = $this->Articles->delete($entity);
Cascading Deletes
When deleting entities, associated data can also be deleted. If your HasOne and HasMany associations are
configured as dependent, delete operations will cascade to those entities as well. By default entities
in associated tables are removed using Cake\ORM\Table::deleteAll(). You can elect to have the
ORM load related entities, and delete them individually by setting the cascadeCallbacks option to
true. A sample HasMany association with both these options enabled would be:
480
Note: Setting cascadeCallbacks to true, results in considerably slower deletes when compared to
bulk deletes. The cascadeCallbacks option should only be enabled when your application has important
work handled by event listeners.
Bulk Deletes
Cake\ORM\Table::deleteAll($conditions)
There may be times when deleting rows one by one is not efficient or useful. In these cases it is more
performant to use a bulk-delete to remove many rows at once:
// Delete all the spam
function destroySpam()
{
return $this->deleteAll(['is_spam' => true]);
}
Association Type
hasOne
hasMany
belongsTo
belongsToMany
Example
A user has one profile.
A user can have multiple articles.
Many articles belong to a user.
Tags belong to many articles.
Associations are defined during the initialize() method of your table object. Methods matching the
association type allow you to define the associations in your application. For example if we wanted to define
a belongsTo association in our ArticlesTable:
More Information
481
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->belongsTo('Authors');
}
}
The simplest form of any association setup takes the table alias you want to associate with. By default
all of the details of an association will use the CakePHP conventions. If you want to customize how your
associations are handled you can do so with the second parameter:
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->belongsTo('Authors', [
'className' => 'Publishing.Authors',
'foreignKey' => 'authorid',
'propertyName' => 'person'
]);
}
}
The same table can be used multiple times to define different types of associations. For example consider a
case where you want to separate approved comments and those that have not been moderated yet:
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->hasMany('Comments', [
'className' => 'Comments',
'conditions' => ['approved' => true]
]);
$this->hasMany('UnapprovedComments', [
'className' => 'Comments',
'conditions' => ['approved' => false],
'propertyName' => 'unapproved_comments'
]);
}
}
482
As you can see, by specifying the className key, it is possible to use the same table as different associations for the same table. You can even create self-associated tables to create parent-child relationships:
class CategoriesTable extends Table
{
public function initialize(array $config)
{
$this->hasMany('SubCategories', [
'className' => 'Categories',
]);
$this->belongsTo('ParentCategories', [
'className' => 'Categories',
]);
}
}
You can also setup associations in mass by making a single call to Table::addAssociations()
which accepts an array containing a set of table names indexed by association type as an argument:
class PostsTable extends Table
{
public function initialize(array $config)
{
$this->addAssociations([
'belongsTo' => [
'Users' => ['className' => 'App\Model\Table\UsersTable']
],
'hasMany' => ['Comments'],
'belongsToMany' => ['Tags']
]);
}
}
Each association type accepts multiple associations where the keys are the aliases, and the values are association config data. If numeric keys are used the values will be treated as association aliases.
HasOne Associations
Lets set up a Users Table with a hasOne relationship to an Addresses Table.
First, your database tables need to be keyed correctly. For a hasOne relationship to work, one table has to
contain a foreign key that points to a record in the other. In this case the addresses table will contain a field
called user_id. The basic pattern is:
hasOne: the other model contains the foreign key.
More Information
483
Relation
Users hasOne Addresses
Doctors hasOne Mentors
Schema
addresses.user_id
mentors.doctor_id
Note: It is not mandatory to follow CakePHP conventions, you can override the use of any foreignKey
in your associations definitions. Nevertheless sticking to conventions will make your code less repetitive,
easier to read and to maintain.
If we had the UsersTable and AddressesTable classes made we could make the association with the
following code:
class UsersTable extends Table
{
public function initialize(array $config)
{
$this->hasOne('Addresses');
}
}
If you need more control, you can define your associations using array syntax. For example, you might want
to limit the association to include only certain records:
class UsersTable extends Table
{
public function initialize(array $config)
{
$this->hasOne('Addresses', [
'className' => 'Addresses',
'conditions' => ['Addresses.primary' => '1'],
'dependent' => true
]);
}
}
484
dependent: When the dependent key is set to true, and an entity is deleted, the associated model
records are also deleted. In this case we set it to true so that deleting a User will also delete her
associated Address.
cascadeCallbacks: When this and dependent are true, cascaded deletes will load and delete entities
so that callbacks are properly triggered. When false, deleteAll() is used to remove associated
data and no callbacks are triggered.
propertyName: The property name that should be filled with data from the associated table into
the source table results. By default this is the underscored & singular name of the association so
address in our example.
strategy: Defines the query strategy to use. Defaults to join. The other valid value is select, which
utilizes sub-queries instead.
finder: The finder method to use when loading associated records.
Once this association has been defined, find operations on the Users table can contain the Address record if
it exists:
// In a controller or table method.
$query = $users->find('all')->contain(['Addresses']);
foreach ($query as $user) {
echo $user->address->street;
}
BelongsTo Associations
Now that we have Address data access from the User table, lets define a belongsTo association in the Addresses table in order to get access to related User data. The belongsTo association is a natural complement
to the hasOne and hasMany associations.
When keying your database tables for a belongsTo relationship, follow this convention:
belongsTo: the current model contains the foreign key.
Relation
Addresses belongsTo Users
Mentors belongsTo Doctors
Schema
addresses.user_id
mentors.doctor_id
More Information
485
{
$this->belongsTo('Users');
}
}
486
HasMany Associations
An example of a hasMany association is Article hasMany Comments. Defining this association will allow
us to fetch an articles comments when the article is loaded.
When creating your database tables for a hasMany relationship, follow this convention:
hasMany: the other model contains the foreign key.
Relation
Article hasMany Comment
Product hasMany Option
Doctor hasMany Patient
Schema
Comment.article_id
Option.product_id
Patient.doctor_id
More Information
487
Relying on the example above, we have passed an array containing the desired composite keys to
foreignKey. By default the bindingKey would be automatically defined as id and hash respectively, but lets assume that you need to specify different binding fields than the defaults, you can setup it
manually in your bindingKeys array:
// Within ArticlesTable::initialize() call
$this->hasMany('Reviews', [
'foreignKey' => [
'article_id',
'article_hash'
],
'bindingKey' => [
'whatever_id',
'whatever_hash'
]
]);
It is important to note that foreignKey values refers to the reviews table and bindingKey values refers
to the articles table.
Possible keys for hasMany association arrays include:
className: the class name of the model being associated to the current model. If youre defining a
User hasMany Comment relationship, the className key should equal Comments.
foreignKey: the name of the foreign key found in the other table. This is especially handy if you need
to define multiple hasMany relationships. The default value for this key is the underscored, singular
name of the actual model, suffixed with _id.
bindingKey: The name of the column in the current table, that will be used for matching the
foreignKey. If not specified, the primary key (for example the id column of the Articles
table) will be used.
conditions:
an array of find() compatible
['Comments.visible' => true]
conditions
or
SQL
strings
such
as
sort an array of find() compatible order clauses or SQL strings such as ['Comments.created'
=> 'ASC']
dependent: When dependent is set to true, recursive model deletion is possible. In this example,
Comment records will be deleted when their associated Article record has been deleted.
cascadeCallbacks: When this and dependent are true, cascaded deletes will load and delete entities
so that callbacks are properly triggered. When false, deleteAll() is used to remove associated
data and no callbacks are triggered.
propertyName: The property name that should be filled with data from the associated table into the
source table results. By default this is the underscored & plural name of the association so comments
in our example.
strategy: Defines the query strategy to use. Defaults to select. The other valid value is subquery,
which replaces the IN list with an equivalent subquery.
saveStrategy: Either append or replace. When append the current records are appended to any
records in the database. When replace associated records not in the current set will be removed. If
488
the foreign key is a null able column or if dependent is true records will be orphaned.
finder: The finder method to use when loading associated records.
Once this association has been defined, find operations on the Articles table can contain the Comment
records if they exist:
// In a controller or table method.
$query = $articles->find('all')->contain(['Comments']);
foreach ($query as $article) {
echo $article->comments[0]->text;
}
When the subquery strategy is used, SQL similar to the following will be generated:
SELECT * FROM articles;
SELECT * FROM comments WHERE article_id IN (SELECT id FROM articles);
You may want to cache the counts for your hasMany associations. This is useful when you often need
to show the number of associated records, but dont want to load all the records just to count them. For
example, the comment count on any given article is often cached to make generating lists of articles more
efficient. You can use the CounterCacheBehavior to cache counts of associated records.
You should make sure that your database tables do not contain columns that match association property
names. If for example you have counter fields that conflict with association properties, you must either
rename the association property, or the column name.
BelongsToMany Associations
An example of a BelongsToMany association is Article BelongsToMany Tags, where the tags from one
article are shared with other articles. BelongsToMany is often referred to as has and belongs to many, and
is a classic many to many association.
The main difference between hasMany and BelongsToMany is that the link between the models in a BelongsToMany association are not exclusive. For example, we are joining our Articles table with a Tags
table. Using funny as a Tag for my Article, doesnt use up the tag. I can also use it on the next article I
write.
Three database tables are required for a BelongsToMany association. In the example above we would need
tables for articles, tags and articles_tags. The articles_tags table contains the data that
links tags and articles together. The joining table is named after the two tables involved, separated with an
underscore by convention. In its simplest form, this table consists of article_id and tag_id.
belongsToMany requires a separate join table that includes both model names.
More Information
489
Relationship
Article belongsToMany Tag
Patient belongsToMany
Doctor
490
through Allows you to provide a either the name of the Table instance you want used on the join
table, or the instance itself. This makes customizing the join table keys possible, and allows you to
customize the behavior of the pivot table.
cascadeCallbacks: When this is true, cascaded deletes will load and delete entities so that callbacks are properly triggered on join table records. When false, deleteAll() is used to remove
associated data and no callbacks are triggered. This defaults to false to help reduce overhead.
propertyName: The property name that should be filled with data from the associated table into the
source table results. By default this is the underscored & plural name of the association, so tags in
our example.
strategy: Defines the query strategy to use. Defaults to select. The other valid value is subquery,
which replaces the IN list with an equivalent subquery.
saveStrategy: Either append or replace. Defaults to replace. Indicates the mode to be used for
saving associated entities. The former will only create new links between both side of the relation and
the latter will do a wipe and replace to create the links between the passed entities when saving.
finder: The finder method to use when loading associated records.
Once this association has been defined, find operations on the Articles table can contain the Tag records if
they exist:
// In a controller or table method.
$query = $articles->find('all')->contain(['Tags']);
foreach ($query as $article) {
echo $article->tags[0]->text;
}
When the subquery strategy is used, SQL similar to the following will be generated:
SELECT * FROM articles;
SELECT * FROM tags
INNER JOIN articles_tags ON (
tags.id = article_tags.tag_id
AND article_id IN (SELECT id FROM articles)
);
491
A Student can take many Courses and a Course can be taken by many Students. This is a simple many to
many association. The following table would suffice:
id | student_id | course_id
Now what if we want to store the number of days that were attended by the student on the course and their
final grade? The table wed want would be:
id | student_id | course_id | days_attended | grade
The way to implement our requirement is to use a join model, otherwise known as a hasMany through
association. That is, the association is a model itself. So, we can create a new model CoursesMemberships.
Take a look at the following models.
class StudentsTable extends Table
{
public function initialize(array $config)
{
$this->belongsToMany('Courses', [
'through' => 'CourseMemberships',
]);
}
}
class CoursesTable extends Table
{
public function initialize(array $config)
{
$this->belongsToMany('Students', [
'through' => 'CourseMemberships',
]);
}
}
class CoursesMembershipsTable extends Table
{
public function initialize(array $config)
{
$this->belongsTo('Students');
$this->belongsTo('Courses');
}
}
The CoursesMemberships join table uniquely identifies a given Students participation on a Course in addition to extra meta-information.
492
Behaviors
Behaviors are a way to organize and enable horizontal re-use of Model layer logic. Conceptually they are
similar to traits. However, behaviors are implemented as separate classes. This allows them to hook into the
life-cycle callbacks that models emit, while providing trait-like features.
Behaviors provide a convenient way to package up behavior that is common across many models. For
example, CakePHP includes a TimestampBehavior. Many models will want timestamp fields, and the
logic to manage these fields is not specific to any one model. It is these kinds of scenarios that behaviors are
a perfect fit for.
Using Behaviors
Behaviors provide an easy way to create horizontally re-usable pieces of logic related to table classes. You
may be wondering why behaviors are regular classes and not traits. The primary reason for this is event
listeners. While traits would allow for re-usable pieces of logic, they would complicate binding events.
To add a behavior to your table you can call the addBehavior() method. Generally the best place to do
this is in the initialize() method:
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table
{
public function initialize(array $config)
{
More Information
493
$this->addBehavior('Timestamp');
}
}
As with associations, you can use plugin syntax and provide additional configuration options:
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Timestamp', [
'events' => [
'Model.beforeSave' => [
'created_at' => 'new',
'modified_at' => 'always'
]
]
]);
}
}
Core Behaviors
CounterCache
class Cake\ORM\Behavior\CounterCacheBehavior
Often times web applications need to display counts of related objects. For example, when showing a list
of articles you may want to display how many comments it has. Or when showing a user you might want
to show how many friends/followers she has. The CounterCache behavior is intended for these situations.
CounterCache will update a field in the associated models assigned in the options when it is invoked. The
fields should exist in the database and be of the type INT.
Basic Usage
You enable the CounterCache behavior like any other behavior, but it wont do anything until you configure
some relations and the field counts that should be stored on each of them. Using our example below, we
could cache the comment count for each article with the following:
class CommentsTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('CounterCache', [
'Articles' => ['comment_count']
494
]);
}
}
The CounterCache configuration should be a map of relation names and the specific configuration for that
relation.
The counters value will be updated each time an entity is saved or deleted. The counter will not be updated
when you use updateAll() or deleteAll(), or execute SQL you have written.
Advanced Usage
If you need to keep a cached counter for less than all of the related records, you can supply additional
conditions or finder methods to generate a counter value:
// Use a specific find method.
// In this case find(published)
$this->addBehavior('CounterCache', [
'Articles' => [
'comment_count' => [
'finder' => 'published'
]
]
]);
If you dont have a custom finder method you can provide an array of conditions to find records instead:
$this->addBehavior('CounterCache', [
'Articles' => [
'comment_count' => [
'conditions' => ['Comments.spam' => false]
]
]
]);
If you want CounterCache to update multiple fields, for example both showing a conditional count and a
basic count you can add these fields in the array:
$this->addBehavior('CounterCache', [
'Articles' => ['comment_count',
'published_comment_count' => [
'finder' => 'published'
]
]
]);
Lastly, if a custom finder and conditions are not suitable you can provide a callback method. This callable
must return the count value to be stored:
More Information
495
$this->addBehavior('CounterCache', [
'Articles' => [
'rating_avg' => function ($event, $entity, $table) {
return 4.5;
}
]
]);
Note: The CounterCache behavior works for belongsTo associations only. For example for Comments
belongsTo Articles, you need to add the CounterCache behavior to the CommentsTable in order to
generate comment_count for Articles table.
It is possible though to make this work for belongsToMany associations. You need to enable the CounterCache behavior in a custom through table configured in association options. See how to configure a
custom join table Using the through Option.
Timestamp
class Cake\ORM\Behavior\TimestampBehavior
The timestamp behavior allows your table objects to update one or more timestamps on each model event.
This is primarily used to populate data into created and modified fields. However, with some additional configuration, you can update any timestamp/datetime column on any event a table publishes.
Basic Usage
You enable the timestamp behavior like any other behavior:
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Timestamp');
}
}
As you can see above, in addition to the standard Model.beforeSave event, we are also updating the
completed_at column when orders are completed.
Updating Timestamps on Entities
Sometimes youll want to update just the timestamps on an entity without changing any other properties.
This is sometimes referred to as touching a record. In CakePHP you can use the touch() method to do
exactly this:
// Touch based on the Model.beforeSave event.
$articles->touch($article);
// Touch based on a specific event.
$orders->touch($order, 'Orders.completed');
More Information
497
Translate
class Cake\ORM\Behavior\TranslateBehavior
The Translate behavior allows you to create and retrieve translated copies of your entities in multiple languages. It does so by using a separate i18n table where it stores the translation for each of the fields of any
given Table object that its bound to.
Warning: The TranslateBehavior does not support composite primary keys at this point in time.
A Quick Tour
After creating the i18n table in your database attach the behavior to any Table object you want to make
translatable:
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Translate', ['fields' => ['title']]);
}
}
Now, select a language to be used for retrieving entities by changing the application language, which will
affect all translations:
// In a controller. Change the locale, e.g. to Spanish
I18n::locale('es');
$this->loadModel('Articles');
Working with multiple translations can be done by using a special trait in your Entity class:
use Cake\ORM\Behavior\Translate\TranslateTrait;
use Cake\ORM\Entity;
498
If you want to go deeper on how it works or how to tune the behavior for your needs, keep on reading the
rest of this chapter.
Initializing the i18n Database Table
In order to use the behavior, you need to create a i18n table with the correct schema. Currently the only
way of loading the i18n table is by manually running the following SQL script in your database:
CREATE TABLE i18n (
id int NOT NULL auto_increment,
locale varchar(6) NOT NULL,
model varchar(255) NOT NULL,
foreign_key int(10) NOT NULL,
field varchar(255) NOT NULL,
content text,
PRIMARY KEY
(id),
UNIQUE INDEX I18N_LOCALE_FIELD(locale, model, foreign_key, field),
INDEX I18N_FIELD(model, foreign_key, field)
);
https://en.wikipedia.org/wiki/UN_M.49
More Information
499
The first thing to note is that you are required to pass the fields key in the configuration array. This list
of fields is needed to tell the behavior what columns will be able to store translations.
Using a Separate Translations Table
If you wish to use a table other than i18n for translating a particular repository, you can specify it in the
behaviors configuration. This is common when you have multiple tables to translate and you want a cleaner
separation of the data that is stored for each different table:
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Translate', [
'fields' => ['title', 'body'],
'translationTable' => 'ArticlesI18n'
]);
}
}
You need to make sure that any custom table you use has the columns field, foreign_key, locale
and model.
Reading Translated Content
As shown above you can use the locale() method to choose the active translation for entities that are
loaded:
500
This method works with any finder in your tables. For example, you can use TranslateBehavior with
find('list'):
I18n::locale('es');
$data = $this->Articles->find('list')->toArray();
// Data will contain
[1 => 'Mi primer artculo', 2 => 'El segundo artculo', 15 => 'Otro articulo'
...]
In the example above you will get a list of entities back that have a _translations property set. This
property will contain a list of translation data entities. For example the following properties would be
accessible:
// Outputs 'en'
echo $article->_translations['en']->locale;
// Outputs 'title'
echo $article->_translations['en']->field;
// Outputs 'My awesome post!'
echo $article->_translations['en']->body;
A more elegant way for dealing with this data is by adding a trait to the entity class that is used for your
table:
use Cake\ORM\Behavior\Translate\TranslateTrait;
use Cake\ORM\Entity;
class Article extends Entity
{
More Information
501
use TranslateTrait;
}
This trait contains a single method called translation, which lets you access or create new translation
entities on the fly:
// Outputs 'title'
echo $article->translation('en')->title;
// Adds a new translation data entity to the article
$article->translation('de')->title = 'Wunderbar';
The above would only load translated data that had content.
Retrieving All Translations For Associations
It is also possible to find translations for any association in a single find operation:
502
$article = $this->Articles->find('translations')->contain([
'Categories' => function ($query) {
return $query->find('translations');
}
])->first();
// Outputs 'Programacin'
echo $article->categories[0]->translation('es')->name;
This assumes that Categories has the TranslateBehavior attached to it. It simply uses the query builder
function for the contain clause to use the translations custom finder in the association.
Retrieving one language without using I18n::locale
calling I18n::locale('es'); changes the default locale for all translated finds, there may be times
you wish to retrieve translated content without modifying the applications state. For these scenarios use the
behavior locale() method:
I18n::locale('en'); // reset for illustration
$this->loadModel('Articles');
$this->Articles->locale('es'); // specific locale
$article = $this->Articles->get(12);
echo $article->title; // Echoes 'Un Artculo', yay piece of cake!
Note that this only changes the locale of the Articles table, it would not affect the langauge of associated
data. To affect associated data its necessary to call locale on each table for example:
I18n::locale('en'); // reset for illustration
$this->loadModel('Articles');
$this->Articles->locale('es');
$this->Articles->Categories->locale('es');
$data = $this->Articles->find('all', ['contain' => ['Categories']]);
This example also assumes that Categories has the TranslateBehavior attached to it.
Saving in Another Language
The philosophy behind the TranslateBehavior is that you have an entity representing the default language,
and multiple translations that can override certain fields in such entity. Keeping this in mind, you can
intuitively save translations for any given entity. For example, given the following setup:
// in src/Model/Table/ArticlesTable.php
class ArticlesTable extends Table
{
public function initialize(array $config)
More Information
503
{
$this->addBehavior('Translate', ['fields' => ['title', 'body']]);
}
}
// in src/Model/Entity/Article.php
class Article extends Entity
{
use TranslateTrait;
}
// In a Controller
$articles = $this->loadModel('Articles');
$article = new Article([
'title' => 'My First Article',
'body' => 'This is the content',
'footnote' => 'Some afterwords'
]);
$this->Articles->save($article);
So, after you save your first article, you can now save a translation for it, there are a couple ways to do it.
The first one is setting the language directly into the entity:
$article->_locale = 'es';
$article->title = 'Mi primer Artculo';
$this->Articles->save($article);
After the entity has been saved, the translated field will be persisted as well, one thing to note is that values
from the default language that were not overridden will be preserved:
// Outputs 'This is the content'
echo $article->body;
// Outputs 'Mi primer Artculo'
echo $article->title;
Once you override the value, the translation for that field will be saved and can be retrieved as usual:
$article->body = 'El contendio';
$this->Articles->save($article);
The second way to use for saving entities in another language is to set the default language directly to the
table:
I18n::locale('es');
$article->title = 'Mi Primer Artculo';
$this->Articles->save($article);
Setting the language directly in the table is useful when you need to both retrieve and save entities for the
same language or when you need to save multiple entities at once.
504
As of 3.3.0, working with multiple translations has been streamlined. You can create form inputs for your
translated fields:
// In a view template.
<?= $this->Form->create($article); ?>
<fieldset>
<legend>French</legend>
<?= $this->Form->input('_translations.fr.title'); ?>
<?= $this->Form->input('_translations.fr.body'); ?>
</fieldset>
<fieldset>
<legend>Spanish</legend>
<?= $this->Form->input('_translations.es.title'); ?>
<?= $this->Form->input('_translations.es.body'); ?>
</fieldset>
In your controller, you can marshal the data as normal, but with the translations option enabled:
$article = $this->Articles->newEntity($this->request->data, [
'translations' => true
]);
$this->Articles->save($article);
This will result in your article, the french and spanish translations all being persisted. Youll need to remember to add _translations into the $_accessible fields of your entity as well.
More Information
505
The above will use the validator created by validationTranslated to validated translated entities.
New in version 3.3.0: Validating translated entities, and streamlined translation saving was added in 3.3.0
Tree
class Cake\ORM\Behavior\TreeBehavior
Its fairly common to want to store hierarchical data in a database table. Examples of such data might be
categories with unlimited subcategories, data related to a multilevel menu system or a literal representation
of hierarchy such as departments in a company.
Relational databases are usually not well suited for storing and retrieving this type of data, but there are a
few known techniques that can make them effective for working with multi-level information.
The TreeBehavior helps you maintain a hierarchical data structure in the database that can be queried without
much overhead and helps reconstruct the tree data for finding and displaying processes.
Requirements
This behavior requires the following columns in your table:
parent_id (nullable) The column holding the ID of the parent row
lft (integer, signed) Used to maintain the tree structure
rght (integer, signed) Used to maintain the tree structure
You can configure the name of those fields should you need to customize them. More information on the
meaning of the fields and how they are used can be found in this article describing the MPTT logic117
117
506
http://www.sitepoint.com/hierarchical-data-database-2/
Warning: The TreeBehavior does not support composite primary keys at this point in time.
A Quick Tour
You enable the Tree behavior by adding it to the Table you want to store hierarchical data in:
class CategoriesTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Tree');
}
}
Once added, you can let CakePHP build the internal structure if the table is already holding some rows:
$categories = TableRegistry::get('Categories');
$categories->recover();
You can verify it works by getting any row from the table and asking for the count of descendants it has:
$node = $categories->get(1);
echo $categories->childCount($node);
If you instead need a threaded list, where children for each node are nested in a hierarchy, you can stack the
threaded finder:
$children = $categories
->find('children', ['for' => 1])
->find('threaded')
->toArray();
foreach ($children as $child) {
echo "{$child->name} has " . count($child->children) . " direct children";
}
Traversing threaded results usually requires recursive functions in, but if you only require a result set containing a single field from each level so you can display a list, in an HTML select for example, it is better to
use the treeList finder:
More Information
507
$list = $categories->find('treeList');
// In a CakePHP template file:
echo $this->Form->input('categories', ['options' => $list]);
// Or you can output it in plain text, for example in a CLI script
foreach ($list as $categoryName) {
echo $categoryName . "\n";
}
One very common task is to find the tree path from a particular node to the root of the tree. This is useful,
for example, for adding the breadcrumbs list for a menu structure:
$nodeId = 5;
$crumbs = $categories->find('path', ['for' => $nodeId]);
foreach ($crumbs as $crumb) {
echo $crumb->name . ' > ';
}
Trees constructed with the TreeBehavior cannot be sorted by any column other than lft, this is because
the internal representation of the tree depends on this sorting. Luckily, you can reorder the nodes inside the
same level without having to change their parent:
508
$node = $categories->get(5);
// Move the node so it shows up one position up when listing children.
$categories->moveUp($node);
// Move the node to the top of the list inside the same level.
$categories->moveUp($node, true);
// Move the node to the bottom.
$categories->moveDown($node, true);
Configuration
If the default column names that are used by this behavior dont match your own schema, you can provide
aliases for them:
public function initialize(array $config)
{
$this->addBehavior('Tree', [
'parent' => 'ancestor_id', // Use this instead of parent_id
'left' => 'tree_left', // Use this instead of lft
'right' => 'tree_right' // Use this instead of rght
]);
}
If you dont want to cache the level using a db field you can use TreeBehavior::getLevel() method
to get level of a node.
Scoping and Multi Trees
Sometimes you want to persist more than one tree structure inside the same table, you can achieve that
by using the scope configuration. For example, in a locations table you may want to create one tree per
country:
class LocationsTable extends Table
{
More Information
509
In the previous example, all tree operations will be scoped to only the rows having the column
country_name set to Brazil. You can change the scoping on the fly by using the config function:
$this->behaviors()->Tree->config('scope', ['country_name' => 'France']);
Optionally, you can have a finer grain control of the scope by passing a closure as the scope:
$this->behaviors()->Tree->config('scope', function ($query) {
$country = $this->getConfigureContry(); // A made-up function
return $query->where(['country_name' => $country]);
});
Providing inexistent parent ids when saving or attempting to create a loop in the tree (making a node child
of itself) will throw an exception.
You can make a node a root in the tree by setting the parent_id column to null:
510
$aCategory = $categoriesTable->get(10);
$aCategory->parent_id = null;
$categoriesTable->save($aCategory);
The TreeBehavior will take care of all internal deleting operations for you. It is also possible to only delete
one node and re-assign all children to the immediately superior parent node in the tree:
$aCategory = $categoriesTable->get(10);
$categoriesTable->removeFromTree($aCategory);
$categoriesTable->delete($aCategory);
All children nodes will be kept and a new parent will be assigned to them.
The deletion of a node is based off of the lft and rght values of the entity. This is important to note when
looping through the various children of a node for conditional deletes:
$descendants = $teams->find('children', ['for' => 1]);
foreach ($descendants as $descendant) {
$team = $teams->get($descendant->id); // search for the up-to-date entity
object
if ($team->expired) {
$teams->delete($team); // deletion reorders the lft and rght of
database entries
}
}
The TreeBehavior reorders the lft and rght values of records in the table when a node is deleted. As such,
the lft and rght values of the entities inside $descendants (saved before the delete operation) will be
inaccurate. Entities will have to be loaded and modified on the fly to prevent inconsistencies in the table.
Creating a Behavior
In the following examples we will create a very simple SluggableBehavior. This behavior will allow
us to populate a slug field with the results of Inflector::slug() based on another field.
Before we create our behavior we should understand the conventions for behaviors:
Behavior files are located in src/Model/Behavior, or MyPlugin\Model\Behavior.
More Information
511
App\Model\Behavior
namespace,
or
Similar to tables, behaviors also have an initialize() hook where you can put your behaviors initialization code, if required:
public function initialize(array $config)
{
// Some initialization code here
}
We can now add this behavior to one of our table classes. In this example well use an ArticlesTable,
as articles often have slug properties for creating friendly URLs:
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Sluggable');
}
}
Our new behavior doesnt do much of anything right now. Next, well add a mixin method and an event
listener so that when we save entities we can automatically slug a field.
Defining Mixin Methods
Any public method defined on a behavior will be added as a mixin method on the table object it is attached
to. If you attach two behaviors that provide the same methods an exception will be raised. If a behavior
provides the same method as a table class, the behavior method will not be callable from the table. Behavior
mixin methods will receive the exact same arguments that are provided to the table. For example, if our
SluggableBehavior defined the following method:
512
Applying this configuration will make slug() not callable, however it will add a superSlug() mixin
method to the table. Notably if our behavior implemented other public methods they would not be available
as mixin methods with the above configuration.
Since the exposed methods are decided by configuration you can also rename/remove mixin methods when
adding a behavior to a table. For example:
// In a table's initialize() method.
$this->addBehavior('Sluggable', [
'implementedMethods' => [
'superSlug' => 'slug',
]
]);
Cake\Datasource\EntityInterface;
Cake\Event\Event;
Cake\ORM\Behavior;
Cake\ORM\Entity;
Cake\ORM\Query;
More Information
513
use Cake\Utility\Inflector;
class SluggableBehavior extends Behavior
{
protected $_defaultConfig = [
'field' => 'title',
'slug' => 'slug',
'replacement' => '-',
];
public function slug(Entity $entity)
{
$config = $this->config();
$value = $entity->get($config['field']);
$entity->set($config['slug'], Inflector::slug($value, $config[
'replacement']));
}
public function beforeSave(Event $event, EntityInterface $entity)
{
$this->slug($entity);
}
}
Defining Finders
Now that we are able to save articles with slug values, we should implement a finder method so we can fetch
articles by their slug. Behavior finder methods, use the same conventions as Custom Finder Methods do.
Our find('slug') method would look like:
514
Once our behavior has the above method we can call it:
$article = $articles->find('slug', ['slug' => $value])->first();
Applying this configuration will make find('slug') trigger an error. However it will make
find('slugged') available. Notably if our behavior implemented other finder methods they would
not be available, as they are not included in the configuration.
Since the exposed methods are decided by configuration you can also rename/remove finder methods when
adding a behavior to a table. For example:
// In a table's initialize() method.
$this->addBehavior('Sluggable', [
'implementedFinders' => [
'slugged' => 'findSlug',
]
]);
More Information
515
}
];
}
The TranslateBehavior has a non-trivial implementation of this interface that you might want to refer
to.
New in version 3.3.0: The ability for behaviors to participate in marshalling was added in 3.3.0
Removing Loaded Behaviors
To remove a behavior from your table you can call the removeBehavior() method:
// Remove the loaded behavior
$this->removeBehavior('Sluggable');
the
the
For example if a parent (e.g. an AppTable) class loaded the Timestamp behavior you could do the
following to add, modify or remove the configurations for the behavior. In this case, we will add an event
we want Timestamp to respond to:
namespace App\Model\Table;
use App\Model\Table\AppTable; // similar to AppController
class UsersTable extends AppTable
{
516
Schema System
CakePHP features a schema system that is capable of reflecting and generating schema information for tables
in SQL datastores. The schema system can generate/reflect a schema for any SQL platform that CakePHP
supports.
The main pieces of the schema system are Cake\Database\Schema\Collection and
Cake\Database\Schema\Table. These classes give you access to database-wide and individual Table object features respectively.
The primary use of the schema system is for Fixtures. However, it can also be used in your application if
required.
Schema\Table Objects
class Cake\Database\Schema\Table
The schema subsystem provides a simple Table object to hold data about a table in a database. This object
is returned by the schema reflection features:
use Cake\Database\Schema\Table;
// Create a table one column at a time.
$t = new Table('posts');
$t->addColumn('id', [
'type' => 'integer',
'length' => 11,
'null' => false,
'default' => null,
])->addColumn('title', [
'type' => 'string',
'length' => 255,
More Information
517
Schema\Table objects allow you to build up information about a tables schema. It helps to normalize
and validate the data used to describe a table. For example, the following two forms are equivalent:
$t->addColumn('title', 'string');
// and
$t->addColumn('title', [
'type' => 'string'
]);
While equivalent, the 2nd form allows more detail and control. This emulates the existing features available
in Schema files + the fixture schema in 2.x.
Accessing Column Data
Columns are either added as constructor arguments, or via addColumn(). Once fields are added information
can be fetched using column() or columns():
// Get the array of data about a column
$c = $t->column('title');
// Get the list of all columns.
$cols = $t->columns();
518
If you add a primary key constraint to a single integer column it will automatically be converted into a
auto-increment/serial column depending on the database platform:
$t = new Table('posts');
$t->addColumn('id', 'integer')
->addConstraint('primary', [
'type' => 'primary',
'columns' => ['id']
]);
In the above example the id column would generate the following SQL in MySQL:
CREATE TABLE `posts` (
`id` INTEGER AUTO_INCREMENT,
PRIMARY KEY (`id`)
)
If your primary key contains more than one column, none of them will automatically be converted to an
auto-increment value. Instead you will need to tell the table object which column in the composite key you
want to auto-increment:
$t = new Table('posts');
$t->addColumn('id', [
'type' => 'integer',
'autoIncrement' => true,
])
->addColumn('account_id', 'integer')
->addConstraint('primary', [
'type' => 'primary',
'columns' => ['id', 'account_id']
More Information
519
]);
The autoIncrement option only works with integer and biginteger columns.
Reading Indexes and Constraints
Indexes and constraints can be read out of a table object using accessor methods. Assuming that $t is a
populated Table instance you could do the following:
// Get contraints. Will return the
// names of all constraints.
$constraints = $t->constraints()
// Get data about a single constraint.
$constraint = $t->constraint('author_id_idx')
// Get indexes. Will return the
// names of all indexes.
$indexes = $t->indexes()
// Get data about a single index.
$index = $t->index('author_id_idx')
Platform dialects only handle the keys they are interested in and ignore the rest. Not all options are supported
on all platforms.
Converting Tables into SQL
Using the createSql() or dropSql() you can get platform specific SQL for creating or dropping a
specific table:
$db = ConnectionManager::get('default');
$schema = new Table('posts', $fields, $indexes);
// Create a table
$queries = $schema->createSql($db);
520
By using a connections driver the schema data can be converted into platform specific SQL. The return of
createSql and dropSql is a list of SQL queries required to create a table and the required indexes.
Some platforms may require multiple statements to create tables with comments and/or indexes. An array
of queries is always returned.
Schema Collections
class Cake\Database\Schema\Collection
Collection provides access to the various tables available on a connection. You can use it to get the list
of tables or reflect tables into Table objects. Basic usage of the class looks like:
$db = ConnectionManager::get('default');
// Create a schema collection.
$collection = $db->schemaCollection();
// Get the table names
$tables = $collection->listTables();
// Get a single table (instance of Schema\Table)
$table = $collection->describe('posts');
This will rebuild the metadata cache for all tables on the default connection. If you only need to rebuild
a single table you can do that by providing its name:
bin/cake orm_cache build --connection default articles
In addition to building cached data, you can use the OrmCacheShell to remove cached metadata as well:
# Clear all metadata
bin/cake orm_cache clear
More Information
521
522
CHAPTER 13
Bake Console
CakePHPs bake console is another effort to get you up and running in CakePHP fast. The bake console can
create any of CakePHPs basic ingredients: models, behaviors, views, helpers, controllers, components, test
cases, fixtures and plugins. And we arent just talking skeleton classes: Bake can create a fully functional
application in just a few minutes. In fact, Bake is a natural step to take once an application has been
scaffolded.
Installation
Before trying to use or extend bake, make sure it is installed in your application. Bake is provided as a
plugin that you can install with Composer:
composer require --dev cakephp/bake:~1.0
The above will install bake as a development dependency. This means that it will not be installed when you
do production deployments. The following sections cover bake in more detail:
523
When run with no arguments bin/cake bake will output a list of available tasks. You should see something like:
$ bin/cake bake
Welcome to CakePHP v3.1.6 Console
--------------------------------------------------------------App : src
Path: /var/www/cakephp.dev/src/
PHP: 5.5.8
--------------------------------------------------------------The following commands can be used to generate skeleton code for your
application.
Available bake commands:
-
all
behavior
cell
component
controller
fixture
form
helper
mailer
migration
migration_snapshot
model
plugin
shell
shell-helper
template
test
By using `cake bake [name]` you can invoke a specific bake task.
You can get more information on what each task does, and what options are available using the --help
option:
$ bin/cake bake controller --help
Welcome to CakePHP v3.1.6 Console
--------------------------------------------------------------App : src
Path: /var/www/cakephp.dev/src/
--------------------------------------------------------------Bake a controller skeleton.
Usage:
cake bake controller [subcommand] [options] [<name>]
Subcommands:
524
all
Arguments:
name Name of the controller to bake. Can use Plugin.name to bake
controllers into plugins. (optional)
Bake Themes
The theme option is common to all bake commands, and allows changing the bake template files used when
baking. To create your own templates, see the bake theme creation documentation.
Extending Bake
Bake features an extensible architecture that allows your application or plugins to modify or add-to the base
functionality. Bake makes use of a dedicated view class which does not use standard PHP syntax.
Bake Events
As a view class, BakeView emits the same events as any other view class, plus one extra initialize event.
However, whereas standard view classes use the event prefix View., BakeView uses the event prefix
Bake..
The initialize event can be used to make changes which apply to all baked output, for example to add another
helper to the bake view class this event can be used:
<?php
// config/bootstrap_cli.php
use Cake\Event\Event;
Installation
525
use Cake\Event\EventManager;
EventManager::instance()->on('Bake.initialize', function (Event $event) {
$view = $event->subject;
// In my bake templates, allow the use of the MySpecial helper
$view->loadHelper('MySpecial', ['some' => 'config']);
// And add an $author variable so it's always available
$view->set('author', 'Andy');
});
If you want to modify bake from within another plugin, putting your plugins bake events in the plugin
config/bootstrap.php file is a good idea.
Bake events can be handy for making small changes to existing templates. For example, to change
the variable names used when baking controller/template files one can use a function listening for
Bake.beforeRender to modify the variables used in the bake templates:
<?php
// config/bootstrap_cli.php
use Cake\Event\Event;
use Cake\Event\EventManager;
EventManager::instance()->on('Bake.beforeRender', function (Event $event) {
$view = $event->subject;
// Use $rows for the main data variable in indexes
if ($view->get('pluralName')) {
$view->set('pluralName', 'rows');
}
if ($view->get('pluralVar')) {
$view->set('pluralVar', 'rows');
}
// Use $theOne for the main data variable in view/edit
if ($view->get('singularName')) {
$view->set('singularName', 'theOne');
}
if ($view->get('singularVar')) {
$view->set('singularVar', 'theOne');
}
});
You may also scope the Bake.beforeRender and Bake.afterRender events to a specific generated
file. For instance, if you want to add specific actions to your UsersController when generating from a
Controller/controller.ctp file, you can use the following event:
526
<?php
// config/bootstrap_cli.php
use Cake\Event\Event;
use Cake\Event\EventManager;
use Cake\Utility\Hash;
EventManager::instance()->on(
'Bake.beforeRender.Controller.controller',
function (Event $event) {
$view = $event->subject();
if ($view->viewVars['name'] == 'Users') {
// add the login and logout actions to the Users controller
$view->viewVars['actions'] = [
'login',
'logout',
'index',
'view',
'add',
'edit',
'delete'
];
}
}
);
By scoping event listeners to specific bake templates, you can simplify your bake related event logic and
provide callbacks that are easier to test.
Bake Template Syntax
Bake template files use erb-style (<% %>) tags to denote template logic, and treat everything else including
php tags as plain text.
Note: Bake template files do not use, and are insensitive to, asp_tags php ini setting.
BakeView implements the following tags:
<% A Bake template php open tag
%> A Bake template php close tag
<%= A Bake template php short-echo tag
<%- A Bake template php open tag, stripping any leading whitespace before the tag
-%> A Bake template php close tag, stripping trailing whitespace after the tag
One way to see/understand how bake templates works, especially when attempting to modify bake template
files, is to bake a class and compare the template used with the pre-processed template file which is left in
the applications tmp/bake folder.
Installation
527
The pre-processed template file (tmp/bake/Bake-Shell-shell-ctp.php), which is the file actually rendered,
looks like this:
<CakePHPBakeOpenTagphp
namespace <?= $namespace ?>\Shell;
use Cake\Console\Shell;
/**
* <?= $name ?> shell command.
*/
class <?= $name ?>Shell extends Shell
{
/**
* main() method.
*
* @return bool|int Success or error code.
*/
public function main()
{
}
}
528
Installation
529
Once this file has been created, we need to create a template that bake can use when generating code. Create
src/Template/Bake/foo.ctp. In this file well add the following content:
<?php
namespace <%= $namespace %>\Foo;
/**
* <%= $name %> foo
*/
class <%= $name %>Foo
{
// Add code.
}
You should now see your new task in the output of bin/cake bake. You can run your new task
by running bin/cake bake foo Example. This will generate a new ExampleFoo class in
530
The class suffix will be appened to the name provided in your bake call. In the previous example, it
would create a ExampleFooTest.php file.
The class type will be the sub-namespace used that will lead to your file (relative to the app or the
plugin you are baking into). In the previous example, it would create your test with the namespace
App\Test\TestCase\Foo .
Installation
531
532
CHAPTER 14
Caching
class Cake\Cache\Cache
Caching is frequently used to reduce the time it takes to create or read from other resources. Caching is
often used to make reading from expensive resources less expensive. You can store the results of expensive
queries, or remote webservice access that doesnt frequently change in a cache. Once in the cache, re-reading
the stored resource from the cache is much cheaper than accessing the remote resource.
Caching in CakePHP is primarily facilitated by the Cache class. This class provides a set of static methods
that provide a uniform API to dealing with all different types of Caching implementations. CakePHP comes
with several cache engines built-in, and provides an easy system to implement your own caching systems.
The built-in caching engines are:
FileCache File cache is a simple cache that uses local files. It is the slowest cache engine, and
doesnt provide as many features for atomic operations. However, since disk storage is often quite
cheap, storing large objects, or elements that are infrequently written work well in files.
ApcCache APC cache uses the PHP APCu118 extension. This extension uses shared memory on the
webserver to store objects. This makes it very fast, and able to provide atomic read/write features.
Wincache Wincache uses the Wincache119 extension. Wincache is similar to APC in features and
performance, but optimized for Windows and IIS.
XcacheEngine Xcache120 is a PHP extension that provides similar features to APC.
MemcachedEngine Uses the Memcached121 extension.
RedisEngine Uses the phpredis122 extension. Redis provides a fast and persistent cache system
similar to Memcached, also provides atomic operations.
Regardless of the CacheEngine you choose to use, your application interacts with Cake\Cache\Cache
in a consistent manner. You can swap cache engines as your application grows.
118
119
120
121
122
http://php.net/apcu
http://php.net/wincache
http://xcache.lighttpd.net/
http://php.net/memcached
https://github.com/nicolasff/phpredis
533
Configuration options can also be provided as a DSN string. This is useful when working with environment
variables or PaaS providers:
Cache::config('short', [
'url' => 'memcached://user:password@cache-host/?timeout=3600&prefix=myapp_
',
]);
When using a DSN string you can define any additional parameters/options as query string arguments.
You can also configure Cache engines at runtime:
// Using a short name
Cache::config('short', [
'className' => 'File',
'duration' => '+1 hours',
'path' => CACHE,
'prefix' => 'cake_short_'
]);
534
The name of these configurations short or long is used as the $config parameter for
Cake\Cache\Cache::write() and Cake\Cache\Cache::read(). When configuring Cache
engines you can refer to the class name using the following syntaxes:
Short classname without Engine or a namespace. This will infer that you want to use a Cache engine
in Cake\Cache\Engine or App\Cache\Engine.
Using plugin syntax which allows you to load engines from a specific plugin.
Using a fully qualified namespaced classname. This allows you to use classes located outside of the
conventional locations.
Using an object that extends the CacheEngine class.
Note: When using the FileEngine you might need to use the mask option to ensure cache files are made
with the correct permissions.
Writing to a Cache
static Cake\Cache\Cache::write($key, $value, $config = default)
Cache::write() will write a $value to the Cache. You can read or delete this value later by referring
to it by $key. You may specify an optional configuration to store the cache in as well. If no $config
is specified, default will be used. Cache::write() can store any type of object and is ideal for storing
results of model finds:
if (($posts = Cache::read('posts')) === false) {
$posts = $someService->getAllPosts();
Writing to a Cache
535
Cache::write('posts', $posts);
}
Using Cache::write() and Cache::read() to reduce the number of trips made to the database to
fetch posts.
Note: If you plan to cache the result of queries made with the CakePHP ORM, it is better to use the built-in
cache capabilities of the Query object as described in the Caching Loaded Results section
536
537
static Cake\Cache\Cache::gc($config)
Garbage collects entries in the cache configuration. This is primarily used by FileEngine. It should be
implemented by any Cache engine that requires manual eviction of cached data.
Note: Because APC and Wincache use isolated caches for webserver and CLI they have to be cleared
separately (CLI cannot clear webserver and vice versa).
538
reduces the risk of contention, and ability for two users to simultaneously lower the value by one, resulting
in an incorrect value.
After setting an integer value you can manipulate it using increment() and decrement():
Cache::write('initial_count', 10);
// Later on
Cache::decrement('initial_count');
// Or
Cache::increment('initial_count');
Note: Incrementing and decrementing do not work with FileEngine. You should use APC, Wincache,
Redis or Memcached instead.
Using Groups
Sometimes you will want to mark multiple cache entries to belong to certain group or namespace. This is a
common requirement for mass-invalidating keys whenever some information changes that is shared among
all entries in the same group. This is possible by declaring the groups in cache configuration:
Cache::config('site_home', [
'className' => 'Redis',
'duration' => '+999 days',
'groups' => ['comment', 'article']
]);
539
// src/Model/Table/ArticlesTable.php
public function afterSave($entity, $options = [])
{
if ($entity->isNew()) {
Cache::clearGroup('article', 'site_home');
}
}
Groups are shared across all cache configs using the same engine and same prefix. If you are using groups
and want to take advantage of group deletion, choose a common prefix for all your configs.
static Cake\Cache\Cache::enabled
540
If you need to check on the state of Cache, you can use enabled().
Custom Cache engines must extend Cake\Cache\CacheEngine which defines a number of abstract
methods as well as provides a few initialization methods.
The required API for a CacheEngine is
class Cake\Cache\CacheEngine
The base class for all cache engines used with Cache.
Cake\Cache\CacheEngine::write($key, $value, $config = default)
Returns boolean for success.
Write value for a key into cache, optional string $config specifies configuration name to write to.
Cake\Cache\CacheEngine::read($key)
Returns The cached value or false for failure.
Read a key from the cache. Return false to indicate the entry has expired or does not exist.
Cake\Cache\CacheEngine::delete($key)
Returns Boolean true on success.
Delete a key from the cache. Return false to indicate that the entry did not exist or could not be
deleted.
Cake\Cache\CacheEngine::clear($check)
Returns Boolean true on success.
Delete all keys from the cache. If $check is true, you should validate that each value is actually
expired.
Cake\Cache\CacheEngine::clearGroup($group)
Returns Boolean true on success.
Delete all keys from the cache belonging to the same group.
541
Cake\Cache\CacheEngine::decrement($key, $offset = 1)
Returns Boolean true on success.
Decrement a number under the key and return decremented value
Cake\Cache\CacheEngine::increment($key, $offset = 1)
Returns Boolean true on success.
Increment a number under the key and return incremented value
Cake\Cache\CacheEngine::gc()
Not required, but used to do clean up when resources expire. FileEngine uses this to delete files
containing expired content.
542
CHAPTER 15
CakePHP features not only a web framework but also a console framework for creating console applications. Console applications are ideal for handling a variety of background tasks such as maintenance, and
completing work outside of the request-response cycle. CakePHP console applications allow you to reuse
your application classes from the command line.
CakePHP comes with a number of console applications out of the box. Some of these applications are used
in concert with other CakePHP features (like i18n), and others are for general use to get you working faster.
543
Note: For Windows, the command needs to be bin\cake (note the backslash).
Running the Console with no arguments produces this help message:
Welcome to CakePHP v3.0.0 Console
--------------------------------------------------------------App : App
Path: /Users/markstory/Sites/cakephp-app/src/
--------------------------------------------------------------Current Paths:
-app: src
-root: /Users/markstory/Sites/cakephp-app
-core: /Users/markstory/Sites/cakephp-app/vendor/cakephp/cakephp
Changing Paths:
Your working path should be the same as your application path. To change your
path use the '-app' param.
Example: -app relative/path/to/myapp or -app /absolute/path/to/myapp
Available Shells:
[Bake] bake
[Migrations] migrations
[CORE] i18n, orm_cache, plugin, routes, server
[app] behavior_time, console, orm
To run an app or core command, type cake shell_name [args]
To run a plugin command, type cake Plugin.shell_name [args]
To get help on a specific command, type cake shell_name --help
The first information printed relates to paths. This is helpful if youre running the console from different
parts of the filesystem.
You could then run the any of the listed shells by using its name:
# run server shell
bin/cake server
# run migrations shell
bin/cake migrations -h
# run bake (with plugin prefix)
bin/cake bake.bake -h
Plugin shells can be invoked without a plugin prefix if the shells name does not overlap with an application
or framework shell. In the case that two plugins provide a shell with the same name, the first loaded plugin
will get the short alias. You can always use the plugin.shell format to unambiguously reference a
544
shell.
class Cake\Console\Shell
Creating a Shell
Lets create a shell for use in the Console. For this example, well create a simple Hello world shell. In your
applications src/Shell directory create HelloShell.php. Put the following code inside it:
namespace App\Shell;
use Cake\Console\Shell;
class HelloShell extends Shell
{
public function main()
{
$this->out('Hello world.');
}
}
The conventions for shell classes are that the class name should match the file name, with the suffix of
Shell. In our shell we created a main() method. This method is called when a shell is called with no
additional commands. Well add some more commands in a bit, but for now lets just run our shell. From
your application directory, run:
bin/cake hello
As mentioned before, the main() method in shells is a special method called whenever there are no other
commands or arguments given to a shell. Since our main method wasnt very interesting lets add another
command that does something:
namespace App\Shell;
use Cake\Console\Shell;
class HelloShell extends Shell
{
public function main()
{
$this->out('Hello world.');
}
Creating a Shell
545
After saving this file, you should be able to run the following command and see your name printed out:
bin/cake hello hey_there your-name
Any public method not prefixed by an _ is allowed to be called from the command line. As you can see,
methods invoked from the command line are transformed from the underscored shell argument to the correct
camel-cased method name in the class.
In our heyThere() method we can see that positional arguments are provided to our heyThere()
function. Positional arguments are also available in the args property. You can access switches or options
on shell applications, which are available at $this->params, but well cover that in a bit.
When using a main() method you wont be able to use the positional arguments. This is because the first
positional argument or option is interpreted as the command name. If you want to use arguments, you should
use method names other than main.
546
}
}
The above shell, will fetch a user by username and display the information stored in the database.
Shell Tasks
There will be times when building more advanced console applications, youll want to compose functionality
into re-usable classes that can be shared across many shells. Tasks allow you to extract commands into
classes. For example the bake command is made almost entirely of tasks. You define a tasks for a shell
using the $tasks property:
class UserShell extends Shell
{
public $tasks = ['Template'];
}
You can use tasks from plugins using the standard plugin syntax. Tasks are stored in Shell/Task/
in files named after their classes. So if we were to create a new FileGenerator task, you would create
src/Shell/Task/FileGeneratorTask.php.
Each task must at least implement a main() method. The ShellDispatcher, will call this method when the
task is invoked. A task class looks like:
namespace App\Shell\Task;
use Cake\Console\Shell;
class FileGeneratorTask extends Shell
{
public function main()
{
}
}
A shell can also access its tasks as properties, which makes tasks great for making re-usable chunks of
functionality similar to Components:
// Found in src/Shell/SeaShell.php
class SeaShell extends Shell
{
// Found in src/Shell/Task/SoundTask.php
public $tasks = ['Sound'];
public function main()
{
$this->Sound->main();
}
}
Shell Tasks
547
You can also access tasks directly from the command line:
$ cake sea sound
Note: In order to access tasks directly from the command line, the task must be included in the shell class
$tasks property.
Also, the task name must be added as a sub-command to the Shells OptionParser:
public function getOptionParser()
{
$parser = parent::getOptionParser();
$parser->addSubcommand('sound', [
// Provide help text for the command list
'help' => 'Execute The Sound Task.',
// Link the option parsers together.
'parser' => $this->Sound->getOptionParser(),
]);
return $parser;
}
Would load and return a ProjectTask instance. You can load tasks from plugins using:
$progressBar = $this->Tasks->load('ProgressBar.ProgressBar');
Shell Helpers
If you have complex output generation logic, you can use Shell Helpers to encapsulate this logic in a reusable way.
548
// As a string
$this->dispatchShell('schema create Blog --plugin Blog');
// As an array
$this->dispatchShell('schema', 'create', 'Blog', '--plugin', 'Blog');
The above shows how you can call the schema shell to create the schema for a plugin from inside your
plugins shell.
Parameters passed through extra will be merged in the Shell::$params property and are accessible
with the Shell::param() method. By default, a requested extra param is automatically added when
a Shell is dispatched using dispatchShell(). This requested parameter prevents the CakePHP
console welcome message from being displayed on dispatched shells.
549
Creating Files
Cake\Console\Shell::createFile($path, $contents)
Many Shell applications help automate development or deployment tasks. Creating files is often important
in these use cases. CakePHP provides an easy way to create a file at a given path:
$this->createFile('bower.json', $stuff);
If the Shell is interactive, a warning will be generated, and the user asked if they want to overwrite the file
if it already exists. If the shells interactive property is false, no question will be asked and the file will
simply be overwritten.
Console Output
The Shell class provides a few methods for outputting content:
// Write to stdout
$this->out('Normal message');
// Write to stderr
$this->err('Error message');
// Write to stderr and raise a stop exception
$this->abort('Fatal error');
// Before CakePHP 3.2. Write to stderr and exit()
$this->error('Fatal error');
Shell also includes methods for clearing output, creating blank lines, or drawing a line of dashes:
// Output 2 newlines
$this->out($this->nl(2));
// Clear the user's screen
550
$this->clear();
// Draw a horizontal line
$this->hr();
Lastly, you can update the current line of text on the screen using _io->overwrite():
$this->out('Counting down');
$this->out('10', 0);
for ($i = 9; $i > 0; $i--) {
sleep(1);
$this->_io->overwrite($i, 0, 2);
}
It is important to remember, that you cannot overwrite text once a new line has been output.
You can control the output level of shells, by using the --quiet and --verbose options. These options
are added by default, and allow you to consistently control output levels inside your CakePHP shells.
The --quiet and --verbose options also control how logging data is output to stdout/stderr. Normally
info and higher log messages are output to stdout/stderr. When --verbose is used, debug logs will be
output to stdout. When --quiet is used, only warning and higher log messages will be output to stderr.
Console Output
551
Styling Output
Styling output is done by including tags - just like HTML - in your output. ConsoleOutput will replace these
tags with the correct ansi code sequence, or remove the tags if you are on a console that doesnt support ansi
codes. There are several built-in styles, and you can create more. The built-in ones are
error Error messages. Red text.
warning Warning messages. Yellow text.
info Informational messages. Cyan text.
comment Additional text. Blue text.
question Text that is a question, added automatically by shell.
You can create additional styles using $this->stdout->styles(). To declare a new output style you
could do:
$this->_io->styles('flashy', ['text' => 'magenta', 'blink' => true]);
This would then allow you to use a <flashy> tag in your shell output, and if ansi colours are enabled, the
following would be rendered as blinking magenta text $this->out('<flashy>Whoooa</flashy>
Something went wrong');. When defining styles you can use the following colours for the text
and background attributes:
black
blue
cyan
green
magenta
red
white
yellow
You can also use the following options as boolean switches, setting them to a truthy value enables them.
blink
bold
reverse
underline
Adding a style makes it available on all instances of ConsoleOutput as well, so you dont have to redeclare
styles for both stdout and stderr objects.
552
The above will put the output object into raw output mode. In raw output mode, no styling is done at all.
There are three modes you can use.
ConsoleOutput::COLOR - Output with color escape codes in place.
ConsoleOutput::PLAIN - Plain text output, known style tags will be stripped from the output.
ConsoleOutput::RAW - Raw output, no styling or formatting will be done. This is a good mode
to use if you are outputting XML or, want to debug why your styling isnt working.
By default on *nix systems ConsoleOutput objects default to colour output. On Windows systems, plain
output is the default unless the ANSICON environment variable is present.
New in version 3.2: The abort() method was added in 3.2. In prior versions you can use error() to output
a message and stop execution.
Hook Methods
Cake\Console\Shell::initialize()
Initializes the Shell, acts as constructor for subclasses and allows configuration of tasks prior to shell
execution.
Cake\Console\Shell::startup()
Starts up the Shell and displays the welcome message. Allows for checking and configuring prior to
command or main execution.
Tip: Override the startup() method if you want to remove the welcome information, or otherwise
modify the pre-command flow.
553
554
addSubcommand()
addSubcommands()
command()
description()
epilog()
Cake\Console\ConsoleOptionParser::description($text = null)
Gets or sets the description for the option parser. The description displays above the argument and option
information. By passing in either an array or a string, you can set the value of the description. Calling with
no arguments will return the current value:
// Set multiple lines at once
$parser->description(['line one', 'line two']);
// Read the current value
$parser->description();
The consoles description output can be seen by executing the following command:
user@ubuntu:~/cakeblog$ bin/cake console --help
Welcome to CakePHP v3.0.13 Console
--------------------------------------------------------------App : src
Path: /home/user/cakeblog/src/
--------------------------------------------------------------This shell provides a REPL that you can use to interact with your
application in an interactive fashion. You can use it to run adhoc
queries with your models, or experiment and explore the features of
555
Cake\Console\ConsoleOptionParser::epilog($text = null)
Gets or sets the epilog for the option parser. The epilog is displayed after the argument and option information. By passing in either an array or a string, you can set the value of the epilog. Calling with no arguments
will return the current value:
// Set multiple lines at once
$parser->epilog(['line one', 'line two']);
// Read the current value
$parser->epilog();
To illustrate the epilog() method in action lets add a call to the getOptionParser() method used
above in the src/Shell/ConsoleShell.php:
/**
* Display help for this console.
*
* @return ConsoleOptionParser
*/
public function getOptionParser()
{
$parser = new ConsoleOptionParser('console');
$parser->description(
'This shell provides a REPL that you can use to interact ' .
'with your application in an interactive fashion. You can use ' .
'it to run adhoc queries with your models, or experiment ' .
'and explore the features of CakePHP and your application.' .
"\n\n" .
'You will need to have psysh installed for this Shell to work.'
);
$parser->epilog('Thank you for baking with CakePHP!');
return $parser;
}
The text added with the epilog() method can be seen in the output from the following console command:
user@ubuntu:~/cakeblog$ bin/cake console --help
Welcome to CakePHP v3.0.13 Console
556
--------------------------------------------------------------App : src
Path: /home/user/cakeblog/src/
--------------------------------------------------------------This shell provides a REPL that you can use to interact with your
application in an interactive fashion. You can use it to run adhoc
queries with your models, or experiment and explore the features of
CakePHP and your application.
You will need to have psysh installed for this Shell to work.
Usage:
cake console [-h] [-v] [-q]
Options:
--help, -h
--verbose, -v
--quiet, -q
Adding Arguments
Cake\Console\ConsoleOptionParser::addArgument($name, $params =[])
Positional arguments are frequently used in command line tools, and ConsoleOptionParser allows
you to define positional arguments as well as make them required. You can add arguments one at a time
with $parser->addArgument(); or multiple at once with $parser->addArguments();:
$parser->addArgument('model', ['help' => 'The model to bake']);
557
$parser->addArguments([
'node' => ['help' => 'The node to create', 'required' => true],
'parent' => ['help' => 'The parent node', 'required' => true]
]);
As with all the builder methods on ConsoleOptionParser, addArguments can be used as part of a fluent
method chain.
Validating Arguments
When creating positional arguments, you can use the required flag, to indicate that an argument must be
present when a shell is called. Additionally you can use choices to force an argument to be from a list of
valid choices:
$parser->addArgument('type', [
'help' => 'The type of node to interact with.',
'required' => true,
'choices' => ['aro', 'aco']
]);
The above will create an argument that is required and has validation on the input. If the argument is either
missing, or has an incorrect value an exception will be raised and the shell will be stopped.
Adding Options
Cake\Console\ConsoleOptionParser::addOption($name, $options =[])
Options or flags are also frequently used in command line tools. ConsoleOptionParser supports
creating options with both verbose and short aliases, supplying defaults and creating boolean switches.
Options are created with either $parser->addOption() or $parser->addOptions().
$parser->addOption('connection', [
'short' => 'c',
'help' => 'connection',
'default' => 'default',
]);
The above would allow you to use either cake myshell --connection=other, cake myshell
--connection other, or cake myshell -c other when invoking the shell. You can also create
boolean switches, these switches do not consume values, and their presence just enables them in the parsed
parameters.
$parser->addOption('no-commit', ['boolean' => true]);
With this option, when calling a shell like cake myshell --no-commit something the no-commit
param would have a value of true, and something would be a treated as a positional argument. The builtin --help, --verbose, and --quiet options use this feature.
When creating options you can use the following options to define the behavior of the option:
558
short - The single letter variant for this option, leave undefined for none.
help - Help text for this option. Used when generating help for the option.
default - The default value for this option. If not defined the default will be true.
boolean - The option uses no value, its just a boolean switch. Defaults to false.
choices - An array of valid choices for this option. If left empty all values are valid. An exception
will be raised when parse() encounters an invalid value.
Cake\Console\ConsoleOptionParser::addOptions(array $options)
If you have an array with multiple options you can use $parser->addOptions() to add multiple
options at once.
$parser->addOptions([
'node' => ['short' => 'n', 'help' => 'The node to create'],
'parent' => ['short' => 'p', 'help' => 'The parent node']
]);
As with all the builder methods on ConsoleOptionParser, addOptions can be used as part of a fluent method
chain.
Option values are stored in the $this->params array. You can also use the convenience method
$this->param() to avoid errors when trying to access non-present options.
Validating Options
Options can be provided with a set of choices much like positional arguments can be. When an option has defined choices, those are the only valid choices for an option. All other values will raise an
InvalidArgumentException:
$parser->addOption('accept', [
'help' => 'What version to accept.',
'choices' => ['working', 'theirs', 'mine']
]);
The following option would result in $this->params['verbose'] always being available. This lets
you omit empty() or isset() checks for boolean flags:
559
if ($this->params['verbose']) {
// Do something.
}
Since the boolean options are always defined as true or false you can omit additional check methods
when using the array access. The $this->param() method makes these checks unnecessary for all
cases.
Adding Subcommands
Cake\Console\ConsoleOptionParser::addSubcommand($name, $options =[])
Console applications are often made of subcommands, and these subcommands may require special option
parsing and have their own help. A perfect example of this is bake. Bake is made of many separate tasks
that all have their own help and options. ConsoleOptionParser allows you to define subcommands
and provide command specific option parsers so the shell knows how to parse commands for its tasks:
$parser->addSubcommand('model', [
'help' => 'Bake a model',
'parser' => $this->Model->getOptionParser()
]);
The above is an example of how you could provide help and a specialized option parser for a shells task.
By calling the Tasks getOptionParser() we dont have to duplicate the option parser generation, or
mix concerns in our shell. Adding subcommands in this way has two advantages. First, it lets your shell
document its subcommands in the generated help. It also gives easy access to the subcommand help. With
the above subcommand created you could call cake myshell --help and see the list of subcommands,
and also run cake myshell model --help to view the help for just the model task.
Note: Once your Shell defines subcommands, all subcommands must be explicitly defined.
When defining a subcommand you can use the following options:
help - Help text for the subcommand.
parser - A ConsoleOptionParser for the subcommand. This allows you to create method
specific option parsers. When help is generated for a subcommand, if a parser is present
it will be used.
You can also supply the parser as an array that is compatible with
Cake\Console\ConsoleOptionParser::buildFromArray()
Adding subcommands can be done as part of a fluent method chain.
560
$parser->addSubcommand('check', [
'help' => __('Check the permissions between an ACO and ARO.'),
'parser' => [
'description' => [
__("Use this command to grant ACL permissions. Once executed, the
"),
__("ARO specified (and its children, if any) will have ALLOW
access "),
__("to the specified ACO action (and the ACO's children, if any).
")
],
'arguments' => [
'aro' => ['help' => __('ARO to check.'), 'required' => true],
'aco' => ['help' => __('ACO to check.'), 'required' => true],
'action' => ['help' => __('Action to check')]
]
]
]);
Inside the parser spec, you can define keys for arguments, options, description and epilog.
You cannot define subcommands inside an array style builder. The values for arguments, and options,
should follow the format that Cake\Console\ConsoleOptionParser::addArguments() and
Cake\Console\ConsoleOptionParser::addOptions() use. You can also use buildFromArray on its own, to build an option parser:
public function getOptionParser()
{
return ConsoleOptionParser::buildFromArray([
'description' => [
__("Use this command to grant ACL permissions. Once executed, the
"),
__("ARO specified (and its children, if any) will have ALLOW
access "),
__("to the specified ACO action (and the ACO's children, if any).
")
],
'arguments' => [
'aro' => ['help' => __('ARO to check.'), 'required' => true],
'aco' => ['help' => __('ACO to check.'), 'required' => true],
'action' => ['help' => __('Action to check')]
]
]);
}
Merging ConsoleOptionParsers
Cake\Console\ConsoleOptionParser::merge($spec)
When building a group command, you maybe want to combine several parsers for this:
561
$parser->merge($anotherParser);
Note that the order of arguments for each parser must be the same, and that options must also be compatible
for it work. So do not use keys for different things.
Would both generate the help for bake. If the shell supports subcommands you can get help for those in a
similar fashion:
cake bake model --help
cake bake model -h
This would get you the help specific to bakes model task.
The above would return an XML document with the generated help, options, arguments and subcommands
for the selected shell. A sample XML document would look like:
<?xml version="1.0"?>
<shell>
<command>bake fixture</command>
<description>Generate fixtures for use with the test suite. You can use
`bake fixture all` to bake all fixtures.</description>
<epilog>
Omitting all arguments and options will enter into an interactive
mode.
</epilog>
<subcommands/>
<options>
<option name="--help" short="-h" boolean="1">
<default/>
<choices/>
</option>
562
This asserts that the generated message IDs are valid and fit to the domain the emails are sent from.
Routing in Shells / CLI
563
More Topics
Shell Helpers
New in version 3.1: Shell Helpers were added in 3.1.0
Shell Helpers let you package up complex output generation code. Shell Helpers can be accessed and used
from any shell or task:
// Output some data as a table.
$this->helper('Table')->output($data);
// Get a helper from a plugin.
$this->helper('Plugin.HelperName')->output($data);
You can also get instances of helpers and call any public methods on them:
// Get and use the Progress Helper.
$progress = $this->helper('Progress');
$progress->increment(10);
$progress->draw();
Creating Helpers
While CakePHP comes with a few shell helpers you can create more in your application or plugins. As an example, well create a simple helper to generate fancy headings. First create the
src/Shell/Helper/HeadingHelper.php and put the following in it:
<?php
namespace App\Shell\Helper;
use Cake\Console\Helper;
class HeadingHelper extends Helper
{
public function output($args)
{
$args += ['', '#', 3];
$marker = str_repeat($args[1], $args[2]);
$this->_io->out($marker . ' ' . $args[0] . ' ' . $marker);
}
}
We can then use this new helper in one of our shell commands by calling it:
// With ### on either side
$this->helper('Heading')->output(['It works!']);
// With ~~~~ on either side
$this->helper('Heading')->output(['It works!', '~', 4]);
564
Helpers generally implement the output() method which takes an array of parameters. However, because
Console Helpers are vanilla classes they can implement additional methods that take any form of arguments.
Built-In Helpers
Table Helper
The TableHelper assists in making well formatted ASCII art tables. Using it is pretty simple:
$data = [
['Header 1', 'Header', 'Long Header'],
['short', 'Longish thing', 'short'],
['Longer thing', 'short', 'Longest Value'],
];
$this->helper('Table')->output($data);
// Outputs
+--------------+---------------+---------------+
| Header 1
| Header
| Long Header
|
+--------------+---------------+---------------+
| short
| Longish thing | short
|
| Longer thing | short
| Longest Value |
+--------------+---------------+---------------+
Progress Helper
The ProgressHelper can be used in two different ways. The simple mode lets you provide a callback that is
invoked until the progress is complete:
$this->helper('Progress')->output(['callback' => function ($progress) {
// Do work here.
$progress->increment(20);
$progress->draw();
}]);
You can control the progress bar more by providing additional options:
total The total number of items in the progress bar. Defaults to 100.
width The width of the progress bar. Defaults to 80.
callback The callback that will be called in a loop to advance the progress bar.
An example of all the options in use would be:
$this->helper('Progress')->output([
'total' => 10,
'width' => 20,
'callback' => function ($progress) {
$progress->increment(2);
$progress->draw();
More Topics
565
}
]);
The progress helper can also be used manually to increment and re-render the progress bar as necessary:
$progress = $this->helper('Progress');
$progress->init([
'total' => 10,
'width' => 20,
]);
$progress->increment(4);
$progress->draw();
This will bootstrap your application and start an interactive console. At this point you can interact with your
application code and execute queries using your applications models:
$ bin/cake console
Welcome to CakePHP v3.0.0 Console
--------------------------------------------------------------App : App
Path: /Users/mark/projects/cakephp-app/src/
-------------------------------------------------------------->>> $articles = Cake\ORM\TableRegistry::get('Articles');
// object(Cake\ORM\Table)(
//
// )
>>> $articles->find()->all();
Since your application has been bootstrapped you can also test routing using the REPL:
>>> Cake\Routing\Router::parse('/articles/view/1');
// [
//
'controller' => 'Articles',
//
'action' => 'view',
//
'pass' => [
//
0 => '1'
//
],
//
'plugin' => NULL
// ]
566
I18N Shell
The i18n features of CakePHP use po files123 as their translation source. PO files integrate with commonly
used translation tools like poedit124 .
The i18n shell provides a quick and easy way to generate po template files. These templates files can then
be given to translators so they can translate the strings in your application. Once you have translations done,
pot files can be merged with existing translations to help update your translations.
Generating POT Files
POT files can be generated for an existing application using the extract command. This command will
scan your entire application for __() style function calls, and extract the message string. Each unique string
in your application will be combined into a single POT file:
bin/cake i18n extract
The above will run the extraction shell. The result of this command will be the file src/Locale/default.pot.
You use the pot file as a template for creating po files. If you are manually creating po files from the pot file,
be sure to correctly set the Plural-Forms header line.
123
124
http://en.wikipedia.org/wiki/GNU_gettext
http://www.poedit.net/
More Topics
567
This will generate the required POT files used in the plugins.
Excluding Folders
You can pass a comma separated list of folders that you wish to be excluded. Any path containing a path
segment with the provided values will be ignored:
bin/cake i18n extract --exclude Test,Vendor
Completion Shell
Working with the console gives the developer a lot of possibilities but having to completely know and write
those commands can be tedious. Especially when developing new shells where the commands differ per
minute iteration. The Completion Shells aids in this matter by providing an API to write completion scripts
for shells like bash, zsh, fish etc.
Sub Commands
The Completion Shell consists of a number of sub commands to assist the developer creating its completion
script. Each for a different step in the autocompletion process.
568
Commands
For the first step commands outputs the available Shell Commands, including plugin name when applicable.
(All returned possibilities, for this and the other sub commands, are separated by a space.) For example:
bin/cake Completion commands
Returns:
acl api bake command_list completion console i18n schema server test
testsuite upgrade
Your completion script can select the relevant commands from that list to continue with. (For this and the
following sub commands.)
subCommands
Once the preferred command has been chosen subCommands comes in as the second step and outputs the
possible sub command for the given shell command. For example:
bin/cake Completion subcommands bake
Returns:
controller db_config fixture model plugin project test view
options
As the third and final options outputs options for the given (sub) command as set in getOptionParser. (Including the default options inherited from Shell.) For example:
bin/cake Completion options bake
Returns:
--help -h --verbose -v --quiet -q --everything --connection -c --force -f -plugin -p --prefix --theme -t
You can also pass an additional argument being the shell sub-command : it will output the specific options
of this sub-command.
How to enable Bash autocompletion for the CakePHP Console
First, make sure the bash-completion library is installed. If not, you do it with the following command:
apt-get install bash-completion
More Topics
569
Create a file named cake in /etc/bash_completion.d/ and put the Bash Completion file content inside it.
Save the file, then restart your console.
Note: If you are using MacOS X, you can install the bash-completion library using homebrew with
the command brew install bash-completion. The target directory for the cake file will be
/usr/local/etc/bash_completion.d/.
570
_filedir
return 0
fi
return 0
fi
opts=$(${cake} Completion fuzzy "${COMP_WORDS[@]:1}")
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
if [[ $COMPREPLY = "" ]] ; then
_filedir
return 0
fi
return 0;
}
complete -F _cake cake bin/cake
Using autocompletion
Once enabled, the autocompletion can be used the same way than for other built-in commands, using the
TAB key. Three type of autocompletion are provided. The following output are from a fresh CakePHP
install.
Commands
Sample output for commands autocompletion:
$ bin/cake <tab>
bake
i18n
console
migrations
orm_cache
plugin
routes
server
Subcommands
Sample output for subcommands autocompletion:
$ bin/cake bake <tab>
behavior
helper
cell
mailer
component
migration
controller
migration_snapshot
fixture
model
form
plugin
shell
shell_helper
template
test
Options
Sample output for subcommands options autocompletion:
More Topics
571
-t
-v
--connection -f
-h
--theme
--verbose
--help
--plugin
-q
-p
--prefix
--quiet
Plugin Shell
The plugin shell allows you to load and unload plugins via the command prompt. If you need help, run:
bin/cake plugin --help
Loading Plugins
Via the Load task you are able to load plugins in your config/bootstrap.php. You can do this by running:
bin/cake plugin load MyPlugin
Adding the -b or -r switch to the load task will enable loading of the plugins bootstrap and routes
values:
bin/cake plugin load -b MyPlugin
// Load the bootstrap.php from the plugin
Plugin::load('MyPlugin', ['bootstrap' => true]);
bin/cake plugin load -r MyPlugin
// Load the routes.php from the plugin
Plugin::load('MyPlugin', ['routes' => true]);
Unloading Plugins
You can unload a plugin by specifying its name:
bin/cake plugin unload MyPlugin
directly served by the web server without invoking PHP. You can do this by running:
bin/cake plugin assets symlink
Running the above command will symlink all plugins assets under apps webroot. On Windows, which
doesnt support symlinks, the assets will be copied in respective folders instead of being symlinked.
You can symlink assets of one particular plugin by specifying its name:
bin/cake plugin assets symlink MyPlugin
Routes Shell
New in version 3.1: The RoutesShell was added in 3.1
The RoutesShell provides a simple to use CLI interface for testing and debugging routes. You can use it to
test how routes are parsed, and what URLs routing parameters will generate.
Getting a List of all Routes
bin/cake routes
If your route contains any query string parameters remember to surround the URL in quotes:
bin/cake routes check "/bookmarks/?page=1&sort=title&direction=desc"
Upgrade Shell
The upgrade shell will do most of the work to upgrade your CakePHP application from 2.x to 3.x.
It is provided by a standalone Upgrade plugin125 . Please read the README file to get all information on
how to upgrade your application.
125
https://github.com/cakephp/upgrade
More Topics
573
Server Shell
The ServerShell lets you stand up a simple webserver using the built in PHP webserver. While this
server is not intended for production use it can be handy in development when you want to quickly try an
idea out and dont want to spend time configuring Apache or Nginx. You can start the server shell with:
$ bin/cake server
You should see the server boot up and attach to port 8765. You can visit the CLI server by visiting
http://localhost:8765 in your web-browser. You can close the server by pressing CTRL-C in
your terminal.
Changing the Port and Document Root
You can customize the port and document root using options:
$ bin/cake server --port 8080 --document_root path/to/app
Cache Shell
To help you better manage cached data from a CLI environment, a shell command is available for clearing
cached data your application has:
// Clear one cache config
bin/cake cache clear <configname>
// Clear all cache configs
bin/cake cache clear_all
574
CHAPTER 16
Debugging
Debugging is an inevitable and necessary part of any development cycle. While CakePHP doesnt offer any
tools that directly connect with any IDE or editor, CakePHP does provide several tools to assist in debugging
and exposing what is running under the hood of your application.
Basic Debugging
debug(mixed $var, boolean $showHtml = null, $showFrom = true)
The debug() function is a globally available function that works similarly to the PHP function
print_r(). The debug() function allows you to show the contents of a variable in a number of different
ways. First, if youd like data to be shown in an HTML-friendly way, set the second parameter to true.
The function also prints out the line and file it is originating from by default.
Output from this function is only shown if the core $debug variable has been set to true.
New in version 3.3.0: Calling this method will return passed $var, so that you can, for instance, place it in
return statements, for example:
return debug($data); // will return $data in any case.
http://psysh.org/
575
// Some code
eval(breakpoint());
Will open an interactive console that can be used to check local variables and execute other code. You can
exit the interactive debugger and resume the original execution by running quit or q in the interactive
session.
Outputting Values
static Cake\Error\Debugger::dump($var, $depth = 3)
Dump prints out the contents of a variable. It will print out all properties and methods (if any) of the supplied
variable:
$foo = [1,2,3];
Debugger::dump($foo);
// Outputs
array(
1,
2,
3
)
// Simple object
$car = new Car();
Debugger::dump($car);
// Outputs
object(Car) {
color => 'red'
make => 'Toyota'
model => 'Camry'
mileage => (int)15000
}
Creates a detailed stack trace log at the time of invocation. The log() method prints out data similar to that
done by Debugger::dump(), but to the debug.log instead of the output buffer. Note your tmp directory
(and its contents) must be writable by the web server for log() to work correctly.
Above is the stack trace generated by calling Debugger::trace() in a controller action. Reading the
stack trace bottom to top shows the order of currently running functions (stack frames).
Although this method is used internally, it can be handy if youre creating your own error messages or log
entries for custom situations.
static Cake\Error\Debugger::getType($var)
Generating Stack Traces
577
Get the type of a variable. Objects will return their class name
The above would write Got here into the debug log. You can use log entries to help debug methods
that involve redirects or complicated loops. You can also use Cake\Log\Log::write() to write log
messages. This method can be called statically anywhere in your application one CakeLog has been loaded:
// At the top of the file you want to log in.
use Cake\Log\Log;
// Anywhere that Log has been imported.
Log::debug('Got here');
Debug Kit
DebugKit is a plugin that provides a number of good debugging tools. It primarily provides a toolbar in the
rendered HTML, that provides a plethora of information about your application and the current request. See
the Debug Kit chapter for how to install and use DebugKit.
578
CHAPTER 17
Deployment
Once your application is complete, or even before that youll want to deploy it. There are a few things you
should do when deploying a CakePHP application.
Moving files
You are encouraged to create a git commit and pull or clone that commit or repository on your server and
run composer install. While this requires some knowledge about git and an existing install of git
and composer this process will take care about library dependencies and file and folder permissions.
Be aware that when deploying via FTP you will at least have to fix file and folder permissions.
You can also use this deployment technique to setup a staging- or demo-server (pre-production) and keep it
in sync with your dev box.
Adjust config/app.php
Adjusting app.php, specifically the value of debug is extremely important. Turning debug = false disables a number of development features that should never be exposed to the Internet at large. Disabling
debug changes the following types of things:
Debug messages, created with pr() and debug() are disabled.
Core CakePHP caches are by default flushed every year (about 365 days), instead of every 10 seconds
as in development.
Error views are less informative, and give generic error messages instead.
PHP Errors are not displayed.
Exception stack traces are disabled.
In addition to the above, many plugins and application extensions use debug to modify their behavior.
579
You can check against an environment variable to set the debug level dynamically between environments.
This will avoid deploying an application with debug true and also save yourself from having to change the
debug level each time before deploying to a production environment.
For example, you can set an environment variable in your Apache configuration:
SetEnv CAKEPHP_DEBUG 1
And then you can set the debug level dynamically in app.php:
$debug = (bool)getenv('CAKEPHP_DEBUG');
return [
'debug' => $debug,
.....
];
580
Since handling static assets, such as images, JavaScript and CSS files of plugins, through the Dispatcher
is incredibly inefficient, it is strongly recommended to symlink them for production. This can be done by
using the plugin shell:
bin/cake plugin assets symlink
The above command will symlink the webroot directory of all loaded plugins to appropriate path in the
apps webroot directory.
If your filesystem doesnt allow creating symlinks the directories will be copied instead of being symlinked.
You can also explicitly copy the directories using:
bin/cake plugin assets copy
Deploying an update
After deployment of an update you might also want to run bin/cake orm_cache clear, part of the
ORM Cache Shell shell.
Deploying an update
581
582
CHAPTER 18
Warning:
Before version 3.1, the Email and Transport classes were under the
Cake\Network\Email namespace instead of the Cake\Mailer namespace.
class Cake\Mailer\Email(mixed $profile = null)
Email is a new class to send email. With this class you can send email from any place inside of your
application.
Basic Usage
First of all, you should ensure the class is loaded:
use Cake\Mailer\Email;
After youve loaded Email, you can send an email with the following:
$email = new Email('default');
$email->from(['me@example.com' => 'My Site'])
->to('you@example.com')
->subject('About')
->send('My message');
Since Emails setter methods return the instance of the class, you are able to set its properties with method
chaining.
Email has several methods for defining recipients - to(), cc(), bcc(), addTo(), addCc() and
addBcc(). The main difference being that the first three will overwrite what was already set and the latter
will just add more recipients to their respective field:
$email = new Email();
$email->to('to@example.com', 'To Example');
583
Note: Its also a good idea to set the envelope sender when sending mail on another persons behalf. This
prevents them from getting any messages about deliverability.
Configuration
Configuration for Email defaults is created using config() and configTransport(). You should
put your email presets in the config/app.php file. The config/app.default.php file is an example of this file.
It is not required to define email configuration in config/app.php. Email can be used without it and use
the respective methods to set all configurations separately or load an array of configs.
By defining profiles and transports, you can keep your application code free of configuration data, and avoid
duplication that makes maintenance and deployment more difficult.
To load a predefined configuration, you can use the profile() method or pass it to the constructor of
Email:
$email = new Email();
$email->profile('default');
// Or in constructor
$email = new Email('default');
Instead of passing a string which matches a preset configuration name, you can also just load an array of
options:
$email = new Email();
$email->profile(['from' => 'me@example.org', 'transport' => 'my_custom']);
// Or in constructor
$email = new Email(['from' => 'me@example.org', 'transport' => 'my_custom']);
Changed in version 3.1: The default email profile is automatically set when an Email instance is created.
584
Configuring Transports
static Cake\Mailer\Email::configTransport($key, $config = null)
Email messages are delivered by transports. Different transports allow you to send messages via PHPs
mail() function, SMTP servers, or not at all which is useful for debugging. Configuring transports allows
you to keep configuration data out of your application code and makes deployment simpler as you can
simply change the configuration data. An example transport configuration looks like:
use Cake\Mailer\Email;
// Sample Mail configuration
Email::configTransport('default', [
'className' => 'Mail'
]);
// Sample smtp configuration.
Email::configTransport('gmail', [
'host' => 'ssl://smtp.gmail.com',
'port' => 465,
'username' => 'my@gmail.com',
'password' => 'secret',
'className' => 'Smtp'
]);
You can configure SSL SMTP servers, like Gmail. To do so, put the ssl:// prefix in the host and configure
the port value accordingly. You can also enable TLS SMTP using the tls option:
use Cake\Mailer\Email;
Email::configTransport('gmail', [
'host' => 'smtp.gmail.com',
'port' => 587,
'username' => 'my@gmail.com',
'password' => 'secret',
'className' => 'Smtp',
'tls' => true
]);
The above configuration would enable TLS communication for email messages.
Warning: You will need to have access for less secure apps enabled in your Google account for this to
work: Allowing less secure apps to access your account127 .
127
https://support.google.com/accounts/answer/6010255
Note: To use SSL + SMTP, you will need to have the SSL configured in your PHP install.
Configuration options can also be provided as a DSN string. This is useful when working with environment
Configuration
585
When using a DSN string you can define any additional parameters/options as query string arguments.
static Cake\Mailer\Email::dropTransport($key)
Once configured, transports cannot be modified. In order to modify a transport you must first drop it and
then reconfigure it.
Configuration Profiles
Defining delivery profiles allows you to consolidate common email settings into re-usable profiles. Your
application can have as many profiles as necessary. The following configuration keys are used:
'from': Email or array of sender. See Email::from().
'sender': Email or array of real sender. See Email::sender().
'to': Email or array of destination. See Email::to().
'cc': Email or array of carbon copy. See Email::cc().
'bcc': Email or array of blind carbon copy. See Email::bcc().
'replyTo': Email or array to reply the e-mail. See Email::replyTo().
'readReceipt': Email address or an array of addresses to receive the receipt of read. See
Email::readReceipt().
'returnPath': Email address or an array of addresses to return if have some error.
Email::returnPath().
See
See
'template':
If you are using rendered content,
Email::template().
See
Setting Headers
In Email you are free to set whatever headers you want. When migrating to use Email, do not forget to put
the X- prefix in your headers.
See Email::setHeaders() and Email::addHeaders()
The
above
would
use
src/Template/Email/html/welcome.ctp
for
the
view
and
src/Template/Layout/Email/html/fancy.ctp for the layout. You can send multipart templated email
messages as well:
$email = new Email();
$email->template('welcome', 'fancy')
->emailFormat('both')
->to('bob@example.com')
->from('app@domain.com')
->send();
Setting Headers
587
You can use helpers in emails as well, much like you can in normal template files. By default only the
HtmlHelper is loaded. You can load additional helpers using the helpers() method:
$email->helpers(['Html', 'Custom', 'Text']);
When setting helpers be sure to include Html or it will be removed from the helpers loaded in your email
template.
If you want to send email using templates in a plugin you can use the familiar plugin syntax to do so:
$email = new Email();
$email->template('Blog.new_comment', 'Blog.auto_message');
The above would use templates from the Blog plugin as an example.
In some cases, you might need to override the default template provided by plugins. You can do this using
themes by telling Email to use appropriate theme using Email::theme() method:
$email = new Email();
$email->template('Blog.new_comment', 'Blog.auto_message');
$email->theme('TestTheme');
This allows you to override the new_comment template in your theme without modifying the Blog plugin.
The template file needs to be created in the following path:
src/Template/Plugin/TestTheme/Blog/Email/text/new_comment.ctp.
Sending Attachments
Cake\Mailer\Email::attachments($attachments = null)
588
You can attach files to email messages as well. There are a few different formats depending on what kind of
files you have, and how you want the filenames to appear in the recipients mail client:
1. String: $email->attachments('/full/file/path/file.png') will attach this file
with the name file.png.
2. Array: $email->attachments(['/full/file/path/file.png']) will have the same
behavior as using a string.
3. Array
with
key:
$email->attachments(['photo.png' =>
'/full/some_hash.png']) will attach some_hash.png with the name photo.png. The
recipient will see photo.png, not some_hash.png.
4. Nested arrays:
$email->attachments([
'photo.png' => [
'file' => '/full/some_hash.png',
'mimetype' => 'image/png',
'contentId' => 'my-unique-id'
]
]);
The above will attach the file with different mimetype and with custom Content ID (when set the
content ID the attachment is transformed to inline). The mimetype and contentId are optional in this
form.
4.1. When you are using the contentId, you can use the file in the HTML body like <img
src="cid:my-content-id">.
4.2. You can use the contentDisposition option to disable the Content-Disposition
header for an attachment. This is useful when sending ical invites to clients using outlook.
4.3 Instead of the file option you can provide the file contents as a string using the data option.
This allows you to attach files without needing file paths to them.
Using Transports
Transports are classes designed to send the e-mail over some protocol or method. CakePHP supports the
Mail (default), Debug and SMTP transports.
To configure your method, you must use the Cake\Mailer\Email::transport() method or have
the transport in your configuration:
$email = new Email();
// Use a named transport already configured using Email::configTransport()
$email->transport('gmail');
// Use a constructed object.
$transport = new DebugTransport();
$email->transport($transport);
Using Transports
589
You must implement the method send(Email $email) with your custom logic. Optionally, you can
implement the config($config) method. config() is called before send() and allows you to accept
user configurations. By default, this method puts the configuration in protected attribute $_config.
If you need to call additional methods on the transport before send, you can
Cake\Mailer\Email::transportClass() to get an instance of the transport. Example:
use
$yourInstance = $email->transport('your')->transportClass();
$yourInstance->myCustomMethod();
$email->send();
590
You can create your configuration using Cake\Mailer\Email::config(), or use an array with all
options that you need and use the static method Email::deliver(). Example:
Email::deliver('you@example.com', 'Subject', 'Message', ['from' =>
'me@example.com']);
This method will send an email to you@example.com, from me@example.com with subject Subject
and content Message.
The return of deliver() is a Cake\Mailer\Email instance with all configurations set. If you do not
want to send the email right away, and wish to configure a few things before sending, you can pass the 5th
parameter as false.
The 3rd parameter is the content of message or an array with variables (when using rendered content).
The 4th parameter can be an array with the configurations or a string with the name of configuration in
Configure.
If you want, you can pass the to, subject and message as null and do all configurations in the 4th parameter
(as array or using Configure). Check the list of configurations to see all accepted configs.
591
{
public function welcome($user)
{
$this
->to($user->email)
->subject(sprintf('Welcome %s', $user->name))
->template('welcome_mail') // By default template with same name
as method name is used.
->layout('custom');
}
public function resetPassword($user)
{
$this
->to($user->email)
->subject('Reset password')
->set(['token' => $user->token]);
}
}
In our example we have created two methods, one for sending a welcome email, and another for sending a password reset email. Each of these methods expect a user Entity and utilizes its properties for
configuring each email.
We are now able to use our UserMailer to send out our user-related emails from anywhere in our application. For example, if we wanted to send our welcome email we could do the following:
namespace App\Controller;
use Cake\Mailer\MailerAwareTrait;
class UsersController extends AppController
{
use MailerAwareTrait;
public function register()
{
$user = $this->Users->newEntity();
if ($this->request->is('post')) {
$user = $this->Users->patchEntity($user, $this->request->data())
if ($this->Users->save($user)) {
$this->getMailer('User')->send('welcome', [$user]);
}
}
$this->set('user', $user);
}
}
If we wanted to completely separate sending a user their welcome email from our applications code, we
can have our UserMailer subscribe to the Model.afterSave event. By subscribing to an event, we
can keep our applications user-related classes completely free of email-related logic and instructions. For
example, we could add the following to our UserMailer:
592
593
594
CHAPTER 19
Many of PHPs internal methods use errors to communicate failures. These errors will need to be trapped
and dealt with. CakePHP comes with default error trapping that prints and or logs errors as they occur. This
same error handler is used to catch uncaught exceptions from controllers and other parts of your application.
595
debug level. The default behavior for fatal errors is show a page to internal server error (debug disabled)
or a page with the message, file and line (debug enabled).
Note: If you use a custom error handler, the supported options will depend on your handler.
The BaseErrorHandler defines two abstract methods. _displayError() is used when errors are
triggered. The _displayException() method is called when there is an uncaught exception.
596
// In src/Error/AppError.php
namespace App\Error;
use Cake\Error\BaseErrorHandler;
class AppError extends BaseErrorHandler
{
// Other methods.
public function handleFatalError($code, $description, $file, $line)
{
return 'A fatal error has happened';
}
}
Exception Classes
There are a number of exception classes in CakePHP. The built in exception handling will capture any
uncaught exceptions and render a useful page. Exceptions that do not specifically use a 400 range code, will
be treated as an Internal Server Error.
Exception Classes
597
exception Cake\Network\Exception\NotAcceptableException
Used for doing a 406 Not Acceptable error.
New in version 3.1.7: NotAcceptableException has been added.
exception Cake\Network\Exception\ConflictException
Used for doing a 409 Conflict error.
New in version 3.1.7: ConflictException has been added.
exception Cake\Network\Exception\GoneException
Used for doing a 410 Gone error.
New in version 3.1.7: GoneException has been added.
For more details on HTTP 4xx error status codes see RFC 2616#section-10.4128 .
exception Cake\Network\Exception\InternalErrorException
Used for doing a 500 Internal Server Error.
exception Cake\Network\Exception\NotImplementedException
Used for doing a 501 Not Implemented Errors.
exception Cake\Network\Exception\ServiceUnavailableException
Used for doing a 503 Service Unavailable error.
New in version 3.1.7: Service Unavailable has been added.
For more details on HTTP 5xx error status codes see RFC 2616#section-10.5129 .
You can throw these exceptions from your controllers to indicate failure states, or HTTP errors. An example
use of the HTTP exceptions could be rendering 404 pages for items that have not been found:
use Cake\Network\Exception\NotFoundException;
public function view($id = null)
{
$article = $this->Articles->findById($id)->first();
if (empty($article)) {
throw new NotFoundException(__('Article not found'));
}
$this->set('article', $article);
$this->set('_serialize', ['article']);
}
By using exceptions for HTTP errors, you can keep your code both clean, and give RESTful responses to
client applications and users.
598
https://tools.ietf.org/html/rfc2616.html#section-10.4
https://tools.ietf.org/html/rfc2616.html#section-10.5
exception Cake\View\Exception\MissingViewException
The chosen view class could not be found.
exception Cake\View\Exception\MissingTemplateException
The chosen template file could not be found.
exception Cake\View\Exception\MissingLayoutException
The chosen layout could not be found.
exception Cake\View\Exception\MissingHelperException
The chosen helper could not be found.
exception Cake\View\Exception\MissingElementException
The chosen element file could not be found.
exception Cake\View\Exception\MissingCellException
The chosen cell class could not be found.
exception Cake\View\Exception\MissingCellViewException
The chosen cell view file could not be found.
exception Cake\Controller\Exception\MissingComponentException
A configured component could not be found.
exception Cake\Controller\Exception\MissingActionException
The requested controller action could not be found.
exception Cake\Controller\Exception\PrivateActionException
Accessing private/protected/_ prefixed actions.
exception Cake\Console\Exception\ConsoleException
A console library class encounter an error.
exception Cake\Console\Exception\MissingTaskException
A configured task could not found.
exception Cake\Console\Exception\MissingShellException
The shell class could not be found.
exception Cake\Console\Exception\MissingShellMethodException
The chosen shell class has no method of that name.
exception Cake\Database\Exception\MissingConnectionException
A models connection is missing.
exception Cake\Database\Exception\MissingDriverException
A database driver could not be found.
exception Cake\Database\Exception\MissingExtensionException
A PHP extension is missing for the database driver.
exception Cake\ORM\Exception\MissingTableException
A models table could not be found.
exception Cake\ORM\Exception\MissingEntityException
A models entity could not be found.
599
exception Cake\ORM\Exception\MissingBehaviorException
A models behavior could not be found.
exception Cake\Datasource\Exception\RecordNotFoundException
The requested record could not be found. This will also set HTTP response headers to 404.
exception Cake\Routing\Exception\MissingControllerException
The requested controller could not be found.
exception Cake\Routing\Exception\MissingRouteException
The requested URL cannot be reverse routed or cannot be parsed.
exception Cake\Routing\Exception\MissingDispatcherFilterException
The dispatcher filter could not be found.
exception Cake\Core\Exception\Exception
Base exception class in CakePHP. All framework layer exceptions thrown by CakePHP will extend
this class.
These exception classes all extend Exception. By extending Exception, you can create your own framework errors. All of the standard Exceptions that CakePHP will throw also extend Exception.
Cake\Core\Exception\Exception::responseHeader($header = null, $value = null)
See Cake\Network\Request::header()
All Http and Cake exceptions extend the Exception class, which has a method to add headers to the response.
For instance when throwing a 405 MethodNotAllowedException the rfc2616 says:
"The response MUST include an Allow header containing a list of valid
methods for the requested resource."
The above would cause the configured exception handler to catch and process the NotFoundException.
By default this will create an error page, and log the exception.
600
Exception Renderer
class Cake\Core\Exception\ExceptionRenderer(Exception $exception)
The ExceptionRenderer class with the help of ErrorController takes care of rendering the error pages
for all the exceptions thrown by you application.
The error page views are located at src/Template/Error/. For all 4xx and 5xx errors the template files
error400.ctp and error500.ctp are used respectively. You can customize them as per your needs. By
default your src/Template/Layout/default.ctp is used for error pages too. If for example, you want to use
another layout src/Template/Layout/my_error.ctp for your error pages, simply edit the error views and
add the statement $this->layout = 'my_error'; to the error400.ctp and error500.ctp.
Each framework layer exception has its own view file located in the core templates but you really dont need
to bother customizing them as they are used only during development. With debug turned off all framework
layer exceptions are converted to InternalErrorException.
When caught by the built in exception handler, you would get a $widget variable in your error view
template. In addition if you cast the exception as a string or use its getMessage() method you will get
Seems that Pointy is missing.. This allows you to quickly create your own rich development
errors, just like CakePHP uses internally.
130
http://php.net/manual/en/spl.exceptions.php
Exception Renderer
601
Will create a 501 response code, you can use any HTTP status code you want. In development, if your
exception doesnt have a specific template, and you use a code equal to or greater than 500 you will see the
error500.ctp template. For any other error code youll get the error400.ctp template. If you have defined
an error template for your custom exception, that template will be used in development mode. If youd like
your own exception handling logic even in production, see the next section.
602
// In config/app.php
'Error' => [
'exceptionRenderer' => 'App\Error\AppExceptionRenderer',
// ...
],
// ...
The above would handle any exceptions of the type MissingWidgetException, and allow you to
provide custom display/handling logic for those application exceptions. Exception handling methods get
the exception being handled as their argument. Your custom exception rendering can return either a string
or a Response object. Returning a Response will give you full control over the response.
Note: Your custom renderer should expect an exception in its constructor, and implement a render method.
Failing to do so will cause additional errors.
If you are using a custom exception handling, configuring the renderer will have no effect. Unless you
reference it inside your implementation.
603
// in config/app.php
'Error' => [
'exceptionRenderer' => 'App\Error\AppExceptionRenderer',
// ...
],
// ...
The error controller, whether custom or conventional, is used to render the error page view and receives all
the standard request life-cycle events.
Logging Exceptions
Using the built-in exception handling, you can log all the exceptions that are dealt with by ErrorHandler
by setting the log option to true in your config/app.php. Enabling this will log every exception to
Cake\Log\Log and the configured loggers.
Note: If you are using a custom exception handler this setting will have no effect. Unless you reference it
inside your implementation.
604
CHAPTER 20
Events System
Creating maintainable applications is both a science and an art. It is well-known that a key for having good
quality code is making your objects loosely coupled and strongly cohesive at the same time. Cohesion
means that all methods and properties for a class are strongly related to the class itself and it is not trying
to do the job other objects should be doing, while loosely coupling is the measure of how little a class is
wired to external objects, and how much that class is depending on them.
There are certain cases where you need to cleanly communicate with other parts of an application, without
having to hard code dependencies, thus losing cohesion and increasing class coupling. Using the Observer
pattern, which allows objects to notify other objects and anonymous listeners about changes is a useful
pattern to achieve this goal.
Listeners in the observer pattern can subscribe to events and choose to act upon them if they are relevant. If
you have used JavaScript, there is a good chance that you are already familiar with event driven programming.
CakePHP emulates several aspects of how events are triggered and managed in popular JavaScript libraries
such as jQuery. In the CakePHP implementation, an event object is dispatched to all listeners. The event
object holds information about the event, and provides the ability to stop event propagation at any point.
Listeners can register themselves or can delegate this task to other objects and have the chance to alter the
state and the event itself for the rest of the callbacks.
The event subsystem is at the heart of Model, Behavior, Controller, View and Helper callbacks. If youve
ever used any of them, you are already somewhat familiar with events in CakePHP.
605
Instead, you can use events to allow you to cleanly separate the concerns of your code and allow additional
concerns to hook into your plugin using events. For example, in your Cart plugin you have an Orders model
that deals with creating orders. Youd like to notify the rest of the application that an order has been created.
To keep your Orders model clean you could use events:
// Cart/Model/Table/OrdersTable.php
namespace Cart\Model\Table;
use Cake\Event\Event;
use Cake\ORM\Table;
class OrdersTable extends Table
{
public function place($order)
{
if ($this->save($order)) {
$this->Cart->remove($order);
$event = new Event('Model.Order.afterPlace', $this, [
'order' => $order
]);
$this->eventManager()->dispatch($event);
return true;
}
return false;
}
}
The above code allows you to notify the other parts of the application that an order has been created. You
can then do tasks like send email notifications, update stock, log relevant statistics and other tasks in separate
objects that focus on those concerns.
Each model has a separate event manager, while the View and Controller share one. This allows model
events to be self contained, and allow components or controllers to act upon events created in the view if
necessary.
606
Cake\Event\EventManager. Listeners attached to the global dispatcher will be fired before instance
listeners at the same priority. You can access the global manager using a static method:
// In any configuration file or piece of code that executes before the event
use Cake\Event\EventManager;
EventManager::instance()->on(
'Model.Order.afterPlace',
$aCallback
);
One important thing you should consider is that there are events that will be triggered having the same name
but different subjects, so checking it in the event object is usually required in any function that gets attached
globally in order to prevent some bugs. Remember that with the flexibility of using the global manager,
some additional complexity is incurred.
Cake\Event\EventManager::dispatch() method accepts the event object as an argument and
notifies all listener and callbacks passing this object along. The listeners will handle all the extra logic
around the afterPlace event, you can log the time, send emails, update user statistics possibly in separate
objects and even delegating it to offline tasks if you have the need.
Tracking Events
To keep a list of events that are fired on a particular EventManager, you can enable event tracking. To do
so, simply attach an Cake\Event\EventList to the manager:
EventManager::instance()->setEventList(new EventList());
After firing an event on the manager, you can retrieve it from the event list:
$eventsFired = EventManager::instance()->getEventList();
$firstEvent = $eventsFired[0];
Tracking
can
be
disabled
by
removing
Cake\Event\EventList::trackEvents(false).
the
event
list
or
calling
Core Events
There are a number of core events within the framework which your application can listen to. Each layer of
CakePHP emits events that you can use in your application.
ORM/Model events
Controller events
View events
Core Events
607
Registering Listeners
Listeners are the preferred way to register callbacks for an event. This is done by implementing the
Cake\Event\EventListenerInterface interface in any class you wish to register some callbacks.
Classes implementing it need to provide the implementedEvents() method. This method must return
an associative array with all event names that the class will handle.
To continue our previous example, lets imagine we have a UserStatistic class responsible for calculating
a users purchasing history, and compiling into global site statistics. This is a great place to use a listener
class. Doing so allows you to concentrate the statistics logic in one place and react to events as necessary.
Our UserStatistics listener might start out like:
use Cake\Event\EventListenerInterface;
class UserStatistic implements EventListenerInterface
{
public function implementedEvents()
{
return [
'Model.Order.afterPlace' => 'updateBuyStatistic',
];
}
public function updateBuyStatistic($event, $order)
{
// Code to update statistics
}
}
// Attach the UserStatistic object to the Order's event manager
$statistics = new UserStatistic();
$this->Orders->eventManager()->on($statistics);
As you can see in the above code, the on() function will accept instances of the EventListener interface. Internally, the event manager will use implementedEvents() to attach the correct callbacks.
608
);
});
In addition to anonymous functions you can use any other callable type that PHP supports:
$events = [
'email-sending' => 'EmailSender::sendBuyEmail',
'inventory' => [$this->InventoryManager, 'decrement'],
];
foreach ($events as $callable) {
$eventManager->on('Model.Order.afterPlace', $callable);
}
When working with plugins that dont trigger specific events, you can leverage event listeners on the default
events. Lets take an example UserFeedback plugin which handles feedback forms from users. From your
application you would like to know when a Feedback record has been saved and ultimately act on it. You
can could listen to the global Model.afterSave event. However, you can take a more direct approach
and only listen to the event you really need:
// You can create the following before the
// save operation, ie. config/bootstrap.php
use Cake\ORM\TableRegistry;
// If sending emails
use Cake\Mailer\Email;
TableRegistry::get('ThirdPartyPlugin.Feedbacks')
->eventManager()
->on('Model.afterSave', function($event, $entity)
{
// For example we can send an email to the admin
$email = new Email('default');
$email->from('info@yoursite.com' => 'Your Site')
->to('admin@yoursite.com')
->subject('New Feedback - Your Site')
->send('Body of message');
});
Registering Listeners
609
if (!empty($events)) {
// Perform logic related to presence of 'Verification' event listener.
// For example removing the listener if present.
$this->eventManager()->off('User.Verification');
} else {
// Perform logic related to absence of 'Verification' event listener
}
Establishing Priorities
In some cases you might want to control the order that listeners are invoked. For instance, if we go back to
our user statistics example. It would be ideal if this listener was called at the end of the stack. By calling
it at the end of the listener stack, we can ensure that the event was not cancelled, and that no other listeners
raised exceptions. We can also get the final state of the objects in the case that other listeners have modified
the subject or event object.
Priorities are defined as an integer when adding a listener. The higher the number, the later the method will
be fired. The default priority for all listeners is 10. If you need your method to be run earlier, using any
value below this default will work. On the other hand if you desire to run the callback after the others, using
a number above 10 will do.
If two callbacks happen to have the same priority value, they will be executed with a the order
they were attached. You set priorities using the on() method for callbacks, and declaring it in the
implementedEvents() function for event listeners:
// Setting priority for a callback
$callback = [$this, 'doSomething'];
$this->eventManager()->on(
'Model.Order.afterPlace',
['priority' => 2],
$callback
);
// Setting priority for a listener
class UserStatistic implements EventListenerInterface
{
public function implementedEvents()
{
return [
'Model.Order.afterPlace' => [
'callable' => 'updateBuyStatistic',
'priority' => 100
],
];
610
}
}
As you see, the main difference for EventListener objects is that you need to use an array for specifying
the callable method and the priority preference. The callable key is a special array entry that the manager
will read to know what function in the class it should be calling.
The listeners of the View.afterRender callback should have the following signature:
function (Event $event, $viewFileName)
Each value provided to the Event constructor will be converted into function parameters in the order they
appear in the data array. If you use an associative array, the result of array_values will determine the
function argument order.
Note: Unlike in 2.x, converting event data to listener arguments is the default behavior and cannot be
disabled.
Dispatching Events
Once you have obtained an instance of an event manager you can dispatch events using
Event\EventManager::dispatch(). This method takes an instance of the Cake\Event\Event
class. Lets look at dispatching an event:
// An event listener has to be instantiated before dispatching an event.
// Create a new event and dispatch it.
$event = new Event('Model.Order.afterPlace', $this, [
'order' => $order
]);
$this->eventManager()->dispatch($event);
Cake\Event\Event accepts 3 arguments in its constructor. The first one is the event name, you should
try to keep this name as unique as possible, while making it readable. We suggest a convention as follows: Layer.eventName for general events happening at a layer level (e.g. Controller.startup,
View.beforeRender) and Layer.Class.eventName for events happening in specific classes on a
layer, for example Model.User.afterRegister or Controller.Courses.invalidAccess.
Dispatching Events
611
The second argument is the subject, meaning the object associated to the event, usually when it is the
same class triggering events about itself, using $this will be the most common case. Although a Component could trigger controller events too. The subject class is important because listeners will get immediate
access to the object properties and have the chance to inspect or change them on the fly.
Finally, the third argument is any additional event data.This can be any data you consider useful to pass
around so listeners can act upon it. While this can be an argument of any type, we recommend passing an
associative array.
The Event\EventManager::dispatch() method accepts an event object as an argument and notifies all subscribed listeners.
Stopping Events
Much like DOM events, you may want to stop an event to prevent additional listeners from being notified.
You can see this in action during model callbacks (e.g. beforeSave) in which it is possible to stop the saving
operation if the code detects it cannot proceed any further.
In order to stop events you can either return false in your callbacks or call the stopPropagation()
method on the event object:
public function doSomething($event)
{
// ...
return false; // Stops the event
}
public function updateBuyStatistic($event)
{
// ...
$event->stopPropagation();
}
Stopping an event will prevent any additional callbacks from being called. Additionally the code triggering
the event may behave differently based on the event being stopped or not. Generally it does not make
sense to stop after events, but stopping before events is often used to prevent the entire operation from
occurring.
To check if an event was stopped, you call the isStopped() method in the event object:
public function place($order)
{
$event = new Event('Model.Order.beforePlace', $this, ['order' => $order]);
$this->eventManager()->dispatch($event);
if ($event->isStopped()) {
return false;
}
if ($this->Orders->save($order)) {
// ...
}
// ...
}
612
In the previous example the order would not get saved if the event is stopped during the beforePlace
process.
It is possible to alter any event object property and have the new data passed to the next callback. In most of
the cases, providing objects as event data or result and directly altering the object is the best solution as the
reference is kept the same and modifications are shared across all callback calls.
Dispatching Events
613
// Attaching a function
$this->eventManager()->on('My.event', [$this, 'doSomething']);
// Detaching the function
$this->eventManager()->off('My.event', [$this, 'doSomething']);
// Attaching an anonymous function.
$myFunction = function ($event) { ... };
$this->eventManager()->on('My.event', $myFunction);
// Detaching the anonymous function
$this->eventManager()->off('My.event', $myFunction);
// Adding a EventListener
$listener = new MyEventLister();
$this->eventManager()->on($listener);
// Detaching a single event key from a listener
$this->eventManager()->off('My.event', $listener);
// Detaching all callbacks implemented by a listener
$this->eventManager()->off($listener);
Conclusion
Events are a great way of separating concerns in your application and make classes both cohesive and decoupled from each other. Events can be utilized to de-couple application code and make extensible plugins.
Keep in mind that with great power comes great responsibility. Using too many events can make debugging
harder and require additional integration testing.
Additional Reading
Behaviors
Components
Helpers
Testing Events
614
CHAPTER 21
One of the best ways for an application to reach a larger audience is to cater to multiple languages. This can
often prove to be a daunting task, but the internationalization and localization features in CakePHP make it
much easier.
First, its important to understand some terminology. Internationalization refers to the ability of an application to be localized. The term localization refers to the adaptation of an application to meet specific language
(or culture) requirements (i.e. a locale). Internationalization and localization are often abbreviated as i18n
and l10n respectively; 18 and 10 are the number of characters between the first and last character.
Setting Up Translations
There are only a few steps to go from a single-language application to a multi-lingual application, the first
of which is to make use of the __() function in your code. Below is an example of some code for a
single-language application:
<h2>Popular Articles</h2>
To internationalize your code, all you need to do is to wrap strings in __() like so:
<h2><?= __('Popular Articles') ?></h2>
Doing nothing else, these two code examples are functionally identical - they will both send the same content
to the browser. The __() function will translate the passed string if a translation is available, or return it
unmodified.
Language Files
Translations can be made available by using language files stored in the application. The default format for
CakePHP translation files is the Gettext131 format. Files need to be placed under src/Locale/ and within this
directory, there should be a subfolder for each language the application needs to support:
131
http://en.wikipedia.org/wiki/Gettext
615
/src
/Locale
/en_US
default.po
/en_GB
default.po
validation.po
/es
default.po
The default domain is default, therefore the locale folder should at least contain the default.po file as
shown above. A domain refers to any arbitrary grouping of translation messages. When no group is used,
then the default group is selected.
The core strings messages extracted from the CakePHP library can be stored separately in a file named
cake.po in src/Locale/. The CakePHP localized library132 houses translations for the client-facing translated
strings in the core (the cake domain). To use these files, link or copy them into their expected location:
src/Locale/<locale>/cake.po. If your locale is incomplete or incorrect, please submit a PR in this repository
to fix it.
Plugins can also contain translation files, the convention is to use the under_scored version of the plugin
name as the domain for the translation messages:
MyPlugin
/src
/Locale
/fr
my_plugin.po
/de
my_plugin.po
Translation folders can either be the two letter ISO code of the language or the full locale name such as
fr_FR, es_AR, da_DK which contains both the language and the country where it is spoken.
An example translation file could look like this:
msgid "My name is {0}"
msgstr "Je m'appelle {0}"
msgid "I'm {0,number} years old"
msgstr "J'ai {0,number} ans"
616
https://github.com/cakephp/localized
This will control several aspects of the application, including the default translations language, the date
format, number format and currency whenever any of those is displayed using the localization libraries that
CakePHP provides.
This will also change how numbers and dates are formatted when using one of the localization tools.
If you need to group your messages, for example, translations inside a plugin, you can use the __d()
function to fetch messages from another domain:
echo __d('my_plugin', 'Trending right now');
Sometimes translations strings can be ambiguous for people translating them. This can happen if two strings
are identical but refer to different things. For example, letter has multiple meanings in English. To solve
that problem, you can use the __x() function:
echo __x('written communication', 'He read the first letter');
echo __x('alphabet learning', 'He read the first letter');
The first argument is the context of the message and the second is the message to be translated.
617
Markers are numeric, and correspond to the keys in the passed array. You can also pass variables as independent arguments to the function:
echo __("Small step for {0}, Big leap for {1}", 'Man', 'Humanity');
The ' (single quote) character acts as an escape code in translation messages. Any variables between single
quotes will not be replaced and is treated as literal text. For example:
__("This variable '{0}' be replaced.", 'will not');
These functions take advantage of the ICU MessageFormatter133 so you can translate messages and localize
dates, numbers and currency at the same time:
echo __(
'Hi {0}, your balance on the {1,date} is {2,number,currency}',
['Charles', new FrozenTime('2014-01-13 11:12:00'), 1354.37]
);
// Returns
Hi Charles, your balance on the Jan 13, 2014, 11:12 AM is $ 1,354.37
Numbers in placeholders can be formatted as well with fine grain control of the output:
echo __(
'You have traveled {0,number} kilometers in {1,number,integer} weeks',
[5423.344, 5.1]
);
// Returns
You have traveled 5,423.34 kilometers in 5 weeks
echo __('There are {0,number,#,###} people on earth', 6.1 * pow(10, 8));
133
618
http://php.net/manual/en/messageformatter.format.php
// Returns
There are 6,100,000,000 people on earth
This is the list of formatter specifiers you can put after the word number:
integer: Removes the decimal part
currency: Puts the locale currency symbol and rounds decimals
percent: Formats the number as a percentage
Dates can also be formatted by using the word date after the placeholder number. A list of extra options
follows:
short
medium
long
full
The word time after the placeholder number is also accepted and it understands the same options as date.
Note:
Named placeholders are supported in PHP 5.5+ and are formatted as {name}. When using named placeholders pass the variables in an array using key/value pairs, for example ['name' =>
'Sara','age' => 12].
It is recommended to use PHP 5.5 or higher when making use of internationalization features in CakePHP.
The php5-intl extension must be installed and the ICU version should be above 48.x.y (to check the ICU
version Intl::getIcuVersion()).
Plurals
One crucial part of internationalizing your application is getting your messages pluralized correctly depending on the language they are shown. CakePHP provides a couple ways to correctly select plurals in your
messages.
Using ICU Plural Selection
The first one is taking advantage of the ICU message format that comes by default in the translation functions. In the translations file you could have the following strings
msgid "{0,plural,=0{No records found} =1{Found 1 record} other{Found #
records}}"
msgstr "{0,plural,=0{Ningn resultado} =1{1 resultado} other{# resultados}}"
msgid "{placeholder,plural,=0{No records found} =1{Found 1 record} other
{Found {1} records}}"
msgstr "{placeholder,plural,=0{Ningn resultado} =1{1 resultado} other{{1}
resultados}}"
619
And in the application use the following code to output either of the translations for such string:
__('{0,plural,=0{No records found }=1{Found 1 record} other{Found # records}}
', [0]);
// Returns "Ningn resultado" as the argument {0} is 0
__('{0,plural,=0{No records found} =1{Found 1 record} other{Found # records}}
', [1]);
// Returns "1 resultado" because the argument {0} is 1
__('{placeholder,plural,=0{No records found} =1{Found 1 record} other{Found
{1} records}}', [0, 'many', 'placeholder' => 2])
// Returns "many resultados" because the argument {placeholder} is 2 and
// argument {1} is 'many'
A closer look to the format we just used will make it evident how messages are built:
{ [count placeholder],plural, case1{message} case2{message} case3{...} ... }
The [count placeholder] can be the array key number of any of the variables you pass to the translation function. It will be used for selecting the correct plural form.
Note that to reference [count placeholder] within {message} you have to use #.
You can of course use simpler message ids if you dont want to type the full plural selection sequence in
your code
msgid "search.results"
msgstr "{0,plural,=0{Ningn resultado} =1{1 resultado} other{{1} resultados}}"
The latter version has the downside that there is a need to have a translation messages file even for the
default language, but has the advantage that it makes the code more readable and leaves the complicated
plural selection strings in the translation files.
Sometimes using direct number matching in plurals is impractical. For example, languages like Arabic
require a different plural when you refer to few things and other plural form for many things. In those cases
you can use the ICU matching aliases. Instead of writing:
=0{No results} =1{...} other{...}
620
Make sure you read the Language Plural Rules Guide134 to get a complete overview of the aliases you can
use for each language.
Using Gettext Plural Selection
The second plural selection format accepted is using the built-in capabilities of Gettext. In this case, plurals
will be stored in the .po file by creating a separate message translation line per plural form:
# One message identifier for singular
msgid "One file removed"
# Another one for plural
msgid_plural "{0} files removed"
# Translation in singular
msgstr[0] "Un fichero eliminado"
# Translation in plural
msgstr[1] "{0} ficheros eliminados"
When using this other format, you are required to use another translation function:
// Returns: "10 ficheros eliminados"
$count = 10;
__n('One file removed', '{0} files removed', $count, $count);
// It is also possible to use it inside a domain
__dn('my_plugin', 'One file removed', '{0} files removed', $count, $count);
The number inside msgstr[] is the number assigned by Gettext for the plural form of the language. Some
languages have more than two plural forms, for example Croatian:
msgid "One file removed"
msgid_plural "{0} files removed"
msgstr[0] "{0} datoteka je uklonjena"
msgstr[1] "{0} datoteke su uklonjene"
msgstr[2] "{0} datoteka je uklonjeno"
Please visit the Launchpad languages page135 for a detailed explanation of the plural form numbers for each
language.
http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html
https://translations.launchpad.net/+languages
621
use Aura\Intl\Package;
I18n::translator('animals', 'fr_FR', function () {
$package = new Package(
'default', // The formatting strategy (ICU)
'default' // The fallback domain
);
$package->setMessages([
'Dog' => 'Chien',
'Cat' => 'Chat',
'Bird' => 'Oiseau'
...
]);
return $package;
});
The above code can be added to your config/bootstrap.php so that translations can be found before any
translation function is used. The absolute minimum that is required for creating a translator is that the
loader function should return a Aura\Intl\Package object. Once the code is in place you can use the
translation functions as usual:
I18n::locale('fr_FR');
__d('animals', 'Dog'); // Returns "Chien"
As you see, Package objects take translation messages as an array. You can pass the setMessages()
method however you like: with inline code, including another file, calling another function, etc. CakePHP
provides a few loader functions you can reuse if you just need to change where messages are loaded. For
example, you can still use .po files, but loaded from another location:
use Cake\I18n\MessagesFileLoader as Loader;
// Load messages from src/Locale/folder/sub_folder/filename.po
I18n::translator(
'animals',
'fr_FR',
new Loader('filename', 'folder/sub_folder', 'po')
);
622
The file should be created in the src/I18n/Parser directory of your application. Next, create the translations
file under src/Locale/fr_FR/animals.yaml
Dog: Chien
Cat: Chat
Bird: Oiseau
And finally, configure the translation loader for the domain and locale:
use Cake\I18n\MessagesFileLoader as Loader;
I18n::translator(
'animals',
'fr_FR',
new Loader('animals', 'fr_FR', 'yaml')
);
The above example calls an example external service to load a JSON file with the translations and then just
build a Package object for any locale that is requested in the application.
623
Similarly, you can express Gettext-style plurals using the messages array by having a nested array key per
plural form:
[
'I have read one book' => 'He ledo un libro',
'I have read {0} books' => [
'He ledo un libro',
'He ledo {0} libros'
]
]
The messages to be translated will be passed to the sprintf() function for interpolating the variables:
__('Hello, my name is %s and I am %d years old', 'Jos', 29);
It is possible to set the default formatter for all translators created by CakePHP before they are used for the
first time. This does not include manually created translators using the translator() and config()
methods:
I18n::defaultFormatter('sprintf');
624
Make sure you read the Date & Time and Number sections to learn more about formatting options.
By default dates returned for the ORM results use the Cake\I18n\Time class, so displaying them directly
in you application will be affected by changing the current locale.
The default parsing format is the same as the default string format.
625
// in config/bootstrap.php
DispatcherFactory::add('LocaleSelector');
// Restrict the locales to only en_US, fr_FR
DispatcherFactory::add('LocaleSelector', ['locales' => ['en_US', 'fr_FR']]);
The LocaleSelectorFilter will use the Accept-Language header to automatically set the users
preferred locale. You can use the locale list option to restrict which locales will automatically be used.
626
CHAPTER 22
Logging
While CakePHP core Configure Class settings can really help you see whats happening under the hood,
there are certain times that youll need to log data to the disk in order to find out whats going on. With
technologies like SOAP, AJAX, and REST APIs, debugging can be rather difficult.
Logging can also be a way to find out whats been going on in your application over time. What search
terms are being used? What sorts of errors are my users being shown? How often is a particular query being
executed?
Logging data in CakePHP is easy - the log() function is provided by the LogTrait, which is the common
ancestor for many CakePHP classes. If the context is a CakePHP class (Controller, Component, View,...),
you can log your data. You can also use Log::write() directly. See Writing to Logs.
Logging Configuration
Configuring Log should be done during your applications bootstrap phase. The config/app.php file is
intended for just this. You can define as many or as few loggers as your application needs. Loggers should
be configured using Cake\Core\Log. An example would be:
use Cake\Log\Log;
// Short classname
Log::config('debug', [
'className' => 'File',
'path' => LOGS,
'levels' => ['notice', 'info', 'debug'],
'file' => 'debug',
]);
// Fully namespaced name.
Log::config('error', [
'className' => 'Cake\Log\Engine\FileLog',
'path' => LOGS,
'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'],
627
The above creates two loggers. One named debug the other named error. Each is configured to handle
different levels of messages. They also store their log messages in separate files, so its easy to separate
debug/notice/info logs from more serious errors. See the section on Using Levels for more information on
the different levels and what they mean.
Once a configuration is created you cannot change it. Instead you should drop the configuration and re-create
it using Cake\Log\Log::drop() and Cake\Log\Log::config().
It is also possible to create loggers by providing a closure. This is useful when you need full control over
how the logger object is built. The closure has to return the constructed logger instance. For example:
Log::config('special', function () {
return new \Cake\Log\Engine\FileLog(['path' => LOGS, 'file' => 'log']);
});
Configuration options can also be provided as a DSN string. This is useful when working with environment
variables or PaaS providers:
Log::config('error', [
'url' => 'file:///?levels[]=warning&levels[]=error&file=error',
]);
628
When configuring a log adapter the className parameter is used to locate and load the log handler. All
of the other configuration properties are passed to the log adapters constructor as an array.
namespace App\Log\Engine;
use Cake\Log\Engine\BaseLog;
class DatabaseLog extends BaseLog
{
public function __construct($options = [])
{
parent::__construct($options);
// ...
}
public function log($level, $message, array $context = [])
{
// Write to the database.
}
}
CakePHP requires that all logging adapters implement Psr\Log\LoggerInterface. The class
CakeLogEngineBaseLog is an easy way to satisfy the interface as it only requires you to implement the
log() method. FileLog engine takes the following options:
size Used to implement basic log file rotation. If log file size reaches specified size the existing
file is renamed by appending timestamp to filename and new log file is created. Can be integer bytes
value or human reabable string values like 10MB, 100KB etc. Defaults to 10MB.
rotate Log files are rotated specified times before being removed. If value is 0, old versions are
removed rather then rotated. Defaults to 10.
mask Set the file permissions for created files. If left empty the default permissions are used.
Warning: Engines have the suffix Log. You should avoid class names like SomeLogLog which
include the suffix twice at the end.
Note: You should configure loggers during bootstrapping. config/app.php is the conventional place to
configure log adapters.
In debug mode missing directories will be automatically created to avoid unnecessary errors thrown when
using the FileEngine.
629
The configured directory must be writable by the web server user in order for logging to work correctly.
You can configure additional/alternate FileLog locations when configuring a logger.FileLog accepts a path
which allows for custom paths to be used:
Log::config('custom_path', [
'className' => 'File',
'path' => '/path/to/custom/place/'
]);
Warning: If you do not configure a logging adapter, log messages will not be stored.
Logging to Syslog
In production environments it is highly recommended that you setup your system to use syslog instead of
the files logger. This will perform much better as any writes will be done in a (almost) non-blocking fashion
and your operating system logger can be configured separately to rotate files, pre-process writes or use a
completely different storage for your logs.
Using syslog is pretty much like using the default FileLog engine, you just need to specify Syslog as
the engine to be used for logging. The following configuration snippet will replace the default logger with
syslog, this should be done in the bootstrap.php file:
Log::config('default', [
'engine' => 'Syslog'
]);
The configuration array accepted for the Syslog logging engine understands the following keys:
630
format: An sprintf template strings with two placeholders, the first one for the error level, and the
second for the message itself. This key is useful to add additional information about the server or process in the logged message. For example: %s -Web Server 1 -%s will look like error -Web
Server 1 -An error occurred in this request after replacing the placeholders.
prefix: An string that will be prefixed to every logged message.
flag: An integer flag to be used for opening the connection to the logger, by default LOG_ODELAY
will be used. See openlog documentation for more options
facility: The logging slot to use in syslog. By default LOG_USER is used. See syslog documentation for more options
Writing to Logs
Writing to the log files can be done in 2 different ways.
Cake\Log\Log::write() method:
The second is to use the log() shortcut function available on any using the LogTrait Calling log() will
internally call Log::write():
// Executing this inside a class using LogTrait
$this->log("Something did not work!", 'debug');
All configured log streams are written to sequentially each time Cake\Log\Log::write() is called.
If you have not configured any logging adapters log() will return false and no log messages will be
written.
Using Levels
CakePHP supports the standard POSIX set of logging levels. Each level represents an increasing level of
severity:
Emergency: system is unusable
Alert: action must be taken immediately
Critical: critical conditions
Error: error conditions
Warning: warning conditions
Notice: normal but significant condition
Info: informational messages
Debug: debug-level messages
Writing to Logs
631
You can refer to these levels by name when configuring loggers, and when writing log messages. Alternatively, you can use convenience methods like Cake\Log\Log::error() to clearly indicate the logging
level. Using a level that is not in the above levels will result in an exception.
Logging Scopes
Often times youll want to configure different logging behavior for different subsystems or parts of your
application. Take for example an e-commerce shop. Youll probably want to handle logging for orders and
payments differently than you do other less critical logs.
CakePHP exposes this concept as logging scopes. When log messages are written you can include a scope
name. If there is a configured logger for that scope, the log messages will be directed to those loggers. If a
log message is written to an unknown scope, loggers that handle that level of message will log the message.
For example:
// Configure logs/shops.log to receive all levels, but only
// those with `orders` and `payments` scope.
Log::config('shops', [
'className' => 'File',
'path' => LOGS,
'levels' => [],
'scopes' => ['orders', 'payments'],
'file' => 'shops.log',
]);
// Configure logs/payments.log to receive all levels, but only
// those with `payments` scope.
Log::config('payments', [
'className' => 'File',
'path' => LOGS,
'levels' => [],
'scopes' => ['payments'],
'file' => 'payments.log',
]);
Log::warning('this gets written only to shops.log', ['scope' => ['orders']]);
Log::warning('this gets written to both shops.log and payments.log', ['scope'
=> ['payments']]);
Log::warning('this gets written to both shops.log and payments.log', ['scope'
=> ['unknown']]);
Scopes can also be passed as a single string or a numerically indexed array. Note that using this form will
limit the ability to pass more data as context:
Log::warning('This is a warning', ['orders']);
Log::warning('This is a warning', 'payments');
632
Log API
class Cake\Log\Log
A simple class for writing to logs.
static Cake\Log\Log::config($key, $config)
Parameters
$name (string) Name for the logger being connected, used to drop a logger
later on.
$config (array) Array of configuration information and constructor arguments for the logger.
Get or set the configuration for a Logger. See Logging Configuration for more information.
static Cake\Log\Log::configured
Returns An array of configured loggers.
Get the names of the configured loggers.
static Cake\Log\Log::drop($name)
Parameters
$name (string) Name of the logger you wish to no longer receive messages.
static Cake\Log\Log::write($level, $message, $scope =[])
Write a message into all the configured loggers. $level indicates the level of log message being
created. $message is the message of the log entry being written to. $scope is the scope(s) a log
message is being created in.
static Cake\Log\Log::levels
Call this method without arguments, eg: Log::levels() to obtain current level configuration.
Convenience Methods
The following convenience methods were added to log $message with the appropriate log level.
static Cake\Log\Log::emergency($message, $scope =[])
static Cake\Log\Log::alert($message, $scope =[])
static Cake\Log\Log::critical($message, $scope =[])
static Cake\Log\Log::error($message, $scope =[])
static Cake\Log\Log::warning($message, $scope =[])
static Cake\Log\Log::notice($message, $scope =[])
static Cake\Log\Log::debug($message, $scope =[])
static Cake\Log\Log::info($message, $scope =[])
Log API
633
Logging Trait
trait Cake\Log\LogTrait
A trait that provides shortcut methods for logging
Cake\Log\LogTrait::log($msg, $level = LOG_ERR)
Log a message to the logs. By default messages are logged as ERROR messages. If $msg isnt isnt
a string it will be converted with print_r before being logged.
Using Monolog
Monolog is a popular logger for PHP. Since it implements the same interfaces as the CakePHP loggers, it is
easy to use in your application as the default logger.
After installing Monolog using composer, configure the logger using the Log::config() method:
// config/bootstrap.php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
Log::config('default', function () {
$log = new Logger('app');
$log->pushHandler(new StreamHandler('path/to/your/combined.log'));
return $log;
});
// Optionally stop using the now redundant default loggers
Log::drop('debug');
Log::drop('error');
Use similar methods if you want to configure a different logger for your console:
// config/bootstrap_cli.php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
Log::config('default', function () {
$log = new Logger('cli');
$log->pushHandler(new StreamHandler('path/to/your/combined-cli.log'));
return $log;
});
// Optionally stop using the now redundant default CLI loggers
Configure::delete('Log.debug');
Configure::delete('Log.error');
Note: When using a console specific logger, make sure to conditionally configure your application logger.
634
Using Monolog
635
636
CHAPTER 23
Modelless Forms
class Cake\Form\Form
Most of the time you will have forms backed by ORM entities and ORM tables or other peristent stores, but
there are times when youll need to validate user input and then perform an action if the data is valid. The
most common example of this is a contact form.
Creating a Form
Generally when using the Form class youll want to use a subclass to define your form. This makes testing
easier, and lets you re-use your form. Forms are put into src/Form and usually have Form as a class suffix.
For example, a simple contact form would look like:
// in src/Form/ContactForm.php
namespace App\Form;
use Cake\Form\Form;
use Cake\Form\Schema;
use Cake\Validation\Validator;
class ContactForm extends Form
{
protected function _buildSchema(Schema $schema)
{
return $schema->addField('name', 'string')
->addField('email', ['type' => 'string'])
->addField('body', ['type' => 'text']);
}
protected function _buildValidator(Validator $validator)
{
return $validator->add('name', 'length', [
'rule' => ['minLength', 10],
637
In the above example we see the 3 hook methods that forms provide:
_buildSchema is used to define the schema data that is used by FormHelper to create an HTML
form. You can define field type, length, and precision.
_buildValidator Gets a Cake\Validation\Validator instance that you can attach validators to.
_execute lets you define the behavior you want to happen when execute() is called and the data
is valid.
You can always define additional public methods as you need as well.
638
}
}
In the above example, we use the execute() method to run our forms _execute() method only when
the data is valid, and set flash messages accordingly. We could have also used the validate() method to
only validate the request data:
$isValid = $form->validate($this->request->data);
Values should only be defined if the request method is GET, otherwise you will overwrite your previous
POST Data which might have been incorrect and not been saved.
639
According to how the validator class would have returned the errors, $errors must be in this format:
["fieldName" => ["validatorName" => "The error message to display"]]
Now you will be able to invalidate form fields by setting the fieldName, then set the error messages:
// In a controller
$contact = new ContactForm();
$contact->setErrors(["email" => ["_required" => "Your email is required"]]);
640
$this->Form->create($contact);
$this->Form->input('name');
$this->Form->input('email');
$this->Form->input('body');
$this->Form->button('Submit');
$this->Form->end();
The above would create an HTML form for the ContactForm we defined earlier. HTML forms created with FormHelper will use the defined schema and validator to determine field types, maxlengths, and
validation errors.
641
642
CHAPTER 24
Plugins
CakePHP allows you to set up a combination of controllers, models, and views and release them as a packaged application plugin that others can use in their CakePHP applications. Have a great user management
module, simple blog, or web services module in one of your applications? Package it as a CakePHP plugin
so you can reuse it in other applications and share with the community.
The main tie between a plugin and the application it has been installed into is the applications configuration
(database connection, etc.). Otherwise it operates in its own space, behaving much like it would if it were
an application on its own.
In CakePHP 3.0 each plugin defines its own top-level namespace. For example: DebugKit. By convention,
plugins use their package name as their namespace. If youd like to use a different namespace, you can
configure the plugin namespace, when plugins are loaded.
This would install the latest version of DebugKit and update your composer.json, composer.lock file, update
vendor/cakephp-plugins.php, and update your autoloader.
If the plugin you want to install is not available on packagist.org, you can clone or copy the plugin code
into your plugins directory. Assuming you want to install a plugin named ContactManager, you should
have a folder in plugins named ContactManager. In this directory are the plugins src, tests and any other
directories.
136
http://packagist.org
643
Loading a Plugin
After installing a plugin and setting up the autoloader, you should load the plugin. You can load plugins one
by one, or all of them with a single method:
// In config/bootstrap.php
// Loads a single plugin
Plugin::load('ContactManager');
// Loads a plugin with a vendor namespace at top level.
Plugin::load('AcmeCorp/ContactManager');
// Loads all plugins at once
Plugin::loadAll();
loadAll() loads all plugins available, while allowing you to set certain settings for specific plugins.
load() works similarly, but only loads the plugins you explicitly specify.
Note:
Plugin::loadAll() wont load vendor namespaced plugins that are not defined in
vendor/cakephp-plugins.php.
There is also a handy shell command to enable the plugin. Execute the following line:
bin/cake plugin load ContactManager
This will put the Plugin::load('ContactManager'); snippet in the bootstrap for you.
644
"MyPlugin\\": "./plugins/MyPlugin/src",
"MyPlugin\\Test\\": "./plugins/MyPlugin/tests"
}
If you are using vendor namespaces for your plugins, the namespace to path mapping should resemble the
following:
"psr-4": {
(...)
"AcmeCorp\\Users\\": "./plugins/AcmeCorp/Users/src",
"AcmeCorp\\Users\\Test\\": "./plugins/AcmeCorp/Users/tests"
}
Additionally, you will need to tell Composer to refresh its autoloading cache:
$ php composer.phar dumpautoload
If you are unable to use Composer for any reason, you can also use a fallback autoloading for your plugin:
Plugin::load('ContactManager', ['autoload' => true]);
Plugin Configuration
The load() and loadAll() methods can assist with plugin configuration and routing. Perhaps you want
to load all plugins automatically while specifying custom routes and bootstrap files for certain plugins:
// in config/bootstrap.php
// Using loadAll()
Plugin::loadAll([
'Blog' => ['routes' => true],
'ContactManager' => ['bootstrap' => true],
'WebmasterTools' => ['bootstrap' => true, 'routes' => true],
]);
With either approach you no longer need to manually include() or require() a plugins configuration
or routes file it happens automatically at the right time and place.
You can specify a set of defaults for loadAll() which will apply to every plugin that doesnt have a more
specific configuration.
Plugin Configuration
645
The following example will load the bootstrap file from all plugins, and additionally the routes from the
Blog plugin:
Plugin::loadAll([
['bootstrap' => true],
'Blog' => ['routes' => true]
]);
Note that all files specified should actually exist in the configured plugin(s) or PHP will give warnings for
each file it cannot load. You can avoid potential warnings by using the ignoreMissing option:
Plugin::loadAll([
['ignoreMissing' => true, 'bootstrap' => true],
'Blog' => ['routes' => true]
]);
When loading plugins, the plugin name used should match the namespace. For example, if you have a plugin
with top level namespace Users you would load it using:
Plugin::load('User');
If you prefer to have your vendor name as top level and have a namespace like AcmeCorp/Users, then
you would load the plugin as:
Plugin::load('AcmeCorp/Users');
This will ensure that classnames are resolved properly when using plugin syntax.
Most plugins will indicate the proper procedure for configuring them and setting up the database in their
documentation. Some plugins will require more setup than others.
Using Plugins
You can reference a plugins controllers, models, components, behaviors, and helpers by prefixing the name
of the plugin before
For example, say you wanted to use the ContactManager plugins ContactInfoHelper to output some pretty
contact information in one of your views. In your controller, your $helpers array could look like this:
public $helpers = ['ContactManager.ContactInfo'];
646
Note the name of the plugin folder, ContactManager. It is important that this folder has the same name
as the plugin.
Inside the plugin folder, youll notice it looks a lot like a CakePHP application, and thats basically what
it is. You dont have to include any of the folders you are not using. Some plugins might only define a
Component and a Behavior, and in that case they can completely omit the Template directory.
A plugin can also have basically any of the other directories that your application can, such as Config,
Console, webroot, etc.
Now you can bake using the same conventions which apply to the rest of your app. For example - baking
controllers:
bin/cake bake controller --plugin ContactManager Contacts
Please refer to the chapter Code Generation with Bake if you have any problems with using the command
line. Be sure to re-generate your autoloader once youve created your plugin:
647
Plugin Controllers
Controllers for our ContactManager plugin will be stored in plugins/ContactManager/src/Controller/.
Since the main thing well be doing is managing contacts, well need a ContactsController for this plugin.
So, we place our new ContactsController in plugins/ContactManager/src/Controller and it looks like so:
// plugins/ContactManager/src/Controller/ContactsController.php
namespace ContactManager\Controller;
use ContactManager\Controller\AppController;
class ContactsController extends AppController
{
public function index()
{
//...
}
}
A plugins AppController can hold controller logic common to all controllers in a plugin but is not
required if you dont want to use one.
Before you can access your controllers, youll need to ensure the plugin is loaded and the plugin routes are
loaded. In your config/bootstrap.php add the following:
Plugin::load('ContactManager', ['routes' => true]);
648
routes.
Put
the
following
into
plug-
<?php
use Cake\Routing\Route\DashedRoute;
use Cake\Routing\Router;
Router::plugin(
'ContactManager',
['path' => '/contact-manager'],
function ($routes) {
$routes->fallbacks(DashedRoute::class);
}
);
The above will connect default routes for you plugin. You can customize this file with more specific routes
later on.
If you want to access what weve got going thus far, visit /contact-manager/contacts. You should
get a Missing Model error because we dont have a Contact model defined yet.
If your application includes the default routing CakePHP provides you will be able to access your plugin
controllers using URLs like:
// Access the index route of a plugin controller.
/contact-manager/contacts
// Any action on a plugin controller.
/contact-manager/contacts/view/1
If your application defines routing prefixes, CakePHPs default routing will also connect routes that use the
following pattern:
/:prefix/:plugin/:controller
/:prefix/:plugin/:controller/:action
See the section on Plugin Configuration for information on how to load plugin specific route files.
For plugins you did not create with bake, you will also need to edit the composer.json file to add your plugin
to the autoload classes, this can be done as per the documentation Autoloading Plugin Classes.
Plugin Models
Models for the plugin are stored in plugins/ContactManager/src/Model. Weve already defined a ContactsController for this plugin, so lets create the table and entity for that controller:
// plugins/ContactManager/src/Model/Entity/Contact.php:
namespace ContactManager\Model\Entity;
use Cake\ORM\Entity;
class Contact extends Entity
{
}
Plugin Models
649
// plugins/ContactManager/src/Model/Table/ContactsTable.php:
namespace ContactManager\Model\Table;
use Cake\ORM\Table;
class ContactsTable extends Table
{
}
If you need to reference a model within your plugin when building associations or defining entity classes,
you need to include the plugin name with the class name, separated with a dot. For example:
// plugins/ContactManager/src/Model/Table/ContactsTable.php:
namespace ContactManager\Model\Table;
use Cake\ORM\Table;
class ContactsTable extends Table
{
public function initialize(array $config)
{
$this->hasMany('ContactManager.AltName');
}
}
If you would prefer that the array keys for the association not have the plugin prefix on them, use the
alternative syntax:
// plugins/ContactManager/src/Model/Table/ContactsTable.php:
namespace ContactManager\Model\Table;
use Cake\ORM\Table;
class ContactsTable extends Table
{
public function initialize(array $config)
{
$this->hasMany('AltName', [
'className' => 'ContactManager.AltName',
]);
}
}
You can use TableRegistry to load your plugin tables using the familiar plugin syntax:
use Cake\ORM\TableRegistry;
$contacts = TableRegistry::get('ContactManager.Contacts');
650
Plugin Views
Views behave exactly as they do in normal applications. Just place them in the right folder inside of the
plugins/[PluginName]/src/Template/ folder. For our ContactManager plugin, well need a
view for our ContactsController::index() action, so lets include that as well:
// plugins/ContactManager/src/Template/Contacts/index.ctp:
<h1>Contacts</h1>
<p>Following is a sortable list of your contacts</p>
<!-- A sortable list of contacts would go here....-->
Plugins can provide their own layouts. To add plugin layouts, place your template files inside
plugins/[PluginName]/src/Template/Layout. To use a plugin layout in your controller you
can do the following:
public $layout = 'ContactManager.admin';
If the plugin prefix is omitted, the layout/view file will be located normally.
Note: For information on how to use elements from a plugin, look up Elements
Plugin Assets
A plugins web assets (but not PHP files) can be served through the plugins webroot directory, just like
the main applications assets:
/plugins/ContactManager/webroot/
css/
js/
img/
flash/
pdf/
You may put any type of file in any directory, just like a regular webroot.
Plugin Views
651
Warning: Handling static assets (such as images, JavaScript and CSS files) through the Dispatcher is
very inefficient. See Improve Your Applications Performance for more information.
Plugin assets are served using the AssetFilter dispatcher filter by default. This is only recommended
for development. In production you should symlink plugin assets to improve performance.
If you are not using the helpers, you can prepend /plugin_name/ to the beginning of the URL for an asset
within that plugin to serve it. Linking to /contact_manager/js/some_file.js would serve the asset plugins/ContactManager/webroot/js/some_file.js.
652
parent::initialize();
$this->loadComponent('ContactManager.Example');
}
137
138
http://plugins.cakephp.org
https://github.com/FriendsOfCake/awesome-cakephp
653
654
CHAPTER 25
REST
Many newer application programmers are realizing the need to open their core functionality to a greater
audience. Providing easy, unfettered access to your core API can help get your platform accepted, and
allows for mashups and easy integration with other systems.
While other solutions exist, REST is a great way to provide easy access to the logic youve created in your
application. Its simple, usually XML-based (were talking simple XML, nothing like a SOAP envelope),
and depends on HTTP headers for direction. Exposing an API via REST in CakePHP is simple.
655
}
public function view($id)
{
$recipe = $this->Recipes->get($id);
$this->set([
'recipe' => $recipe,
'_serialize' => ['recipe']
]);
}
public function add()
{
$recipe = $this->Recipes->newEntity($this->request->data);
if ($this->Recipes->save($recipe)) {
$message = 'Saved';
} else {
$message = 'Error';
}
$this->set([
'message' => $message,
'recipe' => $recipe,
'_serialize' => ['message', 'recipe']
]);
}
public function edit($id)
{
$recipe = $this->Recipes->get($id);
if ($this->request->is(['post', 'put'])) {
$recipe = $this->Recipes->patchEntity($recipe, $this->request->
data);
if ($this->Recipes->save($recipe)) {
$message = 'Saved';
} else {
$message = 'Error';
}
}
$this->set([
'message' => $message,
'_serialize' => ['message']
]);
}
public function delete($id)
{
$recipe = $this->Recipes->get($id);
$message = 'Deleted';
if (!$this->Recipes->delete($recipe)) {
$message = 'Error';
}
$this->set([
'message' => $message,
656
RESTful controllers often use parsed extensions to serve up different views based on different kinds of
requests. Since were dealing with REST requests, well be making XML views. You can make JSON
views using CakePHPs built-in JSON and XML views. By using the built in XmlView we can define a
_serialize view variable. This special view variable is used to define which view variables XmlView
should serialize into XML.
If we wanted to modify the data before it is converted into XML we should not define the _serialize
view variable, and instead use template files. We place the REST views for our RecipesController inside
src/Template/Recipes/xml. We can also use the Xml for quick-and-easy XML output in those views. Heres
what our index view might look like:
// src/Template/Recipes/xml/index.ctp
// Do some formatting and manipulation on
// the $recipes array.
$xml = Xml::fromArray(['response' => $recipes]);
echo $xml->asXML();
Creating the logic for the edit action is a bit trickier, but not by much. Since youre providing an API that outputs XML, its a natural choice to receive XML as input. Not to worry, the
Cake\Controller\Component\RequestHandler and Cake\Routing\Router classes make
things much easier. If a POST or PUT request has an XML content-type, then the input is run through
CakePHPs Xml class, and the array representation of the data is assigned to $this->request->data.
The Simple Setup
657
Because of this feature, handling XML and POST data in parallel is seamless: no changes are required to
the controller or model code. Everything you need should end up in $this->request->data.
RESTful Routing
CakePHPs Router makes connecting RESTful resource routes easy. See the section on Creating RESTful
Routes for more information.
658
CHAPTER 26
Security
CakePHP provides you some tools to secure your application. The following sections cover those tools:
Security
class Cake\Utility\Security
The security library139 handles basic security measures such as providing methods for hashing and encrypting data.
http://php.net/mcrypt
This method should never be used to store passwords. Instead you should use the one way hashing methods
provided by Utility\Security::hash(). An example use would be:
139
140
141
http://api.cakephp.org/3.0/class-Cake.Utility.Security.html
http://php.net/openssl
http://php.net/mcrypt
659
If you do not supply an HMAC salt, the Security.salt value will be used. Encrypted values can be
decrypted using Cake\Utility\Security::decrypt().
Decrypt a previously encrypted value. The $key and $hmacSalt parameters must match the values used
to encrypt or decryption will fail. An example use would be:
// Assuming the key is stored somewhere it can be re-used for
// Decryption later.
$key = 'wt1U5MACWJFTXGenFoZoiLwQGrLgdbHA';
$cipher = $user->secrets;
$result = Security::decrypt($cipher, $key);
If the value cannot be decrypted due to changes in the key or HMAC salt false will be returned.
Choosing a Specific Crypto Implementation
If you are upgrading an application from CakePHP 2.x, data encrypted in 2.x is not compatible with openssl.
This is because the encrypted data is not fully AES compliant. If you dont want to go through the trouble
of re-encrypting your data, you can force CakePHP to use mcrypt using the engine() method:
// In config/bootstrap.php
use Cake\Utility\Crypto\Mcrypt;
Security::engine(new Mcrypt());
The above will allow you to seamlessly read data from older versions of CakePHP, and encrypt new data to
be compatible with OpenSSL.
Hashing Data
static Cake\Utility\Security::hash($string, $type = NULL, $salt = false)
Create a hash from string using given method. Fallback on next available method. If $salt is set to true,
the applications salt value will be used:
// Using the application's salt value
$sha1 = Security::hash('CakePHP Framework', 'sha1', true);
// Using a custom salt value
$sha1 = Security::hash('CakePHP Framework', 'sha1', 'my-salt');
// Using the default hash algorithm
$hash = Security::hash('CakePHP Framework');
660
Security
661
662
CHAPTER 27
Sessions
CakePHP provides a wrapper and suite of utility features on top of PHPs native session extension.
Sessions allow you to identify unique users across requests and store persistent data for specific users.
Unlike Cookies, session data is not available on the client side. Usage of $_SESSION is generally avoided
in CakePHP, and instead usage of the Session classes is preferred.
Session Configuration
Session configuration is stored in Configure under the top level Session key, and a number of options
are available:
Session.timeout - The number of minutes before CakePHPs session handler expires the session.
Session.defaults - Allows you to use the built-in default session configurations as a base for
your session configuration. See below for the built-in defaults.
Session.handler - Allows you to define a custom session handler. The core database and cache
session handlers use this. See below for additional information on Session handlers.
Session.ini - Allows you to set additional session ini settings for your config. This combined
with Session.handler replace the custom session handling features of previous versions
CakePHPs defaults session.cookie_secure to true, when your application is on an SSL protocol. If your application serves from both SSL and non-SSL protocols, then you might have problems with
sessions being lost. If you need access to the session on both SSL and non-SSL domains you will want to
disable this:
Configure::write('Session', [
'defaults' => 'php',
'ini' => [
'session.cookie_secure' => false
]
]);
663
By default PHP sets the session cookie to expire as soon as the browser is closed, regardless of the configured
Session.timeout value. The cookie timeout is controlled by the session.cookie_lifetime ini
value and can be configured using:
Configure::write('Session', [
'defaults' => 'php',
'ini' => [
// Invalidate the cookie after 30 minutes without visiting
// any page on the site.
'session.cookie_lifetime' => 1800
]
]);
The difference between Session.timeout and the session.cookie_lifetime value is that the
latter relies on the client telling the truth about the cookie. If you require stricter timeout checking, without
relying on what the client reports, you should use Session.timeout.
Please note that Session.timeout corresponds to the total time of inactivity for a user (i.e. the time
without visiting any page where the session is used), and does not limit the total amount of minutes a user
can stay on the site.
The above will use the built-in php session configuration. You could augment part or all of it by doing the
following:
Configure::write('Session', [
'defaults' => 'php',
'cookie' => 'my_app',
664
The above overrides the timeout and cookie name for the php session configuration. The built-in configurations are:
php - Saves sessions with the standard settings in your php.ini file.
cake - Saves sessions as files inside tmp/sessions. This is a good option when on hosts that
dont allow you to write outside your own home dir.
database - Use the built-in database sessions. See below for more information.
cache - Use the built-in cache sessions. See below for more information.
The accepted values are:
defaults - either php, database, cache or cake as explained above.
handler - An array containing the handler configuration
ini - A list of php.ini directives to set before the session starts.
timeout - The time in minutes the session should stay active
Session Handlers
Session handlers can also be defined in the session config array. By defining the handler.engine config key,
you can name the class name, or provide a handler instance. The class/object must implement the native
PHP SessionHandlerInterface. Implementing this interface will allow Session to automatically
map the methods for the handler. Both the core Cache and Database session handlers use this method for
saving sessions. Additional settings for the handler should be placed inside the handler array. You can then
read those values out from inside your handler:
'Session' => [
'handler' => [
'engine' => 'Database',
'model' => 'CustomSessions'
]
]
The above shows how you could setup the Database session handler with an application model.
When using class names as your handler.engine, CakePHP will expect to find your class in the
Network\Session namespace.
For example, if you had an AppSessionHandler class,
the file should be src/Network/Session/AppSessionHandler.php, and the class name should be
App\Network\Session\AppSessionHandler. You can also use session handlers from inside plugins. By setting the engine to MyPlugin.PluginSessionHandler.
Database Sessions
If you need to use a database to store your session data, configure as follows:
665
'Session' => [
'defaults' => 'database'
]
This configuration will require a database table to be added with at least these fields:
CREATE TABLE `sessions` (
`id` varchar(255) NOT NULL DEFAULT '',
`data` BLOB, -- or BYTEA for PostgreSQL
`expires` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
);
You can find a copy of the schema for the sessions table in the application skeleton.
You can also use your own Table class to handle the saving of the sessions:
'Session' => [
'defaults' => 'database',
'handler' => [
'engine' => 'Database',
'model' => 'CustomSessions'
]
]
The above will tell Session to use the built-in database defaults, and specify that a Table called
CustomSessions will be the delegate for saving session information to the database.
Cache Sessions
The Cache class can be used to store sessions as well. This allows you to store sessions in a cache like APC,
Memcached, or XCache. There are some caveats to using cache sessions, in that if you exhaust the cache
space, sessions will start to expire as records are evicted.
To use Cache based sessions you can configure you Session config like:
Configure::write('Session', [
'defaults' => 'cache',
'handler' => [
'config' => 'session'
]
]);
This will configure Session to use the CacheSession class as the delegate for saving the sessions. You
can use the config key which cache configuration to use. The default cache configuration is 'default'.
figurations, as well as custom ones. The ini key in the session settings, allows you to specify individual
configuration values. For example you can use it to control settings like session.gc_divisor:
Configure::write('Session', [
'defaults' => 'php',
'ini' => [
'session.cookie_name' => 'MyCookie',
'session.cookie_lifetime' => 1800, // Valid for 30 minutes
'session.gc_divisor' => 1000,
'session.cookie_httponly' => true
]
]);
667
Our class extends the built-in DatabaseSession so we dont have to duplicate all of its logic and behavior. We wrap each operation with a Cake\Cache\Cache operation. This lets us fetch sessions from the
fast cache, and not have to worry about what happens when we fill the cache. Using this session handler is
also easy. In your app.php make the session block look like the following:
'Session' => [
'defaults' => 'database',
'handler' => [
'engine' => 'ComboSession',
'model' => 'Session',
'cache' => 'apc'
]
],
// Make sure to add a apc cache config
'Cache' => [
'apc' => ['engine' => 'Apc']
]
Now our application will start using our custom session handler for reading and writing session data.
class Session
Components
In addition to the basic session object, you can also use the Cake\View\Helper\SessionHelper to
interact with the session in your views. A basic example of session usage would be:
$name = $this->request->session()->read('User.name');
// If you are accessing the session multiple times,
// you will probably want a local variable.
$session = $this->request->session();
$name = $session->read('User.name');
Session::write($key, $value)
$key should be the dot separated path you wish to write $value to:
$session->write('Config.language', 'en');
Session::delete($key)
When you need to delete data from the session, you can use delete():
$session->delete('Some.value');
static Session::consume($key)
When you need to read and delete data from the session, you can use consume():
$session->consume('Some.value');
Session::check($key)
If you want to see if data exists in the session, you can use check():
if ($session->check('Config.language')) {
// Config.language exists and is not null.
}
669
Destroying a session will remove all serverside data in the session, but will not remove the session cookie.
Flash Messages
Flash messages are small messages displayed to end users once. They are often used to present error messages, or confirm that actions took place successfully.
To set and display flash messages you should use Flash and Flash
670
CHAPTER 28
Testing
CakePHP comes with comprehensive testing support built-in. CakePHP comes with integration for PHPUnit143 . In addition to the features offered by PHPUnit, CakePHP offers some additional features to make
testing easier. This section will cover installing PHPUnit, and getting started with Unit Testing, and how
you can use the extensions that CakePHP offers.
Installing PHPUnit
CakePHP uses PHPUnit as its underlying test framework. PHPUnit is the de-facto standard for unit testing
in PHP. It offers a deep and powerful set of features for making sure your code does what you think it does.
PHPUnit can be installed through using either a PHAR package144 or Composer145 .
This will add the dependency to the require-dev section of your composer.json, and then install
PHPUnit along with any dependencies.
You can now run PHPUnit using:
$ vendor/bin/phpunit
http://phpunit.de
http://phpunit.de/#download
http://getcomposer.org
671
php phpunit.phar
Tip: As a convenience you can make phpunit.phar available globally on Unix or Linux with the following:
chmod +x phpunit.phar
sudo mv phpunit.phar /usr/local/bin/phpunit
phpunit --version
Please refer to the PHPUnit documentation for instructions regarding Globally installing the PHPUnit PHAR
on Windows146 .
Note: Its a good idea to make the test database and your actual database different databases. This will
prevent embarrassing mistakes later.
672
http://phpunit.de/manual/current/en/installation.html#installation.phar.windows
The above should run any tests you have, or let you know that no tests were run. To run a specific test
you can supply the path to the test as a parameter to PHPUnit. For example, if you had a test case for
ArticlesTable class you could run it with:
$ vendor/bin/phpunit tests/TestCase/Model/Table/ArticlesTableTest
You should see a green bar with some additional information about the tests run, and number passed.
Note: If you are on a Windows system you probably wont see any colours.
673
This is a very simple example, but it will be useful to show how you can create a simple test case.
After creating and saving our helper, well create the test case file in
tests/TestCase/View/Helper/ProgressHelperTest.php. In that file well start with the following:
namespace App\Test\TestCase\View\Helper;
use App\View\Helper\ProgressHelper;
use Cake\TestSuite\TestCase;
use Cake\View\View;
class ProgressHelperTest extends TestCase
{
public function setUp()
{
}
public function testBar()
{
}
}
Well flesh out this skeleton in a minute. Weve added two methods to start with. First is setUp(). This
method is called before every test method in a test case class. Setup methods should initialize the objects
needed for the test, and do any configuration needed. In our setup method well add the following:
public function setUp()
{
parent::setUp();
$View = new View();
$this->Progress = new ProgressHelper($View);
}
Calling the parent method is important in test cases, as TestCase::setUp() does a number things like
backing up the values in Core\Configure and, storing the paths in Core\App.
Next, well fill out the test method. Well use some assertions to ensure that our code creates the output we
expect:
public function testBar()
{
$result = $this->Progress->bar(90);
$this->assertContains('width: 90%', $result);
$this->assertContains('progress-bar', $result);
$result = $this->Progress->bar(33.3333333);
$this->assertContains('width: 33%', $result);
}
The above test is a simple one but shows the potential benefit of using test cases. We use
assertContains() to ensure that our helper is returning a string that contains the content we expect.
If the result did not contain the expected content the test would fail, and we would know that our code is
674
incorrect.
By using test cases you can describe the relationship between a set of known inputs and their expected
output. This helps you be more confident of the code youre writing as you can ensure that the code you
wrote fulfills the expectations and assertions your tests make. Additionally because tests are code, they are
easy to re-run whenever you make a change. This helps prevent the creation of new bugs.
Running Tests
Once you have PHPUnit installed and some test cases written, youll want to run the test cases very frequently. Its a good idea to run tests before committing any changes to help ensure you havent broken
anything.
By using phpunit you can run your application tests. To run your applications tests you can simply run:
// composer installs
$ vendor/bin/phpunit
// phar file
php phpunit.phar
If you have cloned the CakePHP source from GitHub147 and wish to run CakePHPs unit-tests dont forget
to execute the following Composer command prior to running phpunit so that any dependencies are
installed:
$ composer install --dev
From your applications root directory. To run tests for a plugin that is part of your application source, first
cd into the plugin directory, then use phpunit command that matches how you installed phpunit:
cd plugins
// Using composer installed phpunit
../vendor/bin/phpunit
// Using phar file
php ../phpunit.phar
To run tests on a standalone plugin, you should first install the project in a separate directory and install its
dependencies:
git clone git://github.com/cakephp/debug_kit.git
cd debug_kit
php ~/composer.phar install
php ~/phpunit.phar
147
https://github.com/cakephp/cakephp
Running Tests
675
The filter parameter is used as a case-sensitive regular expression for filtering which test methods to run.
This will put the coverage results in your applications webroot directory. You should be able to view the
results by going to http://localhost/your_app/coverage.
Any additional test suites added to the <testsuites> element will automatically be run when you use
phpunit.
If you are using <testsuites> to use fixtures from plugins that you have installed with composer, the
plugins composer.json file should add the fixture namespace to the autoload section. Example:
"autoload": {
"psr-4": {
"PluginName\\Test\\Fixture\\": "tests\\Fixture"
}
},
676
Fixtures
When testing code that depends on models and the database, one can use fixtures as a way to generate
temporary data tables loaded with sample data that can be used by the test. The benefit of using fixtures is
that your test has no chance of disrupting live application data. In addition, you can begin testing your code
prior to actually developing live content for an application.
CakePHP uses the connection named test in your config/app.php configuration file. If this connection is
not usable, an exception will be raised and you will not be able to use database fixtures.
CakePHP performs the following during the course of a fixture based test case:
1. Creates tables for each of the fixtures needed.
2. Populates tables with data, if data is provided in fixture.
3. Runs test methods.
4. Empties the fixture tables.
5. Removes fixture tables from database.
Test Connections
By default CakePHP will alias each connection in your application. Each connection defined in your applications bootstrap that does not start with test_ will have a test_ prefixed alias created. Aliasing
connections ensures, you dont accidentally use the wrong connection in test cases. Connection aliasing is
transparent to the rest of your application. For example if you use the default connection, instead you will
get the test connection in test cases. If you use the replica connection, the test suite will attempt to use
test_replica.
677
Creating Fixtures
When creating a fixture you will mainly define two things: how the table is created (which fields are part
of the table), and which records will be initially populated to the table. Lets create our first fixture, that
will be used to test our own Article model. Create a file named ArticlesFixture.php in your tests/Fixture
directory, with the following content:
namespace App\Test\Fixture;
use Cake\TestSuite\Fixture\TestFixture;
class ArticlesFixture extends TestFixture
{
public $fields = [
'id' => ['type' => 'integer'],
'title' => ['type' => 'string', 'length' => 255, 'null' => false],
'body' => 'text',
'published' => ['type' => 'integer', 'default' => '0', 'null' =>
false],
'created' => 'datetime',
'modified' => 'datetime',
'_constraints' => [
'primary' => ['type' => 'primary', 'columns' => ['id']]
]
];
public $records = [
[
'title' => 'First Article',
'body' => 'First Article Body',
'published' => '1',
'created' => '2007-03-18 10:39:23',
'modified' => '2007-03-18 10:41:31'
],
[
'title' => 'Second Article',
'body' => 'Second Article Body',
'published' => '1',
'created' => '2007-03-18 10:41:23',
'modified' => '2007-03-18 10:43:31'
],
[
'title' => 'Third Article',
'body' => 'Third Article Body',
'published' => '1',
'created' => '2007-03-18 10:43:23',
'modified' => '2007-03-18 10:45:31'
]
];
678
Note: It is recommended to not manually add values to auto incremental columns, as it interferes with the
sequence generation in PostgreSQL and SQLServer.
The $connection property defines the datasource of which the fixture will use. If your application
uses multiple datasources, you should make the fixtures match the models datasources but prefixed with
test_. For example if your model uses the mydb datasource, your fixture should use the test_mydb
datasource. If the test_mydb connection doesnt exist, your models will use the default test datasource.
Fixture datasources must be prefixed with test to reduce the possibility of accidentally truncating all your
applications data when running tests.
We use $fields to specify which fields will be part of this table, and how they are defined. The format
used to define these fields is the same used with Cake\Database\Schema\Table. The keys available
for table definition are:
type CakePHP internal data type. Currently supported:
string: maps to VARCHAR or CHAR
uuid: maps to UUID
text: maps to TEXT
integer: maps to INT
biginteger: maps to BIGINTEGER
decimal: maps to DECIMAL
float: maps to FLOAT
datetime: maps to DATETIME
timestamp: maps to TIMESTAMP
time: maps to TIME
date: maps to DATE
binary: maps to BLOB
fixed Used with string types to create CHAR columns in platforms that support them.
length Set to the specific length the field should take.
precision Set the number of decimal places used on float & decimal fields.
null Set to either true (to allow NULLs) or false (to disallow NULLs).
default Default value the field takes.
We can define a set of records that will be populated after the fixture table is created. The format is fairly
straight forward, $records is an array of records. Each item in $records should be a single row. Inside
each row, should be an associative array of the columns and values for the row. Just keep in mind that each
Fixtures
679
record in the $records array must have a key for every field specified in the $fields array. If a field for a
particular record needs to have a null value, just specify the value of that key as null.
fixtures can become difficult to maintain in larger applications. Because of this CakePHP provides the
ability to import the schema from an existing connection and use the reflected table definition to create the
table definition used in the test suite.
Lets start with an example. Assuming you have a table named articles available in your application, change
the example fixture given in the previous section (tests/Fixture/ArticlesFixture.php) to:
class ArticlesFixture extends TestFixture
{
public $import = ['table' => 'articles'];
}
Fixtures
681
Finally, you can not load/create any schema in a fixture. This is useful if you already have a test database
setup with all the empty tables created. By defining neither $fields or $import a fixture will only insert
its records and truncate the records on each test method.
The above will load the Article and Comment fixtures from the applications Fixture directory. You can also
load fixtures from CakePHP core, or plugins:
class ArticlesTest extends TestCase
{
public $fixtures = ['plugin.debug_kit.articles', 'core.comments'];
}
Using the core prefix will load fixtures from CakePHP, and using a plugin name as the prefix, will load the
fixture from the named plugin.
You can control when your fixtures are loaded by setting Cake\TestSuite\TestCase::$autoFixtures
to false and later load them using Cake\TestSuite\TestCase::loadFixtures():
class ArticlesTest extends TestCase
{
public $fixtures = ['app.articles', 'app.comments'];
public $autoFixtures = false;
public function testMyFunction()
{
$this->loadFixtures('Articles', 'Comments');
}
}
You can load fixtures in subdirectories. Using multiple directories can make it easier to organize your
fixtures if you have a larger application. To load fixtures in subdirectories, simply include the subdirectory
name in the fixture name:
682
We now want to set up a test that will test this table class. Lets now create a file named ArticlesTableTest.php in your tests/TestCase/Model/Table directory, with the following contents:
namespace App\Test\TestCase\Model\Table;
use App\Model\Table\ArticlesTable;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\TestCase;
class ArticlesTableTest extends TestCase
{
public $fixtures = ['app.articles'];
}
In our test cases variable $fixtures we define the set of fixtures that well use. You should remember to
include all the fixtures that will have queries run against them.
683
namespace App\Test\TestCase\Model\Table;
use App\Model\Table\ArticlesTable;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\TestCase;
class ArticlesTableTest extends TestCase
{
public $fixtures = ['app.articles'];
public function setUp()
{
parent::setUp();
$this->Articles = TableRegistry::get('Articles');
}
public function testFindPublished()
{
$query = $this->Articles->find('published');
$this->assertInstanceOf('Cake\ORM\Query', $query);
$result = $query->hydrate(false)->toArray();
$expected = [
['id' => 1, 'title' => 'First Article'],
['id' => 2, 'title' => 'Second Article'],
['id' => 3, 'title' => 'Third Article']
];
$this->assertEquals($expected, $result);
}
}
You can see we have added a method called testFindPublished(). We start by creating an instance of our ArticlesTable class, and then run our find('published') method. In $expected
we set what we expect should be the proper result (that we know since we have defined which records
are initially populated to the article table.) We test that the result equals our expectation by using the
assertEquals() method. See the Running Tests section for more information on how to run your test
case.
684
685
$this->set([
'title' => 'Articles',
'articles' => $result
]);
}
}
Create a file named ArticlesControllerTest.php in your tests/TestCase/Controller directory and put the
following inside:
namespace App\Test\TestCase\Controller;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\IntegrationTestCase;
class ArticlesControllerTest extends IntegrationTestCase
{
public $fixtures = ['app.articles'];
public function testIndex()
{
$this->get('/articles');
$this->assertResponseOk();
// More asserts.
}
public function testIndexQueryData()
{
$this->get('/articles?page=1');
$this->assertResponseOk();
// More asserts.
}
public function testIndexShort()
{
$this->get('/articles/index/short');
$this->assertResponseOk();
$this->assertResponseContains('Articles');
// More asserts.
}
public function testIndexPostData()
{
$data = [
'user_id' => 1,
'published' => 1,
'slug' => 'new-article',
'title' => 'New Article',
'body' => 'New Body'
686
];
$this->post('/articles', $data);
$this->assertResponseSuccess();
$articles = TableRegistry::get('Articles');
$query = $articles->find()->where(['title' => $data['title']]);
$this->assertEquals(1, $query->count());
}
}
This example shows a few of the request sending methods and a few of the assertions that
IntegrationTestCase provides. Before you can do any assertions youll need to dispatch a request.
You can use one of the following methods to send a request:
get() Sends a GET request.
post() Sends a POST request.
put() Sends a PUT request.
delete() Sends a DELETE request.
patch() Sends a PATCH request.
All of the methods except get() and delete() accept a second parameter that allows you to
send a request body. After dispatching a request you can use the various assertions provided by
IntegrationTestCase or by PHPUnit to ensure your request had the correct side-effects.
The state set by these helper methods is reset in the tearDown() method.
687
suming you had an ArticlesController that contained an add method, and that add method required
authentication, you could write the following tests:
public function testAddUnauthenticatedFails()
{
// No session data set.
$this->get('/articles/add');
$this->assertRedirect(['controller' => 'Users', 'action' => 'login']);
}
public function testAddAuthenticated()
{
// Set session data
$this->session([
'Auth' => [
'User' => [
'id' => 1,
'username' => 'testing',
// other keys.
]
]
]);
$this->get('/articles/add');
$this->assertResponseOk();
// Other assertions.
}
688
http://php.net/manual/en/features.http-auth.php
If you are testing other forms of authentication, such as OAuth2, you can set the Authorization header
directly:
public function testOauthToken()
{
$this->configRequest([
'headers' => [
'authorization' => 'Bearer: oauth-token'
]
]);
$this->get('/api/posts');
$this->assertResponseOk();
}
The headers key in configRequest() can be used to configure any additional HTTP headers needed for
an action.
It is also important to enable debug in tests that use tokens to prevent the SecurityComponent from thinking the debug token is being used in a non-debug environment. When testing with other methods like
requireSecure() you can use configRequest() to set the correct environment variables:
// Fake out SSL connections.
$this->configRequest([
'environment' => ['HTTPS' => 'on']
]);
689
You can customize the application class name used, and the constructor arguments, by using the
configApplication() method:
public function setUp()
{
$this->configApplication('App\App', [CONFIG]);
}
After enabling the PSR7 mode, and possibly configuring your application class, you can use the remaining
IntegrationTestCase features as normal.
New in version 3.3.0: PSR7 Middleware and the useHttpServer() method were added in 3.3.0.
Assertion methods
The IntegrationTestCase class provides a number of assertion methods that make testing responses
much simpler. Some examples are:
// Check for a 2xx response code
$this->assertResponseOk();
// Check for a 2xx/3xx response code
$this->assertResponseSuccess();
// Check for a 4xx response code
$this->assertResponseError();
// Check for a 5xx response code
$this->assertResponseFailure();
// Check for a specific response code, e.g. 200
$this->assertResponseCode(200);
// Check the Location header
$this->assertRedirect(['controller' => 'Articles', 'action' => 'index']);
// Check that no Location header has been set
$this->assertNoRedirect();
// Check a part of the Location header
$this->assertRedirectContains('/articles/edit/');
690
In addition to the above assertion methods, you can also use all of the assertions in TestSuite149 and those
found in PHPUnit150 .
http://api.cakephp.org/3.0/class-Cake.TestSuite.TestCase.html
https://phpunit.de/manual/current/en/appendixes.assertions.html
691
{
use StringCompareTrait;
public function setUp()
{
$this->_compareBasePath = APP . 'tests' . DS . 'comparisons' . DS;
parent::setUp();
}
public function testExample()
{
$result = ...;
$this->assertSameAsFile('example.php', $result);
}
}
The
above
example
will
compare
$result
APP/tests/comparisons/example.php.
to
the
contents
of
the
file
692
Now we create the file tests/TestCase/Controller/MarkersControllerTest.php and make sure our web
service is returning the proper response:
class MarkersControllerTest extends IntegrationTestCase
{
public function testGet()
{
$this->configRequest([
'headers' => ['Accept' => 'application/json']
]);
$result = $this->get('/markers/view/1.json');
// Check that the response was a 200
$this->assertResponseOk();
$expected = [
['id' => 1, 'lng' => 66, 'lat' => 45],
];
$expected = json_encode($expected, JSON_PRETTY_PRINT);
$this->assertEquals($expected, $this->_response->body());
}
}
693
We use the JSON_PRETTY_PRINT option as CakePHPs built in JsonView will use that option when
debug is enabled.
Testing Views
Generally most applications will not directly test their HTML code. Doing so is often results in fragile, difficult to maintain test suites that are prone to breaking. When writing functional tests using
IntegrationTestCase you can inspect the rendered view content by setting the return option to
view. While it is possible to test view content using IntegrationTestCase, a more robust and maintainable
integration/view testing can be accomplished using tools like Selenium webdriver151 .
Testing Components
Lets pretend we have a component called PagematronComponent in our application. This component helps
us set the pagination limit value across all the controllers that use it. Here is our example component located
in src/Controller/Component/PagematronComponent.php:
class PagematronComponent extends Component
{
public $controller = null;
public function setController($controller)
{
$this->controller = $controller;
// Make sure the controller is using pagination
if (!isset($this->controller->paginate)) {
$this->controller->paginate = [];
}
}
public function startup(Event $event)
{
$this->setController($event->subject());
}
public function adjust($length = 'short')
{
switch ($length) {
case 'long':
$this->controller->paginate['limit'] = 100;
break;
case 'medium':
$this->controller->paginate['limit'] = 50;
break;
default:
$this->controller->paginate['limit'] = 20;
break;
151
694
http://seleniumhq.org
}
}
}
Now we can write tests to ensure our paginate limit parameter is being
correctly by the adjust() method in our component.
We create the
tests/TestCase/Controller/Component/PagematronComponentTest.php:
set
file
namespace App\Test\TestCase\Controller\Component;
use
use
use
use
use
use
App\Controller\Component\PagematronComponent;
Cake\Controller\Controller;
Cake\Controller\ComponentRegistry;
Cake\Network\Request;
Cake\Network\Response;
Cake\TestSuite\TestCase;
Testing Components
695
parent::tearDown();
// Clean up after we're done
unset($this->component, $this->controller);
}
}
Testing Helpers
Since a decent amount of logic resides in Helper classes, its important to make sure those classes are covered
by test cases.
First we create an example helper to test. The CurrencyRendererHelper will help us display currencies in our views and for simplicity only has one method usd():
// src/View/Helper/CurrencyRendererHelper.php
namespace App\View\Helper;
use Cake\View\Helper;
class CurrencyRendererHelper extends Helper
{
public function usd($amount)
{
return 'USD ' . number_format($amount, 2, '.', ',');
}
}
Here we set the decimal places to 2, decimal separator to dot, thousands separator to comma, and prefix the
formatted number with USD string.
Now we create our tests:
// tests/TestCase/View/Helper/CurrencyRendererHelperTest.php
namespace App\Test\TestCase\View\Helper;
use App\View\Helper\CurrencyRendererHelper;
use Cake\TestSuite\TestCase;
use Cake\View\View;
class CurrencyRendererHelperTest extends TestCase
{
public $helper = null;
// Here we instantiate our helper
public function setUp()
{
parent::setUp();
$View = new View();
$this->helper = new CurrencyRendererHelper($View);
696
}
// Testing the usd() function
public function testUsd()
{
$this->assertEquals('USD 5.30', $this->helper->usd(5.30));
// We should always have 2 decimal digits
$this->assertEquals('USD 1.00', $this->helper->usd(1));
$this->assertEquals('USD 2.05', $this->helper->usd(2.05));
// Testing the thousands separator
$this->assertEquals(
'USD 12,000.70',
$this->helper->usd(12000.70)
);
}
}
Here, we call usd() with different parameters and tell the test suite to check if the returned values are equal
to what is expected.
Save this and execute the test. You should see a green bar and messaging indicating 1 pass and 4 assertions.
When you are testing a Helper which uses other helpers, be sure to mock the View clases loadHelpers
method.
Testing Events
The Events System is a great way to decouple your application code, but sometimes when testing, you tend
to test the results of events in the test cases that execute those events. This is an additional form of coupling
that can be removed by using assertEventFired and assertEventFiredWith instead.
Expanding on the Orders example, say we have the following tables:
class OrdersTable extends Table
{
public function place($order)
{
if ($this->save($order)) {
// moved cart removal to CartsTable
$event = new Event('Model.Order.afterPlace', $this, [
'order' => $order
]);
$this->eventManager()->dispatch($event);
return true;
}
return false;
}
}
Testing Events
697
Note: To assert that events are fired, you must first enable Tracking Events on the event manager you wish
to assert against.
To test the OrdersTable above, we enable tracking in setUp() then assert that the event was fired, and
assert that the $order entity was passed in the event data:
namespace App\Test\TestCase\Model\Table;
use
use
use
use
App\Model\Table\OrdersTable;
Cake\Event\EventList;
Cake\ORM\TableRegistry;
Cake\TestSuite\TestCase;
698
$this->assertTrue($this->Orders->place($order));
$this->assertEventFired('Model.Order.afterPlace', $this->Orders->
eventManager());
$this->assertEventFiredWith('Model.Order.afterPlace', 'order',
$order, $this->Orders->eventManager());
}
}
By default, the global EventManager is used for assertions, so testing global events does not require
passing the event manager:
$this->assertEventFired('My.Global.Event');
$this->assertEventFiredWith('My.Global.Event', 'user', 1);
CakeTestSuite offers several methods for creating test suites based on the file system. It allows you to
run any code you want to prepare your test suite. If we wanted to create a test suite for all our model tests
we could would create tests/TestCase/AllModelTest.php. Put the following in it:
class AllModelTest extends TestSuite
{
public static function suite() {
$suite = new CakeTestSuite('All model tests');
$suite->addTestDirectory(TESTS . 'Case/Model');
return $suite;
}
}
The code above will group all test cases found in the tests/TestCase/Model/ folder. To add an individual file,
use $suite->addTestFile($filename);. You can recursively add a directory for all tests using:
699
$suite->addTestDirectoryRecursive(TESTS . 'TestCase');
They work just like normal tests but you have to remember to use the naming conventions for plugins when
importing classes. This is an example of a testcase for the BlogPost model from the plugins chapter of
this manual. A difference from other tests is in the first line where Blog.BlogPost is imported. You also
need to prefix your plugin fixtures with plugin.blog.blog_posts:
namespace Blog\Test\TestCase\Model\Table;
use Blog\Model\Table\BlogPostsTable;
use Cake\TestSuite\TestCase;
class BlogPostsTableTest extends TestCase
{
// Plugin fixtures located in /plugins/Blog/tests/Fixture/
public $fixtures = ['plugin.blog.blog_posts'];
public function testSomething()
{
// Test something.
}
}
If you want to use plugin fixtures in the app tests you can
plugin.pluginName.fixtureName syntax in the $fixtures array.
reference
them
using
Before you can use fixtures you should double check that your phpunit.xml contains the fixture listener:
<!-- Setup a listener for fixtures -->
<listeners>
<listener
class="\Cake\TestSuite\Fixture\FixtureInjector"
file="./vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureInjector.
php">
<arguments>
<object class="\Cake\TestSuite\Fixture\FixtureManager
" />
700
</arguments>
</listener>
</listeners>
You should also ensure that your fixtures are loadable. Ensure the following is present in your composer.json
file:
"autoload-dev": {
"psr-4": {
"MyPlugin\\Test\\": "./plugins/MyPlugin/tests"
}
}
Note: Remember to run composer.phar dumpautoload when adding new autoload mappings.
http://jenkins-ci.org
701
Integrating a CakePHP application with Jenkins is fairly straightforward. The following assumes youve
already installed Jenkins on *nix system, and are able to administer it. You also know how to create jobs,
and run builds. If you are unsure of any of these, refer to the Jenkins documentation153 .
Create a Job
Start off by creating a job for your application, and connect your repository so that jenkins can access your
code.
By creating an app_local.php file, you have an easy way to define configuration specific to Jenkins. You
can use this same configuration file to override any other configuration files you need on Jenkins.
Its often a good idea to drop and re-create the database before each build as well. This insulates you from
chained failures, where one broken build causes others to fail. Add another shell script step to the build that
contains the following:
mysql -u jenkins -pcakephp_jenkins -e 'DROP DATABASE IF EXISTS jenkins_test;
CREATE DATABASE jenkins_test';
153
702
http://jenkins-ci.org/
If you use clover coverage, or the junit results, make sure to configure those in Jenkins as well. Failing to
configure those steps will mean you wont see the results.
Run a Build
You should be able to run a build now. Check the console output and make any necessary changes to get a
passing build.
703
704
CHAPTER 29
Validation
The validation package in CakePHP provides features to build validators that can validate arbitrary arrays
of data with ease. You can find a list of available Validation rules in the API154 .
Creating Validators
class Cake\Validation\Validator
Validator objects define the rules that apply to a set of fields. Validator objects contain a mapping between
fields and validation sets. In turn, the validation sets contain a collection of rules that apply to the field they
are attached to. Creating a validator is simple:
use Cake\Validation\Validator;
$validator = new Validator();
Once created, you can start defining sets of rules for the fields you want to validate:
$validator
->requirePresence('title')
->notEmpty('title', 'Please fill this field')
->add('title', [
'length' => [
'rule' => ['minLength', 10],
'message' => 'Titles need to be at least 10 characters long',
]
])
->allowEmpty('published')
->add('published', 'boolean', [
'rule' => 'boolean'
])
->requirePresence('body')
->add('body', 'length', [
154
http://api.cakephp.org/3.0/class-Cake.Validation.Validation.html
705
As seen in the example above, validators are built with a fluent interface that allows you to define rules for
each field you want to validate.
There were a few methods called in the example above, so lets go over the various features. The add()
method allows you to add new rules to a validator. You can either add rules individually or in groups as seen
above.
If you have multiple fields that are required, you can define them as a list:
// Define multiple fields for create
$validator->requirePresence(['author_id', 'title'], 'create');
// Define multiple fields for mixed modes
$validator->requirePresence([
'author_id' => [
'mode' => 'create',
'message' => 'An author is required.',
],
'published' => [
'mode' => 'update',
'message' => 'The published state is required.',
]
]);
706
If the minLength rule fails in the example above, the maxLength rule will not be run.
Creating Validators
707
Validation providers can be objects, or class names. If a class name is used the methods must be static. To
use a provider other than default, be sure to set the provider key in your rule:
// Use a rule from the table provider
$validator->add('title', 'custom', [
'rule' => 'customTableMethod',
'provider' => 'table'
]);
You can use the Localized plugin155 to get providers based on countries. With this plugin, youll be able to
validate model fields, depending on a country, ie:
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class PostsTable extends Table
{
public function validationDefault(Validator $validator)
{
$validator = new Validator();
// add the provider to the validator
$validator->provider('fr', 'Localized\Validation\FrValidation');
155
708
https://github.com/cakephp/localized
The localized plugin uses the two letter ISO code of the countries for validation, like en, fr, de.
There are a few methods that are common to all classes, defined through the ValidationInterface interface156 :
phone() to check a phone number
postal() to check a postal code
personId() to check a country specific person ID
https://github.com/cakephp/localized/blob/master/src/Validation/ValidationInterface.php
Creating Validators
709
Closures or callable methods will receive 2 arguments when called. The first will be the value for the field
being validated. The second is a context array containing data related to the validation process:
data: The original data passed to the validation method, useful if you plan to create rules comparing
values.
providers: The complete list of rule provider objects, useful if you need to create complex rules by
calling multiple providers.
newRecord: Whether the validation call is for a new record or a preexisting one.
If you need to pass additional data to your validation methods such as the current users id, you can use a
custom dynamic provider from your controller.
$this->Examples->validator('default')->provider('passed', [
'count' => $countFromController,
'userid' => $this->Auth->user('id')
]);
Then ensure that your validation method has the second context parameter.
public function customValidationMethod($check, array $context)
{
$userid = $context['providers']['passed']['userid'];
}
Conditional Validation
When defining validation rules, you can use the on key to define when a validation rule should be applied.
If left undefined, the rule will always be applied. Other valid values are create and update. Using one
of these values will make the rule apply to only create or update operations.
Additionally, you can provide a callable function that will determine whether or not a particular rule should
be applied:
$validator->add('picture', 'file', [
'rule' => ['mimeType', ['image/jpeg', 'image/png']],
'on' => function ($context) {
return !empty($context['data']['show_profile_picture']);
}
]);
You can access the other submitted field values using the $context['data'] array. The above example
will make the rule for picture optional depending on whether the value for show_profile_picture
is empty. You could also use the uploadedFile validation rule to create optional file upload inputs:
$validator->add('picture', 'file', [
'rule' => ['uploadedFile', ['optional' => true]],
]);
The allowEmpty(), notEmpty() and requirePresence() methods will also accept a callback
function as their last argument. If present, the callback determines whether or not the rule should be applied.
710
Likewise, a field can be required to be populated when certain conditions are met:
$validator->notEmpty('email_frequency', 'This field is required', function (
$context) {
return !empty($context['data']['wants_newsletter']);
});
In the above example, the email_frequency field cannot be left empty if the the user wants to receive
the newsletter.
Further its also possible to require a field to be present under certain conditions only:
$validator->requirePresence('full_name', function ($context) {
if (isset($context['data']['action'])) {
return $context['data']['action'] === 'subscribe';
}
return false;
});
$validator->requirePresence('email');
This would require the full_name field to be present only in case the user wants to create a subscription, while the email field would always be required, since it would also be needed when canceling a
subscription.
New in version 3.1.1: The callable support for requirePresence() was added in 3.1.1
Nesting Validators
New in version 3.0.5.
When validating Modelless Forms with nested data, or when working with models that contain array data
types, it is necessary to validate the nested data you have. CakePHP makes it simple to add validators to
specific attributes. For example, assume you are working with a non-relational database and need to store
an article and its comments:
$data = [
'title' => 'Best article',
'comments' => [
['comment' => '']
]
];
Creating Validators
711
You can create 1:1 relationships with addNested() and 1:N relationships with addNestedMany().
With both methods, the nested validators errors will contribute to the parent validators errors and influence
the final result.
Validating Data
Now that youve created a validator and added the rules you want to it, you can start using it to validate
data. Validators are able to validate array data. For example, if you wanted to validate a contact form before
creating and sending an email you could do the following:
use Cake\Validation\Validator;
$validator = new Validator();
$validator
->requirePresence('email')
->add('email', 'validFormat', [
'rule' => 'email',
'message' => 'E-mail must be valid'
712
])
->requirePresence('name')
->notEmpty('name', 'We need your name.')
->requirePresence('comment')
->notEmpty('comment', 'You need to give a comment.');
$errors = $validator->errors($this->request->data());
if (empty($errors)) {
// Send an email.
}
The errors() method will return a non-empty array when there are validation failures. The returned array
of errors will be structured like:
$errors = [
'email' => ['E-mail must be valid']
];
If you have multiple errors on a single field, an array of error messages will be returned per field. By default
the errors() method applies rules for the create mode. If youd like to apply update rules you can do
the following:
$errors = $validator->errors($this->request->data(), false);
if (empty($errors)) {
// Send an email.
}
Note:
If
you
need
to
validate
entities
you
should
use
methods
like
ORM\Table::newEntity(),
ORM\Table::newEntities(),
ORM\Table::patchEntity(), ORM\Table::patchEntities() or ORM\Table::save()
as they are designed for that.
Validating Entities
While entities are validated as they are saved, you may also want to validate entities before attempting to
do any saving. Validating entities before saving is done automatically when using the newEntity(),
newEntities(), patchEntity() or patchEntities():
// In the ArticlesController class
$article = $this->Articles->newEntity($this->request->data());
if ($article->errors()) {
// Do work to show error messages.
}
Similarly, when you need to pre-validate multiple entities at a time, you can use the newEntities()
method:
Validating Entities
713
Validation is commonly used for user-facing forms or interfaces, and thus it is not limited to only validating
columns in the table schema. However, maintaining integrity of data regardless where it came from is
important. To solve this problem CakePHP offers a second level of validation which is called application
rules. You can read more about them in the Applying Application Rules section.
Core rules that take additional parameters should have an array for the rule key that contains the rule as
the first element, and the additional parameters as the remaining parameters.
157
714
http://api.cakephp.org/3.0/class-Cake.Validation.Validation.html
CHAPTER 30
App Class
class Cake\Core\App
The App class is responsible for resource location and path management.
Finding Classes
static Cake\Core\App::classname($name, $type = , $suffix = )
This method is used to resolve classnames throughout CakePHP. It resolves the short form names CakePHP
uses and returns the fully resolved classname:
// Resolve a short classname with the namespace + suffix.
App::classname('Auth', 'Controller/Component', 'Component');
// Returns Cake\Controller\Component\AuthComponent
// Resolve a plugin name.
App::classname('DebugKit.Toolbar', 'Controller/Component', 'Component');
// Returns DebugKit\Controller\Component\ToolbarComponent
// Names with \ in them will be returned unaltered.
App::classname('App\Cache\ComboCache');
// Returns App\Cache\ComboCache
When resolving classes, the App namespace will be tried, and if the class does not exist the Cake namespace
will be attempted. If both classnames do not exist, false will be returned.
715
This can be done for all namespaces that are part of your application. You can also fetch paths for a plugin:
// Returns the component paths in DebugKit
App::path('Component', 'DebugKit');
App::path() will only return the default path, and will not be able to provide any information about
additional paths the autoloader is configured for.
static Cake\Core\App::core(string $package)
Used for finding the path to a package inside CakePHP:
// Get the path to Cache engines.
App::core('Cache/Engine');
Locating Plugins
static Cake\Core\Plugin::path(string $plugin)
Plugins can be located with Plugin. Using Plugin::path('DebugKit'); for example, will give you
the full path to the DebugKit plugin:
$path = Plugin::path('DebugKit');
Locating Themes
Since themes are plugins, you can use the methods above to get the path to a theme.
716
},
"classmap": [
"vendor/Acme/AcmeLib"
]
}
If your vendor library does not use classes, and instead provides functions, you can configure Composer to
load these files at the beginning of each request using the files autoloading strategy:
"autoload": {
"psr-4": {
"App\\": "App",
"App\\Test\\": "Test",
"": "./Plugin"
},
"files": [
"vendor/Acme/AcmeLib/functions.php"
]
}
After configuring the vendor libraries you will need to regenerate your applications autoloader using:
$ php composer.phar dump-autoload
If you happen to not be using Composer in your application, you will need to manually load all vendor
libraries yourself.
717
718
CHAPTER 31
Collections
class Cake\Collection\Collection
The collection classes provide a set of tools to manipulate arrays or Traversable objects. If you have
ever used underscore.js, you have an idea of what you can expect from the collection classes.
Collection instances are immutable; modifying a collection will instead generate a new collection. This
makes working with collection objects more predictable as operations are side-effect free.
Quick Example
Collections can be created using an array or Traversable object. Youll also interact with collections
every time you interact with the ORM in CakePHP. A simple use of a Collection would be:
use Cake\Collection\Collection;
$items = ['a' => 1, 'b' => 2, 'c' => 3];
$collection = new Collection($items);
// Create a new collection containing elements
// with a value greater than one.
$overOne = $collection->filter(function ($value, $key, $iterator) {
return $value > 1;
});
You can also use the collection() helper function instead of new Collection():
$items = ['a' => 1, 'b' => 2, 'c' => 3];
// These both make a Collection instance.
$collectionA = new Collection($items);
$collectionB = collection($items);
The benefit of the helper method is that it is easier to chain off of than (new Collection($items)).
719
List of Methods
append
contains
every
groupBy
last
max
reject
some
take
transpose
buffered
countBy
extract
indexBy
listNested
min
sample
sortBy
through
combine
chunk
filter
insert
map
nest
shuffle
stopWhen
unfold
compile
each
first
isEmpty
match
reduce
skip
sumOf
zip
Iterating
Cake\Collection\Collection::each(callable $c)
Collections can be iterated and/or transformed into new collections with the each() and map() methods.
The each() method will not create a new collection, but will allow you to modify any objects within the
collection:
$collection = new Collection($items);
$collection = $collection->each(function ($value, $key) {
echo "Element $key: $value";
});
The return of each() will be the collection object. Each will iterate the collection immediately applying
the callback to each value in the collection.
Cake\Collection\Collection::map(callable $c)
The map() method will create a new collection based on the output of the callback being applied to each
object in the original collection:
$items = ['a' => 1, 'b' => 2, 'c' => 3];
$collection = new Collection($items);
$new = $collection->map(function ($value, $key) {
return $value * 2;
});
// $result contains ['a' => 2, 'b' => 4, 'c' => 6];
$result = $new->toArray();
The map() method will create a new iterator which lazily creates the resulting items when iterated.
720
Cake\Collection\Collection::extract($matcher)
One of the most common uses for a map() function is to extract a single column from a collection. If
you are looking to build a list of elements containing the values for a particular property, you can use the
extract() method:
$collection = new Collection($people);
$names = $collection->extract('name');
// $result contains ['mark', 'jose', 'barbara'];
$result = $names->toArray();
As with many other functions in the collection class, you are allowed to specify a dot-separated path for
extracting columns. This example will return a collection containing the author names from a list of articles:
$collection = new Collection($articles);
$names = $collection->extract('author.name');
// $result contains ['Maria', 'Stacy', 'Larry'];
$result = $names->toArray();
Finally, if the property you are looking after cannot be expressed as a path, you can use a callback function
to return it:
$collection = new Collection($articles);
$names = $collection->extract(function ($article) {
return $article->author->name . ', ' . $article->author->last_name;
});
Often, the properties you need to extract a common key present in multiple arrays or objects that are deeply
nested inside other structures. For those cases you can use the {*} matcher in the path key. This matcher is
often helpful when matching HasMany and BelongsToMany association data:
$data = [
[
'name' => 'James',
'phone_numbers' => [
['number' => 'number-1'],
['number' => 'number-2'],
['number' => 'number-3'],
]
],
[
'name' => 'James',
'phone_numbers' => [
['number' => 'number-4'],
['number' => 'number-5'],
]
]
];
$numbers = (new Collection($data))->extract('phone_numbers.{*}.number');
$numbers->toList();
Iterating
721
This last example uses toList() unlike other examples, which is important when were getting results
with possibly duplicate keys. By using toList() well be guaranteed to get all values even if there are
duplicate keys.
Unlike Cake\Utility\Hash::extract() this method only supports the {*} wildcard. All other
wildcard and attributes matchers are not supported.
Cake\Collection\Collection::combine($keyPath, $valuePath, $groupPath = null)
Collections allow you to create a new collection made from keys and values in an existing collection. Both
the key and value paths can be specified with dot notation paths:
$items = [
['id' => 1, 'name' => 'foo', 'parent' => 'a'],
['id' => 2, 'name' => 'bar', 'parent' => 'b'],
['id' => 3, 'name' => 'baz', 'parent' => 'a'],
];
$combined = (new Collection($items))->combine('id', 'name');
// Result will look like this when converted to array
[
1 => 'foo',
2 => 'bar',
3 => 'baz',
];
You can also optionally use a groupPath to group results based on a path:
$combined = (new Collection($items))->combine('id', 'name', 'parent');
// Result will look like this when converted to array
[
'a' => [1 => 'foo', 3 => 'baz'],
'b' => [2 => 'bar']
];
Finally you can use closures to build keys/values/groups paths dynamically, for example when working with
entities and dates (converted to Cake/Time instances by the ORM) you may want to group results by date:
$combined = (new Collection($entities))->combine(
'id',
function ($entity) { return $entity; },
function ($entity) { return $entity->date->toDateString(); }
);
// Result will look like this when converted to array
[
'date string like 2015-05-01' => ['entity1->id' => entity1, 'entity2->id'
=> entity2, ..., 'entityN->id' => entityN]
'date string like 2015-06-01' => ['entity1->id' => entity1, 'entity2->id'
=> entity2, ..., 'entityN->id' => entityN]
722
Cake\Collection\Collection::stopWhen(callable $c)
You can stop the iteration at any point using the stopWhen() method. Calling it in a collection will create
a new one that will stop yielding results if the passed callable returns false for one of the elements:
$items = [10, 20, 50, 1, 2];
$collection = new Collection($items);
$new = $collection->stopWhen(function ($value, $key) {
// Stop on the first value bigger than 30
return $value > 30;
});
// $result contains [10, 20];
$result = $new->toArray();
Cake\Collection\Collection::unfold(callable $c)
Sometimes the internal items of a collection will contain arrays or iterators with more items. If you wish
to flatten the internal structure to iterate once over all elements you can use the unfold() method. It will
create a new collection that will yield every single element nested in the collection:
$items = [[1, 2, 3], [4, 5]];
$collection = new Collection($items);
$new = $collection->unfold();
// $result contains [1, 2, 3, 4, 5];
$result = $new->toList();
When passing a callable to unfold() you can control what elements will be unfolded from each item in
the original collection. This is useful for returning data from paginated services:
$pages = [1, 2, 3, 4];
$collection = new Collection($pages);
$items = $collection->unfold(function ($page, $key) {
// An imaginary web service that returns a page of results
return MyService::fetchPage($page)->toArray();
});
$allPagesItems = $items->toList();
If you are using PHP 5.5+, you can use the yield keyword inside unfold() to return as many elements
for each item in the collection as you may need:
$oddNumbers = [1, 3, 5, 7];
$collection = new Collection($oddNumbers);
$new = $collection->unfold(function ($oddNumber) {
yield $oddNumber;
yield $oddNumber + 1;
});
Iterating
723
Cake\Collection\Collection::chunk($chunkSize)
When dealing with big amounts of items in a collection, it may make sense to process the elements in
batches instead of one by one. For splitting a collection into multiple arrays of a certain size, you can use
the chunk() function:
$items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
$collection = new Collection($items);
$chunked = $collection->chunk(2);
$chunked->toList(); // [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]]
The chunk function is particularly useful when doing batch processing, for example with a database result:
$collection = new Collection($articles);
$collection->map(function ($article) {
// Change a property in the article
$article->property = 'changed';
})
->chunk(20)
->each(function ($batch) {
myBulkSave($batch); // This function will be called for each batch
});
Filtering
Cake\Collection\Collection::filter(callable $c)
Collections make it easy to filter and create new collections based on the result of callback functions. You
can use filter() to create a new collection of elements matching a criteria callback:
$collection = new Collection($people);
$ladies = $collection->filter(function ($person, $key) {
return $person->gender === 'female';
});
$guys = $collection->filter(function ($person, $key) {
return $person->gender === 'male';
});
Cake\Collection\Collection::reject(callable $c)
The inverse of filter() is reject(). This method does a negative filter, removing elements that match
the filter function:
$collection = new Collection($people);
$ladies = $collection->reject(function ($person, $key) {
return $person->gender === 'male';
});
724
Cake\Collection\Collection::every(callable $c)
You can do truth tests with filter functions. To see if every element in a collection matches a test you can
use every():
$collection = new Collection($people);
$allYoungPeople = $collection->every(function ($person) {
return $person->age < 21;
});
Cake\Collection\Collection::some(callable $c)
You can see if the collection contains at least one element matching a filter function using the some()
method:
$collection = new Collection($people);
$hasYoungPeople = $collection->some(function ($person) {
return $person->age < 21;
});
Cake\Collection\Collection::match(array $conditions)
If you need to extract a new collection containing only the elements that contain a given set of properties,
you should use the match() method:
$collection = new Collection($comments);
$commentsFromMark = $collection->match(['user.name' => 'Mark']);
Cake\Collection\Collection::firstMatch(array $conditions)
The property name can be a dot-separated path. You can traverse into nested entities and match the
values they contain. When you only need the first matching element from a collection, you can use
firstMatch():
$collection = new Collection($comments);
$comment = $collection->firstMatch([
'user.name' => 'Mark',
'active' => true
]);
As you can see from the above, both match() and firstMatch() allow you to provide multiple conditions to match on. In addition, the conditions can be for different paths, allowing you to express complex
conditions to match against.
Aggregation
Cake\Collection\Collection::reduce(callable $c)
The counterpart of a map() operation is usually a reduce. This function will help you build a single result
out of all the elements in a collection:
Aggregation
725
In the above example, $totalPrice will be the sum of all single prices contained in the collection. Note
the second argument for the reduce() function takes the initial value for the reduce operation you are
performing:
$allTags = $collection->reduce(function ($accumulated, $article) {
return array_merge($accumulated, $article->tags);
}, []);
Cake\Collection\Collection::min(string|callable
$callback,
SORT_NUMERIC)
$type
To extract the minimum value for a collection based on a property, just use the min() function. This will
return the full element from the collection and not just the smallest value found:
$collection = new Collection($people);
$youngest = $collection->min('age');
echo $youngest->name;
You are also able to express the property to compare by providing a path or a callback function:
$collection = new Collection($people);
$personYoungestChild = $collection->min(function ($person) {
return $person->child->age;
});
$personWithYoungestDad = $collection->min('dad.age');
Cake\Collection\Collection::max(string|callable
$callback,
SORT_NUMERIC)
$type
The same can be applied to the max() function, which will return a single element from the collection
having the highest property value:
$collection = new Collection($people);
$oldest = $collection->max('age');
$personOldestChild = $collection->max(function ($person) {
return $person->child->age;
});
$personWithOldestDad = $collection->min('dad.age');
Cake\Collection\Collection::sumOf(string|callable $callback)
Finally, the sumOf() method will return the sum of a property of all elements:
$collection = new Collection($people);
$sumOfAges = $collection->sumOf('age');
726
As usual, it is possible to provide either a dot-separated path for nested properties or your own callback
function to generate the groups dynamically:
$commentsByUserId = $comments->groupBy('user.id');
$classResults = $students->groupBy(function ($student) {
return $student->grade > 6 ? 'approved' : 'denied';
});
Cake\Collection\Collection::countBy($callback)
If you only wish to know the number of occurrences per group, you can do so by using the countBy()
method. It takes the same arguments as groupBy so it should be already familiar to you:
$classResults = $students->countBy(function ($student) {
return $student->grade > 6 ? 'approved' : 'denied';
Aggregation
727
});
// Result could look like this when converted to array:
['approved' => 70, 'denied' => 20]
Cake\Collection\Collection::indexBy($callback)
There will be certain cases where you know an element is unique for the property you want to group by. If
you wish a single result per group, you can use the function indexBy():
$usersById = $users->indexBy('id');
// When converted to array result could look like
[
1 => 'markstory',
3 => 'jose_zap',
4 => 'jrbasso'
]
As with the groupBy() function you can also use a property path or a callback:
$articlesByAuthorId = $articles->indexBy('author.id');
$filesByHash = $files->indexBy(function ($file) {
return md5($file);
});
Cake\Collection\Collection::zip($elements)
The elements of different collections can be grouped together using the zip() method. It will return a
new collection containing an array grouping the elements from each collection that are placed at the same
position:
$odds = new Collection([1, 3, 5]);
$pairs = new Collection([2, 4, 6]);
$combined = $odds->zip($pairs)->toList(); // [[1, 2], [3, 4], [5, 6]]
As you can already see, the zip() method is very useful for transposing multidimensional arrays:
728
$data = [
2014 => ['jan' => 100, 'feb' => 200],
2015 => ['jan' => 300, 'feb' => 500],
2016 => ['jan' => 400, 'feb' => 600],
]
// Getting jan and feb data together
$firstYear = new Collection(array_shift($data));
$firstYear->zip($data[0], $data[1])->toList();
// Or $firstYear->zip(...$data) in PHP >= 5.6
// Returns
[
[100, 300, 400],
[200, 500, 600]
]
Sorting
Cake\Collection\Collection::sortBy($callback)
Collection values can be sorted in ascending or descending order based on a column or custom function. To
create a new sorted collection out of the values of another one, you can use sortBy:
$collection = new Collection($people);
$sorted = $collection->sortBy('age');
As seen above, you can sort by passing the name of a column or property that is present in the collection
values. You are also able to specify a property path instead using the dot notation. The next example will
sort articles by their authors name:
$collection = new Collection($articles);
$sorted = $collection->sortBy('author.name');
The sortBy() method is flexible enough to let you specify an extractor function that will let you dynamically select the value to use for comparing two different values in the collection:
$collection = new Collection($articles);
$sorted = $collection->sortBy(function ($article) {
return $article->author->name . '-' . $article->title;
});
In order to specify in which direction the collection should be sorted, you need to provide either SORT_ASC
or SORT_DESC as the second parameter for sorting in ascending or descending direction respectively. By
default, collections are sorted in ascending direction:
$collection = new Collection($people);
$sorted = $collection->sortBy('age', SORT_ASC);
Sorting
729
Sometimes you will need to specify which type of data you are trying to compare so that you get consistent
results. For this purpose, you should supply a third argument in the sortBy() function with one of the
following constants:
SORT_NUMERIC: For comparing numbers
SORT_STRING: For comparing string values
SORT_NATURAL: For sorting string containing numbers and youd like those numbers to be order
in a natural way. For example: showing 10 after 2.
SORT_LOCALE_STRING: For comparing strings based on the current locale.
By default, SORT_NUMERIC is used:
$collection = new Collection($articles);
$sorted = $collection->sortBy('title', SORT_ASC, SORT_NATURAL);
Warning: If is often expensive to iterate sorted collections more than once. If you plan to do so,
consider converting the collection to an array or simply use the compile() method on it.
null, 'name'
1, 'name' =>
1, 'name' =>
1, 'name' =>
6, 'name' =>
null, 'name'
=> 'Birds'],
'Land Birds'],
'Eagle'],
'Seagull'],
'Clown Fish'],
=> 'Fish'],
$collection->nest('id', 'parent_id')->toArray();
// Returns
[
[
'id' => 1,
'parent_id' => null,
'name' => 'Birds',
'children' => [
['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds', 'children'
=> []],
730
[]],
[]],
Children elements are nested inside the children property inside each of the items in the collection. This
type of data representation is helpful for rendering menus or traversing elements up to certain level in the
tree.
Cake\Collection\Collection::listNested($dir = desc, $nestingKey = children)
The inverse of nest() is listNested(). This method allows you to flatten a tree structure back into a
linear structure. It takes two parameters; the first one is the traversing mode (asc, desc or leaves), and the
second one is the name of the property containing the children for each element in the collection.
Taking the input the nested collection built in the previous example, we can flatten it:
$nested->listNested()->toList();
// Returns
[
['id' =>
['id' =>
['id' =>
['id' =>
['id' =>
['id' =>
]
1,
2,
3,
4,
6,
5,
'parent_id'
'parent_id'
'parent_id'
'parent_id'
'parent_id'
'parent_id'
=>
=>
=>
=>
=>
=>
null, 'name'
1, 'name' =>
1, 'name' =>
1, 'name' =>
null, 'name'
6, 'name' =>
By default, the tree is traversed from the root to the leaves. You can also instruct it to only return the leaf
elements in the tree:
$nested->listNested()->toArray();
// Returns
[
['id' => 3, 'parent_id' => 1, 'name' => 'Eagle'],
['id' => 4, 'parent_id' => 1, 'name' => 'Seagull'],
['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish']
]
731
Once you have converted a tree into a nested list, you can use the printer() method to configure how
the list output should be formatted:
$nested->listNested()->printer('name', 'id', '--')->toArray();
// Returns
[
3 => 'Eagle',
4 => 'Seagull',
5 -> '--Clown Fish',
]
The printer() method also lets you use a callback to generate the keys and or values:
$nested->listNested()->printer(
function ($el) {
return $el->name;
},
function ($el) {
return $el->id;
}
);
Other Methods
Cake\Collection\Collection::isEmpty()
Allows you to see if a collection contains any elements:
$collection = new Collection([]);
// Returns true
$collection->isEmpty();
$collection = new Collection([1]);
// Returns false
$collection->isEmpty();
Cake\Collection\Collection::contains($value)
Collections allow you to quickly check if they contain one particular value: by using the contains()
method:
$items = ['a' => 1, 'b' => 2, 'c' => 3];
$collection = new Collection($items);
$hasThree = $collection->contains(3);
Comparisons are performed using the === operator. If you wish to do looser comparison types you can use
the some() method.
Cake\Collection\Collection::shuffle()
732
Sometimes you may wish to show a collection of values in a random order. In order to create a new collection
that will return each value in a randomized position, use the shuffle:
$collection = new Collection(['a' => 1, 'b' => 2, 'c' => 3]);
// This could return [2, 3, 1]
$collection->shuffle()->toArray();
Cake\Collection\Collection::transpose()
When you transpose a collection, you get a new collection containing a row made of the each of the original
columns:
$items = [
['Products', '2012', '2013', '2014'],
['Product A', '200', '100', '50'],
['Product B', '300', '200', '100'],
['Product C', '400', '300', '200'],
]
$transpose = (new Collection($items))->transpose()->toList();
// Returns
[
['Products', 'Product A', 'Product B', 'Product C'],
['2012', '200', '300', '400'],
['2013', '100', '200', '300'],
['2014', '50', '100', '200'],
]
Withdrawing Elements
Cake\Collection\Collection::sample(int $size)
Shuffling a collection is often useful when doing quick statistical analysis. Another common operation
when doing this sort of task is withdrawing a few random values out of a collection so that more tests can
be performed on those. For example, if you wanted to select 5 random users to which youd like to apply
some A/B tests to, you can use the sample() function:
$collection = new Collection($people);
// Withdraw maximum 20 random users from this collection
$testSubjects = $collection->sample(20);
sample() will take at most the number of values you specify in the first argument. If there are not enough
elements in the collection to satisfy the sample, the full collection in a random order is returned.
Cake\Collection\Collection::take(int $size, int $from)
Whenever you want to take a slice of a collection use the take() function, it will create a new collection
with at most the number of values you specify in the first argument, starting from the position passed in the
Other Methods
733
second argument:
$topFive = $collection->sortBy('age')->take(5);
// Take 5 people from the collection starting from position 4
$nextTopFive = $collection->sortBy('age')->take(5, 4);
Cake\Collection\Collection::first()
One of the most common uses of take() is getting the first element in the collection. A shortcut method
for achieving the same goal is using the first() method:
$collection = new Collection([5, 4, 3, 2]);
$collection->first(); // Returns 5
Cake\Collection\Collection::last()
Similarly, you can get the last element of a collection using the last() method:
$collection = new Collection([5, 4, 3, 2]);
$collection->last(); // Returns 2
Expanding Collections
Cake\Collection\Collection::append(array|Traversable $items)
You can compose multiple collections into a single one. This enables you to gather data from various
sources, concatenate it, and apply other collection functions to it very smoothly. The append() method
will return a new collection containing the values from both sources:
$cakephpTweets = new Collection($tweets);
$myTimeline = $cakephpTweets->append($phpTweets);
// Tweets containing cakefest from both sources
$myTimeline->filter(function ($tweet) {
return strpos($tweet, 'cakefest');
});
734
Warning: When appending from different sources, you can expect some keys from both collections
to be the same. For example, when appending two simple arrays. This can present a problem when
converting a collection to an array using toArray(). If you do not want values from one collection to
override others in the previous one based on their key, make sure that you call toList() in order to
drop the keys and preserve all values.
Modifiying Elements
Cake\Collection\Collection::insert(string $path, array|Traversable $items)
At times, you may have two separate sets of data that you would like to insert the elements of one set into
each of the elements of the other set. This is a very common case when you fetch data from a data source
that does not support data-merging or joins natively.
Collections offer an insert() method that will allow you to insert each of the elements in one collection
into a property inside each of the elements of another collection:
$users = [
['username' => 'mark'],
['username' => 'juan'],
['username' => 'jose']
];
$languages = [
['PHP', 'Python', 'Ruby'],
['Bash', 'PHP', 'Javascript'],
['Javascript', 'Prolog']
];
$merged = (new Collection($users))->insert('skills', $languages);
When converted to an array, the $merged collection will look like this:
[
['username' => 'mark', 'skills' => ['PHP', 'Python', 'Ruby']],
['username' => 'juan', 'skills' => ['Bash', 'PHP', 'Javascript']],
['username' => 'jose', 'skills' => ['Javascript', 'Prolog']]
];
The first parameter for the insert() method is a dot-separated path of properties to follow so that the
elements can be inserted at that position. The second argument is anything that can be converted to a
collection object.
Please observe that elements are inserted by the position they are found, thus, the first element of the second
collection is merged into the first element of the first collection.
If there are not enough elements in the second collection to insert into the first one, then the target property
will be filled with null values:
Other Methods
735
$languages = [
['PHP', 'Python', 'Ruby'],
['Bash', 'PHP', 'Javascript']
];
$merged = (new Collection($users))->insert('skills', $languages);
// Will yield
[
['username' => 'mark', 'skills' => ['PHP', 'Python', 'Ruby']],
['username' => 'juan', 'skills' => ['Bash', 'PHP', 'Javascript']],
['username' => 'jose', 'skills' => null]
];
The insert() method can operate array elements or objects implementing the ArrayAccess interface.
736
if (!empty($row['total'])) {
$row['tax_amount'] = $row['total'] * 0.25;
}
// More code here...
return $modifiedRow;
}
}
// Use the logic in your map() call
$collection->map(new TotalOrderCalculator)
Cake\Collection\Collection::through(callable $c)
Sometimes a chain of collection method calls can become reusable in other parts of your application, but
only if they are called in that specific order. In those cases you can use through() in combination with a
class implementing __invoke to distribute your handy data processing calls:
$collection
->map(new ShippingCostCalculator)
->map(new TotalOrderCalculator)
->map(new GiftCardPriceReducer)
->buffered()
...
The above method calls can be extracted into a new class so they dont need to be repeated every time:
class FinalCheckOutRowProcessor
{
public function __invoke($collection)
{
return $collection
->map(new ShippingCostCalculator)
->map(new TotalOrderCalculator)
->map(new GiftCardPriceReducer)
->buffered()
...
}
}
// Now you can use the through() method to call all methods at once
$collection->through(new FinalCheckOutRowProcessor);
Optimizing Collections
Cake\Collection\Collection::buffered()
Other Methods
737
Collections often perform most operations that you create using its functions in a lazy way. This means
that even though you can call a function, it does not mean it is executed right away. This is true for a great
deal of functions in this class. Lazy evaluation allows you to save resources in situations where you dont
use all the values in a collection. You might not use all the values when iteration stops early, or when an
exception/failure case is reached early.
Additionally, lazy evaluation helps speed up some operations. Consider the following example:
$collection = new Collection($oneMillionItems);
$collection->map(function ($item) {
return $item * 2;
});
$itemsToShow = $collection->take(30);
Had the collections not been lazy, we would have executed one million operations, even though we only
wanted to show 30 elements out of it. Instead, our map operation was only applied to the 30 elements we
used. We can also derive benefits from this lazy evaluation for smaller collections when we do more than
one operation on them. For example: calling map() twice and then filter().
Lazy evaluation comes with its downside too. You could be doing the same operations more than once if
you optimize a collection prematurely. Consider this example:
$ages = $collection->extract('age');
$youngerThan30 = $ages->filter(function ($item) {
return $item < 30;
});
$olderThan30 = $ages->filter(function ($item) {
return $item > 30;
});
If we iterate both youngerThan30 and olderThan30, the collection would unfortunately execute the
extract() operation twice. This is because collections are immutable and the lazy-extracting operation
would be done for both filters.
Luckily we can overcome this issue with a single function. If you plan to reuse the values from certain
operations more than once, you can compile the results into another collection using the buffered()
function:
$ages = $collection->extract('age')->buffered();
$youngerThan30 = ...
$olderThan30 = ...
Now, when both collections are iterated, they will only call the extracting operation once.
738
// In PHP 5.5+
public function results()
{
...
foreach ($transientElements as $e) {
yield $e;
}
}
$rewindable = (new Collection(results()))->buffered();
Cloning Collections
Cake\Collection\Collection::compile(bool $preserveKeys = true)
Sometimes you need to get a clone of the elements from another collection. This is useful when you need to
iterate the same set from different places at the same time. In order to clone a collection out of another use
the compile() method:
$ages = $collection->extract('age')->compile();
foreach ($ages as $age) {
foreach ($collection as $element) {
echo h($element->name) . ' - ' . $age;
}
}
Other Methods
739
740
CHAPTER 32
The Folder and File utilities are convenience classes to help you read from and write/append to files; list
files within a folder and other common directory related tasks.
Basic Usage
Ensure the classes are loaded:
use Cake\Filesystem\Folder;
use Cake\Filesystem\File;
and search for all .ctp files within that folder using regex:
$files = $dir->find('.*\.ctp');
Now we can loop through the files and read from or write/append to the contents or simply delete the file:
foreach ($files as $file) {
$file = new File($dir->pwd() . DS . $file);
$contents = $file->read();
// $file->write('I am overwriting the contents of this file');
// $file->append('I am adding to the bottom of this file.');
// $file->delete(); // I am deleting this file
$file->close(); // Be sure to close the file when you're done
}
741
Folder API
class Cake\Filesystem\Folder(string $path = false, boolean $create = false, string|boolean
$mode = false)
// Create a new folder with 0755 permissions
$dir = new Folder('/path/to/folder', true, 0755);
property Cake\Filesystem\Folder::$path
Path of the current folder. Folder::pwd() will return the same information.
property Cake\Filesystem\Folder::$sort
Whether or not the list results should be sorted by name.
property Cake\Filesystem\Folder::$mode
Mode to be used when creating folders. Defaults to 0755. Does nothing on Windows machines.
static Cake\Filesystem\Folder::addPathElement(string $path, string $element)
Returns $path with $element added, with correct slash in-between:
$path = Folder::addPathElement('/a/path/for', 'testing');
// $path equals /a/path/for/testing
Cake\Filesystem\Folder::cd($path)
Change directory to $path. Returns false on failure:
$folder = new Folder('/foo');
echo $folder->path; // Prints /foo
$folder->cd('/bar');
echo $folder->path; // Prints /bar
$false = $folder->cd('/non-existent-folder');
742
Cake\Filesystem\Folder::dirsize()
Returns the size in bytes of this Folder and its contents.
Cake\Filesystem\Folder::errors()
Get the error from latest method.
Cake\Filesystem\Folder::find(string $regexpPattern = .*, boolean $sort = false)
Returns an array of all matching files in the current directory:
Folder API
743
// Find all .png in your webroot/img/ folder and sort the results
$dir = new Folder(WWW_ROOT . 'img');
$files = $dir->find('.*\.png', true);
/*
Array
(
[0] => cake.icon.png
[1] => test-error-icon.png
[2] => test-fail-icon.png
[3] => test-pass-icon.png
[4] => test-skip-icon.png
)
*/
Note: The folder find and findRecursive methods will only find files. If you would like to get folders and
files see Folder::read() or Folder::tree()
Cake\Filesystem\Folder::findRecursive(string $pattern = .*, boolean $sort =
false)
Returns an array of all matching files in and below the current directory:
// Recursively find files beginning with test or index
$dir = new Folder(WWW_ROOT);
$files = $dir->findRecursive('(test|index).*');
/*
Array
(
[0] => /var/www/cake/webroot/index.php
[1] => /var/www/cake/webroot/test.php
[2] => /var/www/cake/webroot/img/test-skip-icon.png
[3] => /var/www/cake/webroot/img/test-fail-icon.png
[4] => /var/www/cake/webroot/img/test-error-icon.png
[5] => /var/www/cake/webroot/img/test-pass-icon.png
)
*/
Cake\Filesystem\Folder::inCakePath(string $path = )
Returns true if the file is in a given CakePath.
Cake\Filesystem\Folder::inPath(string $path = , boolean $reverse = false)
Returns true if the file is in the given path:
$Folder = new Folder(WWW_ROOT);
$result = $Folder->inPath(APP);
// $result = true, /var/www/example/ is in /var/www/example/webroot/
$result = $Folder->inPath(WWW_ROOT . 'img' . DS, true);
// $result = true, /var/www/example/webroot/ is in /var/www/example/
webroot/img/
Cake\Filesystem\Folder::realpath(string $path)
Get the real path (taking .. and such into account).
static Cake\Filesystem\Folder::slashTerm(string $path)
Returns $path with added terminating slash (corrected for Windows or other OS).
Folder API
745
File API
class Cake\Filesystem\File(string $path, boolean $create = false, integer $mode = 755)
// Create a new file with 0644 permissions
$file = new File('/path/to/file.php', true, 0644);
property Cake\Filesystem\File::$Folder
The Folder object of the file.
property Cake\Filesystem\File::$name
The name of the file with the extension. Differs from File::name() which returns the name
without the extension.
property Cake\Filesystem\File::$info
An array of file info. Use File::info() instead.
property Cake\Filesystem\File::$handle
Holds the file handler resource if the file is opened.
property Cake\Filesystem\File::$lock
Enable locking for file reading and writing.
property Cake\Filesystem\File::$path
The current files absolute path.
Cake\Filesystem\File::append(string $data, boolean $force = false)
Append the given data string to the current file.
Cake\Filesystem\File::close()
Closes the current file if it is opened.
Cake\Filesystem\File::copy(string $dest, boolean $overwrite = true)
Copy the file to $dest.
Cake\Filesystem\File::create()
Creates the file.
Cake\Filesystem\File::delete()
Deletes the file.
Cake\Filesystem\File::executable()
Returns true if the file is executable.
Cake\Filesystem\File::exists()
Returns true if the file exists.
Cake\Filesystem\File::ext()
Returns the file extension.
746
Cake\Filesystem\File::Folder()
Returns the current folder.
Cake\Filesystem\File::group()
Returns the files group, or false in case of an error.
Cake\Filesystem\File::info()
Returns the file info.
Cake\Filesystem\File::lastAccess()
Returns last access time.
Cake\Filesystem\File::lastChange()
Returns last modified time, or false in case of an error.
Cake\Filesystem\File::md5(integer|boolean $maxsize = 5)
Get the MD5 Checksum of file with previous check of filesize, or false in case of an error.
Cake\Filesystem\File::name()
Returns the file name without extension.
Cake\Filesystem\File::offset(integer|boolean $offset = false, integer $seek = 0)
Sets or gets the offset for the currently opened file.
Cake\Filesystem\File::open(string $mode = r, boolean $force = false)
Opens the current file with the given $mode.
Cake\Filesystem\File::owner()
Returns the files owner.
Cake\Filesystem\File::perms()
Returns the chmod (permissions) of the file.
static Cake\Filesystem\File::prepare(string $data, boolean $forceWindows = false)
Prepares a ascii string for writing. Converts line endings to the correct terminator for the current
platform. For Windows rn will be used, n for all other platforms.
Cake\Filesystem\File::pwd()
Returns the full path of the file.
Cake\Filesystem\File::read(string $bytes = false, string $mode = rb, boolean $force =
false)
Return the contents of the current file as a string or return false on failure.
Cake\Filesystem\File::readable()
Returns true if the file is readable.
Cake\Filesystem\File::safe(string $name = null, string $ext = null)
Makes filename safe for saving.
Cake\Filesystem\File::size()
Returns the filesize in bytes.
Cake\Filesystem\File::writable()
Returns true if the file is writable.
File API
747
748
CHAPTER 33
Hash
class Cake\Utility\Hash
Array management, if done right, can be a very powerful and useful tool for building smarter, more optimized code. CakePHP offers a very useful set of static utilities in the Hash class that allow you to do just
that.
CakePHPs Hash class can be called from any model or controller in the same way Inflector is called.
Example: Hash::combine().
Expression Types
Expression
{n}
{s}
Foo
Definition
Represents a numeric key. Will match any string or numeric key.
Represents a string. Will match any string value including numeric string values.
Matches keys with the exact same value.
All expression elements are supported by all methods. In addition to expression elements, you can use
attribute matching with certain methods. They are extract(), combine(), format(), check(),
map(), reduce(), apply(), sort(), insert(), remove() and nest().
749
Definition
Match elements with a given array key.
Match elements with id equal to 2.
Match elements with id not equal to 2.
Match elements with id greater than 2.
Match elements with id greater than or equal to 2.
Match elements with id less than 2
Match elements with id less than or equal to 2.
Match elements that have values matching the regular expression inside ....
750
You can use paths using {n} and {s} to insert data into multiple points:
$users = Hash::insert($users, '{n}.new', 'value');
Using {n} and {s} will allow you to remove multiple values at once. You can also use attribute
matchers with remove():
$data = [
0 => ['clear' => true, 'Item' => ['id' => 1, 'title' => 'first']],
1 => ['Item' => ['id' => 2, 'title' => 'second']],
2 => ['Item' => ['id' => 3, 'title' => 'third']],
3 => ['clear' => true, 'Item' => ['id' => 4, 'title' => 'fourth']],
4 => ['Item' => ['id' => 5, 'title' => 'fifth']],
];
$result = Hash::remove($data, '{n}[clear].Item[id=4]');
751
752
]
*/
$result = Hash::combine($a, '{n}.User.id', '{n}.User.Data');
/* $result now looks like:
[
[2] => [
[user] => mariano.iglesias
[name] => Mariano Iglesias
]
[14] => [
[user] => phpnut
[name] => Larry E. Masters
]
]
*/
$result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.name');
/* $result now looks like:
[
[2] => Mariano Iglesias
[14] => Larry E. Masters
]
*/
$result = Hash::combine($a, '{n}.User.id', '{n}.User.Data', '{n}.User.
group_id');
/* $result now looks like:
[
[1] => [
[2] => [
[user] => mariano.iglesias
[name] => Mariano Iglesias
]
]
[2] => [
[14] => [
[user] => phpnut
[name] => Larry E. Masters
]
]
]
*/
$result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.name', '{n}.
User.group_id');
/* $result now looks like:
[
[1] => [
[2] => Mariano Iglesias
]
[2] => [
[14] => Larry E. Masters
753
]
]
*/
You can provide arrays for both $keyPath and $valuePath. If you do this, the first value will be
used as a format string, for values extracted by the other paths:
$result = Hash::combine(
$a,
'{n}.User.id',
['%s: %s', '{n}.User.Data.user', '{n}.User.Data.name'],
'{n}.User.group_id'
);
/* $result now looks like:
[
[1] => [
[2] => mariano.iglesias: Mariano Iglesias
]
[2] => [
[14] => phpnut: Larry E. Masters
]
]
/
*
$result = Hash::combine(
$a,
['%s: %s', '{n}.User.Data.user', '{n}.User.Data.name'],
'{n}.User.id'
);
/* $result now looks like:
[
[mariano.iglesias: Mariano Iglesias] => 2
[phpnut: Larry E. Masters] => 14
]
*/
754
=> 'main'],
=> 'about']
=> 'main'],
=> 'about'],
=> 'contact'],
755
// false
$result = Hash::contains($b, $a);
// true
756
/* $res now
[
[0]
[2]
[3]
[4]
looks like:
=>
=>
=>
=>
0
true
0
[
[0] => one thing
[1] => I can tell you
[2] => is you got to be
]
]
*/
757
];
$res = Hash::expand($data);
/* $res now looks like:
[
[
'Post' => ['id' => '1', 'title' => 'First Post'],
'Author' => ['id' => '1', 'user' => 'Kyle'],
],
[
'Post' => ['id' => '2', 'title' => 'Second Post'],
'Author' => ['id' => '3', 'user' => 'Crystal'],
],
];
*/
$array = [
[
'id' => '48c2570e-dfa8-4c32-a35e-0d71cbdd56cb',
'name' => 'mysql raleigh-workshop-08 < 2008-09-05.sql ',
'description' => 'Importing an sql dump'
],
[
'id' => '48c257a8-cf7c-4af2-ac2f-114ecbdd56cb',
'name' => 'pbpaste | grep -i Unpaid | pbcopy',
'description' => 'Remove all lines that say "Unpaid".',
]
];
$arrayB = 4;
$arrayC = [0 => "test array", "cats" => "dogs", "people" => 1267];
$arrayD = ["cats" => "felines", "dog" => "angry"];
$res = Hash::merge($array, $arrayB, $arrayC, $arrayD);
/* $res now looks like:
[
[0] => [
[id] => 48c2570e-dfa8-4c32-a35e-0d71cbdd56cb
[name] => mysql raleigh-workshop-08 < 2008-09-05.sql
[description] => Importing an sql dump
]
[1] => [
[id] => 48c257a8-cf7c-4af2-ac2f-114ecbdd56cb
758
759
$data = ['1' => '1.1', '2', '3' => ['3.1' => '3.1.1']];
$result = Hash::maxDimensions($data);
// $result == 2
$data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => ['3.1.1' => '3.
1.1.1']]];
$result = Hash::maxDimensions($data);
// $result == 3
760
$dir can be either asc or desc. $type can be one of the following values:
regular for regular sorting.
numeric for sorting values as their numeric equivalents.
string for sorting values as their string value.
natural for sorting values in a human friendly way. Will sort foo10 below foo2 as an
example.
static Cake\Utility\Hash::diff(array $data, array $compare)
Computes the difference between two arrays:
$a = [
0 =>
1 =>
];
$b = [
0 =>
1 =>
2 =>
];
761
Example 2
$array1 = ["a" => "b", 1 => 20938, "c" => "string"];
$array2 = ["b" => "b", 3 => 238, "c" => "string", ["extra_field"]];
$res = Hash::mergeDiff($array1, $array2);
/* $res now looks like:
[
[a] => b
[1] => 20938
[c] => string
[b] => b
[3] => 238
[4] => [
[0] => extra_field
]
]
/
*
762
parentPath The path to a key that identifies the parent of each entry. Should be compatible
with Hash::extract(). Defaults to {n}.$alias.parent_id
root The id of the desired top-most result.
For example, if you had the following array of data:
$data = [
['ThreadPost'
['ThreadPost'
['ThreadPost'
['ThreadPost'
['ThreadPost'
['ThreadPost'
=>
=>
=>
=>
=>
=>
['id'
['id'
['id'
['id'
['id'
['id'
=>
=>
=>
=>
=>
=>
1,
2,
3,
4,
5,
6,
'parent_id'
'parent_id'
'parent_id'
'parent_id'
'parent_id'
'parent_id'
=>
=>
=>
=>
=>
=>
null]],
1]],
1]],
1]],
1]],
null]],
763
['ThreadPost'
['ThreadPost'
['ThreadPost'
['ThreadPost'
=>
=>
=>
=>
['id'
['id'
['id'
['id'
=>
=>
=>
=>
];
$result = Hash::nest($data, ['root' => 6]);
/* $result now looks like:
[
(int) 0 => [
'ThreadPost' => [
'id' => (int) 6,
'parent_id' => null
],
'children' => [
(int) 0 => [
'ThreadPost' => [
'id' => (int) 7,
'parent_id' => (int)
],
'children' => []
],
(int) 1 => [
'ThreadPost' => [
'id' => (int) 8,
'parent_id' => (int)
],
'children' => []
],
(int) 2 => [
'ThreadPost' => [
'id' => (int) 9,
'parent_id' => (int)
],
'children' => []
],
(int) 3 => [
'ThreadPost' => [
'id' => (int) 10,
'parent_id' => (int)
],
'children' => []
]
]
]
]
*/
764
CHAPTER 34
Http Client
Doing Requests
Doing requests is simple and straight forward. Doing a GET request looks like:
use Cake\Network\Http\Client;
$http = new Client();
// Simple get
$response = $http->get('http://example.com/test.html');
// Simple get with querystring
$response = $http->get('http://example.com/search', ['q' => 'widget']);
// Simple get with querystring & additional headers
$response = $http->get('http://example.com/search', ['q' => 'widget'], [
'headers' => ['X-Requested-With' => 'XMLHttpRequest']
]);
765
The filehandle will be read until its end; it will not be rewound before being read.
Warning: For compatibility reasons, strings beginning with @ will be evaluated as local or remote file
paths.
This functionality is deprecated as of CakePHP 3.0.5 and will be removed in a future version. Until that
happens, user data being passed to the Http Client must be sanitized as follows:
$response = $http->post('http://example.com/api', [
'search' => ltrim($this->request->data('search'), '@'),
]);
If it is necessary to preserve leading @ characters in query strings, you can pass a pre-encoded query string
from http_build_query():
$response = $http->post('http://example.com/api', http_build_query([
'search' => $this->request->data('search'),
]));
766
use Cake\Network\Http\FormData;
$data = new FormData();
// Create an XML part
$xml = $data->newPart('xml', $xmlString);
// Set the content type.
$xml->type('application/xml');
$data->add($xml);
// Create a file upload with addFile()
// This will append the file to the form data as well.
$file = $data->addFile('upload', fopen('/some/file.txt', 'r'));
$file->contentId('abc123');
$file->disposition('attachment');
// Send the request.
$response = $http->post(
'http://example.com/api',
(string)$data,
['headers' => ['Content-Type' => $data->contentType()]]
);
The type key can either be a one of json, xml or a full mime type. When using the type option, you
should provide the data as a string. If youre doing a GET request that needs both querystring parameters
and a request body you can do the following:
// Send a JSON body in a GET request with query string parameters.
$http = new Client();
$response = $http->get(
'http://example.com/tasks',
['q' => 'test', '_content' => json_encode($data)],
['type' => 'json']
);
767
Authentication
Cake\Http\Client supports a few different authentication systems. Different authentication strategies
can be added by developers. Auth strategies are called before the request is sent, and allow headers to be
added to the request context.
768
By default Cake\Http\Client will use basic authentication if there is no 'type' key in the auth
option.
By setting the type key to digest, you tell the authentication subsystem to use digest authentication.
OAuth 1 Authentication
Many modern web-services require OAuth authentication to access their APIs. The included OAuth authentication assumes that you already have your consumer key and consumer secret:
$http = new Client();
$response = $http->get('http://example.com/profile/1', [], [
'auth' => [
'type' => 'oauth',
'consumerKey' => 'bigkey',
'consumerSecret' => 'secret',
'token' => '...',
'tokenSecret' => '...',
'realm' => 'tickets',
]
]);
OAuth 2 Authentication
Because OAuth2 is often a single header, there is not a specialized authentication adapter. Instead you can
create a client with the access token:
$http = new Client([
'headers' => ['Authorization' => 'Bearer ' . $accessToken]
]);
$response = $http->get('https://example.com/api/profile/1');
Authentication
769
Proxy Authentication
Some proxies require authentication to use them. Generally this authentication is Basic, but it can be implemented by any authentication adapter. By default Http\Client will assume Basic authentication, unless the
type key is set:
$http = new Client();
$response = $http->get('http://example.com/test.php', [], [
'proxy' => [
'username' => 'mark',
'password' => 'testing',
'proxy' => '127.0.0.1:8080',
]
]);
The second proxy parameter must be a string with an IP or a domain without protocol. The username and
password information will be passed through the request headers, while the proxy string will be passed
through stream_context_create()158 .
770
http://php.net/manual/en/function.stream-context-create.php
ssl_verify_depth
ssl_verify_host
Any of these options can be overridden by specifying them when doing requests. host, scheme, proxy, port
are overridden in the request URL:
// Using the scoped client we created earlier.
$response = $http->get('http://foo.com/test.php');
The above will replace the domain, scheme, and port. However, this request will continue using all the other
options defined when the scoped client was created. See Request Method Options for more information on
the options supported.
You can always override the auto-included cookies by setting them in the requests $options parameters:
// Replace a stored cookie with a custom value.
$response = $http->get('/changelogs', [], [
'cookies' => ['sessionid' => '123abc']
]);
Response Objects
class Cake\Http\Client\Response
Response objects have a number of methods for inspecting the response data.
Changed in version 3.3.0: As of 3.3.0 Cake\Http\Client\Response implements the PSR7 ResponseInterface159 .
159
http://www.php-fig.org/psr/psr-7/#3-3-psr-http-message-responseinterface
771
You can also access the stream object for the response and use its methods:
// Get a Psr\Http\Message\StreamInterface containing the response body
$stream = $response->getBody();
// Read a stream 100 bytes at a time.
while (!$stream->eof()) {
echo $stream->read(100);
}
The decoded response data is stored in the response object, so accessing it multiple times has no additional
cost.
772
Response Objects
773
774
CHAPTER 35
Inflector
class Cake\Utility\Inflector
The Inflector class takes a string and can manipulate it to handle word variations such as pluralizations
or camelizing and is normally accessed statically. Example: Inflector::pluralize('example')
returns examples.
You can try out the inflections online at inflector.cakephp.org160 .
Note: pluralize() may not always correctly convert a noun that is already in its plural form.
// Person
echo Inflector::singularize('People');
Note: singularize() may not always correctly convert a noun that is already in its singular form.
160
http://inflector.cakephp.org/
775
It should be noted that underscore will only convert camelCase formatted words. Words that contains spaces
will be lower-cased, but will not contain an underscore.
776
Note: Inflector::slug() has been deprecated since 3.2.7. Use Text::slug() instead.
Inflection Configuration
CakePHPs naming conventions can be really nice - you can name your database table big_boxes, your
model BigBoxes, your controller BigBoxesController, and everything just works together automatically. The way CakePHP knows how to tie things together is by inflecting the words between their singular
and plural forms.
There are occasions (especially for our non-English speaking friends) where you may run into situations
where CakePHPs inflector (the class that pluralizes, singularizes, camelCases, and under_scores) might not
work as youd like. If CakePHP wont recognize your Foci or Fish, you can tell CakePHP about your special
cases.
777
The supplied rules will be merged into the respective inflection sets defined in
Cake/Utility/Inflector, with the added rules taking precedence over the core rules. You
can use Inflector::reset() to clear rules and restore the original Inflector state.
778
CHAPTER 36
Number
class Cake\I18n\Number
If you need NumberHelper functionalities outside of a View, use the Number class:
namespace App\Controller;
use Cake\I18n\Number;
class UsersController extends AppController
{
public function initialize()
{
parent::initialize();
$this->loadComponent('Auth');
}
public function afterLogin()
{
$storageUsed = $this->Auth->user('storage_used');
if ($storageUsed > 5000000) {
// Notify users of quota
$this->Flash->success(__('You are using {0} storage', Number::
toReadableSize($storageUsed)));
}
}
}
All of these functions return the formatted number; they do not automatically echo the output into the view.
779
This method is used to display a number in common currency formats (EUR, GBP, USD). Usage in a view
looks like:
// Called as NumberHelper
echo $this->Number->currency($value, $currency);
// Called as Number
echo Number::currency($value, $currency);
The first parameter, $value, should be a floating point number that represents the amount of money you
are expressing. The second parameter is a string used to choose a predefined currency formatting scheme:
$currency
EUR
GBP
USD
The third parameter is an array of options for further defining the output. The following options are available:
Option
before
after
zero
places
precision
locale
fractionSymbol
fractionPosition
pattern
useIntlCode
Description
Text to display before the rendered number.
Text to display after the rendered number.
The text to use for zero values; can be a string or a number. ie. 0, Free!.
Number of decimal places to use, ie. 2
Maximal number of decimal places to use, ie. 2
The locale name to use for formatting number, ie. fr_FR.
String to use for fraction numbers, ie. cents.
Either before or after to place the fraction symbol.
An ICU number pattern to use for formatting the number ie. #,###.00
Set to true to replace the currency symbol with the international currency code.
If
$currency
value
is
null,
the
default
Cake\I18n\Number::defaultCurrency()
currency
will
be
retrieved
from
780
This method displays a number with the specified amount of precision (decimal places). It will round in
order to maintain the level of precision defined.
// Called as NumberHelper
echo $this->Number->precision(456.91873645, 2);
// Outputs
456.92
// Called as Number
echo Number::precision(456.91873645, 2);
Formatting Percentages
Cake\I18n\Number::toPercentage(mixed $value, int $precision = 2, array $options =[])
Option
multiply
Description
Boolean to indicate whether the value has to be multiplied by 100. Useful for decimal
percentages.
Formatting Percentages
781
// Called as Number
echo Number::toReadableSize(0); // 0 Byte
echo Number::toReadableSize(1024); // 1 KB
echo Number::toReadableSize(1321205.76); // 1.26 MB
echo Number::toReadableSize(5368709120); // 5 GB
Formatting Numbers
Cake\I18n\Number::format(mixed $value, array $options =[])
This method gives you much more control over the formatting of numbers for use in your views (and is used
as the main method by most of the other NumberHelper methods). Using this method might looks like:
// Called as NumberHelper
$this->Number->format($value, $options);
// Called as Number
Number::format($value, $options);
The $value parameter is the number that you are planning on formatting for output. With no $options
supplied, the number 1236.334 would output as 1,236. Note that the default precision is zero decimal places.
The $options parameter is where the real magic for this method resides.
If you pass an integer then this becomes the amount of precision or places for the function.
If you pass an associated array, you can use the following keys:
Option
places
precision
pattern
locale
before
after
Description
Number of decimal places to use, ie. 2
Maximum number of decimal places to use, ie. 2
An ICU number pattern to use for formatting the number ie. #,###.00
The locale name to use for formatting number, ie. fr_FR.
Text to display before the rendered number.
Text to display after the rendered number.
Example:
// Called as NumberHelper
echo $this->Number->format('123456.7890', [
'places' => 2,
'before' => ' ',
'after' => ' !'
]);
// Output ' 123,456.79 !'
echo $this->Number->format('123456.7890', [
'locale' => 'fr_FR'
]);
// Output '123 456,79 !'
782
// Called as Number
echo Number::format('123456.7890', [
'places' => 2,
'before' => ' ',
'after' => ' !'
]);
// Output ' 123,456.79 !'
echo Number::format('123456.7890', [
'locale' => 'fr_FR'
]);
// Output '123 456,79 !'
Format Differences
Cake\I18n\Number::formatDelta(mixed $value, array $options =[])
This method displays differences in value as a signed number:
// Called as NumberHelper
$this->Number->formatDelta($value, $options);
// Called as Number
Number::formatDelta($value, $options);
The $value parameter is the number that you are planning on formatting for output. With no $options
supplied, the number 1236.334 would output as 1,236. Note that the default precision is zero decimal places.
The $options parameter takes the same keys as Number::format() itself:
Format Differences
783
Option
places
precision
locale
before
after
Description
Number of decimal places to use, ie. 2
Maximum number of decimal places to use, ie. 2
The locale name to use for formatting number, ie. fr_FR.
Text to display before the rendered number.
Text to display after the rendered number.
Example:
// Called as NumberHelper
echo $this->Number->formatDelta('123456.7890', [
'places' => 2,
'before' => '[',
'after' => ']'
]);
// Output '[+123,456.79]'
// Called as Number
echo Number::formatDelta('123456.7890', [
'places' => 2,
'before' => '[',
'after' => ']'
]);
// Output '[+123,456.79]'
Configure formatters
Cake\I18n\Number::config(string $locale, int $type = NumberFormatter::DECIMAL, array
$options =[])
This method allows you to configure formatter defaults which persist across calls to various methods.
Example:
Number::config('en_IN', \NumberFormatter::CURRENCY, [
'pattern' => '#,##,##0'
]);
784
CHAPTER 37
Registry Objects
The registry classes provide a simple way to create and retrieve loaded instances of a given object type.
There are registry classes for Components, Helpers, Tasks, and Behaviors.
While the examples below will use Components, the same behavior can be expected for Helpers, Behaviors,
and Tasks in addition to Components.
Loading Objects
Objects can be loaded on-the-fly using add<registry-object>() Example:
$this->loadComponent('Acl.Acl');
$this->addHelper('Flash')
This will result in the Acl property and Flash helper being loaded. Configuration can also be set on-thefly. Example:
$this->loadComponent('Cookie', ['name' => 'sweet']);
Any keys and values provided will be passed to the Components constructor. The one exception to this
rule is className. Classname is a special key that is used to alias objects in a registry. This allows you
to have component names that do not reflect the classnames, which can be helpful when extending core
components:
$this->Auth = $this->loadComponent('Auth', ['className' => 'MyCustomAuth']);
$this->Auth->user(); // Actually using MyCustomAuth::user();
Triggering Callbacks
Callbacks are not provided by registry objects.
events/callbacks for your application.
785
Disabling Callbacks
In previous versions, collection objects provided a disable() method to disable objects from receiving
callbacks. You should use the features in the events system to accomplish this now. For example, you could
disable component callbacks in the following way:
// Remove Auth from callbacks.
$this->eventManager()->off($this->Auth);
// Re-enable Auth for callbacks.
$this->eventManager()->on($this->Auth);
786
CHAPTER 38
Text
class Cake\Utility\Text
The Text class includes convenience methods for creating and manipulating strings and is normally accessed
statically. Example: Text::uuid().
If you need Cake\View\Helper\TextHelper functionalities outside of a View, use the Text class:
namespace App\Controller;
use Cake\Utility\Text;
class UsersController extends AppController
{
public function initialize()
{
parent::initialize();
$this->loadComponent('Auth')
};
public function afterLogin()
{
$message = $this->Users->find('new_message');
if (!empty($message)) {
// Notify user of new message
$this->Flash->success(__(
'You have a new message: {0}',
Text::truncate($message['Message']['body'], 255, ['html' =>
true])
));
}
}
}
787
default
`null
If false no transliteration will
preserve Specific non-word character to preserve. Defaults to `null. For e.g. this option can
be set to . to generate clean file names:
// apple-puree
Text::slug('apple pure');
// apple_puree
Text::slug('apple pure', '_');
// foo-bar.tar.gz
Text::slug('foo bar.tar.gz', ['preserve' => '.']);
161
788
http://userguide.icu-project.org/transforms/general#TOC-Transliterator-Identifiers
Generating UUIDs
static Cake\Utility\Text::uuid
The UUID method is used to generate unique identifiers as per RFC 4122162 . The UUID is a 128-bit string
in the format of 485fc381-e790-47a3-9794-1337c0a8fe68.
Text::uuid(); // 485fc381-e790-47a3-9794-1337c0a8fe68
Formatting Strings
static Cake\Utility\Text::insert($string, $data, $options =[])
The insert method is used to create string templates and to allow for key/value replacements:
Text::insert(
'My name is :name and I am :age years old.',
['name' => 'Bob', 'age' => '65']
);
// Returns: "My name is Bob and I am 65 years old."
https://tools.ietf.org/html/rfc4122.html
Generating UUIDs
789
Wrapping Text
static Cake\Utility\Text::wrap($text, $options =[])
Wraps a block of text to a set width and indents blocks as well. Can intelligently wrap text so words are not
sliced across lines:
$text = 'This is the song that never ends.';
$result = Text::wrap($text, 22);
// Returns
This is the song that
never ends.
You can provide an array of options that control how wrapping is done. The supported options are:
width The width to wrap to. Defaults to 72.
wordWrap Whether or not to wrap whole words. Defaults to true.
indent The character to indent lines with. Defaults to .
indentAt The line number to start indenting text. Defaults to 0.
static Cake\Utility\Text::wrapBlock($text, $options =[])
If you need to ensure that the total width of the generated block wont exceed a certain length even with
internal identation, you need to use wrapBlock() instead of wrap(). This is particulary useful to
generate text for the console for example. It accepts the same options than wrap():
$text = 'This is the song that never ends. This is the song that never ends.';
$result = Text::wrapBlock($text, [
'width' => 22,
'indent' => ' ',
'indentAt' => 1
]);
// Returns
This is the song that
never ends. This
is the song that
never ends.
790
Highlighting Substrings
Cake\Utility\Text::highlight(string $haystack, string $needle, array $options =[])
Highlights $needle in $haystack using the $options['format'] string specified or a default
string.
Options:
format string - The piece of HTML with the phrase that will be highlighted
html bool - If true, will ignore any HTML tags, ensuring that only the correct text is highlighted
Example:
// Called as TextHelper
echo $this->Text->highlight(
$lastSentence,
'using',
['format' => '<span class="highlight">\1</span>']
);
// Called as Text
use Cake\Utility\Text;
echo Text::highlight(
$lastSentence,
'using',
['format' => '<span class="highlight">\1</span>']
);
Output:
Highlights $needle in $haystack <span class="highlight">using</span> the
$options['format'] string specified or a default string.
Removing Links
Cake\Utility\Text::stripLinks($text)
Strips the supplied $text of any HTML links.
Truncating Text
Cake\Utility\Text::truncate(string $text, int $length = 100, array $options)
If $text is longer than $length, this method truncates it at $length and adds a prefix consisting of
'ellipsis', if defined. If 'exact' is passed as false, the truncation will occur at the first whitespace
after the point at which $length is exceeded. If 'html' is passed as true, HTML tags will be respected
and will not be cut off.
Highlighting Substrings
791
$options is used to pass all extra parameters, and has the following possible keys by default, all of which
are optional:
[
'ellipsis' => '...',
'exact' => true,
'html' => false
]
Example:
// Called as TextHelper
echo $this->Text->truncate(
'The killer crept forward and tripped on the rug.',
22,
[
'ellipsis' => '...',
'exact' => false
]
);
// Called as Text
use Cake\Utility\Text;
echo Text::truncate(
'The killer crept forward and tripped on the rug.',
22,
[
'ellipsis' => '...',
'exact' => false
]
);
Output:
The killer crept...
792
Example:
$sampleText = 'I packed my bag and in it I put a PSP, a PS3, a TV, ' .
'a C# program that can divide by zero, death metal t-shirts'
// Called as TextHelper
echo $this->Text->tail(
$sampleText,
70,
[
'ellipsis' => '...',
'exact' => false
]
);
// Called as Text
use Cake\Utility\Text;
echo Text::tail(
$sampleText,
70,
[
'ellipsis' => '...',
'exact' => false
]
);
Output:
...a TV, a C# program that can divide by zero, death metal t-shirts
Extracting an Excerpt
Cake\Utility\Text::excerpt(string $haystack, string $needle, integer $radius=100, string
$ellipsis=...)
Extracts an excerpt from $haystack surrounding the $needle with a number of characters on each side
determined by $radius, and prefix/suffix with $ellipsis. This method is especially handy for search
results. The query string or keywords can be shown within the resulting document.
// Called as TextHelper
echo $this->Text->excerpt($lastParagraph, 'method', 50, '...');
// Called as Text
use Cake\Utility\Text;
echo Text::excerpt($lastParagraph, 'method', 50, '...');
Extracting an Excerpt
793
Output:
... by $radius, and prefix/suffix with $ellipsis. This method is especially
handy for search results. The query...
Output:
red, orange, yellow, green, blue, indigo and violet
794
CHAPTER 39
class Cake\I18n\Time
If you need TimeHelper functionalities outside of a View, use the Time class:
use Cake\I18n\Time;
class UsersController extends AppController
{
public function initialize()
{
parent::initialize();
$this->loadComponent('Auth');
}
public function afterLogin()
{
$time = new Time($this->Auth->user('date_of_birth'));
if ($time->isToday()) {
// Greet user with a happy birthday message
$this->Flash->success(__('Happy birthday to you...'));
}
}
}
Under the hood, CakePHP uses Chronos163 to power its Time utility. Anything you can do with Chronos
and DateTime, you can do with Time and Date.
Note: Prior to 3.2.0 CakePHP used Carbon164 .
For more details on Chronos please see the API documentation165 .
163
164
165
https://github.com/cakephp/chronos
https://github.com/briannesbitt/Carbon
http://api.cakephp.org/chronos/1.0/
795
The Time class constructor can take any parameter that the internal DateTime PHP class can. When
passing a number or numeric string, it will be interpreted as a UNIX timestamp.
In test cases you can mock out now() using setTestNow():
// Fixate time.
$now = new Time('2014-04-12 12:22:30');
Time::setTestNow($now);
// Returns '2014-04-12 12:22:30'
$now = Time::now();
// Returns '2014-04-12 12:22:30'
$now = Time::parse('now');
Manipulation
Once created, you can manipulate Time instances using setter methods:
$now = Time::now();
$now->year(2013)
->month(10)
->day(31);
You can also use the methods provided by PHPs built-in DateTime class:
796
You can get the internal components of a date by accessing its properties:
$now
echo
echo
echo
echo
= Time::now();
$now->year; // 2014
$now->month; // 5
$now->day; // 10
$now->timezone; // America/New_York
Formatting
static Cake\I18n\Time::setJsonEncodeFormat($format)
This method sets the default format used when converting an object to json:
Time::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss'); // For any mutable DateTime
FrozenTime::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss'); // For any immutable
DateTime
Date::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss'); // For any mutable Date
FrozenDate::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss'); // For any immutable
Date
Formatting
797
It is possible to specify the desired format for the string to be displayed. You can either pass IntlDateFormatter constants166 as the first argument of this function, or pass a full ICU date formatting string as specified
in the following resource: http://www.icu-project.org/apiref/icu4c/classSimpleDateFormat.html#details.
You can also format dates with non-gregorian calendars:
// Outputs 'Friday, Aban 9, 1393 AP at 12:00:00 AM GMT'
$result = $now->i18nFormat(\IntlDateFormatter::FULL, null, 'enIR@calendar=persian');
798
http://www.php.net/manual/en/class.intldateformatter.php
$now = Time::parse('2014-10-31');
// Outputs 'Oct 31, 2014 12:00 AM' in en-US
echo $now->nice();
You can alter the timezone in which the date is displayed without altering the Time object itself. This is
useful when you store dates in one timezone, but want to display them in a users own timezone:
$now->i18nFormat(\IntlDateFormatter::FULL, 'Europe/Paris');
Leaving the first parameter as null will use the default formatting string:
$now->i18nFormat(null, 'Europe/Paris');
From now on, datetimes will be displayed in the Spanish preferred format unless a different locale is specified directly in the formatting method.
Likewise, it is possible to alter the default formatting string to be used for i18nFormat:
Time::setToStringFormat(\IntlDateFormatter::SHORT); // For any mutable
DateTime
FrozenTime::setToStringFormat(\IntlDateFormatter::SHORT); // For any
immutable DateTime
Date::setToStringFormat(\IntlDateFormatter::SHORT); // For any mutable Date
FrozenDate::setToStringFormat(\IntlDateFormatter::SHORT); // For any
immutable Date
// The same method exists on Date, FrozenDate and FrozenTime
Time::setToStringFormat([
\IntlDateFormatter::FULL,
\IntlDateFormatter::SHORT
]);
167
http://www.php.net/manual/en/intl.configuration.php#ini.intl.default-locale
Formatting
799
It is recommended to always use the constants instead of directly passing a date format string.
The end option lets you define at which point after which relative times should be formatted using the
format option. The accuracy option lets us control what level of detail should be used for each interval
range:
// If $timestamp is 1 month, 1 week, 5 days and 6 hours ago
echo $timestamp->timeAgoInWords([
'accuracy' => ['month' => 'month'],
'end' => '1 year'
]);
// Outputs '1 month ago'
By setting accuracy to a string, you can specify what is the maximum level of detail you want output:
$time = new Time('+23 hours');
// Outputs 'in about a day'
$result = $time->timeAgoInWords([
'accuracy' => 'day'
]);
Conversion
Cake\I18n\Time::toQuarter()
Once created, you can convert Time instances into timestamps or quarter values:
$time = new Time('2014-06-15');
$time->toQuarter();
$time->toUnixString();
800
$time->isYesterday();
$time->isThisWeek();
$time->isThisMonth();
$time->isThisYear();
Each of the above methods will return true/false based on whether or not the Time instance matches
the present.
Cake\I18n\Time::wasWithinLast($interval)
You can also compare a Time instance within a range in the past:
// Within past 2 days.
echo $time->wasWithinLast(2);
// Within past 2 weeks.
echo $time->wasWithinLast('2 weeks');
Dates
New in version 3.2.
Comparing With the Present
801
The Date class in CakePHP implements the same API and methods as Cake\I18n\Time does. The
main difference between Time and Date is that Date does not track time components, and is always in
UTC. As an example:
use Cake\I18n\Date;
$date = new Date('2015-06-15');
$date->modify('+2 hours');
// Outputs 2015-06-15 00:00:00
echo $date->format('Y-m-d H:i:s');
$date->modify('+36 hours');
// Outputs 2015-06-15 00:00:00
echo $date->format('Y-m-d H:i:s');
If the method call was re-ordered, or if someOtherFunction changed the output could be unexpected.
The mutability of our object creates temporal coupling. If we were to use immutable objects, we could avoid
this issue:
use Cake\I18n\FrozenTime;
$time = new FrozenTime('2015-06-15 08:23:45');
$time = $time->modify('+2 hours');
802
Immutable dates and times are useful in entities as they prevent accidental modifications, and force changes
to be explicit. Using immutable objects helps the ORM to more easily track changes, and ensure that date
and datetime columns are persisted correctly:
// This change will be lost when the article is saved.
$article->updated->modify('+1 hour');
// By replacing the time object the property will be saved.
$article->updated = $article->updated->modify('+1 hour');
803
804
CHAPTER 40
Xml
class Cake\Utility\Xml
The Xml class allows you to transform arrays into SimpleXMLElement or DOMDocument objects, and
back into arrays again.
805
Note: DOMDocument168 and SimpleXML169 implement different APIs. Be sure to use the correct methods on the object you request from Xml.
Your array must have only one element in the top level and it can not be numeric. If the array is not in this
format, Xml will throw an exception. Examples of invalid arrays:
// Top level with numeric key
[
['key' => 'value']
];
168
169
806
http://php.net/domdocument
http://php.net/simplexml
By default array values will be output as XML tags. If you want to define attributes or text values you can
prefix the keys that are supposed to be attributes with @. For value text, use @ as the key:
$xmlArray = [
'project' => [
'@id' => 1,
'name' => 'Name of project, as tag',
'@' => 'Value of project'
]
];
$xmlObject = Xml::fromArray($xmlArray);
$xmlString = $xmlObject->asXML();
Using Namespaces
To use XML Namespaces, create a key in your array with the name xmlns: in a generic namespace or
input the prefix xmlns: in a custom namespace. See the samples:
$xmlArray = [
'root' => [
'xmlns:' => 'http://cakephp.org',
'child' => 'value'
]
];
$xml1 = Xml::fromArray($xmlArray);
$xmlArray(
'root' => [
'tag' => [
'xmlns:pref' => 'http://cakephp.org',
'pref:item' => [
'item 1',
'item 2'
]
]
]
);
$xml2 = Xml::fromArray($xmlArray);
807
<?xml version="1.0"?>
<root><tag xmlns:pref="http://cakephp.org"><pref:item>item 1</pref:item><pref:
item>item 2</pref:item></tag></root>
Creating a Child
After you have created your XML document, you just use the native interfaces for your document type to
add, remove, or manipulate child nodes:
// Using SimpleXML
$myXmlOriginal = '<?xml version="1.0"?><root><child>value</child></root>';
$xml = Xml::build($myXmlOriginal);
$xml->root->addChild('young', 'new value');
// Using DOMDocument
$myXmlOriginal = '<?xml version="1.0"?><root><child>value</child></root>';
$xml = Xml::build($myXmlOriginal, ['return' => 'domdocument']);
$child = $xml->createElement('young', 'new value');
$xml->firstChild->appendChild($child);
Tip:
After manipulating your XML using SimpleXMLElement or DomDocument you can use
Xml::toArray() without a problem.
808
CHAPTER 41
While most of your day-to-day work in CakePHP will be utilizing core classes and methods, CakePHP
features a number of global convenience functions that may come in handy. Many of these functions are
for use with CakePHP classes (loading model or component classes), but many others make working with
arrays or strings a little easier.
Well also cover some of the constants available in CakePHP applications. Using these constants will help
make upgrades more smooth, but are also convenient ways to point to certain files or directories in your
CakePHP application.
Global Functions
Here are CakePHPs globally available functions. Most of them are just convenience wrappers for other
CakePHP functionality, such as debugging and translating content.
__(string $string_id[, $formatArgs ])
This function handles localization in CakePHP applications. The $string_id identifies the ID for
a translation. Strings used for translations are treated as format strings for sprintf(). You can
supply additional arguments to replace placeholders in your string:
__('You have {0} unread messages', $number);
Note: Check out the Internationalization & Localization section for more information.
__d(string $domain, string $msg, mixed $args = null)
Allows you to override the current domain for a single message lookup.
Useful when internationalizing a plugin:
plugin');
echo __d('PluginName','This is my
__dn(string $domain, string $singular, string $plural, integer $count, mixed $args = null)
Allows you to override the current domain for a single plural message lookup. Returns correct
809
plural form of message identified by $singular and $plural for count $count from domain
$domain.
__dx(string $domain, string $context, string $msg, mixed $args = null)
Allows you to override the current domain for a single message lookup. It also allows you to specify
a context.
The context is a unique identifier for the translations string that makes it unique within the same
domain.
__dxn(string $domain, string $context, string $singular, string $plural, integer $count, mixed $args
= null)
Allows you to override the current domain for a single plural message lookup. It also allows you to
specify a context. Returns correct plural form of message identified by $singular and $plural
for count $count from domain $domain. Some languages have more than one form for plural
messages dependent on the count.
The context is a unique identifier for the translations string that makes it unique within the same
domain.
__n(string $singular, string $plural, integer $count, mixed $args = null)
Returns correct plural form of message identified by $singular and $plural for count $count.
Some languages have more than one form for plural messages dependent on the count.
__x(string $context, string $msg, mixed $args = null)
The context is a unique identifier for the translations string that makes it unique within the same
domain.
__xn(string $context, string $singular, string $plural, integer $count, mixed $args = null)
Returns correct plural form of message identified by $singular and $plural for count $count
from domain $domain. It also allows you to specify a context. Some languages have more than one
form for plural messages dependent on the count.
The context is a unique identifier for the translations string that makes it unique within the same
domain.
collection(mixed $items)
Convenience wrapper for instantiating a new Cake\Collection\Collection object, wrapping
the passed argument. The $items parameter takes either a Traversable object or an array.
debug(mixed $var, boolean $showHtml = null, $showFrom = true)
Changed in version 3.3.0: Calling this method will return passed $var, so that you can, for instance,
place it in return statements.
If the core $debug variable is true, $var is printed out. If $showHTML is true or left as null,
the data is rendered to be browser-friendly. If $showFrom is not set to false, the debug output will
start with the line from which it was called. Also see Debugging
pr(mixed $var)
Changed in version 3.3.0: Calling this method will return passed $var, so that you can, for instance,
place it in return statements.
Convenience wrapper for print_r(), with the addition of wrapping <pre> tags around the output.
810
pj(mixed $var)
Changed in version 3.3.0: Calling this method will return passed $var, so that you can, for instance,
place it in return statements.
JSON pretty print convenience function, with the addition of wrapping <pre> tags around the output.
It is meant for debugging the JSON representation of objects and arrays.
env(string $key, string $default = null)
Changed in version 3.1.1: The $default parameter has been added.
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, its
a good idea to always use env() instead of $_SERVER or getenv() (especially if you plan to
distribute the code), since its a full emulation wrapper.
h(string $text, boolean $double = true, string $charset = null)
Convenience wrapper for htmlspecialchars().
pluginSplit(string $name, boolean $dotAppend = false, string $plugin = null)
Splits a dot syntax plugin name into its plugin and class name. If $name does not have a dot, then
index 0 will be null.
Commonly used like list($plugin,$name) = pluginSplit('Users.User');
namespaceSplit(string $class)
Split the namespace from the classname.
Commonly
used
like
namespaceSplit('Cake\Core\App');
list($namespace,$className) =
811
constant CORE_PATH
Path to the root directory with ending directory slash.
constant DS
Short for PHPs DIRECTORY_SEPARATOR, which is / on Linux and \\ on Windows.
constant LOGS
Path to the logs directory.
constant ROOT
Path to the root directory.
constant TESTS
Path to the tests directory.
constant TMP
Path to the temporary files directory.
constant WWW_ROOT
Full path to the webroot.
812
CHAPTER 42
Debug Kit
DebugKit is a plugin supported by the core team that provides a toolbar to help make debugging CakePHP
applications easier.
Installation
By default DebugKit is installed with the default application skeleton. If youve removed it and want to
re-install it, you can do so by running the following from your applications ROOT directory (where composer.json file is located):
php composer.phar require --dev cakephp/debug_kit "~3.0"
Then, you need to enable the plugin by executing the following line:
bin/cake plugin load DebugKit
DebugKit Storage
By default, DebugKit uses a small SQLite database in your applications /tmp directory to store the panel
data. If youd like DebugKit to store its data elsewhere, you should define a debug_kit connection.
Database Configuration
By default DebugKit will store panel data into a SQLite database in your applications tmp directory. If you
cannot install pdo_sqlite, you can configure DebugKit to use a different database by defining a debug_kit
connection in your config/app.php file. For example:
/**
* The debug_kit connection stores DebugKit meta-data.
*/
813
'debug_kit' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Mysql',
'persistent' => false,
'host' => 'localhost',
//'port' => 'nonstandard_port_number',
'username' => 'dbusername',
// Your DB username here
'password' => 'dbpassword',
// Your DB password here
'database' => 'debug_kit',
'encoding' => 'utf8',
'timezone' => 'UTC',
'cacheMetadata' => true,
'quoteIdentifiers' => false,
//'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
],
Toolbar Usage
The DebugKit Toolbar is comprised of several panels, which are shown by clicking the CakePHP icon in
the bottom right-hand corner of your browser window. Once the toolbar is open, you should see a series of
buttons. Each of these buttons expands into a panel of related information.
Each panel lets you look at a different aspect of your application:
Cache See cache usage during a request and clear caches.
Environment Display environment variables related to PHP + CakePHP.
History Displays a list of previous requests, and allows you to load and view toolbar data from
previous requests.
Include View the included files grouped by type.
Log Display any entries made to the log files this request.
Request Displays information about the current request, GET, POST, Cake Parameters, Current Route
information and Cookies.
Session Display the information currently in the Session.
Sql Logs Displays SQL logs for each database connection.
Timer Display any timers that were set during the request with DebugKit\DebugTimer, and
memory usage collected with DebugKit\DebugMemory.
Variables Display View variables set in controller.
Typically, a panel handles the collection and display of a single type of information such as Logs or Request
information. You can choose to view panels from the toolbar or add your own custom panels.
814
As you can see, the panel contains a list of requests. On the left you can see a dot marking the active request.
Clicking any request data will load the panel data for that request. When historical data is loaded the panel
titles will transition to indicate that alternative data has been loaded.
815
*/
class MyCustomPanel extends DebugPanel
{
...
}
Notice that custom panels are required to extend the DebugPanel class.
Callbacks
By default Panel objects have two callbacks, allowing them to hook into the current request. Panels subscribe to the Controller.initialize and Controller.shutdown events. If your panel needs
to subscribe to additional events, you can use the implementedEvents() method to define all of the
events your panel is interested in.
You should refer to the built-in panels for some examples on how you can build panels.
Panel Elements
Each Panel is expected to have a view element that renders the content from the panel. The element name
must be the underscored inflection of the class name. For example SessionPanel has an element named
session_panel.ctp, and SqllogPanel has an element named sqllog_panel.ctp. These elements should be
located in the root of your src/Template/Element directory.
816
To use a plugin or app panel, update your applications DebugKit configuration to include the panel:
// in config/bootstrap.php
Configure::write('DebugKit.panels', ['App', 'MyPlugin.MyCustom']);
Plugin::load('DebugKit', ['bootstrap' => true]);
The above would load all the default panels as well as the AppPanel, and MyCustomPanel panel from
MyPlugin.
817
818
CHAPTER 43
Migrations
Migrations is a plugin supported by the core team that helps you do schema changes in your database by
writing PHP files that can be tracked using your version control system.
It allows you to evolve your database tables over time. Instead of writing schema modifications in SQL, this
plugin allows you to use an intuitive set of methods to implement your database changes.
This plugin is a wrapper for the database migrations library Phinx170 .
Installation
By default Migrations is installed with the default application skeleton. If youve removed it and want
to re-install it, you can do so by running the following from your applications ROOT directory (where
composer.json file is located):
$ php composer.phar require cakephp/migrations "@stable"
// Or if composer is installed globally
$ composer require cakephp/migrations "@stable"
To use the plugin youll need to load it in your applications config/bootstrap.php file. You can use
CakePHPs Plugin shell to load and unload plugins from your config/bootstrap.php:
$ bin/cake plugin load Migrations
Or you can load the plugin by editing your config/bootstrap.php file and adding the following statement:
Plugin::load('Migrations');
Additionally, you will need to configure the default database configuration for your application in your
config/app.php file as explained in the Database Configuration section.
170
https://phinx.org/
819
Overview
A migration is basically a single PHP file that describes the changes to operate to the database. A migration
file can create or drop tables, add or remove columns, create indexes and even insert data into your database.
Heres an example of a migration:
<?php
use Migrations\AbstractMigration;
class CreateProducts extends AbstractMigration
{
/**
* Change Method.
*
* More information on this method is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-change-method
* @return void
*/
public function change()
{
$table = $this->table('products');
$table->addColumn('name', 'string', [
'default' => null,
'limit' => 255,
'null' => false,
]);
$table->addColumn('description', 'text', [
'default' => null,
'null' => false,
]);
$table->addColumn('created', 'datetime', [
'default' => null,
'null' => false,
]);
$table->addColumn('modified', 'datetime', [
'default' => null,
'null' => false,
]);
$table->create();
}
}
This migration will add a table to your database named products with the following column definitions:
id column of type integer as primary key
name column of type string
description column of type text
created column of type datetime
modified column of type datetime
820
Note: Note that this file describes how the database will look after applying the migration. At this point
no products table exists in your database, we have merely created a file that is able to both create the
products table with the specified columns as well as drop it when a rollback operation of the migration
is performed.
Once the file has been created in the config/Migrations folder, you will be able to execute the following
migrations command to create the table in your database:
bin/cake migrations migrate
The following migrations command will perform a rollback and drop the table from your database:
bin/cake migrations rollback
Creating Migrations
Migration files are stored in the config/Migrations directory of your application. The name of the migration files are prefixed with the date in which they were created, in the format YYYYMMDDHHMMSS_MigrationName.php. Here are examples of migration filenames:
20160121163850_CreateProducts.php
20160210133047_AddRatingToProducts.php
The easiest way to create a migrations file is by using the Code Generation with Bake CLI command.
Please make sure you read the official Phinx documentation171 in order to know the complete list of methods
you can use for writing migration files.
Note: When using the bake option, you can still modify the migration before running them if so desired.
Syntax
The bake command syntax follows the form below:
$ bin/cake bake migration CreateProducts name:string description:text created
modified
When using bake to create tables, add columns and so on, to your database, you will usually provide two
things:
171
http://docs.phinx.org/en/latest/migrations.html
Creating Migrations
821
the name of the migration you will generate (CreateProducts in our example)
the columns of the table that will be added or removed in the migration (name:string
description:text created modified in our example)
Due to the conventions, not all schema changes can be performed via these shell commands.
Additionally you can create an empty migrations file if you want full control over what needs to be executed,
by omitting to specify a columns definition:
$ bin/cake migrations create MyCustomMigration
Columns definition
When using columns in the command line, it may be handy to remember that they follow the following
pattern:
fieldName:fieldType?[length]:indexType:indexName
For instance, the following are all valid ways of specifying an email field:
email:string?
email:string:unique
172
822
https://github.com/cakephp/migrations/
email:string:unique:EMAIL_INDEX
email:string[120]:unique:EMAIL_INDEX
The question mark following the fieldType will make the column nullable.
The length parameter for the fieldType is optional and should always be written between bracket.
Fields named created and modified will automatically be set to the type datetime.
Field types a those generically made available by the Phinx library. Those can be:
string
text
integer
biginteger
float
decimal
datetime
timestamp
time
date
binary
boolean
uuid
There are some heuristics to choosing fieldtypes when left unspecified or set to an invalid value. Default
field type is string:
id: integer
created, modified, updated: datetime
Creating a table
You can use bake to create a table:
$ bin/cake bake migration CreateProducts name:string description:text created
modified
The command line above will generate a migration file that resembles:
<?php
use Migrations\AbstractMigration;
class CreateProducts extends AbstractMigration
Creating Migrations
823
{
/**
* Change Method.
*
* More information on this method is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-change-method
* @return void
*/
public function change()
{
$table = $this->table('products');
$table->addColumn('name', 'string', [
'default' => null,
'limit' => 255,
'null' => false,
]);
$table->addColumn('description', 'text', [
'default' => null,
'null' => false,
]);
$table->addColumn('created', 'datetime', [
'default' => null,
'null' => false,
]);
$table->addColumn('modified', 'datetime', [
'default' => null,
'null' => false,
]);
$table->create();
}
}
824
}
}
will generate:
<?php
use Migrations\AbstractMigration;
class AddNameIndexToProducts extends AbstractMigration
{
public function change()
{
$table = $this->table('products');
$table->addColumn('name', 'string')
->addIndex(['name'])
->update();
}
}
Creating Migrations
825
}
}
It will generate a migration file called YYYYMMDDHHMMSS_Initial.php containing all the create statements for all tables in your database.
By default, the snapshot will be created by connecting to the database defined in the default connection
configuration. If you need to bake a snapshot from a different datasource, you can use the --connection
option:
$ bin/cake bake migration_snapshot Initial --connection my_other_connection
826
You can also make sure the snapshot includes only the tables for which you have defined the corresponding
model classes by using the --require-table flag:
$ bin/cake bake migration_snapshot Initial --require-table
When using the --require-table flag, the shell will look through your application Table classes and
will only add the model tables in the snapshot .
The same logic will be applied implicitly if you wish to bake a snapshot for a plugin. To do so, you need to
use the --plugin option:
$ bin/cake bake migration_snapshot Initial --plugin MyPlugin
Only the tables which have a Table object model class defined will be added to the snapshot of your plugin.
Note: When baking a snapshot for a plugin, the migration files will be created in your plugins config/Migrations directory.
Be aware that when you bake a snapshot, it is automatically added to the phinx log table as migrated.
In order to have a point of comparison from your current database state, the migrations shell will generate
a dump file after each migrate or rollback call. The dump file is a file containing the full schema
state of your database at a given point in time.
Once a dump file is generated, every modifications you do directly in your database management system
will be added to the migration file generated when you call the bake migration_diff command.
By default, the diff will be created by connecting to the database defined in the default connection
configuration. If you need to bake a diff from a different datasource, you can use the --connection
option:
$ bin/cake bake migration_diff NameOfTheMigrations --connection my_other_
connection
If you want to use the diff feature on an application that already has a migrations history, you need to
manually create the dump file that will be used as comparison:
$ bin/cake migrations dump
827
The database state must be the same as it would be if you just migrated all your migrations before you create
a dump file. Once the dump file is generated, you can start doing changes in your database and use the bake
migration_diff command whenever you see fit.
Note: The migrations shell can not detect column renamings.
The commands
migrate : Applying Migrations
Once you have generated or written your migration file, you need to execute the following command to
apply the changes to your database:
# Run all the migrations
$ bin/cake migrations migrate
#
#
#
$
#
#
#
#
#
$
# You can run migrations to a different connection than the ``default`` one
# using the ``--connection`` option or ``-c`` for short
$ bin/cake migrations migrate -c my_custom_connection
# Migrations can also be run for plugins. Simply use the ``--plugin`` option
# or ``-p`` for short
$ bin/cake migrations migrate -p MyAwesomePlugin
828
# to a specific version::
$ bin/cake migrations rollback -t 20150103081132
You can also use the --source, --connection and --plugin options just like for the migrate
command.
You can also output the results as a JSON formatted string using the --format option (or -f for short):
$ bin/cake migrations status --format json
You can also use the --source, --connection and --plugin options just like for the migrate
command.
You can also mark all migrations up to a specific version as migrated using the --target option:
$ bin/cake migrations mark_migrated --target=20151016204000
If you do not want the targeted migration to be marked as migrated during the process, you can use the
--exclude flag with it:
$ bin/cake migrations mark_migrated --target=20151016204000 --exclude
Finally, if you wish to mark only the targeted migration as migrated, you can use the --only flag:
$ bin/cake migrations mark_migrated --target=20151016204000 --only
You can also use the --source, --connection and --plugin options just like for the migrate
command.
The commands
829
Note: When you bake a snapshot with the cake bake migration_snapshot command, the created
migration will automatically be marked as migrated.
Deprecated since version 1.4.0: The following way of using the command has been deprecated. Use it only
if you are using a version of the plugin < 1.4.0.
This command expects the migration version number as argument:
$ bin/cake migrations mark_migrated 20150420082532
If you wish to mark all migrations as migrated, you can use the all special value. If you use it, it will mark
all found migrations as migrated:
$ bin/cake migrations mark_migrated all
830
http://docs.phinx.org/en/latest/seeding.html
http://docs.phinx.org/en/latest/seeding.html#creating-a-new-seed-class
Be aware that, as opposed to migrations, seeders are not tracked, which means that the same seeder can be
applied multiple times.
Calling a Seeder from another Seeder
New in version cakephp/migrations: 1.6.2
Usually when seeding, the order in which to insert the data must be respected to not encounter constraints violations. Since Seeders are executed in the alphabetical order by default, you can use the
\Migrations\AbstractSeed::call() method to define your own sequence of seeders execution:
use Migrations\AbstractSeed;
class DatabaseSeed extends AbstractSeed
{
public function run()
{
$this->call('AnotherSeed');
$this->call('YetAnotherSeed');
// You can use the plugin dot syntax to call seeders from a plugin
$this->call('PluginName.FromPluginSeed');
}
}
Note: Make sure to extend the Migrations plugin AbstractSeed class if you want to be able to use the
call() method. This class was added with release 1.6.2.
Each generated dump file is specific to the Connection it is generated from (and is suffixed as such). This allows the bake migration_diff command to properly compute diff in case your application is dealing
with multiple database possibly from different database vendors.
The commands
831
Dump files are created in the same directory as your migrations files.
You can also use the --source, --connection and --plugin options just like for the migrate
command.
832
$rollback = $migrations->rollback();
// Will return true if success. If an error occurred, an exception will be
thrown
$markMigrated = $migrations->markMigrated(20150804222900);
// Will return true if success. If an error occurred, an exception will be
thrown
$seeded = $migrations->seed();
The methods can accept an array of parameters that should match options from the commands:
use Migrations\Migrations;
$migrations = new Migrations();
// Will return an array of all migrations and their status
$status = $migrations->status(['connection' => 'custom', 'source' =>
'MyMigrationsFolder']);
You can pass any options the shell commands would take. The only exception is the markMigrated
command which is expecting the version number of the migrations to mark as migrated as first argument.
Pass the array of parameters as the second argument for this method.
Optionally, you can pass these parameters in the constructor of the class. They will be used as default and
this will prevent you from having to pass them on each method call:
use Migrations\Migrations;
$migrations = new Migrations(['connection' => 'custom', 'source' =>
'MyMigrationsFolder']);
// All the following calls will be done with the parameters passed to the
Migrations class constructor
$status = $migrations->status();
$migrate = $migrations->migrate();
If you need to override one or more default parameters for one call, you can pass them to the method:
use Migrations\Migrations;
$migrations = new Migrations(['connection' => 'custom', 'source' =>
'MyMigrationsFolder']);
// This call will be made with the "custom" connection
$status = $migrations->status();
// This one with the "default" connection
$migrate = $migrations->migrate(['connection' => 'default']);
833
The above will create a CHAR(36) id column that is also the primary key.
Note: When specifying a custom primary key on the command line, you must note it as the primary key in
the id field, otherwise you may get an error regarding duplicate id fields, i.e.:
$ bin/cake bake migration CreateProducts id:uuid:primary name:string
description:text created modified
Additionally, since Migrations 1.3, a new way to deal with primary key was introduced. To do so, your
migration class should extend the new Migrations\AbstractMigration class. You can specify a
autoId property in the Migration class and set it to false, which will turn off the automatic id column
creation. You will need to manually create the column that will be used as a primary key and add it to the
table declaration:
<?php
use Migrations\AbstractMigration;
class CreateProductsTable extends AbstractMigration
{
public $autoId = false;
public function up()
{
$table = $this->table('products');
834
$table
->addColumn('id', 'integer', [
'autoIncrement' => true,
'limit' => 11
])
->addPrimaryKey('id')
->addColumn('name', 'string')
->addColumn('description', 'text')
->create();
}
}
Compared to the previous way of dealing with primary key, this method gives you the ability to have more
control over the primary key column definition: unsigned or not, limit, comment, etc.
All baked migrations and snapshot will use this new way when necessary.
Warning: Dealing with primary key can only be done on table creation operations. This is due to
limitations for some database servers the plugin supports.
Collations
If you need to create a table with a different collation than the database default one, you can define it with
the table() method, as an option:
<?php
use Migrations\AbstractMigration;
class CreateCategoriesTable extends AbstractMigration
{
public function change()
{
$table = $this
->table('categories', [
'collation' => 'latin1_german1_ci'
])
->addColumn('title', 'string', [
'default' => null,
'limit' => 255,
'null' => false,
])
->create();
}
}
Note however this can only be done on table creation : there is currently no way of adding a column to
an existing table with a different collation than the table or the database. Only MySQL and SqlServer
supports this configuration key for the time being.
835
Be sure to read the ORM Cache Shell section of the cookbook if you want to know more about this shell.
Renaming a table
The plugin gives you the ability to rename a table, using the rename() method. In your migration file,
you can do the following:
public function up()
{
$this->table('old_table_name')
->rename('new_table_name');
}
836
CHAPTER 44
Appendices
Appendices contain information regarding the new features introduced in each version and the migration
path between versions.
deprecated.
Use
routing
scopes
and
837
Cake\Http\Client\Response has had the following methods deprecated because they overlap
with PSR7 interface methods:
statusCode() use getStatusCode() instead.
encoding() use getEncoding() instead.
header() use getHeaderLine() instead.
cookie() use getCookie() instead.
version() use getProtocolVersion() instead.
Dispatcher Filters are now deprecated. Use /controllers/middleware instead.
RequestActionTrait has been deprecated. Refactor your code to use View Cells instead.
Cake\\Utility\\Crypto\\Mcrypt engine has been deprecated as the mcrypt extension is
deprecated in PHP 7.1. Use the openssl and Cake\Utility\Crypto\Openssl instead.
Behavior Changes
While these changes are API compatible, they represent minor variances in behavior that may effect your
application:
The default JSON encode format for Date and DateTime instances is now ISO-8601. This means that
the timezone value contains a :. For example 2015-11-06T00:00:00+03:00
Controller::referer() now consistently omits the application base path when generating
application local URLs. Previously string URLs would have the base path prepended to them, while
array URLs would not.
The default ErrorController no longer disables Auth and Security components, as it does
not extend AppController. If you are enabling these components through events, you will need
to update your code.
Entity::clean now cleans original values, clearing them on save. This behavior was a bug as the
entitys original state should not be retained after a save, but instead reflect the new state of the entity.
PSR7 Middleware Support Added
In tandem with the deprecation of Dispatcher Filters, support for PSR7 middleware has been added. Middleware is part of the new HTTP stack that is an opt-in component of CakePHP 3.3.0. By using the new
HTTP stack, you can take advantage of features like:
Using middleware from plugins, and libraries outside of CakePHP.
Leverage the same response object methods in both the responses you get from Http\Client and
the responses your application generates.
Be able to augment the response objects emitted by error handling and asset delivery.
See the /controllers/middleware chapter and adding-http-stack sections for more information and how to add
the new HTTP stack to an existing application.
838
http://www.php-fig.org/psr/psr-7/
839
FormHelper
FormHelper will now automatically set the default value of fields to the default value defined in your
database columns. You can disable this behavior by setting schemaDefault option to false.
Validation
Validator::requirePresence(),
Validator::allowEmpty()
and
Validator::notEmpty() now accept a list of fields. This allows you to more concisely
define the fields that are required.
StringTemplate
StringTemplate::format() now throws an exception instead of returning null when requested
template is not found.
Other Enhancements
Collection::transpose() was added. This method allows you to tranpose the rows and
columns of a matrix with equal length rows.
The default ErrorController now loads RequestHandlerComponent to enable Accept
header based content-type negotiation for error pages.
Routing
Router::parse(), RouteCollection::parse() and Route::parse() had a
$method argument added. It defaults to GET. This new parameter reduces reliance on global
state, and necessary for the PSR7 work integration to be done.
When building resource routes, you can now define a prefix. This is useful when defining nested
resources as you can create specialized controllers for nested resources.
Dispatcher Filters are now deprecated. Use /controllers/middleware instead.
840
Console
Shell tasks that are invoked directly from the CLI no longer have their _welcome method invoked.
They will also have the requested parameter set now.
Shell::err() will now apply the error style to text. The default styling is red text.
Request
Request::is() and Request::addDetector() now supports additional arguments in detectors. This allows detector callables to operate on additional parameters.
Debugging Functions
The pr(), debug(), and pj() functions now return the value being dumped. This makes them
easier to use when values are being returned.
3.2 Migration Guide
3.2 Migration Guide
CakePHP 3.2 is an API compatible upgrade from 3.1. This page outlines the changes and improvements
made in 3.2.
Minimum PHP 5.5 Required
CakePHP 3.2 requires at least PHP 5.5.9. By adopting PHP 5.5 we can provide better Date and Time
libraries and remove dependencies on password compatibility libraries.
Deprecations
As we continue to improve CakePHP, certain features are deprecated as they are replaced with better solutions. Deprecated features will not be removed until 4.0:
Shell::error() is deprecated because its name does not clearly indicate that it both outputs a
message and stops execution. Use Shell::abort() instead.
Cake\Database\Expression\QueryExpression::type()
tieWith() instead.
is
deprecated.
Use
Use
841
The above error level will suppress deprecation warnings from CakePHP.
New Enhancements
Carbon Replaced with Chronos
The Carbon library has been replaced with cakephp/chronos. This new library is a fork of Carbon with
no additional dependencies. It also offer a calendar date object, and immutable versions of both date and
datetime objects.
New Date Object
The Date class allows you to cleanly map DATE columns into PHP objects. Date instances will always
fix their time to 00:00:00 UTC. By default the ORM creates instances of Date when mapping DATE
columns now.
New Immutable Date and Time Objects
The FrozenTime, and FrozenDate classes were added. These classes offer the same API as the Time
object has. The frozen classes provide immutable variants of Time and Date. By using immutable objects, you can prevent accidental mutations. Instead of in-place modifications, modifier methods return new
instances:
use Cake\I18n\FrozenTime;
$time = new FrozenTime('2016-01-01 12:23:32');
$newTime = $time->modify('+1 day');
842
In the above code $time and $newTime are different objects. The $time object retains its original value,
while $newTime has the modified value. See the Immutable Dates and Times section for more information.
As of 3.2, the ORM can map date/datetime columns into immutable objects. See the Enabling Immutable
DateTime Objects section for more information.
CorsBuilder Added
In order to make setting headers related to Cross Origin Requests (CORS) easier, a new CorsBuilder
has been added. This class lets you define CORS related headers with a fluent interface. See Setting Cross
Origin Request Headers (CORS) for more information.
RedirectRoute raises an exception on redirect
Router::redirect() now raises Cake\Network\Routing\RedirectException when a
redirect condition is reached. This exception is caught by the routing filter and converted into a response.
This replaces calls to response->send() and allows dispatcher filters to interact with redirect responses.
ORM Improvements
Containing the same association multiple times now works as expected, and the query builder functions are now stacked.
Function expressions now correctly cast their results.
This means that expressions like
$query->func()->current_date() will return datetime instances.
Field data that fails validation can now be accessed in entities via the invalid() method.
Entity accessor method lookups are now cached and perform better.
Improved Validator API
The Validator object has a number of new methods that make building validators less verbose. For example
adding validation rules to a username field can now look like:
$validator->email('username')
->ascii('username')
->lengthBetween('username', [4, 8]);
Console Improvements
Shell::info(), Shell::warn() and Shell::success() were added. These helper methods make using commonly used styling simpler.
Cake\Console\Exception\StopException was added.
Shell::abort() was added to replace error().
3.x Migration Guide
843
StopException Added
Shell::_stop() and Shell::error() no longer call exit().
Instead they raise
Cake\Console\Exception\StopException. If your shells/tasks are catching \Exception
where these methods would have been called, those catch blocks will need to be updated so they dont
catch the StopException. By not calling exit() testing shells should be easier and require fewer
mocks.
Helper initialize() added
Helpers can now implement an initialize(array $config) hook method like other class types.
Fatal Error Memory Limit Handling
A new configuration option Error.extraFatalErrorMemory can be set to the number of megabytes
to increase the memory limit by when a fatal error is encountered. This allows breathing room to complete
logging or error handling.
Migration Steps
Updating setToStringFormat()
Before CakePHP 3.2 using Time::setToStringFormat() was working on Date Objects as well. After upgrading you will need to add Date::setToStringFormat() in addition to see the formatted Date again.
3.1 Migration Guide
3.1 Migration Guide
CakePHP 3.1 is a fully API compatible upgrade from 3.0. This page outlines the changes and improvements
made in 3.1.
Routing
The default route class has been changed to DashedRoute in the cakephp/app repo. Your current code base is not affected by this, but it is recommended to use this route class from now on.
Name prefix options were added to the various route builder methods. See the Using Named Routes
section for more information.
844
Console
Shell::dispatchShell() no longer outputs the welcome message from the dispatched shell.
The breakpoint() helper function has been added. This function provides a snippet of code that
can be put into eval() to trigger an interactive console. This is very helpful when debugging in test
cases, or other CLI scripts.
The --verbose and --quiet console options now control stdout/stderr logging output levels.
Shell Helpers Added
Console applications can now create helper classes that encapsulate re-usable blocks of output logic.
See the Shell Helpers section for more information.
RoutesShell
RoutesShell has been added and now provides you a simple to use CLI interface for testing and
debugging routes. See the Routes Shell section for more information.
Controller
The following Controller properties are now deprecated:
layout
view - replaced with template
theme
autoLayout
viewPath - replaced with templatePath
viewClass - replaced with className
layoutPath
Instead of setting these properties on your controllers, you should set them on the view using methods
with matching names:
// In a controller, instead of
$this->layout = 'advanced';
// You should use
$this->viewBuilder()->layout('advanced');
These methods should be called after youve determined which view class will be used by a controller/action.
845
AuthComponent
New config option storage has been added.
It contains the storage class name that
AuthComponent uses to store user record. By default SessionStorage is used. If using a
stateless authenticator you should configure AuthComponent to use MemoryStorage instead.
New config option checkAuthIn has been added. It contains the name of the event for which
auth checks should be done. By default Controller.startup is used, but you can set it
to Controller.initialize if you want authentication to be checked before you controllers
beforeFilter() method is run.
The options scope and contain for authenticator classes have been deprecated. Instead, use the
new finder option to configure a custom finder method and modify the query used to find a user
there.
The logic for setting Auth.redirect session variable, which is used to get the URL to be redirected to after login, has been changed. It is now set only when trying to access a protected URL
without authentication. So Auth::redirectUrl() returns the protected URL after login. Under normal circumstances, when a user directly accesses the login page, Auth::redirectUrl()
returns the value set for loginRedirect config.
FlashComponent
FlashComponent now stacks Flash messages when set with the set() or __call() method.
This means that the structure in the Session for stored Flash messages has changed.
CsrfComponent
CSRF cookie expiry time can now be set as a strtotime() compatible value.
Invalid CSRF tokens will now throw a Cake\Network\Exception\InvalidCsrfTokenException
instead of the Cake\Network\Exception\ForbiddenException.
RequestHandlerComponent
RequestHandlerComponent now switches the layout and template based on the parsed extension or Accept header in the beforeRender() callback instead of startup().
addInputType() and viewClassMap() are deprecated. You should use config() to modify
this configuration data at runtime.
When inputTypeMap or viewClassMap are defined in the component settings, they will overwrite the default values. This change makes it possible to remove the default configuration.
846
Network
HttpClient
The default mime type used when sending requests has changed.
Previously
multipart/form-data would always be used.
In 3.1, multipart/form-data
is only used when file uploads are present.
When there are no file uploads,
application/x-www-form-urlencoded is used instead.
ORM
You can now Lazily Eager Load Associations. This feature allows you to conditionally load additional
associations into a result set, entity or collection of entities.
The patchEntity() and newEntity() method now support the onlyIds option. This option allows
you to restrict hasMany/belongsToMany association marshalling to only use the _ids list. This option
defaults to false.
Query
Query::notMatching() was added.
Query::leftJoinWith() was added.
Query::innerJoinWith() was added.
Query::select() now supports Table and Association objects as parameters. These parameter types will select all the columns on the provided table or association instances target table.
Query::distinct() now accepts a string to distinct on a single column.
Table::loadInto() was added.
EXTRACT, DATE_ADD and DAYOFWEEK raw SQL functions have been abstracted to extract(),
dateAdd() and dayOfWeek().
View
You can now set _serialized to true for JsonView and XmlView to serialize all view variables instead of explicitly specifying them.
View::$viewPath is deprecated. You should use View::templatePath() instead.
View::$view is deprecated. You should use View::template() instead.
View::TYPE_VIEW is deprecated. You should use View::TYPE_TEMPLATE instead.
847
Helper
SessionHelper
The SessionHelper has been deprecated. You can use $this->request->session() directly.
FlashHelper
FlashHelper can render multiple messages if multiple messages where set with the
FlashComponent. Each message will be rendered in its own element. Messages will be rendered
in the order they were set.
FormHelper
New option templateVars has been added. templateVars allows you to pass additional variables to your custom form control templates.
Email
Email and Transport classes have been moved under the Cake\Mailer namespace. Their
former namespaces are still usable as class aliases have been set for them.
The default email profile is now automatically set when an Email instance is created. This
behavior is similar to what is done in 2.x.
Mailer
The Mailer class was added. This class helps create reusable emails in an application.
I18n
Time
Time::fromNow() has been added. This method makes it easier to calculate differences from
now.
Time::i18nFormat() now supports non-gregorian calendars when formatting dates.
848
Validation
Validation::geoCoordinate() was added.
Validation::latitude() was added.
Validation::longitude() was added.
Validation::isInteger() was added.
Validation::ascii() was added.
Validation::utf8() was added.
Testing
TestFixture
model key is now supported to retrieve the table name for importing.
3.0 Migration Guide
New ORM Upgrade Guide
CakePHP 3.0 features a new ORM that has been re-written from the ground up. While the ORM used in 1.x
and 2.x has served us well for a long time it had a few issues that we wanted to fix.
Frankenstein - Is it a record, or a table? Currently its both.
Inconsistent API - Model::read() for example.
No query object - Queries are always defined as arrays, this has some limitations and restrictions. For
example it makes doing unions and sub-queries much harder.
Returns arrays - This is a common complaint about CakePHP, and has probably reduced adoption at
some levels.
No record object - This makes attaching formatting methods difficult/impossible.
Containable - Should be part of the ORM, not a crazy hacky behavior.
Recursive - This should be better controlled as defining which associations are included, not a level
of recursiveness.
DboSource - It is a beast, and Model relies on it more than datasource. That separation could be
cleaner and simpler.
Validation - Should be separate, its a giant crazy function right now. Making it a reusable bit would
make the framework more extensible.
The ORM in CakePHP 3.0 solves these and many more problems. The new ORM focuses on relational data
stores right now. In the future and through plugins we will add non relational stores like ElasticSearch and
others.
3.x Migration Guide
849
850
The documentation chapter on Table Objects provides far more detail on how to use table objects than this
guide can. Generally when moving existing model code over it will end up in a table object. Table objects
dont contain any platform dependent SQL. Instead they collaborate with entities and the query builder to do
their work. Table objects also interact with behaviors and other interested parties through published events.
Query Objects
While these are not classes you will build yourself, your application code will make extensive use of the
Query Builder which is central to the new ORM. The query builder makes it easy to build simple or complex
queries including those that were previously very difficult in CakePHP like HAVING, UNION and subqueries.
The various find() calls your application has currently will need to be updated to use the new query builder.
The Query object is responsible for containing the data to make a query without executing the query itself.
It collaborates with the connection/dialect to generate platform specific SQL which is executed creating a
ResultSet as the output.
Entity Objects
In previous versions of CakePHP the Model class returned dumb arrays that could not contain any logic
or behavior. While the community made this short-coming less painful with projects like CakeEntity, the
array results were often a short coming that caused many developers trouble. For CakePHP 3.0, the ORM
always returns object result sets unless you explicitly disable that feature. The chapter on Entities covers the
various tasks you can accomplish with entities.
Entities are created in one of two ways. Either by loading data from the database, or converting request data
into entities. Once created, entities allow you to manipulate the data they contain and persist their data by
collaborating with table objects.
Key Differences
The new ORM is a large departure from the existing Model layer. There are many important differences
that are important in understanding how the new ORM operates and how to update your code.
Inflection Rules Updated
You may have noticed that table classes have a pluralized name. In addition to tables having pluralized
names, associations are also referred in the plural form. This is in contrast to Model where class names and
association aliases were singular. There are a few reasons for this change:
Table classes represent collections of data, not single rows.
Associations link tables together, describing the relations between many things.
While the conventions for table objects are to always use plural forms, your entity association properties
will be populated based on the association type.
851
Note: BelongsTo and HasOne associations will use the singular form in entity properties, while HasMany
and BelongsToMany (HABTM) will use plural forms.
The convention change for table objects is most apparent when building queries. Instead of expressing
queries like:
// Wrong
$query->where(['User.active' => 1]);
It is possible to stack custom finders to append conditions, sorting, limit and any other clause to the same
query before it is executed:
$query = $articles->find('approved')->find('popular');
$query->find('latest');
You can compose queries one into the other to create subqueries easier than ever:
$query = $articles->find('approved');
$favoritesQuery = $article->find('favorites', ['for' => $user]);
$query->where(['id' => $favoritesQuery->select(['id'])]);
You can decorate queries with iterators and call methods without even touching the database. This is great
when you have parts of your view cached and having the results taken from the database is not actually
required:
// No queries made in this example!
$results = $articles->find()
->order(['title' => 'DESC'])
->formatResults(function ($results) {
return $results->extract('title');
});
852
Queries can be seen as the result object, trying to iterate the query, calling toArray() or any method
inherited from collection, will result in the query being executed and results returned to you.
The biggest difference you will find when coming from CakePHP 2.x is that find('first') does not
exist anymore. There is a trivial replacement for it, and it is the first() method:
// Before
$article = $this->Article->find('first');
// Now
$article = $this->Articles->find()->first();
// Before
$article = $this->Article->find('first', [
'conditions' => ['author_id' => 1]
]);
// Now
$article = $this->Articles->find('all', [
'conditions' => ['author_id' => 1]
])->first();
// Can also be written
$article = $this->Articles->find()
->where(['author_id' => 1])
->first();
If you are loading a single record by its primary key, it will be better to just call get():
$article = $this->Articles->get(10);
853
}
}
As you can see, they are pretty straightforward, they get a Query object instead of an array and must return
a Query object back. For 2.x users that implemented afterFind logic in custom finders, you should check
out the Modifying Results with Map/Reduce section, or use the features found on the collection objects. If
in your models you used to rely on having an afterFind for all find operations you can migrate this code in
one of a few ways:
1. Override your entity constructor method and do additional formatting there.
2. Create accessor methods in your entity to create the virtual properties.
3. Redefine findAll() and use formatResults.
In the 3rd case above your code would look like:
public function findAll(Query $query, array $options)
{
return $query->formatResults(function ($results) {
return $results->map(function ($row) {
// Your afterfind logic
});
})
}
You may have noticed that custom finders receive an options array. You can pass any extra information to
your finder using this parameter. This is great news for people migrating from 2.x. Any of the query keys
that were used in previous versions will be converted automatically for you in 3.x to the correct functions:
// This works in both CakePHP 2.x and 3.0
$articles = $this->Articles->find('all', [
'fields' => ['id', 'title'],
'conditions' => [
'OR' => ['title' => 'Cake', 'author_id' => 1],
'published' => true
],
'contain' => ['Authors'], // The only change! (notice plural)
'order' => ['title' => 'DESC'],
'limit' => 10,
]);
If your application uses magic or Dynamic Finders, you will have to adapt those calls. In 3.x the
findAllBy* methods have been removed, instead findBy* always returns a query object. To get the
first result, you need to use the first() method:
$article = $this->Articles->findByTitle('A great post!')->first();
Hopefully, migrating from older versions is not as daunting as it first seems. Many of the features we have
added will help you remove code as you can better express your requirements using the new ORM and at
the same time the compatibility wrappers will help you rewrite those tiny differences in a fast and painless
way.
854
One of the other nice improvements in 3.x around finder methods is that behaviors can implement finder
methods with no fuss. By simply defining a method with a matching name and signature on a Behavior the
finder will automatically be available on any tables the behavior is attached to.
Recursive and ContainableBehavior Removed
In previous versions of CakePHP you needed to use recursive, bindModel(), unbindModel()
and ContainableBehavior to reduce the loaded data to the set of associations you were interested in.
A common tactic to manage associations was to set recursive to -1 and use Containable to manage all
associations. In CakePHP 3.0 ContainableBehavior, recursive, bindModel, and unbindModel have all been
removed. Instead the contain() method has been promoted to be a core feature of the query builder.
Associations are only loaded if they are explicitly turned on. For example:
$query = $this->Articles->find('all');
Will only load data from the articles table as no associations have been included. To load articles and
their related authors you would do:
$query = $this->Articles->find('all')->contain(['Authors']);
By only loading associated data that has been specifically requested you spend less time fighting the ORM
trying to get only the data you want.
No afterFind Event or Virtual Fields
In previous versions of CakePHP you needed to make extensive use of the afterFind callback and virtual
fields in order to create generated data properties. These features have been removed in 3.0. Because of how
ResultSets iteratively generate entities, the afterFind callback was not possible. Both afterFind and
virtual fields can largely be replaced with virtual properties on entities. For example if your User entity has
both first and last name columns you can add an accessor for full_name and generate the property on the fly:
namespace App\Model\Entity;
use Cake\ORM\Entity;
class User extends Entity
{
protected function _getFullName()
{
return $this->first_name . ' ' . $this->last_name;
}
}
Once defined you can access your new property using $user->full_name. Using the Modifying Results
with Map/Reduce features of the ORM allow you to build aggregated data from your results, which is another
use case that the afterFind callback was often used for.
While virtual fields are no longer an explicit feature of the ORM, adding calculated fields is easy to do in
your finder methods. By using the query builder and expression objects you can achieve the same results
3.x Migration Guide
855
As you can see from the example above each of the association types uses a method to create the association.
One other difference is that hasAndBelongsToMany has been renamed to belongsToMany. To find
out more about creating associations in 3.0 see the section on Associations - Linking Tables Together.
Another welcome improvement to CakePHP is the ability to create your own association classes. If you have
association types that are not covered by the built-in relation types you can create a custom Association
sub-class and define the association logic you need.
856
In previous versions of CakePHP validation and the related callbacks covered a few related but different
uses. In CakePHP 3.0, what was formerly called validation is now split into two concepts:
1. Data type and format validation.
2. Enforcing application, or business rules.
Validation is now applied before ORM entities are created from request data. This step lets you ensure data
matches the data type, format, and basic shape your application expects. You can use your validators when
converting request data into entities by using the validate option. See the documentation on Converting
Request Data into Entities for more information.
Application rules allow you to define rules that ensure your applications rules, state and workflows are
enforced. Rules are defined in your Tables buildRules() method. Behaviors can add rules using the
buildRules() hook method. An example buildRules() method for our articles table could be:
857
// In src/Model/Table/ArticlesTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\ORM\RulesChecker;
class Articles extends Table
{
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->existsIn('user_id', 'Users'));
$rules->add(
function ($article, $options) {
return ($article->published && empty($article->reviewer));
},
'isReviewed',
[
'errorField' => 'published',
'message' => 'Articles must be reviewed before publishing.'
]
);
return $rules;
}
}
Note: Identifiers in QueryExpression objects will not be quoted, and you will need to quote them
manually or use IdentifierExpression objects.
858
Updating Behaviors
Like most ORM related features, behaviors have changed in 3.0 as well. They now attach to Table instances which are the conceptual descendant of the Model class in previous versions of CakePHP. There
are a few key differences from behaviors in CakePHP 2.x:
Behaviors are no longer shared across multiple tables. This means you no longer have to namespace
settings stored in a behavior. Each table using a behavior will get its own instance.
The method signatures for mixin methods have changed.
The method signatures for callback methods have changed.
The base class for behaviors have changed.
Behaviors can add finder methods.
New Base Class
The base class for behaviors has changed. Behaviors should now extend Cake\ORM\Behavior; if a
behavior does not extend this class an exception will be raised. In addition to the base class changing, the
constructor for behaviors has been modified, and the startup() method has been removed. Behaviors
that need access to the table they are attached to should define a constructor:
namespace App\Model\Behavior;
use Cake\ORM\Behavior;
class SluggableBehavior extends Behavior
{
protected $_table;
public function __construct(Table $table, array $config)
{
parent::__construct($table, $config);
$this->_table = $table;
}
}
859
The behavior providing the slug() method will receive only 1 argument, and its method signature should
look like:
public function slug($value)
{
// Code here.
}
See Lifecycle Callbacks for the signatures of all the callbacks a behavior can subscribe to.
General Information
CakePHP Development Process
Here we attempt to explain the process we use when developing the CakePHP framework. We rely heavily
on community interaction through tickets and IRC chat. IRC is the best place to find members of the
development team176 and discuss ideas, the latest code, and make general comments. If something more
formal needs to be proposed or there is a problem with a release, the ticket system is the best place to share
your thoughts.
We currently maintain 4 versions of CakePHP.
tagged release : Tagged releases intended for production where stability is more important than
features. Issues filed against these releases will be fixed in the related branch, and be part of the next
release.
mainline branch : These branches are where all bugfixes are merged into. Stable releases are tagged
from these branches. master is the mainline branch for the current release series. 2.x is the
maintenance branch for the 2.x release series. If you are using a stable release and need fixes that
havent made their way into a tagged release check here.
development branches : Development branches contain leading edge fixes and features. They are
named after the major version they are for. E.g 3.next. Once development branches have reached a
stable release point they are merged into the mainline branch.
feature branches : Feature branches contain unfinished or possibly unstable features and are recommended only for power users interested in the most advanced feature set and willing to contribute
176
860
https://github.com/cakephp?tab=members
back to the community. Feature branches are named with the following convention version-feature.
An example would be 3.3-router Which would contain new features for the Router for 3.3.
Hopefully this will help you understand what version is right for you. Once you pick your version you may
feel compelled to contribute a bug report or make general comments on the code.
If you are using a stable version or maintenance branch, please submit tickets or discuss with us on
IRC.
If you are using the development branch or feature branch, the first place to go is IRC. If you have a
comment and cannot reach us in IRC after a day or two, please submit a ticket.
If you find an issue, the best answer is to write a test. The best advice we can offer in writing tests is to look
at the ones included in the core.
As always, if you have any questions or comments, visit us at #cakephp on irc.freenode.net.
Glossary
routing array An array of attributes that are passed to Router::url(). They typically look like:
['controller' => 'Posts', 'action' => 'view', 5]
HTML attributes An array of key => values that are composed into HTML attributes. For example:
// Given
['class' => 'my-class', 'target' => '_blank']
// Would generate
class="my-class" target="_blank"
If an option can be minimized or accepts its name as the value, then true can be used:
// Given
['checked' => true]
// Would generate
checked="checked"
plugin syntax Plugin syntax refers to the dot separated class name indicating classes are part of a plugin:
// The plugin is "DebugKit", and the class name is "Toolbar".
'DebugKit.Toolbar'
// The plugin is "AcmeCorp/Tools", and the class name is "Toolbar".
'AcmeCorp/Tools.Toolbar'
dot notation Dot notation defines an array path, by separating nested levels with . For example:
Cache.default.engine
General Information
861
[
'Cache' => [
'default' => [
'engine' => 'File'
]
]
]
CSRF Cross Site Request Forgery. Prevents replay attacks, double submissions and forged requests from
other domains.
CDN Content Delivery Network. A 3rd party vendor you can pay to help distribute your content to data
centers around the world. This helps put your static assets closer to geographically distributed users.
routes.php A file in config directory that contains routing configuration. This file is included before
each request is processed. It should connect all the routes your application needs so requests can be
routed to the correct controller + action.
DRY Dont repeat yourself. Is a principle of software development aimed at reducing repetition of information of all kinds. In CakePHP DRY is used to allow you to code things once and re-use them across
your application.
PaaS Platform as a Service. Platform as a Service providers will provide cloud based hosting, database
and caching resources. Some popular providers include Heroku, EngineYard and PagodaBox
DSN Data Source Name. A connection string format that is formed like a URI. CakePHP supports DSNs
for Cache, Database, Log and Email connections.
862
c
Cake\Cache, 533
Cake\Collection, 719
Cake\Console, 543
Cake\Console\Exception, 599
Cake\Controller, 197
Cake\Controller\Component, 235
Cake\Controller\Exception, 599
Cake\Core, 147
Cake\Core\Configure, 151
Cake\Core\Configure\Engine, 152
Cake\Core\Exception, 600
Cake\Database, 364
Cake\Database\Exception, 599
Cake\Database\Schema, 517
Cake\Datasource, 363
Cake\Datasource\Exception, 600
Cake\Error, 576
Cake\Filesystem, 741
Cake\Form, 637
Cake\Http, 765
Cake\Http\Client, 771
Cake\I18n, 795
Cake\Log, 633
Cake\Mailer, 583
Cake\Network, 181
Cake\Network\Exception, 597
Cake\ORM, 406
Cake\ORM\Behavior, 506
Cake\ORM\Exception, 599
Cake\Routing, 155
Cake\Routing\Exception, 600
Cake\Utility, 805
Cake\Validation, 705
Cake\View, 253
Cake\View\Exception, 598
Cake\View\Helper, 347
863
864
Index
Symbols
() ( method), 250, 251
:action, 157
:controller, 157
:plugin, 157
$this->request, 181
$this->response, 188
__() (global function), 809
__d() (global function), 809
__dn() (global function), 809
__dx() (global function), 810
__dxn() (global function), 810
__n() (global function), 810
__x() (global function), 810
__xn() (global function), 810
A
acceptLanguage() (Cake\Network\Request method),
188
accepts() (Cake\Network\Request method), 187
accepts() (RequestHandlerComponent method), 241
addArgument() (Cake\Console\ConsoleOptionParser
method), 557
addArguments() (Cake\Console\ConsoleOptionParser
method), 557
addBehavior() (Cake\ORM\Table method), 412
addCrumb()
(Cake\View\Helper\HtmlHelper
method), 322
addDetector() (Cake\Network\Request method), 184
addOption() (Cake\Console\ConsoleOptionParser
method), 558
addOptions() (Cake\Console\ConsoleOptionParser
method), 559
addPathElement()
(Cake\Filesystem\Folder
method), 742
addSubcommand() (Cake\Console\ConsoleOptionParser
method), 560
admin routing, 163
afterDelete() (Cake\ORM\Table method), 412
afterDeleteCommit() (Cake\ORM\Table method),
412
afterFilter() (Cake\Controller\Controller method),
206
afterLayout() (Helper method), 355
afterRender() (Helper method), 355
afterRenderFile() (Helper method), 355
afterRules() (Cake\ORM\Table method), 411
afterSave() (Cake\ORM\Table method), 411
afterSaveCommit() (Cake\ORM\Table method), 411
alert() (Cake\Log\Log method), 633
allInputs()
(Cake\View\Helper\FormHelper
method), 305
allow() (AuthComponent method), 222
allowedActions (SecurityComponent property), 233
allowedControllers (SecurityComponent property),
233
allowMethod() (Cake\Network\Request method),
186
App (class in Cake\Core), 715
APP (global constant), 811
app.php, 143
app.php.default, 143
APP_DIR (global constant), 811
append() (Cake\Collection\Collection method), 734
append() (Cake\Filesystem\File method), 746
application exceptions, 601
865
866
Index
D
dasherize() (Cake\Utility\Inflector method), 776
data() (Cake\Network\Request method), 182
dateTime()
(Cake\View\Helper\FormHelper
method), 294
DAY (global constant), 812
day() (Cake\View\Helper\FormHelper method), 298
debug() (Cake\Log\Log method), 633
debug() (global function), 810
Debugger (class in Cake\Error), 576
decrement() (Cake\Cache\Cache method), 538
decrement() (Cake\Cache\CacheEngine method),
541
decrypt() (Cake\Utility\Security method), 659
defaultCurrency() (Cake\I18n\Number method), 780
867
Index
G
gc() (Cake\Cache\Cache method), 538
gc() (Cake\Cache\CacheEngine method), 542
generateUrl() (Cake\View\Helper\PaginatorHelper
method), 337
get()
(Cake\Datasource\ConnectionManager
method), 363
get() (Cake\ORM\Table method), 424
get() (Cake\ORM\TableRegistry method), 413
get() (Cake\Utility\Hash method), 750
getCrumbList()
(Cake\View\Helper\HtmlHelper
method), 322
getCrumbs()
(Cake\View\Helper\HtmlHelper
method), 322
getType() (Cake\Error\Debugger method), 577
GoneException, 598
greedy star, 157
group() (Cake\Filesystem\File method), 747
groupBy() (Cake\Collection\Collection method),
727
groupConfigs() (Cake\Cache\Cache method), 540
I
i18nFormat() (Cake\I18n\Time method), 797
identify() (AuthComponent method), 210
image() (Cake\View\Helper\HtmlHelper method),
313
in() (Cake\Console\Shell method), 549
inCakePath() (Cake\Filesystem\Folder method), 744
increment() (Cake\Cache\Cache method), 538
increment() (Cake\Cache\CacheEngine method),
542
indexBy() (Cake\Collection\Collection method), 728
Inflector (class in Cake\Utility), 775
info (Cake\Filesystem\File property), 746
info() (Cake\Filesystem\File method), 747
info() (Cake\Log\Log method), 633
IniConfig (class in Cake\Core\Configure\Engine),
152
initialize() (Cake\Console\Shell method), 553
initialize() (Cake\ORM\Table method), 409
inPath() (Cake\Filesystem\Folder method), 744
input() (Cake\Network\Request method), 183
869
J
JsonConfig (class in Cake\Core\Configure\Engine),
153
JsonView (class), 274
L
label() (Cake\View\Helper\FormHelper method),
299
last() (Cake\Collection\Collection method), 734
last() (Cake\View\Helper\PaginatorHelper method),
333
lastAccess() (Cake\Filesystem\File method), 747
lastChange() (Cake\Filesystem\File method), 747
levels() (Cake\Log\Log method), 633
link() (Cake\View\Helper\HtmlHelper method), 314
listNested() (Cake\Collection\Collection method),
731
load() (Cake\Core\Configure method), 149
870
loadComponent()
(Cake\Controller\Controller
method), 204
loadModel() (Cake\Controller\Controller method),
203
lock (Cake\Filesystem\File property), 746
Log (class in Cake\Log), 633
log() (Cake\Error\Debugger method), 576
log() (Cake\Log\LogTrait method), 634
logout() (AuthComponent method), 220
LOGS (global constant), 812
LogTrait (trait in Cake\Log), 634
M
map() (Cake\Collection\Collection method), 720
map() (Cake\Database\Type method), 365
map() (Cake\Utility\Hash method), 760
match() (Cake\Collection\Collection method), 725
max() (Cake\Collection\Collection method), 726
maxDimensions() (Cake\Utility\Hash method), 759
md5() (Cake\Filesystem\File method), 747
media() (Cake\View\Helper\HtmlHelper method),
316
merge()
(Cake\Console\ConsoleOptionParser
method), 561
merge() (Cake\Utility\Hash method), 758
mergeDiff() (Cake\Utility\Hash method), 761
meridian()
(Cake\View\Helper\FormHelper
method), 299
messages() (Cake\Filesystem\Folder method), 745
meta() (Cake\View\Helper\HtmlHelper method),
311
method() (Cake\Network\Request method), 186
MethodNotAllowedException, 597
mime() (Cake\Filesystem\File method), 748
min() (Cake\Collection\Collection method), 726
MINUTE (global constant), 812
minute() (Cake\View\Helper\FormHelper method),
298
MissingActionException, 599
MissingBehaviorException, 599
MissingCellException, 599
MissingCellViewException, 599
MissingComponentException, 599
MissingConnectionException, 599
MissingControllerException, 600
MissingDispatcherFilterException, 600
MissingDriverException, 599
MissingElementException, 599
Index
MissingEntityException, 599
MissingExtensionException, 599
MissingHelperException, 599
MissingLayoutException, 599
MissingRouteException, 600
MissingShellException, 599
MissingShellMethodException, 599
MissingTableException, 599
MissingTaskException, 599
MissingTemplateException, 599
MissingViewException, 598
mode (Cake\Filesystem\Folder property), 742
modified() (Cake\Network\Response method), 193
MONTH (global constant), 812
month() (Cake\View\Helper\FormHelper method),
297
move() (Cake\Filesystem\Folder method), 745
N
name (Cake\Filesystem\File property), 746
name() (Cake\Filesystem\File method), 747
namespaceSplit() (global function), 811
nest() (Cake\Collection\Collection method), 730
nest() (Cake\Utility\Hash method), 763
nestedList()
(Cake\View\Helper\HtmlHelper
method), 318
newQuery() (Cake\Database\Connection method),
369
next() (Cake\View\Helper\PaginatorHelper method),
333
nice() (Cake\I18n\Time method), 798
normalize() (Cake\Utility\Hash method), 762
normalizePath() (Cake\Filesystem\Folder method),
745
NotAcceptableException, 597
NotFoundException, 597
notice() (Cake\Log\Log method), 633
NotImplementedException, 598
Number (class in Cake\I18n), 779
NumberHelper (class in Cake\View\Helper), 323
numbers()
(Cake\View\Helper\PaginatorHelper
method), 332
numeric() (Cake\Utility\Hash method), 759
O
offset() (Cake\Filesystem\File method), 747
open() (Cake\Filesystem\File method), 747
Index
options()
(Cake\View\Helper\PaginatorHelper
method), 335
ordinal() (Cake\I18n\Number method), 783
ordinal()
(Cake\View\Helper\NumberHelper
method), 327
owner() (Cake\Filesystem\File method), 747
P
PaaS, 862
paginate() (Cake\Controller\Controller method), 204
PaginatorComponent
(class
in
Cake\Controller\Component), 235
PaginatorHelper (class in Cake\View\Helper), 328
parseFileSize() (Cake\Utility\Text method), 789
passed arguments, 170
password()
(Cake\View\Helper\FormHelper
method), 287
path (Cake\Filesystem\File property), 746
path (Cake\Filesystem\Folder property), 742
path() (Cake\Core\App method), 715
path() (Cake\Core\Plugin method), 716
perms() (Cake\Filesystem\File method), 747
php:attr (directive), 111
php:attr (role), 112
php:class (directive), 111
php:class (role), 112
php:const (directive), 110
php:const (role), 112
php:exc (role), 112
php:exception (directive), 110
php:func (role), 112
php:function (directive), 110
php:global (directive), 110
php:global (role), 112
php:meth (role), 112
php:method (directive), 111
php:staticmethod (directive), 111
PhpConfig (class in Cake\Core\Configure\Engine),
152
pj() (global function), 810
plugin routing, 164
plugin syntax, 861
plugin() (Cake\Routing\Router method), 165
pluginSplit() (global function), 811
pluralize() (Cake\Utility\Inflector method), 775
postButton()
(Cake\View\Helper\FormHelper
method), 302
871
872
Index
T
Table (class in Cake\Database\Schema), 517
Table (class in Cake\ORM), 423
tableCells()
(Cake\View\Helper\HtmlHelper
method), 320
tableHeaders()
(Cake\View\Helper\HtmlHelper
method), 319
tableize() (Cake\Utility\Inflector method), 776
TableRegistry (class in Cake\ORM), 413
tail() (Cake\Utility\Text method), 792
tail() (Cake\View\Helper\TextHelper method), 344
take() (Cake\Collection\Collection method), 733
templates()
(Cake\View\Helper\HtmlHelper
method), 322
templates()
(Cake\View\Helper\PaginatorHelper
method), 329
TESTS (global constant), 812
Text (class in Cake\Utility), 787
text() (Cake\View\Helper\FormHelper method), 287
textarea() (Cake\View\Helper\FormHelper method),
288
TextHelper (class in Cake\View\Helper), 341
through() (Cake\Collection\Collection method), 737
Time (class in Cake\I18n), 795
time() (Cake\View\Helper\FormHelper method),
295
TIME_START (global constant), 812
timeAgoInWords() (Cake\I18n\Time method), 800
TimeHelper (class in Cake\View\Helper), 346
TimestampBehavior (class in Cake\ORM\Behavior),
496
TMP (global constant), 812
tokenize() (Cake\Utility\Text method), 789
toList() (Cake\Utility\Text method), 794
toList() (Cake\View\Helper\TextHelper method),
346
toPercentage() (Cake\I18n\Number method), 781
toPercentage() (Cake\View\Helper\NumberHelper
method), 325
toQuarter() (Cake\I18n\Time method), 800
toReadableSize() (Cake\I18n\Number method), 781
toReadableSize() (Cake\View\Helper\NumberHelper
method), 325
trace() (Cake\Error\Debugger method), 577
873
X
Xml (class in Cake\Utility), 805
XmlView (class), 273
Y
YEAR (global constant), 812
year() (Cake\View\Helper\FormHelper method), 296
UnauthorizedException, 597
underscore() (Cake\Utility\Inflector method), 776
unfold() (Cake\Collection\Collection method), 723
unlockedFields (SecurityComponent property), 233
unlockField()
(Cake\View\Helper\FormHelper
method), 309
updateAll() (Cake\ORM\Table method), 479
url() (Cake\Routing\Router method), 171
UrlHelper (class in Cake\View\Helper), 347
user() (AuthComponent method), 219
uuid() (Cake\Utility\Text method), 789
V
validatePost (SecurityComponent property), 233
Validator (class in Cake\Validation), 705
variable() (Cake\Utility\Inflector method), 777
vary() (Cake\Network\Response method), 194
vendor/cakephp-plugins.php, 643
View (class in Cake\View), 253
W
warning() (Cake\Log\Log method), 633
wasWithinLast() (Cake\I18n\Time method), 801
WEEK (global constant), 812
wrap() (Cake\Utility\Text method), 790
wrapBlock() (Cake\Utility\Text method), 790
writable() (Cake\Filesystem\File method), 747
write() (Cake\Cache\Cache method), 535
write() (Cake\Cache\CacheEngine method), 541
write() (Cake\Controller\Component\CookieComponent
method), 226
874
Index