0% found this document useful (0 votes)
480 views908 pages

CakePHP Cookbook

CakePHP cookbook

Uploaded by

Wilson Arteaga
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
480 views908 pages

CakePHP Cookbook

CakePHP cookbook

Uploaded by

Wilson Arteaga
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 908

CakePHP Cookbook Documentation

Release 3.8

Cake Software Foundation

Sep 02, 2019


Contents

1 CakePHP at a Glance 1
Conventions Over Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
The Model Layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
The View Layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
The Controller Layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
CakePHP Request Cycle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Just the Start . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Additional Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

2 Quick Start Guide 11


Content Management Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
CMS Tutorial - Creating the Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
CMS Tutorial - Creating the Articles Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

3 3.0 Migration Guide 25


Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Upgrade Tool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Application Directory Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
CakePHP should be installed with Composer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Removed Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
New ORM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Object settings/configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Core . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Console . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Shell / Task . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Event . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Log . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Network . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

i
Sessions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Network\Http . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Network\Email . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Controller\Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
TestSuite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
View\Helper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
I18n . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
L10n . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Utility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

4 Tutorials & Examples 49


Content Management Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
CMS Tutorial - Creating the Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
CMS Tutorial - Creating the Articles Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
CMS Tutorial - Tags and Users . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
CMS Tutorial - Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Bookmarker Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Bookmarker Tutorial Part 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Blog Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Blog Tutorial - Part 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
Blog Tutorial - Part 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
Blog Tutorial - Authentication and Authorization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109

5 Contributing 117
Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
Tickets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Coding Standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
Backwards Compatibility Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139

6 Installation 143
Installing CakePHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
Permissions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
Development Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
Production . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
Fire It Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
URL Rewriting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147

7 Configuration 153
Configuring your Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Additional Class Paths . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
Inflection Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
Environment Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Configure Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Reading and writing configuration files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
Bootstrapping CakePHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
Disabling Generic Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162

8 Routing 163
Quick Tour . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
Connecting Routes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165

ii
Connecting Scoped Middleware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
RESTful Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
Passed Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
Generating URLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
Redirect Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
Entity Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
Custom Route Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
Creating Persistent URL Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
Handling Named Parameters in URLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187

9 Request & Response Objects 193


Request . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
Response . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
Setting Cookies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
Setting Cross Origin Request Headers (CORS) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
Common Mistakes with Immutable Responses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
Cookie Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209

10 Controllers 213
The App Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
Request Flow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
Controller Actions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
Interacting with Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
Redirecting to Other Pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
Loading Additional Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
Paginating a Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
Configuring Components to Load . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
Configuring Helpers to Load . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
Request Life-cycle Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
More on Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221

11 Views 263
The App View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
View Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
Using View Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
Layouts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
View Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
Creating Your Own View Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
More About Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275

12 Database Access & ORM 379


Quick Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379
More Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381

13 Caching 539
Configuring Cache Engines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 540
Writing to a Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 543
Reading From a Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 544
Deleting From a Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 545
Clearing Cached Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 546
Using Cache to Store Counters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 546
Using Cache to Store Common Query Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 546
Using Groups . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 547
Globally Enable or Disable Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 548

iii
Creating a Cache Engine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 548

14 Bake Console 551

15 Console Tools, Shells & Tasks 553


The CakePHP Console . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 553
Console Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 555
Renaming Commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 555
Commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 556
CakePHP Provided Commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 577
Routing in the Console Environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 591

16 Debugging 593
Basic Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 593
Using the Debugger Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 594
Outputting Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 594
Logging With Stack Traces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 595
Generating Stack Traces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 595
Getting an Excerpt From a File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 595
Using Logging to Debug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 596
Debug Kit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 596

17 Deployment 597
Moving files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 597
Adjust config/app.php . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 597
Check Your Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 598
Set Document Root . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 598
Improve Your Application’s Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 598
Deploying an update . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 599

18 Email 601
Basic Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 601
Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 602
Setting Headers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 605
Sending Templated Emails . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 605
Sending Attachments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 606
Using Transports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 607
Sending Messages Quickly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 608
Sending Emails from CLI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 609
Creating Reusable Emails . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 609
Testing Email . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 610

19 Error & Exception Handling 613


Error & Exception Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 613
Changing Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 614
Customize Error Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 614
Customize the ErrorController . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 615
Change the ExceptionRenderer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 615
Creating your Own Error Handler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 617
Creating your own Application Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 618
Built in Exceptions for CakePHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 619

20 Events System 623


Example Event Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 623
Accessing Event Managers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 624

iv
Core Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 625
Registering Listeners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 625
Dispatching Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 629
Additional Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 631

21 Internationalization & Localization 633


Setting Up Translations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 633
Using Translation Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 635
Creating Your Own Translators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 639
Localizing Dates and Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 642
Automatically Choosing the Locale Based on Request Data . . . . . . . . . . . . . . . . . . . . . . . . . . 643

22 Logging 645
Logging Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 645
Error and Exception Logging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 647
Interacting with Log Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 647
Using the FileLog Adapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 648
Logging to Syslog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 648
Writing to Logs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 649
Log API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 650
Logging Trait . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 651
Using Monolog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 651

23 Modelless Forms 653


Creating a Form . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 653
Processing Request Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 654
Setting Form Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 655
Getting Form Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 656
Invalidating Individual Form Fields from Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 656
Creating HTML with FormHelper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 656

24 Plugins 659
Installing a Plugin With Composer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 659
Manually Installing a Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 660
Loading a Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 661
Plugin Hook Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 661
Using Plugin Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 663
Creating Your Own Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 664
Plugin Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 665
Plugin Routes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 665
Plugin Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 666
Plugin Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 667
Plugin Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 668
Plugin Assets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 669
Components, Helpers and Behaviors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 670
Commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 671
Testing your Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 671
Publishing your Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 671
Plugin Map File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 671
Manage Your Plugins using Mixer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 672

25 REST 673
The Simple Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 673
Accepting Input in Other Formats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 675
RESTful Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 676

v
26 Security 677
Security Utility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 677

27 Sessions 681
Session Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 681
Built-in Session Handlers & Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 682
Setting ini directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 684
Creating a Custom Session Handler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 684
Accessing the Session Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 686
Reading & Writing Session Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 686
Destroying the Session . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 687
Rotating Session Identifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 687
Flash Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 687

28 Testing 689
Installing PHPUnit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 689
Test Database Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 690
Checking the Test Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 690
Test Case Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 691
Creating Your First Test Case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 691
Running Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 693
Test Case Lifecycle Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 694
Fixtures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 695
Testing Table Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 700
Controller Integration Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 702
Console Integration Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 711
Testing Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 711
Testing Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 712
Testing Helpers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 713
Testing Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 714
Testing Email . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 716
Creating Test Suites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 716
Creating Tests for Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 716
Generating Tests with Bake . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 717
Integration with Jenkins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 718

29 Validation 721
Creating Validators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 721
Validating Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 729
Validating Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 730
Core Validation Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 731

30 App Class 733


Finding Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 733
Finding Paths to Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 733
Locating Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 734
Locating Themes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 734
Loading Vendor Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 734

31 Collections 737
Quick Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 737
List of Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 738
Iterating . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 738
Filtering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 742
Aggregation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 743

vi
Sorting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 747
Working with Tree Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 748
Other Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 750

32 Folder & File 757


Basic Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 757
Folder API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 758
File API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 761

33 Hash 765
Hash Path Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 765

34 Http Client 781


Doing Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 781
Creating Multipart Requests with Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 782
Sending Request Bodies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 783
Request Method Options . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 783
Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 784
Creating Scoped Clients . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 785
Setting and Managing Cookies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 786
Response Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 787
Changing Transport Adapters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 789

35 Inflector 791
Summary of Inflector Methods and Their Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 791
Creating Plural & Singular Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 792
Creating CamelCase and under_scored Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 792
Creating Human Readable Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 793
Creating Table and Class Name Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 793
Creating Variable Names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 793
Creating URL Safe Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 793
Inflection Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 794

36 Number 795
Formatting Currency Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 796
Setting the Default Currency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 796
Formatting Floating Point Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 796
Formatting Percentages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 797
Interacting with Human Readable Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 797
Formatting Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 798
Format Differences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 799
Configure formatters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 800

37 Registry Objects 801


Loading Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 801
Triggering Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 802
Disabling Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 802

38 Text 803
Convert Strings into ASCII . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 804
Creating URL Safe Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 804
Generating UUIDs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 804
Simple String Parsing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805
Formatting Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805
Wrapping Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 806

vii
Highlighting Substrings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 806
Removing Links . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 807
Truncating Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 807
Truncating the Tail of a String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 808
Extracting an Excerpt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 809
Converting an Array to Sentence Form . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 809

39 Date & Time 811


Creating Time Instances . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 812
Manipulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 812
Formatting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 813
Conversion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 816
Comparing With the Present . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 816
Comparing With Intervals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 816
Dates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 817
Immutable Dates and Times . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 817
Accepting Localized Request Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 818
Supported Timezones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 818

40 Xml 819
Loading XML documents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 819
Loading HTML documents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 820
Transforming a XML String in Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 820
Transforming an Array into a String of XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 820

41 Constants & Functions 823


Global Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 823
Core Definition Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 825
Timing Definition Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 826

42 Chronos 827

43 Debug Kit 829

44 Migrations 831

45 ElasticSearch 833

46 Appendices 835
3.x Migration Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 835
Forwards Compatibility Shimming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 885
General Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 885

PHP Namespace Index 889

Index 891

viii
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.

Conventions Over Configuration

CakePHP provides a basic organizational structure that covers class names, filenames, database table names, and other
conventions. While the conventions take some time to learn, by following the conventions CakePHP provides you
can avoid needless configuration and make a uniform application structure that makes working with various projects
simple. The conventions chapter covers the various conventions that CakePHP uses.

The Model Layer

The Model layer represents the part of your application that implements the business logic. It is responsible for
retrieving data and converting it into the primary meaningful concepts in your application. This includes processing,
validating, associating or other tasks related to handling data.
In the case of a social network, the Model layer would take care of tasks such as saving the user data, saving friends’
associations, storing and retrieving user photos, finding suggestions for new friends, etc. The model objects can be
thought of as “Friend”, “User”, “Comment”, or “Photo”. If we wanted to load some data from our users table we
could do:

use Cake\ORM\TableRegistry;

// Prior to 3.6.0

1
CakePHP Cookbook Documentation, Release 3.8

$users = TableRegistry::get('Users');

$users = TableRegistry::getTableLocator()->get('Users');
$query = $users->find();
foreach ($query as $row) {
echo $row->username;
}

You may notice that we didn’t have to write any code before we could start working with our data. By using conven-
tions, 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;

// Prior to 3.6.0
$users = TableRegistry::get('Users');

$users = TableRegistry::getTableLocator()->get('Users');
$user = $users->newEntity(['email' => '[email protected]']);
$users->save($user);

The View Layer

The View layer renders a presentation of modeled data. Being separate from the Model objects, it is responsible for
using the information it has available to produce any presentational interface your application might need.
For example, the view could use model data to render an HTML view template containing it, or a XML formatted
result for others to consume:

// In a view template file, we'll render an 'element' for each user.


<?php foreach ($users as $user): ?>
<li class="user">
<?= $this->element('user_info', ['user' => $user]) ?>
</li>
<?php endforeach; ?>

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.

The Controller Layer

The Controller layer handles requests from users. It is responsible for rendering a response with the aid of both the
Model and the View layers.
A controller can be seen as a manager that ensures that all resources needed for completing a task are delegated to the
correct workers. It waits for petitions from clients, checks their validity according to authentication or authorization
rules, delegates data fetching or processing to the model, selects the type of presentational data that the clients are
accepting, and finally delegates the rendering process to the View layer. An example of a user registration controller
would be:

2 Chapter 1. CakePHP at a Glance


CakePHP Cookbook Documentation, Release 3.8

public function add()


{
$user = $this->Users->newEntity();
if ($this->request->is('post')) {
$user = $this->Users->patchEntity($user, $this->request->getData());
if ($this->Users->save($user, ['validate' => 'registration'])) {
$this->Flash->success(__('You are now registered.'));
} else {
$this->Flash->error(__('There were some problems.'));
}
}
$this->set('user', $user);
}

You may notice that we never explicitly rendered a view. CakePHP’s conventions will take care of selecting the right
view and rendering it with the view data we prepared with set().

CakePHP Request Cycle

Now that you are familiar with the different layers in CakePHP, lets review how a request cycle works in CakePHP:

CakePHP Request Cycle 3


CakePHP Cookbook Documentation, Release 3.8

The typical CakePHP request cycle starts with a user requesting a page or resource in your application. At a high level
each request goes through the following steps:
1. The webserver rewrite rules direct the request to webroot/index.php.
2. Your Application is loaded and bound to an HttpServer.
3. Your application’s middleware is initialized.
4. A request and response is dispatched through the PSR-7 Middleware that your application uses. Typically this
includes error trapping and routing.
5. If no response is returned from the middleware and the request contains routing information, a controller &
action are selected.
6. The controller’s action is called and the controller interacts with the required Models and Components.
7. The controller delegates response creation to the View to generate the output resulting from the model data.
8. The view uses Helpers and Cells to generate the response body and headers.
9. The response is sent back out through the /controllers/middleware.
10. The HttpServer emits the response to the webserver.

Just the Start

Hopefully this quick overview has piqued your interest. Some other great features in CakePHP are:
• 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

https://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

https://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 you’ll also be lightening our support load. Both the manual and the API have an online
component.

4 Chapter 1. CakePHP at a Glance


CakePHP Cookbook Documentation, Release 3.8

The Bakery

https://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 you’re acquainted with CakePHP, log on and share your knowledge with the community and
gain instant fame and fortune.

The API

https://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.
It’s a straight forward code reference, so bring your propeller hat.

The Test Cases

If you ever feel the information provided in the API is not sufficient, check out the code of the test cases provided with
CakePHP. They can serve as practical examples for function and data member usage for a class.

tests/TestCase/

The IRC Channel

IRC Channels on irc.freenode.net:


• #cakephp – General Discussion
• #cakephp-docs – Documentation
• #cakephp-bakery – Bakery
• #cakephp-fr – French Canal.
If you’re stumped, give us a holler in the CakePHP IRC channel. Someone from the development team4 is usually
there, especially during the daylight hours for North and South America users. We’d love to hear from you, whether
you need some help, want to find users in your area, or would like to donate your brand new sports car.

Official CakePHP Forum

CakePHP Official Forum5


Our official forum where you can ask for help, suggest ideas and have a talk about CakePHP. It’s a perfect place for
quickly finding answers and help others. Join the CakePHP family by signing up.

Stackoverflow

https://stackoverflow.com/6
Tag your questions with cakephp and the specific version you are using to enable existing users of stackoverflow to
find your questions.
4 https://github.com/cakephp?tab=members
5 http://discourse.cakephp.org
6 https://stackoverflow.com/questions/tagged/cakephp/

Additional Reading 5
CakePHP Cookbook Documentation, Release 3.8

Where to get Help in your Language

Brazilian Portuguese

• Brazilian CakePHP Community7

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


7 http://cakephp-br.org
8 https://cakesf.slack.com/messages/denmark/
9 http://cakephp-fr.org
10 https://cakesf.slack.com/messages/german/
11 https://www.facebook.com/groups/146324018754907/
12 http://cakephp.ir
13 https://cakesf.slack.com/messages/netherlands/
14 https://cakesf.slack.com/messages/japanese/
15 https://www.facebook.com/groups/304490963004377/
16 http://groups.google.com/group/cakephp-pt

6 Chapter 1. CakePHP at a Glance


CakePHP Cookbook Documentation, Release 3.8

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 CakePHP’s 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, PascalCased, and end in Controller. UsersController and
ArticleCategoriesController are both examples of conventional controller names.
Public methods on Controllers are often exposed as ‘actions’ accessible through a web browser. For example the /
users/view maps to the view() method of the UsersController out of the box. Protected or private methods
cannot be accessed with routing.

URL Considerations for Controller Names

As you’ve just seen, single word controllers map to a simple lower case URL path. For example, UsersController
(which would be defined in the file name UsersController.php) is accessed from http://example.com/users.
While you can route multiple word controllers in any way you like, the convention is that your URLs are lowercase
and dashed using the DashedRoute class, therefore /article-categories/view-all is the correct form to
access the ArticleCategoriesController::viewAll() action.
When you create links using this->Html->link(), you can use the following conventions for the url array:

$this->Html->link('link-title', [
'prefix' => 'MyPrefix' // PascalCased
'plugin' => 'MyPlugin', // PascalCased
'controller' => 'ControllerName', // PascalCased
'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-4 standard for autoloading. The following are some
examples of class names and their filenames:
• The Controller class LatestArticlesController would be found in a file named LatestArticlesCon-
troller.php
17 https://cakesf.slack.com/messages/spanish/
18 http://groups.google.com/group/cakephp-esp

Additional Reading 7
CakePHP Cookbook Documentation, Release 3.8

• 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 EspeciallyFunk-
ableBehavior.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.

Database Conventions

Table names corresponding to CakePHP models are plural and underscored. For example users,
article_categories, and user_favorite_pages respectively.
Field/Column names with two or more words are underscored: first_name.
Foreign keys in hasMany, belongsTo/hasOne relationships are recognized by default as the (singular) name of the
related table followed by _id. So if Users hasMany Articles, the articles table will refer to the users table via
a user_id foreign key. For a table like article_categories whose name contains multiple words, the foreign
key would be article_category_id.
Join tables, used in BelongsToMany relationships between models, should be named after the model tables
they will join or the bake command won’t work, arranged in alphabetical order (articles_tags rather than
tags_articles). If you need to add additional columns on the junction table you should create a separate en-
tity/table class for that table.
In addition to using an auto-incrementing integer as primary keys, you can also use UUID columns. CakePHP will
create UUID values automatically using (Cake\Utility\Text::uuid()) whenever you save new records using
the Table::save() method.

Model Conventions

Table class names are plural, PascalCased and end in Table. UsersTable, ArticleCategoriesTable,
and UserFavoritePagesTable are all examples of table class names matching the users,
article_categories and user_favorite_pages tables respectively.
Entity class names are singular PascalCased and have no suffix. User, ArticleCategory, and
UserFavoritePage are all examples of entity names matching the users, article_categories and
user_favorite_pages tables respectively.

View Conventions

View template files are named after the controller functions they display, in an underscored form. The viewAll()
function of the ArticlesController class will look for a view template in src/Template/Articles/view_all.ctp.
The basic pattern is src/Template/Controller/underscored_function_name.ctp.

Note: By default CakePHP uses English inflections. If you have database tables/columns that use an-
other language, you will need to add inflection rules (from singular to plural and vice-versa). You can use

8 Chapter 1. CakePHP at a Glance


CakePHP Cookbook Documentation, Release 3.8

Cake\Utility\Inflector to define your custom inflection rules. See the documentation about Inflector for
more information.

Plugins Conventions

It is useful to prefix a CakePHP plugin with “cakephp-” in the package name. This makes the name semantically
related on the framework it depends on.
Do not use the CakePHP namespace (cakephp) as vendor name as this is reserved to CakePHP owned plugins. The
convention is to use lowercase letters and dashes as separator:

// Bad
cakephp/foo-bar

// Good
your-name/cakephp-foo-bar

Summarized

By naming the pieces of your application using CakePHP conventions, you gain functionality without the hassle and
maintenance tethers of configuration. Here’s a final example that ties the conventions together:
• Database table: “articles”
• Table class: ArticlesTable, found at src/Model/Table/ArticlesTable.php
• Entity class: Article, found at src/Model/Entity/Article.php
• Controller class: ArticlesController, found at src/Controller/ArticlesController.php
• View template, found at src/Template/Articles/index.ctp
Using these conventions, CakePHP knows that a request to http://example.com/articles maps to a call on
the index() function of the ArticlesController, where the Articles model is automatically available (and automati-
cally tied to the ‘articles’ 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 you’d need to create anyway.
Now that you’ve been introduced to CakePHP’s fundamentals, you might try a run through the Content Management
Tutorial to see how things fit together.
See awesome list recommendations19 for details.

CakePHP Folder Structure

After you’ve downloaded the CakePHP application skeleton, there are a few top level folders you should see:
• The bin folder holds the Cake console executables.
• The config folder holds the 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 your application’s source files will be placed.
19 https://github.com/FriendsOfCake/awesome-cakephp/blob/master/CONTRIBUTING.md#tips-for-creating-cakephp-plugins

Additional Reading 9
CakePHP Cookbook Documentation, Release 3.8

• 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 translation messages, model descriptions and some-
times session information.
• The vendor folder is where CakePHP and other application dependencies will be installed by Composer20 .
Editing these files is not advised, as Composer will overwrite your changes next time you update.
• The webroot directory is the public document root of your application. It contains all the files you want to be
publicly 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 these directories are not writable.

The src Folder

CakePHP’s src folder is where you will do most of your application development. Let’s look a little closer at the
folders inside src.
Command Contains your application’s console commands. See Console Commands to learn more.
Console Contains the installation script executed by Composer.
Controller Contains your application’s Controllers and their components.
Locale Stores language files for internationalization.
Middleware Stores any /controllers/middleware for your application.
Model Contains your application’s tables, entities and behaviors.
Shell Contains shell tasks for your application. For more information see Console Tools, Shells & Tasks.
Template Presentational files are placed here: elements, error pages, layouts, and view template files.
View Presentational classes are placed here: views, cells, helpers.

Note: The folders Command and Locale are not there by default. You can add them when you need them.

20 http://getcomposer.org

10 Chapter 1. CakePHP at a Glance


CHAPTER 2

Quick Start Guide

The best way to experience and learn CakePHP is to sit down and build something. To start off we’ll build a simple
Content Management application.

Content Management Tutorial

This tutorial will walk you through the creation of a simple CMS (Content Management System) application. To start
with, we’ll be installing CakePHP, creating our database, and building simple article management.
Here’s what you’ll need:
1. A database server. We’re going to be using MySQL server in this tutorial. You’ll need to know enough about
SQL in order to create a database, and run SQL snippets from the tutorial. CakePHP will handle building all the
queries your application needs. Since we’re 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.6.0 (CLI) or higher. Your webserver’s PHP version must also be of 5.6.0
or higher, and should be the same version your command line interface (CLI) PHP is.

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, you’ll need to download and install Composer if you haven’t done so already.
If you have cURL installed, it’s as easy as running the following:

11
CakePHP Cookbook Documentation, Release 3.8

curl -s https://getcomposer.org/installer | php

Or, you can download composer.phar from the Composer website21 .


Then simply type the following line in your terminal from your installation directory to install the CakePHP application
skeleton in the cms directory of the current working directory:

php composer.phar create-project --prefer-dist cakephp/app cms

If you downloaded and ran the Composer Windows Installer22 , 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 cms

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 don’t 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:

/cms
/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 CakePHP’s directory structure works: check out the CakePHP
Folder Structure section.
If you get lost during this tutorial, you can see the finished result on GitHub23 .

Checking our Installation

We can quickly check that our installation is correct, by checking the default home page. Before you can do that,
you’ll need to start the development server:

21 https://getcomposer.org/download/
22 https://getcomposer.org/Composer-Setup.exe
23 https://github.com/cakephp/cms-tutorial

12 Chapter 2. Quick Start Guide


CakePHP Cookbook Documentation, Release 3.8

cd /path/to/our/app

bin/cake server

Note: For Windows, the command needs to be bin\cake server (note the backslash).

This will start PHP’s 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 green chef hats other than CakePHP being able to connect to your
database. If not, you may need to install additional PHP extensions, or set directory permissions.
Next, we will build our Database and create our first model.

CMS Tutorial - Creating the Database

Now that we have CakePHP installed, let’s set up the database for our CMS application. If you haven’t already done
so, create an empty database for use in this tutorial, with a name of your choice, e.g. cake_cms. You can execute the
following SQL to create the necessary tables:

USE cake_cms;

CREATE TABLE users (


id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
created DATETIME,
modified DATETIME
);

CREATE TABLE articles (


id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
slug VARCHAR(191) NOT NULL,
body TEXT,
published BOOLEAN DEFAULT FALSE,
created DATETIME,
modified DATETIME,
UNIQUE KEY (slug),
FOREIGN KEY user_key (user_id) REFERENCES users(id)
) CHARSET=utf8mb4;

CREATE TABLE tags (


id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(191),
created DATETIME,
modified DATETIME,
UNIQUE KEY (title)
) CHARSET=utf8mb4;

CREATE TABLE articles_tags (


article_id INT NOT NULL,
tag_id INT NOT NULL,
PRIMARY KEY (article_id, tag_id),
FOREIGN KEY tag_key(tag_id) REFERENCES tags(id),

CMS Tutorial - Creating the Database 13


CakePHP Cookbook Documentation, Release 3.8

FOREIGN KEY article_key(article_id) REFERENCES articles(id)


);

INSERT INTO users (email, password, created, modified)


VALUES
('[email protected]', 'secret', NOW(), NOW());

INSERT INTO articles (user_id, title, slug, body, published, created, modified)
VALUES
(1, 'First Post', 'first-post', 'This is the first post.', 1, now(), now());

You may have noticed that the articles_tags table used a composite primary key. CakePHP supports composite
primary keys almost everywhere allowing you to have simpler schemas that don’t require additional id columns.
The table and column names we used were not arbitrary. By using CakePHP’s naming conventions, we can leverage
CakePHP more effectively and avoid needing to configure the framework. While CakePHP is flexible enough to
accommodate almost any database schema, adhering to the conventions will save you time as you can leverage the
convention based defaults CakePHP provides.

Database Configuration

Next, let’s tell CakePHP where our database is and how to connect to it. Replace the values in the Datasources.
default array in your config/app.php file with those that apply to your setup. A sample completed configuration
array might look something like the following:

<?php
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_cms',
'encoding' => 'utf8mb4',
'timezone' => 'UTC',
'cacheMetadata' => true,
],
],
// More configuration below.
];

Once you’ve saved your config/app.php file, you should see that ‘CakePHP is able to connect to the database’ section
have a green chef hat.

Note: A copy of CakePHP’s default configuration file is found in config/app.default.php.

14 Chapter 2. Quick Start Guide


CakePHP Cookbook Documentation, Release 3.8

Creating our First Model

Models are the heart of a CakePHP applications. They enable us to read and modify our data. They allow us to build
relations between our data, validate data, and apply application rules. Models build the foundations necessary to build
our controller actions and templates.
CakePHP’s models are composed of Table and Entity objects. Table objects provide access to the collection
of entities stored in a specific table. They are stored in src/Model/Table. The file we’ll be creating will be saved to
src/Model/Table/ArticlesTable.php. The completed file should look like this:
<?php
// 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');
}
}

We’ve attached the Timestamp behavior which will automatically populate the created and modified columns of
our table. By naming our Table object ArticlesTable, CakePHP can use naming conventions to know that our
model uses the articles table. CakePHP also uses conventions to know that the id column is our table’s primary
key.

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 Arti-
cleTable.php), CakePHP will not recognize any of your settings and will use the generated model instead.

We’ll also create an Entity class for our Articles. Entities represent a single record in the database, and provide row
level behavior for our data. Our entity will be saved to src/Model/Entity/Article.php. The completed file should look
like this:
<?php
// src/Model/Entity/Article.php
namespace App\Model\Entity;

use Cake\ORM\Entity;

class Article extends Entity


{
protected $_accessible = [
'*' => true,
'id' => false,
'slug' => false,
];
}

Our entity is quite slim right now, and we’ve only setup the _accessible property which controls how properties
can be modified by Mass Assignment.
We can’t do much with our models right now, so next we’ll create our first Controller and Template to allow us to
interact with our model.

CMS Tutorial - Creating the Database 15


CakePHP Cookbook Documentation, Release 3.8

CMS Tutorial - Creating the Articles Controller

With our model created, we need a controller for our articles. Controllers in CakePHP handle HTTP requests and
execute business logic contained in model methods, to prepare the response. We’ll place this new controller in a file
called ArticlesController.php inside the src/Controller directory. Here’s what the basic controller should look like:

<?php
// src/Controller/ArticlesController.php

namespace App\Controller;

class ArticlesController extends AppController


{
}

Now, let’s add an action to our controller. Actions are controller methods that have routes connected to them. For exam-
ple, when a user requests www.example.com/articles/index (which is also the same as www.example.com/articles),
CakePHP will call the index method of your ArticlesController. This method should query the model layer,
and prepare a response by rendering a Template in the View. The code for that action would look like this:

<?php
// src/Controller/ArticlesController.php

namespace App\Controller;

class ArticlesController extends AppController


{
public function index()
{
$this->loadComponent('Paginator');
$articles = $this->Paginator->paginate($this->Articles->find());
$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. You may be tempted to name your controllers and actions in
a way that allows you to obtain specific URLs. Resist that temptation. Instead, follow the CakePHP Conventions
creating readable, meaningful action names. You can then use Routing to connect the URLs you want to the actions
you’ve created.
Our controller action is very simple. It fetches a paginated set of articles from the database, using the Articles Model
that is automatically loaded via naming conventions. It then uses set() to pass the articles into the Template (which
we’ll create soon). CakePHP will automatically render the template after our controller action completes.

Create the Article List Template

Now that we have our controller pulling data from the model, and preparing our view context, let’s create a view
template for our index action.
CakePHP view templates are presentation-flavored PHP code that is inserted inside the application’s layout. While
we’ll be creating HTML here, Views can also generate JSON, CSV or even binary files like PDFs.
A layout is presentation code that is wrapped around a view. Layout files contain common site elements like headers,
footers and navigation elements. Your application can have multiple layouts, and you can switch between them, but

16 Chapter 2. Quick Start Guide


CakePHP Cookbook Documentation, Release 3.8

for now, let’s just use the default layout.


CakePHP’s template files are stored in src/Template inside a folder named after the controller they correspond to. So
we’ll have to create a folder named ‘Articles’ in this case. Add the following code to your application:

<!-- File: src/Template/Articles/index.ctp -->

<h1>Articles</h1>
<table>
<tr>
<th>Title</th>
<th>Created</th>
</tr>

<!-- Here is where we iterate through our $articles query object, printing out
˓→ article info -->

<?php foreach ($articles as $article): ?>


<tr>
<td>
<?= $this->Html->link($article->title, ['action' => 'view', $article->
˓→slug]) ?>

</td>
<td>
<?= $article->created->format(DATE_RFC850) ?>
</td>
</tr>
<?php endforeach; ?>
</table>

In the last section we assigned the ‘articles’ variable to the view using set(). Variables passed into the view are
available in the view templates as local variables which we used in the above code.
You might have noticed the use of an object called $this->Html. This is an instance of the CakePHP HtmlHelper.
CakePHP comes with a set of view helpers that make tasks like creating links, forms, and pagination buttons easy.
You can learn more about Helpers in their chapter, but what’s important to note here is that the link() method will
generate an HTML link with the given link text (the first parameter) and URL (the second parameter).
When specifying URLs in CakePHP, it is recommended that you use arrays or named routes. These syntaxes allow
you to leverage the reverse routing features CakePHP offers.
At this point, you should be able to point your browser to http://localhost:8765/articles/index. You should see your
list view, correctly formatted with the title and table listing of the articles.

Create the View Action

If you were to click one of the ‘view’ links in our Articles list page, you’d see an error page saying that action hasn’t
been implemented. Lets fix that now:

// Add to existing src/Controller/ArticlesController.php file

public function view($slug = null)


{
$article = $this->Articles->findBySlug($slug)->firstOrFail();
$this->set(compact('article'));
}

CMS Tutorial - Creating the Articles Controller 17


CakePHP Cookbook Documentation, Release 3.8

While this is a simple action, we’ve used some powerful CakePHP features. We start our action off by using
findBySlug() which is a Dynamic Finder. This method allows us to create a basic query that finds articles by
a given slug. We then use firstOrFail() to either fetch the first record, or throw a NotFoundException.
Our action takes a $slug parameter, but where does that parameter come from? If a user requests /articles/
view/first-post, then the value ‘first-post’ is passed as $slug by CakePHP’s routing and dispatching layers.
If we reload our browser with our new action saved, we’d see another CakePHP error page telling us we’re missing a
view template; let’s fix that.

Create the View Template

Let’s 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>
<p><?= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?></p>

You can verify that this is working by trying the links at /articles/index or manually requesting an article by
accessing URLs like /articles/view/first-post.

Adding Articles

With the basic read views created, we need to make it possible for new articles to be created. Start by creating an
add() action in the ArticlesController. Our controller should now look like:
// src/Controller/ArticlesController.php

namespace App\Controller;

use App\Controller\AppController;

class ArticlesController extends AppController


{

public function initialize()


{
parent::initialize();

$this->loadComponent('Paginator');
$this->loadComponent('Flash'); // Include the FlashComponent
}

public function index()


{
$articles = $this->Paginator->paginate($this->Articles->find());
$this->set(compact('articles'));
}

public function view($slug)


{
$article = $this->Articles->findBySlug($slug)->firstOrFail();
$this->set(compact('article'));
}

18 Chapter 2. Quick Start Guide


CakePHP Cookbook Documentation, Release 3.8

public function add()


{
$article = $this->Articles->newEntity();
if ($this->request->is('post')) {
$article = $this->Articles->patchEntity($article, $this->request->
˓→getData());

// Hardcoding the user_id is temporary, and will be removed later


// when we build authentication out.
$article->user_id = 1;

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);
}
}

Note: You need to include the Flash component in any controller where you will use it. Often it makes sense to
include it in your AppController.

Here’s 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 doesn’t 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 information regarding the request that was just received. We use the
Cake\Http\ServerRequest::is() method to check that the request is a HTTP POST request.
Our POST data is available in $this->request->getData(). You can use the pr() or debug() functions
to print it out if you want to see what it looks like. To save our data, we first ‘marshal’ the POST data into an Article
Entity. The Entity is then persisted using the ArticlesTable we created earlier.
After saving our new article we use FlashComponent’s success() method to set a message into the ses-
sion. The success method is provided using PHP’s magic method features24 . Flash messages will be dis-
played on the next page after redirecting. In our layout we have <?= $this->Flash->render() ?> which
displays flash messages and clears the corresponding session variable. Finally, after saving is complete, we
use Cake\Controller\Controller::redirect to send the user back to the articles list. 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 API25 to see the formats in which you can
specify a URL for various CakePHP functions.

Create Add Template

Here’s our add view template:


24 http://php.net/manual/en/language.oop5.overloading.php#object.call
25 https://api.cakephp.org

CMS Tutorial - Creating the Articles Controller 19


CakePHP Cookbook Documentation, Release 3.8

<!-- File: src/Template/Articles/add.ctp -->

<h1>Add Article</h1>
<?php
echo $this->Form->create($article);
// Hard code the user for now.
echo $this->Form->control('user_id', ['type' => 'hidden', 'value' => 1]);
echo $this->Form->control('title');
echo $this->Form->control('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. Here’s the HTML that
$this->Form->create() generates:

<form method="post" action="/articles/add">

Because we called create() without a URL option, FormHelper assumes we want the form to submit back to
the current action.
The $this->Form->control() method is used to create form elements of the same name. The first parameter
tells CakePHP which field they correspond to, and the second parameter allows you to specify a wide array of options
- in this case, the number of rows for the textarea. There’s a bit of introspection and conventions used here. The
control() will output different form elements based on the model field specified, and use inflection to generate
the label text. You can customize the label, the input or any other aspect of the form controls using options. The
$this->Form->end() call closes the form.
Now let’s 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']) ?>

Adding Simple Slug Generation

If we were to save an Article right now, saving would fail as we are not creating a slug attribute, and the column is
NOT NULL. Slug values are typically a URL-safe version of an article’s title. We can use the beforeSave() callback
of the ORM to populate our slug:

// in src/Model/Table/ArticlesTable.php
namespace App\Model\Table;

use Cake\ORM\Table;
// the Text class
use Cake\Utility\Text;

// Add the following method.

public function beforeSave($event, $entity, $options)


{
if ($entity->isNew() && !$entity->slug) {
$sluggedTitle = Text::slug($entity->title);
// trim slug to maximum length defined in schema
$entity->slug = substr($sluggedTitle, 0, 191);
}
}

20 Chapter 2. Quick Start Guide


CakePHP Cookbook Documentation, Release 3.8

This code is simple, and doesn’t take into account duplicate slugs. But we’ll fix that later on.

Add Edit Action

Our application can now save articles, but we can’t edit them. Lets rectify that now. Add the following action to your
ArticlesController:

// in src/Controller/ArticlesController.php

// Add the following method.

public function edit($slug)


{
$article = $this->Articles->findBySlug($slug)->firstOrFail();
if ($this->request->is(['post', 'put'])) {
$this->Articles->patchEntity($article, $this->request->getData());
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.'));
}

$this->set('article', $article);
}

This action first ensures that the user has tried to access an existing record. If they haven’t passed in an $slug
parameter, or the article does not exist, a NotFoundException will be thrown, and the CakePHP ErrorHandler
will render the appropriate error page.
Next the action checks whether the request is either a POST or a PUT request. If it is, then we use the POST/PUT
data to update our article entity by using the patchEntity() method. Finally, we call save() set the appropriate
flash message and either redirect or display validation errors.

Create Edit Template

The edit template should look like this:

<!-- File: src/Template/Articles/edit.ctp -->

<h1>Edit Article</h1>
<?php
echo $this->Form->create($article);
echo $this->Form->control('user_id', ['type' => 'hidden']);
echo $this->Form->control('title');
echo $this->Form->control('body', ['rows' => '3']);
echo $this->Form->button(__('Save Article'));
echo $this->Form->end();
?>

This template outputs the edit form (with the values populated), along with any necessary validation error messages.
You can now update your index view with links to edit specific articles:

<!-- File: src/Template/Articles/index.ctp (edit links added) -->

CMS Tutorial - Creating the Articles Controller 21


CakePHP Cookbook Documentation, Release 3.8

<h1>Articles</h1>
<p><?= $this->Html->link("Add Article", ['action' => 'add']) ?></p>
<table>
<tr>
<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>
<?= $this->Html->link($article->title, ['action' => 'view', $article->
˓→slug]) ?>

</td>
<td>
<?= $article->created->format(DATE_RFC850) ?>
</td>
<td>
<?= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?>
</td>
</tr>
<?php endforeach; ?>

</table>

Update Validation Rules for Articles

Up until this point our Articles had no input validation done. Lets fix that by using a validator:

// src/Model/Table/ArticlesTable.php

// add this use statement right below the namespace declaration to import
// the Validator class
use Cake\Validation\Validator;

// Add the following method.


public function validationDefault(Validator $validator)
{
$validator
->allowEmptyString('title', false)
->minLength('title', 10)
->maxLength('title', 255)

->allowEmptyString('body', false)
->minLength('body', 10);

return $validator;
}

The validationDefault() method tells CakePHP how to validate your data when the save() method is called.
Here, we’ve specified that both the title, and body fields must not be empty, and have certain length constraints.
CakePHP’s validation engine is powerful and flexible. It provides a suite of frequently used rules for tasks like email

22 Chapter 2. Quick Start Guide


CakePHP Cookbook Documentation, Release 3.8

addresses, IP addresses etc. and the 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 we’ve used the Cake\View\Helper\FormHelper::control() method of the FormHelper to
create our form elements, our validation error messages will be shown automatically.

Add Delete Action

Next, let’s make a way for users to delete articles. Start with a delete() action in the ArticlesController:
// src/Controller/ArticlesController.php

public function delete($slug)


{
$this->request->allowMethod(['post', 'delete']);

$article = $this->Articles->findBySlug($slug)->firstOrFail();
if ($this->Articles->delete($article)) {
$this->Flash->success(__('The {0} article has been deleted.', $article->
˓→title));

return $this->redirect(['action' => 'index']);


}
}

This logic deletes the article specified by $slug, and uses $this->Flash->success() to show the user a
confirmation message after redirecting them to /articles. If the user attempts to delete an article using a GET
request, allowMethod() will throw an exception. Uncaught exceptions are captured by CakePHP’s exception
handler, and a nice error page is displayed. There are many built-in Exceptions that can be used to indicate the various
HTTP errors your application might need to generate.

Warning: Allowing content to be deleted using GET requests is very dangerous, as web crawlers could acciden-
tally delete all your content. That is why we used allowMethod() in our controller.

Because we’re only executing logic and redirecting to another action, this action has no template. You might want to
update your index template with links that allow users to delete articles:
<!-- File: src/Template/Articles/index.ctp (delete links added) -->

<h1>Articles</h1>
<p><?= $this->Html->link("Add Article", ['action' => 'add']) ?></p>
<table>
<tr>
<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>
<?= $this->Html->link($article->title, ['action' => 'view', $article->
˓→slug]) ?>

CMS Tutorial - Creating the Articles Controller 23


CakePHP Cookbook Documentation, Release 3.8

</td>
<td>
<?= $article->created->format(DATE_RFC850) ?>
</td>
<td>
<?= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?>
<?= $this->Form->postLink(
'Delete',
['action' => 'delete', $article->slug],
['confirm' => 'Are you sure?'])
?>
</td>
</tr>
<?php endforeach; ?>

</table>

Using View\Helper\FormHelper::postLink() will create a link that uses JavaScript to do a POST request
deleting our article.

Note: This view code also uses the FormHelper to prompt the user with a JavaScript confirmation dialog before
they attempt to delete an article.

With a basic articles management setup, we’ll create the basic actions for our Tags and Users tables.

24 Chapter 2. Quick Start Guide


CHAPTER 3

3.0 Migration Guide

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, we’ve also created a
console application to help you complete some of the time consuming mechanical changes. You can get the upgrade
tool from github26 .

26 https://github.com/cakephp/upgrade

25
CakePHP Cookbook Documentation, Release 3.8

Application Directory Layout

The application directory layout has changed and now follows PSR-427 . You should use the app skeleton28 project as
a reference point when updating your application.

CakePHP should be installed with Composer

Since CakePHP can no longer be installed via PEAR, or in a shared directory, those options are no longer supported.
Instead you should use Composer29 to install CakePHP into your application.

Namespaces

All of CakePHP’s 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 application’s 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 http://www.php-fig.org/psr/psr-4/
28 https://github.com/cakephp/app
29 http://getcomposer.org

26 Chapter 3. 3.0 Migration Guide


CakePHP Cookbook Documentation, Release 3.8

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 op-
tion.
• 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 configu-
ration 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().
• All CacheEngine subclasses now implement a config() method.
• Cake\Cache\Cache::readMany(), Cake\Cache\Cache::deleteMany(), and
Cake\Cache\Cache::writeMany() were added.

New ORM 27
CakePHP Cookbook Documentation, Release 3.8

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 dura-
tion is taken from the cache engine’s 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 renamed to Cake\Core\Configure\ConfigEngineInterface
• Cake\Core\Configure::consume() was added.
• Cake\Core\Configure::load() now expects the file name without extension suffix as this can be de-
rived 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.

28 Chapter 3. 3.0 Migration Guide


CakePHP Cookbook Documentation, Release 3.8

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 CakePHP’s 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 changed. It now takes an instance of Cake\Console\ConsoleIo.


• Shell::param() has been added as convenience access to the parameters.
Additionally all shell methods will be transformed to camel case when invoked. For example, if you had a
hello_world() method inside a shell and invoked it with bin/cake my_shell hello_world, you will
need to rename the method to helloWorld. There are no changes required in the way you invoke commands.

ConsoleOptionParser

• ConsoleOptionParser::merge() has been added to merge parsers.

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 didn’t provide any benefit over the file source itself and the online documenta-
tion/API30 .
30 https://api.cakephp.org/

Console 29
CakePHP Cookbook Documentation, Release 3.8

SchemaShell Removed

The SchemaShell was removed as it was never a complete database migration implementation and better tools such
as Phinx31 have emerged. It has been replaced by the CakePHP Migrations Plugin32 which acts as a wrapper between
CakePHP and Phinx33 .

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 Plugin34
• 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.

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().
31 https://phinx.org/
32 https://github.com/cakephp/migrations
33 https://phinx.org/
34 https://github.com/cakephp/bake

30 Chapter 3. 3.0 Migration Guide


CakePHP Cookbook Documentation, Release 3.8

• 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 Cake’s 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.

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.

Routing 31
CakePHP Cookbook Documentation, Release 3.8

• 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.
• Router::parseExtensions() has been removed. Use Cake\Routing\Router::extensions()
instead. This method must be called before routes are connected. It won’t modify existing routes.
• Router::setExtensions() has been removed. Use Cake\Routing\Router::extensions() in-
stead.
• 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 = []) See
Cake\Routing\Route::match() for information on the new signature.

Dispatcher Filters Configuration Changed

Dispatcher filters are no longer added to your application using Configure. You now append them with
Cake\Routing\DispatcherFactory. This means if your application used Dispatcher.filters, you
should now use Cake\Routing\DispatcherFactory::add().
In addition to configuration changes, dispatcher filters have had some conventions updated, and features added. See
the Dispatcher Filters documentation for more information.

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.

32 Chapter 3. 3.0 Migration Guide


CakePHP Cookbook Documentation, Release 3.8

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 non-static.
• 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 doesn’t 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 names-
pace.
• 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.
• The session cookie timeout is automatically updated in tandem with the timeout in the session data.
• The path for session cookie now defaults to app’s 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.

Network 33
CakePHP Cookbook Documentation, Release 3.8

• 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 authen-
tication systems like OAuth, and file uploads. It uses PHP’s 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 trans-
port 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.
• Controller::httpCodes() has been removed, use Cake\Network\Response::httpCodes()
instead.
• Controller::disableCache() has been removed, use Cake\Network\Response::disableCache()
instead.
• 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.

34 Chapter 3. 3.0 Migration Guide


CakePHP Cookbook Documentation, Release 3.8

• 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 argu-
ment $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.

Component

• The _Collection property is now _registry. It contains an instance of


Cake\Controller\ComponentRegistry now.
• 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 35
CakePHP Cookbook Documentation, Release 3.8

Controller\Components

CookieComponent

• Uses Cake\Network\Request::cookie() to read cookie data, this eases testing, and allows for Con-
trollerTestCase to set cookies.
• Cookies encrypted in previous versions of CakePHP using the cipher() method are now un-readable 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 app’s 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 AuthComponent’s documentation for more info.
• BlowfishAuthenticate class has been removed. Just use FormAuthenticate
• BlowfishPasswordHasher class has been removed. Use DefaultPasswordHasher instead.
• The loggedIn() method has been removed. Use user() instead.
• Configuration options are no longer set as public properties.
• The methods allow() and deny() no longer accept “var args”. All method names need to be passed as first
argument, either as string or array of strings.
• The method login() has been removed and replaced by setUser() instead. To login a user you now have
to call identify() which returns user info upon successful identification and then use setUser() to save
the info to session for persistence across requests.
• BaseAuthenticate::_password() has been removed. Use a PasswordHasher class instead.
• BaseAuthenticate::logout() has been removed.
• AuthComponent now 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.
ACL related classes were moved to a separate plugin. Password hashers, Authentication and Authorization providers
where moved to the \Cake\Auth namespace. You are required to move your providers and hashers to the
App\Auth namespace as well.

36 Chapter 3. 3.0 Migration Guide


CakePHP Cookbook Documentation, Release 3.8

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() in-
stead.
• RequestHandler::getReferer() was removed, use Cake\Network\Request::referer() in-
stead.
• RequestHandler::getClientIP() was removed, use Cake\Network\Request::clientIp()
instead.
• RequestHandler::getAjaxVersion() was removed.
• RequestHandler::mapType() was removed, use Cake\Network\Response::mapType() in-
stead.
• Configuration options are no longer set as public properties.

SecurityComponent

• The following methods and their related properties have been removed from Security compo-
nent: requirePost(), requireGet(), requirePut(), requireDelete(). Use the
Cake\Network\Request::allowMethod() instead.
• SecurityComponent::$disabledFields() has been removed, use
SecurityComponent::$unlockedFields().
• The CSRF related features in SecurityComponent have been extracted and moved into a separate CsrfCompo-
nent. 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.

Model 37
CakePHP Cookbook Documentation, Release 3.8

• 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
• Database\ConnectionManager::config() has been added and is now the only way to configure
connections.
• Database\ConnectionManager::get() has been added. It replaces getDataSource().
• Database\ConnectionManager::configured() has been added. It and config() replace
sourceList() & enumConnectionObjects() with a more standard and consistent API.
• ConnectionManager::create() has been removed. It can be replaced by config($name,
$config) and get($name).

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

38 Chapter 3. 3.0 Migration Guide


CakePHP Cookbook Documentation, Release 3.8

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()
• assertPattern() in favor of assertRegExp()
• assertNoPattern() in favor of assertNotRegExp()
• assertReference() if favor of assertSame()
• assertIsA() in favor of assertInstanceOf()
Note that some methods have switched the argument order, e.g. assertEqual($is, $expected) should now
be assertEquals($expected, $is).
The following assertion methods have been deprecated and will be removed in the future:
• assertWithinMargin() in favor of assertWithinRange()
• assertTags() in favor of assertHtml()
Both method replacements also switched the argument order for a consistent assert method API with $expected as
first argument.
The following assertion methods have been added:
• assertNotWithinRange() as counter part to assertWithinRange()

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 Folders Renamed

The folders containing view files now go under src/Template instead of src/View. This was done to separate the view
files from files containing php classes (eg. Helpers, View classes).
The following View folders have been renamed to avoid naming collisions with controller names:
• Layouts is now Layout

View 39
CakePHP Cookbook Documentation, Release 3.8

• Elements is now Element


• Errors is now Error
• Emails is now Email (same for Email inside Layout)

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 removed from $options argument of Cake\View\View::element(). Spec-
ify the element name as SomePlugin.element_name instead.
• View::getVar() has been removed, use Cake\View\View::get() instead.
• View::$ext has been removed and instead a protected property View::$_ext has been added.
• View::addScript() has been removed. Use Using View Blocks instead.
• The base, webroot, here, data, action, and params magic properties have been removed. You should
access all of these properties on $this->request instead.
• View::start() no longer appends to an existing block. Instead it will overwrite the block content when end
is called. If you need to combine block contents you should fetch the block content when calling start a second
time, or use the capturing mode of append().
• View::prepend() no longer has a capturing mode.
• View::startIfEmpty() has been removed. Now that start() always overwrites startIfEmpty serves no
purpose.
• The View::$Helpers property has been removed and replaced with _helpers. If you need to load helpers
at runtime you should use $this->addHelper() in your view files.
• View will now raise Cake\View\Exception\MissingTemplateException when templates are
missing instead of MissingViewException.

ViewBlock

• ViewBlock::append() has been removed, use Cake\ViewViewBlock::concat() instead. How-


ever, 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.

40 Chapter 3. 3.0 Migration Guide


CakePHP Cookbook Documentation, Release 3.8

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()
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()

View\Helper 41
CakePHP Cookbook Documentation, Release 3.8

• 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 data-
sources.
• FormHelper features an extensible widget system that allows you to create new custom input widgets and aug-
ment 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.
• 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.

42 Chapter 3. 3.0 Migration Guide


CakePHP Cookbook Documentation, Release 3.8

• 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 re-
moved. 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 Bootstrap35 and Foundation36 .
• 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.
• HtmlHelper::meta() now requires $type to be a string. Additional options can further on be passed as
$options.
• HtmlHelper::nestedList() now requires $options to be an array. The forth argument for the tag
type has been removed and included in the $options array.
• The $confirmMessage argument of Cake\View\Helper\HtmlHelper::link() has been removed.
You should now use key confirm in $options to specify the message.

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 ar-
ray now.
35 http://getbootstrap.com/
36 http://foundation.zurb.com/

View\Helper 43
CakePHP Cookbook Documentation, Release 3.8

• The %page% style placeholders have been removed from Cake\View\Helper\PaginatorHelper::counter().


Use {{page}} style placeholders instead.
• 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 Tem-
plates 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.

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 of-
ten became an impediment. It’s 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 Includes37 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 Varnish38 or Squid39 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.
37 http://en.wikipedia.org/wiki/Edge_Side_Includes
38 http://varnish-cache.org
39 http://squid-cache.org

44 Chapter 3. 3.0 Migration Guide


CakePHP Cookbook Documentation, Release 3.8

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');

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::setLocale('en_US');

• The methods below have been moved:


– From Cake\I18n\Multibyte::utf8() to Cake\Utility\Text::utf8()
– From Cake\I18n\Multibyte::ascii() to Cake\Utility\Text::ascii()
– From Cake\I18n\Multibyte::checkMultibyte() to Cake\Utility\Text::isMultibyte()
• 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.

L10n 45
CakePHP Cookbook Documentation, Release 3.8

Testing

• The TestShell has been removed. CakePHP, the application skeleton and newly baked plugins all use
phpunit to run tests.
• 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 IDE’s and other automated tooling.
If you find yourself in need of a way to run tests from a browser you should checkout VisualPHPUnit40 . 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.

Folder & File

The folder and file classes have been renamed:


• Cake\Utility\File renamed to Cake\Filesystem\File
• Cake\Utility\Folder renamed to Cake\Filesystem\Folder

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 transliter-
ations 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'
]);

40 https://github.com/NSinopoli/VisualPHPUnit

46 Chapter 3. 3.0 Migration Guide


CakePHP Cookbook Documentation, Release 3.8

// You should use


Inflector::rules('transliteration', [
'ä' => 'ae',
'æ' => 'ae',
'å' => 'aa'
]);

• 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. With the removal of
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 PHP’s password_hash() and password_verify() to generate and
verify blowfish hashes. The compability library ircmaxell/password-compat41 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 imple-
mentation. You should set the implementation to mcrypt when upgrading.

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.
41 https://packagist.org/packages/ircmaxell/password-compat

Utility 47
CakePHP Cookbook Documentation, Release 3.8

• 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 nam-
ing.
• 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);

Can be migrated by rewriting it to:

(new Time($date))->aMethod();

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().

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.

48 Chapter 3. 3.0 Migration Guide


CHAPTER 4

Tutorials & Examples

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 CakePackages42 and the Bakery43 for exist-
ing applications and components.

Content Management Tutorial

This tutorial will walk you through the creation of a simple CMS application. To start with, we’ll be installing
CakePHP, creating our database, and building simple article management.
Here’s what you’ll need:
1. A database server. We’re going to be using MySQL server in this tutorial. You’ll need to know enough about
SQL in order to create a database, and run SQL snippets from the tutorial. CakePHP will handle building all the
queries your application needs. Since we’re 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.6.0 (CLI) or higher. Your webserver’s PHP version must also be of 5.6.0
or higher, and should be the same version your command line interface (CLI) PHP is.
42 https://plugins.cakephp.org/
43 https://bakery.cakephp.org/

49
CakePHP Cookbook Documentation, Release 3.8

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, you’ll need to download and install Composer if you haven’t done so already.
If you have cURL installed, it’s as easy as running the following:

curl -s https://getcomposer.org/installer | php

Or, you can download composer.phar from the Composer website44 .


Then simply type the following line in your terminal from your installation directory to install the CakePHP application
skeleton in the cms directory of the current working directory:

php composer.phar create-project --prefer-dist cakephp/app cms

If you downloaded and ran the Composer Windows Installer45 , 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 cms

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 don’t 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:

/cms
/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 CakePHP’s directory structure works: check out the CakePHP
Folder Structure section.
If you get lost during this tutorial, you can see the finished result on GitHub46 .

44 https://getcomposer.org/download/
45 https://getcomposer.org/Composer-Setup.exe
46 https://github.com/cakephp/cms-tutorial

50 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

Checking our Installation

We can quickly check that our installation is correct, by checking the default home page. Before you can do that,
you’ll need to start the development server:

cd /path/to/our/app

bin/cake server

Note: For Windows, the command needs to be bin\cake server (note the backslash).

This will start PHP’s 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 green chef hats other than CakePHP being able to connect to your
database. If not, you may need to install additional PHP extensions, or set directory permissions.
Next, we will build our Database and create our first model.

CMS Tutorial - Creating the Database

Now that we have CakePHP installed, let’s set up the database for our CMS application. If you haven’t already done
so, create an empty database for use in this tutorial, with a name of your choice, e.g. cake_cms. You can execute the
following SQL to create the necessary tables:

USE cake_cms;

CREATE TABLE users (


id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
created DATETIME,
modified DATETIME
);

CREATE TABLE articles (


id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
slug VARCHAR(191) NOT NULL,
body TEXT,
published BOOLEAN DEFAULT FALSE,
created DATETIME,
modified DATETIME,
UNIQUE KEY (slug),
FOREIGN KEY user_key (user_id) REFERENCES users(id)
) CHARSET=utf8mb4;

CREATE TABLE tags (


id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(191),
created DATETIME,
modified DATETIME,
UNIQUE KEY (title)
) CHARSET=utf8mb4;

CMS Tutorial - Creating the Database 51


CakePHP Cookbook Documentation, Release 3.8

CREATE TABLE articles_tags (


article_id INT NOT NULL,
tag_id INT NOT NULL,
PRIMARY KEY (article_id, tag_id),
FOREIGN KEY tag_key(tag_id) REFERENCES tags(id),
FOREIGN KEY article_key(article_id) REFERENCES articles(id)
);

INSERT INTO users (email, password, created, modified)


VALUES
('[email protected]', 'secret', NOW(), NOW());

INSERT INTO articles (user_id, title, slug, body, published, created, modified)
VALUES
(1, 'First Post', 'first-post', 'This is the first post.', 1, now(), now());

You may have noticed that the articles_tags table used a composite primary key. CakePHP supports composite
primary keys almost everywhere allowing you to have simpler schemas that don’t require additional id columns.
The table and column names we used were not arbitrary. By using CakePHP’s naming conventions, we can leverage
CakePHP more effectively and avoid needing to configure the framework. While CakePHP is flexible enough to
accommodate almost any database schema, adhering to the conventions will save you time as you can leverage the
convention based defaults CakePHP provides.

Database Configuration

Next, let’s tell CakePHP where our database is and how to connect to it. Replace the values in the Datasources.
default array in your config/app.php file with those that apply to your setup. A sample completed configuration
array might look something like the following:

<?php
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_cms',
'encoding' => 'utf8mb4',
'timezone' => 'UTC',
'cacheMetadata' => true,
],
],
// More configuration below.
];

Once you’ve saved your config/app.php file, you should see that ‘CakePHP is able to connect to the database’ section
have a green chef hat.

Note: A copy of CakePHP’s default configuration file is found in config/app.default.php.

52 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

Creating our First Model

Models are the heart of a CakePHP applications. They enable us to read and modify our data. They allow us to build
relations between our data, validate data, and apply application rules. Models build the foundations necessary to build
our controller actions and templates.
CakePHP’s models are composed of Table and Entity objects. Table objects provide access to the collection
of entities stored in a specific table. They are stored in src/Model/Table. The file we’ll be creating will be saved to
src/Model/Table/ArticlesTable.php. The completed file should look like this:
<?php
// 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');
}
}

We’ve attached the Timestamp behavior which will automatically populate the created and modified columns of
our table. By naming our Table object ArticlesTable, CakePHP can use naming conventions to know that our
model uses the articles table. CakePHP also uses conventions to know that the id column is our table’s primary
key.

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 Arti-
cleTable.php), CakePHP will not recognize any of your settings and will use the generated model instead.

We’ll also create an Entity class for our Articles. Entities represent a single record in the database, and provide row
level behavior for our data. Our entity will be saved to src/Model/Entity/Article.php. The completed file should look
like this:
<?php
// src/Model/Entity/Article.php
namespace App\Model\Entity;

use Cake\ORM\Entity;

class Article extends Entity


{
protected $_accessible = [
'*' => true,
'id' => false,
'slug' => false,
];
}

Our entity is quite slim right now, and we’ve only setup the _accessible property which controls how properties
can be modified by Mass Assignment.
We can’t do much with our models right now, so next we’ll create our first Controller and Template to allow us to
interact with our model.

CMS Tutorial - Creating the Database 53


CakePHP Cookbook Documentation, Release 3.8

CMS Tutorial - Creating the Articles Controller

With our model created, we need a controller for our articles. Controllers in CakePHP handle HTTP requests and
execute business logic contained in model methods, to prepare the response. We’ll place this new controller in a file
called ArticlesController.php inside the src/Controller directory. Here’s what the basic controller should look like:

<?php
// src/Controller/ArticlesController.php

namespace App\Controller;

class ArticlesController extends AppController


{
}

Now, let’s add an action to our controller. Actions are controller methods that have routes connected to them. For exam-
ple, when a user requests www.example.com/articles/index (which is also the same as www.example.com/articles),
CakePHP will call the index method of your ArticlesController. This method should query the model layer,
and prepare a response by rendering a Template in the View. The code for that action would look like this:

<?php
// src/Controller/ArticlesController.php

namespace App\Controller;

class ArticlesController extends AppController


{
public function index()
{
$this->loadComponent('Paginator');
$articles = $this->Paginator->paginate($this->Articles->find());
$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. You may be tempted to name your controllers and actions in
a way that allows you to obtain specific URLs. Resist that temptation. Instead, follow the CakePHP Conventions
creating readable, meaningful action names. You can then use Routing to connect the URLs you want to the actions
you’ve created.
Our controller action is very simple. It fetches a paginated set of articles from the database, using the Articles Model
that is automatically loaded via naming conventions. It then uses set() to pass the articles into the Template (which
we’ll create soon). CakePHP will automatically render the template after our controller action completes.

Create the Article List Template

Now that we have our controller pulling data from the model, and preparing our view context, let’s create a view
template for our index action.
CakePHP view templates are presentation-flavored PHP code that is inserted inside the application’s layout. While
we’ll be creating HTML here, Views can also generate JSON, CSV or even binary files like PDFs.
A layout is presentation code that is wrapped around a view. Layout files contain common site elements like headers,
footers and navigation elements. Your application can have multiple layouts, and you can switch between them, but

54 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

for now, let’s just use the default layout.


CakePHP’s template files are stored in src/Template inside a folder named after the controller they correspond to. So
we’ll have to create a folder named ‘Articles’ in this case. Add the following code to your application:

<!-- File: src/Template/Articles/index.ctp -->

<h1>Articles</h1>
<table>
<tr>
<th>Title</th>
<th>Created</th>
</tr>

<!-- Here is where we iterate through our $articles query object, printing out
˓→ article info -->

<?php foreach ($articles as $article): ?>


<tr>
<td>
<?= $this->Html->link($article->title, ['action' => 'view', $article->
˓→slug]) ?>

</td>
<td>
<?= $article->created->format(DATE_RFC850) ?>
</td>
</tr>
<?php endforeach; ?>
</table>

In the last section we assigned the ‘articles’ variable to the view using set(). Variables passed into the view are
available in the view templates as local variables which we used in the above code.
You might have noticed the use of an object called $this->Html. This is an instance of the CakePHP HtmlHelper.
CakePHP comes with a set of view helpers that make tasks like creating links, forms, and pagination buttons easy.
You can learn more about Helpers in their chapter, but what’s important to note here is that the link() method will
generate an HTML link with the given link text (the first parameter) and URL (the second parameter).
When specifying URLs in CakePHP, it is recommended that you use arrays or named routes. These syntaxes allow
you to leverage the reverse routing features CakePHP offers.
At this point, you should be able to point your browser to http://localhost:8765/articles/index. You should see your
list view, correctly formatted with the title and table listing of the articles.

Create the View Action

If you were to click one of the ‘view’ links in our Articles list page, you’d see an error page saying that action hasn’t
been implemented. Lets fix that now:

// Add to existing src/Controller/ArticlesController.php file

public function view($slug = null)


{
$article = $this->Articles->findBySlug($slug)->firstOrFail();
$this->set(compact('article'));
}

CMS Tutorial - Creating the Articles Controller 55


CakePHP Cookbook Documentation, Release 3.8

While this is a simple action, we’ve used some powerful CakePHP features. We start our action off by using
findBySlug() which is a Dynamic Finder. This method allows us to create a basic query that finds articles by
a given slug. We then use firstOrFail() to either fetch the first record, or throw a NotFoundException.
Our action takes a $slug parameter, but where does that parameter come from? If a user requests /articles/
view/first-post, then the value ‘first-post’ is passed as $slug by CakePHP’s routing and dispatching layers.
If we reload our browser with our new action saved, we’d see another CakePHP error page telling us we’re missing a
view template; let’s fix that.

Create the View Template

Let’s 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>
<p><?= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?></p>

You can verify that this is working by trying the links at /articles/index or manually requesting an article by
accessing URLs like /articles/view/first-post.

Adding Articles

With the basic read views created, we need to make it possible for new articles to be created. Start by creating an
add() action in the ArticlesController. Our controller should now look like:
// src/Controller/ArticlesController.php

namespace App\Controller;

use App\Controller\AppController;

class ArticlesController extends AppController


{

public function initialize()


{
parent::initialize();

$this->loadComponent('Paginator');
$this->loadComponent('Flash'); // Include the FlashComponent
}

public function index()


{
$articles = $this->Paginator->paginate($this->Articles->find());
$this->set(compact('articles'));
}

public function view($slug)


{
$article = $this->Articles->findBySlug($slug)->firstOrFail();
$this->set(compact('article'));
}

56 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

public function add()


{
$article = $this->Articles->newEntity();
if ($this->request->is('post')) {
$article = $this->Articles->patchEntity($article, $this->request->
˓→getData());

// Hardcoding the user_id is temporary, and will be removed later


// when we build authentication out.
$article->user_id = 1;

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);
}
}

Note: You need to include the Flash component in any controller where you will use it. Often it makes sense to
include it in your AppController.

Here’s 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 doesn’t 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 information regarding the request that was just received. We use the
Cake\Http\ServerRequest::is() method to check that the request is a HTTP POST request.
Our POST data is available in $this->request->getData(). You can use the pr() or debug() functions
to print it out if you want to see what it looks like. To save our data, we first ‘marshal’ the POST data into an Article
Entity. The Entity is then persisted using the ArticlesTable we created earlier.
After saving our new article we use FlashComponent’s success() method to set a message into the ses-
sion. The success method is provided using PHP’s magic method features47 . Flash messages will be dis-
played on the next page after redirecting. In our layout we have <?= $this->Flash->render() ?> which
displays flash messages and clears the corresponding session variable. Finally, after saving is complete, we
use Cake\Controller\Controller::redirect to send the user back to the articles list. 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 API48 to see the formats in which you can
specify a URL for various CakePHP functions.

Create Add Template

Here’s our add view template:


47 http://php.net/manual/en/language.oop5.overloading.php#object.call
48 https://api.cakephp.org

CMS Tutorial - Creating the Articles Controller 57


CakePHP Cookbook Documentation, Release 3.8

<!-- File: src/Template/Articles/add.ctp -->

<h1>Add Article</h1>
<?php
echo $this->Form->create($article);
// Hard code the user for now.
echo $this->Form->control('user_id', ['type' => 'hidden', 'value' => 1]);
echo $this->Form->control('title');
echo $this->Form->control('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. Here’s the HTML that
$this->Form->create() generates:

<form method="post" action="/articles/add">

Because we called create() without a URL option, FormHelper assumes we want the form to submit back to
the current action.
The $this->Form->control() method is used to create form elements of the same name. The first parameter
tells CakePHP which field they correspond to, and the second parameter allows you to specify a wide array of options
- in this case, the number of rows for the textarea. There’s a bit of introspection and conventions used here. The
control() will output different form elements based on the model field specified, and use inflection to generate
the label text. You can customize the label, the input or any other aspect of the form controls using options. The
$this->Form->end() call closes the form.
Now let’s 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']) ?>

Adding Simple Slug Generation

If we were to save an Article right now, saving would fail as we are not creating a slug attribute, and the column is
NOT NULL. Slug values are typically a URL-safe version of an article’s title. We can use the beforeSave() callback
of the ORM to populate our slug:

// in src/Model/Table/ArticlesTable.php
namespace App\Model\Table;

use Cake\ORM\Table;
// the Text class
use Cake\Utility\Text;

// Add the following method.

public function beforeSave($event, $entity, $options)


{
if ($entity->isNew() && !$entity->slug) {
$sluggedTitle = Text::slug($entity->title);
// trim slug to maximum length defined in schema
$entity->slug = substr($sluggedTitle, 0, 191);
}
}

58 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

This code is simple, and doesn’t take into account duplicate slugs. But we’ll fix that later on.

Add Edit Action

Our application can now save articles, but we can’t edit them. Lets rectify that now. Add the following action to your
ArticlesController:

// in src/Controller/ArticlesController.php

// Add the following method.

public function edit($slug)


{
$article = $this->Articles->findBySlug($slug)->firstOrFail();
if ($this->request->is(['post', 'put'])) {
$this->Articles->patchEntity($article, $this->request->getData());
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.'));
}

$this->set('article', $article);
}

This action first ensures that the user has tried to access an existing record. If they haven’t passed in an $slug
parameter, or the article does not exist, a NotFoundException will be thrown, and the CakePHP ErrorHandler
will render the appropriate error page.
Next the action checks whether the request is either a POST or a PUT request. If it is, then we use the POST/PUT
data to update our article entity by using the patchEntity() method. Finally, we call save() set the appropriate
flash message and either redirect or display validation errors.

Create Edit Template

The edit template should look like this:

<!-- File: src/Template/Articles/edit.ctp -->

<h1>Edit Article</h1>
<?php
echo $this->Form->create($article);
echo $this->Form->control('user_id', ['type' => 'hidden']);
echo $this->Form->control('title');
echo $this->Form->control('body', ['rows' => '3']);
echo $this->Form->button(__('Save Article'));
echo $this->Form->end();
?>

This template outputs the edit form (with the values populated), along with any necessary validation error messages.
You can now update your index view with links to edit specific articles:

<!-- File: src/Template/Articles/index.ctp (edit links added) -->

CMS Tutorial - Creating the Articles Controller 59


CakePHP Cookbook Documentation, Release 3.8

<h1>Articles</h1>
<p><?= $this->Html->link("Add Article", ['action' => 'add']) ?></p>
<table>
<tr>
<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>
<?= $this->Html->link($article->title, ['action' => 'view', $article->
˓→slug]) ?>

</td>
<td>
<?= $article->created->format(DATE_RFC850) ?>
</td>
<td>
<?= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?>
</td>
</tr>
<?php endforeach; ?>

</table>

Update Validation Rules for Articles

Up until this point our Articles had no input validation done. Lets fix that by using a validator:

// src/Model/Table/ArticlesTable.php

// add this use statement right below the namespace declaration to import
// the Validator class
use Cake\Validation\Validator;

// Add the following method.


public function validationDefault(Validator $validator)
{
$validator
->allowEmptyString('title', false)
->minLength('title', 10)
->maxLength('title', 255)

->allowEmptyString('body', false)
->minLength('body', 10);

return $validator;
}

The validationDefault() method tells CakePHP how to validate your data when the save() method is called.
Here, we’ve specified that both the title, and body fields must not be empty, and have certain length constraints.
CakePHP’s validation engine is powerful and flexible. It provides a suite of frequently used rules for tasks like email

60 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

addresses, IP addresses etc. and the 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 we’ve used the Cake\View\Helper\FormHelper::control() method of the FormHelper to
create our form elements, our validation error messages will be shown automatically.

Add Delete Action

Next, let’s make a way for users to delete articles. Start with a delete() action in the ArticlesController:
// src/Controller/ArticlesController.php

public function delete($slug)


{
$this->request->allowMethod(['post', 'delete']);

$article = $this->Articles->findBySlug($slug)->firstOrFail();
if ($this->Articles->delete($article)) {
$this->Flash->success(__('The {0} article has been deleted.', $article->
˓→title));

return $this->redirect(['action' => 'index']);


}
}

This logic deletes the article specified by $slug, and uses $this->Flash->success() to show the user a
confirmation message after redirecting them to /articles. If the user attempts to delete an article using a GET
request, allowMethod() will throw an exception. Uncaught exceptions are captured by CakePHP’s exception
handler, and a nice error page is displayed. There are many built-in Exceptions that can be used to indicate the various
HTTP errors your application might need to generate.

Warning: Allowing content to be deleted using GET requests is very dangerous, as web crawlers could acciden-
tally delete all your content. That is why we used allowMethod() in our controller.

Because we’re only executing logic and redirecting to another action, this action has no template. You might want to
update your index template with links that allow users to delete articles:
<!-- File: src/Template/Articles/index.ctp (delete links added) -->

<h1>Articles</h1>
<p><?= $this->Html->link("Add Article", ['action' => 'add']) ?></p>
<table>
<tr>
<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>
<?= $this->Html->link($article->title, ['action' => 'view', $article->
˓→slug]) ?>

CMS Tutorial - Creating the Articles Controller 61


CakePHP Cookbook Documentation, Release 3.8

</td>
<td>
<?= $article->created->format(DATE_RFC850) ?>
</td>
<td>
<?= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?>
<?= $this->Form->postLink(
'Delete',
['action' => 'delete', $article->slug],
['confirm' => 'Are you sure?'])
?>
</td>
</tr>
<?php endforeach; ?>

</table>

Using View\Helper\FormHelper::postLink() will create a link that uses JavaScript to do a POST request
deleting our article.

Note: This view code also uses the FormHelper to prompt the user with a JavaScript confirmation dialog before
they attempt to delete an article.

With a basic articles management setup, we’ll create the basic actions for our Tags and Users tables.

CMS Tutorial - Tags and Users

With the basic article creation functionality built, we need to enable multiple authors to work in our CMS. Previously,
we built all the models, views and controllers by hand. This time around we’re going to use Bake Console to create our
skeleton code. Bake is a powerful code generation CLI (Command Line Interface) tool that leverages the conventions
CakePHP uses to create skeleton CRUD (Create, Read, Update, Delete) applications very efficiently. We’re going to
use bake to build our users code:

cd /path/to/our/app

bin/cake bake model users


bin/cake bake controller users
bin/cake bake template users

These 3 commands will generate:


• The Table, Entity, Fixture files.
• The Controller
• The CRUD templates.
• Test cases for each generated class.
Bake will also use the CakePHP conventions to infer the associations, and validation your models have.

62 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

Adding Tagging to Articles

With multiple users able to access our small CMS it would be nice to have a way to categorize our content. We’ll use
tags and tagging to allow users to create free-form categories and labels for their content. Again, we’ll use bake to
quickly generate some skeleton code for our application:

# Generate all the code at once.


bin/cake bake all tags

Once you have the scaffold code created, create a few sample tags by going to http://localhost:8765/tags/add.
Now that we have a Tags table, we can create an association between Articles and Tags. We can do so by adding the
following to the initialize method on the ArticlesTable:

public function initialize(array $config)


{
$this->addBehavior('Timestamp');
$this->belongsToMany('Tags'); // Add this line
}

This association will work with this simple definition because we followed CakePHP conventions when creating our
tables. For more information, read Associations - Linking Tables Together.

Updating Articles to Enable Tagging

Now that our application has tags, we need to enable users to tag their articles. First, update the add action to look
like:

// in src/Controller/ArticlesController.php

namespace App\Controller;

use App\Controller\AppController;

class ArticlesController extends AppController


{
public function add()
{
$article = $this->Articles->newEntity();
if ($this->request->is('post')) {
$article = $this->Articles->patchEntity($article, $this->request->
˓→getData());

// Hardcoding the user_id is temporary, and will be removed later


// when we build authentication out.
$article->user_id = 1;

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.'));
}
// Get a list of tags.
$tags = $this->Articles->Tags->find('list');

// Set tags to the view context

CMS Tutorial - Tags and Users 63


CakePHP Cookbook Documentation, Release 3.8

$this->set('tags', $tags);

$this->set('article', $article);
}

// Other actions
}

The added lines load a list of tags as an associative array of id => title. This format will let us create a new tag
input in our template. Add the following to the PHP block of controls in src/Template/Articles/add.ctp:
echo $this->Form->control('tags._ids', ['options' => $tags]);

This will render a multiple select element that uses the $tags variable to generate the select box options. You should
now create a couple new articles that have tags, as in the following section we’ll be adding the ability to find articles
by tags.
You should also update the edit method to allow adding or editing tags. The edit method should now look like:
public function edit($slug)
{
$article = $this->Articles
->findBySlug($slug)
->contain('Tags') // load associated Tags
->firstOrFail();
if ($this->request->is(['post', 'put'])) {
$this->Articles->patchEntity($article, $this->request->getData());
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.'));
}

// Get a list of tags.


$tags = $this->Articles->Tags->find('list');

// Set tags to the view context


$this->set('tags', $tags);

$this->set('article', $article);
}

Remember to add the new tags multiple select control we added to the add.ctp template to the
src/Template/Articles/edit.ctp template as well.

Finding Articles By Tags

Once users have categorized their content, they will want to find that content by the tags they used. For this feature
we’ll implement a route, controller action, and finder method to search through articles by tag.
Ideally, we’d have a URL that looks like http://localhost:8765/articles/tagged/funny/cat/gifs. This would let us find
all the articles that have the ‘funny’, ‘cat’ or ‘gifs’ tags. Before we can implement this, we’ll add a new route. Your
config/routes.php should look like:
<?php
use Cake\Http\Middleware\CsrfProtectionMiddleware;

64 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

use Cake\Routing\RouteBuilder;
use Cake\Routing\Router;
use Cake\Routing\Route\DashedRoute;

Router::defaultRouteClass(DashedRoute::class);

Router::scope('/', function (RouteBuilder $routes) {


// Register scoped middleware for in scopes.
$routes->registerMiddleware('csrf', new CsrfProtectionMiddleware([
'httpOnly' => true
]));
$routes->applyMiddleware('csrf');
$routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
$routes->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']);
$routes->fallbacks(DashedRoute::class);
});
// Add this
// New route we're adding for our tagged action.
// The trailing `*` tells CakePHP that this action has
// passed parameters.
Router::scope('/articles', function (RouteBuilder $routes) {
$routes->connect('/tagged/*', ['controller' => 'Articles', 'action' => 'tags']);
});

The above defines a new ‘route’ which connects the /articles/tagged/ path, to ArticlesController::tags().
By defining routes, you can isolate how your URLs look, from how they are implemented. If we were to visit
http://localhost:8765/articles/tagged, we would see a helpful error page from CakePHP informing you that the con-
troller action does not exist. Let’s implement that missing method now. In src/Controller/ArticlesController.php
add the following:
public function tags()
{
// The 'pass' key is provided by CakePHP and contains all
// the passed URL path segments in the request.
$tags = $this->request->getParam('pass');

// Use the ArticlesTable to find tagged articles.


$articles = $this->Articles->find('tagged', [
'tags' => $tags
]);

// Pass variables into the view template context.


$this->set([
'articles' => $articles,
'tags' => $tags
]);
}

To access other parts of the request data, consult the Request section.
Since passed arguments are passed as method parameters, you could also write the action using PHP’s variadic argu-
ment:
public function tags(...$tags)
{
// Use the ArticlesTable to find tagged articles.
$articles = $this->Articles->find('tagged', [
'tags' => $tags

CMS Tutorial - Tags and Users 65


CakePHP Cookbook Documentation, Release 3.8

]);

// Pass variables into the view template context.


$this->set([
'articles' => $articles,
'tags' => $tags
]);
}

Creating the Finder Method

In CakePHP we like to keep our controller actions slim, and put most of our application’s logic in the model layer.
If you were to visit the /articles/tagged URL now you would see an error that the findTagged() method has not
been implemented yet, so let’s do that. In src/Model/Table/ArticlesTable.php add the following:

// add this use statement right below the namespace declaration to import
// the Query class
use Cake\ORM\Query;

// 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)
{
$columns = [
'Articles.id', 'Articles.user_id', 'Articles.title',
'Articles.body', 'Articles.published', 'Articles.created',
'Articles.slug',
];

$query = $query
->select($columns)
->distinct($columns);

if (empty($options['tags'])) {
// If there are no tags provided, find articles that have no tags.
$query->leftJoinWith('Tags')
->where(['Tags.title IS' => null]);
} else {
// Find articles that have one or more of the provided tags.
$query->innerJoinWith('Tags')
->where(['Tags.title IN' => $options['tags']]);
}

return $query->group(['Articles.id']);
}

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 we’ve leveraged the distinct() and leftJoin() methods which allow us
to find distinct articles that have a ‘matching’ tag.

66 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

Creating the View

Now if you visit the /articles/tagged URL again, CakePHP will show a new error letting you know that you have not
made a view file. Next, let’s build the view file for our tags() action. In src/Template/Articles/tags.ctp put the
following content:

<h1>
Articles tagged with
<?= $this->Text->toList(h($tags), 'or') ?>
</h1>

<section>
<?php foreach ($articles as $article): ?>
<article>
<!-- Use the HtmlHelper to create a link -->
<h4><?= $this->Html->link(
$article->title,
['controller' => 'Articles', 'action' => 'view', $article->slug]
) ?></h4>
<span><?= h($article->created) ?></span>
</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 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 $articles variables in our view template. 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 template scope as local variables.
You should now be able to visit the /articles/tagged/funny URL and see all the articles tagged with ‘funny’.

Improving the Tagging Experience

Right now, adding new tags is a cumbersome process, as authors need to pre-create all the tags they want to use. We
can improve the tag selection UI by using a comma separated text field. This will let us give a better experience to our
users, and use some more great features in the ORM.

Adding a Computed Field

Because we’ll want a simple way to access the formatted tags for an entity, we can add a virtual/computed field to the
entity. In src/Model/Entity/Article.php add the following:

// add this use statement right below the namespace declaration to import
// the Collection class
use Cake\Collection\Collection;

// Update the accessible property to contain `tag_string`


protected $_accessible = [
//other fields...
'tag_string' => true

CMS Tutorial - Tags and Users 67


CakePHP Cookbook Documentation, Release 3.8

];

protected function _getTagString()


{
if (isset($this->_properties['tag_string'])) {
return $this->_properties['tag_string'];
}
if (empty($this->tags)) {
return '';
}
$tags = new Collection($this->tags);
$str = $tags->reduce(function ($string, $tag) {
return $string . $tag->title . ', ';
}, '');
return trim($str, ', ');
}

This will let us access the $article->tag_string computed property. We’ll use this property in controls later
on.

Updating the Views

With the entity updated we can add a new control for our tags. In src/Template/Articles/add.ctp and
src/Template/Articles/edit.ctp, replace the existing tags._ids control with the following:
echo $this->Form->control('tag_string', ['type' => 'text']);

We’ll also need to update the article view template. In src/Template/Articles/view.ctp add the line as shown:
<!-- File: src/Template/Articles/view.ctp -->

<h1><?= h($article->title) ?></h1>


<p><?= h($article->body) ?></p>
// Add the following line
<p><b>Tags:</b> <?= h($article->tag_string) ?></p>

Persisting the Tag String

Now that we can view existing tags as a string, we’ll want to save that data as well. Because we marked
the tag_string as accessible, the ORM will copy that data from the request into our entity. We can use a
beforeSave() hook method to parse the tag string and find/build the related entities. Add the following to
src/Model/Table/ArticlesTable.php:
public function beforeSave($event, $entity, $options)
{
if ($entity->tag_string) {
$entity->tags = $this->_buildTags($entity->tag_string);
}

// Other code
}

protected function _buildTags($tagString)


{
// Trim tags

68 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

$newTags = array_map('trim', explode(',', $tagString));


// Remove all empty tags
$newTags = array_filter($newTags);
// Reduce duplicated tags
$newTags = array_unique($newTags);

$out = [];
$query = $this->Tags->find()
->where(['Tags.title IN' => $newTags]);

// Remove existing tags from the list of new tags.


foreach ($query->extract('title') as $existing) {
$index = array_search($existing, $newTags);
if ($index !== false) {
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;
}

If you now create or edit articles, you should be able to save tags as a comma separated list of tags, and have the tags
and linking records automatically created.
While this code is a bit more complicated than what we’ve 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.

Auto-populating the Tag String

Before we finish up, we’ll need a mechanism that will load the associated tags (if any) whenever we load an article.
In your src/Model/Table/ArticlesTable.php, change:

public function initialize(array $config)


{
$this->addBehavior('Timestamp');
// Change this line
$this->belongsToMany('Tags', [
'joinTable' => 'articles_tags',
'dependent' => true
]);
}

This will tell the Articles table model that there is a join table associated with tags. The ‘dependent’ option tells the
table to delete any associated records from the join table if an article is deleted.
Lastly, update the findBySlug() method calls in src/Controller/ArticlesController.php:

CMS Tutorial - Tags and Users 69


CakePHP Cookbook Documentation, Release 3.8

public function edit($slug)


{
// Update this line
$article = $this->Articles->findBySlug($slug)->contain(['Tags'])
->firstOrFail();
...
}

public function view($slug = null)


{
// Update this line
$article = $this->Articles->findBySlug($slug)->contain(['Tags'])
->firstOrFail();
$this->set(compact('article'));
}

The contain() method tells the ArticlesTable object to also populate the Tags association when the article is loaded.
Now when tag_string is called for an Article entity, there will be data present to create the string!
Next we’ll be adding authentication.

CMS Tutorial - Authentication

Now that our CMS has users, we should enable them to login, and apply some basic access control to the article
creation & editing experiences.

Adding Password Hashing

If you were to create/update a user at this point in time, you might notice that the passwords are stored in plain text.
This is really bad from a security point of view, so lets fix that.
This is also a good time to talk about the model layer in CakePHP. In CakePHP, we separate the methods that operate
on a collection of objects, and a single object into different classes. Methods that operate on the collection of entities
are put in the Table class, while features belonging to a single record are put on the Entity class.
For example, password hashing is done on the individual record, so we’ll implement this behavior on the entity object.
Because we want to hash the password each time it is set, we’ll use a mutator/setter method. CakePHP will call
convention based setter methods any time a property is set in one of your entities. Let’s add a setter for the password.
In src/Model/Entity/User.php add the following:
<?php
namespace App\Model\Entity;

use Cake\Auth\DefaultPasswordHasher; // Add this line


use Cake\ORM\Entity;

class User extends Entity


{

// Code from bake.

// Add this method


protected function _setPassword($value)
{
if (strlen($value)) {

70 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

$hasher = new DefaultPasswordHasher();

return $hasher->hash($value);
}
}
}

Now, point your browser to http://localhost:8765/users to see a list of users. You can edit the default user that was
created during Installation. If you change that user’s password, you should see a hashed password instead of the
original value on the list or view pages. CakePHP hashes passwords with bcrypt49 by default. You can also use SHA-1
or MD5 if you’re working with an existing database, but we recommend bcrypt for all new applications.

Note: Create a hashed password for at least one of the user accounts now! It will be needed in the next steps.

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 hook into the controller’s event
life-cycle and interact with your application that way. To get started, we’ll add the AuthComponent to our application.
We’ll want the create, update and delete methods to require authentication, so we’ll add AuthComponent in our
AppController:

// In src/Controller/AppController.php
namespace App\Controller;

use Cake\Controller\Controller;

class AppController extends Controller


{
public function initialize()
{
// Existing code

$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
],
// If unauthorized, return them to page they were just on
'unauthorizedRedirect' => $this->referer()
]);

// Allow the display action so our PagesController


// continues to work. Also enable the read only actions.
$this->Auth->allow(['display', 'view', 'index']);

49 http://codahale.com/how-to-safely-store-a-password/

CMS Tutorial - Authentication 71


CakePHP Cookbook Documentation, Release 3.8

We’ve just told CakePHP that we want to load the Auth component. We’ve customized the configuration of AuthCom-
ponent, as our users table uses email as the username. Now, if you go any protected URL, such as /articles/
add, you’ll be redirected to /users/login, which will show an error page as we have not written that code yet. So let’s
create the login action:

// 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.');
}
}

Create a new template src/Template/Users/login.ctp and add the following:

<h1>Login</h1>
<?= $this->Form->create() ?>
<?= $this->Form->control('email') ?>
<?= $this->Form->control('password') ?>
<?= $this->Form->button('Login') ?>
<?= $this->Form->end() ?>

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') block and
$this->Auth->allow() calls. Then go and edit the user, saving a new password for them. After saving a
new password for the user, make sure to uncomment the lines we just temporarily commented!

Try it out! Before logging in, visit /articles/add. Since this action is not allowed, you will be redirected to the
login page. After logging in successfully, CakePHP will automatically redirect you back to /articles/add.

Adding Logout

Now that people can log in, you’ll 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.');

72 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

return $this->redirect($this->Auth->logout());
}

This code adds the logout action to the list of actions that do not require authentication and implements the logout
method. Now you can visit /users/logout to log out. You should then be sent to the login page.

Enabling Registrations

If you aren’t logged in and you try to visit /users/add you will be redirected 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 the 'add' action to the allowed actions list.
$this->Auth->allow(['logout', 'add']);
}

The above tells AuthComponent that the add() action of the UsersController 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 won’t be building out user editing, viewing or listing in this tutorial, but that is an
exercise you can complete on your own.

Restricting Article Access

Now that users can log in, we’ll want to limit users to only edit articles that they created. We’ll do this us-
ing an ‘authorization’ adapter. Since our requirements are basic, we can use a controller hook method in our
ArticlesController. But before we do that, we’ll want to tell the AuthComponent how our application
is going to authorize actions. Update your AppController adding the following:
public function isAuthorized($user)
{
// By default deny access.
return false;
}

Next we’ll tell AuthComponent that we want to use controller hook methods for authorization. Your
AppController::initialize() method should now look like:
public function initialize()
{
// Existing code

$this->loadComponent('Flash');
$this->loadComponent('Auth', [
// Added this line
'authorize'=> 'Controller',
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],

CMS Tutorial - Authentication 73


CakePHP Cookbook Documentation, Release 3.8

'loginAction' => [
'controller' => 'Users',
'action' => 'login'
],
// If unauthorized, return them to page they were just on
'unauthorizedRedirect' => $this->referer()
]);

// Allow the display action so our pages controller


// continues to work. Also enable the read only actions.
$this->Auth->allow(['display', 'view', 'index']);
}

We’ll default to denying access, and incrementally grant access where it makes sense. First, we’ll add the authorization
logic for articles. In your ArticlesController add the following:

public function isAuthorized($user)


{
$action = $this->request->getParam('action');
// The add and tags actions are always allowed to logged in users.
if (in_array($action, ['add', 'tags'])) {
return true;
}

// All other actions require a slug.


$slug = $this->request->getParam('pass.0');
if (!$slug) {
return false;
}

// Check that the article belongs to the current user.


$article = $this->Articles->findBySlug($slug)->first();

return $article->user_id === $user['id'];


}

Now if you try to edit or delete an article that does not belong to you, you should be redirected back to the page you
came from. If no error message is displayed, add the following to your layout:

// In src/Template/Layout/default.ctp
<?= $this->Flash->render() ?>

Next you should add the tags action to the actions allowed for unauthenticated users, by adding the following to
initialize() in src/Controller/ArticlesController.php:

$this->Auth->allow(['tags']);

While the above is fairly simplistic it illustrates how you could build more complex logic that combines the current
user and request data to build flexible authorization logic.

Fixing the Add & Edit Actions

While we’ve blocked access to the edit action, we’re still open to users changing the user_id attribute of articles
during edit. We will solve these problems next. First up is the add action.
When creating articles, we want to fix the user_id to be the currently logged in user. Replace your add action with
the following:

74 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

// in src/Controller/ArticlesController.php

public function add()


{
$article = $this->Articles->newEntity();
if ($this->request->is('post')) {
$article = $this->Articles->patchEntity($article, $this->request->getData());

// Changed: Set the user_id from the session.


$article->user_id = $this->Auth->user('id');

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);
}

Next we’ll update the edit action. Replace the edit method with the following:

// in src/Controller/ArticlesController.php

public function edit($slug)


{
$article = $this->Articles
->findBySlug($slug)
->contain('Tags') // load associated Tags
->firstOrFail();

if ($this->request->is(['post', 'put'])) {
$this->Articles->patchEntity($article, $this->request->getData(), [
// Added: Disable modification of user_id.
'accessibleFields' => ['user_id' => false]
]);
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.'));
}
$this->set('article', $article);
}

Here we’re modifying which properties can be mass-assigned, via the options for patchEntity(). See the
Changing Accessible Fields section for more information. Remember to remove the user_id control from
src/Template/Articles/edit.ctp as we no longer need it.

Wrapping Up

We’ve built a simple CMS application that allows users to login, post articles, tag them, explore posted articles by
tag, and applied basic access control to articles. We’ve also added some nice UX improvements by leveraging the
FormHelper and ORM capabilities.
Thank you for taking the time to explore CakePHP. Next, you should learn more about the Database Access & ORM,
or you peruse the /topics.

CMS Tutorial - Authentication 75


CakePHP Cookbook Documentation, Release 3.8

Bookmarker Tutorial

This tutorial will walk you through the creation of a simple bookmarking application (bookmarker). To start with,
we’ll be installing CakePHP, creating our database, and using the tools CakePHP provides to get our application up
fast.
Here’s what you’ll need:
1. A database server. We’re going to be using MySQL server in this tutorial. You’ll need to know enough about
SQL in order to create a database: CakePHP will be taking the reins from there. Since we’re using MySQL,
also make sure that you have pdo_mysql enabled in PHP.
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.6.0 (CLI) or higher. Your webserver’s PHP version must also be of 5.6.0
or higher, and should best be the same version your command line interface (CLI) PHP version is of. If you’d like to
see the completed application, checkout cakephp/bookmarker50 . Let’s 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, you’ll need to download and install Composer if you haven’t done so already.
If you have cURL installed, it’s as easy as running the following:

curl -s https://getcomposer.org/installer | php

Or, you can download composer.phar from the Composer website51 .


Then simply type the following line in your terminal from your installation directory to install the CakePHP application
skeleton in the bookmarker directory:

php composer.phar create-project --prefer-dist cakephp/app bookmarker

If you downloaded and ran the Composer Windows Installer52 , 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 don’t 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

50 https://github.com/cakephp/bookmarker-tutorial
51 https://getcomposer.org/download/
52 https://getcomposer.org/Composer-Setup.exe

76 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

/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 CakePHP’s directory structure works: check out the CakePHP
Folder Structure section.

Checking our Installation

We can quickly check that our installation is correct, by checking the default home page. Before you can do that,
you’ll need to start the development server:

bin/cake server

Note: For Windows, the command needs to be bin\cake server (note the backslash).

This will start PHP’s 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.

Creating the Database

Next, let’s set up the database for our bookmarking application. If you haven’t already done so, create an empty
database for use in this tutorial, with a name of your choice, e.g. cake_bookmarks. You can execute the following
SQL to create the necessary tables:

CREATE TABLE users (


id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
created DATETIME,
modified DATETIME
);

CREATE TABLE bookmarks (


id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
title VARCHAR(50),
description TEXT,
url TEXT,
created DATETIME,

Bookmarker Tutorial 77
CakePHP Cookbook Documentation, Release 3.8

modified DATETIME,
FOREIGN KEY user_key (user_id) REFERENCES users(id)
);

CREATE TABLE tags (


id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
created DATETIME,
modified DATETIME,
UNIQUE KEY (title)
);

CREATE TABLE bookmarks_tags (


bookmark_id INT NOT NULL,
tag_id INT NOT NULL,
PRIMARY KEY (bookmark_id, tag_id),
FOREIGN KEY tag_key(tag_id) REFERENCES tags(id),
FOREIGN KEY bookmark_key(bookmark_id) REFERENCES bookmarks(id)
);

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 CakePHP’s 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, let’s 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 you’ve saved your config/app.php file, you should see that ‘CakePHP is able to connect to the database’ section
have a checkmark.

78 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

Note: A copy of CakePHP’s default configuration file is found in config/app.default.php.

Generating Scaffold Code

Because our database is following the CakePHP conventions, we can use the bake console application to quickly
generate a basic application. In your command line run the following commands:

// On Windows you'll need to use bin\cake instead.


bin/cake bake all users
bin/cake bake all bookmarks
bin/cake bake all tags

This will generate the controllers, models, views, their corresponding test cases, and fixtures for our users, bookmarks
and tags resources. If you’ve 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 application’s database tables. Once
you’re at the list of bookmarks, add a few users, bookmarks, and tags.

Adding Password Hashing

When you created your users (by visiting http://localhost:8765/users), you probably noticed that the passwords were
stored in plain text. This is pretty bad from a security point of view, so let’s get that fixed.
This is also a good time to talk about the model layer in CakePHP. In CakePHP, we separate the methods that operate
on a collection of objects, and a single object into different classes. Methods that operate on the collection of entities
are put in the Table class, while features belonging to a single record are put on the Entity class.
For example, password hashing is done on the individual record, so we’ll implement this behavior on the entity object.
Because, we want to hash the password each time it is set, we’ll use a mutator/setter method. CakePHP will call
convention based setter methods any time a property is set in one of your entities. Let’s add a setter for the password.
In src/Model/Entity/User.php add the following:

namespace App\Model\Entity;

use Cake\Auth\DefaultPasswordHasher; //include this line


use Cake\ORM\Entity;

class User extends Entity


{

// Code from bake.

protected function _setPassword($value)


{
$hasher = new DefaultPasswordHasher();
return $hasher->hash($value);
}
}

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 bcrypt53 by default. You can
also use sha1 or md5 if you’re working with an existing database.
53 http://codahale.com/how-to-safely-store-a-password/

Bookmarker Tutorial 79
CakePHP Cookbook Documentation, Release 3.8

Note: If the password doesn’t get hashed, make sure you followed the same case for the password member of the
class while naming the setter function

Getting Bookmarks with a Specific Tag

Now that we’re storing passwords safely, we can build out some more interesting features in our application. Once
you’ve amassed a collection of bookmarks, it is helpful to be able to search through them by tag. Next we’ll implement
a route, controller action, and finder method to search through bookmarks by tag.
Ideally, we’d have a URL that looks like http://localhost:8765/bookmarks/tagged/funny/cat/gifs. This would let us
find all the bookmarks that have the ‘funny’, ‘cat’ or ‘gifs’ tags. Before we can implement this, we’ll add a new route.
Your config/routes.php should look like:
<?php
use Cake\Routing\Route\DashedRoute;
use Cake\Routing\Router;

Router::defaultRouteClass(DashedRoute::class);

// New route we're adding for our tagged action.


// The trailing `*` tells CakePHP that this action has
// passed parameters.
Router::scope(
'/bookmarks',
['controller' => 'Bookmarks'],
function ($routes) {
$routes->connect('/tagged/*', ['action' => 'tags']);
}
);

Router::scope('/', function ($routes) {


// Connect the default home and /pages/* routes.
$routes->connect('/', [
'controller' => 'Pages',
'action' => 'display', 'home'
]);
$routes->connect('/pages/*', [
'controller' => 'Pages',
'action' => 'display'
]);

// Connect the conventions based default routes.


$routes->fallbacks();
});

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, from how
they are implemented. If we were to visit http://localhost:8765/bookmarks/tagged, we would see a helpful error
page from CakePHP informing you that the controller action does not exist. Let’s implement that missing method
now. In src/Controller/BookmarksController.php add the following:
public function tags()
{
// The 'pass' key is provided by CakePHP and contains all
// the passed URL path segments in the request.

80 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

$tags = $this->request->getParam('pass');

// Use the BookmarksTable to find tagged bookmarks.


$bookmarks = $this->Bookmarks->find('tagged', [
'tags' => $tags
]);

// Pass variables into the view template context.


$this->set([
'bookmarks' => $bookmarks,
'tags' => $tags
]);
}

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 application’s 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 let’s 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)
{
$bookmarks = $this->find()
->select(['id', 'url', 'title', 'description']);

if (empty($options['tags'])) {
$bookmarks
->leftJoinWith('Tags')
->where(['Tags.title IS' => null]);
} else {
$bookmarks
->innerJoinWith('Tags')
->where(['Tags.title IN ' => $options['tags']]);
}

return $bookmarks->group(['Bookmarks.id']);
}

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 we’ve leveraged the innerJoinWith(), where() and group()
methods which allow us to find distinct bookmarks that have a matching tag. When no tags are provided we use a
leftJoinWith() and modify the ‘where’ condition, finding bookmarks without 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, let’s build the view file for our tags() action. In src/Template/Bookmarks/tags.ctp put the
following content:

Bookmarker Tutorial 81
CakePHP Cookbook Documentation, Release 3.8

<h1>
Bookmarks tagged with
<?= $this->Text->toList(h($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(h($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, we’ve created a basic application to manage bookmarks, tags and users. However, everyone can see everyone
else’s tags. In the next chapter, we’ll 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.

Bookmarker Tutorial Part 2

After finishing the first part of this tutorial you should have a very basic bookmarking application. In this chapter
we’ll be adding authentication and restricting the bookmarks each user can see/modify to only the ones they own.

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 controller’s event
life-cycle and interact with your application that way. To get started, we’ll add the AuthComponent to our application.
We’ll pretty much want every method to require authentication, so we’ll add AuthComponent in our AppController:

// In src/Controller/AppController.php
namespace App\Controller;

use Cake\Controller\Controller;

82 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

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'
],
'unauthorizedRedirect' => $this->referer() // If unauthorized, return
˓→them to page they were just on

]);

// Allow the display action so our pages controller


// continues to work.
$this->Auth->allow(['display']);
}
}

We’ve just told CakePHP that we want to load the Flash and Auth components. In addition, we’ve customized the
configuration of AuthComponent, as our users table uses email as the username. Now, if you go to any URL you’ll
be kicked to /users/login, which will show an error page as we have not written that code yet. So let’s create the login
action:
// 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.');
}
}

And in src/Template/Users/login.ctp add the following:


<h1>Login</h1>
<?= $this->Form->create() ?>
<?= $this->Form->control('email') ?>
<?= $this->Form->control('password') ?>
<?= $this->Form->button('Login') ?>
<?= $this->Form->end() ?>

Note: The control() method is available since 3.4. For prior versions you can use the input() method instead.

Bookmarker Tutorial Part 2 83


CakePHP Cookbook Documentation, Release 3.8

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.

Adding Logout

Now that people can log in, you’ll 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.

Enabling Registrations

If you aren’t 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 the 'add' action 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 won’t 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.

Restricting Bookmark Access

Now that users can log in, we’ll want to limit the bookmarks they can see to the ones they made. We’ll do this
using an ‘authorization’ adapter. Since our requirements are pretty simple, we can write some simple code in our
BookmarksController. But before we do that, we’ll want to tell the AuthComponent how our application is
going to authorize actions. In your AppController add the following:

public function isAuthorized($user)


{

84 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

return false;
}

Also, add the following to the configuration for Auth in your AppController:
'authorize' => 'Controller',

Your initialize() method should now look like:


public function initialize()
{
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authorize'=> 'Controller',//added this line
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
],
'unauthorizedRedirect' => $this->referer()
]);

// Allow the display action so our pages controller


// continues to work.
$this->Auth->allow(['display']);
}

We’ll default to denying access, and incrementally grant access where it makes sense. First, we’ll add the authorization
logic for bookmarks. In your BookmarksController add the following:
public function isAuthorized($user)
{
$action = $this->request->getParam('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 (!$this->request->getParam('pass.0')) {
return false;
}

// Check that the bookmark belongs to the current user.


$id = $this->request->getParam('pass.0');
$bookmark = $this->Bookmarks->get($id);
if ($bookmark->user_id == $user['id']) {
return true;
}
return parent::isAuthorized($user);
}

Bookmarker Tutorial Part 2 85


CakePHP Cookbook Documentation, Release 3.8

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. If no error message is displayed, add the following to your layout:

// In src/Template/Layout/default.ctp
<?= $this->Flash->render() ?>

You should now see the authorization error messages.

Fixing List view and Forms

While view and delete are working, edit, add and index have a few problems:
1. When adding a bookmark you can choose the user.
2. When editing a bookmark you can choose the user.
3. The list page shows bookmarks from other users.
Let’s tackle the add form first. To begin with remove the control('user_id') from
src/Template/Bookmarks/add.ctp. With that removed, we’ll also update the add() action from
src/Controller/BookmarksController.php to look like:

public function add()


{
$bookmark = $this->Bookmarks->newEntity();
if ($this->request->is('post')) {
$bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->
˓→getData());

$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']);
}

By setting the entity property with the session data, we remove any possibility of the user modifying which
user a bookmark is for. We’ll 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->
˓→getData());

$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.');

86 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

}
$tags = $this->Bookmarks->Tags->find('list');
$this->set(compact('bookmark', 'tags'));
$this->set('_serialize', ['bookmark']);
}

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 we’ll leave that as an exercise you can
complete on your own.

Improving the Tagging Experience

Right now, adding new tags is a difficult process, as the TagsController disallows all access. Instead of allowing
access, we can improve the tag selection UI by using a comma separated text field. This will let us give a better
experience to our users, and use some more great features in the ORM.

Adding a Computed Field

Because we’ll want a simple way to access the formatted tags for an entity, we can add a virtual/computed field to the
entity. In src/Model/Entity/Bookmark.php add the following:

use Cake\Collection\Collection;

protected function _getTagString()


{
if (isset($this->_properties['tag_string'])) {
return $this->_properties['tag_string'];
}
if (empty($this->tags)) {
return '';
}
$tags = new Collection($this->tags);
$str = $tags->reduce(function ($string, $tag) {
return $string . $tag->title . ', ';
}, '');
return trim($str, ', ');
}

Bookmarker Tutorial Part 2 87


CakePHP Cookbook Documentation, Release 3.8

This will let us access the $bookmark->tag_string computed property. We’ll use this property in controls later
on. Remember to add the tag_string property to the _accessible list in your entity, as we’ll want to ‘save’ it
later on.
In src/Model/Entity/Bookmark.php add the tag_string to $_accessible this way:

protected $_accessible = [
'user_id' => true,
'title' => true,
'description' => true,
'url' => true,
'user' => true,
'tags' => true,
'tag_string' => true,
];

Updating the Views

With the entity updated we can add a new control for our tags. In src/Template/Bookmarks/add.ctp and
src/Template/Bookmarks/edit.ctp, replace the existing tags._ids control with the following:

echo $this->Form->control('tag_string', ['type' => 'text']);

Persisting the Tag String

Now that we can view existing tags as a string, we’ll want to save that data as well. Because we marked
the tag_string as accessible, the ORM will copy that data from the request into our entity. We can use a
beforeSave() hook method to parse the tag string and find/build the related entities. Add the following to
src/Model/Table/BookmarksTable.php:

public function beforeSave($event, $entity, $options)


{
if ($entity->tag_string) {
$entity->tags = $this->_buildTags($entity->tag_string);
}
}

protected function _buildTags($tagString)


{
// Trim tags
$newTags = array_map('trim', explode(',', $tagString));
// Remove all empty tags
$newTags = array_filter($newTags);
// Reduce duplicated tags
$newTags = array_unique($newTags);

$out = [];
$query = $this->Tags->find()
->where(['Tags.title IN' => $newTags]);

// Remove existing tags from the list of new tags.


foreach ($query->extract('title') as $existing) {
$index = array_search($existing, $newTags);
if ($index !== false) {
unset($newTags[$index]);

88 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

}
}
// 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 we’ve 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

We’ve expanded our bookmarking application to handle authentication and basic authorization/access control scenar-
ios. We’ve 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. We’ll be installing CakePHP, creating a
database, and creating enough application logic to list, add, edit, and delete blog articles.
Here’s what you’ll need:
1. A running web server. We’re going to assume you’re using Apache, though the instructions for using other
servers should be very similar. We might have to play a little with the server configuration, but most folks can
get CakePHP up and running without any configuration at all. Make sure you have PHP 5.6.0 or greater, and
that the mbstring and intl extensions are enabled in PHP.
2. A database server. We’re going to be using MySQL server in this tutorial. You’ll need to know enough about
SQL in order to create a database: CakePHP will be taking the reins from there. Since we’re using MySQL,
also make sure that you have pdo_mysql enabled in PHP.
3. Basic PHP knowledge.
Let’s 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, you’ll need to download and install Composer if you haven’t done so already.
If you have cURL installed, it’s as easy as running the following:

curl -s https://getcomposer.org/installer | php

Blog Tutorial 89
CakePHP Cookbook Documentation, Release 3.8

Or, you can download composer.phar from the Composer website54 .


Then simply type the following line in your terminal from your installation directory to install the CakePHP application
skeleton in the directory that you wish to use it with. For this example we will be using “blog” but feel free to change
it to something else.:

php composer.phar create-project --prefer-dist cakephp/app blog

In case you’ve already got composer installed globally, you may instead type:

composer self-update && composer create-project --prefer-dist cakephp/app blog

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 don’t 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 CakePHP’s directory structure works: check out the CakePHP
Folder Structure section.

Directory Permissions on tmp and logs

The tmp and logs directories need to have proper permissions to be writable by your webserver. If you used
Composer for the install, this should have been done for you and confirmed with a “Permissions set on <folder>”
message. If you instead got an error message or want to do it manually, the best way would be to find out what user
your webserver runs as (<?= `whoami`; ?>) and change the ownership of these two directories to that user. The
final command you run (in *nix) might look something like this:

chown -R www-data tmp


chown -R www-data logs

If for some reason CakePHP can’t write to these directories, you’ll be informed by a warning while not in production
mode.
54 https://getcomposer.org/download/

90 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

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 -R 777 tmp


chmod -R 777 logs

Creating the Blog Database

Next, let’s set up the underlying MySQL database for our blog. If you haven’t already done so, create an empty
database for use in this tutorial, with a name of your choice, e.g. cake_blog. Right now, we’ll just create a single
table to store our articles. We’ll also throw in a few articles to use for testing purposes. Execute the following SQL
statements into your database:

# First, create our articles table


CREATE TABLE articles (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(50),
body TEXT,
created DATETIME DEFAULT NULL,
modified DATETIME DEFAULT NULL
);

# Then insert some articles for testing:


INSERT INTO articles (title,body,created)
VALUES ('The title', 'This is the article body.', NOW());
INSERT INTO articles (title,body,created)
VALUES ('A title once again', 'And the article body follows.', NOW());
INSERT INTO articles (title,body,created)
VALUES ('Title strikes back', 'This is really exciting! Not.', NOW());

The choices on table and column names are not arbitrary. If you follow CakePHP’s database naming conventions,
and CakePHP’s class naming conventions (both outlined in CakePHP Conventions), you’ll be able to take advantage
of a lot of free functionality and avoid configuration. CakePHP is flexible enough to accommodate even inconsistent
legacy database schemas, but adhering to the conventions will save you time.
Check out CakePHP Conventions for more information, but it’s suffice to say that naming our table ‘articles’ automat-
ically hooks it to our Articles model, and having fields called ‘modified’ and ‘created’ will be automatically managed
by CakePHP.

Database Configuration

Next, let’s 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,

Blog Tutorial 91
CakePHP Cookbook Documentation, Release 3.8

'host' => 'localhost',


'username' => 'cake_blog',
'password' => 'AngelF00dC4k3~',
'database' => 'cake_blog',
'encoding' => 'utf8',
'timezone' => 'UTC'
],
],
// More configuration below.
];

Once you’ve 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 CakePHP’s 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 they’re 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 you’d need to change the default salt value by editing config/app.php. It doesn’t matter much what the
new value is, as long as it’s not guessable:

'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.

Blog Tutorial - Part 2

Create an Article Model

Models are the bread and butter of CakePHP applications. By creating a CakePHP model that will interact with our
database, we’ll have the foundation in place needed to do our view, add, edit, and delete operations later.
CakePHP’s model class files are split between Table and Entity objects. Table objects provide access to the
collection of entities stored in a specific table and go in src/Model/Table. The file we’ll be creating will be saved to
src/Model/Table/ArticlesTable.php. The completed file should look like this:

92 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

// 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');
}
}

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 Arti-
cleTable.php), CakePHP will not recognize any of your settings and will use the generated model instead.

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 CakePHP’s 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 /bake/usage.

Create the Articles Controller

Next, we’ll create a controller for our articles. The controller is where all interaction with articles will happen. In a
nutshell, it’s the place where you play with the business logic contained in the models and get work related to articles
done. We’ll place this new controller in a file called ArticlesController.php inside the src/Controller directory.
Here’s what the basic controller should look like:

// src/Controller/ArticlesController.php

namespace App\Controller;

class ArticlesController extends AppController


{
}

Now, let’s add an action to our controller. Actions often represent a single function or interface in an
application. For example, when users request www.example.com/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:

Blog Tutorial - Part 2 93


CakePHP Cookbook Documentation, Release 3.8

// 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.

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 we’ll 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 CakePHP’s 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 /bake/usage.
To learn more about CakePHP’s controllers, check out the Controllers chapter.

Creating Article Views

Now that we have our data flowing from our model, and our application logic is defined by our controller, let’s create
a view for the index action we created above.
CakePHP views are just presentation-flavored fragments that fit inside an application’s layout. For most applications,
they’re HTML mixed with PHP, but they may end up as XML, CSV, or even binary data.
A layout is presentation code that is wrapped around a view. Multiple layouts can be defined, and you can switch
between them, but for now, let’s just use the default.
Remember in the last section how we assigned the ‘articles’ variable to the view using the set() method? That
would hand down the query object collection to the view to be invoked with a foreach iteration.
CakePHP’s template files are stored in src/Template inside a folder named after the controller they correspond to
(we’ll have to create a folder named ‘Articles’ in this case). To format this article data in a nice table, our view code
might look something like this:

94 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

<!-- File: src/Template/Articles/index.ctp -->

<h1>Blog articles</h1>
<table>
<tr>
<th>Id</th>
<th>Title</th>
<th>Created</th>
</tr>

<!-- Here is 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]) ?>

</td>
<td>
<?= $article->created->format(DATE_RFC850) ?>
</td>
</tr>
<?php endforeach; ?>
</table>

Hopefully this should look somewhat simple.


You might have noticed the use of an object called $this->Html. This is an instance of the CakePHP
Cake\View\Helper\HtmlHelper class. CakePHP comes with a set of view helpers that make things like
linking, form output a snap. You can learn more about how to use them in Helpers, but what’s important to note here
is that the link() method will generate an HTML link with the given title (the first parameter) and URL (the second
parameter).
When specifying URLs in CakePHP, it is recommended that you use the array format. This is explained in more
detail in the section on Routes. Using the array format for URLs allows you to take advantage of CakePHP’s reverse
routing capabilities. You can also specify URLs relative to the base of the application in the form of /controller/
action/param1/param2 or use named routes.
At this point, you should be able to point your browser to http://www.example.com/articles/index. You should see
your view, correctly formatted with the title and table listing of the articles.
If you happened to have clicked on one of the links we created in this view (that link a article’s title to a URL /
articles/view/some\_id), you were probably informed by CakePHP that the action hasn’t yet been defined.
If you were not so informed, either something has gone wrong, or you actually did define it already, in which case you
are very sneaky. Otherwise, we’ll create it in the ArticlesController now:

// src/Controller/ArticlesController.php

namespace App\Controller;

class ArticlesController extends AppController


{

public function index()


{
$this->set('articles', $this->Articles->find('all'));

Blog Tutorial - Part 2 95


CakePHP Cookbook Documentation, Release 3.8

public function view($id = null)


{
$article = $this->Articles->get($id);
$this->set(compact('article'));
}
}

The set() call should look familiar. Notice we’re using get() rather than find('all') because we only really
want a single article’s information.
Notice that our view action takes a parameter: the ID of the article we’d 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 let’s 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 let’s 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'));
}

public function view($id)

96 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

{
$article = $this->Articles->get($id);
$this->set(compact('article'));
}

public function add()


{
$article = $this->Articles->newEntity();
if ($this->request->is('post')) {
// Prior to 3.4.0 $this->request->data() was used.
$article = $this->Articles->patchEntity($article, $this->request->
˓→getData());

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);
}
}

Note: You need to include the Flash component in any controller where you will use it. If necessary, include it in
your AppController.

Here’s what the add() action does: if the HTTP method of the request was POST, try to save the data using the
Articles model. If for some reason it doesn’t save, just render the view. This gives us a chance to show the user
validation errors or other warnings.
Every CakePHP request includes a ServerRequest 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\Http\ServerRequest::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->getData() ( Or $this->request->data() for CakePHP v3.3 and under ).
You can use the pr() or debug() functions to print it out if you want to see what it looks like.
We use FlashComponent’s success() and error() methods to set a message to a session variable. These methods
are provided using PHP’s magic method features55 . 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 corre-
sponding session variable. The controller’s 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 API56 to see the
formats in which you can specify a URL for various CakePHP functions.
Calling the save() method will check for validation errors and abort the save if any occur. We’ll discuss how those
errors are handled in the following sections.

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.
55 http://php.net/manual/en/language.oop5.overloading.php#object.call
56 https://api.cakephp.org

Blog Tutorial - Part 2 97


CakePHP Cookbook Documentation, Release 3.8

To take advantage of the validation features, you’ll need to use CakePHP’s Form helper in your views. The
Cake\View\Helper\FormHelper is available by default to all views at $this->Form.
Here’s our add view:

<!-- File: src/Template/Articles/add.ctp -->

<h1>Add Article</h1>
<?php
echo $this->Form->create($article);
echo $this->Form->control('title');
echo $this->Form->control('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. Here’s the HTML that
$this->Form->create() generates:

<form method="post" action="/articles/add">

If create() is called with no parameters supplied, it assumes you are building a form that submits via POST to the
current controller’s add() action (or edit() action when id is included in the form data).
The $this->Form->control() method is used to create form elements of the same name. The first parameter
tells CakePHP which field they correspond to, and the second parameter allows you to specify a wide array of options
- in this case, the number of rows for the textarea. There’s a bit of introspection and automagic here: control()
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 let’s 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. Let’s look back at our Articles model and make a few adjustments:

// src/Model/Table/ArticlesTable.php

namespace App\Model\Table;

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')

98 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

->notEmpty('body')
->requirePresence('body');

return $validator;
}
}

The validationDefault() method tells CakePHP how to validate your data when the save() method is called.
Here, we’ve specified that both the body and title fields must not be empty, and are required for both create and update
operations. CakePHP’s validation engine is strong, with a number of pre-built rules (credit card numbers, email
addresses, etc.) and flexibility for adding your own validation rules. For more information 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 we’ve used the Cake\View\Helper\FormHelper::control() method of the FormHelper to
create our form elements, our validation error messages will be shown automatically.

Editing Articles

Post editing: here we go. You’re a CakePHP pro by now, so you should have picked up a pattern. Make the action,
then the view. Here’s what the edit() action of the ArticlesController would look like:
// src/Controller/ArticlesController.php

public function edit($id = null)


{
$article = $this->Articles->get($id);
if ($this->request->is(['post', 'put'])) {
// Prior to 3.4.0 $this->request->data() was used.
$this->Articles->patchEntity($article, $this->request->getData());
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.'));
}

$this->set('article', $article);
}

This action first ensures that the user has tried to access an existing record. If they haven’t 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 Article</h1>
<?php
echo $this->Form->create($article);
echo $this->Form->control('title');
echo $this->Form->control('body', ['rows' => '3']);
echo $this->Form->button(__('Save Article'));

Blog Tutorial - Part 2 99


CakePHP Cookbook Documentation, Release 3.8

echo $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 (edit 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>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]) ?>

</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, let’s make a way for users to delete articles. Start with a delete() action in the 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']);

100 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

}
}

This logic deletes the article specified by $id, and uses $this->Flash->success() to show the user a confir-
mation 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 CakePHP’s exception handler,
and a nice error page is displayed. There are many built-in Exceptions that can be used to indicate the various HTTP
errors your application might need to generate.
Because we’re just executing some logic and redirecting, this action has no view. You might want to update your index
view with links that allow users to delete 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>
</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>

Using View\Helper\FormHelper::postLink() will create a link that uses JavaScript to do a POST request
deleting our article.

Warning: Allowing content to be deleted using GET requests is dangerous, as web crawlers could accidentally
delete all your content.

Blog Tutorial - Part 2 101


CakePHP Cookbook Documentation, Release 3.8

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, CakePHP’s default routing works well enough. Developers who are sensitive to user-friendliness and
general search engine compatibility will appreciate the way that CakePHP’s URLs map to specific actions. So we’ll
just make a quick change to routes in this tutorial.
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 its
PagesController, rendering a view called “home”. Instead, we’ll replace this with our ArticlesController by
creating a routing rule.
CakePHP’s routing is found in config/routes.php. You’ll 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
‘/’. It’s therefore a good idea to always use arrays for URLs as this means your routes define where a URL goes, and
also ensures that links point to the same place.

Conclusion

Creating applications this way will win you peace, honor, love, and money beyond even your wildest fantasies. Simple,
isn’t it? Keep in mind that this tutorial was very basic. CakePHP has many more features to offer, and is flexible in
ways we didn’t wish to cover here for simplicity’s sake. Use the rest of this manual as a guide for building more
feature-rich applications.
Now that you’ve created a basic CakePHP application, you can either continue to Blog Tutorial - Part 3, or start your
own project. You can also peruse the /topics or API57 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
57 https://api.cakephp.org

102 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

2. Elements: Including and reusing view snippets


3. /bake/usage: Generating basic CRUD code
4. Blog Tutorial - Authentication and Authorization: User authentication and authorization tutorial

Blog Tutorial - Part 3

Create a Tree Category

Let’s continue our blog application and imagine we want to categorize our articles. We want the categories to be
ordered, and for this, we will use the Tree behavior to help us organize the categories.
But first, we need to modify our tables.

Migrations Plugin

We will use the migrations plugin58 to create a table in our database. If you already have an articles table in your
database, erase it.
Now open your application’s 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

The migrations plugin will now be in your application’s plugins folder. Also, add
$this->addPlugin('Migrations'); to your application’s bootstrap method.
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', [

58 https://github.com/cakephp/migrations

Blog Tutorial - Part 3 103


CakePHP Cookbook Documentation, Release 3.8

'default' => null,


'limit' => 11,
'null' => false,
]);
$table->addColumn('created', 'datetime', [
'default' => null,
'null' => false,
]);
$table->addColumn('modified', 'datetime', [
'default' => null,
'null' => false,
]);
$table->create();
}
}

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

This will generate the following file in config/Migrations:


<?php

use Migrations\AbstractMigration;

class CreateCategories extends AbstractMigration


{
public function change()
{
$table = $this->table('categories');
$table->addColumn('parent_id', 'integer', [
'default' => null,
'limit' => 11,
'null' => false,
]);
$table->addColumn('lft', 'integer', [
'default' => null,
'limit' => 10,
'null' => false,
]);
$table->addColumn('rght', 'integer', [
'default' => null,
'limit' => 10,
'null' => false,
]);
$table->addColumn('name', 'string', [
'default' => null,
'limit' => 100,
'null' => false,
]);
$table->addColumn('description', 'string', [
'default' => null,
'limit' => 255,
'null' => false,
]);

104 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

$table->addColumn('created', 'datetime', [
'default' => null,
'null' => false,
]);
$table->addColumn('modified', 'datetime', [
'default' => null,
'null' => false,
]);
$table->create();
}
}

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

Modifying the Tables

With our tables set up, we can now focus on categorizing our articles.
We suppose you already have the files (Tables, Controllers and Templates of Articles) from part 2. So we’ll just add
the references to categories.
We need to associate the Articles and Categories tables together. Open the src/Model/Table/ArticlesTable.php file
and add the following:

// 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',
]);
}
}

Generate Skeleton Code for Categories

Create all files by launching bake commands:

bin/cake bake model Categories


bin/cake bake controller Categories
bin/cake bake template Categories

Blog Tutorial - Part 3 105


CakePHP Cookbook Documentation, Release 3.8

Alternatively, you can bake all with just one line:

bin/cake bake all Categories

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 /.

You’ll need to edit the following in src/Template/Categories/add.ctp and src/Template/Categories/edit.ctp:

echo $this->Form->control('parent_id', [
'options' => $parentCategories,
'empty' => 'No parent category'
]);

Attach TreeBehavior to CategoriesTable

The TreeBehavior helps you manage hierarchical Tree structures in database table. It uses the MPTT logic59 to manage
the data. MPTT tree structures are optimized for reads, which often makes them a good fit for read heavy applications
like blogs.
If you open the src/Model/Table/CategoriesTable.php file, you’ll see that the TreeBehavior has been attached to your
CategoriesTable in the initialize() method. Bake adds this behavior to any Tables that contain lft and rght
columns:

$this->addBehavior('Tree');

With the TreeBehavior attached you’ll be able to access some features like reordering the categories. We’ll see that in
a moment.
But for now, you have to remove the following controls in your Categories add and edit template files:

echo $this->Form->control('lft');
echo $this->Form->control('rght');

In addition you should disable or remove the requirePresence from the validator for both the lft and rght columns
in your CategoriesTable model:

public function validationDefault(Validator $validator)


{
$validator
->add('id', 'valid', ['rule' => 'numeric'])
->allowEmptyString('id', 'create');

$validator
->add('lft', 'valid', ['rule' => 'numeric'])
// ->requirePresence('lft', 'create')
->notEmpty('lft');

$validator
->add('rght', 'valid', ['rule' => 'numeric'])
// ->requirePresence('rght', 'create')

59 http://www.sitepoint.com/hierarchical-data-database-2/

106 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

->notEmpty('rght');
}

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.

Reordering Categories with TreeBehavior

In your categories index template file, you can list the categories and re-order them.
Let’s modify the index method in your CategoriesController.php and add moveUp() and moveDown() methods
to be able to reorder the categories in the tree:

class CategoriesController extends AppController


{
public function index()
{
$categories = $this->Categories->find()
->order(['lft' => 'ASC']);
$this->set(compact('categories'));
$this->set('_serialize', ['categories']);
}

public function moveUp($id = null)


{
$this->request->allowMethod(['post', 'put']);
$category = $this->Categories->get($id);
if ($this->Categories->moveUp($category)) {
$this->Flash->success('The category has been moved Up.');
} else {
$this->Flash->error('The category could not be moved up. Please, try
˓→again.');

}
return $this->redirect($this->referer(['action' => 'index']));
}

public function moveDown($id = null)


{
$this->request->allowMethod(['post', 'put']);
$category = $this->Categories->get($id);
if ($this->Categories->moveDown($category)) {
$this->Flash->success('The category has been moved down.');
} else {
$this->Flash->error('The category could not be moved down. Please, try
˓→again.');

}
return $this->redirect($this->referer(['action' => 'index']));
}
}

In src/Template/Categories/index.ctp replace the existing content with:

<div class="actions large-2 medium-3 columns">


<h3><?= __('Actions') ?></h3>
<ul class="side-nav">
<li><?= $this->Html->link(__('New Category'), ['action' => 'add']) ?></li>

Blog Tutorial - Part 3 107


CakePHP Cookbook Documentation, Release 3.8

</ul>
</div>
<div class="categories index large-10 medium-9 columns">
<table cellpadding="0" cellspacing="0">
<thead>
<tr>
<th>Id</th>
<th>Parent Id</th>
<th>Lft</th>
<th>Rght</th>
<th>Name</th>
<th>Description</th>
<th>Created</th>
<th class="actions"><?= __('Actions') ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($categories as $category): ?>
<tr>
<td><?= $category->id ?></td>
<td><?= $category->parent_id ?></td>
<td><?= $category->lft ?></td>
<td><?= $category->rght ?></td>
<td><?= h($category->name) ?></td>
<td><?= h($category->description) ?></td>
<td><?= h($category->created) ?></td>
<td class="actions">
<?= $this->Html->link(__('View'), ['action' => 'view', $category->
˓→id]) ?>

<?= $this->Html->link(__('Edit'), ['action' => 'edit', $category->


˓→id]) ?>

<?= $this->Form->postLink(__('Delete'), ['action' => 'delete',


˓→$category->id], ['confirm' => __('Are you sure you want to delete # {0}?',

˓→$category->id)]) ?>

<?= $this->Form->postLink(__('Move down'), ['action' => 'moveDown',


˓→$category->id], ['confirm' => __('Are you sure you want to move down # {0}?',

˓→$category->id)]) ?>

<?= $this->Form->postLink(__('Move up'), ['action' => 'moveUp',


˓→$category->id], ['confirm' => __('Are you sure you want to move up # {0}?',

˓→$category->id)]) ?>

</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>

Modifying the ArticlesController

In our ArticlesController, we’ll get the list of all the categories. This will allow us to choose a category for an
Article when creating or editing it:

// src/Controller/ArticlesController.php

namespace App\Controller;

108 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

// Prior to 3.6 use Cake\Network\Exception\NotFoundException


use Cake\Http\Exception\NotFoundException;

class ArticlesController extends AppController


{

// ...

public function add()


{
$article = $this->Articles->newEntity();
if ($this->request->is('post')) {
// Prior to 3.4.0 $this->request->data() was used.
$article = $this->Articles->patchEntity($article, $this->request->
˓→getData());

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'));
}
}

Modifying the Articles Templates

The article add file should look something like this:

<!-- File: src/Template/Articles/add.ctp -->

<h1>Add Article</h1>
<?php
echo $this->Form->create($article);
// just added the categories control
echo $this->Form->control('category_id');
echo $this->Form->control('title');
echo $this->Form->control('body', ['rows' => '3']);
echo $this->Form->button(__('Save Article'));
echo $this->Form->end();

When you go to the address /yoursite/categories/add you should see a list of categories to choose.

Blog Tutorial - Authentication and Authorization

Following our Blog Tutorial example, imagine we wanted to secure access to certain URLs, based on the logged-in
user. We also have another requirement: to allow our blog to have multiple authors who can create, edit, and delete
their own articles while disallowing other authors from making changes to articles they do not own.

Blog Tutorial - Authentication and Authorization 109


CakePHP Cookbook Documentation, Release 3.8

Creating All User-Related Code

First, let’s create a new table in our blog database to hold our users’ data:

CREATE TABLE users (


id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50),
password VARCHAR(255),
role VARCHAR(20),
created DATETIME DEFAULT NULL,
modified DATETIME DEFAULT NULL
);

We have adhered to the CakePHP conventions in naming tables, but we’re also taking advantage of another convention:
By using the username and password columns in a users table, CakePHP will be able to auto-configure 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;

class UsersTable extends Table


{

public function validationDefault(Validator $validator)


{
return $validator
->notEmpty('username', 'A username is required')
->notEmpty('password', 'A password is required')
->notEmpty('role', 'A role is required')
->add('role', 'inList', [
'rule' => ['inList', ['admin', 'author']],
'message' => 'Please enter a valid role'
]);
}

Let’s 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);

110 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

$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')) {
// Prior to 3.4.0 $this->request->data() was used.
$user = $this->Users->patchEntity($user, $this->request->getData());
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->control('username') ?>
<?= $this->Form->control('password') ?>
<?= $this->Form->control('role', [
'options' => ['admin' => 'Admin', 'author' => 'Author']
]) ?>
</fieldset>
<?= $this->Form->button(__('Submit')); ?>
<?= $this->Form->end() ?>
</div>

Authentication (Login and Logout)

We’re now ready to add our authentication layer. In CakePHP this is handled by the
Cake\Controller\Component\AuthComponent, a class responsible for requiring login for certain
actions, handling user login and logout, and also authorizing logged-in users to the actions they are allowed to reach.

Blog Tutorial - Authentication and Authorization 111


CakePHP Cookbook Documentation, Release 3.8

To add this component to your application open your src/Controller/AppController.php file and add the following
lines:
// src/Controller/AppController.php

namespace App\Controller;

use Cake\Controller\Controller;
use Cake\Event\Event;

class AppController extends Controller


{
//...

public function initialize()


{
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'loginRedirect' => [
'controller' => 'Articles',
'action' => 'index'
],
'logoutRedirect' => [
'controller' => 'Pages',
'action' => 'display',
'home'
]
]);
}

public function beforeFilter(Event $event)


{
$this->Auth->allow(['index', 'view', 'display']);
}
//...
}

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. Let’s 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)

112 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

{
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']);
}

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(__('Invalid username or password, try again'));
}
}

public function logout()


{
return $this->redirect($this->Auth->logout());
}
}

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)


{
if (strlen($password) > 0) {
return (new DefaultPasswordHasher)->hash($password);
}
}

// ...
}

Now every time the password property is assigned to the user it will be hashed using the
DefaultPasswordHasher class. We’re just missing a template view file for the login function. Open up

Blog Tutorial - Authentication and Authorization 113


CakePHP Cookbook Documentation, Release 3.8

your src/Template/Users/login.ctp file and add the following lines:

<!-- File: src/Template/Users/login.ctp -->

<div class="users form">


<?= $this->Flash->render() ?>
<?= $this->Form->create() ?>
<fieldset>
<legend><?= __('Please enter your username and password') ?></legend>
<?= $this->Form->control('username') ?>
<?= $this->Form->control('password') ?>
</fieldset>
<?= $this->Form->button(__('Login')); ?>
<?= $this->Form->end() ?>
</div>

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 that’s it! It looks too simple to be true. Let’s 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 AppController’s beforeFilter() function.
The login() action calls the $this->Auth->identify() function in the AuthComponent, and it works with-
out 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 redi-
rection 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.

Authorization (who’s allowed to access what)

As stated before, we are converting this blog into a multi-user authoring tool, and in order to do this, we need to modify
the articles table a bit to add the reference to the Users table:

ALTER TABLE articles ADD COLUMN user_id INT(11);

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();
if ($this->request->is('post')) {
// Prior to 3.4.0 $this->request->data() was used.
$article = $this->Articles->patchEntity($article, $this->request->getData());
// 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)) {

114 Chapter 4. Tutorials & Examples


CakePHP Cookbook Documentation, Release 3.8

$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.
Let’s 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;
}

// 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 aren’t
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, we’ll delegate supplying those extra rules to each individual controller. The rules we’re
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:

Blog Tutorial - Authentication and Authorization 115


CakePHP Cookbook Documentation, Release 3.8

// src/Controller/ArticlesController.php

public function isAuthorized($user)


{
// All registered users can add articles
// Prior to 3.4.0 $this->request->param('action') was used.
if ($this->request->getParam('action') === 'add') {
return true;
}

// The owner of an article can edit and delete it


// Prior to 3.4.0 $this->request->param('action') was used.
if (in_array($this->request->getParam('action'), ['edit', 'delete'])) {
// Prior to 3.4.0 $this->request->params('pass.0')
$articleId = (int)$this->request->getParam('pass.0');
if ($this->Articles->isOwnedBy($articleId, $user['id'])) {
return true;
}
}

return parent::isAuthorized($user);
}

We’re now overriding the AppController’s isAuthorized() call and internally checking if the parent class is
already authorizing the user. If he isn’t, 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, we’re
calling a isOwnedBy() function in the Articles table. Let’s 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.
Should you need more control, we suggest you read the complete Auth guide in the AuthComponent section where
you will find more about configuring the component, creating custom Authorization classes, and much more.

Suggested Follow-up Reading

1. /bake/usage Generating basic CRUD code


2. AuthComponent: User registration and login

116 Chapter 4. Tutorials & Examples


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 GitHub’s online editor for that page.
CakePHP documentation is continuously integrated60 , and deployed after each pull request is merged.

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 don’t
change often:
• index.rst
60 http://en.wikipedia.org/wiki/Continuous_integration

117
CakePHP Cookbook Documentation, Release 3.8

• 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 follow-
ing 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,...

Translator tips

• Browse and edit in the language you want the content to be translated to - otherwise you won’t see what has
already been translated.
• Feel free to dive right in if your chosen language already exists on the book.
• Use Informal Form61 .
• Translate both the content and the title at the same time.
61 http://en.wikipedia.org/wiki/Register_(linguistics)

118 Chapter 5. Contributing


CakePHP Cookbook Documentation, Release 3.8

• Do compare to the English content before submitting a correction (if you correct something, but don’t integrate
an ‘upstream’ change your submission won’t 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 entities62 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.

Documentation Formatting Guide

The new CakePHP documentation is written with ReST formatted text63 . ReST (Re Structured Text) is a plain text
markup syntax similar to markdown, or textile. To maintain consistency it is recommended that when adding to the
CakePHP documentation you follow the guidelines here on how to format and structure your text.

Line Length

Lines of text should be wrapped at 80 columns. The only exception should be long URLs, and code snippets.

Headings and Sections

Section headers are created by underlining the title with punctuation characters at least the length of the text.
• # Is used to denote page titles.
• = Is used for sections in a page.
• - Is used for subsections.
• ~ Is used for sub-subsections.
• ^ Is used for sub-sub-subsections.
Headings should not be nested more than 5 levels deep. Headings should be preceded and followed by a blank line.

Paragraphs

Paragraphs are simply blocks of text, with all the lines at the same level of indentation. Paragraphs should be separated
by one blank line.

Inline Markup

• One asterisk: text for emphasis (italics) We’ll use it for general highlighting/emphasis.
– *text*.
• Two asterisks: text for strong emphasis (boldface) We’ll use it for working directories, bullet list subject, table
names and excluding the following word “table”.
62 http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references
63 http://en.wikipedia.org/wiki/ReStructuredText

Documentation 119
CakePHP Cookbook Documentation, Release 3.8

– **/config/Migrations**, **articles**, etc.


• Two backquotes: text for code samples We’ll use it for names of method options, names of table columns,
object names, excluding the following word “object” and for method/function names – include “()”.
– ``cascadeCallbacks``, ``true``, ``id``, ``PagesController``, ``config()``,
etc.
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

#. 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.

Definition lists can be created by doing the following:

term
definition
CakePHP
An MVC framework for PHP

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.

120 Chapter 5. Contributing


CakePHP Cookbook Documentation, Release 3.8

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.net64

Links to Other Pages

:doc:
Other pages in the documentation can be linked to using the :doc: role. You can link to the specified doc-
ument using either an absolute or relative path reference. You should omit the .rst extension. For exam-
ple, if the reference :doc:`form` appears in the document core-helpers/html, then the link refer-
ences core-helpers/form. If the reference was :doc:`/core-helpers`, it would always reference
/core-helpers regardless of where it was used.

Cross Referencing Links

:ref:
You can cross reference any arbitrary title in any document using the :ref: role. Link label targets
must be unique across the entire documentation. When creating labels for class methods, it’s best to use
class-method as the format for your link label.
The most common use of labels is above a title. Example:

.. _label-name:

Section heading
---------------

More content here.

Elsewhere you could reference the above section using :ref:`label-name`. The link’s 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. It’s a great way to ensure that all files have a link
directed to them, but sometimes, you don’t need to insert a link for a file, eg. for our epub-contents 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 phpdomain65 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.
64 http://php.net
65 http://pypi.python.org/pypi/sphinxcontrib-phpdomain

Documentation 121
CakePHP Cookbook Documentation, Release 3.8

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.
.. php:exception:: name
This directive declares a new Exception in the current namespace. The signature can include constructor argu-
ments.
.. php:class:: name
Describes a class. Methods, attributes, and constants belonging to the class should be inside this directive’s
body:

.. php:class:: MyClass

Class description

.. php:method:: method($argument)

Method description

Attributes, methods and constants don’t 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.

122 Chapter 5. Contributing


CakePHP Cookbook Documentation, Release 3.8

.. php:attr:: name
Describe an property/attribute on a class.

Prevent Sphinx to Output Warnings

Sphinx will output warnings if a function is referenced in multiple files. It’s a great way to ensure that you did not
add a function two times, but sometimes, you actually want to write a function in two or more files, eg. debug object
is referenced in /development/debugging and in /core-libraries/global-constants-and-functions. In this case, you can
add :noindex: under the function debug to suppress warnings. Keep only one reference without :no-index:
to still have the function referenced:

.. php:function:: debug(mixed $var, boolean $showHtml = null, $showFrom = true)


:noindex:

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.

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:

Documentation 123
CakePHP Cookbook Documentation, Release 3.8

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 secu-
rity. 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 fea-
tures 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

Tip: This is a helpful tid-bit you probably forgot.

Note: You should pay attention here.

Warning: It could be dangerous.

New in version 2.6.3: This awesome feature was added on version 2.6.3
Deprecated since version 2.6.3: This old feature was deprecated on version 2.6.3

124 Chapter 5. Contributing


CakePHP Cookbook Documentation, Release 3.8

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 CakePHP’s tickets are hosted on GitHub66 .

Reporting Bugs

Well written bug reports are very helpful. There are a few steps to help create the best bug report possible:
• Do: Please search67 for a similar existing ticket, and ensure someone hasn’t already reported your issue, or that
it hasn’t 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 it’s less likely
to get fixed.
• Do: Please give as many details as possible about your environment: (OS, PHP version, CakePHP version).
• Don’t: Please don’t use the ticket system to ask support questions. The #cakephp IRC channel on Freenode68
has many developers available to help answer your questions. Also have a look at Stack Overflow69 .

Reporting Security Issues

If you’ve found a security issue in CakePHP, please use the following procedure instead of the normal bug reporting
system. Instead of using the bug tracker, mailing list or IRC please send an email to security [at] cakephp.org. Emails
sent to this address go to the CakePHP core team on a private mailing list.
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 we’ve 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, it’s a good idea to get your environment setup. You’ll need the following
software:
66 https://github.com/cakephp/cakephp/issues
67 https://github.com/cakephp/cakephp/search?q=it+is+broken&ref=cmdform&type=Issues
68 https://webchat.freenode.net
69 https://stackoverflow.com/questions/tagged/cakephp

Tickets 125
CakePHP Cookbook Documentation, Release 3.8

• Git
• PHP 5.6.0 or greater
• PHPUnit 5.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 '[email protected]'

Note: If you are new to Git, we highly recommend you to read the excellent and free ProGit70 book.

Get a clone of the CakePHP source code from GitHub:


• If you don’t have a GitHub71 account, create one.
• Fork the CakePHP repository72 by clicking the Fork button.
After your fork is made, clone your fork to your local machine:

git clone [email protected]:YOURNAME/cakephp.git

Add the original CakePHP repository as a remote repository. You’ll 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:

# 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:
70 http://git-scm.com/book/
71 http://github.com
72 http://github.com/cakephp/cakephp

126 Chapter 5. Contributing


CakePHP Cookbook Documentation, Release 3.8

• 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 clear commit messages that provide context on what you changed and
why.

Submitting a Pull Request

Once your changes are done and you’re ready for them to be merged into CakePHP, you’ll want to update your branch:

# Rebase fix on top of master


git checkout master
git fetch upstream
git merge upstream/master
git checkout <branch_name>
git rebase master

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 you’ve rebased after pushing your branch, you’ll 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 doesn’t 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.6.0, the branch accepting new features will
be 3.next.
• If your change is a breaks existing functionality, or API’s then you’ll have to choose then next major release.
For example, if the current release is 3.6.0 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 Foundation73 will become the owner of any contributed code. Contributors should follow the CakePHP
73 http://cakefoundation.org/pages/about

Code 127
CakePHP Cookbook Documentation, Release 3.8

Community Guidelines74 .

All bug fixes merged into a maintenance branch will also be merged into upcoming releases periodically by the core
team.

Coding Standards

CakePHP developers will use the PSR-2 coding style guide75 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 Sniffer76 to check that your code follows required standards.

Adding New Features

No new features should be added, without having their own tests – which should be passed before committing them to
the repository.

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.
We recommend to use the IdeHelper77 plugin if you want to maximize IDE compatibility. It will assist to keep the
annotations up-to-date which will make the IDE fully understand how all classes work together and provides better
type-hinting and auto-completion.

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';

74 http://community.cakephp.org/guidelines
75 http://www.php-fig.org/psr/psr-2/
76 https://github.com/cakephp/cakephp-codesniffer
77 https://github.com/dereuromark/cakephp-ide-helper

128 Chapter 5. Contributing


CakePHP Cookbook Documentation, Release 3.8

}
}

In cases where you’re using a multi-line function call use the following guidelines:
• 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)));

Use this instead:

$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. A limit of 80 or 120
characters makes it necessary to distribute complex logic or expressions by function, as well as give functions and
objects shorter, more expressive names. 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 129


CakePHP Cookbook Documentation, Release 3.8

• 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:

<?php
if ($isAdmin):
echo '<p>You are the admin user.</p>';
endif;
?>
<p>The following is also acceptable:</p>

130 Chapter 5. Contributing


CakePHP Cookbook Documentation, Release 3.8

<?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) {


// ...
}

The value to check against should be placed on the right side:

// not recommended
if (null === $this->foo()) {
// ...
}

// recommended
if ($this->foo() === null) {
// ...
}

Function Calls

Functions should be called without space between function’s 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:

public function someFunction($arg1, $arg2 = '')


{
if (expr) {
statement;
}

return $var;
}

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:

Coding Standards 131


CakePHP Cookbook Documentation, Release 3.8

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;
}

There are spaces on both side of the equals sign.

Bail Early

Try to avoid unnecessary nesting by bailing early:

public function run(array $data)


{
...
if (!$success) {
return false;
}

...
}

public function check(array $data)


{
...
if (!$success) {
throw new RuntimeException(...);
}

...
}

This helps to keep the code flow simple and easy to follow.

Typehinting

Arguments that expect objects, arrays or callbacks (callable) can be typehinted. We only typehint public methods,
though, as typehinting is not cost-free:

/**
* Some method description.
*
* @param \Cake\ORM\Table $table The table class to use.
* @param array $array Some array value.
* @param callable $callback Some callback.
* @param bool $boolean Some boolean value.

132 Chapter 5. Contributing


CakePHP Cookbook Documentation, Release 3.8

*/
public function foo(Table $table, array $array, callable $callback, $boolean)
{
}

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:
/**
* Some method description.
*
* @param array|\ArrayObject $array Some array value.
*/
public function foo($array)
{
}

Anonymous Functions (Closures)

Defining anonymous functions follows the PSR-278 coding style guide, where they are declared with a space after the
function keyword, and a space before and after the use keyword:
$closure = function ($arg1, $arg2) use ($var1, $var2) {
// code
};

Method Chaining

Method chaining should have multiple methods spread across separate lines, and indented with four spaces:
$email->from('[email protected]')
->to('[email protected]')
->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 phpDocumentor79 tags:
• @author80
• @copyright81
• @deprecated82 Using the @version <vector> <description> format, where version and
description are mandatory. Version refers to the one it got deprecated in.
78 http://www.php-fig.org/psr/psr-2/
79 http://phpdoc.org
80 http://phpdoc.org/docs/latest/references/phpdoc/tags/author.html
81 http://phpdoc.org/docs/latest/references/phpdoc/tags/copyright.html
82 http://phpdoc.org/docs/latest/references/phpdoc/tags/deprecated.html

Coding Standards 133


CakePHP Cookbook Documentation, Release 3.8

• @example83
• @ignore84
• @internal85
• @link86
• @see87
• @since88
• @version89
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:


Type Description
mixed A variable with undefined (or multiple) type.
83 http://phpdoc.org/docs/latest/references/phpdoc/tags/example.html
84 http://phpdoc.org/docs/latest/references/phpdoc/tags/ignore.html
85 http://phpdoc.org/docs/latest/references/phpdoc/tags/internal.html
86 http://phpdoc.org/docs/latest/references/phpdoc/tags/link.html
87 http://phpdoc.org/docs/latest/references/phpdoc/tags/see.html
88 http://phpdoc.org/docs/latest/references/phpdoc/tags/since.html
89 http://phpdoc.org/docs/latest/references/phpdoc/tags/version.html

134 Chapter 5. Contributing


CakePHP Cookbook Documentation, Release 3.8

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_once90 function.

PHP Tags

Always use long tags (<?php ?>) instead of short tags (<? ?>). The short echo should be used in template files
(.ctp) where appropriate.
90 http://php.net/require_once

Coding Standards 135


CakePHP Cookbook Documentation, Release 3.8

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();

Member Visibility

Use PHP’s public, protected and private keywords for methods and variables.

Example Addresses

For all example URL and mail addresses use “example.com”, “example.org” and “example.net”, for example:

136 Chapter 5. Contributing


CakePHP Cookbook Documentation, Release 3.8

• Email: [email protected]
• WWW: http://www.example.com
• FTP: ftp://ftp.example.com
The “example.com” domain name has been reserved for this (see RFC 260691 ) and is recommended for use in docu-
mentation 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:


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 ap-
plicable.

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);

Careful when using empty()/isset()

While empty() is an easy to use function, it can mask errors and cause unintended effects when '0' and 0 are
given. When variables or properties are already defined, the usage of empty() is not recommended. When working
with variables, it is better to rely on type-coercion to boolean instead of empty():

91 https://tools.ietf.org/html/rfc2606.html

Coding Standards 137


CakePHP Cookbook Documentation, Release 3.8

function manipulate($var)
{
// Not recommended, $var is already defined in the scope
if (empty($var)) {
// ...
}

// Use boolean type coercion


if (!$var) {
// ...
}
if ($var) {
// ...
}
}

When dealing with defined properties you should favour null checks over empty()/isset() checks:

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) {
// ...
}
}

138 Chapter 5. Contributing


CakePHP Cookbook Documentation, Release 3.8

Backwards Compatibility Guide

Ensuring that you can upgrade your applications easily and smoothly is important to us. That’s why we only break
compatibility at major release milestones. You might be familiar with semantic versioning92 , which is the general
guideline we use on all CakePHP projects. In short, semantic versioning means that only major releases (such as 2.0,
3.0, 4.0) can break backwards compatibility. Minor releases (such as 2.1, 3.1, 3.2) may introduce new features, but
are not allowed to break compatibility. Bug fix releases (such as 2.1.2, 3.0.1) do not add new features, but fix bugs or
enhance performance only.

Note: Deprecations are removed with the next major version of the framework. It is advised to early on adapt your
code already each minor as outlined in the deprecation comments and the migration guides.

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 you’ve 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:
92 http://semver.org/

Backwards Compatibility Guide 139


CakePHP Cookbook Documentation, Release 3.8

If you. . . Backwards compatibility?


Typehint against the class Yes
Create a new instance Yes
Extend the class Yes
Access a public property Yes
Call a public method Yes
Extend a class and. . .
Override a public property Yes
Access a protected property No1
Override a protected property No1
Override a protected method No1
Call a protected method No1
Add a public property No
Add a public method No
Add an argument to an overridden method No1
Add a default argument value to an existing method argument 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:

In a minor release can you. . .


Classes
Remove a class No
Remove an interface No
Remove a trait No
Make final No
Make abstract No
Change name Yes2
Properties
Add a public property Yes
Remove a public property No
Add a protected property Yes
Remove a protected property Yes3
Methods
Add a public method Yes
Remove a public method No
Add a protected method Yes
Move to parent class Yes
Remove a protected method Yes3
Reduce visibility No
Change method name Yes2
Add a new argument with default value Yes
Add a new required argument to an existing method. No
Remove a default value from an existing argument No
Change method type void Yes
1 Your code may be broken by minor releases. Check the migration guide for details.

140 Chapter 5. Contributing


CakePHP Cookbook Documentation, Release 3.8

Deprecations

In each minor release, features may be deprecated. If features are deprecated, API documentation and runtime warn-
ings will be added. Runtime errors help you locate code that needs to be updated before it breaks. If you wish to
disable runtime warnings you can do so using the Error.errorLevel configuration value:

// in config/app.php
// ...
'Error' => [
'errorLevel' => E_ALL ^ E_USER_DEPRECATED,
]
// ...

Will disable runtime deprecation warnings.

2 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.

Backwards Compatibility Guide 141


CakePHP Cookbook Documentation, Release 3.8

142 Chapter 5. Contributing


CHAPTER 6

Installation

CakePHP has a few system requirements:


• HTTP Server. For example: Apache. Having mod_rewrite is preferred, but by no means required. You can also
use nginx, or Microsoft IIS if you prefer.
• PHP 5.6.0 or greater (including PHP 7.3).
• mbstring PHP extension
• intl PHP extension
• simplexml PHP extension
• PDO PHP extension

Note: 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 isn’t required, we imagine that most applications will utilize one. CakePHP supports a variety
of database storage engines:
• MySQL (5.5.3 or greater)
• MariaDB (5.5 or greater)
• PostgreSQL
• Microsoft SQL Server (2008 or higher)
• SQLite 3

143
CakePHP Cookbook Documentation, Release 3.8

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 your PHP version is up to date:

php -v

You should have PHP 5.6.0 (CLI) or higher. Your webserver’s PHP version must also be of 5.6.0 or higher, and should
be the same version your command line interface (CLI) uses.

Installing Composer

CakePHP uses Composer93 , a dependency management tool, as the officially supported method for installation.
• Installing Composer on Linux and macOS
1. Run the installer script as described in the official Composer documentation94 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

• Installing Composer on Windows


For Windows systems, you can download Composer’s Windows installer here95 . Further instructions for Com-
poser’s Windows installer can be found within the README here96 .

Create a CakePHP Project

You can create a new CakePHP application using composer’s create-project command :

composer create-project --prefer-dist cakephp/app my_app_name

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 Github97 .
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.
93 http://getcomposer.org
94 https://getcomposer.org/download/
95 https://github.com/composer/windows-setup/releases/
96 https://github.com/composer/windows-setup
97 https://github.com/cakephp/cakephp/tags

144 Chapter 6. Installation


CakePHP Cookbook Documentation, Release 3.8

Keeping Up To Date with the Latest CakePHP Changes

By default this is what your application composer.json looks like:

"require": {
"cakephp/cakephp": "3.8.*"
}

Each time you run php composer.phar update you will receive patch releases for this minor version. You can
instead change this to ^3.8 to also receive the latest stable minor releases of the 3.x branch.
If you want to stay up to date with the latest unreleased changes in CakePHP, designate dev-master as the package
version in your application’s composer.json:

"require": {
"cakephp/cakephp": "dev-master"
}

Be aware that this is not recommended, as your application can break when the next major version is released. Addi-
tionally, composer does not cache development branches, so it slows down consecutive composer installs/updates.

Installation using Oven

Another quick way to install CakePHP is via Oven98 . It is a small PHP script which checks the necessary system
requirements, and creates a new CakePHP application.

Note: IMPORTANT: This is not a deployment script. It is aimed to help developers install CakePHP for the first time
and set up a development environment quickly. Production environments should consider several other factors, like
file permissions, virtualhost configuration, etc.

Permissions

CakePHP uses the tmp directory for a number of different operations. Model descriptions, cached views, and session
information are 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. Composer’s 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.
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

98 https://github.com/CakeDC/oven

Permissions 145
CakePHP Cookbook Documentation, Release 3.8

In order to use the CakePHP console tools, you need to ensure that bin/cake file is executable. On *nix or macOS,
you can execute:
chmod +x bin/cake

On Windows, the .bat file should be executable already. If you are using a Vagrant, or any other virtualized environ-
ment, any shared directories need to be shared with execute permissions (Please refer to your virtualized environment’s
documentation on how to do this).
If, for whatever reason, you cannot change the permissions of the bin/cake file, you can run the CakePHP console
with:
php bin/cake.php

Development Server

A development installation is the fastest way to setup CakePHP. In this example, we use CakePHP’s console to run
PHP’s 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 there is conflict 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

This will serve your application at http://192.168.13.37:5673/.


That’s it! Your CakePHP application is up and running without having to configure a web server.

Note: Try bin/cake server -H 0.0.0.0 if the server is unreachable from other hosts.

Warning: The development server should never be used in a production environment. It is only intended as a
basic development server.

If you’d prefer to use a real web server, you should be able to move your CakePHP install (including the hidden files)
inside your web server’s document root. You should then be able to point your web-browser at the directory you
moved the files into and see your application in action.

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.
After installing your application using one of the methods above into the directory of your choosing - we’ll assume
you chose /cake_install - your production setup will look like this on the file system:

146 Chapter 6. Installation


CakePHP Cookbook Documentation, Release 3.8

/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, let’s 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, you’ll be presented with CakePHP’s 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 box–and usually does–we’ve 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.
1. Make sure that an .htaccess override is allowed and that AllowOverride is set to All for the correct Document-
Root. 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

Fire It Up 147
CakePHP Cookbook Documentation, Release 3.8

# 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 won’t 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/ [L]
RewriteRule (.*) webroot/$1 [L]
</IfModule>

CakePHP webroot directory (will be copied to your application’s 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:

<Directory />
Options FollowSymLinks
AllowOverride All
</Directory>
<Directory /var/www>
Options FollowSymLinks
AllowOverride All
Order Allow,Deny
Allow from all
</Directory>

On macOS, another solution is to use the tool virtualhostx99 to make a Virtual Host to point to your folder.
For many hosting services (GoDaddy, 1and1), your web server is being served from a user directory that al-
ready uses mod_rewrite. If you are installing CakePHP into a user directory (http://example.com/~username/
99 http://clickontyler.com/virtualhostx/

148 Chapter 6. Installation


CakePHP Cookbook Documentation, Release 3.8

cakephp/), or any other URL structure that already utilizes mod_rewrite, you’ll need to add RewriteBase state-
ments 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 Apache’s 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 prevent incorrect assets from being sent to index.php and instead display your web server’s 404
page.
Additionally you can create a matching HTML 404 page, or use the default built-in CakePHP 404 by adding an
ErrorDocument directive:

ErrorDocument 404 /404-not-found

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 on your setup, you will have to modify this, but at the very least,
you will need PHP running as a FastCGI instance. The following configuration redirects the request to webroot/
index.php:

location / {
try_files $uri $uri/ /index.php?$args;
}

A sample of the server directive is as follows:

server {
listen 80;
listen [::]:80;
server_name www.example.com;
return 301 http://example.com$request_uri;
}

server {

URL Rewriting 149


CakePHP Cookbook Documentation, Release 3.8

listen 80;
listen [::]:80;
server_name example.com;

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 fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_intercept_errors on;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}

Note: Recent configurations of PHP-FPM are set to listen to the unix php-fpm socket instead of TCP port 9000 on
address 127.0.0.1. If you get 502 bad gateway errors from the above configuration, try update fastcgi_pass to
use the unix socket path (eg: fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;) instead of the TCP port.

IIS7 (Windows hosts)

IIS7 does not natively support .htaccess files. While there are add-ons that can add this support, you can also import
htaccess rules into IIS to use CakePHP’s native rewrites. To do this, follow these steps:
1. Use Microsoft’s Web Platform Installer100 to install the URL Rewrite Module 2.0101 or download it directly
(32-bit102 / 64-bit103 ).
2. Create a new file called web.config in your CakePHP root folder.
3. Using Notepad or any XML-safe editor, copy the following code into your new web.config file:

<?xml version="1.0" encoding="UTF-8"?>


<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Exclude direct access to webroot/*"
stopProcessing="true">
<match url="^webroot/(.*)$" ignoreCase="false" />
<action type="None" />
</rule>
<rule name="Rewrite routed access to assets(img, css, files, js,
˓→favicon)"

100 http://www.microsoft.com/web/downloads/platform.aspx
101 http://www.iis.net/downloads/microsoft/url-rewrite
102 http://www.microsoft.com/en-us/download/details.aspx?id=5747
103 http://www.microsoft.com/en-us/download/details.aspx?id=7435

150 Chapter 6. Installation


CakePHP Cookbook Documentation, Release 3.8

stopProcessing="true">
<match url="^(font|img|css|files|js|favicon.ico)(.*)$" />
<action type="Rewrite" url="webroot/{R:1}{R:2}"
appendQueryString="false" />
</rule>
<rule name="Rewrite requested file/folder to index.php"
stopProcessing="true">
<match url="^(.*)$" ignoreCase="false" />
<action type="Rewrite" url="index.php"
appendQueryString="true" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>

Once the web.config file is created with the correct IIS-friendly rewrite rules, CakePHP’s links, CSS, JavaScript, and
rerouting should work correctly.

I Can’t Use URL Rewriting

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

'App' => [
// ...
// 'baseUrl' => env('SCRIPT_NAME'),
]

Also remove these .htaccess files:

/.htaccess
webroot/.htaccess

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

URL Rewriting 151


CakePHP Cookbook Documentation, Release 3.8

152 Chapter 6. Installation


CHAPTER 7

Configuration

While conventions remove the need to configure all of CakePHP, you’ll 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.

Configuring your Application

Configuration is generally stored in either PHP or INI files, and loaded during the application bootstrap. CakePHP
comes with one configuration file by default, but if required you can add additional configuration files and load them
in your application’s bootstrap code. Cake\Core\Configure is used for global configuration, and classes like
Cache provide config() methods to make configuration simple and transparent.

Loading Additional Configuration Files

If your application has many configuration options it can be helpful to split configuration into multiple files. After
creating each of the files in your config/ directory you can load them in bootstrap.php:

use Cake\Core\Configure;
use Cake\Core\Configure\Engine\PhpConfig;

Configure::config('default', new PhpConfig());


Configure::load('app', 'default', false);
Configure::load('other_config', 'default');

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.

153
CakePHP Cookbook Documentation, Release 3.8

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 don’t plan to use Apache’s mod_rewrite with CakePHP. Don’t 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 application’s 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 CDN’s 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 CDN’s 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 CDN’s 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:
• (bool) false - Doesn’t do anything (default)
• (bool) true - Appends the timestamp when debug is true
• (string) ‘force’ - Always appends the timestamp.
Changed in version 3.6.0: As of 3.6.0, you can override this global setting when linking assets using the
timestamp option.

154 Chapter 7. Configuration


CakePHP Cookbook Documentation, Release 3.8

Asset.cacheTime Sets the asset cache time. This determines the http header Cache-Control’s max-age, and the
http header’s Expire’s time for assets. This can take anything that you version of php’s strtotime function104
can take. The default is +1 day.

Using a CDN

To use a CDN for loading your static assets, change App.imageBaseUrl, App.cssBaseUrl, App.
jsBaseUrl to point the CDN URI, for example: https://mycdn.example.com/ (note the trailing /).
All images, scripts and styles loaded via HtmlHelper will prepend the absolute CDN path, matching the same relative
path used in the application. Please note there is a specific use case when using plugin based assets: plugins will not
use the plugin’s prefix when absolute ...BaseUrl URI is used, for example By default:
• $this->Helper->assetUrl('TestPlugin.logo.png') resolves to test_plugin/logo.png
If you set App.imageBaseUrl to https://mycdn.example.com/:
• $this->Helper->assetUrl('TestPlugin.logo.png') resolves to https://mycdn.
example.com/logo.png.

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.

Error and Exception Handling Configuration

See the Error and Exception Configuration for information on configuring error and exception handlers.

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.
104 http://php.net/manual/en/function.strtotime.php

Configuring your Application 155


CakePHP Cookbook Documentation, Release 3.8

Additional Class Paths

Additional class paths are setup through the autoloaders your application uses. When using composer to generate
your autoloader, you could do the following, to provide fallback paths for controllers in your application:

"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/"]
}
}

Plugin, View Template and Locale Paths

Since plugins, view templates and locales are not classes, they cannot have an autoloader configured. CakePHP
provides three Configure variables to setup additional paths for these resources. In your config/app.php you can set
these variables:

return [
// More configuration
'App' => [
'paths' => [
'plugins' => [
ROOT . DS . 'plugins' . DS,
'/path/to/other/plugins/'
],
'templates' => [
APP . 'Template' . DS,
APP . 'Template2' . DS
],
'locales' => [
APP . 'Locale' . DS
]
]
]
];

Paths should end with a directory separator, or they will not work properly.

Inflection Configuration

See the Inflection Configuration docs for more information.

156 Chapter 7. Configuration


CakePHP Cookbook Documentation, Release 3.8

Environment Variables

Many modern cloud providers, like Heroku, let you define environment variables for configuration data. You can
configure your CakePHP through environment variables in the 12factor app style105 . Environment variables allow
your application to require less state making your application easier to manage when it is deployed across a number
of environments.
As you can see in your app.php, the env() function is used to read configuration from the environment, and build the
application configuration. CakePHP uses DSN strings for databases, logs, email transports and cache configurations
allowing you to easily vary these libraries in each environment.
For local development, CakePHP leverages dotenv106 to allow easy local development using environment variables.
You will see a config/.env.default in your application. By copying this file into config/.env and cus-
tomizing the values you can configure your application.
You should avoid committing the config/.env file to your repository and instead use the config/.env.
default as a template with placeholder values so everyone on your team knows what environment variables are
in use and what should go in each one.
Once your environment variables have been set, you can use env() to read data from the environment:

$debug = env('APP_DEBUG', false);

The second value passed to the env function is the default value. This value will be used if no environment variable
exists for the given key.
Changed in version 3.5.0: dotenv library support was added to the application skeleton.

Configure Class

class Cake\Core\Configure
CakePHP’s 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 won’t end up
breaking the MVC structure CakePHP provides.

Writing Configuration data

static Cake\Core\Configure::write($key, $value)


Use write() to store data in the application’s configuration:

Configure::write('Company.name','Pizza, Inc.');
Configure::write('Company.slogan','Pizza for your body and soul');

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:


105 http://12factor.net/
106 https://github.com/josegonzalez/php-dotenv

Environment Variables 157


CakePHP Cookbook Documentation, Release 3.8

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.

Reading Configuration Data

static Cake\Core\Configure::read($key = null, $default = null)


Used to read configuration data from the application. If a key is supplied, the data is returned. Using our examples
from write() above, we can read that data back:

// Returns 'Pizza Inc.'


Configure::read('Company.name');

// Returns 'Pizza for your body and soul'


Configure::read('Company.slogan');

Configure::read('Company');
// Returns:
['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul'];

// Returns 'fallback' as Company.nope is undefined.


Configure::read('Company.nope', 'fallback');

If $key is left null, all values in Configure will be returned.


Changed in version 3.5.0: The $default parameter was added in 3.5.0
static Cake\Core\Configure::readOrFail($key)
Reads configuration data just like Cake\Core\Configure::read but expects to find a key/value pair. In case
the requested pair does not exist, a RuntimeException will be thrown:

Configure::readOrFail('Company.name'); // Yields: 'Pizza, Inc.'


Configure::readOrFail('Company.geolocation'); // Will throw an exception

Configure::readOrFail('Company');

// Yields:
['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul'];

New in version 3.1.7: Configure::readOrFail() was added in 3.1.7

Checking to see if Configuration Data is Defined

static Cake\Core\Configure::check($key)
Used to check if a key/path exists and has non-null value:

$exists = Configure::check('Company.name');

158 Chapter 7. Configuration


CakePHP Cookbook Documentation, Release 3.8

Deleting Configuration Data

static Cake\Core\Configure::delete($key)
Used to delete information from the application’s configuration:

Configure::delete('Company.name');

Reading & Deleting Configuration Data

static Cake\Core\Configure::consume($key)
Read and delete a key from Configure. This is useful when you want to combine reading and deleting values in a
single operation.
static Cake\Core\Configure::consumeOrFail($key)
Consumes configuration data just like Cake\Core\Configure::consume but expects to find a key/value pair.
In case the requested pair does not exist, a RuntimeException will be thrown:

Configure::consumeOrFail('Company.name'); // Yields: 'Pizza, Inc.'


Configure::consumeOrFail('Company.geolocation'); // Will throw an exception

Configure::consumeOrFail('Company');

// Yields:
['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul'];

New in version 3.6.0: Configure::readOrFail() was added in 3.6.0

Reading and writing configuration files

static Cake\Core\Configure::config($name, $engine)


CakePHP comes with two built-in configuration file engines. Cake\Core\Configure\Engine\PhpConfig
is able to read PHP config files, in the same format that Configure has historically read.
Cake\Core\Configure\Engine\IniConfig is able to read ini config files. See the PHP documenta-
tion107 for more information on the specifics of ini files. To use a core config engine, you’ll need to attach it to
Configure using Configure::config():

use Cake\Core\Configure\Engine\PhpConfig;

// Read config files from config


Configure::config('default', new PhpConfig());

// Read config files from another path.


Configure::config('default', new PhpConfig('/path/to/your/config/files/'));

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():

107 http://php.net/parse_ini_file

Reading and writing configuration files 159


CakePHP Cookbook Documentation, Release 3.8

// 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');

Loading Configuration Files

static Cake\Core\Configure::load($key, $config = ’default’, $merge = true)


Once you’ve attached a config engine to Configure you can load configuration files:

// Load my_file.php using the 'default' engine object.


Configure::load('my_file', '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.

Creating or Modifying Configuration Files

static Cake\Core\Configure::dump($key, $config = ’default’, $keys = [])


Dumps all or some of the data in Configure into a file or storage system supported by a config engine. The se-
rialization 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. Save all data in Configure to the file my_config.php:

Configure::dump('my_config', 'default');

Save only the error handling configuration:

Configure::dump('error', 'default', ['Error', 'Exception']);

Configure::dump() can be used to either modify or overwrite configuration files that are readable with
Configure::load()

Storing Runtime Configuration

static Cake\Core\Configure::store($name, $cacheConfig = ’default’, $data = null)


You can also store runtime configuration values for use in a future request. Since configure only remembers values for
the current request, you will need to store any modified configuration information if you want to use it in subsequent
requests:

160 Chapter 7. Configuration


CakePHP Cookbook Documentation, Release 3.8

// Store the current configuration in the 'user_1234' key in the 'default' cache.
Configure::store('user_1234', 'default');

Stored configuration data is persisted in the named cache configuration. See the Caching documentation for more
information on caching.

Restoring Runtime Configuration

static Cake\Core\Configure::restore($name, $cacheConfig = ’default’)


Once you’ve stored runtime configuration, you’ll probably need to restore it so you can access it again.
Configure::restore() does exactly that:

// Restore runtime configuration from the cache.


Configure::restore('user_1234', 'default');

When restoring configuration information it’s 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.

Configuration Engines

CakePHP provides the ability to load configuration files from a number of different sources, and features a pluggable
system for creating your own configuration engines108 . The built in configuration engines are:
• JsonConfig109
• IniConfig110
• PhpConfig111
By default your application will use PhpConfig.

Bootstrapping CakePHP

If you have any additional configuration needs, you should add them to your application’s 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.
• Defining cache configuration.
• Defining logging configuration.
• Loading custom inflections.
• Loading configuration files.
It might be tempting to place formatting functions there in order to use them in your controllers. As you’ll see in the
Controllers and Views sections there are better ways you add custom logic to your application.
108 https://api.cakephp.org/3.x/class-Cake.Core.Configure.ConfigEngineInterface.html
109 https://api.cakephp.org/3.x/class-Cake.Core.Configure.Engine.JsonConfig.html
110 https://api.cakephp.org/3.x/class-Cake.Core.Configure.Engine.IniConfig.html
111 https://api.cakephp.org/3.x/class-Cake.Core.Configure.Engine.PhpConfig.html

Bootstrapping CakePHP 161


CakePHP Cookbook Documentation, Release 3.8

Application::bootstrap()

In addition to the config/bootstrap.php file which should be used to configure low-level concerns of your application,
you can also use the Application::bootstrap() hook method to load/initialize plugins, and attach global
event listeners:

// in src/Application.php
namespace App;

use Cake\Core\Plugin;
use Cake\Http\BaseApplication;

class Application extends BaseApplication


{
public function bootstrap()
{
// Call the parent to `require_once` config/bootstrap.php
parent::bootstrap();

$this->addPlugin('MyPlugin', ['bootstrap' => true, 'routes' => true]);


}
}

Loading plugins/events in Application::bootstrap() makes Controller Integration Testing easier as events


and routes will be re-processed on each test method.

Disabling Generic Tables

While utilizing generic table classes - also called auto-tables - when quickly creating new applications and baking
models is useful, generic table class can make debugging more difficult in some scenarios.
You can check if any query was emitted from a generic table class via DebugKit via the SQL panel in DebugKit. If
you’re still having trouble diagnosing an issue that could be caused by auto-tables, you can throw an exception when
CakePHP implicitly uses a generic Cake\ORM\Table instead of your concrete class like so:

// In your bootstrap.php
use Cake\Event\EventManager;
// Prior to 3.6 use Cake\Network\Exception\InteralErrorException
use Cake\Http\Exception\InternalErrorException;

$isCakeBakeShellRunning = (PHP_SAPI === 'cli' && isset($argv[1]) && $argv[1] === 'bake
˓→');

if (!$isCakeBakeShellRunning) {
EventManager::instance()->on('Model.initialize', function($event) {
$subject = $event->getSubject();
if (get_class($subject) === 'Cake\ORM\Table') {
$msg = sprintf(
'Missing table class or incorrect alias when registering table class
˓→for database table %s.',

$subject->getTable());
throw new InternalErrorException($msg);
}
});
}

162 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 URL’s 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 application’s 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 a 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 article’s content:

163
CakePHP Cookbook Documentation, Release 3.8

$routes->connect('/articles/*', ['controller' => 'Articles', 'action' => 'view']);

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 restrict some parameters to conform to a regular expression:

$routes->connect(
'/articles/:id',
['controller' => 'Articles', 'action' => 'view']
)
->setPatterns(['id' => '\d+'])
->setPass(['id']);

// Prior to 3.5 use the options array


$routes->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:

// In routes.php
$routes->connect(
'/login',
['controller' => 'Users', 'action' => 'login'],
['_name' => 'login']
);

use Cake\Routing\Router;

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']);
});

164 Chapter 8. Routing


CakePHP Cookbook Documentation, Release 3.8

The above route would match /blog/ and send it to Blog\Controller\ArticlesController::index().


The application skeleton comes with a few routes to get you started. Once you’ve added your own routes, you can
remove the default routes if you don’t need them.

Connecting Routes

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. This method defaults to the / scope. To create a scope and connect some
routes we’ll use the scope() method:

// In config/routes.php
use Cake\Routing\Route\DashedRoute;

Router::scope('/', function ($routes) {


// Connect the generic fallback 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',
['targetKey' => 'targetValue'],
['option' => 'matchingRegex']
);

The first parameter is used to tell the router what sort of URL you’re 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 you’ve 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 defines the route ‘target’. This can be defined either as an
array, or as a destination string. A few examples of route targets are:

// Array target to an application controller


$routes->connect(
'/users/view/*',
['controller' => 'Users', 'action' => 'view']
);
// String target to an application controller. requires >=3.6.0
$routes->connect('/users/view/*', 'Users::view');

// Array target to a prefixed plugin controller


$routes->connect(
'/admin/cms/articles',
['prefix' => 'admin', 'plugin' => 'Cms', 'controller' => 'Articles', 'action' =>
˓→'index']

);
// String target to a prefixed plugin controller. requires >=3.6.0
$routes->connect('/admin/cms/articles', 'Cms.Admin/Articles::index');

Connecting Routes 165


CakePHP Cookbook Documentation, Release 3.8

The first route we connect matches URLs starting with /users/view and maps those requests to the
UsersController->view(). The trailing /* tells the router to pass any additional segments as method ar-
guments. For example, /users/view/123 would map to UsersController->view(123).
The above example also illustrates string targets. String targets provide a compact way to define a route’s destination.
String targets have the following syntax:

[Plugin].[Prefix]/[Controller]::[action]

Some example string targets are:

// Application controller
'Bookmarks::view'

// Application controller with prefix


Admin/Bookmarks::view

// Plugin controller
Cms.Articles::edit

// Prefixed plugin controller


Vendor/Cms.Management/Admin/Articles::view

New in version 3.6.0: String based route targets were added.


Earlier we used the greedy star (/*) to capture additional path segments, there is also the trailing star (/**). Using a
trailing double star, will capture the remainder of a URL as a single passed argument. This is useful when you want to
use an argument that included a / in it:

$routes->connect(
'/pages/**',
['controller' => 'Pages', 'action' => 'show']
);

The incoming URL of /pages/the-example-/-and-proof would result in a single passed argument of


the-example-/-and-proof.
The second parameter of connect() can define any parameters that compose the default route parameters:

$routes->connect(
'/government',
['controller' => 'Pages', 'action' => 'display', 5]
);

This example uses the second parameter of connect() to define default parameters. If you built an application 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.
A common use for routing is to rename controllers and their actions. Instead of accessing our users controller at
/users/some_action/5, we’d like to be able to access it through /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 UsersController. 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 that this

166 Chapter 8. Routing


CakePHP Cookbook Documentation, Release 3.8

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.
The routes we’ve connected so far will match any HTTP verb. If you are building a REST API you’ll often want to
map HTTP actions to different controller methods. The RouteBuilder provides helper methods that make defining
routes for specific HTTP verbs simpler:

// Create a route that only responds to GET requests.


$routes->get(
'/cooks/:id',
['controller' => 'Users', 'action' => 'view'],
'users:view'
);

// Create a route that only responds to PUT requests


$routes->put(
'/cooks/:id',
['controller' => 'Users', 'action' => 'update'],
'users:update'
);

The above routes map the same URL to different controller actions based on the HTTP verb used. GET requests will
go to the ‘view’ action, while PUT requests will go to the ‘update’ action. There are HTTP helper methods for:
• GET
• POST
• PUT
• PATCH
• DELETE
• OPTIONS
• HEAD
All of these methods return the route instance allowing you to leverage the fluent setters to further configure your
route.
New in version 3.5.0: The HTTP verb helper methods were added.

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->getParam() 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 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']
)->setPatterns(['id' => '[0-9]+']);

Connecting Routes 167


CakePHP Cookbook Documentation, Release 3.8

// Prior to 3.5 use the options array


$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;

// Create a builder with a different route class.


$routes->scope('/', function ($routes) {
$routes->setRouteClass(DashedRoute::class);
$routes->connect('/:controller/:id', ['action' => 'view'])
->setPatterns(['id' => '[0-9]+']);

// Prior to 3.5 use options array


$routes->connect(
'/:controller/:id',
['action' => 'view'],
['id' => '[0-9]+']
);
});

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->getParam('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:
// Prior to 3.5 use the options array instead of setPatterns()
$routes->connect(
'/:userShortcut',
['controller' => 'Teachers', 'action' => 'profile', 1],
)->setPatterns(['userShortcut' => '(?i:principal)']);

168 Chapter 8. Routing


CakePHP Cookbook Documentation, Release 3.8

One more example, and you’ll be a routing pro:

// Prior to 3.5 use the options array instead of setPatterns()


$routes->connect(
'/:controller/:year/:month/:day',
['action' => 'index']
)->setPatterns([
'year' => '[12][0-9]{3}',
'month' => '0[1-9]|1[012]',
'day' => '0[1-9]|[12][0-9]|3[01]'
]);

This is rather involved, but shows how powerful routes can be. The URL supplied has four route elements. The first is
familiar to us: it’s 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 (capturing groups) 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, hand-
ing the requests to the index() actions of their respective controllers, with the date parameters in
$this->request->getParam().
As of 3.6.0 you can use {var} for route elements instead of :var. This new parameter style enables route elements
to be embedded in non-capturing blocks. For example:

$routes->connect(
'/images/resize/{id}/{width}x{height}',
['controller' => 'Images', 'action' => 'view']
);

Would be impossible to define using :var style placeholders.

Reserved Route Elements

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.

Connecting Routes 169


CakePHP Cookbook Documentation, Release 3.8

• # 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 RESTful Routing.
• _name Name of route. If you have setup named routes, you can use this key to specify it.

Configuring Route Options

There are a number of route options that can be set on each route. After connecting a route you can use its fluent
builder methods to further configure the route. These methods replace many of the keys in the $options parameter
of connect():
$routes->connect(
'/:lang/articles/:slug',
['controller' => 'Articles', 'action' => 'view']
)
// Allow GET and POST requests.
->setMethods(['GET', 'POST'])

// Only match on the blog subdomain.


->setHost('blog.example.com')

// Set the route elements that should be converted to passed arguments


->setPass(['slug'])

// Set the matching patterns for route elements


->setPatterns([
'slug' => '[a-z0-9-_]+',
'lang' => 'en|fr|es',
])

// Also allow JSON file extensions


->setExtensions(['json'])

// Set lang to be a persistent parameter


->setPersist(['lang']);

New in version 3.5.0: Fluent builder methods were added in 3.5.0

Passing Parameters to Action

When connecting routes using Route Elements you may want to have routed elements be passed arguments instead.
The pass option whitelists which route elements should also be made available as arguments passed into the controller
functions:
// src/Controller/BlogsController.php
public function view($articleId = null, $slug = null)
{
// Some code here...
}

// routes.php
Router::scope('/', function ($routes) {
$routes->connect(
'/blog/:id-:slug', // E.g. /blog/3-CakePHP_Rocks

170 Chapter 8. Routing


CakePHP Cookbook Documentation, Release 3.8

['controller' => 'Blogs', 'action' => 'view']


)
// Define the route elements in the route template
// to pass as function arguments. Order matters since this
// will simply map ":id" to $articleId in your action
->setPass(['id', 'slug'])
// Define a pattern that `id` must match.
->setPatterns([
'id' => '[0-9]+',
]);
});

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'
]);

Using Named Routes

Sometimes you’ll find typing out all the URL parameters for a route too verbose, or you’d like to take advantage of
the performance improvements that named routes have. When connecting routes you can specifiy a _name option,
this option can be used in reverse routing to identify the route you want to use:

// Connect a route with a name.


$routes->connect(
'/login',
['controller' => 'Users', 'action' => 'login'],
['_name' => 'login']
);

// Name a verb specific route (3.5.0+)


$routes->post(
'/logout',
['controller' => 'Users', 'action' => 'logout'],
'logout'
);

// Generate a URL using a named route.


$url = Router::url(['_name' => 'logout']);

// Generate a URL using a named route,

Connecting Routes 171


CakePHP Cookbook Documentation, Release 3.8

// with some query string args.


$url = Router::url(['_name' => 'login', 'username' => 'jimmy']);

If your route template contains any route elements like :controller you’ll 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->get('/ping', ['controller' => 'Pings'], '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 you’d expect:

Router::plugin('Contacts', ['_namePrefix' => 'contacts:'], function ($routes) {


$routes->scope('/api', ['_namePrefix' => 'api:'], function ($routes) {
// This route's name will be `contacts:api:ping`
$routes->get('/ping', ['controller' => 'Pings'], 'ping');
});
});

// Generate a URL for the ping route


Router::url(['_name' => 'contacts:api:ping']);

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:

172 Chapter 8. Routing


CakePHP Cookbook Documentation, Release 3.8

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 application’s Controller namespace. By having pre-
fixes 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. Us-
ing 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:

Router::prefix('admin', ['param' => 'value'], function ($routes) {


// Routes connected here are prefixed with '/admin' and
// have the 'param' routing key set.
$routes->connect('/:controller');
});

You can define prefixes inside plugin scopes as well:

Router::plugin('DebugKit', function ($routes) {


$routes->prefix('admin', function ($routes) {
$routes->connect('/:controller');
});
});

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 from the controller methods through
$this->request->getParam('prefix')
When using prefix routes it’s important to set the prefix option. Here’s how to build this link using the HTML helper:

Connecting Routes 173


CakePHP Cookbook Documentation, Release 3.8

// 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.

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
plugin’s 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. The Plugin Routes section has more information on building plugin routes.

Creating Links to Plugin Routes

You can create links that point to a plugin, by adding the plugin key to your URL array:

echo $this->Html->link(
'New todo',

174 Chapter 8. Routing


CakePHP Cookbook Documentation, Release 3.8

['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.

SEO-Friendly Routing

Some developers prefer to use dashes in URLs, as it’s 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);
});

Matching Specific HTTP Methods

Routes can match specific HTTP methods using the HTTP verb helper methods:

Router::scope('/', function($routes) {
// This route only matches on POST requests.
$routes->post(
'/reviews/start',
['controller' => 'Reviews', 'action' => 'start']
);

// Match multiple verbs


// Prior to 3.5 use $options['_method'] to set method
$routes->connect(
'/reviews/start',
[
'controller' => 'Reviews',
'action' => 'start',
]
)->setMethods(['POST', 'PUT']);
});

You can match multiple HTTP methods by using an array. Because the _method parameter is a routing key, it
participates in both URL parsing and URL generation. To generate URLs for method specific routes you’ll need to
include the _method key when generating the URL:

Connecting Routes 175


CakePHP Cookbook Documentation, Release 3.8

$url = Router::url([
'controller' => 'Reviews',
'action' => 'start',
'_method' => 'POST',
]);

Matching Specific Hostnames

Routes can use the _host option to only match specific hosts. You can use the *. wildcard to match any subdomain:

Router::scope('/', function($routes) {
// This route only matches on http://images.example.com
// Prior to 3.5 use the _host option
$routes->connect(
'/images/default-logo.png',
['controller' => 'Images', 'action' => 'default']
)->setHost('images.example.com');

// This route only matches on http://*.example.com


$routes->connect(
'/images/old-log.png',
['controller' => 'Images', 'action' => 'oldLogo']
)->setHost('*.example.com');
});

The _host option is also used in URL generation. If your _host option specifies an exact domain, that domain will
be included in the generated URL. However, if you use a wildcard, then you will need to provide the _host parameter
when generating URLs:

// If you have this route


$routes->connect(
'/images/old-log.png',
['controller' => 'Images', 'action' => 'oldLogo']
)->setHost('images.example.com');

// You need this to generate a url


echo Router::url([
'controller' => 'Images',
'action' => 'oldLogo',
'_host' => 'images.example.com',
]);

New in version 3.4.0: The _host option was added in 3.4.0

Routing File Extensions

static Cake\Routing\Router::extensions(string|array|null $extensions, $merge = true)


To handle different file extensions with your routes, you can define extensions on a global, as well as on a scoped level.
Defining global extensions can be achieved via the routers static Router::extensions() method:

Router::extensions(['json', 'xml']);
// ...

This will affect all routes that are being connected afterwards, no matter their scope.

176 Chapter 8. Routing


CakePHP Cookbook Documentation, Release 3.8

In order to restrict extensions to specific scopes, you can define them using the
Cake\Routing\RouteBuilder::setExtensions() method:

Router::scope('/', function ($routes) {


// Prior to 3.5.0 use `extensions()`
$routes->setExtensions(['json', 'xml']);
});

This will enable the named extensions for all routes that are being connected in that scope after the
setExtensions() call, including those that are being connected in nested scopes. Similar to the global
Router::extensions() method, any routes connected prior to the call 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.
Also be aware that re-opened scopes will not inherit extensions defined in previously opened scopes.

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) {


// Prior to 3.5.0 use `extensions()`
$routes->setExtensions(['json', 'xml', 'html']);
$routes->connect(
'/:title',
['controller' => 'Pages', 'action' => 'view']
)->setPass(['title']);
});

Then to create links which map back to the routes simply use:

$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.

Connecting Scoped Middleware

While Middleware can be applied to your entire application, applying middleware to specific routing scopes offers
more flexibility, as you can apply middleware only where it is needed allowing your middleware to not concern itself
with how/where it is being applied.

Note: Applied scoped middleware will be run by RoutingMiddleware, normally at the end of your application’s
middleware queue.

Before middleware can be applied to a scope, it needs to be registered into the route collection:

// in config/routes.php
use Cake\Http\Middleware\CsrfProtectionMiddleware;
use Cake\Http\Middleware\EncryptedCookieMiddleware;

Connecting Scoped Middleware 177


CakePHP Cookbook Documentation, Release 3.8

Router::scope('/', function ($routes) {


$routes->registerMiddleware('csrf', new CsrfProtectionMiddleware());
$routes->registerMiddleware('cookies', new EncryptedCookieMiddleware());
});

Once registered, scoped middleware can be applied to specific scopes:

$routes->scope('/cms', function ($routes) {


// Enable CSRF & cookies middleware
$routes->applyMiddleware('csrf', 'cookies');
$routes->get('/articles/:action/*', ['controller' => 'Articles'])
});

In situations where you have nested scopes, inner scopes will inherit the middleware applied in the containing scope:

$routes->scope('/api', function ($routes) {


$routes->applyMiddleware('ratelimit', 'auth.api');
$routes->scope('/v1', function ($routes) {
$routes->applyMiddleware('v1compat');
// Define routes here.
});
});

In the above example, the routes defined in /v1 will have ‘ratelimit’, ‘auth.api’, and ‘v1compat’ middleware applied.
If you re-open a scope, the middleware applied to routes in each scope will be isolated:

$routes->scope('/blog', function ($routes) {


$routes->applyMiddleware('auth');
// Connect the authenticated actions for the blog here.
});
$routes->scope('/blog', function ($routes) {
// Connect the public actions for the blog here.
});

In the above example, the two uses of the /blog scope do not share middleware. However, both of these scopes will
inherit middleware defined in their enclosing scopes.

Grouping Middleware

To help keep your route code DRY (Do not Repeat Yourself) middleware can be combined into groups. Once com-
bined groups can be applied like middleware can:

$routes->registerMiddleware('cookie', new EncryptedCookieMiddleware());


$routes->registerMiddleware('auth', new AuthenticationMiddleware());
$routes->registerMiddleware('csrf', new CsrfProtectionMiddleware());
$routes->middlewareGroup('web', ['cookie', 'auth', 'csrf']);

// Apply the group


$routes->applyMiddleware('web');

New in version 3.5.0: Scoped middleware & middleware groups were added in 3.5.0

178 Chapter 8. Routing


CakePHP Cookbook Documentation, Release 3.8

RESTful Routing

Router makes it easy to generate RESTful routes for your controllers. RESTful routes are helpful when you are
creating API endpoints for your application. If we wanted to allow REST access to a recipe controller, we’d do
something like this:

// In config/routes.php...

Router::scope('/', function ($routes) {


// Prior to 3.5.0 use `extensions()`
$routes->setExtensions(['json']);
$routes->resources('Recipes');
});

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 URL.format Controller action invoked


GET /recipes.format RecipesController::index()
GET /recipes/123.format RecipesController::view(123)
POST /recipes.format RecipesController::add()
PUT /recipes/123.format RecipesController::edit(123)
PATCH /recipes/123.format RecipesController::edit(123)
DELETE /recipes/123.format RecipesController::delete(123)

The HTTP method being used is detected from a few different sources. The sources in order of preference are:
1. The _method POST variable
2. The X_HTTP_METHOD_OVERRIDE header.
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.

Creating Nested Resource Routes

Once you have connected resources in a scope, you can connect routes for sub-resources as well. Sub-resource routes
will be prepended by the original resource name and a id parameter. For example:

Router::scope('/api', function ($routes) {


$routes->resources('Articles', function ($routes) {
$routes->resources('Comments');
});
});

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

You can get the article_id in CommentsController by:

$this->request->getParam('article_id');

RESTful Routing 179


CakePHP Cookbook Documentation, Release 3.8

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']);
});
});

The above would map the ‘Comments’ resource to the App\Controller\Articles\CommentsController.


Having separate controllers lets you keep your controller logic simpler. The prefixes created this way are compatible
with Prefix Routing.

Note: While you can nest resources as deeply as you require, it is not recommended to nest more than 2 resources
together.

New in version 3.3: The prefix option was added to resources() in 3.3.

Limiting the Routes Created

By default CakePHP will connect 6 routes for each resource. If you’d like to only connect specific resource routes you
can use the only option:
$routes->resources('Articles', [
'only' => ['index', 'view']
]);

Would create read only resource routes. The route names are create, update, view, index, and delete.

Changing the Controller Actions Used

You may need to change the controller action names that are used when connecting routes. For example, if your
edit() action is called put() you can use the actions key to rename the actions used:
$routes->resources('Articles', [
'actions' => ['update' => 'put', 'create' => 'add']
]);

The above would use put() for the edit() action, and add() instead of create().

Mapping Additional Resource Routes

You can map additional resource methods using the map option:
$routes->resources('Articles', [
'map' => [
'deleteAll' => [
'action' => 'deleteAll',
'method' => 'DELETE'
]
]
]);
// This would connect /articles/deleteAll

180 Chapter 8. Routing


CakePHP Cookbook Documentation, Release 3.8

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.

Custom Route Classes for Resource Routes

You can provide connectOptions key in the $options array for resources() to provide custom setting used
by connect():

Router::scope('/', function ($routes) {


$routes->resources('Books', [
'connectOptions' => [
'routeClass' => 'ApiRoute',
]
];
});

URL Inflection for Resource Routes

By default, multi-worded controllers’ URL fragments are the underscored form of the controller’s name. E.g.,
BlogPostsController’s URL fragment would be /blog_posts.
You can specify an alternative inflection type using the inflect option:

Router::scope('/', function ($routes) {


$routes->resources('BlogPosts', [
'inflect' => 'dasherize' // Will use ``Inflector::dasherize()``
]);
});

The above will generate URLs styled like: /blog-posts.

Note: As of CakePHP 3.1 the official app skeleton uses DashedRoute as its default route class. Using the
'inflect' => 'dasherize' option when connecting resource routes is recommended for URL consistency.

Changing the Path Element

By default resource routes use an inflected form of the resource name for the URL segment. You can set a custom
URL segment with the path option:

RESTful Routing 181


CakePHP Cookbook Documentation, Release 3.8

Router::scope('/', function ($routes) {


$routes->resources('BlogPosts', ['path' => 'posts']);
});

New in version 3.5.0: The path option was added in 3.5.0

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->getParam('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());
}
}

You would get the following output:

Array
(
[0] => recent
[1] => mark
)

This same data is also available at $this->request->getParam('pass') in your controllers, views, and
helpers. The values in the pass array are numerically indexed based on the order they appear in the called URL:

debug($this->request->getParam('pass'));

Either of the above would output:

Array
(
[0] => recent
[1] => mark
)

When generating URLs, using a routing array you add passed arguments as values without string keys in the array:

['controller' => 'Articles', 'action' => 'view', 5]

Since 5 has a numeric key, it is treated as a passed argument.

182 Chapter 8. Routing


CakePHP Cookbook Documentation, Release 3.8

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 ‘posts’ 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.
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.
• _method Define the HTTP verb the URL is for.
• _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.

Generating URLs 183


CakePHP Cookbook Documentation, Release 3.8

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 don’t 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.
);
})

Redirects /home/* to /articles/view and passes the parameters to /articles/view. Using an array as
the redirect destination allows you to use other routes to define where a URL string should be redirected to. You can
redirect to external locations using string URLs as the destination:
Router::scope('/', function ($routes) {
$routes->redirect('/articles/*', 'http://google.com', ['status' => 302]);
});

This would redirect /articles/* to http://google.com with a HTTP status of 302.

Entity Routing

Entity routing allows you to use an entity, an array or object implement ArrayAccess as the source of routing
parameters. This allows you to refactor routes more easily, and generate URLs with less code. For example, if you
start off with a route that looks like:
$routes->get(
'/view/:id',
['controller' => 'Articles', 'action' => 'view'],
'articles:view'
);

You can generate URLs to this route using:


// $article is an entity in the local scope.
Router::url(['_name' => 'articles:view', 'id' => $article->id]);

Later on, you may want to expose the article slug in the URL for SEO purposes. In order to do this you would need to
update everywhere you generate a URL to the articles:view route, which could take some time. If we use entity
routes we pass the entire article entity into URL generation allowing us to skip any rework when URLs require more
parameters:
use Cake\Routing\Route\EntityRoute;

// Create entity routes for the rest of this scope.


$routes->setRouteClass(EntityRoute::class);

184 Chapter 8. Routing


CakePHP Cookbook Documentation, Release 3.8

// Create the route just like before.


$routes->get(
'/view/:id',
['controller' => 'Articles', 'action' => 'view'],
'articles:view'
);

Now we can generate URLs using the _entity key:

Router::url(['_name' => 'articles:view', '_entity' => $article]);

This will extract both the id property and the slug property out of the provided entity.
New in version 3.6.0: Entity routing was added in 3.6.0

Custom Route Classes

Custom route classes allow you to extend and change how individual routes parse requests and handle reverse routing.
Route classes have a few conventions:
• Route classes are expected to be found in the Routing\\Route namespace of your application or plugin.
• Route classes should extend Cake\Routing\Route.
• Route classes should implement one or both of match() and/or parse().
The parse() method is used to parse an incoming URL. It should generate an array of request parameters that can
be resolved into a controller & action. Return false from this method to indicate a match failure.
The match() method is used to match an array of URL parameters and create a string URL. If the URL parameters
do not match the route false should be returned.
You can use a custom route class when making a route by using the routeClass option:

$routes->connect(
'/:slug',
['controller' => 'Articles', 'action' => 'view'],
['routeClass' => 'SlugRoute']
);

// Or by setting the routeClass in your scope.


$routes->scope('/', function ($routes) {
//Prior to 3.5.0 use `routeClass()`
$routes->setRouteClass('SlugRoute');
$routes->connect(
'/:slug',
['controller' => 'Articles', 'action' => 'view']
);
});

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.

Default Route Class

static Cake\Routing\Router::defaultRouteClass($routeClass = null)

Custom Route Classes 185


CakePHP Cookbook Documentation, Release 3.8

If you want to use an alternate route class for all your routes besides the default Route, you can do so by calling
Router::defaultRouteClass() before setting up any routes and avoid having to specify the routeClass
option for each route. For example using:

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);

Is equivalent to the following explicit calls:

use Cake\Routing\Route\DashedRoute;

$routes->connect('/:controller', ['action' => 'index'], ['routeClass' =>


˓→DashedRoute::class]);

$routes->connect('/:controller/:action/*', [], ['routeClass' => 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.

Creating Persistent URL Parameters

You can hook into the URL generation process using URL filter functions. Filter functions are called before the URLs
are matched against the routes, this allows you to prepare URLs before routing.
Callback filter functions should expect the following parameters:
• $params The URL parameters being processed.
• $request The current request.
The URL filter function should always return the parameters even if unmodified.
URL filters allow you to implement features like persistent parameters:

Router::addUrlFilter(function ($params, $request) {


if ($request->getParam('lang') && !isset($params['lang'])) {
$params['lang'] = $request->getParam('lang');
}

186 Chapter 8. Routing


CakePHP Cookbook Documentation, Release 3.8

return $params;
});

Filter functions are applied in the order they are connected.


Another use case is changing a certain route on runtime (plugin routes for example):

Router::addUrlFilter(function ($params, $request) {


if (empty($params['plugin']) || $params['plugin'] !== 'MyPlugin' || empty($params[
˓→'controller'])) {

return $params;
}
if ($params['controller'] === 'Languages' && $params['action'] === 'view') {
$params['controller'] = 'Locations';
$params['action'] = 'index';
$params['language'] = $params[0];
unset($params[0]);
}
return $params;
});

This will alter the following route:

Router::url(['plugin' => 'MyPlugin', 'controller' => 'Languages', 'action' => 'view',


˓→'es']);

into this:

Router::url(['plugin' => 'MyPlugin', 'controller' => 'Locations', 'action' => 'index',


˓→ 'language' => 'es']);

Warning: If you are using the caching features of routing-middleware you must define the URL filters in your
application bootstrap() as filters are not part of the cached data.

Handling Named Parameters in URLs

Although named parameters were removed in CakePHP 3.0, applications may have published URLs containing them.
You can continue to accept URLs containing named parameters.
In your controller’s beforeFilter() method you can call parseNamedParams() to extract any named param-
eters from the passed arguments:

public function beforeFilter(Event $event)


{
parent::beforeFilter($event);
$this->request = Router::parseNamedParams($this->request);
}

This will populate $this->request->getParam('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.

Handling Named Parameters in URLs 187


CakePHP Cookbook Documentation, Release 3.8

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 plugin’s 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']);

• RoutingFilter applies application routing rules to the request URL. Populates


$request->getParam() with the results of routing.
• ControllerFactory uses $request->getParam() 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 application’s 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; let’s take
a look at how they are added:

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():

188 Chapter 8. Routing


CakePHP Cookbook Documentation, Release 3.8

use Cake\Routing\Filter\RoutingFilter;

DispatcherFactory::add(new RoutingFilter());

Configuring Filter Order

When adding filters, you can control the order they are invoked in using event handler priorities. While filters can
define a default priority using the $_priority property, you can set a specific priority when attaching the filter:

DispatcherFactory::add('Asset', ['priority' => 1]);


DispatcherFactory::add(new AssetFilter(['priority' => 1]));

The higher the priority the later this filter will be invoked.

Conditionally Applying Filters

If you don’t 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.

Building a Filter

To create a filter, define a class in src/Routing/Filter. In this example, we’ll 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->getData('request');
$response = $event->getData('response');
if (!$request->getCookie('landing_page')) {
$response->cookie([
'name' => 'landing_page',

Handling Named Parameters in URLs 189


CakePHP Cookbook Documentation, Release 3.8

'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 ServerRequest and Response objects
(Cake\Http\ServerRequest and Cake\Http\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.

Let’s now create another filter for altering response headers in any public page, in our case it would be anything served
from the PagesController:

namespace App\Routing\Filter;

use Cake\Event\Event;
use Cake\Routing\DispatcherFilter;

class HttpCacheFilter extends DispatcherFilter


{

public function afterDispatch(Event $event)


{
$request = $event->getData('request');
$response = $event->getData('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,

190 Chapter 8. Routing


CakePHP Cookbook Documentation, Release 3.8

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.

Handling Named Parameters in URLs 191


CakePHP Cookbook Documentation, Release 3.8

192 Chapter 8. Routing


CHAPTER 9

Request & Response Objects

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\Http\ServerRequest
ServerRequest 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 ServerRequest 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 client’s 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.
As of 3.4.0, CakePHP’s request object implements the PSR-7 ServerRequestInterface112 making it easier to use li-
braries from outside of CakePHP.

Request Parameters

The request exposes routing parameters through the getParam() method:


112 http://www.php-fig.org/psr/psr-7/

193
CakePHP Cookbook Documentation, Release 3.8

$controllerName = $this->request->getParam('controller');

// Prior to 3.4.0
$controllerName = $this->request->param('controller');

To get all routing parameters as an array use getAttribute():

$parameters = $this->request->getAttribute('params');

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:

// Passed arguments
$passedArgs = $this->request->getParam('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 routing 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.

Query String Parameters

Cake\Http\ServerRequest::getQuery($name)
Query string parameters can be read using the getQuery() method:

// URL is /posts/index?page=1&sort=title
$page = $this->request->getQuery('page');

// Prior to 3.4.0
$page = $this->request->query('page');

You can either directly access the query property, or you can use getQuery() method to read the URL query array
in an error-free manner. Any keys that do not exist will return null:

$foo = $this->request->getQuery('value_that_does_not_exist');
// $foo === null

// You can also provide default values


$foo = $this->request->getQuery('does_not_exist', 'default val');

If you want to access all the query parameters you can use getQueryParams():

$query = $this->request->getQueryParams();

New in version 3.4.0: getQueryParams() and getQuery() were added in 3.4.0

194 Chapter 9. Request & Response Objects


CakePHP Cookbook Documentation, Release 3.8

Request Body Data

Cake\Http\ServerRequest::getData($name, $default = null)


All POST data can be accessed using Cake\Http\ServerRequest::getData(). Any form data that contains
a data prefix will have that data prefix removed. For example:

// An input with a name attribute equal to 'MyModel[title]' is accessible at


$title = $this->request->getData('MyModel.title');

Any keys that do not exist will return null:

$foo = $this->request->getData('Value.that.does.not.exist');
// $foo == null

PUT, PATCH or DELETE Data

Cake\Http\ServerRequest::input($callback[, $options ])
When building REST services, you often accept request data on PUT and DELETE requests. Any application/
x-www-form-urlencoded request body data will automatically be parsed and set to $this->data for PUT and
DELETE requests. If you are accepting JSON or XML data, see below for how you can access those request bodies.
When accessing the input data, you can decode it with an optional function. This is useful when interacting with
XML or JSON request body content. Additional parameters for the decoding function can be passed as arguments to
input():

$jsonData = $this->request->input('json_decode');

Environment Variables (from $_SERVER and $_ENV)

Cake\Http\ServerRequest::env($key, $value = null)


ServerRequest::env() is a wrapper for env() global function and acts as a getter/setter for environment
variables without having to modify globals $_SERVER and $_ENV:

// Get the host


$host = $this->request->env('HTTP_HOST');

// Set a value, generally helpful in testing.


$this->request->env('REQUEST_METHOD', 'POST');

To access all the environment variables in a request use getServerParams():

$env = $this->request->getServerParams();

New in version 3.4.0: getServerParams() was added in 3.4.0

XML or JSON Data

Applications employing REST often exchange data in non-URL-encoded post bodies. You can read input data in any
format using Http\ServerRequest::input(). By providing a decoding function, you can receive the content
in a deserialized format:

Request 195
CakePHP Cookbook Documentation, Release 3.8

// Get JSON encoded data submitted to a PUT/POST action


$jsonData = $this->request->input('json_decode');

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, Http\ServerRequest::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']);

Path Information

The request object also provides useful information about the paths in your application. The base and webroot
attributes are useful for generating URLs, and determining whether or not your application is in a subdirectory. The
attributes you can use are:

// Assume the current request URL is /subdir/articles/edit/1?page=1

// Holds /subdir/articles/edit/1?page=1
$here = $request->getRequestTarget();

// Holds /subdir
$base = $request->getAttribute('base');

// Holds /subdir/
$base = $request->getAttribute('webroot');

// Prior to 3.4.0
$webroot = $request->webroot;
$base = $request->base;
$here = $request->here();

Checking Request Conditions

Cake\Http\ServerRequest::is($type, $args...)
The request object provides an easy way to inspect certain conditions in a given request. By using the is() method
you can check a number of common conditions, as well as inspect other application specific request criteria:

$isPost = $this->request->is('post');

You can also extend the request detectors that are available, by using
Cake\Http\ServerRequest::addDetector() to create new kinds of detectors. There are different
types of detectors that you can create:
• Environment value comparison - Compares a value fetched from env() for equality with the provided value.
• Header value comparison - If the specified header exists with the specified value, or if the callable returns true.
• 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. Sub-
sequent calls to add an already defined options detector will merge the options.

196 Chapter 9. Request & Response Objects


CakePHP Cookbook Documentation, Release 3.8

• 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\Http\ServerRequest::addDetector($name, $options)
Some examples would be:

// Add an environment detector.


$this->request->addDetector(
'post',
['env' => 'REQUEST_METHOD', 'value' => 'POST']
);

// Add a pattern value detector.


$this->request->addDetector(
'iphone',
['env' => 'HTTP_USER_AGENT', 'pattern' => '/iPhone/i']
);

// Add an option detector


$this->request->addDetector('internalIp', [
'env' => 'CLIENT_IP',
'options' => ['192.168.0.101', '192.168.0.100']
]);

// Add a header detector with value comparison


$this->request->addDetector('fancy', [
'env' => 'CLIENT_IP',
'header' => ['X-Fancy' => 1]
]);

// Add a header detector with callable comparison


$this->request->addDetector('fancy', [
'env' => 'CLIENT_IP',
'header' => ['X-Fancy' => function ($value, $header) {
return in_array($value, ['1', '0', 'yes', 'no'], true);
}]
]);

// Add a callback detector. Must be a valid callable.


$this->request->addDetector(
'awesome',
function ($request) {
return $request->getParam('awesome');
}
);

// Add a detector that uses multiple condition types


// If any type matches the check passes.
$this->request->addDetector(
'csv',
[
'accept' => ['text/csv'],
'param' => '_ext',
'value' => 'csv',
]
);

Request 197
CakePHP Cookbook Documentation, Release 3.8

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.
• 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.
ServerRequest also includes methods like Cake\Http\ServerRequest::domain(),
Cake\Http\ServerRequest::subdomains() and Cake\Http\ServerRequest::host() to
make applications that use subdomains simpler.

Session Data

To access the session for a given request use the session() method:

$userName = $this->request->session()->read('Auth.User.name');

For more information, see the Sessions documentation for how to use the session object.

Host and Domain Name

Cake\Http\ServerRequest::domain($tldLength = 1)
Returns the domain name your application is running on:

// Prints 'example.org'
echo $request->domain();

Cake\Http\ServerRequest::subdomains($tldLength = 1)
Returns the subdomains your application is running on as an array:

// Returns ['my', 'dev'] for 'my.dev.example.org'


$subdomains = $request->subdomains();

Cake\Http\ServerRequest::host()
Returns the host your application is on:

198 Chapter 9. Request & Response Objects


CakePHP Cookbook Documentation, Release 3.8

// Prints 'my.dev.example.org'
echo $request->host();

Reading the HTTP Method

Cake\Http\ServerRequest::getMethod()
Returns the HTTP method the request was made with:

// Output POST
echo $request->getMethod();

// Prior to 3.4.0
echo $request->method();

Restricting Which HTTP method an Action Accepts

Cake\Http\ServerRequest::allowMethod($methods)
Set allowed HTTP methods. If not matched, will throw MethodNotAllowedException. The 405 response will
include the required Allow header with the passed methods:

public function delete()


{
// Only accept POST and DELETE requests
$this->request->allowMethod(['post', 'delete']);
...
}

Reading HTTP Headers

Allows you to access any of the HTTP_* headers that were used for the request. For example:

// Get the header as a string


$userAgent = $this->request->getHeaderLine('User-Agent');

// Get an array of all values.


$acceptHeader = $this->request->getHeader('Accept');

// Check if a header exists


$hasAcceptHeader = $this->request->hasHeader('Accept');

// Prior to 3.4.0
$userAgent = $this->request->header('User-Agent');

While some apache installs don’t make the Authorization header accessible, CakePHP will make it available
through apache specific methods as required.
Cake\Http\ServerRequest::referer($local = false)
Returns the referring address for the request.
Cake\Http\ServerRequest::clientIp()
Returns the current visitor’s IP address.

Request 199
CakePHP Cookbook Documentation, Release 3.8

Trusting Proxy Headers

If your application is behind a load balancer or running on a cloud service, you will often get the load balancer host,
port and scheme in your requests. Often load balancers will also send HTTP-X-Forwarded-* headers with the
original values. The forwarded headers will not be used by CakePHP out of the box. To have the request object use
these headers set the trustProxy property to true:

$this->request->trustProxy = true;

// These methods will now use the proxied headers.


$port = $this->request->port();
$host = $this->request->host();
$scheme = $this->request->scheme();
$clientIp = $this->request->clientIp();

Once proxies are trusted the clientIp() method will use the last IP address in the X-Forwarded-For header.
If your application is behind multiple proxies, you can use setTrustedProxies() to define the IP addresses of
proxies in your control:

$request->setTrustedProxies(['127.1.1.1', '127.8.1.3']);

After proxies are trusted clientIp() will use the first IP address in the X-Forwarded-For header providing it
is the only value that isn’t from a trusted proxy.
New in version 3.7.0: setTrustedProxies() was added.

Checking Accept Headers

Cake\Http\ServerRequest::accepts($type = null)
Find out which content types the client accepts, or check whether it accepts a particular type of content.
Get all types:

$accepts = $this->request->accepts();

Check for a single type:

$acceptsJson = $this->request->accepts('application/json');

Cake\Http\ServerRequest::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:

$acceptsLanguages = $this->request->acceptLanguage();

Check whether a specific language is accepted:

$acceptsSpanish = $this->request->acceptLanguage('es-es');

Cookies

Request cookies can be read through a number of methods:

200 Chapter 9. Request & Response Objects


CakePHP Cookbook Documentation, Release 3.8

// Get the cookie value, or null if the cookie is missing.


$rememberMe = $this->request->getCookie('remember_me');

// Read the value, or get the default of 0


$rememberMe = $this->request->getCookie('remember_me', 0);

// Get all cookies as an hash


$cookies = $this->request->getCookieParams();

// Get a CookieCollection instance (starting with 3.5.0)


$cookies = $this->request->getCookieCollection()

See the Cake\Http\Cookie\CookieCollection documentation for how to work with cookie collection.
New in version 3.5.0: ServerRequest::getCookieCollection() was added in 3.5.0

Response
class Cake\Http\Response
Cake\Http\Response is the default response class in CakePHP. It encapsulates a number of features and func-
tionality 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\Http\ServerRequest, Cake\Http\Response
consolidates a number of methods previously found on Controller, RequestHandlerComponent and
Dispatcher. The old methods are deprecated in favour of using Cake\Http\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.

Dealing with Content Types

Cake\Http\Response::withType($contentType = null)
You can control the Content-Type of your application’s responses with Cake\Http\Response::withType().
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 = $this->response->withType('vcf');

// Prior to 3.4.0
$this->response->type('vcf');

Usually, you’ll want to map additional content types in your controller’s beforeFilter() callback, so you can
leverage the automatic view switching features of RequestHandlerComponent if you are using it.

Response 201
CakePHP Cookbook Documentation, Release 3.8

Sending Files

Cake\Http\Response::withFile($path, $options = [])


There are times when you want to send files as responses for your requests. You can accomplish that by using
Cake\Http\Response::withFile():

public function sendFile($id)


{
$file = $this->Attachments->getFile($id);
$response = $this->response->withFile($file['path']);
// Return the response to prevent controller from trying to render
// a view.
return $response;
}

// Prior to 3.4.0
$file = $this->Attachments->getFile($id);
$this->response->file($file['path']);
// Return the response 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 it’s a known file type listed in Cake\Http\Response::$_mimeTypes. You can add new types prior to calling
Cake\Http\Response::withFile() by using the Cake\Http\Response::withType() method.
If you want, you can also force a file to be downloaded instead of displayed in the browser by specifying the options:

$response = $this->response->withFile(
$file['path'],
['download' => true, 'name' => 'foo']
);

// Prior to 3.4.0
$this->response->file(
$file['path'],
['download' => true, 'name' => 'foo']
);

The supported options are:


name The name allows you to specify an alternate file name to be sent to the user.
download A boolean value indicating whether headers should be set to force download.

Sending a String as File

You can respond with a file that does not exist on the disk, such as a pdf or an ics generated on the fly from a string:

public function sendIcs()


{
$icsString = $this->Calendars->generateIcs();
$response = $this->response;

// Inject string content into response body (3.4.0+)


$response = $response->withStringBody($icsString);

202 Chapter 9. Request & Response Objects


CakePHP Cookbook Documentation, Release 3.8

// Inject string content into response body (before 3.4.0)


$response->body($icsString);

$response = $response->withType('ics');

// Optionally force file download


$response = $response->withDownload('filename_for_download.ics');

// Return response object to prevent controller from trying to render


// a view.
return $response;
}

Callbacks can also return the body as a string:

$path = '/some/file.png';
$this->response->body(function () use ($path) {
return file_get_contents($path);
});

Setting Headers

Cake\Http\Response::withHeader($header, $value)
Setting headers is done with the Cake\Http\Response::withHeader() method. Like all of the PSR-7 inter-
face methods, this method returns a new instance with the new header:

// Add/replace a header
$response = $response->withHeader('X-Extra', 'My header');

// Set multiple headers


$response = $response->withHeader('X-Extra', 'My header')
->withHeader('Location', 'http://example.com');

// Append a value to an existing header


$response = $response->withAddedHeader('Set-Cookie', 'remember_me=1');

// Prior to 3.4.0 - Set a header


$this->response->header('Location', 'http://example.com');

Headers are not sent when set. Instead, they are held until the response is emitted by Cake\Http\Server.
You can now use the convenience method Cake\Http\Response::withLocation() to directly set or get the
redirect location header.

Setting the Body

Cake\Http\Response::withStringBody($string)
To set a string as the response body, do the following:

// Set a string into the body


$response = $response->withStringBody('My Body');

// If you want a json response

Response 203
CakePHP Cookbook Documentation, Release 3.8

$response = $response->withType('application/json')
->withStringBody(json_encode(['Foo' => 'bar']));

New in version 3.4.3: withStringBody() was added in 3.4.3


Cake\Http\Response::withBody($body)
To set the response body, use the withBody() method, which is provided by the
Zend\Diactoros\MessageTrait:

$response = $response->withBody($stream);

// Prior to 3.4.0 - Set the body


$this->response->body('My Body');

Be sure that $stream is a Psr\Http\Message\StreamInterface object. See below on how to create a new
stream.
You can also stream responses from files using Zend\Diactoros\Stream streams:

// To stream from a file


use Zend\Diactoros\Stream;

$stream = new Stream('/path/to/file', 'rb');


$response = $response->withBody($stream);

You can also stream responses from a callback using the CallbackStream. This is useful when you have resources
like images, CSV files or PDFs you need to stream to the client:

// Streaming from a callback


use Cake\Http\CallbackStream;

// Create an image.
$img = imagecreate(100, 100);
// ...

$stream = new CallbackStream(function () use ($img) {


imagepng($img);
});
$response = $response->withBody($stream);

// Prior to 3.4.0 you can use the following to create streaming responses.
$file = fopen('/some/file.png', 'r');
$this->response->body(function () use ($file) {
rewind($file);
fpassthru($file);
fclose($file);
});

Setting the Character Set

Cake\Http\Response::withCharset($charset)
Sets the charset that will be used in the response:

$this->response = $this->response->withCharset('UTF-8');

204 Chapter 9. Request & Response Objects


CakePHP Cookbook Documentation, Release 3.8

// Prior to 3.4.0
$this->response->charset('UTF-8');

Interacting with Browser Caching

Cake\Http\Response::withDisabledCache()
You sometimes need to force browsers not to cache the results of a controller action.
Cake\Http\Response::withDisabledCache() is intended for just that:

public function index()


{
// Disable caching
$this->response = $this->response->withDisabledCache();

// Prior to 3.4.0
$this->response->disableCache();
}

Warning: Disabling caching from SSL domains while trying to send files to Internet Explorer can result in errors.

Cake\Http\Response::withCache($since, $time = ’+1 day’)


You can also tell clients that you want them to cache responses. By using
Cake\Http\Response::withCache():

public function index()


{
// Enable caching
$this->response = $this->response->withCache('-1 minute', '+5 days');
}

The above would tell clients to cache the resulting response for 5 days, hopefully speeding up your visitors’ experi-
ence. The withCache() method sets the Last-Modified value to the first argument. Expires header and the
max-age directive are set based on the second parameter. Cache-Control’s public directive is set as well.

Fine Tuning HTTP Cache

One of the best and easiest ways of speeding up your application is to use HTTP cache. Under this caching model,
you are only required to help clients decide if they should use a cached copy of the response by setting a few headers
such as modified time and response entity tag.
Rather than forcing you to code the logic for caching and for invalidating (refreshing) it once the data has changed,
HTTP uses two models, expiration and validation, which usually are much simpler to use.
Apart from using Cake\Http\Response::withCache(), you can also use many other methods to fine-tune
HTTP cache headers to take advantage of browser or reverse proxy caching.

The Cache Control Header

Cake\Http\Response::withSharable($public, $time = null)

Response 205
CakePHP Cookbook Documentation, Release 3.8

Used under the expiration model, this header contains multiple indicators that can change the way browsers or proxies
use the cached content. A Cache-Control header can look like this:

Cache-Control: private, max-age=3600, must-revalidate

Response class helps you set this header with some utility methods that will produce a final valid Cache-Control
header. The first is the withSharable() 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 = $this->response->withSharable(true, 3600);
}

public function my_data()


{
// ...
// Set the Cache-Control as private for 3600 seconds
$this->response = $this->response->withSharable(false, 3600);
}

Response exposes separate methods for setting each of the directives in the Cache-Control header.

The Expiration Header

Cake\Http\Response::withExpires($time)
You can set the Expires header to a date and time after which the response is no longer considered fresh. This
header can be set using the withExpires() method:

public function view()


{
$this->response = $this->response->withExpires('+5 days');
}

This method also accepts a DateTime instance or any string that can be parsed by the DateTime class.

The Etag Header

Cake\Http\Response::withEtag($tag, $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 withEtag() 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.

206 Chapter 9. Request & Response Objects


CakePHP Cookbook Documentation, Release 3.8

To take advantage of this header, you must either call the checkNotModified() method manually or include the
Request Handling in your controller:

public function index()


{
$articles = $this->Articles->find('all');
$response = $this->response->withEtag($this->Articles->generateHash($articles));
if ($response->checkNotModified($this->request)) {
return $response;
}
$this->response = $response;
// ...
}

Note: Most proxy users should probably consider using the Last Modified Header instead of Etags for performance
and compatibility reasons.

The Last Modified Header

Cake\Http\Response::withModified($time)
Also, under the HTTP cache validation model, you can set the Last-Modified header to indicate the date and time
at which the resource was modified for the last time. Setting this header helps CakePHP tell caching clients whether
the response was modified or not based on their cache.
To take advantage of this header, you must either call the checkNotModified() method manually or include the
Request Handling in your controller:

public function view()


{
$article = $this->Articles->find()->first();
$response = $this->response->withModified($article->modified);
if ($response->checkNotModified($this->request)) {
return $response;
}
$this->response;
// ...
}

The Vary Header

Cake\Http\Response::withVary($header)
In some cases, you might want to serve different content using the same URL. This is often the case if you have a
multilingual page or respond with different HTML depending on the browser. Under such circumstances you can use
the Vary header:

$response = $this->response->withVary('User-Agent');
$response = $this->response->withVary('Accept-Encoding', 'User-Agent');
$response = $this->response->withVary('Accept-Language');

Response 207
CakePHP Cookbook Documentation, Release 3.8

Sending Not-Modified Responses

Cake\Http\Response::checkNotModified(Request $request)
Compares the cache headers for the request object with the cache header from the response and determines whether it
can still be considered fresh. If so, deletes the response content, and sends the 304 Not Modified header:
// In a controller action.
if ($this->response->checkNotModified($this->request)) {
return $this->response;
}

Setting Cookies

Cookies can be added to response using either an array or a Cake\Http\Cookie\Cookie object:


use Cake\Http\Cookie\Cookie;
use DateTime;

// Add a cookie as an array using the immutable API (3.4.0+)


$this->response = $this->response->withCookie(new Cookie(
'remember_me',
'yes',
new DateTime('+1 year'), // expiration time
'/', // path
'', // domain
false, // secure
true // httponly
]);

// Before 3.4.0
$this->response->cookie('remember', [
'value' => 'yes',
'path' => '/',
'httpOnly' => true,
'secure' => false,
'expire' => strtotime('+1 year')
]);

See the Creating Cookies section for how to use the cookie object. You can use withExpiredCookie() to send
an expired cookie in the response. This will make the browser remove its local cookie:
// As of 3.5.0
$this->response = $this->response->withExpiredCookie('remember_me');

Setting Cross Origin Request Headers (CORS)

As of 3.2 you can use the cors() method to define HTTP Access Control113 related headers with a fluent interface:
$this->response = $this->response->cors($this->request)
->allowOrigin(['*.cakephp.org'])
->allowMethods(['GET', 'POST'])

113 https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS

208 Chapter 9. Request & Response Objects


CakePHP Cookbook Documentation, Release 3.8

->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 request’s Origin value matches one of the allowed Origin values.
New in version 3.2: The CorsBuilder was added in 3.2

Common Mistakes with Immutable Responses

As of CakePHP 3.4.0, response objects offer a number of methods that treat responses as immutable objects. Im-
mutable objects help prevent difficult to track accidental side-effects, and reduce mistakes caused by method calls
caused by refactoring that change ordering. While they offer a number of benefits, immutable objects can take some
getting used to. Any method that starts with with operates on the response in an immutable fashion, and will always
return a new instance. Forgetting to retain the modified instance is the most frequent mistake people make when
working with immutable objects:

$this->response->withHeader('X-CakePHP', 'yes!');

In the above code, the response will be lacking the X-CakePHP header, as the return value of the withHeader()
method was not retained. To correct the above code you would write:

$this->response = $this->response->withHeader('X-CakePHP', 'yes!');

Cookie Collections

class Cake\Http\Cookie\CookieCollection
CookieCollection objects are accessible from the request and response objects. They let you interact with groups
of cookies using immutable patterns, which allow the immutability of the request and response to be preserved.

Creating Cookies

class Cake\Http\Cookie\Cookie
Cookie objects can be defined through constructor objects, or by using the fluent interface that follows immutable
patterns:

use Cake\Http\Cookie\Cookie;

// All arguments in the constructor


$cookie = new Cookie(
'remember_me', // name
1, // value
new DateTime('+1 year'), // expiration time, if applicable
'/', // path, if applicable
'example.com', // domain, if applicable

Common Mistakes with Immutable Responses 209


CakePHP Cookbook Documentation, Release 3.8

false, // secure only?


true // http only ?
);

// Using the builder methods


$cookie = (new Cookie('remember_me'))
->withValue('1')
->withExpiry(new DateTime('+1 year'))
->withPath('/')
->withDomain('example.com')
->withSecure(false)
->withHttpOnly(true);

Once you have created a cookie, you can add it to a new or existing CookieCollection:

use Cake\Http\Cookie\CookieCollection;

// Create a new collection


$cookies = new CookieCollection([$cookie]);

// Add to an existing collection


$cookies = $cookies->add($cookie);

// Remove a cookie by name


$cookies = $cookies->remove('remember_me');

Note: Remember that collections are immutable and adding cookies into, or removing cookies from a collection,
creates a new collection object.

Cookie objects can be added to responses:

// Add one cookie


$response = $this->response->withCookie($cookie);

// Replace the entire cookie collection


$response = $this->response->withCookieCollection($cookies);

Cookies set to responses can be encrypted using the encrypted-cookie-middleware.


New in version 3.8.0: Response::withCookieCollection() was added.

Reading Cookies

Once you have a CookieCollection instance, you can access the cookies it contains:

// Check if a cookie exists


$cookies->has('remember_me');

// Get the number of cookies in the collection


count($cookies);

// Get a cookie instance


$cookie = $cookies->get('remember_me');

210 Chapter 9. Request & Response Objects


CakePHP Cookbook Documentation, Release 3.8

Once you have a Cookie object you can interact with it’s state and modify it. Keep in mind that cookies are im-
mutable, so you’ll need to update the collection if you modify a cookie:

// Get the value


$value = $cookie->getValue()

// Access data inside a JSON value


$id = $cookie->read('User.id');

// Check state
$cookie->isHttpOnly();
$cookie->isSecure();

New in version 3.5.0: CookieCollection and Cookie were added in 3.5.0.

Cookie Collections 211


CakePHP Cookbook Documentation, Release 3.8

212 Chapter 9. Request & Response Objects


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 con-
troller’s 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, it’s also possible to have controllers work with more than one model. In CakePHP, a
controller is named after the primary model it handles.
Your application’s 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 application’s 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.

The App Controller

As stated in the introduction, the AppController class is the parent class to all of your application’s con-
trollers. AppController itself extends the Cake\Controller\Controller class included in CakePHP.
AppController is defined in src/Controller/AppController.php as follows:
namespace App\Controller;

use Cake\Controller\Controller;

213
CakePHP Cookbook Documentation, Release 3.8

class AppController extends Controller


{
}

Controller attributes and methods created in your AppController will be available in all controllers that extend
it. Components (which you’ll 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 Controller’s 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, CakePHP’s Cake\Routing\Router and


Cake\Routing\Dispatcher classes use Connecting Routes to find and create the correct controller in-
stance. 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 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

214 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

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 don’t 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 you’d like to skip the default behavior, you can return a Cake\Http\Response object from the
action with the fully created response.
In order for you to use a controller effectively in your own application, we’ll cover some of the core attributes and
methods provided by CakePHP’s controllers.

Interacting with Views

Controllers interact with views in a number of ways. First, they are able to pass data to the views, using
Controller::set(). You can also decide which view class to use, and which view file should be rendered
from the controller.

Setting View Variables

Cake\Controller\Controller::set(string $var, mixed $value)


The Controller::set() method is the main way to send data from your controller to your view. Once you’ve
used Controller::set(), the variable can be accessed in your view:

// First you pass data from the controller:

$this->set('color', 'pink');

// Then, in the view, you can utilize the data:


?>

You have selected <?= h($color) ?> icing for the cake.

Interacting with Views 215


CakePHP Cookbook Documentation, Release 3.8

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);

Keep in mind that view vars are shared among all parts rendered by your view. They will be available in all parts of
the view: the template, the layout and all elements inside the former two.

Setting View Options

If you want to customize the view class, layout/template paths, helpers or the theme that will be used when rendering
the view, you can use the viewBuilder() method to get a builder. This builder can be used to define properties of
the view before it is created:

$this->viewBuilder()
->helpers(['MyCustom'])
->theme('Modern')
->className('Modern.Admin');

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 you’ve 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:

namespace App\Controller;

class RecipesController extends AppController


{
// ...
public function search()
{
// Render the view in src/Template/Recipes/search.ctp
$this->render();
}
// ...
}

216 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

Although CakePHP will automatically call it after every action’s logic (unless you’ve 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');
}
}

This would render src/Template/Posts/custom_file.ctp instead of src/Template/Posts/my_action.ctp.


You can also render views inside plugins using the following syntax: $this->render('PluginName.
PluginController/custom_file'). For example:

namespace App\Controller;

class PostsController extends AppController


{
public function my_action()
{
$this->render('Users.UserDetails/custom_file');
}
}

This would render plugins/Users/src/Template/UserDetails/custom_file.ctp

Redirecting to Other Pages

Cake\Controller\Controller::redirect(string|array $url, integer $status)


The flow control method you’ll use most often is Controller::redirect(). This method takes its first param-
eter in the form of a CakePHP-relative URL. When a user has successfully placed an order, you might wish to redirect
him to a receipt screen.

Redirecting to Other Pages 217


CakePHP Cookbook Documentation, Release 3.8

public function place_order()


{
// Logic for finalizing order goes here
if ($success) {
return $this->redirect(
['controller' => 'Orders', 'action' => 'thanks']
);
}
return $this->redirect(
['controller' => 'Orders', 'action' => 'confirm']
);
}

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');

You can also pass data to the action:

return $this->redirect(['action' => 'edit', $id]);

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());

An example using query strings and hash would look like:

return $this->redirect([
'controller' => 'Orders',
'action' => 'confirm',
'?' => [
'product' => 'pizza',
'quantity' => 5
],
'#' => 'top'
]);

The generated URL would be:

http://www.example.com/orders/confirm?product=pizza&quantity=5#top

Redirecting to Another Action on the Same Controller

Cake\Controller\Controller::setAction($action, $args...)
If you need to forward the current action to a different action on the same controller, you can use
Controller::setAction() to update the request object, modify the view template that will be rendered and
forward execution to the named action:

218 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

// From a delete action, you can render the updated


// list page.
$this->setAction('index');

Loading Additional Models

Cake\Controller\Controller::loadModel(string $modelClass, string $type)


The loadModel() function comes handy when you need to use a model table/collection that is not the controller’s
default one:

// In a controller method.
$this->loadModel('Articles');
$recentArticles = $this->Articles->find('all', [
'limit' => 5,
'order' => 'Articles.created DESC'
]);

If you are using a table provider other than the built-in ORM you can link that table system into CakePHP’s controllers
by connecting its factory method:

// In a controller method.
$this->modelFactory(
'ElasticIndex',
['ElasticIndexes', 'factory']
);

After registering a table factory, you can use loadModel to load instances:

// In a controller method.
$this->loadModel('Locations', 'ElasticIndex');

Note: The built-in ORM’s 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]
]
];
}

Loading Additional Models 219


CakePHP Cookbook Documentation, Release 3.8

Configuring Components to Load

Cake\Controller\Controller::loadComponent($name, $config = [])


In your Controller’s initialize() method you can define any components you want loaded, and any configuration
data for them:

public function initialize()


{
parent::initialize();
$this->loadComponent('Csrf');
$this->loadComponent('Comments', Configure::read('Comments'));
}

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 infor-
mation. As mentioned earlier the $components property will be merged with the property defined in each of your
controller’s parent classes.

Configuring Helpers to Load

property Cake\Controller\Controller::$helpers
Let’s look at how to tell a CakePHP Controller that you plan to use additional MVC classes:

class RecipesController extends AppController


{
public $helpers = ['Form'];
}

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.

Request Life-cycle Callbacks

CakePHP controllers trigger several events/callbacks that you can use to insert logic around the request life-cycle:

Event List

• Controller.initialize
• Controller.startup
• Controller.beforeRedirect
• Controller.beforeRender
• Controller.shutdown

220 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

Controller Callback Methods

By default the following callback methods are connected to related events if the methods are implemented by your
controllers
Cake\Controller\Controller::beforeFilter(Event $event)
Called during the Controller.initialize event which occurs before every action in the controller. It’s
a handy place to check for an active session or inspect user permissions.

Note: The beforeFilter() method will be called for missing actions.

Returning a response from a beforeFilter method will not prevent other listeners of the same event from
being called. You must explicitly stop the event.
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 AppController’s callbacks within child controller callbacks for best results:

//use Cake\Event\Event;
public function beforeFilter(Event $event)
{
parent::beforeFilter($event);
}

More on Controllers

The Pages Controller

CakePHP’s 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:

More on Controllers 221


CakePHP Cookbook Documentation, Release 3.8

AuthComponent

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.
Deprecated since version 4.0.0: The AuthComponent is deprecated as of 4.0.0 and will be replaced by the authoriza-
tion114 and authentication115 plugins.

Suggested Reading Before Continuing

Configuring authentication requires several steps including defining a users table, creating a model, controller & views,
etc.
This is all covered step by step in the CMS Tutorial.
If you are looking for existing authentication and/or authorization solutions for CakePHP, have a look at the Authen-
tication and Authorization116 section of the Awesome CakePHP list.

Authentication

Authentication is the process of identifying users by provided credentials and ensuring that users are who they say
they are. Generally, this is done through a username and password, that are checked against a known list of users. In
CakePHP, there are several built-in ways of authenticating users stored in your application.
• FormAuthenticate allows you to authenticate users based on form POST data. Usually, this is a login form
that users enter information into.
• BasicAuthenticate allows you to authenticate users using Basic HTTP authentication.
• DigestAuthenticate allows you to authenticate users using Digest HTTP authentication.
By default AuthComponent uses FormAuthenticate.

Choosing an Authentication Type

Generally, you’ll want to offer form based authentication. It is the easiest for users using a web-browser to use. If
you are building an API or webservice, you may want to consider basic authentication or digest authentication. The
key differences between digest and basic authentication are mostly related to how passwords are handled. In basic
authentication, the username and password are transmitted as plain-text to the server. This makes basic authentication
un-suitable for applications without SSL, as you would end up exposing sensitive passwords. Digest authentication
uses a digest hash of the username, password, and a few other details. This makes digest authentication more appro-
priate for applications without SSL encryption.
You can also use authentication systems like OpenID as well; however, OpenID is not part of CakePHP core.
114 https://book.cakephp.org/authorization/
115 https://book.cakephp.org/authentication/
116 https://github.com/FriendsOfCake/awesome-cakephp/blob/master/README.md#authentication-and-authorization

222 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

Configuring Authentication Handlers

You configure authentication handlers using the authenticate config. You can configure one or many handlers for
authentication. Using multiple handlers allows you to support different ways of logging users in. When logging users
in, authentication handlers are checked in the order they are declared. Once one handler is able to identify the user, no
other handlers will be checked. Conversely, you can halt all authentication by throwing an exception. You will need
to catch any thrown exceptions and handle them as needed.
You can configure authentication handlers in your controller’s beforeFilter() or initialize() methods.
You can pass configuration information into each authentication object using an array:

// Simple setup
$this->Auth->config('authenticate', ['Form']);

// Pass settings in
$this->Auth->config('authenticate', [
'Basic' => ['userModel' => 'Members'],
'Form' => ['userModel' => 'Members']
]);

In the second example, you’ll 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']
]
]

More on Controllers 223


CakePHP Cookbook Documentation, Release 3.8

]);
}

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 configu-
ration should look like:

public function initialize()


{
parent::initialize();
$this->loadComponent('Auth', [
'loginAction' => [
'controller' => 'Users',
'action' => 'login',
'plugin' => 'Users'
],
'authError' => 'Did you really think you are allowed to see that?',
'authenticate' => [
'Form' => [
'fields' => ['username' => 'email']
]
],
'storage' => 'Session'
]);
}

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 be returned unchanged by clients. Defaults to md5($config['realm']).

Note: To find the user record, the database is queried only using the username. The password check is done in PHP.
This is necessary because hashing algorithms like bcrypt (which is used by default) generate a new hash each time,
even for the same string and you can’t just do simple string comparison in SQL to check if the password matches.

Customizing Find Query

You can customize the query used to fetch the user record using the finder option in authenticate class config:

public function initialize()


{
parent::initialize();
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'finder' => 'auth'
]

224 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

],
]);
}

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 a condition. You must ensure that you select the fields you need to
authenticate a user, such as username and password:

public function findAuth(\Cake\ORM\Query $query, array $options)


{
$query
->select(['id', 'username', 'password'])
->where(['Users.active' => 1]);

return $query;
}

Note: finder option is available since 3.1. Prior to that you can use scope and contain options to modify a
query.

Identifying Users and Logging Them In

AuthComponent::identify()
You need to manually call $this->Auth->identify() to identify the user using credentials provided in request.
Then use $this->Auth->setUser() to log the user in, i.e., save user info to session.
When authenticating users, attached authentication objects are checked in the order they are attached. Once one of
the objects can identify the user, no other objects are checked. A sample login function for working with a login form
could look like:

public function login()


{
if ($this->request->is('post')) {
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
return $this->redirect($this->Auth->redirectUrl());
} else {
$this->Flash->error(__('Username or password is incorrect'));
}
}
}

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 won’t actually check the credentials against an authentication class.

More on Controllers 225


CakePHP Cookbook Documentation, Release 3.8

Redirecting Users After Login

AuthComponent::redirectUrl()
After logging a user in, you’ll generally want to redirect them back to where they came from. Pass a URL in to set the
destination a user should be redirected to after logging in.
If no parameter is passed, the returned URL will use the following rules:
• Returns the normalized URL from the redirect query string value if it is present and for the same domain
the current app is running on. Before 3.4.0, the Auth.redirect session value was used.
• If there is no query string/session value and there is a config with loginRedirect, the loginRedirect
value is returned.
• If there is no redirect value and no loginRedirect, / is returned.

Creating Stateless Authentication Systems

Basic and digest are stateless authentication schemes and don’t require an initial POST or a form. If using only
basic/digest authenticators you don’t require a login action in your controller. Stateless authentication will re-verify
the user’s credentials on each request, this creates a small amount of additional overhead, but allows clients to login
without using cookies and makes AuthComponent more suitable for building APIs.
For stateless authenticators, the storage config should be set to Memory so that AuthComponent does not use
a session to store user record. You may also want to set config unauthorizedRedirect to false so that
AuthComponent throws a ForbiddenException instead of the default behavior of redirecting to referrer.
The unauthorizedRedirect option only applies to authenticated users. When a user is not yet authenticated and
you do not want the user to be redirected, you will need to load one or more stateless authenticators, like Basic or
Digest.
Authentication objects can implement a getUser() method that can be used to support user login systems that
don’t rely on cookies. A typical getUser method looks at the request/environment and uses the information there to
confirm the identity of the user. HTTP Basic authentication for example uses $_SERVER['PHP_AUTH_USER']
and $_SERVER['PHP_AUTH_PW'] for the username and password fields.

Note: In case authentication does not work like expected, check if queries are executed at
all (see BaseAuthenticate::_query($username)). In case no queries are executed check if
$_SERVER['PHP_AUTH_USER'] and $_SERVER['PHP_AUTH_PW'] do get populated by the webserver. If
you are using Apache with FastCGI-PHP you might need to add this line to your .htaccess file in webroot:

RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]

On each request, these values, PHP_AUTH_USER and PHP_AUTH_PW, are used to re-identify the user and ensure
they are the valid user. As with authentication object’s authenticate() method, the getUser() method should
return an array of user information on the success or false on failure.

public function getUser(ServerRequest $request)


{
$username = env('PHP_AUTH_USER');
$pass = env('PHP_AUTH_PW');

if (empty($username) || empty($pass)) {
return false;
}

226 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

return $this->_findUser($username, $pass);


}

The above is how you could implement the 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, you’ll need to configure AuthComponent:


$this->loadComponent('Auth', [
'authenticate' => [
'Basic' => [
'fields' => ['username' => 'username', 'password' => 'api_key'],
'userModel' => 'Users'
],
],
'storage' => 'Memory',
'unauthorizedRedirect' => false
]);

Here we’re 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;
use Cake\Utility\Text;
use Cake\Event\Event;
use Cake\ORM\Table;
use Cake\Utility\Security;

class UsersTable extends Table


{
public function beforeSave(Event $event)
{
$entity = $event->getData('entity');

if ($entity->isNew()) {
$hasher = new DefaultPasswordHasher();

// Generate an API 'token'


$entity->api_key_plain = Security::hash(Security::randomBytes(32), 'sha256
˓→ ', false);

More on Controllers 227


CakePHP Cookbook Documentation, Release 3.8

// 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 user’s request.

Using Digest Authentication

Digest authentication offers an improved security model over basic authentication, as the user’s credentials are never
sent in the request header. Instead, a hash is sent.
To use digest authentication, you’ll need to configure AuthComponent:
$this->loadComponent('Auth', [
'authenticate' => [
'Digest' => [
'fields' => ['username' => 'username', 'password' => 'digest_hash'],
'userModel' => 'Users'
],
],
'storage' => 'Memory',
'unauthorizedRedirect' => false
]);

Here we’re using username + digest_hash as our fields and use the Users model.

Hashing Passwords For Digest Authentication

Because Digest authentication requires a password hashed in the format defined by the RFC, in order to cor-
rectly hash a password for use with Digest authentication you should use the special password hashing function
on DigestAuthenticate. If you are going to be combining digest authentication with any other authentication
strategies, it’s also recommended that you store the digest password in a separate column, from the normal password
hash:
namespace App\Model\Table;

use Cake\Auth\DigestAuthenticate;
use Cake\Event\Event;
use Cake\ORM\Table;

class UsersTable extends Table


{
public function beforeSave(Event $event)
{
$entity = $event->getData('entity');

228 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

// Make a password for digest auth.


$entity->digest_hash = DigestAuthenticate::password(
$entity->username,
$entity->plain_password,
env('SERVER_NAME')
);
return true;
}
}

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.

Creating Custom Authentication Objects

Because authentication objects are pluggable, you can create custom authentication objects in your application or
plugins. If for example, you wanted to create an OpenID authentication object. In src/Auth/OpenidAuthenticate.php
you could put the following:

namespace App\Auth;

use Cake\Auth\BaseAuthenticate;
use Cake\Http\ServerRequest;
use Cake\Http\Response;

class OpenidAuthenticate extends BaseAuthenticate


{
public function authenticate(ServerRequest $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. It’s 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 identi-
fied 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',

More on Controllers 229


CakePHP Cookbook Documentation, Release 3.8

'Auth.logout' => 'logout'


];
}

Using Custom Authentication Objects

Once you’ve created your custom authentication objects, you can use them by including them in AuthComponent’s
authenticate array:

$this->Auth->config('authenticate', [
'Openid', // app authentication object.
'AuthBag.Openid', // plugin authentication object.
]);

Note: Note that when using simple notation there’s no ‘Authenticate’ word when initiating the authentication object.
Instead, if using namespaces, you’ll need to set the full namespace of the class, including the ‘Authenticate’ word.

Handling Unauthenticated Requests

When an unauthenticated user tries to access a protected page first the unauthenticated() method of the last
authenticator in the chain is called. The authenticate object can handle sending response or redirection by returning a
response object to indicate no further action is necessary. Due to this, the order in which you specify the authentication
provider in authenticate config matters.
If authenticator returns null, AuthComponent redirects user to the login action. If it’s an AJAX request and config
ajaxLogin is specified that element is rendered else a 403 HTTP status code is returned.

Displaying Auth Related Flash Messages

In order to display the session error messages that Auth generates, you need to add the following code to your layout.
Add the following two lines to the src/Template/Layout/default.ctp file in the body section:

// Only this is necessary after 3.4.0


echo $this->Flash->render();

// Prior to 3.4.0 this will be required as well.


echo $this->Flash->render('auth');

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 ‘default’. Prior to 3.4.0, the key defaulted to ‘auth’.
• element - The element name to use for rendering, defaults to null.
• params - The array of additional parameters to use, defaults to [].
In addition to the flash message settings you can customize other error messages AuthComponent uses. In your
controller’s beforeFilter(), or component settings you can use authError to customize the error used for
when authorization fails:

230 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

$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 controller’s 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;
use Cake\ORM\Entity;

class User extends Entity


{

// ...

protected function _setPassword($password)


{
if (strlen($password) > 0) {
return (new DefaultPasswordHasher)->hash($password);
}
}

// ...
}

AuthComponent is configured by default to use the DefaultPasswordHasher when validating user credentials
so no additional configuration is required in order to authenticate users.
DefaultPasswordHasher uses the bcrypt hashing algorithm internally, which is one of the stronger password
hashing solutions used in the industry. While it is recommended that you use this password hasher class, the case may
be that you are managing a database of users whose password was hashed differently.

Creating Custom Password Hasher Classes

In order to use a different password hasher, you need to create the class in src/Auth/LegacyPasswordHasher.php
and implement the hash() and check() methods. This class needs to extend the AbstractPasswordHasher
class:
namespace App\Auth;

use Cake\Auth\AbstractPasswordHasher;

class LegacyPasswordHasher extends AbstractPasswordHasher

More on Controllers 231


CakePHP Cookbook Documentation, Release 3.8

public function hash($password)


{
return sha1($password);
}

public function check($password, $hashedPassword)


{
return sha1($password) === $hashedPassword;
}
}

Then you are required to configure the AuthComponent to use your own password hasher:

public function initialize()


{
parent::initialize();
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'passwordHasher' => [
'className' => 'Legacy',
]
]
]
]);
}

Supporting legacy systems is a good idea, but it is even better to keep your database with the latest security advance-
ments. The following section will explain how to migrate from one hashing algorithm to CakePHP’s 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']
]
]
]
]
]);
}

232 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

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 the value to ensure
passwords are salted.
In order to update old users’ passwords on the fly, you can change the login function accordingly:

public function login()


{
if ($this->request->is('post')) {
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
if ($this->Auth->authenticationProvider()->needsPasswordRehash()) {
$user = $this->Users->get($this->Auth->user('id'));
$user->password = $this->request->getData('password');
$this->Users->save($user);
}
return $this->redirect($this->Auth->redirectUrl());
}
...
}
}

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->getData());
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 won’t have the user id available.

Accessing the Logged In User

AuthComponent::user($key = null)

More on Controllers 233


CakePHP Cookbook Documentation, Release 3.8

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 doesn’t exist, null will be returned.

Logging Users Out

AuthComponent::logout()
Eventually, you’ll 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');

Default value for checkAuthIn is 'Controller.startup' - but by using 'Controller.initialize'


initial authentication is done before beforeFilter() method.

Authorization

Authorization is the process of ensuring that an identified/authenticated user is allowed to access the resources they
are requesting. If enabled AuthComponent can automatically check authorization handlers and ensure that logged
in users are allowed to access the resources they are requesting. There are several built-in authorization handlers and
you can create custom ones for your application or as part of a plugin.
• ControllerAuthorize Calls isAuthorized() on the active controller, and uses the return of that to
authorize a user. This is often the most simple way to authorize users.

Note: The ActionsAuthorize & CrudAuthorize adapter available in CakePHP 2.x have now been moved
to a separate plugin cakephp/acl117 .

117 https://github.com/cakephp/acl

234 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

Configuring Authorization Handlers

You configure authorization handlers using the authorize config key. You can configure one or many handlers
for authorization. Using multiple handlers allows you to support different ways of checking authorization. When
authorization handlers are checked, they will be called in the order they are declared. Handlers should return false,
if they are unable to check authorization, or the check has failed. Handlers should return true if they were able to
check authorization successfully. Handlers will be called in sequence until one passes. If all checks fail, the user will
be redirected to the page they came from. Additionally, you can halt all authorization by throwing an exception. You
will need to catch any thrown exceptions and handle them.
You can configure authorization handlers in your controller’s beforeFilter() or initialize() methods. You
can pass configuration information into each authorization object, using an array:

// Basic setup
$this->Auth->config('authorize', ['Controller']);

// Pass settings in
$this->Auth->config('authorize', [
'Actions' => ['actionPath' => 'controllers/'],
'Controller'
]);

Much like authenticate, authorize, helps you keep your code DRY, by using the all key. This spe-
cial 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 they are not authorized to access, they will be 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.

Creating Custom Authorize Objects

Because authorize objects are pluggable, you can create custom authorize objects in your application or plugins. If
for example, you wanted to create an LDAP authorize object. In src/Auth/LdapAuthorize.php you could put the
following:

namespace App\Auth;

use Cake\Auth\BaseAuthorize;
use Cake\Http\ServerRequest;

class LdapAuthorize extends BaseAuthorize


{
public function authorize($user, ServerRequest $request)
{

More on Controllers 235


CakePHP Cookbook Documentation, Release 3.8

// Do things for ldap here.


}
}

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 user’s access, true should be returned. It’s 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 you’ve created your custom authorize object, you can use them by including them in your AuthComponent’s
authorize array:

$this->Auth->config('authorize', [
'Ldap', // app authorize object.
'AuthBag.Combo', // plugin authorize object.
]);

Using No Authorization

If you’d like to not use any of the built-in authorization objects and want to handle things entirely out-
side of AuthComponent, you can set $this->Auth->config('authorize', false);. By default
AuthComponent starts off with authorize set to false. If you don’t use an authorization scheme, make sure
to check authorization yourself in your controller’s 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 don’t require users to be logged
in. AuthComponent is pessimistic and defaults to denying access. You can mark actions as public actions by using
AuthComponent::allow(). By marking actions as public, AuthComponent will not check for a logged in
user nor will authorize objects to be checked:

// Allow all actions


$this->Auth->allow();

// Allow only the index action.


$this->Auth->allow('index');

// Allow only the view and index actions.


$this->Auth->allow(['view', 'index']);

By calling it empty you allow all actions to be public. For a single action, you can provide the action name as a 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 the normal functioning of AuthComponent.

236 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

Making Actions Require Authorization

AuthComponent::deny($actions = null)
By default all actions require authorization. However, after making actions public you want to revoke the public
access. You can do so using AuthComponent::deny():

// Deny all actions.


$this->Auth->deny();

// Deny one action


$this->Auth->deny('add');

// Deny a group of actions.


$this->Auth->deny(['add', 'edit']);

By calling it empty you deny all actions. For a single action, you can provide the action name as a 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
don’t 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


{
public function initialize()
{
parent::initialize();
$this->loadComponent('Auth', [
'authorize' => 'Controller',
]);
}

public function isAuthorized($user = null)


{
// Any registered user can access public functions
if (!$this->request->getParam('prefix')) {
return true;
}

// Only admins can access admin functions


if ($this->request->getParam('prefix') === 'admin') {
return (bool)($user['role'] === 'admin');
}

// Default deny
return false;
}
}

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.

More on Controllers 237


CakePHP Cookbook Documentation, Release 3.8

Configuration options

The following settings can all be defined either in your controller’s 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’.
• key - The key to use; defaults to ‘auth’.
• params - The array of additional parameters to use; defaults to ‘[]’.
loginAction A URL (defined as a string or array) to the controller action that handles logins. Defaults to /users/
login.
loginRedirect The URL (defined as a string or array) to the controller action users should be redirected to after
logging in. This value will be ignored if the user has an Auth.redirect value in their session.
logoutRedirect The default action to redirect to after the user is logged out. While AuthComponent does not han-
dle post-logout redirection, a redirect URL will be returned from AuthComponent::logout(). Defaults
to loginAction.
unauthorizedRedirect Controls handling of unauthorized access. By default unauthorized user is redirected to the
referrer URL or loginAction or ‘/’. If set to false, a ForbiddenException exception is thrown instead of
redirecting.
storage Storage class to use for persisting user record. When using stateless authenticator you should set this to
Memory. Defaults to Session. You can pass config options to storage class using array format. For e.g. to use
a custom session key you can set storage to ['className' => 'Session', 'key' => 'Auth.
Admin'].
checkAuthIn Name of the event in which initial auth checks should be done. Defaults to Controller.
startup. You can set it to Controller.initialize if you want the check to be done before controller’s
beforeFilter() method is run.
You can get current configuration values by calling $this->Auth->config():: only the configuration option:

$this->Auth->config('loginAction');

$this->redirect($this->Auth->config('loginAction'));

This is useful if you want to redirect a user to the login route for example. Without a parameter, the full configuration
will be returned.

238 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

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. Cookies added through CookieComponent will only be sent if the
controller action completes.
Deprecated since version 3.5.0: Cookies are available in the ServerRequest see Cookies. For encrypted cookies
see the encrypted-cookie-middleware.

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
]);

To configure a specific key use the configKey() method:

$this->Cookie->configKey('User', 'path', '/');


$this->Cookie->configKey('User', [
'expires' => '+10 days',
'httpOnly' => true
]);

There are a number of configurable values for cookies:


expires How long the cookies should last for. Defaults to 1 month.
path The path on the server in which the cookie will be available on. If path is set to ‘/foo/’, the cookie will only
be available within the /foo/ directory and all sub-directories such as /foo/bar/ of domain. The default value is
app’s base path.
domain The domain that the cookie is available. To make the cookie available on all subdomains of example.com set
domain to ‘.example.com’.
secure Indicates that the cookie should only be transmitted over a secure HTTPS connection. When set to true, the
cookie will only be set if a secure connection exists.
key Encryption key used when encrypted cookies are enabled. Defaults to Security.salt.
httpOnly Set to true to make HTTP only cookies. Cookies that are HTTP only are not accessible in JavaScript.
Defaults to false.
encryption Type of encryption to use. Defaults to ‘aes’. Can also be ‘rijndael’ for backwards compatibility.

More on Controllers 239


CakePHP Cookbook Documentation, Release 3.8

Using the Component

The CookieComponent offers a number of methods for working with Cookies.


Cake\Controller\Component\CookieComponent::write(mixed $key, mixed $value = null)
The write() method is the heart of the cookie component. $key is the cookie variable name you want, and the
$value is the information to be stored:

$this->Cookie->write('name', 'Larry');

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);

Cake\Controller\Component\CookieComponent::read(mixed $key = null)


This method is used to read the value of a cookie variable with the name specified by $key.

// Outputs "Larry"
echo $this->Cookie->read('name');

// You can also use the dot notation for read


echo $this->Cookie->read('User.name');

// To get the variables which you had grouped


// using the dot notation as an array use the following
$this->Cookie->read('User');

// This outputs something like ['name' => 'Larry', 'role' => 'Lead']

Warning: CookieComponent cannot interact with bare strings values that contain ,. The compo-
nent will attempt to interpret these values as arrays, leading to incorrect results. Instead you should use
$request->getCookie().

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:

240 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

// Delete a variable
$this->Cookie->delete('bar');

// Delete the cookie variable bar, but not everything under foo
$this->Cookie->delete('foo.bar');

Cross Site Request Forgery

By enabling the CSRF Component you get protection against attacks. CSRF118 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.
The CsrfComponent works by setting a cookie to the user’s browser. When forms are created with the
Cake\View\Helper\FormHelper, a hidden field is added containing the CSRF token. During the
Controller.startup event, if the request is a POST, PUT, DELETE, PATCH request the component will com-
pare the request data & cookie value. If either is missing or the two values mismatch the component will throw a
Cake\Network\Exception\InvalidCsrfTokenException.

Note: You should always verify the HTTP method being used before executing to avoid side-effects. You should
check the HTTP method or use Cake\Http\ServerRequest::allowMethod() to ensure the correct HTTP
method is used.

New in version 3.1: The exception type changed from Cake\Network\Exception\ForbiddenException


to Cake\Network\Exception\InvalidCsrfTokenException.
Deprecated since version 3.5.0: You should use csrf-middleware instead of CsrfComponent.

Using the CsrfComponent

Simply by adding the CsrfComponent to your components array, you can benefit from the CSRF protection it
provides:

public function initialize()


{
parent::initialize();
$this->loadComponent('Csrf');
}

Settings can be passed into the component through your component’s 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:
118 http://en.wikipedia.org/wiki/Cross-site_request_forgery

More on Controllers 241


CakePHP Cookbook Documentation, Release 3.8

$token = $this->request->getParam('_csrfToken');

Integration with FormHelper

The CsrfComponent integrates seamlessly with FormHelper. Each time you create a form with FormHelper, it will
insert a hidden field containing the CSRF token.

Note: When using the CsrfComponent you should always start your forms with the FormHelper. If you do not, you
will need to manually create hidden inputs in each of your forms.

CSRF Protection and AJAX Requests

In addition to request data parameters, CSRF tokens can be submitted through a special X-CSRF-Token header.
Using a header often makes it easier to integrate a CSRF token with JavaScript heavy applications, or XML/JSON
based API endpoints.

Disabling the CSRF Component for Specific Actions

While not recommended, you may want to disable the CsrfComponent on certain requests. You can do this using the
controller’s event dispatcher, during the beforeFilter() method:

public function beforeFilter(Event $event)


{
$this->getEventManager()->off($this->Csrf);
}

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, FlashComponent’s __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');

// Uses src/Template/Element/Flash/great_success.ctp
$this->Flash->greatSuccess('This was greatly successful');

242 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

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.
FlashComponent’s __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/Flash for ren-
dering the flash message.

Note: By default, CakePHP escapes the content in flash messages to prevent cross site scripting. User data in your
flash messages will be HTML encoded and safe to be printed. If you want to include HTML in your flash messages,
you need to pass the escape option and adjust your flash message templates to allow disabling escaping when the
escape option is passed.

HTML in Flash Messages

New in version 3.3.3.

More on Controllers 243


CakePHP Cookbook Documentation, Release 3.8

It is possible to output HTML in flash messages by using the 'escape' option key:

$this->Flash->info(sprintf('<b>%s</b> %s', h($highlight), h($message)), ['escape' =>


˓→false]);

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 controller’s beforeFilter().
By using the Security Component you automatically get form tampering protection. Hidden token fields will automat-
ically be inserted into forms and checked by the Security component.
If you are using Security component’s 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.
You should always verify the HTTP method being used before executing to avoid side-effects. You should check the
HTTP method or use Cake\Http\ServerRequest::allowMethod() to ensure the correct HTTP method is
used.

Handling Blackhole Callbacks

SecurityComponent::blackHole(object $controller, string $error = ”, SecurityException $exception


= null)
If an action is restricted by the Security Component it is ‘black-holed’ as an invalid request which will result in a 400
error by default. You can configure this behavior by setting the blackHoleCallback configuration option to a
callback function in the controller.
By configuring a callback method you can customize how the blackhole process works:

244 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

public function beforeFilter(Event $event)


{
parent::beforeFilter($event);

$this->Security->setConfig('blackHoleCallback', 'blackhole');
}

public function blackhole($type, SecurityException $exception)


{
if ($exception->getMessage() === 'Request is not SSL and the action is required
˓→to be secure') {

// Reword the exception message with a translatable string.


$exception->setMessage(__('Please access the requested page through HTTPS'));
}

// Re-throw the conditionally reworded exception.


throw $exception;

// Alternatively, handle the error, e.g. set a flash message &


// redirect to HTTPS version of the requested page.
}

Note: use $this->Security->config() for CakePHP versions prior to 3.4

The $type parameter can have the following values:


• ‘auth’ Indicates a form validation error, or a controller/action mismatch error.
• ‘secure’ Indicates an SSL method restriction failure.
New in version cakephp/cakephp: 3.2.6
As of v3.2.6 an additional parameter is included in the blackHole callback, an instance of the
Cake\Controller\Exception\SecurityException is included as a second parameter.

Restrict Actions to SSL

SecurityComponent::requireSecure()
Sets the actions that require a SSL-secured request. Takes any number of arguments. Can be called with no
arguments to force all actions to require a SSL-secured.
SecurityComponent::requireAuth()
Sets the actions that require a valid Security Component generated token. Takes any number of arguments. Can
be called with no arguments to force all actions to require a valid authentication.

Restricting Cross Controller Communication

allowedControllers A list of controllers which can send requests to this controller. This can be used to control cross
controller requests.
allowedActions A list of actions which are allowed to send requests to this controller’s actions. This can be used to
control cross controller requests.
These configuration options allow you to restrict cross controller communication. Set them with the setConfig()
method, or config() if you are using a CakePHP version below 3.4.

More on Controllers 245


CakePHP Cookbook Documentation, Release 3.8

Form Tampering Prevention

By default the SecurityComponent prevents users from tampering with forms in specific ways. The
SecurityComponent will prevent the following things:
• Unknown fields cannot be added to the form.
• Fields cannot be removed from the form.
• Values in hidden inputs cannot be modified.
Preventing these types of tampering is accomplished by working with the FormHelper and tracking which fields are
in a form. The values for hidden fields are tracked as well. All of this data is combined and turned into a hash. When
a form is submitted, the SecurityComponent will use the POST data to build the same structure and compare the
hash.

Note: The SecurityComponent will not prevent select options from being added/changed. Nor will it prevent radio
options from being added/changed.

unlockedFields Set to a list of form fields to exclude from POST validation. Fields can be unlocked either in the
Component, or with FormHelper::unlockField(). Fields that have been unlocked are not required to
be part of the POST and hidden unlocked fields do not have their values checked.
validatePost Set to false to completely skip the validation of POST requests, essentially turning off form validation.
The above configuration options can be set with setConfig() or config() for CakePHP versions below 3.4.

Usage

Using the security component is generally done in the controller’s beforeFilter(). You would specify the secu-
rity restrictions you want and the Security Component will enforce them on its startup:

namespace App\Controller;

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)


{
parent::beforeFilter($event);

if ($this->request->getParam('admin')) {
$this->Security->requireSecure();
}
}
}

The above example would force all actions that had admin routing to require secure SSL requests:

246 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

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)


{
parent::beforeFilter($event);

if ($this->request->getParam('admin')) {
$this->Security->requireSecure();
}
}

public function forceSSL($error = '', SecurityException $exception = null)


{
if ($exception instanceof SecurityException && $exception->getType() ===
˓→'secure') {

return $this->redirect('https://' . env('SERVER_NAME') . Router::url(


˓→$this->request->getRequestTarget()));

throw $exception;
}
}

Note: Use $this->request->here() for CakePHP versions prior to 3.4.0

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.

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:

More on Controllers 247


CakePHP Cookbook Documentation, Release 3.8

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)


{
parent::beforeFilter($event);

$this->Security->setConfig('unlockedActions', ['edit']);
}
}

Note: Use $this->Security->config() for CakePHP versions prior to 3.4.0

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 links & buttons simple.

Using Controller::paginate()

In the controller, we start by defining the default query conditions pagination will use in the $paginate con-
troller variable. These conditions, serve as the basis for your pagination queries. They are augmented by the sort,
direction, limit, and page parameters passed in from the URL. It is important to note that the order key must
be defined in an array structure like below:
class ArticlesController extends AppController
{
public $paginate = [
'limit' => 25,
'order' => [
'Articles.title' => 'asc'

248 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

]
];

public function initialize()


{
parent::initialize();
$this->loadComponent('Paginator');
}
}

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:
class ArticlesController extends AppController
{
public $paginate = [
'finder' => 'published',
];
}

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->getParam('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 = [

More on Controllers 249


CakePHP Cookbook Documentation, Release 3.8

'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.
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 hasn’t already been added. The con-
troller’s 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->getParam('paging'). A more complete ex-
ample 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);

250 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

Using the Paginator Directly

If you need to paginate data from another component you may want to use the PaginatorComponent directly. It features
a similar API to the controller method:
$articles = $this->Paginator->paginate($articleTable->find(), $config);

// Or
$articles = $this->Paginator->paginate($articleTable, $config);

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 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 both in the controller’s
$paginate property and in the call to the paginate() method:
// Paginate property
public $paginate = [
'Articles' => ['scope' => 'article'],
'Tags' => ['scope' => 'tag']
];

// 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

Paginating the Same Model multiple Times

To paginate the same model multiple times within a single controller action you need to define an alias for the model.
See Using the TableRegistry for additional details on how to use the table registry:
// In a controller action
$this->paginate = [
'ArticlesTable' => [
'scope' => 'published_articles',
'limit' => 10,
'order' => [
'id' => 'desc',
],
],

More on Controllers 251


CakePHP Cookbook Documentation, Release 3.8

'UnpublishedArticlesTable' => [
'scope' => 'unpublished_articles',
'limit' => 10,
'order' => [
'id' => 'desc',
],
],
];

// Register an additional table object to allow differentiating in pagination


˓→component

TableRegistry::config('UnpublishedArticles', [
'className' => 'App\Model\Table\ArticlesTable',
'table' => 'articles',
'entityClass' => 'App\Model\Entity\Article',
]);

$publishedArticles = $this->paginate(
$this->Articles->find('all', [
'scope' => 'published_articles'
])->where(['published' => true])
);

$unpublishedArticles = $this->paginate(
TableRegistry::getTableLocator()->get('UnpublishedArticles')->find('all', [
'scope' => 'unpublished_articles'
])->where(['published' => false])
);

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 per Page

The number of results that are fetched per page is exposed to the user as the limit parameter. It is generally
undesirable to allow users to fetch all rows in a paginated set. The maxLimit option asserts that no one can set this
limit too high from the outside. 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, for example
reducing it to 10:

public $paginate = [
// Other keys here.

252 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

'maxLimit' => 10
];

If the request’s 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));
}

Out of Range Page Requests

The PaginatorComponent will throw a NotFoundException when trying to access a non-existent page, i.e. page
number requested is greater than total page count.
So you could either let the normal error page be rendered or use a try catch block and take appropriate action when a
NotFoundException is caught:
// Prior to 3.6 use Cake\Network\Exception\NotFoundException
use Cake\Http\Exception\NotFoundException;

public function index()


{
try {
$this->paginate();
} catch (NotFoundException $e) {
// Do something here like redirecting to first or last page.
// $this->request->getParam('paging') will give you required info.
}
}

Pagination in the View

Check the View\Helper\PaginatorHelper documentation for how to create links for pagination navigation.

Request Handling

class RequestHandlerComponent(ComponentCollection $collection, array $config = [])


The Request Handler component is used in CakePHP to obtain additional information about the HTTP requests that
are made to your application. You can use it to see what content types clients prefer, automatically parse request input,
define how content types map to view classes or template paths.
By default RequestHandler will automatically detect AJAX requests based on the X-Requested-With
HTTP header that many JavaScript libraries use. When used in conjunction with

More on Controllers 253


CakePHP Cookbook Documentation, Release 3.8

Cake\Routing\Router::extensions(), RequestHandler will automatically switch the layout and


template files to those that match non-HTML media types. 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 POST’ed to
your Controllers, it will be parsed into an array which is assigned to $this->request->getData(), and can
then be accessed as you would standard POST 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
}

Obtaining Request Information

Request Handler has several methods that provide information about the client and its request.
RequestHandlerComponent::accepts($type = null)
$type can be a string, or an array, or null. If a string, accepts() will return true if the client accepts the
content type. If an array is specified, accepts() return true if any one of the content types is accepted by
the client. If null returns an array of the content-types that the client accepts. For example:

class ArticlesController extends AppController


{

public function initialize()


{
parent::initialize();
$this->loadComponent('RequestHandler');
}

public function beforeFilter(Event $event)


{
if ($this->RequestHandler->accepts('html')) {
// Execute code only if client accepts an HTML (text/html)
// response.
} elseif ($this->RequestHandler->accepts('xml')) {
// Execute XML-only code
}
if ($this->RequestHandler->accepts(['xml', 'rss', 'atom'])) {
// Executes if the client accepts any of the above: XML, RSS
// or Atom.
}
}
}

Other request ‘type’ detection methods include:


RequestHandlerComponent::isXml()
Returns true if the current request accepts XML as a response.

254 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

RequestHandlerComponent::isRss()
Returns true if the current request accepts RSS as a response.
RequestHandlerComponent::isAtom()
Returns true if the current call accepts an Atom response, false otherwise.
RequestHandlerComponent::isMobile()
Returns true if user agent string matches a mobile web browser, or if the client accepts WAP content. The
supported Mobile User Agent strings are:
• Android
• AvantGo
• BlackBerry
• DoCoMo
• Fennec
• iPad
• iPhone
• iPod
• J2ME
• MIDP
• NetFront
• Nokia
• Opera Mini
• Opera Mobi
• PalmOS
• PalmSource
• portalmmm
• Plucker
• ReqwirelessWeb
• SonyEricsson
• 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:

More on Controllers 255


CakePHP Cookbook Documentation, Release 3.8

if ($this->request->is('ajax')) {
$this->response = $this->response->withDisabledCache();

// Prior to 3.4.0
$this->response->disableCache();
}
// Continue Controller action

Automatically Decoding Request Data

Add a request data decoder. The handler should contain a callback, and any additional arguments for the callback. The
callback should return an array of data contained in the request input. For example adding a CSV handler could look
like:

class ArticlesController extends AppController


{
public function initialize()
{
parent::initialize();
$parser = function ($data) {
$rows = str_getcsv($data, "\n");
foreach ($rows as &$row) {
$row = str_getcsv($row, ',');
}
return $rows;
};
$this->loadComponent('RequestHandler', [
'inputTypeMap' => [
'csv' => [$parser]
]
]);
}
}

You can use any callable119 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->getData() an array of the JSON input data, without the additional
true you’d 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.
Changed in version 3.6.0: You should prefer using body-parser-middleware instead of RequestHandlerComponent.

Checking Content-Type Preferences

RequestHandlerComponent::prefers($type = null)
119 http://php.net/callback

256 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

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 content-types 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 controller’s
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'
]);

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
]);
}

More on Controllers 257


CakePHP Cookbook Documentation, Release 3.8

Using Custom ViewClasses

When using JsonView/XmlView you might want to override the default serialization with a custom View class, or add
View classes for other types.
You can map existing and new types to your custom classes. You can also set this automatically by using the
viewClassMap setting:

public function initialize()


{
parent::initialize();
$this->loadComponent('RequestHandler', [
'viewClassMap' => [
'json' => 'ApiKit.MyJson',
'xml' => 'ApiKit.MyXml',
'csv' => 'ApiKit.Csv'
]
]);
}

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.

Configuring Components

Many of the core components require configuration. Some examples of components requiring configuration are Au-
thComponent and Cookie. Configuration for these components, and for components in general, is usually done via
loadComponent() in your Controller’s 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', ['expires' => '1 day']);
}

You can configure components at runtime using the config() method. Often, this is done in your controller’s
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:

258 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

// 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 config-
uration 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:

// 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
}

The above would alias MyAuthComponent to $this->Auth in your controllers.

Note: Aliasing a component replaces that instance anywhere that component is used, including inside other Compo-
nents.

Loading Components on the Fly

You might not need all of your components available on every controller action. In situations like this you can load a
component at runtime using the loadComponent() method in your controller:

// In a controller action
$this->loadComponent('OneTimer');
$time = $this->OneTimer->getTime();

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.

More on Controllers 259


CakePHP Cookbook Documentation, Release 3.8

Using Components

Once you’ve 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 in your controller, you could access it like so:

class PostsController extends AppController


{
public function initialize()
{
parent::initialize();
$this->loadComponent('Flash');
}

public function delete()


{
if ($this->Post->delete($this->request->getData('Post.id')) {
$this->Flash->success('Post deleted.');
return $this->redirect(['action' => 'index']);
}
}

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 some-
thing 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.

260 Chapter 10. Controllers


CakePHP Cookbook Documentation, Release 3.8

Including your Component in your Controllers

Once our component is finished, we can use it in the application’s controllers by loading it during the controller’s
initialize() method. Once loaded, the controller will be given a new attribute named after the component,
through which we can access an instance of it:

// In a controller
// Make the new component available at $this->Math,
// as well as the standard $this->Csrf
public function initialize()
{
parent::initialize();
$this->loadComponent('Math');
$this->loadComponent('Csrf');
}

When including Components in a Controller you can also declare a set of parameters that will be passed on to the
Component’s 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');
}

The above would pass the array containing precision and randomGenerator to MathComponent::initialize()
in the $config parameter.

Using Other Components in your Component

Sometimes one of your components may need to use another component. In this case you can include other compo-
nents in your component the exact same way you include them in controllers - using the $components var:

// src/Controller/Component/CustomComponent.php
namespace App\Controller\Component;

use Cake\Controller\Component;

class CustomComponent extends Component


{
// The other component your component uses
public $components = ['Existing'];

// Execute any other additional setup for your component.


public function initialize(array $config)
{
$this->Existing->foo();
}

public function bar()


{

More on Controllers 261


CakePHP Cookbook Documentation, Release 3.8

// ...
}
}

// src/Controller/Component/ExistingComponent.php
namespace App\Controller\Component;

use Cake\Controller\Component;

class ExistingComponent extends Component


{

public function foo()


{
// ...
}
}

Note: In contrast to a component included in a controller no callbacks will be triggered on a component’s component.

Accessing a Component’s Controller

From within a Component you can access the current controller through the registry:

$controller = $this->_registry->getController();

You can access the controller in any callback method from the event object:

$controller = $event->getSubject();

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 controller’s beforeFilter method, but after the controller’s initialize() method.
startup(Event $event)
Is called after the controller’s beforeFilter method but before the controller executes the current action handler.
beforeRender(Event $event)
Is called after the controller executes the requested action’s 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 controller’s 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.

262 Chapter 10. Controllers


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 PDF’s 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.

The App View

AppView is your application’s default View class. AppView itself extends the Cake\View\View class included
in CakePHP and is defined in src/View/AppView.php as follows:

<?php
namespace App\View;

use Cake\View\View;

class AppView extends View


{
}

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 View’s constructor for this kind of use:

263
CakePHP Cookbook Documentation, Release 3.8

<?php
namespace App\View;

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 syntax120
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; ?>

Using Short Tag support:

<?= $variable ?>

Alternative Control Structures

Control structures, like if, for, foreach, switch, and while can be written in a simplified format. Notice that
there are no braces. Instead, the end brace for the foreach is replaced with endforeach. Each of the control
structures listed above has a similar closing syntax: endif, endfor, endforeach, and endwhile. Also notice
that instead of using a semicolon after each structure (except the last one), there is a colon.
The following is an example using foreach:

<ul>
<?php foreach ($todo as $item): ?>
<li><?= $item ?></li>
<?php endforeach; ?>
</ul>

120 http://php.net/manual/en/control-structures.alternative-syntax.php

264 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

Another example, using if/elseif/else. Notice the colons:

<?php if ($username === 'sally'): ?>


<h3>Hi Sally</h3>
<?php elseif ($username === 'joe'): ?>
<h3>Hi Joe</h3>
<?php else: ?>
<h3>Hi unknown user</h3>
<?php endif; ?>

If you’d prefer using a templating language like Twig121 , 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 controller’s 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:
• templates: Templates are the part of the page that is unique to the action being run. They form the meat of your
application’s 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:

<?= h($user->bio); ?>

Setting View Variables

Cake\View\View::set(string $var, mixed $value)


Views have a set() method that is analogous to the set() found in Controller objects. Using set() from your view
file will add the variables to the layout and elements that will be rendered later. See Setting View Variables for more
information on using set().
In your view file you can do:
121 http://twig.sensiolabs.org

View Templates 265


CakePHP Cookbook Documentation, Release 3.8

$this->set('activeMenuButton', 'posts');

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><?= h($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->title);

$this->start('sidebar');
?>
<li>
<?php
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');

266 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

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 view’s 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();

Using View Blocks

View blocks provide a flexible API that allows you to define slots or blocks in your views/layouts that will be defined
elsewhere. For example, blocks are ideal for implementing things such as sidebars, or regions to load assets at the
bottom/top of the layout. Blocks can be defined in two ways: either as a capturing block, or by direct assignment.
The start(), append(), prepend(), assign(), fetch(), and end() methods allow you to work with
capturing blocks:
// Create the sidebar block.
$this->start('sidebar');
echo $this->element('sidebar/recent_topics');
echo $this->element('sidebar/recent_comments');
$this->end();

// Append into the sidebar later on.


$this->start('sidebar');
echo $this->fetch('sidebar');
echo $this->element('sidebar/popular_topics');
$this->end();

You can also append into a block using append():


$this->append('sidebar');
echo $this->element('sidebar/popular_topics');
$this->end();

// The same as the above.


$this->append('sidebar', $this->element('sidebar/popular_topics'));

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', '');

New in version 3.2: View::reset() was added in 3.2


Assigning a block’s content is often useful when you want to convert a view variable into a block. For example, you
may want to use a block for the page title, and sometimes assign the title as a view variable in the controller:

Using View Blocks 267


CakePHP Cookbook Documentation, Release 3.8

// In view file or layout above $this->fetch('title')


$this->assign('title', $title);

The prepend() method allows you to prepend content to an existing block:

// Prepend to sidebar
$this->prepend('sidebar', 'this content goes on top of sidebar');

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:

// 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>

Using Blocks for Script and CSS Files

The HtmlHelper ties into view blocks, and its script(), css(), and meta() methods each update a block
with the same name when used with the block = true option:

<?php
// In your view file
$this->Html->script('carousel', ['block' => true]);
$this->Html->css('carousel', ['block' => true]);
?>

// In your layout file.


<!DOCTYPE html>
<html lang="en">
<head>
<title><?= h($this->fetch('title')) ?></title>
<?= $this->fetch('script') ?>
<?= $this->fetch('css') ?>
</head>
// Rest of the layout follows

268 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

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') ?>

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.
CakePHP’s 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') Here’s 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).

Layouts 269
CakePHP Cookbook Documentation, Release 3.8

The content block contains the contents of the rendered view.


You can set the title block content from inside your view file:

$this->assign('title', 'View Active Users');

Empty values for the title block will be automatically replaced with a representation of the current template path,
such as 'Admin/Articles'.
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 view’s $layout property:

// From a controller
public function view()
{
// Set the layout.
$this->viewBuilder()->setLayout('admin');

// Before 3.4
$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 viewActive()
{
$this->set('title', 'View Active Users');
$this->viewBuilder()->setLayout('default_small_ad');

// or the following before 3.4


$this->viewBuilder()->layout('default_small_ad');

// or the following before 3.1


$this->layout = 'default_small_ad';
}

public function viewImage()


{
$this->viewBuilder()->setLayout('image');

// Output user image


}
}

Besides a default layout CakePHP’s official skeleton app also has an ‘ajax’ layout. The Ajax layout is handy for
crafting AJAX responses - it’s an empty layout. (Most AJAX calls only require a bit of markup in return, rather than a
fully-rendered interface.)

270 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

The skeleton app also has a default layout to help generate RSS.

Using Layouts from Plugins

If you want to use a layout that exists in a plugin, you can use plugin syntax. For example, to use the contact layout
from the Contacts plugin:

namespace App\Controller;

class UsersController extends AppController


{
public function viewActive()
{
$this->viewBuilder()->setLayout('Contacts.contact');
// or the following before 3.4
$this->viewBuilder()->layout('Contacts.contact');
// or the following before 3.1
$this->layout = 'Contacts.contact';
}
}

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 mini-view 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');

Passing Variables into an Element

You can pass data to an element through the element’s second argument:

echo $this->element('helpbox', [
"helptext" => "Oh, this text is very helpful."
]);

Inside the element file, all the passed variables are available as members of the parameter array. 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."

Keep in mind that in those view vars are merged with the view vars from the view itself. So all view vars set using
Controller::set() in the controller and View::set() in the view itself are also available inside the element.

Elements 271
CakePHP Cookbook Documentation, Release 3.8

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 you’ve 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() call’s
cached result. For example:

echo $this->element(
'helpbox',
['var' => $var],
['cache' => ['key' => 'first_use', 'config' => 'view_long']]
);

echo $this->element(
'helpbox',
['var' => $differenVar],
['cache' => ['key' => 'second_use', 'config' => 'view_long']]
);

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.

272 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

Requesting Elements from a Plugin

If you are using a plugin and wish to use elements from within the plugin, just use the familiar plugin syntax. If the
view is being rendered for a plugin controller/action, the plugin name will automatically be prefixed onto all elements
used, unless another plugin name is present. If the element doesn’t exist in the plugin, it will look in the main APP
folder:

echo $this->element('Contacts.helpbox');

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');

Requesting Elements from the App

If you are within a plugin’s template file and want to render an element residing in your main application rather than
this or another plugin, use the following:

echo $this->element('some_global_element', [], ['plugin' => false]);


// or...
echo $this->element('some_global_element', ['localVar' => $someData], ['plugin' =>
˓→false]);

Routing prefix and Elements

New in version 3.0.1.


If you have a Routing prefix configured, the Element path resolution can switch to a prefix location, as Layouts and
action View do. Assuming you have a prefix “Admin” configured and you call:

echo $this->element('my_element');

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.

Caching Sections of Your View

Cake\View\View::cache(callable $block, array $options = [])


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:

Elements 273
CakePHP Cookbook Documentation, Release 3.8

// 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.

Creating Your Own View Classes

You may need to create custom view classes to enable new types of data views, or add additional custom view-
rendering logic to your application. Like most components of CakePHP, view classes have a few conventions:
• View class files should be put in src/View. For example: src/View/PdfView.php
• View classes should be suffixed with View. For example: PdfView.
• When referencing view class names you should omit the View suffix. For example:
$this->viewBuilder()->setClassName('Pdf');.
You’ll also want to extend View to ensure things work correctly:

// 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.
}
}

274 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

Replacing the render method lets you take full control over how your content is rendered.

More About Views

View Cells

View cells are small mini-controllers that can invoke view logic and render out templates. The idea of cells is borrowed
from cells in Ruby122 , where they fulfill a similar role and purpose.

When to use Cells

Cells are ideal for building reusable page components that require interaction with models, view logic, and rendering
logic. A simple example would be the cart in an online store, or a data-driven navigation menu in a CMS.

Creating a Cell

To create a cell, define a class in src/View/Cell and a template in src/Template/Cell/. In this example, we’ll be making
a cell to display the number of messages in a user’s notification inbox. First, create the class file. Its contents should
look like:

namespace App\View\Cell;

use Cake\View\Cell;

class InboxCell extends Cell


{

public function display()


{
}

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.
We’ll 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

Would generate the code we created above.


122 https://github.com/apotonick/cells

More About Views 275


CakePHP Cookbook Documentation, Release 3.8

Implementing the Cell

Assume that we are working on an application that allows users to send messages to each other. We have a Messages
model, and we want to show the count of unread messages without having to pollute AppController. This is a perfect
use case for a cell. In the class we just made, add the following:

namespace App\View\Cell;

use Cake\View\Cell;

class InboxCell extends Cell


{

public function display()


{
$this->loadModel('Messages');
$unread = $this->Messages->find('unread');
$this->set('unread_count', $unread->count());
}

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>

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 action’s 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:

276 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

namespace App\Controller;

use App\Controller\AppController;
use Cake\View\CellTrait;

class DashboardsController extends AppController


{
use CellTrait;

// More code.
}

Passing Arguments to a Cell

You will often want to parameterize cell methods to make cells more flexible. By using the second and third arguments
of cell(), you can pass action parameters and additional options to your cell classes, as an indexed array:

$cell = $this->cell('Inbox::recent', ['-3 days']);

The above would match the following function signature:

public function recent($since)


{
}

Rendering a Cell

Once a cell has been loaded and executed, you’ll 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 file-
name 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() ?>.

Rendering Alternate Templates

By convention cells render templates that match the action they are executing. If you need to render a different view
template, you can specify the template to use when rendering the cell:

// Calling render() explicitly


echo $this->cell('Inbox::recent', ['-3 days'])->render('messages');

// Set template before echoing the cell.


$cell = $this->cell('Inbox');
$cell->viewBuilder()->setTemplate('messages');

More About Views 277


CakePHP Cookbook Documentation, Release 3.8

// Before 3.4
$cell->viewBuilder()->template('messages');
// Before 3.1
$cell->template = 'messages';
echo $cell;

Caching Cell Output

When rendering a cell you may want to cache the rendered output if the contents don’t change often or to help improve
performance of your application. You can define the cache option when creating a cell to enable & configure caching:
// Cache using the default config and a generated key
$cell = $this->cell('Inbox', [], ['cache' => true]);

// Cache to a specific cache config and a generated key


$cell = $this->cell('Inbox', [], ['cache' => ['config' => 'cell_cache']]);

// Specify the key and config to use.


$cell = $this->cell('Inbox', [], [
'cache' => ['config' => 'cell_cache', 'key' => 'inbox_' . $user->id]
]);

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 tem-
plate / layout. Each cell is self-contained and only has access to variables passed as arguments to the View::cell()
call.

Paginating Data inside a Cell

Creating a cell that renders a paginated result set can be done by leveraging the Paginator class of the ORM. An
example of paginating a user’s favorite messages could look like:
namespace App\View\Cell;

use Cake\View\Cell;
use Cake\Datasource\Paginator;

class FavoritesCell extends Cell


{
public function display($user)
{
$this->loadModel('Messages');

// Create a paginator
$paginator = new Paginator();

// Paginate the model


$results = $paginator->paginate(
$this->Messages,
$this->request->getQueryParams(),
[
// Use a parameterized custom finder.

278 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

'finder' => ['favorites' => [$user]],

// Use scoped query string parameters.


'scope' => 'favorites',
]
);

$paging = $paginator->getPagingParams() + (array)$request->getParam('paging');


$this->request = $this->request->withParam('paging', $paging));

$this->set('favorites', $results);
}
}

The above cell would paginate the Messages model using scoped pagination parameters.
New in version 3.5.0: Cake\Datasource\Paginator was added in 3.5.0.

Cell Options

Cells can declare constructor options that are converted into properties when creating a cell object:
namespace App\View\Cell;

use Cake\View\Cell;
use Cake\Datasource\Paginator;

class FavoritesCell extends Cell


{
protected $_validCellOptions = ['limit'];

protected $limit = 3;

public function display($userId)


{
$this->loadModel('Users');
$result = $this->Users->find('friends', ['for' => $userId]);
$this->set('favorites', $result);
}
}

Here we have defined a $limit property and add limit as a cell option. This will allow us to define the option
when creating the cell:
$cell = $this->cell('Favorites', [$user->id], ['limit' => 10])

Cell options are handy when you want data available as properties allowing you to override default values.

Themes

Themes in CakePHP are simply plugins that focus on providing template files. See the section on Creating Your Own
Plugins. You can take advantage of themes, making it easy to switch the look and feel of your page quickly. 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 controller’s action or beforeRender() callback:

More About Views 279


CakePHP Cookbook Documentation, Release 3.8

class ExamplesController extends AppController


{
// For CakePHP before 3.1
public $theme = 'Modern';

public function beforeRender(\Cake\Event\Event $event)


{
$this->viewBuilder()->setTheme('Modern');

// For CakePHP before 3.5


$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. It’s important to remember that CakePHP expects PascalCase 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 plug-
ins/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 can’t 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.
If your theme also acts as a plugin, don’t forget to ensure it is loaded in your application’s bootstrap method. For
example:

// Load our plugin theme residing in the folder /plugins/Modern


$this->addPlugin('Modern');

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, it’s recommended that
you Improve Your Application’s Performance.
All of CakePHP’s built-in helpers are aware of themes and will create the correct paths automatically. Like template
files, if a file isn’t 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

280 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

JSON and XML views

The JsonView and XmlView let you create JSON and XML responses, and integrate with the
Cake\Controller\Component\RequestHandlerComponent.
By enabling RequestHandlerComponent in your application, and enabling support for the json and/or xml
extensions, you can automatically leverage the new view classes. JsonView and XmlView will be referred to as
data views for the rest of this page.
There are two ways you can generate data views. The first is by using the _serialize key, and the second is by
creating normal template files.

Enabling Data Views in Your Application

Before you can use the data view classes, you’ll first need to load the
Cake\Controller\Component\RequestHandlerComponent in your controller:
public function initialize()
{
...
$this->loadComponent('RequestHandler');
}

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.
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 don’t 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.

More About Views 281


CakePHP Cookbook Documentation, Release 3.8

$this->set('articles', $this->paginate());
// Specify which view vars JsonView should serialize.
$this->set('_serialize', 'articles');
}
}

You can also define _serialize as an array of view variables to combine:

namespace App\Controller;

class ArticlesController extends AppController


{
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler');
}

public function index()


{
// Some code that created $articles and $comments

// Set the view vars that have to be serialized.


$this->set(compact('articles', 'comments'));

// Specify which view vars JsonView should serialize.


$this->set('_serialize', ['articles', 'comments']);
}
}

Defining _serialize as an array has added the 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.

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'));

282 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

You can do more complex manipulations, or use helpers to do formatting as well. The data view classes don’t support
layouts. They assume that the view file will output the serialized content.

Note: As of 3.1.0 until 3.5.0, AppController in the application skeleton automatically added '_serialize'
=> true to all XML/JSON requests. You will need to remove this code from the beforeRender callback or set
'_serialize' => false in your controller’s action if you want to use view files.

Creating XML Views

class XmlView
By default when using _serialize the XmlView will wrap your serialized view variables with a <response>
node. You can set a custom name for this node using the _rootNode view variable.
The XmlView class supports the _xmlOptions variable that allows you to customize the options used to generate
XML, e.g. tags vs attributes.
An example of using XmlView would be to generate a sitemap.xml123 . This document type requires that you change
_rootNode and set attributes. Attributes are defined using the @ prefix:

public function sitemap()


{
$pages = $this->Pages->find();
$urls = [];
foreach ($pages as $page) {
$urls[] = [
'loc' => Router::url(['controller' => 'Pages', 'action' => 'view', $page->
˓→slug, '_full' => true]),

'lastmod' => $page->modified->format('Y-m-d'),


'changefreq' => 'daily',
'priority' => '0.5'
];
}

// Define a custom root node in the generated document.


$this->set('_rootNode', 'urlset');
$this->set([
// Define an attribute on the root node.
'@xmlns' => 'http://www.sitemaps.org/schemas/sitemap/0.9',
'url' => $urls
]);
$this->set('_serialize', ['@xmlns', 'url']);
}

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_encode124 documentation for the valid values of this option.
For example, to serialize validation error output of CakePHP entities in a consistent form of JSON do:
123 https://www.sitemaps.org/protocol.html
124 http://php.net/json_encode

More About Views 283


CakePHP Cookbook Documentation, Release 3.8

// In your controller's action when saving failed


$this->set('errors', $articles->errors());
$this->set('_jsonOptions', JSON_FORCE_OBJECT);
$this->set('_serialize', ['errors']);

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.

Example Usage

While the RequestHandlerComponent can automatically set the view based on the request content-type or extension,
you could also handle view mappings in your controller:
// src/Controller/VideosController.php
namespace App\Controller;

use App\Controller\AppController;

// Prior to 3.6 use Cake\Network\Exception\NotFoundException


use Cake\Http\Exception\NotFoundException;

class VideosController extends AppController


{
public function export($format = '')
{
$format = strtolower($format);

// Format to view mapping


$formats = [
'xml' => 'Xml',
'json' => 'Json',
];

// Error on unknown type


if (!isset($formats[$format])) {
throw new NotFoundException(__('Unknown format.'));
}

// Set Out Format View


$this->viewBuilder()->className($formats[$format]);

// Get data
$videos = $this->Videos->find('latest');

// Set Data View


$this->set(compact('videos'));
$this->set('_serialize', ['videos']);

// Set Force Download


// Prior to 3.4.0
// $this->response->download('report-' . date('YmdHis') . '.' . $format);

284 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

return $this->response->withDownload('report-' . date('YmdHis') . '.' .


˓→ $format);
}
}

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:

Breadcrumbs

class Cake\View\Helper\BreadcrumbsHelper(View $view, array $config = [])


New in version 3.3.6.
BreadcrumbsHelper provides a way to easily deal with the creation and rendering of a breadcrumbs trail for your app.

Creating a Breadcrumbs Trail

You can add a crumb to the list using the add() method. It takes three arguments:
• title The string to be displayed as a the title of the crumb
• url A string or an array of parameters that will be given to the Url
• options An array of attributes for the item and itemWithoutLink templates. See the section about defining
attributes for the item for more information.
In addition to adding to the end of the trail, you can do a variety of operations:
// Add at the end of the trail
$this->Breadcrumbs->add(
'Products',
['controller' => 'products', 'action' => 'index']
);

// Add multiple crumbs at the end of the trail


$this->Breadcrumbs->add([
['title' => 'Products', 'url' => ['controller' => 'products', 'action' => 'index
˓→']],

['title' => 'Product name', 'url' => ['controller' => 'products', 'action' =>
˓→'view', 1234]]

]);

// Prepended crumbs will be put at the top of the list


$this->Breadcrumbs->prepend(
'Products',
['controller' => 'products', 'action' => 'index']
);

More About Views 285


CakePHP Cookbook Documentation, Release 3.8

// Prepend multiple crumbs at the top of the trail, in the order given
$this->Breadcrumbs->prepend([
['title' => 'Products', 'url' => ['controller' => 'products', 'action' => 'index
˓→']],

['title' => 'Product name', 'url' => ['controller' => 'products', 'action' =>
˓→'view', 1234]]

]);

// Insert in a specific slot. If the slot is out of


// bounds, an exception will be raised.
$this->Breadcrumbs->insertAt(
2,
'Products',
['controller' => 'products', 'action' => 'index']
);

// Insert before another crumb, based on the title.


// If the named crumb title cannot be found,
// an exception will be raised.
$this->Breadcrumbs->insertBefore(
'A product name', // the title of the crumb to insert before
'Products',
['controller' => 'products', 'action' => 'index']
);

// Insert after another crumb, based on the title.


// If the named crumb title cannot be found,
// an exception will be raised.
$this->Breadcrumbs->insertAfter(
'A product name', // the title of the crumb to insert after
'Products',
['controller' => 'products', 'action' => 'index']
);

Using these methods gives you the ability to work with CakePHP’s 2-step rendering process. Since templates and
layouts are rendered from the inside out (meaning, included elements are rendered first), this allows you to define
precisely where you want to add a breadcrumb.

Rendering the Breadcrumbs Trail

After adding crumbs to the trail, you can easily render it using the render() method. This method accepts two array
arguments:
• $attributes : An array of attributes that will applied to the wrapper template. This gives you the ability
to add attributes to the HTML tag. It accepts the special templateVars key to allow the insertion of custom
template variables in the template.
• $separator : An array of attributes for the separator template. Possible properties are:
– separator The string to be displayed as a separator
– innerAttrs To provide attributes in case your separator is divided in two elements
– templateVars Allows the insertion of custom template variable in the template
All other properties will be converted as HTML attributes and will replace the attrs key in the template. If
you use the default for this option (empty), it will not render a separator.
Here is an example of how to render a trail:

286 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

echo $this->Breadcrumbs->render(
['class' => 'breadcrumbs-trail'],
['separator' => '<i class="fa fa-angle-right"></i>']
);

Customizing the Output

The BreadcrumbsHelper internally uses the StringTemplateTrait, which gives the ability to easily customize
output of various HTML strings. It includes four templates, with the following default declaration:
[
'wrapper' => '<ul{{attrs}}>{{content}}</ul>',
'item' => '<li{{attrs}}><a href="{{url}}"{{innerAttrs}}>{{title}}</a></li>{
˓→{separator}}',

'itemWithoutLink' => '<li{{attrs}}><span{{innerAttrs}}>{{title}}</span></li>{


˓→{separator}}',

'separator' => '<li{{attrs}}><span{{innerAttrs}}>{{separator}}</span></li>'


]

You can easily customize them using the templates() method from the StringTemplateTrait:
$this->Breadcrumbs->templates([
'wrapper' => '<nav class="breadcrumbs"><ul{{attrs}}>{{content}}</ul></nav>',
]);

Since your templates will be rendered, the templateVars option allows you to add your own template variables in
the various templates:
$this->Breadcrumbs->templates([
'item' => '<li{{attrs}}>{{icon}}<a href="{{url}}"{{innerAttrs}}>{{title}}</a></li>
˓→{{separator}}'

]);

And to define the {{icon}} parameter, just specify it when adding the crumb to the trail:
$this->Breadcrumbs->add(
'Products',
['controller' => 'products', 'action' => 'index'],
[
'templateVars' => [
'icon' => '<i class="fa fa-money"></i>'
]
]
);

Defining Attributes for the Item

If you want to apply specific HTML attributes to both the item and its sub-item , you can leverage the innerAttrs
key, which the $options argument provides. Everything except innerAttrs and templateVars will be ren-
dered as HTML attributes:
$this->Breadcrumbs->add(
'Products',
['controller' => 'products', 'action' => 'index'],

More About Views 287


CakePHP Cookbook Documentation, Release 3.8

[
'class' => 'products-crumb',
'data-foo' => 'bar',
'innerAttrs' => [
'class' => 'inner-products-crumb',
'id' => 'the-products-crumb'
]
]
);

// Based on the default template, this will render the following HTML :
<li class="products-crumb" data-foo="bar">
<a href="/products/index" class="inner-products-crumb" id="the-products-crumb">
˓→Products</a>

</li>

Clearing the Breadcrumbs

You can clear the bread crumbs using the reset() method. This can be useful when you want to transform the
crumbs and overwrite the list:

$crumbs = $this->Breadcrumbs->getCrumbs();
$crumbs = collection($crumbs)->map(function ($crumb) {
$crumb['options']['class'] = 'breadcrumb-item';
return $crumb;
})->toArray();

$this->Breadcrumbs->reset()->add($crumbs);

New in version 3.4.0: The reset() method was added in 3.4.0

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. Flash-
Component and FlashHelper primarily use elements to render flash messages. Flash elements are found under the
src/Template/Element/Flash directory. You’ll notice that CakePHP’s App template comes with three flash elements:
success.ctp, default.ctp, and error.ctp.

Rendering Flash Messages

To render a flash message, you can simply use FlashHelper’s render() method in your template file:

<?= $this->Flash->render() ?>

By default, CakePHP uses a “flash” key for flash messages in a session. But, if you’ve 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:

288 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

// In your Controller
$this->Flash->set('The user has been saved.', [
'element' => 'success'
]);

// In your template file: Will use great_success.ctp instead of succcess.ctp


<?= $this->Flash->render('flash', [
'element' => 'great_success'
]);

Note: When building custom flash message templates, be sure to properly HTML encode any user data. CakePHP
won’t escape flash message parameters for you.

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, using different layouts for your front-end and admin section.

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.

Starting a Form

Cake\View\Helper\FormHelper::create(mixed $context = null, array $options = [])


• $context - The context for which the form is being defined. Can be an ORM entity, ORM resultset, array of
metadata or false/null (to make a model-less form).
• $options - An array of options and/or HTML attributes.
The first method you’ll need to use in order to take advantage of the FormHelper is create(). This method outputs
an opening form tag.

More About Views 289


CakePHP Cookbook Documentation, Release 3.8

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 $context argument is used as the form’s ‘context’. There are several built-in form contexts and you can add
your own, which we’ll cover below, in a following section. The built-in providers map to the following values of
$context:
• An Entity instance or an iterator will map to EntityContext125 ; this context class allows FormHelper to work
with results from the built-in ORM.
• An array containing the 'schema' key, will map to ArrayContext126 which allows you to create simple data
structures to build forms against.
• null and false will map to NullContext127 ; this context class simply satisfies the interface FormHelper
requires. This context is useful if you want to build a short form that doesn’t require ORM persistence.
All context classes also have access to the request data, making it simpler to build forms.
Once a form has been created with a context, all controls 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;
}
$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) ?>

125 https://api.cakephp.org/3.x/class-Cake.View.Form.EntityContext.html
126 https://api.cakephp.org/3.x/class-Cake.View.Form.ArrayContext.html
127 https://api.cakephp.org/3.x/class-Cake.View.Form.NullContext.html

290 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

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.

In some cases, the entity’s ID is automatically appended to the end of the form’s action URL. If you would like to
avoid an ID being added to the URL, you can pass a string to $options['url'], such as '/my-account' or
\Cake\Routing\Router::url(['controller' => 'Users', 'action' => 'myAccount']).

Options for Form Creation

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. Valid values:
• 'type' - Allows you to choose the type of form to create. If no type is provided then it will be autodetected
based on the form context. Valid values:
– 'get' - Will set the form method to HTTP GET.
– 'file' - Will set the form method to POST and the 'enctype' to “multipart/form-data”.
– 'post' - Will set the method to POST.
– 'put', 'delete', 'patch' - Will override the HTTP method with PUT, DELETE or PATCH
respectively, when the form is submitted.
• 'method' - Valid values are the same as above. Allows you to explicitly override the form’s method.
• 'url' - Specify the URL the form will submit to. Can be a string or a URL array.
• 'encoding' - Sets the accept-charset encoding for the form. Defaults to
Configure::read('App.encoding').
• 'enctype' - Allows you to set the form encoding explicitly.
• 'templates' - The templates you want to use for this form. Any templates provided will be merged on top
of the already loaded templates. Can be either a filename (without extension) from /config or an array of
templates to use.
• 'context' - Additional options for the form context class. (For example the EntityContext accepts a
'table' option that allows you to set the specific Table class the form should be based on.)
• 'idPrefix' - Prefix for generated ID attributes.
• 'templateVars' - Allows you to provide template variables for the formStart template.
• autoSetCustomValidity - Set to true to use custom required and notBlank validation messages in the
control’s HTML5 validity message. Default is false.

Tip: Besides the above options you can provide, in the $options argument, any valid HTML attributes that you
want to pass to the created form element.

More About Views 291


CakePHP Cookbook Documentation, Release 3.8

Getting form values from the query string

New in version 3.4.0.


A FormHelper’s values sources define where its rendered elements, such as input-tags, receive their values from.
By default FormHelper draws its values from the ‘context’. The default contexts, such as EntityContext, will
fetch data from the current entity, or from $request->getData().
If however, you are building a form that needs to read from the query string, you can use valueSource() to change
where FormHelper reads data input data from:

// Prioritize query string over context:


echo $this->Form->create($article, [
'valueSources' => ['query', 'context']
]);

// Same effect:
echo $this->Form
->setValueSources(['query', 'context'])
->create($articles);

// Only read data from the query string


echo $this->Form->create($article);
$this->Form->setValueSources('query');

// Same effect:
echo $this->Form->create($article, ['valueSources' => 'query']);

The supported sources are context, data and query. You can use one or more sources. Any widgets generated
by FormHelper will gather their values from the sources, in the order you setup.
The value sources will be reset to the default (['context']) when end() is called.

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 a 'file' value for type, changes the form submission method to ‘post’, and includes an enctype of
“multipart/form-data” 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.
E.g.

echo $this->Form->create($article, ['type' => 'file']);

Output:

<form enctype="multipart/form-data" method="post" action="/articles/add">

292 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

When using 'put', 'patch' or 'delete' as 'type' values, 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.

Setting a URL for the Form

Using the 'url' option allows you to point the form to a specific action in your current controller or another controller
in your application.
For example, if you’d like to point the form to the publish() action of the current controller, you would supply an
$options array, like the following:

echo $this->Form->create($article, ['url' => ['action' => 'publish']]);

Output:

<form method="post" action="/articles/publish">

If the desired form action isn’t 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">

Or you can point to an external domain:

echo $this->Form->create(null, [
'url' => 'http://www.google.com/search',
'type' => 'get'
]);

Output:

<form method="get" action="http://www.google.com/search">

Use 'url' => false if you don’t 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']
]);

More About Views 293


CakePHP Cookbook Documentation, Release 3.8

The above will use the rules defined in the register validator, which are defined by
UsersTable::validationRegister(), for $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:

echo $this->Form->create($user, [
'context' => [
'validator' => [
'Users' => 'register',
'Comments' => 'default'
]
]
]);

The above would use register for the user, and default for the user’s comments.

Creating context classes

While the built-in context classes are intended to cover the basic cases you’ll 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\ContextInterface128 . 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 Controls

Cake\View\Helper\FormHelper::control(string $fieldName, array $options = [])


• $fieldName - A field name in the form 'Modelname.fieldname'.
• $options - An optional array that can include both Options for Control, and options of the other methods
(which control() employs internally to generate various HTML elements) as well as any valid HTML at-
tributes.
The control() method lets you to generate complete form controls. These controls will include a wrapping div,
label, control widget, and validation error if necessary. By using the metadata in the form context, this method will
choose an appropriate control type for each field. Internally control() uses the other methods of FormHelper.

Tip: Please note that while the fields generated by the control() method are called generically “inputs” on this
page, technically speaking, the control() method can generate not only all of the HTML input type elements,
but also other HTML form elements (e.g. select, button, textarea).

By default the control() method will employ the following widget templates:
128 https://api.cakephp.org/3.x/class-Cake.View.Form.ContextInterface.html

294 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

'inputContainer' => '<div class="input {{type}}{{required}}">{{content}}</div>'


'input' => '<input type="{{type}}" name="{{name}}"{{attrs}}/>'

In case of validation errors it will also use:

'inputContainerError' => '<div class="input {{type}}{{required}} error">{{content}}{


˓→{error}}</div>'

The type of control created (when we provide no additional options to specify the generated element type) is inferred
via model introspection and depends on the column datatype:
Column Type Resulting Form Field
string, uuid (char, varchar, etc.) text
boolean, tinyint(1) checkbox
decimal number
float number
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 control type if you need to:

echo $this->Form->control('published', ['type' => 'checkbox']);

Tip: As a small subtlety, generating specific elements via the control() form method will always also generate
the wrapping div, by default. Generating the same type of element via one of the specific form methods (e.g.
$this->Form->checkbox('published');) in most cases won’t generate the wrapping div. Depending on
your needs you can use one or another.

The wrapping div will have a required class name appended if the validation rules for the model’s field in-
dicate that it is required and not allowed to be empty. You can disable automatic required flagging using the
'required' option:

echo $this->Form->control('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, let’s assume that your Users model includes fields for a username (varchar), password (varchar), ap-
proved (datetime) and quote (text). You can use the control() method of the FormHelper to create appropriate
controls for all of these form fields:

More About Views 295


CakePHP Cookbook Documentation, Release 3.8

echo $this->Form->create($user);
// The following generates a Text input
echo $this->Form->control('username');
// The following generates a Password input
echo $this->Form->control('password');
// Assuming 'approved' is a datetime or timestamp field the following
//generates: Day, Month, Year, Hour, Minute
echo $this->Form->control('approved');
// The following generates a Textarea element
echo $this->Form->control('quote');

echo $this->Form->button('Add');
echo $this->Form->end();

A more extensive example showing some options for a date field:

echo $this->Form->control('birth_dt', [
'label' => 'Date of birth',
'minYear' => date('Y') - 70,
'maxYear' => date('Y') - 18,
]);

Besides the specific Options for Control, you also can specify any option accepted by corresponding specific method
for the chosen (or inferred by CakePHP) control type and any HTML attribute (for instance onfocus).
If you want to create a select form field while using a belongsTo (or hasOne) relation, you can add the following to
your UsersController (assuming your User belongsTo Group):

$this->set('groups', $this->Users->Groups->find('list'));

Afterwards, add the following to your view template:

echo $this->Form->control('group_id', ['options' => $groups]);

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'));

Afterwards, add the following to your view template:

echo $this->Form->control('groups._ids', ['options' => $groups]);

If your model name consists of two or more words (e.g. “UserGroups”), when passing the data using set() you
should name your data in a pluralised and lower camelCased129 format as follows:

$this->set('userGroups', $this->UserGroups->find('list'));

Note: You should not use FormHelper::control() to generate submit buttons. Use
View\Helper\FormHelper::submit() instead.

129 https://en.wikipedia.org/wiki/Camel_case#Variations_and_synonyms

296 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

Field Naming Conventions

When creating control widgets you should name your fields after the matching attributes in the form’s entity. For
example, if you created a form for an $article entity, you would create fields named after the properties. E.g.
title, body and published.
You can create controls for associated models, or arbitrary models by passing in association.fieldname as the
first parameter:
echo $this->Form->control('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.
When creating datetime related controls, 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 con-
verted into DateTime objects when entities are marshalled.

Options for Control

FormHelper::control() supports a large number of options via its $options argument. In addition
to its own options, control() accepts options for the inferred/chosen generated control types (e.g. for
checkbox or textarea), as well as HTML attributes. This subsection will cover the options specific to
FormHelper::control().
• $options['type'] - A string that specifies the widget type to be generated. In addition to the field types
found in the Creating Form Controls, you can also create 'file', 'password', and any other type supported
by HTML5. By specifying a 'type' you will force the type of the generated control, overriding model
introspection. Defaults to null.
E.g.
echo $this->Form->control('field', ['type' => 'file']);
echo $this->Form->control('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'] - Either a string caption or an array of options for the label. You can set this key to
the string you would like to be displayed within the label that usually accompanies the input HTML element.
Defaults to null.
E.g.
echo $this->Form->control('name', [
'label' => 'The User Alias'
]);

More About Views 297


CakePHP Cookbook Documentation, Release 3.8

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 generation of the label element.
E.g.

echo $this->Form->control('name', ['label' => false]);

Output:

<div class="input">
<input name="name" type="text" value="" id="name" />
</div>

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.
E.g.

echo $this->Form->control('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['options'] - You can provide in here an array containing the elements to be generated for
widgets such as radio or select, which require an array of items as an argument (see Creating Radio
Buttons and Creating Select Pickers for more details). Defaults to null.
• $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 the error message output & field classes set the 'error'
key to false. Defaults to null.
E.g.

echo $this->Form->control('name', ['error' => false]);

To override the model error messages use an array with the keys matching the original validation error messages.
E.g.

$this->Form->control('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.

298 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

• $options['nestedInput'] - Used with checkboxes and radio buttons. Controls whether the input ele-
ment is generated inside or outside the label element. When control() generates a checkbox or a radio
button, you can set this to false to force the generation of the HTML input element outside of the label
element.
On the other hand you can set this to true for any control type to force the generated input element inside the
label. If you change this for radio buttons then you need to also modify the default radioWrapper template.
Depending on the generated control type it defaults to true or false.
• $options['templates'] - The templates you want to use for this input. Any specified templates will
be merged on top of the already loaded templates. This option can be either a filename (without extension) in
/config that contains the templates you want to load, or an array of templates to use.
• $options['labelOptions'] - Set this to false to disable labels around nestedWidgets or set it to an
array of attributes to be provided to the label tag.

Generating Specific Types of Controls

In addition to the generic control() method, FormHelper has specific methods for generating a number of
different types of controls. These can be used to generate just the control widget itself, and combined with other meth-
ods like View\Helper\FormHelper::label() and View\Helper\FormHelper::error() to generate
fully custom form layouts.

Common Options For Specific Controls

Many of the various control element methods support a common set of options which, depending on the form method
used, must be provided inside the $options or in the $attributes array argument. All of these options are also
supported by the control() method. To reduce repetition, the common options shared by all control methods are
as follows:
• 'id' - Set this key to force the value of the DOM id for the control. This will override the 'idPrefix' that
may be set.
• 'default' - Used to set a default value for the control 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']);

Example with select field (size “Medium” will be selected as default):

$sizes = ['s' => 'Small', 'm' => 'Medium', 'l' => 'Large'];
echo $this->Form->select('size', $sizes, ['default' => 'm']);

Note: You cannot use default to check a checkbox - instead you might set the value in
$this->request->getData() in your controller, or set the control option 'checked' to true.
Beware of using false to assign a default value. A false value is used to disable/exclude options of a control
field, so 'default' => false would not set any value at all. Instead use 'default' => 0.

• 'value' - Used to set a specific value for the control field. This will override any value that may else be
injected from the context, such as Form, Entity or request->getData() etc.

More About Views 299


CakePHP Cookbook Documentation, Release 3.8

Note: If you want to set a field to not render its value fetched from context or valuesSource you will need to
set '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 control 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.

Creating Input Elements

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 or $attributes parameter. In this case, however, this parameter is used
primarily to specify HTML tag attributes (such as the value or DOM id of an element in the form).

Creating Text Inputs

Cake\View\Helper\FormHelper::text(string $name, array $options)


• $name - A field name in the form 'Modelname.fieldname'.
• $options - An optional array including any of the Common Options For Specific Controls as well as any valid
HTML attributes.
Creates a simple input HTML element of text type.
E.g.

echo $this->Form->text('username', ['class' => 'users']);

Will output:

<input name="username" type="text" class="users">

Creating Password Inputs

Cake\View\Helper\FormHelper::password(string $fieldName, array $options)


• $fieldName - A field name in the form 'Modelname.fieldname'.
• $options - An optional array including any of the Common Options For Specific Controls as well as any valid
HTML attributes.
Creates a simple input element of password type.
E.g.

echo $this->Form->password('password');

Will output:

<input name="password" value="" type="password">

300 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

Creating Hidden Inputs

Cake\View\Helper\FormHelper::hidden(string $fieldName, array $options)


• $fieldName - A field name in the form 'Modelname.fieldname'.
• $options - An optional array including any of the Common Options For Specific Controls as well as any valid
HTML attributes.
Creates a hidden form input.
E.g.

echo $this->Form->hidden('id');

Will output:

<input name="id" type="hidden" />

Creating Textareas

Cake\View\Helper\FormHelper::textarea(string $fieldName, array $options)


• $fieldName - A field name in the form 'Modelname.fieldname'.
• $options - An optional array including any of the Common Options For Specific Controls, of the specific
textarea options (see below) as well as any valid HTML attributes.
Creates a textarea control field. The default widget template used is:

'textarea' => '<textarea name="{{name}}"{{attrs}}>{{value}}</textarea>'

For example:

echo $this->Form->textarea('notes');

Will output:

<textarea name="notes"></textarea>

If the form is being edited (i.e. the array $this->request->getData() contains the information previously
saved for the User entity), the value corresponding to notes field will automatically be added to the HTML gener-
ated.
Example:

<textarea name="notes" id="notes">


This text is to be edited.
</textarea>

Options for Textarea


In addition to the Common Options For Specific Controls, textarea() supports a couple of specific options:
• 'escape' - Determines whether or not the contents of the textarea should be escaped. Defaults to true.
E.g.

More About Views 301


CakePHP Cookbook Documentation, Release 3.8

echo $this->Form->textarea('notes', ['escape' => false]);


// OR....
echo $this->Form->control('notes', ['type' => 'textarea', 'escape' => false]);

• 'rows', 'cols' - You can use these two keys to set the HTML attributes which specify the number of rows
and columns for the textarea field.
E.g.
echo $this->Form->textarea('comment', ['rows' => '5', 'cols' => '5']);

Output:
<textarea name="comment" cols="5" rows="5">
</textarea>

Creating Select, Checkbox and Radio Controls

These controls share some commonalities and a few options and thus, they are all grouped in this subsection for easier
reference.

Options for Select, Checkbox and Radio Controls

You can find below the options which are shared by select(), checkbox() and radio() (the options particular
only to one of the methods are described in each method’s own section.)
• 'value' - Sets or selects the value of the affected element(s):
– For checkboxes, it sets the HTML 'value' attribute assigned to the input element to whatever you
provide as value.
– For radio buttons or select pickers it defines which element will be selected when the form is rendered (in
this case 'value' must be assigned a valid, existent element value). May also be used in combination
with any select-type control, such as date(), time(), dateTime():
echo $this->Form->time('close_time', [
'value' => '13:30:00'
]);

Note: The 'value' key for date() and dateTime() controls may also have as value a UNIX timestamp,
or a DateTime object.

For a select control where you set the 'multiple' attribute to true, you can provide an array with the
values you want to select by default:
// HTML <option> elements with values 1 and 3 will be rendered preselected
echo $this->Form->select(
'rooms',
[1, 2, 3, 4, 5],
[
'multiple' => true,
'value' => [1, 3]
]
);

302 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

• 'empty' - Applies to radio() and select(). Defaults to false.


– When passed to radio() and set to true it will create an extra input element as the first radio button,
with a value of '' and a label caption equal to the string 'empty'. If you want to control the label
caption set this option to a string instead.
– When passed to a select method, this creates a blank HTML option element with an empty value
in your drop down list. If you want to have an empty value with text displayed instead of just a blank
option, pass a string to 'empty':

echo $this->Form->select(
'field',
[1, 2, 3, 4, 5],
['empty' => '(choose one)']
);

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>

• 'hiddenField' - For checkboxes and radio buttons, by default, a hidden input element is also created,
along with the main element, so that the key in $this->request->getData() will exist even without a
value specified. For checkboxes its value defaults to 0 and for radio buttons to ''.
Example of default output:

<input type="hidden" name="published" value="0" />


<input type="checkbox" name="published" value="1" />

This can be disabled by setting 'hiddenField' to false:

echo $this->Form->checkbox('published', ['hiddenField' => false]);

Which outputs:

<input type="checkbox" name="published" value="1">

If you want to create multiple blocks of controls on a form, that are all grouped together, you should set this
parameter to false on all controls 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:

<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>

More About Views 303


CakePHP Cookbook Documentation, Release 3.8

<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 'hiddenField' on the second control group would prevent this behavior.
You can set a hidden field to a value other than 0, such as ‘N’:

echo $this->Form->checkbox('published', [
'value' => 'Y',
'hiddenField' => 'N',
]);

Using Collections to build options

It’s possible to use the Collection class to build your options array. This approach is ideal if you already have a
collection of entities and would like to build a select element from them.
You can use the combine method to build a basic options array.:

$options = $examples->combine('id', 'name');

It’s also possible to add extra attributes by expanding the array. The following will create a data attribute on the option
element, using the map collection method.:

$options = $examples->map(function ($value, $key) {


return [
'value' => $value->id,
'text' => $value->name,
'data-created' => $value->created
];
});

304 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

Creating Checkboxes

Cake\View\Helper\FormHelper::checkbox(string $fieldName, array $options)


• $fieldName - A field name in the form 'Modelname.fieldname'.
• $options - An optional array including any of the Common Options For Specific Controls, or of the Options
for Select, Checkbox and Radio Controls above, of the checkbox-specific options (see below), as well as any
valid HTML attributes.
Creates a checkbox form element. The widget template used is:

'checkbox' => '<input type="checkbox" name="{{name}}" value="{{value}}"{{attrs}}>'

Options for Checkboxes


• 'checked' - Boolean to indicate whether this checkbox will be checked. Defaults to false.
• 'disabled' - Create a disabled checkbox input.
This method also generates an associated hidden form input element to force the submission of data for the specified
field.
E.g.

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.
E.g.

echo $this->Form->checkbox('done', ['value' => 555]);

Will output:

<input type="hidden" name="done" value="0">


<input type="checkbox" name="done" value="555">

If you don’t want the FormHelper to create a hidden input use 'hiddenField'.
E.g.

echo $this->Form->checkbox('done', ['hiddenField' => false]);

Will output:

<input type="checkbox" name="done" value="1">

Creating Radio Buttons

Cake\View\Helper\FormHelper::radio(string $fieldName, array $options, array $attributes)


• $fieldName - A field name in the form 'Modelname.fieldname'.

More About Views 305


CakePHP Cookbook Documentation, Release 3.8

• $options - An optional array containing at minimum the labels for the radio buttons. Can also contain values
and HTML attributes. When this array is missing, the method will either generate only the hidden input (if
'hiddenField' is true) or no element at all (if 'hiddenField' is false).
• $attributes - An optional array including any of the Common Options For Specific Controls, or of the
Options for Select, Checkbox and Radio Controls, of the radio button specific attributes (see below), as well as
any valid HTML attributes.
Creates a set of radio button inputs. The default widget templates used are:

'radio' => '<input type="radio" name="{{name}}" value="{{value}}"{{attrs}}>'


'radioWrapper' => '{{label}}'

Attributes for Radio Buttons


• 'label' - Boolean to indicate whether or not labels for widgets should be displayed, or an array of attributes
to apply to all labels. In case a class attribute is defined, selected will be added to the class attribute of
checked buttons. Defaults to true.
• 'hiddenField' - If set to true a hidden input with a value of '' will be included. This is useful for
creating radio sets that are non-continuous. Defaults to true.
• 'disabled' - Set to true or 'disabled' to disable all the radio buttons. Defaults to false.
You must provide the label captions for the radio buttons via the $options argument.
For example:

$this->Form->radio('gender', ['Masculine', 'Feminine', 'Neuter']);

Will output:

<input name="gender" value="" type="hidden">


<label for="gender-0">
<input name="gender" value="0" id="gender-0" type="radio">
Masculine
</label>
<label for="gender-1">
<input name="gender" value="1" id="gender-1" type="radio">
Feminine
</label>
<label for="gender-2">
<input name="gender" value="2" id="gender-2" type="radio">
Neuter
</label>

Generally $options contains simple key => value pairs. However, if you need to put custom attributes on your
radio buttons you can use an expanded format.
E.g.

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;'],
]
);

306 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

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>

You can define additional attributes for an individual option’s label as well:

echo $this->Form->radio(
'favorite_color',
[
['value' => 'r', 'text' => 'Red', 'label' => ['class' => 'red']],
['value' => 'u', 'text' => 'Blue', 'label' => ['class' => 'blue']],
]
);

Will output:

<input type="hidden" name="favorite_color" value="">


<label for="favorite-color-r" class="red">
<input type="radio" name="favorite_color" value="r" style="color:red;" id=
˓→"favorite-color-r">

Red
</label>
<label for="favorite-color-u" class="blue">
<input type="radio" name="favorite_color" value="u" style="color:blue;" id=
˓→"favorite-color-u">

Blue
</label>

If the label key is used on an option, the attributes in $attributes['label'] will be ignored.
Changed in version 3.8.0: The label key in complex options was added.

Creating Select Pickers

Cake\View\Helper\FormHelper::select(string $fieldName, array $options, array $attributes)


• $fieldName - A field name in the form 'Modelname.fieldname'. This will provide the name attribute
of the select element.
• $options - An optional array containing the list of items for the select picker. When this array is missing, the
method will generate only the empty select HTML element without any option elements inside it.

More About Views 307


CakePHP Cookbook Documentation, Release 3.8

• $attributes - An optional array including any of the Common Options For Specific Controls, or of the
Options for Select, Checkbox and Radio Controls, or of the select-specific attributes (see below), as well as any
valid HTML attributes.
Creates a select element, populated with the items from the $options array. If $attributes['value']
is provided, then the HTML option element(s) which have the specified value(s) will be shown as selected when
rendering the select picker.
By default select uses the following widget templates:

'select' => '<select name="{{name}}"{{attrs}}>{{content}}</select>'


'option' => '<option value="{{value}}"{{attrs}}>{{text}}</option>'

May also use:

'optgroup' => '<optgroup label="{{label}}"{{attrs}}>{{content}}</optgroup>'


'selectMultiple' => '<select name="{{name}}[]" multiple="multiple"{{attrs}}>{{content}
˓→}</select>'

Attributes for Select Pickers


• 'multiple' - If set to true allows multiple selections in the select picker. If set to 'checkbox', multiple
checkboxes will be created instead. Defaults to null.
• 'escape' - Boolean. If true the contents of the option elements inside the select picker will be HTML
entity encoded. Defaults to true.
• 'val' - Allows preselecting a value in the select picker.
• 'disabled' - Controls the disabled attribute. If set to true disables the whole select picker. If set to an
array it will disable only those specific option elements whose values are provided in the array.
The $options argument allows you to manually specify the contents of the option elements of a select control.
E.g.

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>

The array for $options can also be supplied as key-value pairs.


E.g.

echo $this->Form->select('field', [
'Value 1' => 'Label 1',
'Value 2' => 'Label 2',
'Value 3' => 'Label 3'
]);

Output:

308 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

<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 (nested array). This
works on multiple checkboxes and radio buttons too, but instead of optgroup it wraps the elements in fieldset
elements.
For example:

$options = [
'Group 1' => [
'Value 1' => 'Label 1',
'Value 2' => 'Label 2'
],
'Group 2' => [
'Value 3' => 'Label 3'
]
];
echo $this->Form->select('field', $options);

Output:

<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>

To generate HTML attributes within an option tag:

$options = [
['text' => 'Description 1', 'value' => 'value 1', 'attr_name' => 'attr_value 1'],
['text' => 'Description 2', 'value' => 'value 2', 'attr_name' => 'attr_value 2'],
['text' => 'Description 3', 'value' => 'value 3', 'other_attr_name' => 'other_
˓→attr_value'],

];
echo $this->Form->select('field', $options);

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>

Controlling Select Pickers via Attributes


By using specific options in the $attributes parameter you can control certain behaviors of the select()
method.

More About Views 309


CakePHP Cookbook Documentation, Release 3.8

• 'empty' - Set the 'empty' key in the $attributes argument to true (the default value is false) to
add a blank option with an empty value at the top of your dropdown list.
For example:
$options = ['M' => 'Male', 'F' => 'Female'];
echo $this->Form->select('gender', $options, ['empty' => true]);

Will output:
<select name="gender">
<option value=""></option>
<option value="M">Male</option>
<option value="F">Female</option>
</select>

• 'escape' - The select() method allows for an attribute called 'escape' which accepts a boolean value
and determines whether to HTML entity encode the contents of the select’s option elements.
E.g.
// This will prevent HTML-encoding the contents of each option element
$options = ['M' => 'Male', 'F' => 'Female'];
echo $this->Form->select('gender', $options, ['escape' => false]);

• 'multiple' - If set to true, the select picker will allow multiple selections.
E.g.
echo $this->Form->select('field', $options, ['multiple' => true]);

Alternatively, set 'multiple' to 'checkbox' in order to output a list of related checkboxes:


$options = [
'Value 1' => 'Label 1',
'Value 2' => 'Label 2'
];
echo $this->Form->select('field', $options, [
'multiple' => 'checkbox'
]);

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">
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>

• 'disabled' - This option can be set in order to disable all or some of the select’s option items. To
disable all items set 'disabled' to true. To disable only certain items, assign to 'disabled' an array
containing the keys of the items to be disabled.

310 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

E.g.

$options = [
'M' => 'Masculine',
'F' => 'Feminine',
'N' => 'Neuter'
];
echo $this->Form->select('gender', $options, [
'disabled' => ['M', 'N']
]);

Will output:

<select name="gender">
<option value="M" disabled="disabled">Masculine</option>
<option value="F">Feminine</option>
<option value="N" disabled="disabled">Neuter</option>
</select>

This option also works when 'multiple' is set to 'checkbox':

$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>

Creating File Inputs

Cake\View\Helper\FormHelper::file(string $fieldName, array $options)


• $fieldName - A field name in the form 'Modelname.fieldname'.
• $options - An optional array including any of the Common Options For Specific Controls as well as any valid
HTML attributes.
Creates a file upload field in the form. The widget template used by default is:

More About Views 311


CakePHP Cookbook Documentation, Release 3.8

'file' => '<input type="file" name="{{name}}"{{attrs}}>'

To add a file upload field to a form, you must first make sure that the form enctype is set to 'multipart/
form-data'.
So start off with a create() method such as the following:

echo $this->Form->create($document, ['enctype' => 'multipart/form-data']);


// OR
echo $this->Form->create($document, ['type' => 'file']);

Next add a line that looks like either of the following two lines to your form’s view template file:

echo $this->Form->control('submittedfile', [
'type' => 'file'
]);

// OR
echo $this->Form->file('submittedfile');

Note: 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 CakePHP was installed
on a Windows server (the key 'tmp_name' will contain a different path in a Unix environment):

$this->request->data['submittedfile']

// would contain the following array:


[
'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 uploads130 .

Note: When using $this->Form->file(), remember to set the form encoding-type, by setting the 'type'
option to 'file' in $this->Form->create().

Creating Date & Time Related Controls

The date and time related methods share a number of common traits and options and hence are grouped together into
this subsection.
130 http://php.net/features.file-upload

312 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

Common Options for Date & Time Controls

These options are common for the date and time related controls:
• 'empty' - If true an extra, empty, option HTML element is added inside select at the top of the list. If
a string, that string is displayed as the empty element. Defaults to true.
• 'default' | value - Use either of the two to set the default value to be shown by the field. A value in
$this->request->getData() matching the field name will override this value. If no default is provided
time() will be used.
• 'year', 'month', 'day', 'hour', 'minute', 'second', 'meridian' - These options
allow you to control which control elements are generated or not. By setting any of these options to false
you can disable the generation of that specific that select picker (if by default it would be rendered in the used
method). In addition each option allows you to pass HTML attributes to that specific select element.

Options for Date-Related Controls

These options are concerning the date-related methods - i.e. year(), month(), day(), dateTime() and
date():
• 'monthNames' - If false, 2 digit numbers will be used instead of text for displaying months in the select
picker. If set to an array (e.g. ['01' => 'Jan', '02' => 'Feb', ...]), the given array will be
used.
• 'minYear' - The lowest value to use in the year select picker.
• 'maxYear' - The maximum value to use in the year select picker.
• 'orderYear' - The order of year values in the year select picker. Possible values are 'asc' and 'desc'.
Defaults to 'desc'.
• 'year', 'month', 'day' - These options allow you to control which control elements are generated or
not. By setting any of these options to false you can disable the generation of that specific that select picker
(if by default it would be rendered in the used method). In addition each option allows you to pass HTML
attributes to that specific select element.

Options for Time-Related Controls

These options are concerning the time-related methods - hour(), minute(), second(), dateTime() and
time():
• 'interval' - The interval in minutes between the values which are displayed in the option elements of the
minutes select picker. Defaults to 1.
• 'round' - Set to up or down if you want to force rounding minutes in either direction when the value doesn’t
fit neatly into an interval. Defaults to null.
• timeFormat - Applies to dateTime() and time(). The time format to use in the select picker; either
12 or 24. When this option is set to anything else than 24 the format will be automatically set to 12 and the
meridian select picker will be displayed automatically to the right of the seconds select picker. Defaults to
24.
• format - Applies to hour(). The time format to use; either 12 or 24. In case it’s set to 12 the meridian
select picker won’t be automatically displayed. It’s up to you to either add it or provide means to infer from the
form context the right period of the day. Defaults to 24.

More About Views 313


CakePHP Cookbook Documentation, Release 3.8

• second - Applies to dateTime() and time(). Set to true to enable the seconds drop down. Defaults to
false.
• 'hour', 'minute', 'second', 'meridian' - These options allow you to control which control
elements are generated or not. By setting any of these options to false you can disable the generation of that
specific that select picker (if by default it would be rendered in the used method). In addition each option allows
you to pass HTML attributes to that specific select element.

Creating DateTime Controls

Cake\View\Helper\FormHelper::dateTime($fieldName, $options = [])


• $fieldName - A string that will be used as a prefix for the HTML name attribute of the select elements.
• $options - An optional array including any of the Common Options For Specific Controls, or specific datetime
options (see above), as well as any valid HTML attributes.
Creates a set of select elements for date and time.
To control the order of controls, and any elements/content between the controls you can override the dateWidget
template. By default the dateWidget template is:
{{year}}{{month}}{{day}}{{hour}}{{minute}}{{second}}{{meridian}}

Calling the method without additional options will generate, by default, 5 select pickers, for: year (4 digits), month
(full English name), day (num), hour (num), minutes (num).
For example
<?= $this->form->dateTime('registered') ?>

Output:
<select name="registered[year]">
<option value="" selected="selected"></option>
<option value="2022">2022</option>
...
<option value="2012">2012</option>
</select>
<select name="registered[month]">
<option value="" selected="selected"></option>
<option value="01">January</option>
...
<option value="12">December</option>
</select>
<select name="registered[day]">
<option value="" selected="selected"></option>
<option value="01">1</option>
...
<option value="31">31</option>
</select>
<select name="registered[hour]">
<option value="" selected="selected"></option>
<option value="00">0</option>
...
<option value="23">23</option>
</select>
<select name="registered[minute]">
<option value="" selected="selected"></option>

314 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

<option value="00">00</option>
...
<option value="59">59</option>
</select>

To create datetime controls with custom classes/attributes on a specific select box, you can provide them as arrays of
options for each component, within the $options argument.
For example:
echo $this->Form->dateTime('released', [
'year' => [
'class' => 'year-classname',
],
'month' => [
'class' => 'month-class',
'data-type' => 'month',
],
]);

Which would create the following two select pickers:


<select name="released[year]" class="year-class">
<option value="" selected="selected"></option>
<option value="00">0</option>
<option value="01">1</option>
<!-- .. snipped for brevity .. -->
</select>
<select name="released[month]" class="month-class" data-type="month">
<option value="" selected="selected"></option>
<option value="01">January</option>
<!-- .. snipped for brevity .. -->
</select>

Creating Date Controls

Cake\View\Helper\FormHelper::date($fieldName, $options = [])


• $fieldName - A field name that will be used as a prefix for the HTML name attribute of the select
elements.
• $options - An optional array including any of the Common Options For Specific Controls, of the Common
Options for Date & Time Controls, any applicable Options for Time-Related Controls, as well as any valid
HTML attributes.
Creates, by default, three select pickers populated with values for: year (4 digits), month (full English name) and day
(numeric), respectively.
You can further control the generated select elements by providing additional options.
For example:
// Assuming current year is 2017; this disables day picker, removes empty
// option on year picker, limits lowest year, adds HTML attributes on year,
// adds a string 'empty' option on month, changes month to numeric
<?php
echo $this->Form->date('registered', [
'minYear' => 2018,

More About Views 315


CakePHP Cookbook Documentation, Release 3.8

'monthNames' => false, // Months are displayed as numbers


'empty' => [
'year' => false, // The year select control has no option for empty value
'month' => 'Choose month...', // The month select control does, though
],
'day' => false, // Do not show day select control
'year' => [
'class' => 'cool-years',
'title' => 'Registration Year'
]
]);
?>

Output:

<select class= "cool-years" name="registered[year]" title="Registration Year">


<option value="2022">2022</option>
<option value="2021">2021</option>
...
<option value="2018">2018</option>
</select>
<select name="registered[month]">
<option value="" selected="selected">Choose month...</option>
<option value="01">1</option>
...
<option value="12">12</option>
</select>

Creating Time Controls

Cake\View\Helper\FormHelper::time($fieldName, $options = [])


• $fieldName - A field name that will be used as a prefix for the HTML name attribute of the select
elements.
• $options - An optional array including any of the Common Options For Specific Controls, of the Common
Options for Date & Time Controls, any applicable Options for Time-Related Controls, as well as any valid
HTML attributes.
Creates, by default, two select elements (hour and minute) populated with values for 24 hours and 60 min-
utes, respectively. Additionally, HTML attributes may be supplied in $options for each specific component. If
$options['empty'] is false, the select picker will not include an empty default option.
For example, to create a time range with minutes selectable in 15 minute increments, and to apply classes to the select
boxes, you could do the following:

echo $this->Form->time('released', [
'interval' => 15,
'hour' => [
'class' => 'foo-class',
],
'minute' => [
'class' => 'bar-class',
],
]);

Which would create the following two select pickers:

316 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

<select name="released[hour]" class="foo-class">


<option value="" selected="selected"></option>
<option value="00">0</option>
<option value="01">1</option>
<!-- .. snipped for brevity .. -->
<option value="22">22</option>
<option value="23">23</option>
</select>
<select name="released[minute]" class="bar-class">
<option value="" selected="selected"></option>
<option value="00">00</option>
<option value="15">15</option>
<option value="30">30</option>
<option value="45">45</option>
</select>

Creating Year Controls

Cake\View\Helper\FormHelper::year(string $fieldName, array $options = [])


• $fieldName - A field name that will be used as a prefix for the HTML name attribute of the select element.
• $options - An optional array including any of the Common Options For Specific Controls, of the Common
Options for Date & Time Controls, any applicable Options for Date-Related Controls, as well as any valid
HTML attributes.
Creates a select element populated with the years from minYear to maxYear (when these options are provided)
or else with values starting from -5 years to +5 years counted from today. Additionally, HTML attributes may be
supplied in $options. If $options['empty'] is false, the select picker will not include an empty item in the
list.
For example, to create a year range from 2000 to the current year you would do the following:

echo $this->Form->year('purchased', [
'minYear' => 2000,
'maxYear' => date('Y')
]);

If it was 2009, you would get the following:

<select name="purchased[year]">
<option value=""></option>
<option value="2009">2009</option>
<option value="2008">2008</option>
<option value="2007">2007</option>
<option value="2006">2006</option>
<option value="2005">2005</option>
<option value="2004">2004</option>
<option value="2003">2003</option>
<option value="2002">2002</option>
<option value="2001">2001</option>
<option value="2000">2000</option>
</select>

More About Views 317


CakePHP Cookbook Documentation, Release 3.8

Creating Month Controls

Cake\View\Helper\FormHelper::month(string $fieldName, array $attributes)


• $fieldName - A field name that will be used as a prefix for the HTML name attribute of the select element.
• $attributes - An optional array including any of the Common Options For Specific Controls, of the Common
Options for Date & Time Controls, any applicable Options for Date-Related Controls, as well as any valid
HTML attributes.
Creates a select element populated with month names.
For example:

echo $this->Form->month('mob');

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>

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.
E.g.

echo $this->Form->month('mob', ['monthNames' => false]);

Note: The default months can be localized with CakePHP Internationalization & Localization features.

Creating Day Controls

Cake\View\Helper\FormHelper::day(string $fieldName, array $attributes)


• $fieldName - A field name that will be used as a prefix for the HTML name attribute of the select element.
• $attributes - An optional array including any of the Common Options For Specific Controls, of the Common
Options for Date & Time Controls, any applicable Options for Date-Related Controls, as well as any valid
HTML attributes.
Creates a select element populated with the (numerical) days of the month.
To create an empty option element with a prompt text of your choosing (e.g. the first option is ‘Day’), you can
supply the text in the 'empty' parameter.

318 Chapter 11. Views


CakePHP Cookbook Documentation, Release 3.8

For example:

echo $this->Form->day('created', ['empty' => 'Day']);

Will output:

<select name="created[day]">
<option value="" selected="selected">Day</option>
<option value="01">1</option>
<option value="02">2</option>
<option value="03">3</option>
...
<option value="31">31</option>
</select>

Creating Hour Controls

Cake\View\Helper\FormHelper::hour(string $fieldName, array $attributes)


• $fieldName - A field name that will be used as a prefix for the HTML name attribute of the select element.
• $attributes - An optional array including any of the Common Options For Specific Controls, of the Common
Options for Date & Time Controls, any applicable Options for Time-Related Controls, as well as any valid
HTML attributes.
Creates a select element populated with the hours of the day.
You can create either 12 or 24 hour pickers using the 'format' option:

echo $this->Form->hour('created', [
'format' => 12
]);
echo $this->Form->hour('created', [
'format' => 24
]);

Creating Minute Controls

Cake\View\Helper\FormHelper::minute(string $fieldName, array $attributes)


• $fieldName - A field name that will be used as a prefix for the HTML name attribute of the select element.
• $attributes - An optional array including any of the Common Options For Specific Controls, of the Common
Options for Date & Time Controls, any applicable Options for Time-Related Controls, as well as any valid
HTML attributes.
Creates a select element populated with values for the minutes of the hour. You can create a select picker that only
contains specific values by using the 'interval' option.
For example, if you wanted 10 minutes increments you would do the following:

// In your view template file


echo $this->Form->minute('arrival', [
'interval' => 10
]);

This would output:

More About Views 319


CakePHP Cookbook Documentation, Release 3.8

<select name="arrival[minute]">
<option value="" selected="selected"></option>
<option value="00">00</option>
<option value="10">10</option>
<option value="20">20</option>
<option value="30">30</option>
<option value="40">40</option>
<option value="50">50</option>
</select>

Creating Meridian Controls

Cake\View\Helper\Fo