Cake Php Book
Cake Php Book
Release 5.x
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 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Additional Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3 Migration Guides 31
5.0 Upgrade Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
5.0 Migration Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
PHPUnit 10 Upgrade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
5 Contributing 81
Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Tickets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Coding Standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
Backwards Compatibility Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
i
6 Installation 109
Installing CakePHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Permissions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
Development Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
Production . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
Fire It Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
URL Rewriting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
7 Configuration 121
Configuring your Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Environment Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
Additional Class Paths . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Inflection Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
Configure Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
Reading and writing configuration files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
8 Routing 133
Quick Tour . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
Connecting Routes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
Route Scoped Middleware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
RESTful Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
Passed Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Generating URLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
Generating Asset URLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Redirect Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
Entity Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
Custom Route Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
Creating Persistent URL Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
10 Controllers 185
The App Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
Request Flow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
Controller Actions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
Interacting with Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
Content Type Negotiation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
Content Type Negotiation Fallbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
Using AjaxView . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
Redirecting to Other Pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
Loading Additional Tables/Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
Paginating a Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
Configuring Components to Load . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
Request Life-cycle Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
Controller Middleware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
More on Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
11 Views 207
The App View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
View Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
ii
Extending Layouts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
Using View Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
Layouts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
View Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
Creating Your Own View Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
More About Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
13 Caching 505
Configuring Cache Engines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 506
Writing to a Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 509
Reading From a Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 511
Deleting From a Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 512
Clearing Cached Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513
Using Cache to Store Counters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513
Using Cache to Store Common Query Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 514
Using Groups . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 514
Globally Enable or Disable Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515
Creating a Cache Engine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515
16 Debugging 553
Basic Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 553
Using the Debugger Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 554
Outputting Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 554
Logging With Stack Traces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 555
Generating Stack Traces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 555
Getting an Excerpt From a File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 555
Editor Integration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 556
Using Logging to Debug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 556
Debug Kit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 557
17 Deployment 559
Moving files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 559
Adjusting Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 559
Check Your Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 560
Set Document Root . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 560
Improve Your Application’s Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 561
Deploying an update . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 561
18 Mailer 563
Basic Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 563
iii
Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 564
Setting Headers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 566
Sending Templated Emails . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 566
Sending Attachments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 567
Sending Emails from CLI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 568
Creating Reusable Emails . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 568
Configuring Transports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 570
Sending emails without using Mailer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 572
Testing Mailers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 573
22 Logging 613
Logging Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 613
Error and Exception Logging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 615
Writing to Logs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 615
Logging to Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 617
Logging to Syslog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 618
Creating Log Engines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 618
Log API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 620
Logging Trait . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 621
Using Monolog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 621
iv
Creating HTML with FormHelper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 627
24 Pagination 629
Basic Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 629
Advanced Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 630
Simple Pagination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 631
Paginating Multiple Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 631
Control which Fields Used for Ordering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 633
Limit the Maximum Number of Rows per Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 633
Out of Range Page Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 633
Using a paginator class directly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 634
Pagination in the View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 634
25 Plugins 635
Installing a Plugin With Composer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 635
Manually Installing a Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 636
Loading a Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 636
Plugin Hook Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 637
Plugin Loading Options . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 637
Loading plugins through Application::bootstrap() . . . . . . . . . . . . . . . . . . . . . . . . . . . 637
Using Plugin Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 638
Creating Your Own Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 639
Plugin Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 640
Plugin Routes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 641
Plugin Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 642
Plugin Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 643
Plugin Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 644
Plugin Assets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 645
Components, Helpers and Behaviors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 646
Commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 646
Testing your Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 647
Publishing your Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 647
Plugin Map File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 647
Manage Your Plugins using Mixer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 647
26 REST 649
Getting Started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 649
Encoding Response Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 651
Parsing Request Bodies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 651
27 Security 653
Security Utility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 653
CSRF Protection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 655
Content Security Policy Middleware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 658
Security Header Middleware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 659
HTTPS Enforcer Middleware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 660
28 Sessions 663
Session Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 663
Built-in Session Handlers & Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 665
Setting ini directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 667
Creating a Custom Session Handler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 667
Accessing the Session Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 669
Reading & Writing Session Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 669
Destroying the Session . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 670
v
Rotating Session Identifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 671
Flash Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 671
29 Testing 673
Installing PHPUnit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 673
Test Database Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 674
Checking the Test Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 674
Test Case Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 675
Creating Your First Test Case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 675
Running Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 677
Test Case Lifecycle Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 679
Fixtures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 679
Loading Routes in Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 685
Testing Table Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 687
Controller Integration Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 689
Console Integration Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 701
Mocking Injected Dependencies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 701
Mocking HTTP Client Responses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 701
Testing Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 701
Testing Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 701
Testing Helpers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 703
Testing Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 704
Testing Email . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 706
Creating Test Suites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 706
Creating Tests for Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 707
Generating Tests with Bake . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 708
30 Validation 709
Creating Validators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 709
Make Rules ‘last’ by default . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 715
Validating Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 718
Validating Entity Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 719
Core Validation Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 719
32 Collections 725
Quick Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 725
List of Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 726
Iterating . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 726
Filtering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 731
Aggregation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 732
Sorting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 736
Working with Tree Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 737
Other Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 739
33 Hash 747
Hash Path Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 747
vi
Doing Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 763
Creating Multipart Requests with Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 764
Sending Request Bodies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 765
Request Method Options . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 766
Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 766
Creating Scoped Clients . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 768
Setting and Managing Cookies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 769
Response Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 770
Changing Transport Adapters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 772
Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 772
35 Inflector 775
Summary of Inflector Methods and Their Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 775
Creating Plural & Singular Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 776
Creating CamelCase and under_scored Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 777
Creating Human Readable Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 777
Creating Table and Class Name Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 777
Creating Variable Names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 778
Inflection Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 778
36 Number 779
Formatting Currency Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 780
Setting the Default Currency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 780
Getting the Default Currency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 781
Formatting Floating Point Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 781
Formatting Percentages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 781
Interacting with Human Readable Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 782
Formatting Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 782
Format Differences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 784
Configure formatters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 784
38 Text 789
Convert Strings into ASCII . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 790
Creating URL Safe Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 790
Generating UUIDs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 791
Simple String Parsing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 791
Formatting Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 791
Wrapping Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 792
Highlighting Substrings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 793
Removing Links . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 793
Truncating Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 793
Truncating the Tail of a String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 794
Extracting an Excerpt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 795
Converting an Array to Sentence Form . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 796
vii
Comparing With the Present . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 804
Comparing With Intervals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 804
Date . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805
Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805
Accepting Localized Request Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805
Supported Timezones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805
40 Xml 807
Loading XML documents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 807
Loading HTML documents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 808
Transforming a XML String in Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 808
Transforming an Array into a String of XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 809
42 Chronos 815
44 Migrations 819
45 ElasticSearch 821
46 Appendices 823
5.x Migration Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 823
Backwards Compatibility Shimming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 823
Forwards Compatibility Shimming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 823
General Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 824
Index 829
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.
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 represents the part of your application that implements the business logic. It is responsible for re-
trieving 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\Locator\LocatorAwareTrait;
1
CakePHP Book, Release 5.x
You may notice that we didn’t have to write any code before we could start working with our data. By using conventions,
CakePHP will use standard classes for table and entity classes that have not yet been defined.
If we wanted to make a new user and save it (with validation) we would do something like:
use Cake\ORM\Locator\LocatorAwareTrait;
$users = $this->fetchTable('Users');
$user = $users->newEntity(['email' => '[email protected]']);
$users->save($user);
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:
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 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:
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().
Now that you are familiar with the different layers in CakePHP, lets review how a request cycle works in CakePHP:
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.
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
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.
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.
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/
Additional Reading 5
CakePHP Book, Release 5.x
Slack
Discord
CakePHP Discord5
You can also join us on Discord.
Stackoverflow
https://stackoverflow.com/7
Tag your questions with cakephp and the specific version you are using to enable existing users of stackoverflow to
find your questions.
Danish
French
German
Dutch
Japanese
Portuguese
Spanish
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, CamelCased, and end in Controller. UsersController and
MenuLinksController are both examples of conventional controller names.
Public methods on Controllers are often exposed as ‘actions’ accessible through a web browser. They are camelBacked.
For example the /users/view-me maps to the viewMe() method of the UsersController out of the box (if one
uses default dashed inflection in routing). Protected or private methods cannot be accessed with routing.
10 https://cakesf.slack.com/messages/german/
11 https://www.facebook.com/groups/146324018754907/
12 https://cakesf.slack.com/messages/netherlands/
13 https://cakesf.slack.com/messages/japanese/
14 https://www.facebook.com/groups/304490963004377/
15 https://cakesf.slack.com/messages/portuguese/
16 https://cakesf.slack.com/messages/spanish/
Additional Reading 7
CakePHP Book, Release 5.x
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 lower-
case and dashed using the DashedRoute class, therefore /menu-links/view-all is the correct form to access the
MenuLinksController::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' // CamelCased
'plugin' => 'MyPlugin', // CamelCased
'controller' => 'ControllerName', // CamelCased
'action' => 'actionName' // camelBacked
]
For more information on CakePHP URLs and parameter handling, see Connecting Routes.
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
• 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 EspeciallyFunkableBe-
havior.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, menu_links, and
user_favorite_pages respectively. Table name whose name contains multiple words should only pluralize the last
word, for example, menu_links.
Column names with two or more words are underscored, for example, 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 menu_links whose name contains multiple words, the foreign key would be
menu_link_id.
Join (or “junction”) tables are used in BelongsToMany relationships between models. These should be named for the
tables they connect. The names should be pluralized and sorted alphabetically: articles_tags, not tags_articles
or article_tags. The bake command will not work if this convention is not followed. If the junction table holds any
data other than the linking foreign keys, you should create a concrete entity/table class for the 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, CamelCased and end in Table. UsersTable, MenuLinksTable, and
UserFavoritePagesTable are all examples of table class names matching the users, menu_links and
user_favorite_pages tables respectively.
Entity class names are singular CamelCased and have no suffix. User, MenuLink, and UserFavoritePage are all
examples of entity names matching the users, menu_links and user_favorite_pages tables respectively.
Enum class names should use a {Entity}{Column} convention, and enum cases should use CamelCased names.
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 templates/Articles/view_all.php.
The basic pattern is templates/Controller/underscored_function_name.php.
Note: By default CakePHP uses English inflections. If you have database tables/columns that use another language,
you will need to add inflection rules (from singular to plural and vice-versa). You can use 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
Additional Reading 9
CakePHP Book, Release 5.x
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”, “menu_links”
• 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 templates/Articles/index.php
Using these conventions, CakePHP knows that a request to http://example.com/articles maps to a call on the
index() method of the ArticlesController, where the Articles model is automatically available. None of these
relationships have been configured by any means other than by creating classes and files that you’d need to create
anyway.
Additional Reading 11
CakePHP Book, Release 5.x
Foreign keys Relationships are recognized by default as the (singular) name of the related table followed
hasMany be- by _id. Users hasMany Articles, articles table will refer to the users table via a user_id
longsTo/ hasOne foreign key.
BelongsToMany
Multiple Words menu_links whose name contains multiple words, the foreign key would be
menu_link_id.
Auto Increment 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.
Join tables 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). Additional
columns on the junction table you should create a separate entity/table class for that table.
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.
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.
• The templates folder has presentational files placed here: elements, error pages, layouts, and view template files.
• The resources folder has sub folder for various types of resource files. The locales sub folder stores language
files for internationalization.
• 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 Composer18 . Edit-
ing 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.
18 https://getcomposer.org
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 Command Objects to learn more.
Console
Contains the installation script executed by Composer.
Controller
Contains your application’s Controllers and their components.
Middleware
Stores any /controllers/middleware for your application.
Model
Contains your application’s tables, entities and behaviors.
View
Presentational classes are placed here: views, cells, helpers.
Note: The folder Command is not present by default. You can add it when you need it.
Additional Reading 13
CakePHP Book, Release 5.x
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.
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’re using a supported PHP version:
php -v
You should at least have got installed PHP 8.1 (CLI) or higher. Your webserver’s PHP version must also be of 8.1 or
higher, and should be the same version your command line interface (CLI) PHP is.
15
CakePHP Book, Release 5.x
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, run the following:
If you downloaded and ran the Composer Windows Installer20 , then type the following line in your terminal from your
installation directory (ie. C:\wamp\www\dev):
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 like the following, though other files may also be present:
cms/
bin/
config/
plugins/
resources/
src/
templates/
tests/
tmp/
vendor/
webroot/
composer.json
index.php
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 GitHub21 .
Tip: The bin/cake console utility can build most of the classes and data tables in this tutorial automatically. However,
we recommend following along with the manual code examples to understand how the pieces fit together and how to
add your application logic.
19 https://getcomposer.org/download/
20 https://getcomposer.org/Composer-Setup.exe
21 https://github.com/cakephp/cms-tutorial
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.
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 the name of your choice such as cake_cms. If you are using
MySQL/MariaDB, you can execute the following SQL to create the necessary tables:
USE cake_cms;
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());
If you are using PostgreSQL, connect to the cake_cms database and execute the following SQL instead:
INSERT INTO articles (user_id, title, slug, body, published, created, modified)
VALUES
(1, 'First Post', 'first-post', 'This is the first post.', TRUE, NOW(), NOW());
You may have noticed that the articles_tags table uses 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 lever-
age 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_local.php file with those that apply to your setup. A sample completed configuration
array might look something like the following:
<?php
// config/app_local.php
return [
// More configuration above.
'Datasources' => [
'default' => [
'host' => 'localhost',
'username' => 'cakephp',
'password' => 'AngelF00dC4k3~',
'database' => 'cake_cms',
'url' => env('DATABASE_URL', null),
],
],
// More configuration below.
];
Once you’ve saved your config/app_local.php file, you should see that the ‘CakePHP is able to connect to the database’
section has a green chef hat.
Note: The file config/app_local.php is a local override of the file config/app.php used to configure your development
environment quickly.
Migrations
The SQL statements to create the tables for this tutorial can also be generated using the Migrations Plugin. Migrations
provide a platform-independent way to run queries so the subtle differences between MySQL, PostgreSQL, SQLite,
etc. don’t become obstacles.
Note: Some adjustments to the generated code might be necessary. For example, the composite primary key on
articles_tags will be set to auto-increment both columns:
$table->addColumn('article_id', 'integer', [
'autoIncrement' => true,
'default' => null,
'limit' => 11,
'null' => false,
]);
$table->addColumn('tag_id', 'integer', [
'autoIncrement' => true,
'default' => null,
'limit' => 11,
'null' => false,
]);
Remove those lines to prevent foreign key problems. Once adjustments are done:
Fill the seed data above into the new UsersSeed and ArticlesSeed classes, then:
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;
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;
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.
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 for
now, let’s just use the default layout.
CakePHP’s template files are stored in templates 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:
<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 -->
</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. 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.
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:
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 \Cake\Datasource\Exception\
RecordNotFoundException.
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.
Let’s create the view for our new ‘view’ action and place it in templates/Articles/view.php
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:
<?php
// src/Controller/ArticlesController.php
namespace App\Controller;
use App\Controller\AppController;
if ($this->Articles->save($article)) {
$this->Flash->success(__('Your article has been saved.'));
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, which is there already for this tutorial.
sages 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 API24 to see the formats in which you can specify a URL for various CakePHP functions.
<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:
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 templates/Articles/index.php view to include a new “Add Article” link. Before the
<table>, add the following line:
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:
<?php
// in src/Model/Table/ArticlesTable.php
namespace App\Model\Table;
(continues on next page)
24 https://api.cakephp.org
use Cake\ORM\Table;
// the Text class
use Cake\Utility\Text;
// the EventInterface class
use Cake\Event\EventInterface;
This code is simple, and doesn’t take into account duplicate slugs. But we’ll fix that later on.
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
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.'));
$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 RecordNotFoundException 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.
<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:
<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 -->
</td>
<td>
<?= $article->created->format(DATE_RFC850) ?>
</td>
<td>
<?= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?>
</td>
</tr>
<?php endforeach; ?>
</table>
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;
->notEmptyString('body')
->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
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.
Next, let’s make a way for users to delete articles. Start with a delete() action in the ArticlesController:
// src/Controller/ArticlesController.php
$article = $this->Articles->findBySlug($slug)->firstOrFail();
if ($this->Articles->delete($article)) {
$this->Flash->success(__('The {0} article has been deleted.', $article->title));
This logic deletes the article specified by $slug, and uses $this->Flash->success() to show the user a confir-
mation 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 accidentally
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:
<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 -->
</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 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
With a basic articles management setup, we’ll create the basic actions for our Tags and Users tables.
Migration Guides
Migration guides contain information regarding the new features introduced in each version and the migration path
between 4.x and 5.x.
First, check that your application is running on latest CakePHP 4.x version.
Once your application is running on latest CakePHP 4.x, enable deprecation warnings in config/app.php:
'Error' => [
'errorLevel' => E_ALL,
]
Now that you can see all the warnings, make sure these are fixed before proceeding with the upgrade.
Some potentially impactful deprecations you should make sure you have addressed are:
• Table::query() was deprecated in 4.5.0. Use selectQuery(), updateQuery(), insertQuery() and
deleteQuery() instead.
31
CakePHP Book, Release 5.x
If you are not running on PHP 8.1 or higher, you will need to upgrade PHP before updating CakePHP.
Note: The upgrade tool only works on applications running on latest CakePHP 4.x. You cannot run the upgrade tool
after updating to CakePHP 5.0.
Because CakePHP 5 leverages union types and mixed, there are many backwards incompatible changes concerning
method signatures and file renames. To help expedite fixing these tedious changes there is an upgrade CLI tool:
With the upgrade tool installed you can now run it on your application or plugin:
After applying rector refactorings you need to upgrade CakePHP, its plugins, PHPUnit and maybe other dependencies
in your composer.json. This process heavily depends on your application so we recommend you compare your
composer.json with what is present in cakephp/app25 .
After the version strings are adjusted in your composer.json execute composer update -W and check its output.
Next, ensure the rest of your application has been updated to be based upon the latest version of cakephp/app26 .
25 https://github.com/cakephp/app/blob/5.x/composer.json
26 https://github.com/cakephp/app/blob/5.x/
CakePHP 5.0 contains breaking changes, and is not backwards compatible with 4.x releases. Before attempting to
upgrade to 5.0, first upgrade to 4.5 and resolve all deprecation warnings.
Refer to the 5.0 Upgrade Guide for step by step instructions on how to upgrade to 5.0.
All methods, properties and functionality that were emitting deprecation warnings as of 4.5 have been removed.
Breaking Changes
In addition to the removal of deprecated features there have been breaking changes made:
Global
• Type declarations were added to all function parameter and returns where possible. These are intended to match
the docblock annotations, but include fixes for incorrect annotations.
• Type declarations were added to all class properties where possible. These also include some fixes for incorrect
annotations.
• The SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR constants were removed.
• Use of #[\AllowDynamicProperties] removed everywhere. It was used for the following classes:
– Command/Command
– Console/Shell
– Controller/Component
– Controller/Controller
– Mailer/Mailer
– View/Cell
– View/Helper
– View/View
• The supported database engine versions were updated:
– MySQL (5.7 or higher)
– MariaDB (10.1 or higher)
– PostgreSQL (9.6 or higher)
– Microsoft SQL Server (2012 or higher)
– SQLite 3 (3.16 or higher)
Auth
• Auth has been removed. Use the cakephp/authentication27 and cakephp/authorization28 plugins instead.
Cache
• The Wincache engine was removed. The wincache extension is not supported on PHP 8.
Collection
• combine() now throws an exception if the key path or group path doesn’t exist or contains a null value. This
matches the behavior of indexBy() and groupBy().
Console
Connection
• Connection::prepare() has been removed. You can use Connection::execute() instead to execute a
SQL query by specifing the SQL string, params and types in a single call.
• Connection::enableQueryLogging() has been removed. If you haven’t enabled logging through the
connection config then you can later set the logger instance for the driver to enable query logging
$connection->getDriver()->setLogger().
Controller
• The method signature for Controller::__construct() has changed. So you need to adjust your code ac-
cordingly if you are overriding the constructor.
• After loading components are no longer set as dynamic properties. Instead Controller uses __get() to pro-
vide property access to components. This change can impact applications that use property_exists() on
components.
• The components’ Controller.shutdown event callback has been renamed from shutdown to afterFilter
to match the controller one. This makes the callbacks more consistent.
• PaginatorComponent has been removed and should be replaced by calling $this->paginate() in your con-
troller or using Cake\Datasource\Paging\NumericPaginator directly
27 https://book.cakephp.org/authentication/2/en/index.html
28 https://book.cakephp.org/authorization/2/en/index.html
29 https://book.cakephp.org/5/en/console-commands/commands.html
• RequestHandlerComponent has been removed. See the 4.4 migration30 guide for how to upgrade
• SecurityComponent has been removed. Use FormProtectionComponent for form tampering protection or
HttpsEnforcerMiddleware to enforce use of HTTPS for requests instead.
• Controller::paginate() no longer accepts query options like contain for its $settings argu-
ment. You should instead use the finder option $this->paginate($this->Articles, ['finder' =>
'published']). Or you can create required select query before hand and then pass it to paginate() $query
= $this->Articles->find()->where(['is_published' => true]); $this->paginate($query);.
Core
• The function getTypeName() has been dropped. Use PHP’s get_debug_type() instead.
• The dependency on league/container was updated to 4.x. This will require the addition of typehints to your
ServiceProvider implementations.
• deprecationWarning() now has a $version parameter.
• The App.uploadedFilesAsObjects configuration option has been removed alongside of support for PHP file
upload shaped arrays throughout the framework.
• ClassLoader has been removed. Use composer to generate autoload files instead.
Database
• The DateTimeType and DateType now always return immutable objects. Additionally the interface for Date
objects reflects the ChronosDate interface which lacks all of the time related methods that were present in
CakePHP 4.x.
• DateType::setLocaleFormat() no longer accepts an array.
• Query now accepts only \Closure parameters instead of callable. Callables can be converted to closures
using the new first-class array syntax in PHP 8.1.
• Query::execute() no longer runs results decorator callbacks. You must use Query::all() instead.
• TableSchemaAwareInterface was removed.
• Driver::quote() was removed. Use prepared statements instead.
• Query::orderBy() was added to replace Query::order().
• Query::groupBy() was added to replace Query::group().
• SqlDialectTrait has been removed and all its functionality has been moved into the Driver class itself.
• CaseExpression has been removed and should be replaced with QueryExpression::case() or
CaseStatementExpression
• Connection::connect() has been removed. Use $connection->getDriver()->connect() instead.
• Connection::disconnect() has been removed. Use $connection->getDriver()->disconnect() in-
stead.
• cake.database.queries has been added as an alternative to the queriesLog scope
• The ability to enable/disable ResultSet buffering has been removed. Results are always buffered.
30 https://book.cakephp.org/4/en/appendices/4-4-migration-guide.html#requesthandlercomponent
Datasource
• The getAccessible() method was added to EntityInterface. Non-ORM implementations need to imple-
ment this method now.
• The aliasField() method was added to RepositoryInterface. Non-ORM implementations need to imple-
ment this method now.
Event
• Event payloads must be an array. Other object such as ArrayAccess are no longer cast to array and will raise a
TypeError now.
• It is recommended to adjust event handlers to be void methods and use $event->setResult() instead of
returning the result
Error
• ErrorHandler and ConsoleErrorHandler have been removed. See the 4.4 migration31 guide for how to
upgrade
• ExceptionRenderer has been removed and should be replaced with WebExceptionRenderer
• ErrorLoggerInterface::log() has been removed and should be replaced with
ErrorLoggerInterface::logException()
• ErrorLoggerInterface::logMessage() has been removed and should be replaced with
ErrorLoggerInterface::logError()
Filesystem
• The Filesystem package was removed, and Filesystem class was moved to the Utility package.
Http
• ServerRequest is no longer compatible with files as arrays. This behavior has been disabled by default since
4.1.0. The files data will now always contain UploadedFileInterfaces objects.
I18n
• Translation files for plugins with vendor prefixed names (FooBar/Awesome) will now have that prefix in the
file name, e.g. foo_bar_awesome.po to avoid collision with a awesome.po file from a corresponding plugin
(Awesome).
Log
• Log engine config now uses null instead of false to disable scopes. So instead of 'scopes' => false you
need to use 'scopes' => null in your log config.
Mailer
ORM
• EntityTrait::has() now returns true when an attribute exists and is set to null. In previous versions of
CakePHP this would return false. See the release notes for 4.5.0 for how to adopt this behavior in 4.x.
• EntityTrait::extractOriginal() now returns only existing fields, similar to
extractOriginalChanged().
• Finder arguments are now required to be associative arrays as they were always expected to be.
• TranslateBehavior now defaults to the ShadowTable strategy. If you are using the Eav strategy you will
need to update your behavior configuration to retain the previous behavior.
• allowMultipleNulls option for isUnique rule now default to true matching the original 3.x behavior.
• Table::query() has been removed in favor of query-type specific functions.
• Table::updateQuery(), Table::selectQuery(), Table::insertQuery(), and
Table::deleteQuery()) were added and return the new type-specific query objects below.
• SelectQuery, InsertQuery, UpdateQuery and DeleteQuery were added which represent only a single type
of query and do not allow switching between query types nor calling functions unrelated to the specific query
type.
• Table::_initializeSchema() has been removed and should be replaced by calling $this->getSchema()
inside the initialize() method.
• SaveOptionsBuilder has been removed. Use a normal array for options instead.
Routing
• Static methods connect(), prefix(), scope() and plugin() of the Router have been removed and should
be replaced by calling their non-static method variants via the RouteBuilder instance.
• RedirectException has been removed. Use \Cake\Http\Exception\RedirectException instead.
33 https://book.cakephp.org/5/en/core-libraries/email.html
TestSuite
• TestSuite was removed. Users should use environment variables to customize unit test settings instead.
• TestListenerTrait was removed. PHPUnit dropped support for these listeners. See PHPUnit 10 Upgrade
• IntegrationTestTrait::configRequest() now merges config when called multiple times instead of re-
placing the currently present config.
Validation
• Validation::isEmpty() is no longer compatible with file upload shaped arrays. Support for PHP file upload
arrays has been removed from ServerRequest as well so you should not see this as a problem outside of tests.
• Previously, most data validation error messages were simply The provided value is invalid. Now, the
data validation error messages are worded more precisely. For example, The provided value must be
greater than or equal to \`5\`.
View
Deprecations
The following is a list of deprecated methods, properties and behaviors. These features will continue to function in 5.x
and will be removed in 6.0.
Database
• Query::order() was deprecated. Use Query::orderBy() instead now that Connection methods are no
longer proxied. This aligns the function name with the SQL statement.
• Query::group() was deprecated. Use Query::groupBy() instead now that Connection methods are no
longer proxied. This aligns the function name with the SQL statement.
ORM
• Calling Table::find() with options array is deprecated. Use named arguments34 instead. For
e.g. instead of find('all', ['conditions' => $array]) use find('all', conditions: $array).
Similarly for custom finder options, instead of find('list', ['valueField' => 'name']) use
find('list', valueField: 'name') or multiple named arguments like find(type: 'list',
valueField: 'name', conditions: $array).
New Features
CakePHP 5 leverages the expanded type system feature available in PHP 8.1+. CakePHP also uses assert() to provide
improved error messages and additional type soundness. In production mode, you can configure PHP to not generate
code for assert() yielding improved application performance. See the Improve Your Application’s Performance for
how to do this.
Collection
• Added unique() which filters out duplicate value specified by provided callback.
• reject() now supports a default callback which filters out truthy values which is the inverse of the default
behavior of filter()
Core
Database
• ConnectionManager now supports read and write connection roles. Roles can be configured with read and
write keys in the connection config that override the shared config.
• Query::all() was added which runs result decorator callbacks and returns a result set for select queries.
• Query::comment() was added to add a SQL comment to the executed query. This makes it easier to debug
queries.
• EnumType was added to allow mapping between PHP backed enums and a string or integer column.
• getMaxAliasLength() and getConnectionRetries() were added to DriverInterface.
• Supported drivers now automatically add auto-increment only to integer primary keys named “id” instead of all
integer primary keys. Setting ‘autoIncrement’ to false always disables on all supported drivers.
34 https://www.php.net/manual/en/functions.arguments.php#functions.named-arguments
Http
• Added support for PSR-1735 factories interface. This allows cakephp/http to provide a client implementation
to libraries that allow automatic interface resolution like php-http.
• Added CookieCollection::__get() and CookieCollection::__isset() to add ergonomic ways to ac-
cess cookies without exceptions.
ORM
Entities have a new opt-in functionality that allows making entities handle properties more strictly. The new behavior
is called ‘required fields’. When enabled, accessing properties that are not defined in the entity will raise exceptions.
This impacts the following usage:
$entity->get();
$entity->has();
$entity->getOriginal();
isset($entity->attribute);
$entity->attribute;
Fields are considered defined if they pass array_key_exists. This includes null values. Because this can be a tedious
to enable feature, it was deferred to 5.0. We’d like any feedback you have on this feature as we’re considering making
this the default behavior in the future.
Table finders can now have typed arguments as required instead of an options array. For e.g. a finder for fetching posts
by category or user:
return $query;
}
{
if ($categoryId) {
$query->where(['category_id' => $categoryId]);
}
(continues on next page)
35 https://www.php-fig.org/psr/psr-17/
return $query;
}
The finder can then be called as find('byCategoryOrUser', userId: $somevar). You can even include
the special named arguments for setting query clauses. find('byCategoryOrUser', userId: $somevar,
conditions: ['enabled' => true]).
A similar change has been applied to the RepositoryInterface::get() method:
TestSuite
• IntegrationTestTrait::requestAsJson() has been added to set JSON headers for the next request.
Plugin Installer
• The plugin installer has been updated to automatically handle class autoloading for your app plugins. So you can
remove the namespace to path mappings for your plugins from your composer.json and just run composer
dumpautoload.
PHPUnit 10 Upgrade
With CakePHP 5 the minimum PHPUnit version has changed from ^8.5 || ^9.3 to ^10.1. This introduces a few
breaking changes from PHPUnit as well as from CakePHP’s side.
PHPUnit 10 Upgrade 41
CakePHP Book, Release 5.x
phpunit.xml adjustments
It is recommended to let PHPUnit update its configuration file via the following command:
vendor/bin/phpunit --migrate-configuration
Note: Make sure you are already on PHPUnit 10 via vendor/bin/phpunit --version before executing this com-
mand!
With this command out of the way your phpunit.xml already has most of the recommended changes present.
PHPUnit 10 removed the old hook system and introduced a new Event system36 which requires the following code in
your phpunit.xml to be adjusted from:
<extensions>
<extension class="Cake\TestSuite\Fixture\PHPUnitExtension"/>
</extensions>
to:
<extensions>
<bootstrap class="Cake\TestSuite\Fixture\Extension\PHPUnitExtension"/>
</extensions>
You can convert the removed ->withConsecutive() method to a working interim solution like you can see here:
->withConsecutive(['firstCallArg'], ['secondCallArg'])
->with(
...self::withConsecutive(['firstCallArg'], ['secondCallArg'])
)
the static self::withConsecutive() method has been added via the Cake\TestSuite\
PHPUnitConsecutiveTrait to the base Cake\TestSuite\TestCase class so you don’t have to manually
add that trait to your Testcase classes.
36 https://docs.phpunit.de/en/10.5/extending-phpunit.html#extending-the-test-runner
If your testcases leverage the data provider feature of PHPUnit then you have to adjust your data providers to be static:
PHPUnit 10 Upgrade 43
CakePHP Book, Release 5.x
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 CakePackages37 and the Bakery38 for existing
applications and components.
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’re using a supported PHP version:
php -v
You should at least have got installed PHP 8.1 (CLI) or higher. Your webserver’s PHP version must also be of 8.1 or
higher, and should be the same version your command line interface (CLI) PHP is.
37 https://plugins.cakephp.org/
38 https://bakery.cakephp.org/
45
CakePHP Book, Release 5.x
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, run the following:
If you downloaded and ran the Composer Windows Installer40 , then type the following line in your terminal from your
installation directory (ie. C:\wamp\www\dev):
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 like the following, though other files may also be present:
cms/
bin/
config/
plugins/
resources/
src/
templates/
tests/
tmp/
vendor/
webroot/
composer.json
index.php
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 GitHub41 .
Tip: The bin/cake console utility can build most of the classes and data tables in this tutorial automatically. However,
we recommend following along with the manual code examples to understand how the pieces fit together and how to
add your application logic.
39 https://getcomposer.org/download/
40 https://getcomposer.org/Composer-Setup.exe
41 https://github.com/cakephp/cms-tutorial
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.
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 the name of your choice such as cake_cms. If you are using
MySQL/MariaDB, you can execute the following SQL to create the necessary tables:
USE cake_cms;
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());
If you are using PostgreSQL, connect to the cake_cms database and execute the following SQL instead:
INSERT INTO articles (user_id, title, slug, body, published, created, modified)
VALUES
(1, 'First Post', 'first-post', 'This is the first post.', TRUE, NOW(), NOW());
You may have noticed that the articles_tags table uses 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 lever-
age 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_local.php file with those that apply to your setup. A sample completed configuration
array might look something like the following:
<?php
// config/app_local.php
return [
// More configuration above.
'Datasources' => [
'default' => [
'host' => 'localhost',
'username' => 'cakephp',
'password' => 'AngelF00dC4k3~',
'database' => 'cake_cms',
'url' => env('DATABASE_URL', null),
],
],
// More configuration below.
];
Once you’ve saved your config/app_local.php file, you should see that the ‘CakePHP is able to connect to the database’
section has a green chef hat.
Note: The file config/app_local.php is a local override of the file config/app.php used to configure your development
environment quickly.
Migrations
The SQL statements to create the tables for this tutorial can also be generated using the Migrations Plugin. Migrations
provide a platform-independent way to run queries so the subtle differences between MySQL, PostgreSQL, SQLite,
etc. don’t become obstacles.
Note: Some adjustments to the generated code might be necessary. For example, the composite primary key on
articles_tags will be set to auto-increment both columns:
$table->addColumn('article_id', 'integer', [
'autoIncrement' => true,
'default' => null,
'limit' => 11,
'null' => false,
]);
$table->addColumn('tag_id', 'integer', [
'autoIncrement' => true,
'default' => null,
'limit' => 11,
'null' => false,
]);
Remove those lines to prevent foreign key problems. Once adjustments are done:
Fill the seed data above into the new UsersSeed and ArticlesSeed classes, then:
Models are the heart of 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 provide the foundation necessary to
create 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
declare(strict_types=1);
namespace App\Model\Table;
use Cake\ORM\Table;
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
declare(strict_types=1);
namespace App\Model\Entity;
use Cake\ORM\Entity;
Right now, our entity is quite slim; we’ve only set up the _accessible property, which controls how properties can
be modified by Mass Assignment.
Tip: The ArticlesTable and Article Entity classes can be generated from a terminal:
We can’t do much with this model yet. Next, we’ll create our first Controller and Template to allow us to interact with
our model.
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;
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;
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.
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 for
now, let’s just use the default layout.
CakePHP’s template files are stored in templates 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:
<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 -->
</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. 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.
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:
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 \Cake\Datasource\Exception\
RecordNotFoundException.
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.
Let’s create the view for our new ‘view’ action and place it in templates/Articles/view.php
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:
<?php
// src/Controller/ArticlesController.php
namespace App\Controller;
use App\Controller\AppController;
if ($this->Articles->save($article)) {
$this->Flash->success(__('Your article has been saved.'));
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, which is there already for this tutorial.
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 session. The
success method is provided using PHP’s magic method features43 . Flash messages will be displayed on the
next page after redirecting. In our layout we have <?= $this->Flash->render() ?> which displays flash mes-
sages 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 API44 to see the formats in which you can specify a URL for various CakePHP functions.
<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:
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 templates/Articles/index.php view to include a new “Add Article” link. Before the
<table>, add the following line:
43 https://php.net/manual/en/language.oop5.overloading.php#object.call
44 https://api.cakephp.org
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:
<?php
// in src/Model/Table/ArticlesTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
// the Text class
use Cake\Utility\Text;
// the EventInterface class
use Cake\Event\EventInterface;
This code is simple, and doesn’t take into account duplicate slugs. But we’ll fix that later on.
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
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.'));
$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 RecordNotFoundException 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.
<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:
<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 -->
</table>
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;
->notEmptyString('body')
->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
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.
Next, let’s make a way for users to delete articles. Start with a delete() action in the ArticlesController:
// src/Controller/ArticlesController.php
$article = $this->Articles->findBySlug($slug)->firstOrFail();
if ($this->Articles->delete($article)) {
$this->Flash->success(__('The {0} article has been deleted.', $article->title));
This logic deletes the article specified by $slug, and uses $this->Flash->success() to show the user a confir-
mation 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 accidentally
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:
<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 -->
</td>
(continues on next page)
</table>
Using 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.
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
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:
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:
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.
Now that our application has tags, we need to enable users to tag their articles. First, update the add action to look like:
<?php
// in src/Controller/ArticlesController.php
namespace App\Controller;
use App\Controller\AppController;
if ($this->Articles->save($article)) {
$this->Flash->success(__('Your article has been saved.'));
$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 templates/Articles/add.php:
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:
$this->set('article', $article);
}
Remember to add the new tags multiple select control we added to the add.php template to the tem-
plates/Articles/edit.php template as well.
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 (with the baked comments removed) should look like:
<?php
use Cake\Routing\Route\DashedRoute;
use Cake\Routing\RouteBuilder;
$routes->setRouteClass(DashedRoute::class);
// Add this
// New route we're adding for our tagged action.
// The trailing `*` tells CakePHP that this action has
// passed parameters.
$builder->scope('/articles', function (RouteBuilder $builder) {
$builder->connect('/tagged/*', ['controller' => 'Articles', 'action' => 'tags']);
});
$builder->fallbacks();
});
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:
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:
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\SelectQuery;
$query = $query
->select($columns)
->distinct($columns);
if (empty($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' => $tags]);
}
return $query->groupBy(['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.
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:
<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.php 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’.
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.
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;
This will let us access the $article->tag_string computed property. We’ll use this property in controls later on.
With the entity updated we can add a new control for our tags. In templates/Articles/add.php and tem-
plates/Articles/edit.php, replace the existing tags._ids control with the following:
We’ll also need to update the article view template. In templates/Articles/view.php add the line as shown:
You should also update the view method to allow retrieving existing tags:
// src/Controller/ArticlesController.php file
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:
// Other code
}
$out = [];
$tags = $this->Tags->find()
->where(['Tags.title IN' => $newTags])
->all();
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.
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:
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:
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.
Now that our CMS has users, we can enable them to login using the cakephp/authentication45 plugin. We’ll start off by
ensuring passwords are stored securely in our database. Then we are going to provide a working login and logout, and
enable new users to register.
You need to have created the Controller, Table, Entity and templates for the users table in your database. You
can do this manually like you did before for the ArticlesController, or you can use the bake shell to generate the classes
for you using:
If you create or update a user with this setup, 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 use different classes to operate on
collections of records and single records. 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 a
convention based setter method 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;
Now, point your browser to http://localhost:8765/users to see a list of users. Remember you’ll need to have your local
server running. Start a standalone PHP server using bin/cake server.
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 bcrypt46 by
default. We recommend bcrypt for all new applications to keep your security standards high. This is the recommended
password hash algorithm for PHP47 .
Note: Create a hashed password for at least one of the user accounts now! It will be needed in the next steps. After
updating the password, you’ll see a long string stored in the password column. Note bcrypt will generate a different
hash even for the same password saved twice.
Adding Login
Now it’s time to configure the Authentication Plugin. The Plugin will handle the authentication process using 3 different
classes:
• Application will use the Authentication Middleware and provide an AuthenticationService, holding all the
configuration we want to define how are we going to check the credentials, and where to find them.
• AuthenticationService will be a utility class to allow you configure the authentication process.
• AuthenticationMiddleware will be executed as part of the middleware queue, this is before your Controllers
are processed by the framework, and will pick the credentials and process them to check if the user is authenti-
cated.
If you remember, we used AuthComponent before to handle all these steps. Now the logic is divided into specific
classes and the authentication process happens before your controller layer. First it checks if the user is authenticated
(based on the configuration you provided) and injects the user and the authentication results into the request for further
reference.
In src/Application.php, add the following imports:
46 https://codahale.com/how-to-safely-store-a-password/
47 https://www.php.net/manual/en/function.password-hash.php
// in src/Application.php
class Application extends BaseApplication
implements AuthenticationServiceProviderInterface
{
// src/Application.php
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$middlewareQueue
// ... other middleware added before
->add(new RoutingMiddleware($this))
->add(new BodyParserMiddleware())
// Add the AuthenticationMiddleware. It should be after routing and body parser.
->add(new AuthenticationMiddleware($this));
return $middlewareQueue;
}
{
$authenticationService = new AuthenticationService([
'unauthenticatedRedirect' => Router::url('/users/login'),
'queryParam' => 'redirect',
]);
return $authenticationService;
}
// src/Controller/AppController.php
public function initialize(): void
{
parent::initialize();
$this->loadComponent('Flash');
// Add this line to check authentication result and lock your site
$this->loadComponent('Authentication.Authentication');
Now, on every request, the AuthenticationMiddleware will inspect the request session to look for an authenticated
user. If we are loading the /users/login page, it will also inspect the posted form data (if any) to extract the cre-
dentials. By default the credentials will be extracted from the username and password fields in the request data. The
authentication result will be injected in a request attribute named authentication. You can inspect the result at any
time using $this->request->getAttribute('authentication') from your controller actions. All your pages
will be restricted as the AuthenticationComponent is checking the result on every request. When it fails to find any
authenticated user, it will redirect the user to the /users/login page. Note at this point, the site won’t work as we
don’t have a login page yet. If you visit your site, you’ll get an “infinite redirect loop” so let’s fix that.
Note: If your application serves from both SSL and non-SSL protocols, then you might have problems with sessions
being lost, in case your application is on non-SSL protocol. You need to enable access by setting session.cookie_secure
to false in your config config/app.php or config/app_local.php. (See CakePHP’s defaults on session.cookie_secure)
return $this->redirect($redirect);
}
// display error if user submitted and authentication failed
if ($this->request->is('post') && !$result->isValid()) {
$this->Flash->error(__('Invalid username or password'));
}
}
Now login page will allow us to correctly login into the application. Test it by requesting any page of your site. After
being redirected to the /users/login page, enter the email and password you picked previously when creating your
user. You should be redirected successfully after login.
We need to add a couple more details to configure our application. We want all view and index pages accessible
without logging in so we’ll add this specific configuration in AppController:
// in src/Controller/AppController.php
public function beforeFilter(\Cake\Event\EventInterface $event)
{
parent::beforeFilter($event);
// for all controllers in our application, make index and view
// actions public, skipping the authentication check
$this->Authentication->addUnauthenticatedActions(['index', 'view']);
}
Note: If you don’t have a user with a hashed password yet, comment the
$this->loadComponent('Authentication.Authentication') line in your AppController and all other
lines where Authentication is used. Then go to /users/add to create a new user picking email and password.
Afterward, make sure to uncomment the lines we just temporarily commented!
Try it out by visiting /articles/add before logging in! 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.
Logout
// in src/Controller/UsersController.php
public function logout()
{
$result = $this->Authentication->getResult();
// regardless of POST or GET, redirect if user is logged in
if ($result && $result->isValid()) {
$this->Authentication->logout();
(continues on next page)
Now you can visit /users/logout to log out. You should then be sent to the login page.
Enabling Registrations
If you try to visit /users/add without being logged in, 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 fix the following line:
The above tells AuthenticationComponent that the add() action of the UsersController does not require au-
thentication or authorization. You may want to take the time to clean up the Users/add.php 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.
Now that users can log in, we’ll want to limit users to only edit articles that they created by applying authorization
policies.
With users now able to login to our CMS, we want to apply authorization rules to ensure that each user only edits the
posts they own. We’ll use the authorization plugin48 to do this.
Load the plugin by adding the following statement to the bootstrap() method in src/Application.php:
$this->addPlugin('Authorization');
The Authorization plugin integrates into your application as a middleware layer and optionally a component to make
checking authorization easier. First, lets apply the middleware. In src/Application.php add the following to the class
imports:
use Authorization\AuthorizationService;
use Authorization\AuthorizationServiceInterface;
use Authorization\AuthorizationServiceProviderInterface;
(continues on next page)
48 https://book.cakephp.org/authorization/2
The AuthorizationMiddleware will call a hook method on your application when it starts handling the request.
This hook method allows your application to define the AuthorizationService it wants to use. Add the following
method your src/Application.php:
{
$resolver = new OrmResolver();
The OrmResolver lets the authorization plugin find policy classes for ORM entities and queries. Other resolvers can
be used to find policies for other resources types.
Next, lets add the AuthorizationComponent to AppController. In src/Controller/AppController.php add the
following to the initialize() method:
$this->loadComponent('Authorization.Authorization');
Lastly we’ll mark the add, login, and logout actions as not requiring authorization by adding the following to
src/Controller/UsersController.php:
The skipAuthorization() method should be called in any controller action that should be accessible to all users
even those who have not logged in yet.
The Authorization plugin models authorization and permissions as Policy classes. These classes implement the logic
to check whether or not a identity is allowed to perform an action on a given resource. Our identity is going to be
our logged in user, and our resources are our ORM entities and queries. Lets use bake to generate a basic policy:
This will generate an empty policy class for our Article entity. You can find the generated policy in
src/Policy/ArticlePolicy.php. Next update the policy to look like the following:
<?php
namespace App\Policy;
use App\Model\Entity\Article;
use Authorization\IdentityInterface;
class ArticlePolicy
{
public function canAdd(IdentityInterface $user, Article $article)
{
// All logged in users can create articles.
return true;
}
While we’ve defined some very simple rules, you can use as complex logic as your application requires in your policies.
With our policy created we can start checking authorization in each controller action. If we forget to check or skip
authorization in an controller action the Authorization plugin will raise an exception letting us know we forgot to apply
authorization. In src/Controller/ArticlesController.php add the following to the add, edit and delete methods:
$article = $this->Articles->findBySlug($slug)->firstOrFail();
$this->Authorization->authorize($article);
// Rest of the method.
}
The AuthorizationComponent::authorize() method will use the current controller action name to generate the
policy method to call. If you’d like to call a different policy method you can call authorize with the operation name:
$this->Authorization->authorize($article, 'update');
Lastly add the following to the tags, view, and index methods on the ArticlesController:
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:
// in src/Controller/ArticlesController.php
if ($this->request->is('post')) {
$article = $this->Articles->patchEntity($article, $this->request->getData());
if ($this->Articles->save($article)) {
$this->Flash->success(__('Your article has been saved.'));
Next we’ll update the edit action. Replace the edit method with the following:
// in src/Controller/ArticlesController.php
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.'));
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 tem-
plates/Articles/edit.php 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.
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 integrated49 , 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.
49 https://en.wikipedia.org/wiki/Continuous_integration
81
CakePHP Book, Release 5.x
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
• intro.rst
• quickstart.rst
• installation.rst
• /intro folder
• /tutorials-and-examples folder
The structure of all language folders should mirror the English folder structure. If the structure changes for the English
version, we should apply those changes in the other languages.
For example, if a new English file is created in en/file.rst, we should:
• Add the file in all other languages : fr/file.rst, zh/file.rst, . . .
• Delete the content, but keeping the title, meta information and eventual toc-tree elements. The following
note will be added while nobody has translated the file:
File Title
##########
.. note::
The documentation is not currently supported in XX language for this
page.
You can refer to the English version in the select top menu to have
information about this page's topic.
one-toc-file
other-toc-file
.. meta::
:title lang=xx: File Title
:keywords lang=xx: title, description,...
82 Chapter 5. Contributing
CakePHP Book, Release 5.x
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 Form50 .
• Translate both the content and the title at the same time.
• Do compare to the English content before submitting a correction (if you correct something, but 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. For example, “asdf asdf Controller asdf” or “asdf
asdf Kontroller (Controller) asfd”.
• Do not submit partial translations.
• Do not edit a section with a pending change.
• Do not use HTML entities51 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.
The new CakePHP documentation is written with ReST formatted text52 . 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.
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.
50 https://en.wikipedia.org/wiki/Register#Linguistics
51 https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references
52 https://en.wikipedia.org/wiki/ReStructuredText
Documentation 83
CakePHP Book, Release 5.x
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”.
– **/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
84 Chapter 5. Contributing
CakePHP Book, Release 5.x
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.
External Links
The resulting link would look like this: External Link to php.net53
:doc:
Other pages in the documentation can be linked to using the :doc: role. You can link to the specified document
using either an absolute or relative path reference. You should omit the .rst extension. For example, if the ref-
erence :doc:`form` appears in the document core-helpers/html, then the link references core-helpers/
form. If the reference was :doc:`/core-helpers`, it would always reference /core-helpers regardless of
where it was used.
: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
---------------
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>`.
53 https://php.net
Documentation 85
CakePHP Book, Release 5.x
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.
The CakePHP documentation uses the phpdomain54 to provide custom directives for describing PHP objects and con-
structs. Using these directives and roles is required to give proper indexing and cross referencing features.
.. 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
.. php:method:: methodName()
See also:
php:method, php:attr, php:const
54 https://pypi.org/project/sphinxcontrib-phpdomain/
86 Chapter 5. Contributing
CakePHP Book, Release 5.x
.. php:method:: name(signature)
Describe a class method, its arguments, return value, and exceptions:
.. php:staticmethod:: ClassName::methodName(signature)
Describe a static method, its arguments, return value and exceptions, see php:method for options.
.. php:attr:: name
Describe an property/attribute on a class.
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:
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:
: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:
Documentation 87
CakePHP Book, Release 5.x
: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:
This is a paragraph::
while ($i--) {
doStuff()
}
Literal text is not modified or formatted, save that one level of indentation is removed.
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 direc-
tive should be written in complete sentences and include all appropriate punctuation.
• .. warning:: Warnings are used to document potential stumbling blocks, or information pertaining to security.
The content of the directive should be written in complete sentences and include all appropriate punctuation.
• .. versionadded:: X.Y.Z “Version added” admonitions are used to display notes specific to new features
added at a specific version, X.Y.Z being the version on which the said feature was added.
• .. deprecated:: X.Y.Z As opposed to “version added” admonitions, “deprecated” admonition are used to
notify of a deprecated feature, X.Y.Z being the version on which the said feature was deprecated.
All admonitions are made the same:
.. note::
88 Chapter 5. Contributing
CakePHP Book, Release 5.x
Samples
New in version 4.0.0: This awesome feature was added in version 4.0.0
Deprecated since version 4.0.1: This old feature was deprecated on version 4.0.1
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 GitHub55 .
Reporting Bugs
Well written bug reports are very helpful. There are a few steps to help create the best bug report possible:
• Do: Please search56 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. Both the support channel on the CakePHP
Slack workspace57 and the #cakephp IRC channel on Freenode58 have many developers available to help answer
your questions. Also have a look at Stack Overflow59 or the official CakePHP forum60 .
55 https://github.com/cakephp/cakephp/issues
56 https://github.com/cakephp/cakephp/search?q=it+is+broken&ref=cmdform&type=Issues
57 https://cakesf.herokuapp.com
58 https://webchat.freenode.net
59 https://stackoverflow.com/questions/tagged/cakephp
60 https://discourse.cakephp.org
Tickets 89
CakePHP Book, Release 5.x
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:
• Git
• PHP 8.1 or greater
• PHPUnit 5.7.0 or greater
Set up your user information with your name/handle and working email address:
Note: If you are new to Git, we highly recommend you to read the excellent and free ProGit61 book.
61 https://git-scm.com/book/
62 https://github.com
63 https://github.com/cakephp/cakephp
90 Chapter 5. Contributing
CakePHP Book, Release 5.x
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:
Tip: Use a descriptive name for your branch. Referencing the ticket or feature name is a good convention. Examples
include ticket-1234 and feature-awesome.
The above will create a local branch based on the upstream (CakePHP) 2.x branch. Work on your fix, and make as
many commits as you need; but keep in mind the following:
• Follow the Coding Standards.
• Add a test case to show the bug is fixed, or that the new feature works.
• Keep your commits logical, and write clear commit messages that provide context on what you changed and why.
Once your changes are done and you’re ready for them to be merged into CakePHP, you’ll want to update your branch:
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:
Code 91
CakePHP Book, Release 5.x
Check that all your tests continue to pass. Then push your branch to your fork:
If you’ve rebased after pushing your branch, you’ll need to use force push:
Once your branch is on GitHub, you can submit a pull request on GitHub.
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 4.0.0, the branch accepting new features will be
4.next.
• If your change is a breaks existing functionality, or APIs then you’ll have to choose then next major release. For
example, if the current release is 4.0.0 then the next time existing behavior can be broken will be in 5.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 Foundation64 will become the owner of any contributed code. Contributors should follow the CakePHP
Community Guidelines65 .
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-12 coding style guide66 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 Sniffer67 to check that your code follows required standards.
64 https://cakefoundation.org/old
65 https://cakephp.org/get-involved
66 https://www.php-fig.org/psr/psr-12/
67 https://github.com/cakephp/cakephp-codesniffer
92 Chapter 5. Contributing
CakePHP Book, Release 5.x
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 IdeHelper68 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
// base level
// level 1
// level 2
// level 1
// base level
Or:
$booleanVariable = true;
$stringVariable = 'moose';
if ($booleanVariable) {
echo 'Boolean value is true';
if ($stringVariable === 'moose') {
echo 'We have encountered a moose';
}
}
In cases where 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)));
Coding Standards 93
CakePHP Book, Release 5.x
$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.
• 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
if (expr)
statement;
// good
if (expr) {
statement;
(continues on next page)
94 Chapter 5. Contributing
CakePHP Book, Release 5.x
// 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:
Template Files
In template 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>
<?php if ($isAdmin): ?>
<p>You are the admin user.</p>
<?php endif; ?>
Coding Standards 95
CakePHP Book, Release 5.x
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:
// 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:
As you can see above there should be one space on both sides of equals sign (=).
Method Definition
return $var;
}
Parameters with a default value, should be placed last in function definition. Try to make your functions return some-
thing, at least true or false, so it can be determined whether the function call was successful:
96 Chapter 5. Contributing
CakePHP Book, Release 5.x
if (!($dnsInfo) || !($dnsInfo['phpType'])) {
return $this->addError();
}
return true;
}
Bail Early
...
}
...
}
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.
(continues on next page)
Coding Standards 97
CakePHP Book, Release 5.x
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)
{
}
Defining anonymous functions follows the PSR-1269 coding style guide, where they are declared with a space after the
function keyword, and a space before and after the use keyword:
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 phpDocumentor70 tags:
• @deprecated71 Using the @version <vector> <description> format, where version and description
are mandatory. Version refers to the one it got deprecated in.
• @example72
69 https://www.php-fig.org/psr/psr-12/
70 https://phpdoc.org
71 https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/deprecated.html
72 https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/example.html
98 Chapter 5. Contributing
CakePHP Book, Release 5.x
• @ignore73
• @internal74
• @link75
• @see76
• @since77
• @version78
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.
73 https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/ignore.html
74 https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/internal.html
75 https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/link.html
76 https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/see.html
77 https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/since.html
78 https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/version.html
Coding Standards 99
CakePHP Book, Release 5.x
Variable Types
int|bool
For more than two types it is usually best to just use mixed.
When returning the object itself (for example, for chaining), one should use $this instead:
/**
* Foo function.
*
* @return $this
*/
public function foo()
{
return $this;
}
Including Files
// 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_once79 function.
PHP Tags
Always use long tags (<?php ?>) instead of short tags (<? ?>). The short echo should be used in template files where
appropriate.
Short Echo
The short echo should be used in template files in place of <?php echo. It should be immediately followed by a single
space, the variable or function value to echo, a single space, and the php closing tag:
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
function longFunctionName()
{
}
79 https://php.net/require_once
Classes
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'];
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:
• 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 260680 ) 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
80 https://datatracker.ietf.org/doc/html/rfc2606.html
Casting
Constants
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);
Enums
While empty() often seems correct to use, 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():
function manipulate($var)
{
// Not recommended, $var is already defined in the scope
if (empty($var)) {
// ...
}
When dealing with defined properties you should favour null checks over empty()/isset() checks:
class Thing
{
private $property; // Defined
}
}
}
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:
// Recommended
if ($array['key'] !== null) {
// ...
}
}
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 versioning81 , 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 that you adapt to depre-
cations as they are introduced to ensure future upgrades are easier.
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.
81 https://semver.org/
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:
Working on CakePHP
If you are helping make CakePHP even better please keep the following guidelines in mind when adding/changing
functionality:
In a minor release you can:
1 Your code may be broken by minor releases. Check the migration guide for details.
Deprecations
In each minor release, features may be deprecated. If features are deprecated, API documentation and runtime warnings
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,
]
// ...
benefit.
3 Avoid whenever possible. Any removals need to be documented in the migration guide.
Experimental Features
Experimental features are not included in the above backwards compatibility promises. Experimental features can
have breaking changes made in minor releases as long as they remain experimental. Experimental features can be
identified by the warning in the book and the usage of @experimental in the API documentation.
Experimental features are intended to help gather feedback on how a feature works before it becomes stable. Once the
interfaces and behavior has been vetted with the community the experimental flags will be removed.
Installation
Note: In XAMPP, intl extension is included but you have to uncomment extension=php_intl.dll (or
extension=intl) 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.7 or higher)
• MariaDB (10.1 or higher)
• PostgreSQL (9.6 or higher)
• Microsoft SQL Server (2012 or higher)
• SQLite 3
109
CakePHP Book, Release 5.x
The Oracle database is supported through the Driver for Oracle Database82 community plugin.
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 8.1 (CLI) or higher. Your webserver’s PHP version must also be of 8.1 or higher, and should be
the same version your command line interface (CLI) uses.
Installing Composer
CakePHP uses Composer83 , 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 documentation84 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
You can create a new CakePHP application using composer’s create-project command:
Once Composer finishes downloading the application skeleton and the core CakePHP library, you should have a func-
tioning 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 templates/Pages/home.php.
Although composer is the recommended installation method, there are pre-installed downloads available on Github87 .
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.
82 https://github.com/CakeDC/cakephp-oracle-driver
83 https://getcomposer.org
84 https://getcomposer.org/download/
85 https://github.com/composer/windows-setup/releases/
86 https://github.com/composer/windows-setup
87 https://github.com/cakephp/cakephp/tags
"require": {
"cakephp/cakephp": "5.0.*"
}
Each time you run php composer.phar update you will receive patch releases for this minor version. You can
instead change this to ^5.0 to also receive the latest stable minor releases of the 5.x branch.
Another quick way to install CakePHP is via DDEV88 . It is an open source tool for launching local web development
environments.
If you want to configure a new project, you just need:
mkdir my-cakephp-app
cd my-cakephp-app
ddev config --project-type=cakephp --docroot=webroot
ddev composer create --prefer-dist cakephp/app:~5.0
ddev launch
Please check DDEV Docs89 for details on how to install / update DDEV.
Note: IMPORTANT: This is not a deployment script. It is aimed to help developers to set up a development environ-
ment quickly. It is not intended for production environments.
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
88 https://ddev.com/
89 https://ddev.readthedocs.io/
Permissions 111
CakePHP Book, Release 5.x
the following commands from your application directory just once in your project to ensure that permissions will be
setup properly:
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:
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:
cake_install/
bin/
config/
logs/
plugins/
resources/
src/
templates/
tests/
tmp/
vendor/
webroot/ (this directory is set as DocumentRoot)
.gitignore
.htaccess
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.
Production 113
CakePHP Book, Release 5.x
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 https://cwiki.
apache.org/confluence/display/httpd/DistrosDefaultLayout for further information.
1. Make sure that an .htaccess override is allowed and that AllowOverride is set to All for the correct DocumentRoot.
You should see something similar to:
# Each directory to which Apache has access can be configured with respect
# to which services and features are allowed and/or disabled in that
# directory (and its subdirectories).
#
# First, we configure the "default" to be a very restrictive set of
# features.
<Directory />
Options FollowSymLinks
AllowOverride All
# Order deny,allow
# Deny from all
</Directory>
2. Make sure you are loading mod_rewrite correctly. You should see something like:
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
(continues on next page)
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 virtualhostx90 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/
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. Mod-
ify 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:
90 https://clickontyler.com/virtualhostx/
nginx
nginx does not make use of .htaccess files like Apache, so it is necessary to create those rewrit-
ten 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;
}
server {
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.
NGINX Unit
NGINX Unit91 is dynamically configurable in runtime; the following configuration relies on webroot/index.php,
also serving other .php scripts if present via cakephp_direct:
{
"listeners": {
"*:80": {
"pass": "routes/cakephp"
}
},
"routes": {
"cakephp": [
{
"match": {
"uri": [
"*.php",
"*.php/*"
]
},
"action": {
"pass": "applications/cakephp_direct"
}
},
{
"action": {
"share": "/path/to/cakephp/webroot/",
"fallback": {
"pass": "applications/cakephp_index"
}
}
}
]
},
"applications": {
"cakephp_direct": {
"type": "php",
"root": "/path/to/cakephp/webroot/",
"user": "www-data"
},
"cakephp_index": {
"type": "php",
"root": "/path/to/cakephp/webroot/",
"user": "www-data",
"script": "index.php"
}
}
}
91 https://unit.nginx.org
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 Installer92 to install the URL Rewrite Module 2.093 or download it directly (32-
bit94 / 64-bit95 ).
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:
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.
92 https://www.microsoft.com/web/downloads/platform.aspx
93 https://www.iis.net/downloads/microsoft/url-rewrite
94 https://download.microsoft.com/download/D/8/1/D81E5DD6-1ABB-46B0-9B4B-21894E18B77F/rewrite_x86_en-US.msi
95 https://download.microsoft.com/download/1/2/8/128E2E22-C1B9-44A4-BE2A-5859ED1D4592/rewrite_amd64_en-US.msi
Lighttpd
Lighttpd does not make use of .htaccess files like Apache, so it is necessary to add a url.rewrite-once configuration
in conf/lighttpd.conf. Ensure the following is present in your lighthttpd configuration:
server.modules += (
"mod_alias",
"mod_cgi",
"mod_rewrite"
)
# Directory Alias
alias.url = ( "/TestCake" => "C:/Users/Nicola/Documents/TestCake" )
# CGI Php
cgi.assign = ( ".php" => "c:/php/php-cgi.exe" )
The above lines include PHP CGI configuration and example application configuration for an application on the /
TestCake path.
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'),
]
/.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.
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.
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 setConfig() methods to make configuration simple and transparent.
The application skeleton features a config/app.php file which should contain configuration that doesn’t vary across the
various environments your application is deployed in. The config/app_local.php file should contain the configuration
data that varies between environments and should be managed by configuration management, or your deployment
tooling. Both of these files reference environment variables through the env() function that enables configuration
values to set though the server environment.
121
CakePHP Book, Release 5.x
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;
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 style96 . 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 dotenv97 to make local development automatically reload environment
variables. Use composer to require this library and then there is a block of code in bootstrap.php that needs to be
uncommented to harness it.
You will see a config/.env.example in your application. By copying this file into config/.env and customizing
the values you can configure your application.
You should avoid committing the config/.env file to your repository and instead use the config/.env.example 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:
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.
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.
96 https://12factor.net/
97 https://github.com/josegonzalez/php-dotenv
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 /. For example, /basedir is a valid App.base.
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 command) 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 (for example, 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.
App.uploadedFilesAsObjects
Defines whether uploaded files are being represented as objects (true), or arrays (false). This option is being
treated as enabled by default. See the File Uploads section in the Request & Response Objects chapter for more
information.
Security.salt
A random string used in hashing. This value is also used as the HMAC salt when doing symmetric 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)
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 Error and Exception Configuration for information on configuring error and exception handlers.
Logging Configuration
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.
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/"]
}
}
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' => [
(continues on next page)
Paths should end with a directory separator, or they will not work properly.
Inflection Configuration
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.
Note: The dot notation used in the $key parameter can be used to organize your configuration settings into logical
groups.
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.
Note: Any configuration changes done using Configure::write() are in memory and will not persist across re-
quests.
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:
Configure::read('Company');
// Returns:
['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul'];
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');
// Yields:
['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul'];
static Cake\Core\Configure::check($key)
$exists = Configure::check('Company.name');
static Cake\Core\Configure::delete($key)
Configure::delete('Company.name');
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');
// Yields:
['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul'];
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 documentation99 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;
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():
99 https://php.net/parse_ini_file
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');
Once you’ve attached a config engine to Configure you can load configuration files:
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.
Warning: When merging configuration files with $merge = true, dot notation in keys is not expanded:
// config1.php
'Key1' => [
'Key2' => [
'Key3' => ['NestedKey1' => 'Value'],
],
],
// config2.php
'Key1.Key2' => [
'Key3' => ['NestedKey2' => 'Value2'],
]
Configure::load('config1', 'default');
Configure::load('config2', 'default', true);
Dumps all or some of the data in Configure into a file or storage system supported by a config engine. The serialization
format is decided by the config engine attached as $config. For example, if the ‘default’ engine is a Cake\Core\
Configure\Engine\PhpConfig, the generated file will be a PHP configuration file loadable by the Cake\Core\
Configure\Engine\PhpConfig
Given that the ‘default’ engine is an instance of PhpConfig. Save all data in Configure to the file my_config.php:
Configure::dump('my_config', 'default');
Configure::dump() can be used to either modify or overwrite configuration files that are readable with
Configure::load()
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:
// 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.
Once you’ve stored runtime configuration, you’ll probably need to restore it so you can access it again.
Configure::restore() does exactly that:
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 engines100 . The built in configuration engines are:
• JsonConfig101
• IniConfig102
• PhpConfig103
By default your application will use PhpConfig.
100 https://api.cakephp.org/5.x/interface-Cake.Core.Configure.ConfigEngineInterface.html
101 https://api.cakephp.org/5.x/class-Cake.Core.Configure.Engine.JsonConfig.html
102 https://api.cakephp.org/5.x/class-Cake.Core.Configure.Engine.IniConfig.html
103 https://api.cakephp.org/5.x/class-Cake.Core.Configure.Engine.PhpConfig.html
Routing
class Cake\Routing\RouterBuilder
Routing provides you tools that map URLs to controller actions. By defining routes, you can separate how your appli-
cation is implemented from how its URLs are structured.
Routing in CakePHP also encompasses the idea of reverse routing, where an array of parameters can be transformed
into a URL string. By using reverse routing, you can re-factor your 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 config/routes.php file:
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:
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:
133
CakePHP Book, Release 5.x
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(
'/upgrade',
['controller' => 'Subscriptions', 'action' => 'create'],
['_name' => 'upgrade']
);
use Cake\Routing\Router;
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:
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\RouteBuilder;
use Cake\Routing\Route\DashedRoute;
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:
);
$routes->connect('/admin/cms/articles', 'Cms.Admin/Articles::index');
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 arguments.
[Plugin].[Prefix]/[Controller]::[action]
// Application controller
'Articles::view'
// Plugin controller
Cms.Articles::edit
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']
);
$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
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:
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.
Route Elements
You can specify your own route elements and doing so gives you the power to define places in the URL where pa-
rameters 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 spec-
ify 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]+']);
$routes->connect(
'/{controller}/{id}',
['action' => 'view'],
(continues on next page)
The above example illustrates how to create a quick way to view models from any controller by crafting a URL that
looks like /controllername/{id}. The URL provided to connect() specifies two route elements: {controller}
and {id}. The {controller} element is a CakePHP default route element, so the router knows how to match and
identify controller names in URLs. The {id} element is a custom route element, and must be further clarified by
specifying a matching regular expression in the third parameter of connect().
CakePHP does not automatically produce lowercased and dashed URLs when using the {controller} parameter. If
you need this, the above example could be rewritten like so:
use Cake\Routing\Route\DashedRoute;
$routes->connect(
'/{controller}/{id}',
['action' => 'view'],
['id' => '[0-9]+']
);
});
The DashedRoute class will make sure that the {controller} and {plugin} parameters are correctly lowercased
and dashed.
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:
If you would like to provide a case insensitive URL, you can use regular expression inline modifiers:
$routes->connect(
'/{userShortcut}',
['controller' => 'Teachers', 'action' => 'profile', 1],
)->setPatterns(['userShortcut' => '(?i:principal)']);
$routes->connect(
'/{controller}/{year}/{month}/{day}',
(continues on next page)
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, handing the requests to the
index() actions of their respective controllers, with the date parameters in $this->request->getParam().
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 value of App.fullBaseUrl mentioned in General Configuration will be prepended to gen-
erated URLs.
• # Allows you to set URL hash fragments.
• _https Set to true to convert the generated URL to https or false to force http. Prior to 4.5.0 use _ssl.
• _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.
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'])
When connecting routes using Route Elements you may want to have routed elements be passed arguments instead.
The pass option indicates 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
$routes->scope('/', function (RouteBuilder $routes) {
$routes->connect(
'/blog/{id}-{slug}', // For example, /blog/3-CakePHP_Rocks
['controller' => 'Blogs', 'action' => 'view']
)
// Define the route elements in the route template
// to prepend as function arguments. Order matters as this
// will pass the `$id` and `$slug` elements as the first and
(continues on next page)
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.php
// 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'
]);
We talked about string targets above. The same also works for URL generation using Router::pathUrl():
echo Router::pathUrl('Articles::index');
// outputs: /articles
Tip: IDE support for Path Routing autocomplete can be enabled with CakePHP IdeHelper Plugin104 .
104 https://github.com/dereuromark/cakephp-ide-helper
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 specify a _name option, this
option can be used in reverse routing to identify the route you want to use:
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:
// Connect routes.
});
// Or with prefix()
$routes->prefix('Admin', ['_namePrefix' => 'admin:'], function (RouteBuilder $routes) {
// Connect routes.
});
You can also use the _namePrefix option inside nested scopes and it works as you’d expect:
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.
Prefix Routing
Many applications require an administration section where privileged users can make changes. This is often done
through a special URL such as /admin/users/edit/5. In CakePHP, prefix routing can be enabled by using the
prefix scope method:
use Cake\Routing\Route\DashedRoute;
Prefixes are mapped to sub-namespaces in your application’s Controller namespace. By having prefixes as separate
controllers you can create smaller and simpler controllers. Behavior that is common to the prefixed and non-prefixed
controllers can be encapsulated using inheritance, Components, or traits. Using our users example, accessing the URL
/admin/users/edit/5 would call the edit() method of our src/Controller/Admin/UsersController.php passing
5 as the first parameter. The view file used would be templates/Admin/Users/edit.php
You can map the URL /admin to your index() action of pages controller using following route:
When creating prefix routes, you can set additional route parameters using the $options argument:
Note the additional route parameters will be added to all the connected routes defined inside the prefix block. You
will need to use all the parameters in the url array to build the route later, if you don’t use them you’ll get a
MissingRouteException.
Multi word prefixes are by default converted using dasherize inflection, ie MyPrefix would be mapped to my-prefix
in the URL. Make sure to set a path for such prefixes if you want to use a different format like for example underscoring:
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:
The above would create a route template like /manager/admin/{controller}/{action}. 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, and to use the same CamelCased format that is used
in the prefix() method. Here’s how to build this link using the HTML helper:
// Leave a prefix
echo $this->Html->link(
'View Post',
['prefix' => false, 'controller' => 'Articles', 'action' => 'view', 5]
);
You can create links that point to a prefix, by adding the prefix key to your URL array:
echo $this->Html->link(
'New admin todo',
['prefix' => 'Admin', 'controller' => 'TodoItems', 'action' => 'create']
);
echo $this->Html->link(
'New todo',
['prefix' => 'Admin/MyPrefix', 'controller' => 'TodoItems', 'action' => 'create']
);
This would link to a controller with the namespace App\\Controller\\Admin\\MyPrefix and the file path src/
Controller/Admin/MyPrefix/TodoItemsController.php.
Note: The prefix is always CamelCased here, even if the routing result is dashed. The route itself will do the inflection
if necessary.
Plugin Routing
Routes for Plugins should be created using the plugin() method. This method creates a new routing scope for the
plugin’s routes:
When creating plugin scopes, you can customize the path element used with the path option:
When using scopes you can nest plugin scopes within prefix scopes:
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.
You can create links that point to a plugin, by adding the plugin key to your URL array:
echo $this->Html->link(
'New todo',
['plugin' => 'Todo', 'controller' => 'TodoItems', 'action' => 'create']
);
Conversely if the active request is a plugin request and you want to create a link that has no plugin you can do the
following:
echo $this->Html->link(
'New todo',
['plugin' => null, 'controller' => 'Users', 'action' => 'profile']
);
By setting 'plugin' => null you tell the Router that you want to create a link that is not part of a plugin.
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;
Routes can match specific HTTP methods using the HTTP verb helper methods:
You can match multiple HTTP methods by using an array. Because the _method parameter is a routing key, it partic-
ipates 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:
$url = Router::url([
'controller' => 'Reviews',
'action' => 'start',
'_method' => 'POST',
]);
Routes can use the _host option to only match specific hosts. You can use the *. wildcard to match any subdomain:
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:
To handle different file extensions in your URLs, you can define the extensions using the Cake\Routing\
RouteBuilder::setExtensions() method:
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.
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 from the URL, 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:
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']
);
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;
In situations where you have nested scopes, inner scopes will inherit the middleware applied in the containing scope:
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:
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 combined
groups can be applied like middleware can:
RESTful Routing
Router helps 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...
The first line sets up a number of default routes for REST access where method specifies the desired result format, for
example, xml, json and rss. These routes are HTTP Request Method sensitive.
Note: The default for pattern for resource IDs only matches integers or UUIDs. If your IDs are different you will have
to supply a regular expression pattern via the id option, for example, $builder->resources('Recipes', ['id'
=> '.*']).
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.
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:
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}
$this->request->getParam('article_id');
By default resource routes map to the same prefix as the containing scope. If you have both nested and non-nested
resource controllers you can use a different controller in each context by using prefixes:
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.
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.
The default route name and controller action used are as follows:
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().
You can map additional resource methods using the map option:
$routes->resources('Articles', [
'map' => [
'deleteAll' => [
'action' => 'deleteAll',
'method' => 'DELETE'
]
]
]);
// This would connect /articles/deleteAll
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' => 'PUT',
'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.
Resource routes can be connected to controllers in routing prefixes by connecting routes within a prefixed scope or by
using the prefix option:
$routes->resources('Articles', [
'prefix' => 'Api',
]);
You can provide connectOptions key in the $options array for resources() to provide custom setting used by
connect():
By default, multi-worded controllers’ URL fragments are the dashed form of the controller’s name. For example,
BlogPostsController’s URL fragment would be /blog-posts.
You can specify an alternative inflection type using the inflect option:
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:
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:
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'));
Array
(
[0] => recent
[1] => mark
)
When generating URLs, using a routing array you add passed arguments as values without string keys in the array:
Generating URLs
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.
If you create URLs using strings like:
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:
or:
$requestParams = Router::getRequest()->getAttribute('params');
$this->Html->link('View', Router::reverse($requestParams));
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.
The choice of technique is determined by how well you can predict the routing array elements.
Using Router::url()
Router::url() allows you to use routing arrays in situations where the array elements required are fixed or easily
deduced.
It will provide reverse routing when the destination url is well defined:
$this->Html->link(
'View',
['controller' => 'Articles', 'action' => 'view', $id]
);
It is also useful when the destination is unknown but follows a well defined pattern:
$this->Html->link(
'View',
['controller' => $controller, 'action' => 'view', $id]
);
$routes->url([
'controller' => 'Articles',
'action' => 'index',
'?' => ['page' => 1],
'#' => 'top'
]);
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 value of App.fullBaseUrl mentioned in General Configuration will be prepended to gen-
erated URLs.
• _https Set to true to convert the generated URL to https or false to force http. Prior to 4.5.0 use _ssl
• _name Name of route. If you have setup named routes, you can use this key to specify it.
Using Router::reverse()
Router::reverse() allows you to use the Request Parameters in cases where the current URL with some modification
is the basis for the destination and the elements of the current URL are unpredictable.
As an example, imagine a blog that allowed users to create Articles and Comments, and to mark both as either pub-
lished or draft. Both the index page URLs might include the user id. The Comments URL might also include an
article id to identify what article the comment refers to.
Here are urls for this scenario:
/articles/index/42
/comments/index/42/18
When the author uses these pages, it would be convenient to include links that allow the page to be displayed with all
results, published only, or draft only.
To keep the code DRY, it would be best to include the links through an element:
// element/filter_published.php
$params = $this->getRequest()->getAttribute('params');
The links generated by these method calls would include one or two pass parameters depending on the structure of the
current URL. And the code would work for any future URL, for example, if you started using pathPrefixes or if you
added more pass parameters.
The significant difference between the two arrays and their use in these reverse routing methods is in the way they
include pass parameters.
Routing arrays include pass parameters as un-keyed values in the array:
$url = [
'controller' => 'Articles',
'action' => 'View',
$id, //a pass parameter
'page' => 3, //a query argument
];
Request parameters include pass parameters on the ‘pass’ key of the array:
$url = [
'controller' => 'Articles',
'action' => 'View',
'pass' => [$id], //the pass parameters
'?' => ['page' => 3], //the query arguments
];
So it is possible, if you wish, to convert the request parameters into a routing array or vice versa.
The Asset class provides methods for generating URLs to your application’s css, javascript, images and other static
asset files:
use Cake\Routing\Asset;
The above methods also accept an array of options as their second parameter:
• fullBase Append the full URL with domain name.
• pathPrefix Path prefix for relative URLs.
• plugin` You can provide false` to prevent paths from being treated as a plugin asset.
• timestamp Overrides the value of Asset.timestamp in Configure. Set to false to skip timestamp generation.
Set to true to apply timestamps when debug is true. Set to 'force' to always enable timestamping regardless
of debug value.
// Generates http://example.org/img/logo.png
$img = Asset::url('logo.png', ['fullBase' => true]);
// Generates /img/logo.png?1568563625
// Where the timestamp is the last modified time of the file.
$img = Asset::url('logo.png', ['timestamp' => true]);
// Generates `/debug_kit/img/cake.png`
$img = Asset::imageUrl('DebugKit.cake.png');
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:
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:
Entity Routing
Entity routing allows you to use an entity, an array or object implement ArrayAccess as the source of routing param-
eters. 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'],
(continues on next page)
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;
This will extract both the id property and the slug property out of the provided entity.
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.
• 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 null 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']
(continues on next page)
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.
If you want to use an alternate route class for your routes besides the default Route, you can do so by calling
RouterBuilder::setRouteClass() before setting up any routes and avoid having to specify the routeClass op-
tion for each route. For example using:
use Cake\Routing\Route\DashedRoute;
$routes->setRouteClass(DashedRoute::class);
will cause all routes connected after this to use the DashedRoute route class. Calling the method without an argument
will return current default route class.
Fallbacks Method
Cake\Routing\RouterBuilder::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 RouterBuilder::setRouteClass() is used.
Calling fallbacks like so:
use Cake\Routing\Route\DashedRoute;
$routes->fallbacks(DashedRoute::class);
use Cake\Routing\Route\DashedRoute;
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.
Warning: Fallback route templates are very generic and allow URLs to be generated and parsed for controllers &
actions that do not exist. Fallback URLs can also introduce ambiguity and duplication in your URLs.
As your application grows, it is recommended to move away from fallback URLs and explicitly define the routes in
your application.
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 parameter array being processed.
• $request The current request (Cake\Http\ServerRequest instance).
The URL filter function should always return the parameters even if unmodified.
URL filters allow you to implement features like persistent parameters:
return $params;
});
return $params;
}
if ($params['controller'] === 'Languages' && $params['action'] === 'view') {
$params['controller'] = 'Locations';
$params['action'] = 'index';
$params['language'] = $params[0];
unset($params[0]);
}
return $params;
});
Router::url(['plugin' => 'MyPlugin', 'controller' => 'Languages', 'action' => 'view', 'es
˓→']);
into this:
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.
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.
Changed in version 4.4.0: The ServerRequest is available via DI. So you can get it from container or use it as a
dependency for your service.
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.
CakePHP’s request object implements the PSR-7 ServerRequestInterface105 making it easier to use libraries from out-
side of CakePHP.
105 https://www.php-fig.org/psr/psr-7/
163
CakePHP Book, Release 5.x
Request Parameters
$controllerName = $this->request->getParam('controller');
$parameters = $this->request->getAttribute('params');
// 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.
// URL is /posts/index?page=1&sort=title
$page = $this->request->getQuery('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
If you want to access all the query parameters you can use getQueryParams():
$query = $this->request->getQueryParams();
All POST data normally available through PHP’s $_POST global variable can be accessed using Cake\Http\
ServerRequest::getData(). For example:
You can use a dot separated names to access nested data. For example:
$value = $this->request->getData('address.street_name');
$foo = $this->request->getData('value.that.does.not.exist');
// $foo == null
You can also use body-parser-middleware to parse request body of different content types into an array, so that it’s
accessible through ServerRequest::getData().
If you want to access all the data parameters you can use getParsedBody():
$data = $this->request->getParsedBody();
File Uploads
Uploaded files can be accessed through the request body data, using the Cake\Http\ServerRequest::getData()
method described above. For example, a file from an input element with a name attribute of attachment, can be
accessed like this:
$attachment = $this->request->getData('attachment');
By default file uploads are represented in the request data as objects that implement
\Psr\Http\Message\UploadedFileInterface106 . In the current implementation, the $attachment variable in the
above example would by default hold an instance of \Laminas\Diactoros\UploadedFile.
Accessing the uploaded file details is fairly simple, here’s how you can obtain the same data as provided by the old style
file upload array:
$name = $attachment->getClientFilename();
$type = $attachment->getClientMediaType();
$size = $attachment->getSize();
$tmpName = $attachment->getStream()->getMetadata('uri');
$error = $attachment->getError();
Moving the uploaded file from its temporary location to the desired target location, doesn’t require manually accessing
the temporary file, instead it can be easily done by using the objects moveTo() method:
$attachment->moveTo($targetPath);
106 https://www.php-fig.org/psr/psr-7/#16-uploaded-files
Request 165
CakePHP Book, Release 5.x
In an HTTP environment, the moveTo() method will automatically validate whether the file is an actual uploaded file,
and throw an exception in case necessary. In an CLI environment, where the concept of uploading files doesn’t exist, it
will allow to move the file that you’ve referenced irrespective of its origins, which makes testing file uploads possible.
Cake\Http\ServerRequest::getUploadedFile($path)
Returns the uploaded file at a specific path. The path uses the same dot syntax as the Cake\Http\
ServerRequest::getData() method:
$attachment = $this->request->getUploadedFile('attachment');
Returns all uploaded files in a normalized array structure. For the above example with the file input name of
attachment, the structure would look like:
[
'attachment' => object(Laminas\Diactoros\UploadedFile) {
// ...
}
]
Cake\Http\ServerRequest::withUploadedFiles(array $files)
This method sets the uploaded files of the request object, it accepts an array of objects that implement
\Psr\Http\Message\UploadedFileInterface107 . It will replace all possibly existing uploaded files:
$files = [
'MyModel' => [
'attachment' => new \Laminas\Diactoros\UploadedFile(
$streamOrFile,
$size,
$errorStatus,
$clientFilename,
$clientMediaType
),
'anotherAttachment' => new \Laminas\Diactoros\UploadedFile(
'/tmp/hfz6dbn.tmp',
123,
\UPLOAD_ERR_OK,
'attachment.txt',
'text/plain'
),
],
];
$this->request = $this->request->withUploadedFiles($files);
Note: Uploaded files that have been added to the request via this method, will not be available in the request
body data, ie you cannot retrieve them via Cake\Http\ServerRequest::getData()! If you need them in the
107 https://www.php-fig.org/psr/psr-7/#16-uploaded-files
request data (too), then you have to set them via Cake\Http\ServerRequest::withData() or Cake\Http\
ServerRequest::withParsedBody().
Cake\Http\ServerRequest::getBody()
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 available via $request->getData()
for PUT and DELETE requests. If you are accepting JSON or XML data, you can access the raw data with getBody():
If your requests contain XML or JSON request content, you should consider using body-parser-middleware to have
CakePHP automatically parse those content types making the parsed data available in $request->getData() and
$request->getParsedBody().
ServerRequest::getEnv() is a wrapper for getenv() global function and acts as a getter/setter for environment
variables without having to modify globals $_SERVER and $_ENV:
$env = $this->request->getServerParams();
Applications employing REST often exchange data in non-URL-encoded post bodies. You can read input data in any
format using input(). By providing a decoding function, you can receive the content in a deserialized format:
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, input() supports passing in additional
parameters as well:
Request 167
CakePHP Book, Release 5.x
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:
// Holds /subdir/articles/edit/1?page=1
$here = $request->getRequestTarget();
// Holds /subdir
$base = $request->getAttribute('base');
// Holds /subdir/
$base = $request->getAttribute('webroot');
Cake\Http\ServerRequest::is($type, $args...)
The request object provides a 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.
• 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)
Request 169
CakePHP Book, Release 5.x
Session Data
To access the session for a given request use the getSession() method or use the session attribute:
$session = $this->request->getSession();
$session = $this->request->getAttribute('session');
$data = $session->read('sessionKey');
For more information, see the Sessions documentation for how to use the session object.
Cake\Http\ServerRequest::domain($tldLength = 1)
// Prints 'example.org'
echo $request->domain();
Cake\Http\ServerRequest::subdomains($tldLength = 1)
Cake\Http\ServerRequest::host()
// Prints 'my.dev.example.org'
echo $request->host();
Cake\Http\ServerRequest::getMethod()
// Output POST
echo $request->getMethod();
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:
Allows you to access any of the HTTP_* headers that were used for the request. For example:
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 = true)
Request 171
CakePHP Book, Release 5.x
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;
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.
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();
$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();
$acceptsSpanish = $this->request->acceptLanguage('es-es');
Reading Cookies
See the Cake\Http\Cookie\CookieCollection documentation for how to work with cookie collection.
Uploaded Files
Requests expose the uploaded file data in getData() or getUploadedFiles() as UploadedFileInterface objects:
Manipulating URIs
Requests contain a URI object, which contains methods for interacting with the requested URI:
Request 173
CakePHP Book, Release 5.x
Response
class Cake\Http\Response
Cake\Http\Response is the default response class in CakePHP. It encapsulates a number of features and functionality
for generating HTTP responses in your application. It also assists in testing, as it can be mocked/stubbed allowing you
to inspect headers that will be sent.
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.
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 setTypeMap()
as well:
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.
Sending Files
There are times when you want to send files as responses for your requests. You can accomplish that by using Cake\
Http\Response::withFile():
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']
);
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:
$response = $response->withType('ics');
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 interface
methods, this method returns a new instance with the new header:
// Add/replace a header
$response = $response->withHeader('X-Extra', 'My header');
Response 175
CakePHP Book, Release 5.x
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.
Cake\Http\Response::withStringBody($string)
Cake\Http\Response::withBody($body)
To set the response body, use the withBody() method, which is provided by the Laminas\Diactoros\
MessageTrait:
$response = $response->withBody($stream);
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 Laminas\Diactoros\Stream streams:
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:
// Create an image.
$img = imagecreate(100, 100);
// ...
Cake\Http\Response::withCharset($charset)
$this->response = $this->response->withCharset('UTF-8');
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:
Warning: Disabling caching from SSL domains while trying to send files to Internet Explorer can result in errors.
You can also tell clients that you want them to cache responses. By using Cake\Http\Response::withCache():
The above would tell clients to cache the resulting response for 5 days, hopefully speeding up your visitors’ experience.
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.
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.
Response 177
CakePHP Book, Release 5.x
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:
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:
Response exposes separate methods for setting each of the directives in the Cache-Control 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:
This method also accepts a DateTime instance or any string that can be parsed by the DateTime class.
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.
To take advantage of this header, you must either call the checkNotModified() method manually or include the
Checking HTTP Cache in your controller:
$response = $this->response->withEtag($checksum);
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.
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
Checking HTTP Cache in your controller:
Response 179
CakePHP Book, Release 5.x
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');
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
use Cake\Http\Cookie\Cookie;
use DateTime;
// Add a cookie
$this->response = $this->response->withCookie(Cookie::create(
'remember_me',
'yes',
// All keys are optional
[
'expires' => new DateTime('+1 year'),
'path' => '',
'domain' => '',
'secure' => false,
'httponly' => false,
'samesite' => null // Or one of CookieInterface::SAMESITE_* constants
]
));
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:
The cors() method is used to define HTTP Access Control108 related headers with a fluent interface:
$this->response = $this->response->cors($this->request)
->allowOrigin(['*.cakephp.org'])
->allowMethods(['GET', 'POST'])
->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.
Tip: CakePHP has no built-in CORS middleware because dealing with CORS requests is very application specific.
We recommend you build your own CORSMiddleware if you need one and adjust the response object as desired.
Response objects offer a number of methods that treat responses as immutable objects. Immutable 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:
108 https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
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;
Once you have created a cookie, you can add it to a new or existing CookieCollection:
use Cake\Http\Cookie\CookieCollection;
Note: Remember that collections are immutable and adding cookies into, or removing cookies from a collection,
creates a new collection object.
Reading Cookies
Once you have a CookieCollection instance, you can access the cookies it contains:
// Get a cookie instance. Will throw an error if the cookie is not found
$cookie = $cookies->get('remember_me');
Once you have a Cookie object you can interact with it’s state and modify it. Keep in mind that cookies are immutable,
so you’ll need to update the collection if you modify a cookie:
// Check state
$cookie->isHttpOnly();
$cookie->isSecure();
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.
185
CakePHP Book, Release 5.x
As stated in the introduction, the AppController class is the parent class to all of your application’s controllers.
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;
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) con-
trollers.
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;
Request Flow
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
The template files for these actions would be templates/Recipes/view.php, templates/Recipes/share.php, and tem-
plates/Recipes/search.php. 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.
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.
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:
$this->set('color', 'pink');
You have selected <?= h($color) ?> icing for the cake.
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,
];
$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.
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()
->addHelper('MyCustom')
->setTheme('Modern')
->setClassName('Modern.Admin');
The above shows how you can load custom helpers, set the theme and use a custom view class.
Rendering a View
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 templates/Recipes/search.php will be rendered:
namespace App\Controller;
Although CakePHP will automatically call it after every action’s logic (unless you’ve called
$this->disableAutoRender()), 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 templates folder. This allows direct
rendering of elements, very useful in AJAX calls:
The second parameter $layout of Controller::render() allows you to specify the layout with which the view is
rendered.
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;
You can also render views inside plugins using the following syntax: $this->render('PluginName.
PluginController/custom_file'). For example:
namespace App\Controller;
Cake\Controller\Controller::addViewClasses()
Controllers can define a list of view classes they support. After the controller’s action is complete CakePHP will use
the view list to perform content-type negotiation with either Routing File Extensions or Accept headers. This enables
your application to re-use the same controller action to render an HTML view or render a JSON or XML response. To
define the list of supported view classes for a controller is done with the addViewClasses() method:
namespace App\Controller;
use Cake\View\JsonView;
use Cake\View\XmlView;
$this->addViewClasses([JsonView::class, XmlView::class]);
}
}
The application’s View class is automatically used as a fallback when no other view can be selected based on the
request’s Accept header or routing extension. If your application only supports content types for a specific actions,
you can call addClasses() within your action too:
If within your controller actions you need to process the request or load data differently based on the content type you
can use Checking Request Conditions:
// In a controller action
In case your app need more complex logic to decide which view classes to use then you can override the
Controller::viewClasses() method and return an array of view classes as required.
Note: View classes must implement the static contentType() hook method to participate in content-type negotiation.
If no View can be matched with the request’s content type preferences, CakePHP will use the base View class. If you
want to require content-type negotiation, you can use the NegotiationRequiredView which sets a 406 status code:
You can use the TYPE_MATCH_ALL content type value to build your own fallback view logic:
namespace App\View;
use Cake\View\View;
It is important to remember that match-all views are applied only after content-type negotiation is attempted.
Using AjaxView
In applications that use hypermedia or AJAX clients, you often need to render view contents without the wrapping
layout. You can use the AjaxView that is bundled with the application skeleton:
AjaxView will respond as text/html and use the ajax layout. Generally this layout is minimal or contains client
specific markup. This replaces usage of RequestHandlerComponent automatically using the AjaxView in 4.x.
The redirect() method adds a Location header and sets the status code of a response and returns it. You should
return the response created by redirect() to have CakePHP send the redirect instead of completing the controller
action and rendering a view.
You can redirect using routing array values:
return $this->redirect([
'controller' => 'Orders',
'action' => 'confirm',
$order->id,
'?' => [
'product' => 'pizza',
'quantity' => 5
],
'#' => 'top'
]);
return $this->redirect('/orders/confirm');
return $this->redirect('http://www.example.com');
return $this->redirect($this->referer());
By using the second parameter you can define a status code for your redirect:
See the Using Redirects in Component Events section for how to redirect out of a life-cycle handler.
The fetchTable() method comes handy when you need to use an ORM table that is not the controller’s default one:
// In a controller method.
$recentArticles = $this->fetchTable('Articles')->find('all',
limit: 5,
order: 'Articles.created DESC'
)
->all();
The fetchModel() method is useful to load non ORM models or ORM tables that are not the controller’s default:
use ModelAwareTrait;
// If you skip the 2nd argument it will by default try to load a ORM table.
$authors = $this->fetchModel('Authors');
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 a way to customize how paginate() behaves:
In your Controller’s initialize() method you can define any components you want loaded, and any configuration
data for them:
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
By default the following callback methods are connected to related events if the methods are implemented by your
controllers
Cake\Controller\Controller::beforeFilter(EventInterface $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.
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(EventInterface $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 render() manually before
the end of a given action.
Cake\Controller\Controller::afterFilter(EventInterface $event)
Called during the Controller.shutdown event which is triggered after every controller action, and after ren-
dering 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\EventInterface;
public function beforeFilter(EventInterface $event)
{
parent::beforeFilter($event);
}
Controller Middleware
Middleware can be defined globally, in a routing scope or within a controller. To define middleware for a specific
controller use the middleware() method from your controller’s initialize() method:
Middleware defined by a controller will be called before beforeFilter() and action methods are called.
More on Controllers
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 templates/Pages/home.php. If you make the view file templates/Pages/about_us.php 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:
Flash
FlashComponent provides a way to set one-time notification messages to be displayed after processing a form or ac-
knowledging data. CakePHP refers to these messages as “flash messages”. FlashComponent writes flash messages to
$_SESSION, to be rendered in a View using FlashHelper.
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 templates/element/flash directory. By convention, camelcased methods will
map to the lowercased and underscored element name:
// Uses templates/element/flash/success.php
$this->Flash->success('This was successful');
// Uses templates/element/flash/great_success.php
$this->Flash->greatSuccess('This was greatly successful');
Alternatively, to set a plain-text message without rendering an element, you can use the set() method:
$this->Flash->set('This is a message');
Flash messages are appended to an array internally. Successive calls to set() or __call() with the same key will
append the messages in the $_SESSION. If you want to overwrite existing messages when setting a flash message, set
the clear option 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.
• clear 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',
'clear' => true,
(continues on next page)
// In your View
<?= $this->Flash->render('positive') ?>
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.php element under plugins/PluginName/templates/element/flash for rendering
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.
It is possible to output HTML in flash messages by using the 'escape' option key:
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.
FormProtection
Note: When using the FormProtection Component you must use the FormHelper to create your forms. In addition, you
must not override any of the fields’ “name” attributes. The FormProtection Component looks for certain indicators that
are created and managed by the FormHelper (especially those created in create() and end()). Dynamically altering
the fields that are submitted in a POST request, such as disabling, deleting or creating new fields via JavaScript, is
likely to cause the form token validation to fail.
By default the FormProtectionComponent prevents users from tampering with forms in specific ways. It will prevent
the following things:
• Form’s action (URL) cannot be modified.
• 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 and hidden
token fields are automatically be inserted into forms. When a form is submitted, the FormProtectionComponent will
use the POST data to build the same structure and compare the hash.
Note: The FormProtectionComponent will not prevent select options from being added/changed. Nor will it prevent
radio options from being added/changed.
Usage
Configuring the form protection component is generally done in the controller’s initialize() or beforeFilter()
callbacks
Available options are:
validate
Set to false to completely skip the validation of POST requests, essentially turning off form validation.
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.
unlockedActions
Actions to exclude from POST validation checks.
validationFailureCallback
Callback to call in case of validation failure. Must be a valid Closure. Unset by default in which case exception
is thrown on validation failure.
namespace App\Controller;
use App\Controller\AppController;
use Cake\Event\EventInterface;
$this->loadComponent('FormProtection');
}
The above example would disable form tampering prevention for admin prefixed routes.
There may be cases where you want to disable form tampering prevention for an action (ex. AJAX requests).
You may “unlock” these actions by listing them in $this->FormProtection->setConfig('unlockedActions',
['edit']); in your beforeFilter():
namespace App\Controller;
use App\Controller\AppController;
use Cake\Event\EventInterface;
$this->FormProtection->setConfig('unlockedActions', ['edit']);
(continues on next page)
This example would disable all security checks for the edit action.
If form protection validation fails it will result in a 400 error by default. You can configure this behavior by setting the
validationFailureCallback configuration option to a callback function in the controller.
By configuring a callback method you can customize how the failure handling process works:
$this->FormProtection->setConfig(
'validationFailureCallback',
function (BadRequestException $exception) {
// You can either return a response instance or throw the exception
// received as argument.
}
);
}
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 response times:
// in a Controller
public function initialize(): void
{
parent::initialize();
$this->addComponent('CheckHttpCache');
}
Enabling the CheckHttpCacheComponent in your controller automatically activates a beforeRender check. This
check compares caching headers set in the response object to the caching headers sent in the request to determine
whether the response was not modified since the last time the client asked for it. The following request headers are
used:
• If-None-Match is compared with the response’s Etag header.
• If-Modified-Since is compared with the response’s Last-Modified header.
If response headers match the request header criteria, then view rendering is skipped. This saves your application
generating a view, saving bandwidth and time. When response headers match, an empty response is returned with a
304 Not Modified status code.
Configuring Components
Many of the core components require configuration. One example would be the FormProtection. 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:
You can configure components at runtime using the setConfig() method. Often, this is done in your controller’s
beforeFilter() method. The above could also be expressed as:
Like helpers, components implement getConfig() and setConfig() methods to read and write configuration data:
// Set config
$this->Flash->setConfig('key', 'myFlash');
As with helpers, components will automatically merge their $_defaultConfig property with constructor configura-
tion to create the $_config property which is accessible with getConfig() and setConfig().
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->Flash or another common Component reference with a custom implementation:
// src/Controller/PostsController.php
class PostsController extends AppController
{
public function initialize(): void
{
$this->loadComponent('Flash', [
'className' => 'MyFlash',
]);
}
}
Note: Aliasing a component replaces that instance anywhere that component is used, including inside other Compo-
nents.
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.
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:
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.
Warning: Component methods don’t have access to /development/dependency-injection like Controller actions
have. Use a service class inside your controller actions instead of a component if you need this functionality.
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;
Note: All components must extend Cake\Controller\Component. Failing to do this will trigger an exception.
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->Flash
public function initialize(): void
{
parent::initialize();
$this->loadComponent('Math');
$this->loadComponent('Flash');
}
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(): void
{
parent::initialize();
$this->loadComponent('Math', [
'precision' => 2,
'randomGenerator' => 'srand',
]);
$this->loadComponent('Flash');
}
The above would pass the array containing precision and randomGenerator to MathComponent::initialize() in
the $config parameter.
Sometimes one of your components may need to use another component. You can load other components by adding
them to the $components property:
// src/Controller/Component/CustomComponent.php
namespace App\Controller\Component;
use Cake\Controller\Component;
// src/Controller/Component/ExistingComponent.php
namespace App\Controller\Component;
use Cake\Controller\Component;
Note: In contrast to a component included in a controller no callbacks will be triggered on a component’s component.
From within a Component you can access the current controller through the registry:
$controller = $this->getController();
Component Callbacks
Components also offer a few request life-cycle callbacks that allow them to augment the request cycle.
beforeFilter(EventInterface $event)
Is called before the controller’s beforeFilter() method, but after the controller’s initialize() method.
startup(EventInterface $event)
Is called after the controller’s beforeFilter() method but before the controller executes the current action handler.
beforeRender(EventInterface $event)
Is called after the controller executes the requested action’s logic, but before the controller renders views and
layout.
afterFilter(EventInterface $event)
Is called during the Controller.shutdown event, before output is sent to the browser.
beforeRedirect(EventInterface $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.
To redirect from within a component callback method you can use the following:
return $this->getController()->redirect('/');
}
By stopping the event you let CakePHP know that you don’t want any other component callbacks to run, and that
the controller should not handle the action any further. As of 4.1.0 you can raise a RedirectException to signal a
redirect:
use Cake\Http\Exception\RedirectException;
use Cake\Routing\Router;
Raising an exception will halt all other event listeners and create a new response that doesn’t retain or inherit any of the
current response’s headers. When raising a RedirectException you can include additional headers:
Views
class Cake\View\View
Views are the V in MVC. Views are responsible for generating the specific output required for the request. Often
this is in the form of HTML, XML, or JSON, but streaming files and creating PDFs that users can download are also
responsibilities of the View Layer.
CakePHP comes with a few built-in View classes for handling the most common rendering scenarios:
• To create XML or JSON webservices you can use the JSON and XML views.
• To serve protected files, or dynamically generated files, you can use Sending Files.
• To create multiple themed views, you can use Themes.
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;
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:
207
CakePHP Book, Release 5.x
<?php
namespace App\View;
use Cake\View\View;
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 are regular PHP files and utilize the alternative PHP syntax109 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
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>
109 https://php.net/manual/en/control-structures.alternative-syntax.php
If you’d prefer to use a templating language like Twig110 , checkout the CakePHP Twig Plugin111
Template files are stored in templates/, 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 templates/Products/view.php.
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 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:
110 https://twig.symfony.com
111 https://github.com/cakephp/twig-view
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:
$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:
<!-- templates/Common/view.php -->
<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:
<!-- templates/Posts/view.php -->
<?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(); ?>
(continues on next page)
The post view above shows how you can extend a view, and populate a set of blocks. Any content not already in a
defined block will be captured and put into a special block named content. When a view contains a call to extend(),
execution continues to the bottom of the current view file. Once it is complete, the extended view will be rendered.
Calling extend() more than once in a view file will override the parent view that will be processed next:
$this->extend('/Common/view');
$this->extend('/Common/index');
The above will result in /Common/index.php 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.
Extending Layouts
Just like views, layouts can also be extended. Like views, you use extend() to extend layouts. Layout extensions can
update or replace blocks, and update or replace the content rendered by the child layout. For example if we wanted to
wrap a block with additional markup you could do:
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:
$this->append('sidebar');
echo $this->element('sidebar/popular_topics');
$this->end();
If you need to clear or overwrite a block there are a couple of alternatives. The reset() method will clear or overwrite
a block at any time. The assign() method with an empty content string can also be used to clear the specified block.:
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:
// 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:
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 templates/layout/default.php
<?php if ($this->fetch('menu')): ?>
<div class="menu">
<h3>Menu options</h3>
<?= $this->fetch('menu') ?>
(continues on next page)
You can also provide a default value for a block if it does not exist. This allows you to add placeholder content when a
block does not exist. You can provide a default value using the second argument:
<div class="shopping-cart">
<h3>Your Cart</h3>
<?= $this->fetch('cart', 'Your cart is empty') ?>
</div>
The 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]);
?>
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 templates/layout/default.php. 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 templates/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')
Layouts 213
CakePHP Book, Release 5.x
<!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>
</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).
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 templates/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');
(continues on next page)
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;
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.)
The skeleton app also has a default layout to help generate RSS.
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;
Layouts 215
CakePHP Book, Release 5.x
Elements
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 templates/element/ folder, and have the .php filename extension. They are output using the element
method of the view:
echo $this->element('helpbox');
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 same
way that Controller::set() in the controller works with template files). In the above example, the tem-
plates/element/helpbox.php file can use the $helptext variable:
// Inside templates/element/helpbox.php
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.
The View::element() method also supports options for the element. The options supported are ‘cache’ and ‘call-
backs’. 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 configu-
ration 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:
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.
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');
Elements 217
CakePHP Book, Release 5.x
are equivalent and will result in the same element being rendered.
For elements inside subfolder of a plugin (for example, plugins/Contacts/Template/element/sidebar/helpbox.php),
use the following:
echo $this->element('Contacts.sidebar/helpbox');
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 templates/Admin/element/. If such a file does not exist, it will be looked for in the
default location.
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:
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.
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;
Replacing the render method lets you take full control over how your content is rendered.
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 Ruby112 , where they fulfill a similar role and purpose.
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.
112 https://github.com/trailblazer/cells
Creating a Cell
To create a cell, define a class in src/View/Cell and a template in templates/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;
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 templates/cell/Inbox/display.php. This
will be our template for our new cell.
You can generate this stub code quickly using bake:
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;
Because Cells use the LocatorAwareTrait and ViewVarsTrait, they behave very much like a controller would.
We can use the fetchTable() and set() methods just like we would in a controller. In our template file, add the
following:
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:
The above will load the named cell class and execute the display() method. You can execute other methods using
the following:
If you need controller logic to decide which cells to load in a request, you can use the CellTrait in your controller to
enable the cell() method there:
namespace App\Controller;
use App\Controller\AppController;
use Cake\View\CellTrait;
// More code.
}
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:
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:
This will render the template matching the lowercased and underscored version of our action name like display.php.
Because cells use View to render templates, you can load additional cells within a cell template if required.
Note: Echoing a cell uses the PHP __toString() magic method which prevents PHP from showing the filename
and line number for any fatal errors raised. To obtain a meaningful error message, it is recommended to use the
Cell::render() method, for example <?= $cell->render() ?>.
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:
echo $cell;
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:
If a key is generated the underscored version of the cell class and template name will be used.
Note: A new View instance is used to render each cell and these new objects do not share context with the main template
/ layout. Each cell is self-contained and only has access to variables passed as arguments to the View::cell() call.
Creating a cell that renders a paginated result set can be done by leveraging a 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\Paging\NumericPaginator;
$this->set('favorites', $results);
}
}
The above cell would paginate the Messages model using scoped pagination parameters.
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;
protected $limit = 3;
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 options are handy when you want data available as properties allowing you to override default values.
Cells have their own context and their own View instance but Helpers loaded inside your AppView::initialize()
function are still loaded as usual.
Loading a specific Helper just for a specific cell can be done via the following example:
namespace App\View\Cell;
use Cake\View\Cell;
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, allowing you 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.
First ensure your theme plugin is loaded in your application’s bootstrap method. For example:
To use themes, set the theme name in your controller’s action or beforeRender() callback:
Theme template files need to be within a plugin with the same name. For example, the above theme would be found
in plugins/Modern/templates. It’s important to remember that CakePHP expects PascalCase plugin/theme names.
Beyond that, the folder structure within the plugins/Modern/templates folder is exactly the same as templates/.
For example, the view file for an edit action of a Posts controller would reside at plug-
ins/Modern/templates/Posts/edit.php. Layout files would reside in plugins/Modern/templates/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/templates/plugin/Cms/Tags/edit.php 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 templates/ folder. This way,
you can create master template files and simply override them on a case-by-case basis within your theme folder.
Theme Assets
Because themes are standard CakePHP plugins, they can include any necessary assets in their webroot direc-
tory. This allows for packaging and distribution of themes. Whilst in development, requests for theme as-
sets will be handled by CakeRoutingMiddlewareAssetMiddleware (which is loaded by default in cakephp/app
Application::middleware()). 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:
// and links to
plugins/PurpleCupcake/webroot/css/main.css
The JsonView and XmlView integration with CakePHP’s Content Type Negotiation features and let you create JSON
and XML responses.
These view classes are most commonly used alongside CakeControllerController::viewClasses().
There are two ways you can generate data views. The first is by using the serialize option, and the second is by
creating normal template files.
In your AppController or in an individual controller you can implement the viewClasses() method and provide
all of the views you want to support:
use Cake\View\JsonView;
use Cake\View\XmlView;
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.
The serialize option indicates which 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;
use Cake\View\JsonView;
namespace App\Controller;
use Cake\View\JsonView;
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.
You should use template files if you need to manipulate your view content before creating the final output. For example,
if we had articles with 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'));
}
}
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.
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 option.
The XmlView class supports the xmlOptions option that allows you to customize the options, such as tags or
attributes, used to generate XML.
An example of using XmlView would be to generate a sitemap.xml113 . This document type requires that you change
rootNode and set attributes. Attributes are defined using the @ prefix:
class JsonView
The JsonView class supports the jsonOptions option that allows you to customize the bit-mask used to generate
JSON. See the json_encode114 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:
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.
While you can use the viewClasses hook method most of the time, if you want total control over view class selection
you can directly choose the view class:
// src/Controller/VideosController.php
namespace App\Controller;
use App\Controller\AppController;
use Cake\Http\Exception\NotFoundException;
// Get data
$videos = $this->Videos->find('latest')->all();
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
BreadcrumbsHelper provides a way to easily deal with the creation and rendering of a breadcrumbs trail for your app.
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:
]);
// 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]],
]);
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.
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:
echo $this->Breadcrumbs->render(
['class' => 'breadcrumbs-trail'],
['separator' => '<i class="fa fa-angle-right"></i>']
);
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}}',
You can easily customize them using the setTemplates() method from the StringTemplateTrait:
$this->Breadcrumbs->setTemplates([
'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->setTemplates([
'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>',
],
]
);
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 rendered as
HTML attributes:
$this->Breadcrumbs->add(
'Products',
['controller' => 'products', 'action' => 'index'],
[
'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>
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);
Flash
FlashHelper provides a way to render flash messages that were set in $_SESSION by FlashComponent. FlashCom-
ponent and FlashHelper primarily use elements to render flash messages. Flash elements are found under the tem-
plates/element/flash directory. You’ll notice that CakePHP’s App template comes with three flash elements: suc-
cess.php, default.php, and error.php.
To render a flash message, you can simply use FlashHelper’s render() method in your template file:
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:
You can also override any of the options that were set in FlashComponent:
// In your Controller
$this->Flash->set('The user has been saved.', [
'element' => 'success'
]);
// In your template file: the flashy element file from the Company Plugin
<?= $this->Flash->render('flash', [
'element' => 'Company.flashy'
]);
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.
For more information about the available array options, please refer to the FlashComponent section.
If you have a Routing prefix configured, you can now have your Flash elements stored in tem-
plates/{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.
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 templates/element/flash/error.php file it will be used, just as with any Elements
and Views.
Form
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
• $context - The context for which the form is being defined. Can be an ORM entity, ORM resultset, Form
instance, array of metadata or 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.
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:
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 EntityContext115 ; this context class allows FormHelper to work
with results from the built-in ORM.
115 https://api.cakephp.org/5.x/class-Cake.View.Form.EntityContext.html
• An array containing the 'schema' key, will map to ArrayContext116 which allows you to create simple data
structures to build forms against.
• null will map to NullContext117 ; 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.
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:
Output:
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.php:
// Since $article->isNew() is false, we will get an edit form
<?= $this->Form->create($article) ?>
Output:
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']).
116 https://api.cakephp.org/5.x/class-Cake.View.Form.ArrayContext.html
117 https://api.cakephp.org/5.x/class-Cake.View.Form.NullContext.html
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 respec-
tively, 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 con-
trol’s HTML5 validity message. Default is true.
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.
A FormHelper’s values sources define where its rendered elements, such as input-tags, receive their values from.
The supported sources are context, data and query. You can use one or more sources by setting valueSources
option or by using setValuesSource(). Any widgets generated by FormHelper will gather their values from the
sources, in the order you setup.
By default FormHelper draws its values from data or context, i.e. it will fetch data from $request->getData()
or, if not present, from the active context’s data, that are the entity’s data in the case of EntityContext.
If however, you are building a form that needs to read from the query string, you can change where FormHelper reads
input data from:
// Same effect:
echo $this->Form
->setValueSources(['query', 'context'])
->create($articles, ['type' => 'get']);
When input data has to be processed by the entity, i.e. marshal transformations, table query result or entity computa-
tions, and displayed after one or multiple form submissions where request data is retained, you need to put context
first:
The value sources will be reset to the default ['data', 'context'] when end() is called.
By using the type option you can change the HTTP method a form will use:
Output:
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.
For example:
Output:
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.
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:
Output:
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:
echo $this->Form->create(null, [
'url' => 'https://www.google.com/search',
'type' => 'get',
]);
Output:
Use 'url' => false if you don’t want to output a URL as the form action.
Often models will have multiple validator sets, you can have FormHelper mark fields required based on the specific
validator your controller action is going to apply. For example, your Users table has specific validation rules that only
apply when an account is being registered:
echo $this->Form->create($user, [
'context' => ['validator' => 'register'],
]);
The above will use validation 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. FormHelper uses validators to
generate HTML5 required attributes, relevant ARIA attributes, and set error messages with the browser validator API118
. If you would like to disable HTML5 validation messages use:
$this->Form->setConfig('autoSetCustomValidity', false);
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\ContextInterface119 . 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:
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.
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 such as select, button, textarea.
118 https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation#Customized_error_messages
119 https://api.cakephp.org/5.x/interface-Cake.View.Form.ContextInterface.html
By default the control() method will employ the following widget templates:
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
date
datetime, timestamp
datetime-local
datetimefractional, timestampfractional
datetime-local
time
time
month
month
year
select with years
binary
file
The $options parameter allows you to choose a specific control type if you need to:
Tip: As a small subtlety, generating specific elements via the control() form method will always also gener-
ate 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 indicate
that it is required and not allowed to be empty. You can disable automatic required flagging using the 'required'
option:
To skip browser validation triggering for the whole form you can set option 'formnovalidate' => true for the
input button you generate using submit() or set 'novalidate' => true in options for create().
For example, let’s assume that your Users model includes fields for a username (varchar), password (varchar), approved
(datetime) and quote (text). You can use the control() method of the FormHelper to create appropriate controls for
all of these form fields:
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 an input of type "datetime-local"
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();
echo $this->Form->control('birth_date', [
'label' => 'Date of birth',
'min' => date('Y') - 70,
'max' => 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')->all());
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')->all());
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 camelCased120 format as follows:
$this->set('userGroups', $this->UserGroups->find('list')->all());
Note: You should not use FormHelper::control() to generate submit buttons. Use submit() instead.
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 matches the
conventions you use 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 converted into
DateTime objects when entities are marshalled.
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.
For example:
120 https://en.wikipedia.org/wiki/Camel_case#Variations_and_synonyms
Output:
• $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.
For example:
echo $this->Form->control('name', [
'label' => 'The User Alias'
]);
Output:
<div class="input">
<label for="name">The User Alias</label>
<input name="name" type="text" value="" id="name">
</div>
Alternatively, set this key to false to disable the generation of the label element.
For example:
Output:
<div class="input">
<input name="name" type="text" value="" id="name">
</div>
If the label is disabled, and a placeholder attribute is provided, the generated input will have aria-label set.
Set the label option 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.
For example:
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.
For example:
To override the model error messages use an array with the keys matching the original validation error messages.
For example:
$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.
To disable the HTML entity encoding for error messages only, the 'escape' sub key can be used:
$this->Form->control('name', [
'error' => ['escape' => false],
]);
• $options['nestedInput'] - Used with checkboxes and radio buttons. Controls whether the input element 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.
• $options['readonly'] - Set the field to readonly in form.
For example:
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 methods like
label() and error() to generate fully custom form layouts.
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). If no default value is provided, the column’s
default value will be used.
Example usage:
$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.
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.
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).
Will output:
echo $this->Form->password('password');
Will output:
echo $this->Form->hidden('id');
Will output:
Creating Textareas
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 generated.
Example:
• '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.
For example:
Output:
These controls share some commonalities and a few options and thus, they are all grouped in this subsection for easier
reference.
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:
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 speci-
fied. For checkboxes its value defaults to 0 and for radio buttons to ''.
Example of default output:
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>
<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
(continues on next page)
<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',
]);
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.:
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.:
Creating Checkboxes
echo $this->Form->checkbox('done');
Will output:
It is possible to specify the value of the checkbox by using the $options array.
For example:
Will output:
If you don’t want the FormHelper to create a hidden input use 'hiddenField'.
For example:
Will output:
Will output:
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.
For example:
echo $this->Form->radio(
'favorite_color',
[
['value' => 'r', 'text' => 'Red', 'style' => 'color:red;'],
['value' => 'u', 'text' => 'Blue', 'style' => 'color:blue;'],
['value' => 'g', 'text' => 'Green', 'style' => 'color:green;'],
]
);
Will output:
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:
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.
• $fieldName - A field name in the form 'fieldname' or 'related_entity.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.
• $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 pro-
vided, 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:
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>
For example:
echo $this->Form->select('field', [
'Value 1' => 'Label 1',
'Value 2' => 'Label 2',
'Value 3' => 'Label 3'
]);
Output:
<select name="field">
<option value="Value 1">Label 1</option>
<option value="Value 2">Label 2</option>
<option value="Value 3">Label 3</option>
</select>
If you would like to generate a select with optgroups, just pass data in hierarchical format (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>
$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>
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.
For example:
• 'multiple' - If set to true, the select picker will allow multiple selections.
For example:
$options = [
'Value 1' => 'Label 1',
'Value 2' => 'Label 2'
];
echo $this->Form->select('field', $options, [
'multiple' => 'checkbox'
]);
Output:
• '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.
For example:
$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>
$options = [
'Value 1' => 'Label 1',
'Value 2' => 'Label 2'
];
echo $this->Form->select('field', $options, [
'multiple' => 'checkbox',
'disabled' => ['Value 1']
]);
Output:
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:
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.
To prevent the submittedfile from being over-written as blank, remove it from $_accessible. Alternatively, you
can unset the index by using beforeMarshal:
{
if ($data['submittedfile'] === '') {
unset($data['submittedfile']);
}
}
Upon submission, file fields can be accessed though UploadedFileInterface objects on the request. To move
uploaded files to a permanent location, you can use:
$fileobject = $this->request->getData('submittedfile');
$destination = UPLOAD_DIRECTORY . $fileobject->getClientFilename();
Note: When using $this->Form->file(), remember to set the form encoding-type, by setting the 'type' option
to 'file' in $this->Form->create().
• $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 as well as any valid
HTML attributes.
This method will generate an input tag with type “datetime-local”.
For example
Output:
The value for the input can be any valid datetime string or DateTime instance.
For example
Output:
• $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 as well as any valid
HTML attributes.
This method will generate an input tag with type “date”.
For example
Output:
• $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 as well as any valid
HTML attributes.
This method will generate an input tag with type “time”.
For example
echo $this->Form->time('released');
Output:
• $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 as well as any valid
HTML attributes.
This method will generate an input tag with type “month”.
For example:
echo $this->Form->month('mob');
Will output:
• $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 as well as any valid
HTML attributes. Other valid options are:
– min: The lowest value to use in the year select picker.
echo $this->Form->year('purchased', [
'min' => 2000,
'max' => date('Y')
]);
<select name="purchased">
<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>
Creating Labels
echo $this->Form->label('name');
echo $this->Form->label('name', 'Your username');
Output:
<label for="name">Name</label>
<label for="name">Your username</label>
With the third parameter $options you can set the id or class:
Output:
FormHelper exposes a couple of methods that allow us to easily check for field errors and when necessary display
customized error messages.
Displaying Errors
The 'errorList' and 'errorItem' templates are used to format mutiple error messages per field.
Example:
if ($this->Form->isFieldError('ticket')) {
echo $this->Form->error('ticket', 'Completely custom error message!');
}
If you would click the Submit button of your form without providing a value for the Ticket field, your form would output:
Note: When using control(), errors are rendered by default, so you don’t need to use isFieldError() or call
error() manually.
Tip: If you use a certain model field to generate multiple form fields via control(), and you want the same valida-
tion error message displayed for each one, you will probably be better off defining a custom error message inside the
respective validator rules.
Cake\View\Helper\FormHelper::isFieldError(string $fieldName)
if ($this->Form->isFieldError('gender')) {
echo $this->Form->error('gender');
}
If the autoSetCustomValidity FormHelper option is set to true, error messages for the field’s required and notBlank
validation rules will be used in lieu of the default browser HTML5 required messages. Enabling the option will add
the onvalid and oninvalid event attributes to your fields, for example:
If you want to manually set those events with custom JavaScript, you can set the autoSetCustomValidity option to
false and use the special customValidityMessage template variable instead. This template variable is added when
a field is required:
// example template
[
'input' => '<input type="{{type}}" name="{{name}}" data-error-message="{
˓→{customValidityMessage}}" {{attrs}}/>',
You could then use JavaScript to set the onvalid and oninvalid events as you like.
• $caption - An optional string providing the button’s text caption or a path to an image. Defaults to 'Submit'.
• $options - An optional array including any of the Common Options For Specific Controls, or of the specific
submit options (see below) as well as any valid HTML attributes.
Creates an input element of submit type, with $caption as value. If the supplied $caption is a URL pointing
to an image (i.e. if the string contains ‘://’ or contains any of the extensions ‘.jpg, .jpe, .jpeg, .gif’), an image submit
button will be generated, using the specified image if it exists. If the first character is ‘/’ then the image path is relative
to webroot, else if the first character is not ‘/’ then the image path is relative to webroot/img.
By default it will use the following widget templates:
Will output:
You can pass a relative or absolute URL of an image to the caption parameter instead of the caption text:
echo $this->Form->submit('ok.png');
Will output:
Submit inputs are useful when you only need basic text or images. If you need more complex button content you should
use button().
Will output:
Cake\View\Helper\FormHelper::end($secureAttributes = [])
• $secureAttributes - Optional. Allows you to provide secure attributes which will be passed as HTML at-
tributes into the hidden input elements generated for the FormProtectionComponent.
The end() method closes and completes a form. Often, end() will only output a closing form tag, but using end()
is a good practice as it enables FormHelper to insert the hidden form elements that Cake\Controller\Component\
FormProtectionComponent requires:
If you need to add additional attributes to the generated hidden inputs you can use the $secureAttributes argument.
For example:
Will output:
<div style="display:none;">
<input type="hidden" name="_Token[fields]" data-type="hidden"
value="2981c38990f3f6ba935e6561dc77277966fabd6d%3AAddresses.id">
<input type="hidden" name="_Token[unlocked]" data-type="hidden"
value="address%7Cfirst_name">
</div>
Note: If you are using Cake\Controller\Component\SecurityComponent in your application you should always
end your forms with end().
• $title - Mandatory string providing the button’s text caption. By default not HTML encoded.
• $url - The URL of the form provided as a string or as array.
• $options - An optional array including any of the Common Options For Specific Controls, or of the specific
options (see below) as well as any valid HTML attributes.
Creates a <button> tag with a surrounding <form> element that submits via POST, by default. Also, by default, it
generates hidden input fields for the SecurityComponent.
Options for POST Button
• 'data' - Array with key/value to pass in hidden input.
• 'method' - Request method to use. E.g. set to 'delete' to simulate a HTTP/1.1 DELETE request. Defaults
to 'post'.
• 'form' - Array with any option that FormHelper::create() can take.
• Also, the postButton() method will accept the options which are valid for the button() method.
For example:
// In templates/Tickets/index.php
<?= $this->Form->postButton('Delete Record', ['controller' => 'Tickets', 'action' =>
˓→'delete', 5]) ?>
</div>
</form>
Since this method generates a form element, do not use this method in an already opened form. Instead use Cake\
View\Helper\FormHelper::submit() or Cake\View\Helper\FormHelper::button() to create buttons inside
opened forms.
Note: Be careful to not put a postLink inside an open form. Instead use the block option to buffer the form into a
view block
Like many helpers in CakePHP, FormHelper uses string templates to format the HTML it creates. While the default
templates are intended to be a reasonable set of defaults, you may need to customize the templates to suit your appli-
cation.
To change the templates when the helper is loaded you can set the 'templates' option when including the helper in
your controller:
// In a View class
$this->loadHelper('Form', [
'templates' => 'app_form',
]);
This would load the tags found in config/app_form.php. This file should contain an array of templates indexed by
name:
// in config/app_form.php
return [
'inputContainer' => '<div class="form-control">{{content}}</div>',
];
Any templates you define will replace the default ones included in the helper. Templates that are not replaced, will
continue to use the default values.
You can also change the templates at runtime using the setTemplates() method:
$myTemplates = [
'inputContainer' => '<div class="form-control">{{content}}</div>',
];
$this->Form->setTemplates($myTemplates);
Warning: Template strings containing a percentage sign (%) need special attention; you should prefix this character
with another percentage so it looks like %%. The reason is that internally templates are compiled to be used with
sprintf(). Example: '<div style="width:{{size}}%%">{{content}}</div>'
List of Templates
The list of default templates, their default format and the variables they expect can be found in the FormHelper API
documentation121 .
In addition to these templates, the control() method will attempt to use distinct templates for each control container.
For example, when creating a datetime control the datetimeContainer will be used if it is present. If that container
is missing the inputContainer template will be used.
For example:
Similar to controlling containers, the control() method will also attempt to use distinct templates for each form group.
A form group is a combo of label and control. For example, when creating a radio control the radioFormGroup will
be used if it is present. If that template is missing by default each set of label & input is rendered using the default
formGroup template.
For example:
121 https://api.cakephp.org/5.x/class-Cake.View.Helper.FormHelper.html#%24_defaultConfig
You can add additional template placeholders in custom templates, and populate those placeholders when generating
controls.
For example:
Output:
By default CakePHP nests checkboxes created via control() and radio buttons created by both control() and
radio() within label elements. This helps make it easier to integrate popular CSS frameworks. If you need to place
checkbox/radio inputs outside of the label you can do so by modifying the templates:
$this->Form->setTemplates([
'nestingLabel' => '{{hidden}}{{input}}<label{{attrs}}>{{text}}</label>',
'formGroup' => '{{input}}{{label}}',
]);
This will make radio buttons and checkboxes render outside of their labels.
• $fields - An array of fields to generate. Allows setting custom types, labels and other options for each specified
field.
• $options - Optional. An array of options. Valid keys are:
1. 'fieldset' - Set this to false to disable the fieldset. If empty, the fieldset will be enabled. Can also be
an array of parameters to be applied as HTML attributes to the fieldset tag.
2. legend - String used to customize the legend text. Set this to false to disable the legend for the generated
input set.
Generates a set of controls for the given context wrapped in a fieldset. You can specify the generated fields by
including them:
echo $this->Form->controls([
'name',
'email'
]);
You can customize the generated controls by defining additional options in the $fields parameter:
echo $this->Form->controls([
'name' => ['label' => 'custom label'],
]);
When customizing, $fields, you can use the $options parameter to control the generated legend/fieldset.
For example:
echo $this->Form->controls(
[
'name' => ['label' => 'custom label'],
],
[
'legend' => 'Update your post',
]
);
• $fields - Optional. An array of customizations for the fields that will be generated. Allows setting custom
types, labels and other options.
• $options - Optional. An array of options. Valid keys are:
1. 'fieldset' - Set this to false to disable the fieldset. If empty, the fieldset will be enabled. Can also be
an array of parameters to be applied as HTMl attributes to the fieldset tag.
2. legend - String used to customize the legend text. Set this to false to disable the legend for the generated
control set.
This method is closely related to controls(), however the $fields argument is defaulted to all fields in the current
top-level entity. To exclude specific fields from the generated controls, set them to false in the $fields parameter:
Creating forms for associated data is straightforward and is closely related to the paths in your entity’s data. Assuming
the following table relations:
• Authors HasOne Profiles
• Authors HasMany Articles
• Articles HasMany Comments
• Articles BelongsTo Authors
• Articles BelongsToMany Tags
If we were editing an article with its associations loaded we could create the following controls:
$this->Form->create($article);
// Article controls.
echo $this->Form->control('title');
The above controls could then be marshalled into a completed entity graph using the following code in your controller:
The above example shows an expanded example for belongs to many associations, with separate inputs for each entity
and join data record. You can also create a multiple select input for belongs to many associations:
You can add custom control widgets in CakePHP, and use them like any other control type. All of the core control types
are implemented as widgets, which means you can override any core widget with your own implementation as well.
Widget classes have a very simple required interface. They must implement the Cake\View\Widget\
WidgetInterface. This interface requires the render(array $data) and secureFields(array $data) meth-
ods to be implemented. The render() method expects an array of data to build the widget and is expected to return
a string of HTML for the widget. The secureFields() method expects an array of data as well and is expected to
return an array containing the list of fields to secure for this widget. If CakePHP is constructing your widget you can
expect to get a Cake\View\StringTemplate instance as the first argument, followed by any dependencies you define.
If we wanted to build an Autocomplete widget you could do the following:
namespace App\View\Widget;
use Cake\View\Form\ContextInterface;
use Cake\View\StringTemplate;
use Cake\View\Widget\WidgetInterface;
/**
* Constructor.
(continues on next page)
/**
* Methods that render the widget.
*
* @param array $data The data to build an input with.
* @param \Cake\View\Form\ContextInterface $context The current form context.
*
* @return string
*/
public function render(array $data, ContextInterface $context): string
{
$data += [
'name' => '',
];
return $this->_templates->format('autocomplete', [
'name' => $data['name'],
'attrs' => $this->_templates->formatAttributes($data, ['name'])
]);
}
Obviously, this is a very simple example, but it demonstrates how a custom widget could be built. This widget would
render the “autocomplete” string template, such as:
$this->Form->setTemplates([
'autocomplete' => '<input type="autocomplete" name="{{name}}" {{attrs}} />'
]);
For more information on string templates, see Customizing the Templates FormHelper Uses.
Using Widgets
You can load custom widgets when loading FormHelper or by using the addWidget() method. When loading
FormHelper, widgets are defined as a setting:
// In View class
$this->loadHelper('Form', [
'widgets' => [
'autocomplete' => ['Autocomplete'],
],
]);
If your widget requires other widgets, you can have FormHelper populate those dependencies by declaring them:
$this->loadHelper('Form', [
'widgets' => [
'autocomplete' => [
'App\View\Widget\AutocompleteWidget',
'text',
'label',
],
],
]);
In the above example, the autocomplete widget would depend on the text and label widgets. If your widget needs
access to the View, you should use the _view ‘widget’. When the autocomplete widget is created, it will be passed
the widget objects that are related to the text and label names. To add widgets using the addWidget() method
would look like:
// Using a classname.
$this->Form->addWidget(
'autocomplete',
['Autocomplete', 'text', 'label']
);
This will create the custom widget with a label and wrapping div just like controls() always does. Alternatively,
you can create just the control widget using the magic method:
Cake\Controller\Component\SecurityComponent offers several features that make your forms safer and more
secure. By simply including the SecurityComponent in your controller, you’ll automatically benefit from form
tampering-prevention features.
As mentioned previously when using SecurityComponent, you should always close your forms using end(). This will
ensure that the special _Token inputs are generated.
Cake\View\Helper\FormHelper::unlockField($name)
$this->Form->unlockField('id');
• $fields - Optional. An array containing the list of fields to use when generating the hash. If not provided, then
$this->fields will be used.
• $secureAttributes - Optional. An array of HTML attributes to be passed into the generated hidden input
elements.
Generates a hidden input field with a security hash based on the fields used in the form or an empty string when
secured forms are not in use. If $secureAttributes is set, these HTML attributes will be merged into the hidden
input tags generated for the SecurityComponent. This is especially useful to set HTML5 attributes like 'form'.
Html
The role of the HtmlHelper in CakePHP is to make HTML-related options easier, faster, and more resilient to change.
Using this helper will enable your application to be more light on its feet, and more flexible on where it is placed in
relation to the root of a domain.
Many HtmlHelper methods include a $attributes parameter, that allow you to tack on any extra attributes on your
tags. Here are a few examples of how to use the $attributes parameter:
The most important task the HtmlHelper accomplishes is creating well formed markup. This section will cover some
of the methods of the HtmlHelper and how to use them.
Cake\View\Helper\HtmlHelper::charset($charset=null)
Used to create a meta tag specifying the document’s character. The default value is UTF-8. An example use:
echo $this->Html->charset();
Will output:
Alternatively,
echo $this->Html->charset('ISO-8859-1');
Will output:
Creates a link(s) to a CSS style-sheet. If the block option is set to true, the link tags are added to the css block which
you can print inside the head tag of the document.
You can use the block option to control which block the link element will be appended to. By default it will append
to the css block.
If key ‘rel’ in $options array is set to ‘import’ the stylesheet will be imported.
This method of CSS inclusion assumes that the CSS file specified resides inside the webroot/css directory if path
doesn’t start with a ‘/’.
echo $this->Html->css('forms');
Will output:
Will output:
You can include CSS files from any loaded plugin using plugin syntax. To include plug-
ins/DebugKit/webroot/css/toolbar.css you could use the following:
echo $this->Html->css('DebugKit.toolbar.css');
If you want to include a CSS file which shares a name with a loaded plugin you can do the following. For example if
you had a Blog plugin, and also wanted to include webroot/css/Blog.common.css, you would:
Builds CSS style definitions based on the keys and values of the array passed to the method. Especially handy if your
CSS file is dynamic.
echo $this->Html->style([
'background' => '#633',
'border-bottom' => '1px solid #000',
'padding' => '10px'
]);
Will output:
This method is handy for linking to external resources like RSS/Atom feeds and favicons. Like css(), you can specify
whether or not you’d like this tag to appear inline or appended to the meta block by setting the ‘block’ key in the
$attributes parameter to true, ie - ['block' => true].
If you set the “type” attribute using the $attributes parameter, CakePHP contains a few shortcuts:
<?= $this->Html->meta(
'favicon.ico',
'/favicon.ico',
['type' => 'icon']
);
?>
// Output (line breaks added)
(continues on next page)
<?= $this->Html->meta(
'Comments',
'/comments/index.rss',
['type' => 'rss']
);
?>
// Output (line breaks added)
<link
href="http://example.com/comments/index.rss"
title="Comments"
type="application/rss+xml"
rel="alternate"
/>
This method can also be used to add the meta keywords and descriptions. Example:
<?= $this->Html->meta(
'keywords',
'enter any meta keyword here'
);
?>
// Output
<meta name="keywords" content="enter any meta keyword here" />
<?= $this->Html->meta(
'description',
'enter any meta description here'
);
?>
// Output
<meta name="description" content="enter any meta description here" />
In addition to making predefined meta tags, you can create link elements:
<?= $this->Html->meta([
'link' => 'http://example.com/manifest',
'rel' => 'manifest'
]);
(continues on next page)
Any attributes provided to meta() when called this way will be added to the generated link tag.
Linking to Images
Creates a formatted image tag. The path supplied should be relative to webroot/img/.
Will output:
To create an image link specify the link destination using the url option in $attributes.
echo $this->Html->image("recipes/6.jpg", [
"alt" => "Brownies",
'url' => ['controller' => 'Recipes', 'action' => 'view', 6]
]);
Will output:
<a href="/recipes/view/6">
<img src="/img/recipes/6.jpg" alt="Brownies" />
</a>
If you are creating images in emails, or want absolute paths to images you can use the fullBase option:
Will output:
You can include image files from any loaded plugin using plugin syntax. To include plug-
ins/DebugKit/webroot/img/icon.png You could use the following:
echo $this->Html->image('DebugKit.icon.png');
If you want to include an image file which shares a name with a loaded plugin you can do the following. For example
if you had a Blog plugin, and also wanted to include webroot/img/Blog.icon.png, you would:
If you would like the prefix of the URL to not be /img, you can override this setting by specifying the prefix in the
$options array
Will output:
Creating Links
General purpose method for creating HTML links. Use $options to specify attributes for the element and whether or
not the $title should be escaped.
echo $this->Html->link(
'Enter',
'/pages/home',
['class' => 'button', 'target' => '_blank']
);
Will output:
echo $this->Html->link(
'Dashboard',
['controller' => 'Dashboards', 'action' => 'index', '_full' => true]
);
Will output:
<a href="http://www.yourdomain.com/dashboards/index">Dashboard</a>
echo $this->Html->link(
'Delete',
['controller' => 'Recipes', 'action' => 'delete', 6],
['confirm' => 'Are you sure you wish to delete this recipe?']
);
Will output:
<a href="/recipes/delete/6"
onclick="return confirm(
'Are you sure you wish to delete this recipe?'
);">
Delete
</a>
Will output:
HTML special characters in $title will be converted to HTML entities. To disable this conversion, set the escape
option to false in the $options array.
echo $this->Html->link(
$this->Html->image("recipes/6.jpg", ["alt" => "Brownies"]),
"recipes/view/6",
['escape' => false]
);
Will output:
<a href="/recipes/view/6">
<img src="/img/recipes/6.jpg" alt="Brownies" />
</a>
Setting escape to false will also disable escaping of attributes of the link. You can use the option escapeTitle to
disable just escaping of title and not the attributes.
echo $this->Html->link(
$this->Html->image('recipes/6.jpg', ['alt' => 'Brownies']),
'recipes/view/6',
['escapeTitle' => false, 'title' => 'hi "howdy"']
);
Will output:
Also check Cake\View\Helper\UrlHelper::build() method for more examples of different types of URLs.
Cake\View\Helper\HtmlHelper::linkFromPath(string $title, string $path, array $params = [], array $options
= [])
If you want to use route path strings, you can do that using this method:
Options:
• type Type of media element to generate, valid values are “audio” or “video”. If type is not provided media type
is guessed based on file’s mime type.
• text Text to include inside the video tag
• pathPrefix Path prefix to use for relative URLs, defaults to ‘files/’
• fullBase If provided the src attribute will get a full address including domain name
Returns a formatted audio/video tag:
// Output
<audio src="/files/audio.mp3"></audio>
<?= $this->Html->media('video.mp4', [
'fullBase' => true,
'text' => 'Fallback text'
]) ?>
// Output
<video src="http://www.somehost.com/files/video.mp4">Fallback text</video>
<?= $this->Html->media(
['video.mp4', ['src' => 'video.ogg', 'type' => "video/ogg; codecs='theora, vorbis'
˓→"]],
['autoplay']
) ?>
// Output
<video autoplay="autoplay">
<source src="/files/video.mp4" type="video/mp4"/>
<source src="/files/video.ogg" type="video/ogg;
codecs='theora, vorbis'"/>
</video>
You can use $options to set additional properties to the generated script tag. If an array of script tags is used, the
attributes will be applied to all of the generated script tags.
This method of JavaScript file inclusion assumes that the JavaScript file specified resides inside the webroot/js direc-
tory:
echo $this->Html->script('scripts');
Will output:
<script src="/js/scripts.js"></script>
You can link to files with absolute paths as well to link files that are not in webroot/js:
echo $this->Html->script('/otherdir/script_file');
echo $this->Html->script('https://code.jquery.com/jquery.min.js');
Will output:
<script src="https://code.jquery.com/jquery.min.js"></script>
Will output:
<script src="/js/jquery.js"></script>
<script src="/js/wysiwyg.js"></script>
<script src="/js/scripts.js"></script>
You can append the script tag to a specific block using the block option:
In your layout you can output all the script tags added to ‘scriptBottom’:
echo $this->fetch('scriptBottom');
You can include script files from any loaded plugin using plugin syntax. To include plug-
ins/DebugKit/webroot/js/toolbar.js You could use the following:
echo $this->Html->script('DebugKit.toolbar.js');
If you want to include a script file which shares a name with a loaded plugin you can do the following. For example if
you had a Blog plugin, and also wanted to include webroot/js/Blog.plugins.js, you would:
To generate Javascript blocks from PHP view code, you can use one of the script block methods. Scripts can either be
output in place, or buffered into a block:
Cake\View\Helper\HtmlHelper::scriptStart($options = [])
Cake\View\Helper\HtmlHelper::scriptEnd()
You can use the scriptStart() method to create a capturing block that will output into a <script> tag. Captured
script snippets can be output inline, or buffered into a block:
Once you have buffered javascript, you can output it as you would any other View Block:
// In your layout
echo $this->fetch('script');
$list = [
'Languages' => [
'English' => [
'American',
'Canadian',
'British',
],
'Spanish',
'German',
]
];
echo $this->Html->nestedList($list);
Output:
Output:
<tr>
<th>Date</th>
<th>Title</th>
<th>Active</th>
</tr>
echo $this->Html->tableHeaders(
['Date', 'Title','Active'],
['class' => 'status'],
['class' => 'product_table']
);
Output:
<tr class="status">
<th class="product_table">Date</th>
<th class="product_table">Title</th>
<th class="product_table">Active</th>
</tr>
You can set attributes per column, these are used instead of the defaults provided in the $thOptions:
echo $this->Html->tableHeaders([
'id',
['Name' => ['class' => 'highlight']],
(continues on next page)
Output:
<tr>
<th>id</th>
<th class="highlight">Name</th>
<th class="sortable">Date</th>
</tr>
Creates table cells, in rows, assigning <tr> attributes differently for odd- and even-numbered rows. Wrap a single table
cell within an [] for specific <td>-attributes.
echo $this->Html->tableCells([
['Jul 7th, 2007', 'Best Brownies', 'Yes'],
['Jun 21st, 2007', 'Smart Cookies', 'Yes'],
['Aug 1st, 2006', 'Anti-Java Cake', 'No'],
]);
Output:
echo $this->Html->tableCells([
['Jul 7th, 2007', ['Best Brownies', ['class' => 'highlight']] , 'Yes'],
['Jun 21st, 2007', 'Smart Cookies', 'Yes'],
['Aug 1st, 2006', 'Anti-Java Cake', ['No', ['id' => 'special']]],
]);
Output:
<tr>
<td>
Jul 7th, 2007
</td>
<td class="highlight">
Best Brownies
</td>
<td>
Yes
</td>
</tr>
<tr>
(continues on next page)
echo $this->Html->tableCells(
[
['Red', 'Apple'],
['Orange', 'Orange'],
['Yellow', 'Banana'],
],
['class' => 'darker']
);
Output:
<tr class="darker"><td>Red</td><td>Apple</td></tr>
<tr><td>Orange</td><td>Orange</td></tr>
<tr class="darker"><td>Yellow</td><td>Banana</td></tr>
Cake\View\Helper\HtmlHelper::setTemplates(array $templates)
You can load a configuration file containing templates using the templater directly:
<?php
return [
'javascriptlink' => '<script src="{{url}}" type="text/javascript"{{attrs}}></script>'
];
Warning: Template strings containing a percentage sign (%) need special attention, you should prefix this character
with another percentage so it looks like %%. The reason is that internally templates are compiled to be used with
sprintf(). Example: <div style="width:{{size}}%%">{{content}}</div>
Number
The NumberHelper contains convenient methods that enable display numbers in common formats in your views. These
methods include ways to format currency, percentages, data sizes, format numbers to specific precisions and also to
give you more flexibility with formatting numbers.
All of these functions return the formatted number; they do not automatically echo the output into the view.
This method is used to display a number in common currency formats (EUR, GBP, USD), based on the 3-letter ISO
4217 currency code. Usage in a view looks like:
// Called as NumberHelper
echo $this->Number->currency($value, $currency);
// Called as Number
echo Number::currency($value, $currency);
The first parameter, $value, should be a floating point number that represents the amount of money you are expressing.
The second parameter is a string used to choose a predefined currency formatting scheme:
The third parameter is an array of options for further defining the output. The following options are available:
Option Description
before Text to display before the rendered number.
after Text to display after the rendered number.
zero The text to use for zero values; can be a string or a number. ie. 0, ‘Free!’.
places Number of decimal places to use, ie. 2
precision Maximal number of decimal places to use, ie. 2
locale The locale name to use for formatting number, ie. “fr_FR”.
fractionSymbol String to use for fraction numbers, ie. ‘ cents’.
fractionPosition Either ‘before’ or ‘after’ to place the fraction symbol.
pattern An ICU number pattern to use for formatting the number ie. #,###.00
useIntlCode Set to true to replace the currency symbol with the international currency code.
If $currency value is null, the default currency will be retrieved from Cake\I18n\Number::defaultCurrency().
To format currencies in an accounting format you should set the currency format:
Number::setDefaultCurrencyFormat(Number::FORMAT_CURRENCY_ACCOUNTING);
Cake\View\Helper\NumberHelper::setDefaultCurrency($currency)
Setter for the default currency. This removes the need to always pass the currency to Cake\I18n\
Number::currency() and change all currency outputs by setting other default. If $currency is set to null, it
will clear the currently stored value.
Cake\View\Helper\NumberHelper::getDefaultCurrency()
Getter for the default currency. If default currency was set earlier using setDefaultCurrency(), then that value will
be returned. By default, it will retrieve the intl.default_locale ini value if set and 'en_US' if not.
This method displays a number with the specified amount of precision (decimal places). It will round in order to
maintain the level of precision defined.
// Called as NumberHelper
echo $this->Number->precision(456.91873645, 2);
// Outputs
456.92
// Called as Number
echo Number::precision(456.91873645, 2);
Formatting Percentages
Option Description
multiply Boolean to indicate whether the value has to be multiplied by 100. Useful for decimal percentages.
Like Cake\I18n\Number::precision(), this method formats a number according to the supplied precision (where
numbers are rounded to meet the given precision). This method also expresses the number as a percentage and appends
the output with a percent sign.
Cake\View\Helper\NumberHelper::toReadableSize(string $size)
This method formats data sizes in human readable forms. It provides a shortcut way to convert bytes to KB, MB, GB,
and TB. The size is displayed with a two-digit precision level, according to the size of data supplied (i.e. higher sizes
are expressed in larger terms):
// Called as NumberHelper
echo $this->Number->toReadableSize(0); // 0 Byte
echo $this->Number->toReadableSize(1024); // 1 KB
echo $this->Number->toReadableSize(1321205.76); // 1.26 MB
echo $this->Number->toReadableSize(5368709120); // 5 GB
// Called as Number
echo Number::toReadableSize(0); // 0 Byte
echo Number::toReadableSize(1024); // 1 KB
echo Number::toReadableSize(1321205.76); // 1.26 MB
echo Number::toReadableSize(5368709120); // 5 GB
Formatting Numbers
This method gives you much more control over the formatting of numbers for use in your views (and is used as the
main method by most of the other NumberHelper methods). Using this method might looks like:
// Called as NumberHelper
$this->Number->format($value, $options);
// Called as Number
Number::format($value, $options);
The $value parameter is the number that you are planning on formatting for output. With no $options supplied, the
number 1236.334 would output as 1,236. Note that the default precision is zero decimal places.
The $options parameter is where the real magic for this method resides.
• If you pass an integer then this becomes the amount of precision or places for the function.
• If you pass an associated array, you can use the following keys:
Option Description
places Number of decimal places to use, ie. 2
precision Maximum number of decimal places to use, ie. 2
pattern An ICU number pattern to use for formatting the number ie. #,###.00
locale The locale name to use for formatting number, ie. “fr_FR”.
before Text to display before the rendered number.
after Text to display after the rendered number.
Example:
// Called as NumberHelper
echo $this->Number->format('123456.7890', [
'places' => 2,
'before' => '¥ ',
'after' => ' !'
]);
// Output '¥ 123,456.79 !'
echo $this->Number->format('123456.7890', [
'locale' => 'fr_FR'
]);
// Output '123 456,79 !'
// Called as Number
echo Number::format('123456.7890', [
'places' => 2,
'before' => '¥ ',
'after' => ' !'
]);
// Output '¥ 123,456.79 !'
echo Number::format('123456.7890', [
(continues on next page)
echo Number::ordinal(1);
// Output '1st'
echo Number::ordinal(2);
// Output '2nd'
echo Number::ordinal(2, [
'locale' => 'fr_FR'
]);
// Output '2e'
echo Number::ordinal(410);
// Output '410th'
Format Differences
// Called as NumberHelper
$this->Number->formatDelta($value, $options);
// Called as Number
Number::formatDelta($value, $options);
The $value parameter is the number that you are planning on formatting for output. With no $options supplied, the
number 1236.334 would output as 1,236. Note that the default precision is zero decimal places.
The $options parameter takes the same keys as Number::format() itself:
Option Description
places Number of decimal places to use, ie. 2
precision Maximum number of decimal places to use, ie. 2
locale The locale name to use for formatting number, ie. “fr_FR”.
before Text to display before the rendered number.
after Text to display after the rendered number.
Example:
// Called as NumberHelper
echo $this->Number->formatDelta('123456.7890', [
'places' => 2,
'before' => '[',
'after' => ']'
]);
// Output '[+123,456.79]'
// Called as Number
echo Number::formatDelta('123456.7890', [
'places' => 2,
'before' => '[',
'after' => ']'
]);
// Output '[+123,456.79]'
Paginator
The PaginatorHelper is used to output pagination controls such as page numbers and next/previous links.
See also Pagination for information on how to create paginated datasets and do paginated queries.
Cake\View\Helper\PaginatorHelper::setPaginated($paginated, $options)
By default the helper uses the first instance of Cake\Datasource\Paging\PaginatedInterface it finds in the view
variables. (Generally the result of Controller::paginate()).
You can use PaginatorHelper::setPaginated() to explicitly set the paginated resultset that the helper should use.
PaginatorHelper Templates
Internally PaginatorHelper uses a series of simple HTML templates to generate markup. You can modify these tem-
plates to customize the HTML generated by the PaginatorHelper.
Templates use {{var}} style placeholders. It is important to not add any spaces around the {{}} or the replacements
will not work.
When adding the PaginatorHelper in your controller, you can define the ‘templates’ setting to define a template file to
load. This allows you to customize multiple templates and keep your code DRY:
// In your AppView.php
public function initialize(): void
{
...
$this->loadHelper('Paginator', ['templates' => 'paginator-templates']);
}
This will load the file located at config/paginator-templates.php. See the example below for how the file should look
like. You can also load templates from a plugin using plugin syntax:
// In your AppView.php
public function initialize(): void
{
...
$this->loadHelper('Paginator', ['templates' => 'MyPlugin.paginator-templates']);
}
Whether your templates are in the primary application or a plugin, your templates file should look something like:
return [
'number' => '<a href="{{url}}">{{text}}</a>',
];
Cake\View\Helper\PaginatorHelper::setTemplates($templates)
This method allows you to change the templates used by PaginatorHelper at runtime. This can be useful when you want
to customize templates for a particular method call:
// Change a template
$this->Paginator->setTemplates([
'number' => '<em><a href="{{url}}">{{text}}</a></em>'
]);
Warning: Template strings containing a percentage sign (%) need special attention, you should prefix this character
with another percentage so it looks like %%. The reason is that internally templates are compiled to be used with
sprintf(). Example: ‘<div style=”width:{{size}}%%”>{{content}}</div>’
Template Names
Parameters
• $key (string) – The name of the column that the recordset should be sorted.
• $title (string) – Title for the link. If $title is null, $key will be used converted to “Title
Case” format and used as the title.
• $options (array) – Options for sorting link.
Generates a sorting link. Sets querystring parameters for the sort and direction. Links will default to sorting by asc.
After the first click, links generated with sort() will handle direction switching automatically. If the resultset is sorted
‘asc’ by the specified key the returned link will sort by ‘desc’. Uses the sort, sortAsc, sortDesc, sortAscLocked
and sortDescLocked templates.
Accepted keys for $options:
• escape Whether you want the contents HTML entity encoded, defaults to true.
• direction The default direction to use when this link isn’t active.
• lock Lock direction. Will only use the default direction then, defaults to false.
Assuming you are paginating some posts, and are on page one:
echo $this->Paginator->sort('user_id');
Output:
You can use the title parameter to create custom text for your link:
Output:
If you are using HTML like images in your links remember to set escaping off:
echo $this->Paginator->sort(
'user_id',
'<em>User account</em>',
['escape' => false]
);
Output:
The direction option can be used to set the default direction for a link. Once a link is active, it will automatically switch
directions like normal:
Output:
The lock option can be used to lock sorting into the specified direction:
Cake\View\Helper\PaginatorHelper::numbers($options = [])
Returns a set of numbers for the paged result set. Uses a modulus to decide how many numbers to show on each side
of the current page By default 8 links on either side of the current page will be created if those pages exist. Links will
not be generated for pages that do not exist. The current page is also not a link. The number, current and ellipsis
templates will be used.
Supported options are:
• before Content to be inserted before the numbers.
• after Content to be inserted after the numbers.
• modulus how many numbers to include on either side of the current page, defaults to 8.
• first Whether you want first links generated, set to an integer to define the number of ‘first’ links to generate.
Defaults to false. If a string is set a link to the first page will be generated with the value as the title:
• last Whether you want last links generated, set to an integer to define the number of ‘last’ links to generate.
Defaults to false. Follows the same logic as the first option. There is a last() method to be used separately
as well if you wish.
While this method allows a lot of customization for its output. It is also ok to just call the method without any parameters.
echo $this->Paginator->numbers();
Using the first and last options you can create links to the beginning and end of the page set. The following would
create a set of page links that include links to the first 2 and last 2 pages in the paged results:
In addition to generating links that go directly to specific page numbers, you’ll often want links that go to the previous
and next links, first and last pages in the paged data set.
Cake\View\Helper\PaginatorHelper::prev($title = '<< Previous', $options = [])
Parameters
• $title (string) – Title for the link.
• $options (mixed) – Options for pagination link.
Generates a link to the previous page in a set of paged records. Uses the prevActive and prevDisabled
templates.
$options supports the following keys:
• escape Whether you want the contents HTML entity encoded, defaults to true.
• disabledTitle The text to use when the link is disabled. Defaults to the $title parameter.
A simple example would be:
If you were currently on the second page of posts, you would get the following:
<li class="prev">
<a rel="prev" href="/posts/index?page=1&sort=title&order=desc">
<< previous
</a>
</li>
The above creates a single link for the first page. Will output nothing if you are on the first page. You can also
use an integer to indicate how many first paging links you want generated:
echo $this->Paginator->first(3);
The above will create links for the first 3 pages, once you get to the third or greater page. Prior to that nothing
will be output. Uses the first template.
The options parameter accepts the following:
• escape Whether or not the text should be escaped. Set to false if your content contains HTML.
Cake\View\Helper\PaginatorHelper::last($last = 'last >>', $options = [])
This method works very much like the first() method. It has a few differences though. It will not generate
any links if you are on the last page for a string values of $last. For an integer value of $last no links will be
generated once the user is inside the range of last pages. Uses the last template.
PaginatorHelper can be used to create pagination link tags in your page <head> elements:
Cake\View\Helper\PaginatorHelper::current()
Gets the current page of the recordset:
Cake\View\Helper\PaginatorHelper::hasPrev()
Returns true if the given result set is not at the first page.
Cake\View\Helper\PaginatorHelper::hasPage(int $page = 1)
Returns true if the given result set has the page number given by $page.
Cake\View\Helper\PaginatorHelper::total()
Returns the total number of pages for the provided model.
Returns a counter string for the paged result set. Using a provided format string and a number of options you can create
localized and application specific indicators of where a user is in the paged data set. Uses the counterRange, and
counterPages templates.
Supported formats are ‘range’, ‘pages’ and custom. Defaults to pages which would output like ‘1 of 10’. In the custom
mode the supplied string is parsed and tokens are replaced with actual values. The available tokens are:
• {{page}} - the current page displayed.
• {{pages}} - total number of pages.
• {{current}} - current number of records being shown.
• {{count}} - the total number of records in the result set.
• {{start}} - number of the first record being displayed.
• {{end}} - number of the last record being displayed.
• {{model}} - The pluralized human form of the model name. If your model was ‘RecipePage’, {{model}} would
be ‘recipe pages’.
You could also supply only a string to the counter method using the tokens available. For example:
echo $this->Paginator->counter(
'Page {{page}} of {{pages}}, showing {{current}} records out of
{{count}} total, starting on record {{start}}, ending on {{end}}'
);
echo $this->Paginator->counter('range');
By default returns a full pagination URL string for use in non-standard contexts (i.e. JavaScript).
Cake\View\Helper\PaginatorHelper::options($options = [])
Sets all the options for the PaginatorHelper. Supported options are:
• url The URL of the paginating action.
The option allows your to set/override any element for URLs generated by the helper:
$this->Paginator->options([
'url' => [
'lang' => 'en',
'?' => [
'sort' => 'email',
'direction' => 'desc',
'page' => 6,
],
]
]);
The example above adds the en route parameter to all links the helper will generate. It will also create links with
specific sort, direction and page values. By default PaginatorHelper will merge in all of the current passed
arguments and query string parameters.
• escape Defines if the title field for links should be HTML escaped. Defaults to true.
Example Usage
It’s up to you to decide how to show records to the user, but most often this will be done inside HTML tables. The
examples below assume a tabular layout, but the PaginatorHelper available in views doesn’t always need to be restricted
as such.
See the details on PaginatorHelper122 in the API. As mentioned, the PaginatorHelper also offers sorting features which
can be integrated into your table column headers:
The links output from the sort() method of the PaginatorHelper allow users to click on table headers to toggle the
sorting of the data by a given field.
It is also possible to sort a column based on associations:
<table>
<tr>
<th><?= $this->Paginator->sort('title', 'Title') ?></th>
<th><?= $this->Paginator->sort('Authors.name', 'Author') ?></th>
</tr>
<?php foreach ($recipes as $recipe): ?>
<tr>
<td><?= h($recipe->title) ?> </td>
<td><?= h($recipe->name) ?> </td>
</tr>
<?php endforeach; ?>
</table>
Note: Sorting by columns in associated models requires setting these in the PaginationComponent::paginate
property. Using the example above, the controller handling the pagination would need to set its sortableFields key
as follows:
$this->paginate = [
'sortableFields' => [
'Posts.title',
'Authors.name',
],
];
122 https://api.cakephp.org/5.x/class-Cake.View.Helper.PaginatorHelper.html
For more information on using the sortableFields option, please see Control which Fields Used for Ordering.
The final ingredient to pagination display in views is the addition of page navigation, also supplied by the Pagination-
Helper:
The wording output by the counter() method can also be customized using special markers:
<?= $this->Paginator->counter([
'format' => 'Page {{page}} of {{pages}}, showing {{current}} records out of
{{count}} total, starting on record {{start}}, ending on {{end}}'
]) ?>
If you are paginating multiple queries you’ll need to use PaginatorHelper::setPaginated() first before calling
other methods of the helper, so that they generate expected output.
PaginatorHelper will automatically use the scope defined in when the query was paginated. To set additional URL
parameters for multiple pagination you can include the scope names in options():
$this->Paginator->options([
'url' => [
// Additional URL parameters for the 'articles' scope
'articles' => [
'?' => ['articles' => 'yes']
],
// Additional URL parameters for the 'comments' scope
'comments' => [
'articleId' => 1234,
],
],
]);
Text
The TextHelper contains methods to make text more usable and friendly in your views. It aids in enabling links,
formatting URLs, creating excerpts of text around chosen words or phrases, highlighting key words in blocks of text,
and gracefully truncating long stretches of text.
Adds links to the well-formed email addresses in $text, according to any options defined in $options (see
HtmlHelper::link()).
Output:
This method automatically escapes its input. Use the escape option to disable this if necessary.
Linking URLs
Same as autoLinkEmails(), only this method searches for strings that start with https, http, ftp, or nntp and links
them appropriately.
This method automatically escapes its input. Use the escape option to disable this if necessary.
Performs the functionality in both autoLinkUrls() and autoLinkEmails() on the supplied $text. All URLs and
emails are linked appropriately given the supplied $options.
This method automatically escapes its input. Use the escape option to disable this if necessary.
Cake\View\Helper\TextHelper::autoParagraph(string $text)
Adds proper <p> around text where double-line returns are found, and <br> where single-line returns are found.
contact [email protected]';
$formattedText = $this->Text->autoParagraph($myText);
Output:
Highlighting Substrings
Highlights $needle in $haystack using the $options['format'] string specified or a default string.
Options:
• format string - The piece of HTML with the phrase that will be highlighted
• html bool - If true, will ignore any HTML tags, ensuring that only the correct text is highlighted
Example:
// Called as TextHelper
echo $this->Text->highlight(
$lastSentence,
'using',
['format' => '<span class="highlight">\1</span>']
);
// Called as Text
use Cake\Utility\Text;
echo Text::highlight(
$lastSentence,
'using',
['format' => '<span class="highlight">\1</span>']
);
Output:
Removing Links
Cake\View\Helper\TextHelper::stripLinks($text)
Truncating Text
If $text is longer than $length, this method truncates it at $length and adds a suffix consisting of 'ellipsis',
if defined. If 'exact' is passed as false, the truncation will occur at the first whitespace after the point at which
$length is exceeded. If 'html' is passed as true, HTML tags will be respected and will not be cut off.
$options is used to pass all extra parameters, and has the following possible keys by default, all of which are optional:
[
'ellipsis' => '...',
'exact' => true,
'html' => false
]
Example:
// Called as TextHelper
echo $this->Text->truncate(
'The killer crept forward and tripped on the rug.',
22,
[
'ellipsis' => '...',
'exact' => false
]
);
// Called as Text
use Cake\Utility\Text;
echo Text::truncate(
'The killer crept forward and tripped on the rug.',
22,
[
'ellipsis' => '...',
'exact' => false
]
);
Output:
If $text is longer than $length, this method removes an initial substring with length consisting of the difference and
prepends a prefix consisting of 'ellipsis', if defined. If 'exact' is passed as false, the truncation will occur at
the first whitespace prior to the point at which truncation would otherwise take place.
$options is used to pass all extra parameters, and has the following possible keys by default, all of which are optional:
[
'ellipsis' => '...',
'exact' => true
]
Example:
$sampleText = 'I packed my bag and in it I put a PSP, a PS3, a TV, ' .
'a C# program that can divide by zero, death metal t-shirts'
// Called as TextHelper
echo $this->Text->tail(
$sampleText,
70,
[
'ellipsis' => '...',
'exact' => false
]
);
// Called as Text
use Cake\Utility\Text;
echo Text::tail(
$sampleText,
70,
[
'ellipsis' => '...',
'exact' => false
]
);
Output:
...a TV, a C# program that can divide by zero, death metal t-shirts
Extracting an Excerpt
Extracts an excerpt from $haystack surrounding the $needle with a number of characters on each side determined
by $radius, and prefix/suffix with $ellipsis. This method is especially handy for search results. The query string
or keywords can be shown within the resulting document.
// Called as TextHelper
echo $this->Text->excerpt($lastParagraph, 'method', 50, '...');
// Called as Text
use Cake\Utility\Text;
Output:
Creates a comma-separated list where the last two items are joined with ‘and’:
// Called as TextHelper
echo $this->Text->toList($colors);
// Called as Text
use Cake\Utility\Text;
echo Text::toList($colors);
Output:
Time
The TimeHelper allows for the quick processing of time related information. The TimeHelper has two main tasks that
it can perform:
1. It can format time strings.
2. It can test time.
A common use of the TimeHelper is to offset the date and time to match a user’s time zone. Lets use a forum as an ex-
ample. Your forum has many users who may post messages at any time from any part of the world. A way to manage the
time is to save all dates and times as GMT+0 or UTC. Uncomment the line date_default_timezone_set('UTC');
in config/bootstrap.php to ensure your application’s time zone is set to GMT+0.
Next add a time zone field to your users table and make the necessary modifications to allow your users to set their time
zone. Now that we know the time zone of the logged in user we can correct the date and time on our posts using the
TimeHelper:
echo $this->Time->format(
$post->created,
\IntlDateFormatter::FULL,
false,
$user->time_zone
);
// Will display 'Saturday, August 22, 2011 at 11:53:00 PM GMT'
// for a user in GMT+0. While displaying,
// 'Saturday, August 22, 2011 at 03:53 PM GMT-8:00'
// for a user in GMT-8
Most of TimeHelper’s features are intended as backwards compatible interfaces for applications that are upgrading
from older versions of CakePHP. Because the ORM returns Cake\I18n\Time instances for every timestamp and
datetime column, you can use the methods there to do most tasks. For example, to read about the accepted formatting
strings take a look at the Cake\I18n\Time::i18nFormat()123 method.
Url
The UrlHelper helps you to generate URLs from your other helpers. It also gives you a single place to customize how
URLs are generated by overriding the core helper with an application one. See the Aliasing Helpers section for how to
do this.
Generating URLs
Returns a URL pointing to a combination of controller and action. If $url is empty, it returns the REQUEST_URI,
otherwise it generates the URL for the controller and action combo. If fullBase is true, the full base URL will be
prepended to the result:
echo $this->Url->build([
'controller' => 'Posts',
'action' => 'view',
'bar',
]);
// Output
/posts/view/bar
123 https://api.cakephp.org/5.x/class-Cake.I18n.Time.html#i18nFormat()
echo $this->Url->build([
'controller' => 'Posts',
'action' => 'list',
'_ext' => 'rss',
]);
// Output
/posts/list.rss
echo $this->Url->build([
'controller' => 'Posts',
'action' => 'list',
'prefix' => 'Admin',
]);
// Output
/admin/posts/list
URL (starting with ‘/’) with the full base URL prepended:
// Output
http://somedomain.com/posts
echo $this->Url->build([
'controller' => 'Posts',
'action' => 'search',
'?' => ['foo' => 'bar'],
'#' => 'first',
]);
// Output
/posts/search?foo=bar#first
The above example uses the ? special key for specifying query string parameters and # key for URL fragment.
URL for named route:
The 2nd parameter allows you to define options controlling HTML escaping, and whether or not the base path should
be added:
$this->Url->build('/posts', [
'escape' => false,
'fullBase' => true,
]);
If you want to use route path strings, you can do that using this method:
echo $this->Url->buildFromPath('Articles::index');
// outputs: /articles
URL with asset timestamp wrapped by a <link rel="preload"/>, here pre-loading a font. Note: The file must exist
and Configure::read('Asset.timestamp') must return true or 'force' for the timestamp to be appended:
echo $this->Html->meta([
'rel' => 'preload',
'href' => $this->Url->assetUrl(
'/assets/fonts/your-font-pack/your-font-name.woff2'
),
'as' => 'font',
]);
If you are generating URLs for CSS, Javascript or image files there are helper methods for each of these asset types:
// Outputs /img/icon.png
$this->Url->image('icon.png');
// Outputs /js/app.js
$this->Url->script('app.js');
// Outputs /css/app.css
$this->Url->css('app.css');
If you need to customize how asset URLs are generated, or want to use custom asset cache busting parameters you can
use the assetUrlClassName option:
// In view initialize
$this->loadHelper('Url', ['assetUrlClassName' => AppAsset::class]);
When using the assetUrlClassName you must implement the same methods as Cake\Routing\Asset does.
For further information check Router::url124 in the API.
Configuring Helpers
You configure helpers in CakePHP by declaring them in a view class. An AppView class comes with every CakePHP
application and is the ideal place to add helpers for global use:
To add helpers from plugins use the plugin syntax used elsewhere in CakePHP:
$this->addHelper('Blog.Comment');
You don’t have to explicitly add Helpers that come from CakePHP or your application. These helpers can be lazily
loaded upon first use. For example:
From within a plugin’s views, plugin helpers can also be lazily loaded. For example, view templates in the ‘Blog’
plugin, can lazily load helpers from the same plugin.
You can use the current action name to conditionally add helpers:
You can also use your controller’s beforeRender method to add helpers:
Configuration options
You can pass configuration options to helpers. These options can be used to set attribute values or modify the behavior
of a helper:
namespace App\View\Helper;
use Cake\View\Helper;
use Cake\View\View;
By default all configuration options will be merged with the $_defaultConfig property. This property should define
the default values of any configuration your helper requires. For example:
namespace App\View\Helper;
use Cake\View\Helper;
use Cake\View\StringTemplateTrait;
/**
* @var array<string, mixed>
*/
protected array $_defaultConfig = [
'errorClass' => 'error',
(continues on next page)
Any configuration provided to your helper’s constructor will be merged with the default values during construction and
the merged data will be set to _config. You can use the getConfig() method to read runtime configuration:
Using helper configuration allows you to declaratively configure your helpers and keep configuration logic out of your
controller actions. If you have configuration options that cannot be included as part of a class declaration, you can set
those in your controller’s beforeRender callback:
Aliasing Helpers
One common setting to use is the className option, which allows you to create aliased helpers in your views. This
feature is useful when you want to replace $this->Html or another common Helper reference with a custom imple-
mentation:
// src/View/AppView.php
class AppView extends View
{
public function initialize(): void
{
$this->addHelper('Html', [
'className' => 'MyHtml',
]);
}
}
// src/View/Helper/MyHtmlHelper.php
namespace App\View\Helper;
use Cake\View\Helper\HtmlHelper;
Note: Aliasing a helper replaces that instance anywhere that helper is used, including inside other Helpers.
Using Helpers
Once you’ve configured which helpers you want to use in your controller, each helper is exposed as a public property
in the view. For example, if you were using the HtmlHelper you would be able to access it by doing the following:
echo $this->Html->css('styles');
The above would call the css() method on the HtmlHelper. You can access any loaded helper using
$this->{$helperName}.
There may be situations where you need to dynamically load a helper from inside a view. You can use the view’s
Cake\View\HelperRegistry to do this:
The HelperRegistry is a registry and supports the registry API used elsewhere in CakePHP.
Callback Methods
Helpers feature several callbacks that allow you to augment the view rendering process. See the Helper Class and the
Events System documentation for more information.
Creating Helpers
You can create custom helper classes for use in your application or plugins. Like most components of CakePHP, helper
classes have a few conventions:
• Helper class files should be put in src/View/Helper. For example: src/View/Helper/LinkHelper.php
• Helper classes should be suffixed with Helper. For example: LinkHelper.
• When referencing helper names you should omit the Helper suffix. For example:
$this->addHelper('Link'); or $this->loadHelper('Link');.
You’ll also want to extend Helper to ensure things work correctly:
/* src/View/Helper/LinkHelper.php */
namespace App\View\Helper;
use Cake\View\Helper;
You may wish to use some functionality already existing in another helper. To do so, you can specify helpers you wish
to use with a $helpers array, formatted just as you would in a controller:
namespace App\View\Helper;
use Cake\View\Helper;
Once you’ve created your helper and placed it in src/View/Helper/, you can load it in your views:
Once your helper has been loaded, you can use it in your views by accessing the matching view property:
Note: The HelperRegistry will attempt to lazy load any helpers not specifically identified in your Controller.
If you would like to access a View variable inside a helper, you can use $this->getView()->get() like:
If you would like to render an Element inside your Helper you can use $this->getView()->element() like:
Helper Class
class Helper
Callbacks
By implementing a callback method in a helper, CakePHP will automatically subscribe your helper to the relevant
event. Unlike previous versions of CakePHP you should not call parent in your callbacks, as the base Helper class
does not implement any of the callback methods.
Helper::beforeRenderFile(EventInterface $event, $viewFile)
Is called before each view file is rendered. This includes elements, views, parent views and layouts.
Helper::afterRenderFile(EventInterface $event, $viewFile, $content)
Is called after each view file is rendered. This includes elements, views, parent views and layouts. A callback
can modify and return $content to change how the rendered content will be displayed in the browser.
Helper::beforeRender(EventInterface $event, $viewFile)
The beforeRender method is called after the controller’s beforeRender method but before the controller renders
view and layout. Receives the file being rendered as an argument.
Helper::afterRender(EventInterface $event, $viewFile)
Is called after the view has been rendered but before layout rendering has started.
Helper::beforeLayout(EventInterface $event, $layoutFile)
Is called before layout rendering starts. Receives the layout filename as an argument.
Helper::afterLayout(EventInterface $event, $layoutFile)
Is called after layout rendering is complete. Receives the layout filename as an argument.
In CakePHP, working with data through the database is done with two primary object types:
• Repositories or table objects provide access to collections of data. They allow you to save new records, mod-
ify/delete existing ones, define relations, and perform bulk operations.
• Entities represent individual records and allow you to define row/record level behavior & functionality.
These two classes are usually responsible for managing almost everything that happens regarding your data, its validity,
interactions and evolution of the information workflow in your domain of work.
CakePHP’s built-in ORM specializes in relational databases, but can be extended to support alternative datasources.
The CakePHP ORM borrows ideas and concepts from both ActiveRecord and Datamapper patterns. It aims to create
a hybrid implementation that combines aspects of both patterns to create a fast, simple to use ORM.
Before we get started exploring the ORM, make sure you configure your database connections.
Quick Example
To get started you don’t have to write any code. If you’ve followed the CakePHP conventions for your database tables
you can just start using the ORM. For example if we wanted to load some data from our articles table we would
start off creating our Articles table class. Create src/Model/Table/ArticlesTable.php with the following code:
<?php
namespace App\Model\Table;
use Cake\ORM\Table;
321
CakePHP Book, Release 5.x
Then in a controller or command we can have CakePHP create an instance for us:
In other contexts, you can use the LocatorAwareTrait which add accessor methods for ORM tables:
use Cake\ORM\Locator\LocatorAwareTrait;
Within a static method you can use the FactoryLocator to get the table locator:
$articles = TableRegistry::getTableLocator()->get('Articles');
Table classes represent collections of entities. Next, lets create an entity class for our Articles. Entity classes let you
define accessor and mutator methods, define custom logic for individual records and much more. We’ll start off by
adding the following to src/Model/Entity/Article.php after the <?php opening tag:
namespace App\Model\Entity;
use Cake\ORM\Entity;
Entities use the singular CamelCase version of the table name as their class name by default. Now that we have created
our entity class, when we load entities from the database we’ll get instances of our new Article class:
use Cake\ORM\Locator\LocatorAwareTrait;
$articles = $this->fetchTable('Articles');
$resultset = $articles->find()->all();
CakePHP uses naming conventions to link the Table and Entity class together. If you need to customize which entity a
table uses you can use the entityClass() method to set a specific classname.
See the chapters on Table Objects and Entities for more information on how to use table objects and entities in your
application.
More Information
Database Basics
The CakePHP database access layer abstracts and provides help with most aspects of dealing with relational databases
such as, keeping connections to the server, building queries, preventing SQL injections, inspecting and altering
schemas, and with debugging and profiling queries sent to the database.
Quick Tour
The functions described in this chapter illustrate what is possible to do with the lower-level database access API. If
instead you want to learn more about the complete ORM, you can read the Query Builder and Table Objects sections.
The easiest way to create a database connection is using a DSN string:
use Cake\Datasource\ConnectionManager;
$dsn = 'mysql://root:password@localhost/my_database';
ConnectionManager::setConfig('default', ['url' => $dsn]);
Once created, you can access the connection object to start using it:
$connection = ConnectionManager::get('default');
use Cake\Datasource\ConnectionManager;
$connection = ConnectionManager::get('default');
$results = $connection->execute('SELECT * FROM articles')->fetchAll('assoc');
$results = $connection
->execute('SELECT * FROM articles WHERE id = :id', ['id' => 1])
->fetchAll('assoc');
use Cake\Datasource\ConnectionManager;
use DateTime;
$connection = ConnectionManager::get('default');
$results = $connection
->execute(
'SELECT * FROM articles WHERE created >= :created',
(continues on next page)
Instead of writing the SQL manually, you can use the query builder:
use Cake\Datasource\ConnectionManager;
use DateTime;
$connection = ConnectionManager::get('default');
$connection->insert('articles', [
'title' => 'A New Article',
'created' => new DateTime('now')
], ['created' => 'datetime']);
Updating rows in the database is equally intuitive, the following example will update the article with id 10:
use Cake\Datasource\ConnectionManager;
$connection = ConnectionManager::get('default');
$connection->update('articles', ['title' => 'New title'], ['id' => 10]);
Similarly, the delete() method is used to delete rows from the database, the following example deletes the article
with id 10:
use Cake\Datasource\ConnectionManager;
$connection = ConnectionManager::get('default');
$connection->delete('articles', ['id' => 10]);
Configuration
By convention database connections are configured in config/app.php. The connection information defined in this file
is fed into Cake\Datasource\ConnectionManager creating the connection configuration your application will be
using. Sample connection information can be found in config/app.default.php. A sample connection configuration
would look like:
'Datasources' => [
'default' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Mysql',
'persistent' => false,
'host' => 'localhost',
'username' => 'my_app',
'password' => 'secret',
'database' => 'my_app',
'encoding' => 'utf8mb4',
'timezone' => 'UTC',
'cacheMetadata' => true,
],
],
The above will create a ‘default’ connection, with the provided parameters. You can define as many connections as
you want in your configuration file. You can also define additional connections at runtime using Cake\Datasource\
ConnectionManager::setConfig(). An example of that would be:
use Cake\Datasource\ConnectionManager;
ConnectionManager::setConfig('default', [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Mysql',
'persistent' => false,
'host' => 'localhost',
'username' => 'my_app',
'password' => 'secret',
'database' => 'my_app',
'encoding' => 'utf8mb4',
'timezone' => 'UTC',
'cacheMetadata' => true,
]);
Configuration options can also be provided as a DSN string. This is useful when working with environment variables
or PaaS providers:
ConnectionManager::setConfig('default', [
'url' => 'mysql://my_app:sekret@localhost/my_app?encoding=utf8&timezone=UTC&
˓→cacheMetadata=true',
]);
When using a DSN string you can define any additional parameters/options as query string arguments.
By default, all Table objects will use the default connection. To use a non-default connection, see Configuring
Connections.
There are a number of keys supported in database configuration. A full list is as follows:
className
The fully namespaced class name of the class that represents the connection to a database server. This class is re-
sponsible for loading the database driver, providing SQL transaction mechanisms and preparing SQL statements
among other things.
driver
The class name of the driver used to implement all specificities for a database engine. This can either be a short
classname using plugin syntax, a fully namespaced name, or a constructed driver instance. Examples of short
classnames are Mysql, Sqlite, Postgres, and Sqlserver.
persistent
Whether or not to use a persistent connection to the database. This option is not supported by SqlServer. An
exception is thrown if you attempt to set persistent to true with SqlServer.
host
The database server’s hostname (or IP address).
username
The username for the account.
password
The password for the account.
database
The name of the database for this connection to use. Avoid using . in your database name. Because of how
it complicates identifier quoting CakePHP does not support . in database names. The path to your SQLite
database should be an absolute path (for example, ROOT . DS . 'my_app.db') to avoid incorrect paths caused
by relative paths.
port (optional)
The TCP port or Unix socket used to connect to the server.
encoding
Indicates the character set to use when sending SQL statements to the server. This defaults to the database’s
default encoding for all databases other than DB2.
timezone
Server timezone to set.
schema
Used in PostgreSQL database setups to specify which schema to use.
unix_socket
Used by drivers that support it to connect via Unix socket files. If you are using PostgreSQL and want to use
Unix sockets, leave the host key blank.
ssl_key
The file path to the SSL key file. (Only supported by MySQL).
ssl_cert
The file path to the SSL certificate file. (Only supported by MySQL).
ssl_ca
The file path to the SSL certificate authority. (Only supported by MySQL).
init
A list of queries that should be sent to the database server as when the connection is created.
log
Set to true to enable query logging. When enabled queries will be logged at a debug level with the queriesLog
scope.
quoteIdentifiers
Set to true if you are using reserved words or special characters in your table or column names. Enabling this
setting will result in queries built using the Query Builder having identifiers quoted when creating SQL. It should
be noted that this decreases performance because each query needs to be traversed and manipulated before being
executed.
flags
An associative array of PDO constants that should be passed to the underlying PDO instance. See the PDO
documentation for the flags supported by the driver you are using.
cacheMetadata
Either boolean true, or a string containing the cache configuration to store meta data in. Having metadata
caching disabled by setting it to false is not advised and can result in very poor performance. See the Metadata
Caching section for more information.
mask
Set the permissions on the generated database file. (Only supported by SQLite)
cache
The cache flag to send to SQLite.
mode
The mode flag value to send to SQLite.
At this point, you might want to take a look at the CakePHP Conventions. The correct naming for your tables (and the
addition of some columns) can score you some free functionality and help you avoid configuration. For example, if you
name your database table big_boxes, your table BigBoxesTable, and your controller BigBoxesController, everything
will work together automatically. By convention, use underscores, lower case, and plural forms for your database table
names - for example: bakers, pastry_stores, and savory_cakes.
Note: If your MySQL server is configured with skip-character-set-client-handshake then you MUST use
the flags config to set your charset encoding. For example:
Connections can have separate read and write roles. Read roles are expected to represent read-only replicas and write
roles are expected to be the default connection and support write operations.
Read roles are configured by providing a read key in the connection config. Write roles are configured by providing a
write key.
Role configurations override the values in the shared connection config. If the read and write role configurations are
the same, a single connection to the database is used for both:
'default' => [
'driver' => 'mysql',
'username' => '...',
'password' => '...',
'database' => '...',
'read' => [
'host' => 'read-db.example.com',
],
'write' => [
(continues on next page)
You can specify the same value for both read and write key without creating multiple connections to the database.
Managing Connections
class Cake\Datasource\ConnectionManager
The ConnectionManager class acts as a registry to access database connections your application has. It provides a
place that other objects can get references to existing connections.
Accessing Connections
static Cake\Datasource\ConnectionManager::get($name)
use Cake\Datasource\ConnectionManager;
$connection = ConnectionManager::get('default');
Using setConfig() and get() you can create new connections that are not defined in your configuration files at
runtime:
ConnectionManager::setConfig('my_connection', $config);
$connection = ConnectionManager::get('my_connection');
See the Configuration for more information on the configuration data used when creating connections.
Data Types
class Cake\Database\TypeFactory
Since not every database vendor includes the same set of data types, or the same names for similar data types, CakePHP
provides a set of abstracted data types for use with the database layer. The types CakePHP supports are:
string
Maps to VARCHAR type. In SQL Server the NVARCHAR types are used.
char
Maps to CHAR type. In SQL Server the NCHAR type is used.
text
Maps to TEXT types.
uuid
Maps to the UUID type if a database provides one, otherwise this will generate a CHAR(36) field.
binaryuuid
Maps to the UUID type if the database provides one, otherwise this will generate a BINARY(16) column
integer
Maps to the INTEGER type provided by the database. BIT is not yet supported at this moment.
smallinteger
Maps to the SMALLINT type provided by the database.
tinyinteger
Maps to the TINYINT or SMALLINT type provided by the database. In MySQL TINYINT(1) is treated as a
boolean.
biginteger
Maps to the BIGINT type provided by the database.
float
Maps to either DOUBLE or FLOAT depending on the database. The precision option can be used to define the
precision used.
decimal
Maps to the DECIMAL type. Supports the length and precision options. Values for decimal type ares be
represented as strings (not as float as some might expect). This is because decimal types are used to represent
exact numeric values in databases and using float type for them in PHP can potentially lead to precision loss.
If you want the values to be float in your PHP code then consider using FLOAT or DOUBLE type columns in
your database. Also, depending on your use case you can explicitly map your decimal columns to float type in
your table schema.
boolean
Maps to BOOLEAN except in MySQL, where TINYINT(1) is used to represent booleans. BIT(1) is not yet
supported at this moment.
binary
Maps to the BLOB or BYTEA type provided by the database.
date
Maps to a native DATE column type. The return value of this column type is Cake\I18n\Date which emulates
the date related methods of PHP’s DateTime class.
datetime
See DateTime Type.
datetimefractional
See DateTime Type.
timestamp
Maps to the TIMESTAMP type.
timestampfractional
Maps to the TIMESTAMP(N) type.
time
Maps to a TIME type in all databases.
json
Maps to a JSON type if it’s available, otherwise it maps to TEXT.
enum
See Enum Type.
These types are used in both the schema reflection features that CakePHP provides, and schema generation features
CakePHP uses when using test fixtures.
Each type can also provide translation functions between PHP and SQL representations. These methods are invoked
based on the type hints provided when doing queries. For example a column that is marked as ‘datetime’ will automati-
cally convert input parameters from DateTime instances into a timestamp or formatted datestrings. Likewise, ‘binary’
columns will accept file handles, and generate file handles when reading data.
DateTime Type
class Cake\Database\DateTimeType
Maps to a native DATETIME column type. In PostgreSQL and SQL Server this turns into a TIMESTAMP type.
The default return value of this column type is Cake\I18n\DateTime which extends Chronos125 and the native
DateTimeImmutable.
Cake\Database\DateTimeType::setTimezone(string|\DateTimeZone|null $timezone)
If your database server’s timezone does not match your application’s PHP timezone then you can use this method to
specify your database’s timezone. This timezone will then used when converting PHP objects to database’s datetime
string and vice versa.
class Cake\Database\DateTimeFractionalType
Can be used to map datetime columns that contain microseconds such as DATETIME(6) in MySQL. To use this type
you need to add it as a mapped type:
// in config/bootstrap.php
use Cake\Database\TypeFactory;
use Cake\Database\Type\DateTimeFractionalType;
class Cake\Database\DateTimeTimezoneType
Can be used to map datetime columns that contain time zones such as TIMESTAMPTZ in PostgreSQL. To use this type
you need to add it as a mapped type:
// in config/bootstrap.php
use Cake\Database\TypeFactory;
use Cake\Database\Type\DateTimeTimezoneType;
125 https://github.com/cakephp/chronos
Enum Type
class Cake\Database\EnumType
Maps a BackedEnum126 to a string or integer column. To use this type you need to specify which column is associated
to which BackedEnum inside the table class:
use \Cake\Database\Type\EnumType;
use \App\Model\Enum\ArticleStatus;
// in src/Model/Table/ArticlesTable.php
public function initialize(array $config): void
{
$this->getSchema()->setColumnType('status', EnumType::from(ArticleStatus::class));
}
namespace App\Model\Enum;
class Cake\Database\TypeFactory
If you need to use vendor specific types that are not built into CakePHP you can add additional new types to CakePHP’s
type system. Type classes are expected to implement the following methods:
• toPHP: Casts given value from a database type to a PHP equivalent.
• toDatabase: Casts given value from a PHP type to one acceptable by a database.
• toStatement: Casts given value to its Statement equivalent.
• marshal: Marshals flat data into PHP objects.
To fulfill the basic interface, extend Cake\Database\Type. For example if we wanted to add a JSON type, we could
make the following type class:
126 https://www.php.net/manual/en/language.enumerations.backed.php
// in src/Database/Type/JsonType.php
namespace App\Database\Type;
use Cake\Database\Driver;
use Cake\Database\Type\BaseType;
use PDO;
return PDO::PARAM_STR;
}
}
By default the toStatement() method will treat values as strings which will work for our new type.
Once we’ve created our new type, we need to add it into the type mapping. During our application bootstrap we should
do the following:
use Cake\Database\TypeFactory;
TypeFactory::map('json', \App\Database\Type\JsonType:class);
Implementing ColumnSchemaAwareInterface gives you more control over custom datatypes. This avoids overwrit-
ing schema definitions if your datatype has an unambiguous SQL column definition. For example, we could have our
JSON type be used anytime a TEXT column with a specific comment is used:
// in src/Database/Type/JsonType.php
namespace App\Database\Type;
use Cake\Database\Driver;
use Cake\Database\Type\BaseType;
use Cake\Database\Type\ColumnSchemaAwareInterface;
use Cake\Database\Schema\TableSchemaInterface;
use PDO;
/**
* Convert abstract schema definition into a driver specific
* SQL snippet that can be used in a CREATE TABLE statement.
*
* Returning null will fall through to CakePHP's built-in types.
*/
public function getColumnSql(
TableSchemaInterface $schema,
string $column,
(continues on next page)
return $sql;
}
/**
* Convert the column data returned from schema reflection
* into the abstract schema data.
*
* Returning null will fall through to CakePHP's built-in types.
*/
public function convertColumnDefinition(
array $definition,
Driver $driver
): ?array {
return [
'type' => $this->_name,
'length' => null,
];
}
The $definition data passed to convertColumnDefinition() will contain the following keys. All keys will exist
but may contain null if the key has no value for the current database driver:
• length The length of a column if available..
• precision The precision of the column if available.
• scale Can be included for SQLServer connections.
The previous example maps a custom datatype for a ‘json’ column type which is easily represented as a string in a
SQL statement. Complex SQL data types cannot be represented as strings/integers in SQL queries. When working
with these datatypes your Type class needs to implement the Cake\Database\Type\ExpressionTypeInterface
interface. This interface lets your custom type represent a value as a SQL expression. As an example, we’ll build a
simple Type class for handling POINT type data out of MySQL. First we’ll define a ‘value’ object that we can use to
represent POINT data in PHP:
// in src/Database/Point.php
namespace App\Database;
// Factory method.
public static function parse($value)
{
// Parse the WKB data from MySQL.
$unpacked = unpack('x4/corder/Ltype/dlat/dlong', $value);
With our value object created, we’ll need a Type class to map data into this value object and into SQL expressions:
namespace App\Database\Type;
use App\Database\Point;
use Cake\Database\Driver;
use Cake\Database\Expression\FunctionExpression;
use Cake\Database\ExpressionInterface;
use Cake\Database\Type\BaseType;
use Cake\Database\Type\ExpressionTypeInterface;
return null;
}
Connection Classes
class Cake\Database\Connection
Connection classes provide a simple interface to interact with database connections in a consistent way. They are
intended as a more abstract interface to the driver layer and provide features for executing queries, logging queries, and
doing transactional operations.
Executing Queries
Once you’ve gotten a connection object, you’ll probably want to issue some queries with it. CakePHP’s database
abstraction layer provides wrapper features on top of PDO and native drivers. These wrappers provide a similar interface
to PDO. There are a few different ways you can run queries depending on the type of query you need to run and what
kind of results you need back. The most basic method is execute() which allows you to run complet SQL queries:
$statement = $connection->execute(
'UPDATE articles SET published = ? WHERE id = ?',
[1, 2]
);
Without any type hinting information, execute will assume all placeholders are string values. If you need to bind
specific types of data, you can use their abstract type names when creating a query:
$statement = $connection->execute(
'UPDATE articles SET published_date = ? WHERE id = ?',
[new DateTime('now'), 2],
['date', 'integer']
);
Cake\Database\Connection::selectQuery()
These methods allow you to use rich data types in your applications and properly convert them into SQL statements.
The last and most flexible way of creating queries is to use the Query Builder. This approach allows you to build
complex and expressive queries without having to use platform specific SQL. When using the query builder, no SQL
will be sent to the database server until the execute() method is called, or the query is iterated. Iterating a query will
first execute it and then start iterating over the result set:
$query = $connection->selectQuery();
$query->select('*')
->from('articles')
->where(['published' => true]);
Note: Instead of iterating the $query you can also call it’s all() method to get the results.
Cake\Database\Connection::updateQuery()
$query = $connection->updateQuery('articles')
->set(['published' => true])
(continues on next page)
Cake\Database\Connection::insertQuery()
$query = $connection->insertQuery();
$query->into('articles')
->columns(['title'])
->values(['1st article']);
$statement = $query->execute();
Cake\Database\Connection::deleteQuery()
$query = $connection->deleteQuery();
$query->delete('articles')
->where(['id' => 2]);
$statement = $query->execute();
Using Transactions
The connection objects provide you a few simple ways you do database transactions. The most basic way of doing
transactions is through the begin(), commit() and rollback() methods, which map to their SQL equivalents:
$connection->begin();
$connection->execute('UPDATE articles SET published = ? WHERE id = ?', [true, 2]);
$connection->execute('UPDATE articles SET published = ? WHERE id = ?', [false, 4]);
$connection->commit();
Cake\Database\Connection::transactional(callable $callback)
In addition to this interface connection instances also provide the transactional() method which makes handling
the begin/commit/rollback calls much simpler:
$connection->transactional(function ($connection) {
$connection->execute('UPDATE articles SET published = ? WHERE id = ?', [true, 2]);
$connection->execute('UPDATE articles SET published = ? WHERE id = ?', [false, 4]);
});
In addition to basic queries, you can execute more complex queries using either the Query Builder or Table Objects.
The transactional method will do the following:
• Call begin.
• Call the provided closure.
• If the closure raises an exception, a rollback will be issued. The original exception will be re-thrown.
• If the closure returns false, a rollback will be issued.
• If the closure executes successfully, the transaction will be committed.
When using the lower level database API, you will often encounter statement objects. These objects allow you to
manipulate the underlying prepared statement from the driver. After creating and executing a query object, or using
execute() you will have a StatementInterface instance.
Once a query is executed using execute(), results can be fetched using fetch(), fetchAll():
$statement->execute();
After executing a statement, you can fetch the number of affected rows:
$rowCount = $statement->rowCount();
If your query was not successful, you can get related error information using the errorCode() and errorInfo()
methods. These methods work the same way as the ones provided by PDO:
$code = $statement->errorCode();
$info = $statement->errorInfo();
Query Logging
Query logging can be enabled when configuring your connection by setting the log option to true.
When query logging is enabled, queries will be logged to Cake\Log\Log using the ‘debug’ level, and the ‘queriesLog’
scope. You will need to have a logger configured to capture this level & scope. Logging to stderr can be useful when
working on unit tests, and logging to files/syslog can be useful when working with web requests:
use Cake\Log\Log;
// Console logging
Log::setConfig('queries', [
'className' => 'Console',
'stream' => 'php://stderr',
'scopes' => ['queriesLog']
]);
Note: Query logging is only intended for debugging/development uses. You should never leave query logging on in
production as it will negatively impact the performance of your application.
Identifier Quoting
By default CakePHP does not quote identifiers in generated SQL queries. The reason for this is identifier quoting has
a few drawbacks:
• Performance overhead - Quoting identifiers is much slower and complex than not doing it.
• Not necessary in most cases - In non-legacy databases that follow CakePHP’s conventions there is no reason to
quote identifiers.
If you are using a legacy schema that requires identifier quoting you can enable it using the quoteIdentifiers setting
in your Configuration. You can also enable this feature at runtime:
$connection->getDriver()->enableAutoQuoting();
When enabled, identifier quoting will cause additional query traversal that converts all identifiers into
IdentifierExpression objects.
Metadata Caching
CakePHP’s ORM uses database reflection to determine the schema, indexes and foreign keys your application con-
tains. Because this metadata changes infrequently and can be expensive to access, it is typically cached. By default,
metadata is stored in the _cake_model_ cache configuration. You can define a custom cache configuration using the
cacheMetatdata option in your datasource configuration:
'Datasources' => [
'default' => [
// Other keys go here.
You can also configure the metadata caching at runtime with the cacheMetadata() method:
CakePHP also includes a CLI tool for managing metadata caches. See the Schema Cache Tool chapter for more infor-
mation.
Creating Databases
If you want to create a connection without selecting a database you can omit the database name:
$dsn = 'mysql://root:password@localhost/';
You can now use your connection object to execute queries that create/modify databases. For example to create a
database:
Note: When creating a database it is a good idea to set the character set and collation parameters. If these values are
missing, the database will set whatever system default values it uses.
Query Builder
class Cake\ORM\Query\SelectQuery\SelectQuery
The ORM’s query builder provides a simple to use fluent interface for creating and running queries. By composing
queries together, you can create advanced queries using unions and subqueries with ease.
Underneath the covers, the query builder uses PDO prepared statements which protect against SQL injection attacks.
The easiest way to create a SelectQuery object is to use find() from a Table object. This method will return an
incomplete query ready to be modified. You can also use a table’s connection object to access the lower level query
builder that does not include ORM features, if necessary. See the Executing Queries section for more information:
use Cake\ORM\Locator\LocatorAwareTrait;
$articles = $this->fetchTable('Articles');
When inside a controller, you can use the automatic table variable that is created using the conventions system:
// Inside ArticlesController.php
$query = $this->Articles->find();
use Cake\ORM\Locator\LocatorAwareTrait;
$query = $this->fetchTable('Articles')->find();
For the remaining examples, assume that $articles is a Table. When inside controllers, you can use
$this->Articles instead of $articles.
Almost every method in a SelectQuery object will return the same query, this means that SelectQuery objects are
lazy, and will not be executed unless you tell them to:
You can of course chain the methods you call on SelectQuery objects:
$query = $articles
->find()
->select(['id', 'name'])
->where(['id !=' => 1])
->orderBy(['created' => 'DESC']);
If you try to call debug() on a SelectQuery object, you will see its internal state and the SQL that will be executed in
the database:
// Outputs
// ...
// 'sql' => 'SELECT * FROM articles where id = ?'
// ...
You can execute a query directly without having to use foreach on it. The easiest way is to either call the all() or
toList() methods:
$resultsIteratorObject = $articles
->find()
->where(['id >' => 1])
->all();
(continues on next page)
$resultsArray = $articles
->find()
->where(['id >' => 1])
->all()
->toList();
debug($resultsArray[0]->title);
In the above example, $resultsIteratorObject will be an instance of Cake\ORM\ResultSet, an object you can
iterate and apply several extracting and traversing methods on.
Often, there is no need to call all(), you can simply iterate the SelectQuery object to get its results. Query objects
can also be used directly as the result object; trying to iterate the query, calling toList() or toArray(), will result
in the query being executed and results returned to you.
You can use the first() method to get the first result in the query:
$article = $articles
->find()
->where(['id' => 1])
->first();
debug($article->title);
$list = $articles->find('list')->all();
foreach ($list as $id => $title) {
echo "$id : $title"
}
For more information on how to customize the fields used for populating the list refer to Finding Key/Value Pairs
section.
Once you get familiar with the Query object methods, it is strongly encouraged that you visit the Collection section to
improve your skills in efficiently traversing the results. The resultset (returned by calling the SelectQuery’s all()
method) implements the collection interface:
// An advanced example
$results = $articles->find()
->where(['id >' => 1])
->orderBy(['title' => 'DESC'])
->all()
->map(function ($row) {
$row->trimmedTitle = trim($row->title);
return $row;
})
->combine('id', 'trimmedTitle') // combine() is another collection method
->toArray(); // Also a collections library method
Query objects are lazily evaluated. This means a query is not executed until one of the following things occur:
• The query is iterated with foreach.
• The query’s execute() method is called. This will return the underlying statement object, and is to be used
with insert/update/delete queries.
• The query’s first() method is called. This will return the first result in the set built by SELECT (it adds LIMIT
1 to the query).
• The query’s all() method is called. This will return the result set and can only be used with SELECT statements.
• The query’s toList() or toArray() method is called.
Until one of these conditions are met, the query can be modified without additional SQL being sent to the database. It
also means that if a Query hasn’t been evaluated, no SQL is ever sent to the database. Once executed, modifying and
re-evaluating a query will result in additional SQL being run. Calling the same query without modification multiple
times will return same reference.
If you want to take a look at what SQL CakePHP is generating, you can turn database query logging on.
Selecting Data
CakePHP makes building SELECT queries simple. To limit the fields fetched, you can use the select() method:
$query = $articles->find();
$query->select(['id', 'title', 'body']);
foreach ($query->all() as $row) {
debug($row->title);
}
You can set aliases for fields by providing fields as an associative array:
To set some basic conditions you can use the where() method:
You can also pass an anonymous function to the where() method. The passed anonymous function will receive
an instance of \Cake\Database\Expression\QueryExpression as its first argument, and \Cake\ORM\Query\
SelectQuery as its second:
$query = $articles->find();
$query->where(function (QueryExpression $exp, SelectQuery $q) {
return $exp->eq('published', true);
});
See the Advanced Conditions section to find out how to construct more complex WHERE conditions.
By default a query will select all fields from a table, the exception is when you call the select() function yourself
and pass certain fields:
If you wish to still select all fields from a table after having called select($fields), you can pass the table instance
to select() for this purpose:
You can use selectAlso() to select all fields on a table and also select some additional fields:
$query = $articlesTable->find();
$query->selectAlso(['count' => $query->func()->count('*')]);
If you want to select all but a few fields on a table, you can use selectAllExcept():
$query = $articlesTable->find();
You can also pass an Association object when working with contained associations.
CakePHP’s ORM offers abstraction for some commonly used SQL functions. Using the abstraction allows the ORM to
select the platform specific implementation of the function you want. For example, concat is implemented differently
in MySQL, PostgreSQL and SQL Server. Using the abstraction allows your code to be portable:
Note that most of the functions accept an additional argument to specify the types to bind to the arguments and/or the
return type, for example:
sum()
Calculate a sum. Assumes arguments are literal values.
avg()
Calculate an average. Assumes arguments are literal values.
min()
Calculate the min of a column. Assumes arguments are literal values.
max()
Calculate the max of a column. Assumes arguments are literal values.
count()
Calculate the count. Assumes arguments are literal values.
cast()
Convert a field or expression from one data type to another.
concat()
Concatenate two values together. Assumes arguments are bound parameters.
coalesce()
Coalesce values. Assumes arguments are bound parameters.
dateDiff()
Get the difference between two dates/times. Assumes arguments are bound parameters.
now()
Defaults to returning date and time, but accepts ‘time’ or ‘date’ to return only those values.
extract()
Returns the specified date part from the SQL expression.
dateAdd()
Add the time unit to the date expression.
dayOfWeek()
Returns a FunctionExpression representing a call to SQL WEEKDAY function.
Window-Only Functions
$query = $articles->find()->innerJoinWith('Categories');
$concat = $query->func()->concat([
'Articles.title' => 'identifier',
' - CAT: ',
(continues on next page)
Both literal and identifier arguments allow you to reference other columns and SQL literals while identifier
will be appropriately quoted if auto-quoting is enabled. If not marked as literal or identifier, arguments will be bound
parameters allowing you to safely pass user data to the function.
The above example generates something like this in MYSQL.
SELECT CONCAT(
Articles.title,
:c0,
Categories.name,
:c1,
(DATEDIFF(NOW(), Articles.created))
) FROM articles;
The :c0 argument will have ' - CAT:' text bound when the query is executed. The dateDiff expression was trans-
lated to the appropriate SQL.
Custom Functions
If func() does not already wrap the SQL function you need, you can call it directly through func() and still safely
pass arguments and user data as described. Make sure you pass the appropriate argument type for custom functions or
they will be treated as bound parameters:
$query = $articles->find();
$year = $query->func()->year([
'created' => 'identifier'
]);
$time = $query->func()->date_format([
'created' => 'identifier',
"'%H:%i'" => 'literal'
]);
$query->select([
'yearCreated' => $year,
'timeCreated' => $time
]);
Note: Use func() to pass untrusted user data to any SQL function.
Ordering Results
$query = $articles->find()
->orderBy(['title' => 'ASC', 'id' => 'ASC']);
When calling orderBy() multiple times on a query, multiple clauses will be appended. However, when using
finders you may sometimes need to overwrite the ORDER BY. Set the second parameter of orderBy() (as well as
orderByAsc() or orderByDesc()) to SelectQuery::OVERWRITE or to true:
$query = $articles->find()
->orderBy(['title' => 'ASC']);
// Later, overwrite the ORDER BY clause instead of appending to it.
$query = $articles->find()
->orderBy(['created' => 'DESC'], SelectQuery::OVERWRITE);
The orderByAsc and orderByDesc methods can be used when you need to sort on complex expressions:
$query = $articles->find();
$concat = $query->func()->concat([
'title' => 'identifier',
'synopsis' => 'identifier'
]);
$query->orderByAsc($concat);
Limiting Results
To limit the number of rows or set the row offset you can use the limit() and page() methods:
As you can see from the examples above, all the methods that modify the query provide a fluent interface, allowing you
to build a query through chained method calls.
When using aggregate functions like count and sum you may want to use group by and having clauses:
$query = $articles->find();
$query->select([
'count' => $query->func()->count('view_count'),
'published_date' => 'DATE(created)'
])
->groupBy('published_date')
->having(['count >' => 3]);
Case Statements
The ORM also offers the SQL case expression. The case expression allows for implementing if ... then ...
else logic inside your SQL. This can be useful for reporting on data where you need to conditionally sum or count
data, or where you need to specific data based on a condition.
If we wished to know how many published articles are in our database, we could use the following SQL:
SELECT
COUNT(CASE WHEN published = 'Y' THEN 1 END) AS number_published,
COUNT(CASE WHEN published = 'N' THEN 1 END) AS number_unpublished
FROM articles
To do this with the query builder, we’d use the following code:
$query = $articles->find();
$publishedCase = $query->newExpr()
->case()
->when(['published' => 'Y'])
->then(1);
$unpublishedCase = $query->newExpr()
->case()
->when(['published' => 'N'])
->then(1);
$query->select([
'number_published' => $query->func()->count($publishedCase),
'number_unpublished' => $query->func()->count($unpublishedCase)
]);
The when() method accepts SQL snippets, array conditions, and Closure for when you need additional logic to build
the cases. If we wanted to classify cities into SMALL, MEDIUM, or LARGE based on population size, we could do
the following:
$query = $cities->find();
$sizing = $query->newExpr()->case()
->when(['population <' => 100000])
->then('SMALL')
->when($query->newExpr()->between('population', 100000, 999000))
->then('MEDIUM')
->when(['population >=' => 999001])
(continues on next page)
You need to be careful when including user provided data into case expressions as it can create SQL injection vulner-
abilities:
For more complex scenarios you can use QueryExpression objects and bound values:
$userValue = $query->newExpr()
->case()
->when($query->newExpr('population >= :userData'))
->then(123, 'integer');
By using bindings you can safely embed user data into complex raw SQL snippets.
then(), when() and else() will try to infer the value type based on the parameter type. If you need to bind a value
as a different type you can declare the desired type:
You can create if ... then ... else conditions by using else():
$published = $query->newExpr()
->case()
->when(['published' => true])
->then('Y');
->else('N');
Also, it’s possible to create the simple variant by passing a value to case():
$published = $query->newExpr()
->case($query->identifier('published'))
->when(true)
->then('Y');
->else('N');
The addCase function can also chain together multiple statements to create if .. then .. [elseif .. then ..
] [ .. else ] logic inside your SQL.
If we wanted to classify cities into SMALL, MEDIUM, or LARGE based on population size, we could do the following:
$query = $cities->find()
->where(function (QueryExpression $exp, SelectQuery $q) {
return $exp->addCase(
[
$q->newExpr()->lt('population', 100000),
$q->newExpr()->between('population', 100000, 999000),
$q->newExpr()->gte('population', 999001),
],
['SMALL', 'MEDIUM', 'LARGE'], # values matching conditions
['string', 'string', 'string'] # type of each value
);
});
# WHERE CASE
# WHEN population < 100000 THEN 'SMALL'
# WHEN population BETWEEN 100000 AND 999000 THEN 'MEDIUM'
# WHEN population >= 999001 THEN 'LARGE'
# END
Any time there are fewer case conditions than values, addCase will automatically produce an if .. then .. else
statement:
$query = $cities->find()
->where(function (QueryExpression $exp, SelectQuery $q) {
return $exp->addCase(
[
$q->newExpr()->eq('population', 0),
],
['DESERTED', 'INHABITED'], # values matching conditions
['string', 'string'] # type of each value
);
});
# WHERE CASE
# WHEN population = 0 THEN 'DESERTED' ELSE 'INHABITED' END
While ORMs and object result sets are powerful, creating entities is sometimes unnecessary. For example, when
accessing aggregated data, building an Entity may not make sense. The process of converting the database results to
entities is called hydration. If you wish to disable this process you can do this:
$query = $articles->find();
$query->enableHydration(false); // Results as arrays instead of entities
$result = $query->toList(); // Execute the query and return the array
After executing those lines, your result should look similar to this:
[
['id' => 1, 'title' => 'First Article', 'body' => 'Article 1 body' ...],
(continues on next page)
After your queries, you may need to do some post-processing. If you need to add a few calculated fields or derived
data, you can use the formatResults() method. This is a lightweight way to map over the result sets. If you need
more control over the process, or want to reduce results you should use the Map/Reduce feature instead. If you were
querying a list of people, you could calculate their age with a result formatter:
return $row;
});
});
As you can see in the example above, formatting callbacks will get a ResultSetDecorator as their first argument. The
second argument will be the Query instance the formatter was attached to. The $results argument can be traversed
and modified as necessary.
Result formatters are required to return an iterator object, which will be used as the return value for the query. Formatter
functions are applied after all the Map/Reduce routines have been executed. Result formatters can be applied from
within contained associations as well. CakePHP will ensure that your formatters are properly scoped. For example,
doing the following would work as you may expect:
return $author;
});
});
}]);
// Get results
$results = $query->all();
// Outputs 29
echo $results->first()->author->age;
As seen above, the formatters attached to associated query builders are scoped to operate only on the data in the
association. CakePHP will ensure that computed values are inserted into the correct entity.
Advanced Conditions
The query builder makes it simple to build complex where clauses. Grouped conditions can be expressed by providing
combining where() and expression objects. For simple queries, you can build conditions using an array of conditions:
$query = $articles->find()
->where([
'author_id' => 3,
'OR' => [['view_count' => 2], ['view_count' => 3]],
]);
If you’d prefer to avoid deeply nested arrays, you can use the callback form of where() to build your queries. The
callback accepts a QueryExpression which allows you to use the expression builder interface to build more complex
conditions without arrays. For example:
return $exp->or([
'promoted' => true,
$query->newExpr()->and([$author, $published])
]);
});
SELECT *
FROM articles
WHERE (
(
(author_id = 2 OR author_id = 3)
AND
(published = 1 AND view_count = 10)
)
OR promoted = 1
)
The QueryExpression passed to the callback allows you to use both combinators and conditions to build the full
expression.
Combinators
These create new QueryExpression objects and set how the conditions added to that expression are joined
together.
• and() creates new expression objects that joins all conditions with AND.
• or() creates new expression objects that joins all conditions with OR.
Conditions
These are added to the expression and automatically joined together depending on which combinator was used.
$query = $articles->find()
->where(function (QueryExpression $exp) {
return $exp
->eq('author_id', 2)
->eq('published', true)
->notEq('spam', true)
->gt('view_count', 10);
});
Since we started off using where(), we don’t need to call and(), as that happens implicitly. The above shows a few
new condition methods being combined with AND. The resulting SQL would look like:
SELECT *
FROM articles
WHERE (
author_id = 2
AND published = 1
AND spam != 1
AND view_count > 10)
However, if we wanted to use both AND & OR conditions we could do the following:
$query = $articles->find()
->where(function (QueryExpression $exp) {
$orConditions = $exp->or(['author_id' => 2])
->eq('author_id', 5);
return $exp
->add($orConditions)
->eq('published', true)
->gte('view_count', 10);
});
SELECT *
FROM articles
WHERE (
(author_id = 2 OR author_id = 5)
AND published = 1
AND view_count >= 10
)
The combinators also allow you pass in a callback which takes the new expression object as a parameter if you want
to separate the method chaining:
$query = $articles->find()
->where(function (QueryExpression $exp) {
$orConditions = $exp->or(function (QueryExpression $or) {
return $or->eq('author_id', 2)
->eq('author_id', 5);
});
(continues on next page)
return $exp
->not($orConditions)
->lte('view_count', 10);
});
$query = $articles->find()
->where(function (QueryExpression $exp) {
$orConditions = $exp->or(['author_id' => 2])
->eq('author_id', 5);
return $exp
->not($orConditions)
->lte('view_count', 10);
});
SELECT *
FROM articles
WHERE (
NOT (author_id = 2 OR author_id = 5)
AND view_count <= 10
)
$query = $articles->find()
->where(function (QueryExpression $exp, SelectQuery $q) {
$year = $q->func()->year([
'created' => 'identifier'
]);
return $exp
->gte($year, 2014)
->eq('published', true);
});
SELECT *
FROM articles
WHERE (
YEAR(created) >= 2014
AND published = 1
)
When using the expression objects you can use the following methods to create conditions:
• eq() Creates an equality condition:
$query = $cities->find()
->where(function (QueryExpression $exp, SelectQuery $q) {
return $exp->eq('population', '10000');
});
# WHERE population = 10000
$query = $cities->find()
->where(function (QueryExpression $exp, SelectQuery $q) {
return $exp->notEq('population', '10000');
});
# WHERE population != 10000
$query = $cities->find()
->where(function (QueryExpression $exp, SelectQuery $q) {
return $exp->like('name', '%A%');
});
# WHERE name LIKE "%A%"
$query = $cities->find()
->where(function (QueryExpression $exp, SelectQuery $q) {
return $exp->notLike('name', '%A%');
});
# WHERE name NOT LIKE "%A%"
$query = $cities->find()
->where(function (QueryExpression $exp, SelectQuery $q) {
return $exp->in('country_id', ['AFG', 'USA', 'EST']);
});
# WHERE country_id IN ('AFG', 'USA', 'EST')
$query = $cities->find()
->where(function (QueryExpression $exp, SelectQuery $q) {
return $exp->notIn('country_id', ['AFG', 'USA', 'EST']);
});
# WHERE country_id NOT IN ('AFG', 'USA', 'EST')
$query = $cities->find()
->where(function (QueryExpression $exp, SelectQuery $q) {
return $exp->gt('population', '10000');
});
# WHERE population > 10000
$query = $cities->find()
->where(function (QueryExpression $exp, SelectQuery $q) {
return $exp->gte('population', '10000');
});
# WHERE population >= 10000
$query = $cities->find()
->where(function (QueryExpression $exp, SelectQuery $q) {
return $exp->lt('population', '10000');
});
# WHERE population < 10000
$query = $cities->find()
->where(function (QueryExpression $exp, SelectQuery $q) {
return $exp->lte('population', '10000');
});
# WHERE population <= 10000
$query = $cities->find()
->where(function (QueryExpression $exp, SelectQuery $q) {
return $exp->isNull('population');
});
# WHERE (population) IS NULL
$query = $cities->find()
->where(function (QueryExpression $exp, SelectQuery $q) {
return $exp->isNotNull('population');
});
# WHERE (population) IS NOT NULL
$query = $cities->find()
->where(function (QueryExpression $exp, SelectQuery $q) {
return $exp->between('population', 999, 5000000);
});
# WHERE population BETWEEN 999 AND 5000000,
$subquery = $cities->find()
->select(['id'])
->where(function (QueryExpression $exp, SelectQuery $q) {
return $exp->equalFields('countries.id', 'cities.country_id');
})
->andWhere(['population >' => 5000000]);
(continues on next page)
$query = $countries->find()
->where(function (QueryExpression $exp, SelectQuery $q) use ($subquery) {
return $exp->exists($subquery);
});
# WHERE EXISTS (SELECT id FROM cities WHERE countries.id = cities.country_id AND␣
˓→population > 5000000)
$subquery = $cities->find()
->select(['id'])
->where(function (QueryExpression $exp, SelectQuery $q) {
return $exp->equalFields('countries.id', 'cities.country_id');
})
->andWhere(['population >' => 5000000]);
$query = $countries->find()
->where(function (QueryExpression $exp, SelectQuery $q) use ($subquery) {
return $exp->notExists($subquery);
});
# WHERE NOT EXISTS (SELECT id FROM cities WHERE countries.id = cities.country_id␣
˓→AND population > 5000000)
Expression objects should cover many commonly used functions and expressions. If you find yourself unable to cre-
ate the required conditions with expressions you can may be able to use bind() to manually bind parameters into
conditions:
$query = $cities->find()
->where([
'start_date BETWEEN :start AND :end',
])
->bind(':start', '2014-01-01', 'date')
->bind(':end', '2014-12-31', 'date');
In situations when you can’t get, or don’t want to use the builder methods to create the conditions you want you can
also use snippets of SQL in where clauses:
Warning: The field names used in expressions, and SQL snippets should never contain untrusted content as you
will create SQL Injection vectors. See the Using SQL Functions section for how to safely include unsafe data into
function calls.
When you need to reference a column or SQL identifier in your queries you can use the identifier() method:
$query = $countries->find();
$query->select([
'year' => $query->func()->year([$query->identifier('created')])
])
->where(function ($exp, $query) {
return $exp->gt('population', 100000);
});
$query = $this->Orders->find();
$query->select(['Customers.customer_name', 'total_orders' => $query->func()->count(
˓→'Orders.order_id')])
->contain('Customers')
->groupBy(['Customers.customer_name'])
->having(['total_orders >=' => $query->identifier('Customers.minimum_order_count')]);
Warning: To prevent SQL injections, Identifier expressions should never have untrusted data passed into them.
Collation
In situations that you need to deal with accented characters, multilingual data or case-sensitive comparisons, you can
use the $collation parameter of IdentifierExpression or StringExpression to apply a character expression
to a certain collation:
use Cake\Database\Expression\IdentifierExpression;
When building queries using the ORM, you will generally not have to indicate the data types of the columns you are
interacting with, as CakePHP can infer the types based on the schema data. If in your queries you’d like CakePHP to
automatically convert equality to IN comparisons, you’ll need to indicate the column data type:
$query = $articles->find()
->where(['id' => $ids], ['id' => 'integer[]']);
The above will automatically create id IN (...) instead of id = ?. This can be useful when you do not know
whether you will get a scalar or array of parameters. The [] suffix on any data type name indicates to the query builder
that you want the data handled as an array. If the data is not an array, it will first be cast to an array. After that, each
value in the array will be cast using the type system. This works with complex types as well. For example, you could
take a list of DateTime objects using:
$query = $articles->find()
->where(['post_date' => $dates], ['post_date' => 'date[]']);
When a condition value is expected to be null or any other value, you can use the IS operator to automatically create
the correct expression:
$query = $categories->find()
->where(['parent_id IS' => $parentId]);
The above will generate``parent_id = :c1`` or parent_id IS NULL depending on the type of $parentId
When a condition value is expected not to be null or any other value, you can use the IS NOT operator to automatically
create the correct expression:
$query = $categories->find()
->where(['parent_id IS NOT' => $parentId]);
The above will generate``parent_id != :c1`` or parent_id IS NOT NULL depending on the type of $parentId
Raw Expressions
When you cannot construct the SQL you need using the query builder, you can use expression objects to add snippets
of SQL to your queries:
$query = $articles->find();
$expr = $query->newExpr()->add('1 + 1');
$query->select(['two' => $expr]);
Expression objects can be used with any query builder methods like where(), limit(), groupBy(), select()
and many other methods.
Warning: Using expression objects leaves you vulnerable to SQL injection. You should never use untrusted data
into expressions.
If you have configured Read and Write Connections in your application, you can have a query run on the read connec-
tion using one of the role methods:
Expression Conjuction
It is possible to change the conjunction used to join conditions in a query expression using the method
setConjunction:
$query = $articles->find();
$expr = $query->newExpr(['1','1'])->setConjunction('+');
$query->select(['two' => $expr]);
$query = $products->find();
$query->select(function ($query) {
$stockQuantity = $query->func()->sum('Stocks.quantity');
$totalStockValue = $query->func()->sum(
$query->newExpr(['Stocks.quantity', 'Products.unit_price'])
->setConjunction('*')
);
return [
'Products.name',
'stock_quantity' => $stockQuantity,
'Products.unit_price',
'total_stock_value' => $totalStockValue
];
})
->innerJoinWith('Stocks')
->groupBy(['Products.id', 'Products.name', 'Products.unit_price']);
Tuple Comparison
Tuple comparison involves comparing two rows of data (tuples) element by element, typically using comparison oper-
ators like <, >, =:
$products->find()
->where([
'OR' => [
['unit_price <' => 20],
(continues on next page)
use Cake\Database\Expression\TupleComparison;
$products->find()
->where(
new TupleComparison(
['unit_price', 'tax_percentage'],
[20, 5],
['integer', 'integer'], # type of each value
'<='
)
);
Tuple Comparison can also be used with IN and the result can be transformed even on DBMS that does not natively
support it:
$articles->find()
->where(
new TupleComparison(
['articles.id', 'articles.author_id'],
[[10, 10], [30, 10]],
['integer', 'integer'],
'IN'
),
);
Getting Results
Once you’ve made your query, you’ll want to retrieve rows from it. There are a few ways of doing this:
You can use any of the collection methods on your query objects to pre-process or transform the results:
You can use first or firstOrFail to retrieve a single record. These methods will alter the query adding a LIMIT 1
clause:
Using a single query object, it is possible to obtain the total number of rows found for a set of conditions:
The count() method will ignore the limit, offset and page clauses, thus the following will return the same result:
This is useful when you need to know the total result set size in advance, without having to construct another
SelectQuery object. Likewise, all result formatting and map-reduce routines are ignored when using the count()
method.
Moreover, it is possible to return the total count for a query containing group by clauses without having to rewrite the
query in any way. For example, consider this query for retrieving article ids and their comments count:
$query = $articles->find();
$query->select(['Articles.id', $query->func()->count('Comments.id')])
->matching('Comments')
->groupBy(['Articles.id']);
$total = $query->count();
After counting, the query can still be used for fetching the associated records:
$list = $query->all();
Sometimes, you may want to provide an alternate method for counting the total records of a query. One common
use case for this is providing a cached value or an estimate of the total rows, or to alter the query to remove expensive
unneeded parts such as left joins. This becomes particularly handy when using the CakePHP built-in pagination system
which calls the count() method:
In the example above, when the pagination component calls the count method, it will receive the estimated hard-coded
number of rows.
When fetching entities that don’t change often you may want to cache the results. The SelectQuery class makes this
simple:
$query->cache('recent_articles');
Will enable caching on the query’s result set. If only one argument is provided to cache() then the ‘default’ cache
configuration will be used. You can control which caching configuration is used with the second parameter:
// Instance of CacheEngine
$query->cache('recent_articles', $memcache);
In addition to supporting static keys, the cache() method accepts a function to generate the key. The function you give
it will receive the query as an argument. You can then read aspects of the query to dynamically generate the cache key:
The cache method makes it simple to add cached results to your custom finders or through event listeners.
When the results for a cached query are fetched the following happens:
1. If the query has results set, those will be returned.
2. The cache key will be resolved and cache data will be read. If the cache data is not empty, those results will be
returned.
3. If the cache misses, the query will be executed, the Model.beforeFind event will be triggered, and a new
ResultSet will be created. This ResultSet will be written to the cache and returned.
Loading Associations
The builder can help you retrieve data from multiple tables at the same time with the minimum amount of queries
possible. To be able to fetch associated data, you first need to setup associations between the tables as described in
the Associations - Linking Tables Together section. This technique of combining queries to fetch associated data from
other tables is called eager loading.
Eager loading helps avoid many of the potential performance problems surrounding lazy-loading in an ORM. The
queries generated by eager loading can better leverage joins, allowing more efficient queries to be made. In CakePHP
you state which associations should be eager loaded using the ‘contain’ method:
// As an option to find()
$query = $articles->find('all', contain: ['Authors', 'Comments']);
The above will load the related author and comments for each article in the result set. You can load nested associations
using nested arrays to define the associations to be loaded:
$query = $articles->find()->contain([
'Authors' => ['Addresses'], 'Comments' => ['Authors']
]);
Alternatively, you can express nested associations using the dot notation:
$query = $articles->find()->contain([
'Authors.Addresses',
'Comments.Authors'
]);
$query = $products->find()->contain([
'Shops.Cities.Countries',
'Shops.Managers'
]);
$query = $products->find()->contain([
'Shops' => ['Cities.Countries', 'Managers']
]);
You can select fields from all associations with multiple contain() statements:
$query = $this->find()->select([
'Realestates.id',
'Realestates.title',
'Realestates.description'
])
->contain([
(continues on next page)
If you need to reset the containments on a query you can set the second argument to true:
$query = $articles->find();
$query->contain(['Authors', 'Comments'], true);
Note: Association names in contain() calls should use the same association casing as in your association definitions,
not the property name used to hold the association record(s). For example, if you have declared an association as
belongsTo('Users') then you must use contain('Users') and not contain('users') or contain('user').
When using contain() you are able to restrict the data returned by the associations and filter them by conditions. To
specify conditions, pass an anonymous function that receives as the first argument a query object, \Cake\ORM\Query\
SelectQuery:
$this->paginate['contain'] = [
'Comments' => function (SelectQuery $query) {
return $query->select(['body', 'author_id'])
->where(['Comments.approved' => true]);
}
];
Warning: If the results are missing association entities, make sure the foreign key columns are selected in the
query. Without the foreign keys, the ORM cannot find matching rows.
$query = $articles->find()->contain([
'Comments',
'Authors.Profiles' => function (SelectQuery $q) {
return $q->where(['Profiles.is_published' => true]);
}
]);
In the above example, you’ll still get authors even if they don’t have a published profile. To only get authors with a
published profile use matching(). If you have defined custom finders in your associations, you can use them inside
contain():
// Bring all articles, but only bring the comments that are approved and
// popular.
$query = $articles->find()->contain('Comments', function (SelectQuery $q) {
return $q->find('approved')->find('popular');
});
Note: With BelongsTo and HasOne associations only select and where clauses are valid in the contain() query.
With HasMany and BelongsToMany all clauses such as order() are valid.
You can control more than just the query clauses used by contain(). If you pass an array with the association, you
can override the foreignKey, joinType and strategy. See Associations - Linking Tables Together for details on
the default value and options for each association type.
You can pass false as the new foreignKey to disable foreign key constraints entirely. Use the queryBuilder option
to customize the query when using an array:
$query = $articles->find()->contain([
'Authors' => [
'foreignKey' => false,
'queryBuilder' => function (SelectQuery $q) {
return $q->where(/* ... */); // Full conditions for filtering
}
]
]);
If you have limited the fields you are loading with select() but also want to load fields off of contained associations,
you can pass the association object to select():
// Select id & title from articles, but all fields off of Users.
$query = $articles->find()
->select(['id', 'title'])
->select($articles->Users)
->contain(['Users']);
// Select id & title from articles, but all fields off of Users.
$query = $articles->find()
->select(['id', 'title'])
->contain(['Users' => function(SelectQuery $q) {
return $q->enableAutoFields();
}]);
When loading HasMany and BelongsToMany associations, you can use the sort option to sort the data in those asso-
ciations:
$query->contain([
'Comments' => [
'sort' => ['Comments.created' => 'DESC']
]
]);
A fairly common query case with associations is finding records ‘matching’ specific associated data. For example if
you have ‘Articles belongsToMany Tags’ you will probably want to find Articles that have the CakePHP tag. This is
extremely simple to do with the ORM in CakePHP:
$query = $articles->find();
$query->matching('Tags', function ($q) {
return $q->where(['Tags.name' => 'CakePHP']);
});
You can apply this strategy to HasMany associations as well. For example if ‘Authors HasMany Articles’, you could
find all the authors with recently published articles using the following:
$query = $authors->find();
$query->matching('Articles', function ($q) {
return $q->where(['Articles.created >=' => new DateTime('-10 days')]);
});
Filtering by deep associations uses the same predictable syntax from contain():
// Bring unique articles that were commented by `markstory` using passed variable
// Dotted matching paths should be used over nested matching() calls
(continues on next page)
Note: As this function will create an INNER JOIN, you might want to consider calling distinct on the find query as
you might get duplicate rows if your conditions don’t exclude them already. This might be the case, for example, when
the same users comments more than once on a single article.
The data from the association that is ‘matched’ will be available on the _matchingData property of entities. If both
match and contain the same association, you can expect to get both the _matchingData and standard association
properties in your results.
Using innerJoinWith
Sometimes you need to match specific associated data but without actually loading the matching records like
matching(). You can create just the INNER JOIN that matching() uses with innerJoinWith():
$query = $articles->find();
$query->innerJoinWith('Tags', function ($q) {
return $q->where(['Tags.name' => 'CakePHP']);
});
$query = $products->find()->innerJoinWith(
'Shops.Cities.Countries', function ($q) {
return $q->where(['Countries.name' => 'Japan']);
}
);
You can combine innerJoinWith() and contain() with the same association when you want to match specific
records and load the associated data together. The example below matches Articles that have specific Tags and loads
the same Tags:
Note: If you use innerJoinWith() and want to select() fields from that association, you need to use an alias for
the field:
$query
->select(['country_name' => 'Countries.name'])
->innerJoinWith('Countries');
If you don’t use an alias, you will see the data in _matchingData as described by matching() above. This is an edge
case from matching() not knowing you manually selected the field.
Warning: You should not combine innerJoinWith() and matching() with the same association. This will
produce multiple INNER JOIN statements and might not create the query you expected.
Using notMatching
The opposite of matching() is notMatching(). This function will change the query so that it filters results that have
no relation to the specified association:
$query = $articlesTable
->find()
->notMatching('Tags', function ($q) {
return $q->where(['Tags.name' => 'boring']);
});
The above example will find all articles that were not tagged with the word boring. You can apply this method to
HasMany associations as well. You could, for example, find all the authors with no published articles in the last 10
days:
$query = $authorsTable
->find()
->notMatching('Articles', function ($q) {
return $q->where(['Articles.created >=' => new \DateTime('-10 days')]);
});
It is also possible to use this method for filtering out records not matching deep associations. For example, you could
find articles that have not been commented on by a certain user:
$query = $articlesTable
->find()
->notMatching('Comments.Users', function ($q) {
return $q->where(['username' => 'jose']);
});
Since articles with no comments at all also satisfy the condition above, you may want to combine matching() and
notMatching() in the same query. The following example will find articles having at least one comment, but not
commented by a certain user:
$query = $articlesTable
->find()
->notMatching('Comments.Users', function ($q) {
return $q->where(['username' => 'jose']);
(continues on next page)
Note: As notMatching() will create a LEFT JOIN, you might want to consider calling distinct on the find query
as you can get duplicate rows otherwise.
Keep in mind that contrary to the matching() function, notMatching() will not add any data to the _matchingData
property in the results.
Using leftJoinWith
On certain occasions you may want to calculate a result based on an association, without having to load all the records
for it. For example, if you wanted to load the total number of comments an article has along with all the article data,
you can use the leftJoinWith() function:
$query = $articlesTable->find();
$query->select(['total_comments' => $query->func()->count('Comments.id')])
->leftJoinWith('Comments')
->groupBy(['Articles.id'])
->enableAutoFields(true);
The results for the above query will contain the article data and the total_comments property for each of them.
leftJoinWith() can also be used with deeply nested associations. This is useful, for example, for bringing the count
of articles tagged with a certain word, per author:
$query = $authorsTable
->find()
->select(['total_articles' => $query->func()->count('Articles.id')])
->leftJoinWith('Articles.Tags', function ($q) {
return $q->where(['Tags.name' => 'awesome']);
})
->groupBy(['Authors.id'])
->enableAutoFields(true);
This function will not load any columns from the specified associations into the result set.
Adding Joins
In addition to loading related data with contain(), you can also add additional joins with the query builder:
$query = $articles->find()
->join([
'table' => 'comments',
'alias' => 'c',
'type' => 'LEFT',
'conditions' => 'c.article_id = articles.id',
]);
You can append multiple joins at the same time by passing an associative array with multiple joins:
$query = $articles->find()
->join([
'c' => [
'table' => 'comments',
'type' => 'LEFT',
'conditions' => 'c.article_id = articles.id',
],
'u' => [
'table' => 'users',
'type' => 'INNER',
'conditions' => 'u.id = articles.user_id',
]
]);
As seen above, when adding joins the alias can be the outer array key. Join conditions can also be expressed as an array
of conditions:
$query = $articles->find()
->join([
'c' => [
'table' => 'comments',
'type' => 'LEFT',
'conditions' => [
'c.created >' => new DateTime('-5 days'),
'c.moderated' => true,
'c.article_id = articles.id'
]
],
], ['c.created' => 'datetime', 'c.moderated' => 'boolean']);
When creating joins by hand and using array based conditions, you need to provide the datatypes for each column in
the join conditions. By providing datatypes for the join conditions, the ORM can correctly convert data types into SQL.
In addition to join() you can use rightJoin(), leftJoin() and innerJoin() to create joins:
It should be noted that if you set the quoteIdentifiers option to true when defining your Connection, join con-
ditions between table fields should be set as follow:
$query = $articles->find()
->join([
'c' => [
'table' => 'comments',
'type' => 'LEFT',
'conditions' => [
'c.article_id' => new \Cake\Database\Expression\IdentifierExpression(
˓→'articles.id'),
],
],
]);
This ensures that all of your identifiers will be quoted across the Query, avoiding errors with some database Drivers
(PostgreSQL notably)
Inserting Data
Unlike earlier examples, you should can’t use find() to create insert queries. Instead, create a new InsertQuery
object using insertQuery():
$query = $articles->insertQuery();
$query->insert(['title', 'body'])
->values([
'title' => 'First post',
'body' => 'Some body text',
])
->execute();
To insert multiple rows with only one query, you can chain the values() method as many times as you need:
$query = $articles->insertQuery();
$query->insert(['title', 'body'])
->values([
'title' => 'First post',
'body' => 'Some body text',
])
->values([
'title' => 'Second post',
'body' => 'Another body text',
])
->execute();
Generally, it is easier to insert data using entities and save(). By composing a SELECT and INSERT query together,
you can create INSERT INTO ... SELECT style queries:
$select = $articles->find()
->select(['title', 'body', 'published'])
(continues on next page)
$query = $articles->insertQuery()
->insert(['title', 'body', 'published'])
->values($select)
->execute();
Note: Inserting records with the query builder will not trigger events such as Model.afterSave. Instead you should
use the ORM to save data.
Updating Data
As with insert queries, you should not use find() to create update queries. Instead, create new a Query object using
updateQuery():
$query = $articles->updateQuery();
$query->set(['published' => true])
->where(['id' => $id])
->execute();
Note: Updating records with the query builder will not trigger events such as Model.afterSave. Instead you should
use the ORM to save data.
Deleting Data
As with insert queries, you can’t use find() to create delete queries. Instead, create new a query object using
deleteQuery():
$query = $articles->deleteQuery();
$query->where(['id' => $id])
->execute();
While the ORM and database abstraction layers prevent most SQL injections issues, it is still possible to leave yourself
vulnerable through improper use.
When using condition arrays, the key/left-hand side as well as single value entries must not contain user data:
$query->where([
// Data on the key/left-hand side is unsafe, as it will be
// inserted into the generated query as-is
$userData => $value,
(continues on next page)
When using the expression builder, column names must not contain user data:
When building function expressions, function names should never contain user data:
// Not safe.
$query->func()->{$userData}($arg1);
$expr = $query->newExpr()->add($userData);
$query->select(['two' => $expr]);
Binding values
It is possible to protect against many unsafe situations by using bindings. Values can be bound to queries using the
Cake\Database\Query::bind() method.
The following example would be a safe variant of the unsafe, SQL injection prone example given above:
$query
->where([
'MATCH (comment) AGAINST (:userData)',
'created < NOW() - :moreUserData',
])
->bind(':userData', $userData, 'string')
->bind(':moreUserData', $moreUserData, 'datetime');
If your application requires using more complex queries, you can express many complex queries using the ORM query
builder.
Unions
$inReview = $articles->find()
->where(['need_review' => true]);
$unpublished = $articles->find()
->where(['published' => false]);
$unpublished->union($inReview);
You can create UNION ALL queries using the unionAll() method:
$inReview = $articles->find()
->where(['need_review' => true]);
$unpublished = $articles->find()
->where(['published' => false]);
$unpublished->unionAll($inReview);
Subqueries
Subqueries enable you to compose queries together and build conditions and results based on the results of other
queries:
$matchingComment = $articles->getAssociation('Comments')->find()
->select(['article_id'])
->distinct()
->where(['comment LIKE' => '%CakePHP%']);
Subqueries are accepted anywhere a query expression can be used. For example, in the select(), from() and join()
methods. The above example uses a standard ORM\Query\SelectQuery object that will generate aliases, these aliases
can make referencing results in the outer query more complex. As of 4.2.0 you can use Table::subquery() to create
a specialized query instance that will not generate aliases:
$comments = $articles->getAssociation('Comments')->getTarget();
$matchingComment = $comments->subquery()
->select(['article_id'])
->distinct()
->where(['comment LIKE' => '%CakePHP%']);
$query = $articles->find()
->where(['id IN' => $matchingComment]);
Most relational database vendors support taking out locks when doing select operations. You can use the epilog()
method for this:
// In MySQL
$query->epilog('FOR UPDATE');
The epilog() method allows you to append raw SQL to the end of queries. You should never put raw user data into
epilog().
Window Functions
Window functions allow you to perform calculations using rows related to the current row. They are commonly used
to calculate totals or offsets on partial sets of rows in the query. For example if we wanted to find the date of the earliest
and latest comment on each article we could use window functions:
$query = $articles->find();
$query->select([
'Articles.id',
'Articles.title',
'Articles.user_id'
'oldest_comment' => $query->func()
->min('Comments.created')
->partition('Comments.article_id'),
'latest_comment' => $query->func()
->max('Comments.created')
->partition('Comments.article_id'),
])
->innerJoinWith('Comments');
Window expressions can be applied to most aggregate functions. Any aggregate function that cake abstracts with a
wrapper in FunctionsBuilder will return an AggregateExpression which lets you attach window expressions.
You can create custom aggregate functions through FunctionsBuilder::aggregate().
These are the most commonly supported window features. Most features are provided by AggregateExpresion, but
make sure you follow your database documentation on use and restrictions.
• orderBy($fields) Order the aggregate group the same as a query ORDER BY.
• partition($expressions) Add one or more partitions to the window based on column names.
• rows($start, $end) Define a offset of rows that precede and/or follow the current row that should be included
in the aggregate function.
• range($start, $end) Define a range of row values that precede and/or follow the current row that should be
included in the aggregate function. This evaluates values based on the orderBy() field.
If you need to re-use the same window expression multiple times you can create named windows using the window()
method:
$query = $articles->find();
return $window;
});
$query->select([
'Articles.id',
'Articles.title',
'Articles.user_id'
'oldest_comment' => $query->func()
->min('Comments.created')
->over('related_article'),
'latest_comment' => $query->func()
->max('Comments.created')
->over('related_article'),
]);
Common Table Expressions or CTE are useful when building reporting queries where you need to compose the results
of several smaller query results together. They can serve a similar purpose to database views or subquery results.
Common Table Expressions differ from derived tables and views in a couple ways:
1. Unlike views, you don’t have to maintain schema for common table expressions. The schema is implicitly based
on the result set of the table expression.
2. You can reference the results of a common table expression multiple times without incurring performance penal-
ties unlike subquery joins.
As an example lets fetch a list of customers and the number of orders each of them has made. In SQL we would use:
WITH orders_per_customer AS (
SELECT COUNT(*) AS order_count, customer_id FROM orders GROUP BY customer_id
)
SELECT name, orders_per_customer.order_count
FROM customers
INNER JOIN orders_per_customer ON orders_per_customer.customer_id = customers.id
To build that query with the ORM query builder we would use:
If you need to build a recursive query (WITH RECURSIVE ...), chain ->recursive() onto return $cte.
While the query builder makes most queries possible through builder methods, very complex queries can be tedious
and complicated to build. You may want to execute the desired SQL directly.
Executing SQL directly allows you to fine tune the query that will be run. However, doing so doesn’t let you use
contain or other higher level ORM features.
Table Objects
class Cake\ORM\Table
Table objects provide access to the collection of entities stored in a specific table. Each table in your application should
have an associated Table class which is used to interact with a given table. If you do not need to customize the behavior
of a given table CakePHP will generate a Table instance for you to use.
Before trying to use Table objects and the ORM, you should ensure that you have configured your database connection.
Basic Usage
To get started, create a Table class. These classes live in src/Model/Table. Tables are a type model collection specific
to relational databases, and the main interface to your database in CakePHP’s ORM. The most basic table class would
look like:
// src/Model/Table/ArticlesTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
Note that we did not tell the ORM which table to use for our class. By convention table objects will use a table that
matches the lower cased and underscored version of the class name. In the above example the articles table will be
used. If our table class was named BlogPosts your table should be named blog_posts. You can specify the table to
use by using the setTable() method:
namespace App\Model\Table;
use Cake\ORM\Table;
No inflection conventions will be applied when specifying a table. By convention the ORM also expects each table to
have a primary key with the name of id. If you need to modify this you can use the setPrimaryKey() method:
namespace App\Model\Table;
use Cake\ORM\Table;
By default table objects use an entity class based on naming conventions. For example if your table class is called
ArticlesTable the entity would be Article. If the table class was PurchaseOrdersTable the entity would
be PurchaseOrder. If however, you want to use an entity that doesn’t follow the conventions you can use the
setEntityClass() method to change things up:
As seen in the examples above Table objects have an initialize() method which is called at the end of the construc-
tor. It is recommended that you use this method to do initialization logic instead of overriding the constructor.
Before you can query a table, you’ll need to get an instance of the table. You can do this by using the TableLocator
class:
// In a controller
$articles = $this->fetchTable('Articles');
TableLocator provides the various dependencies for constructing a table, and maintains a registry of all the con-
structed table instances making it easier to build relations and configure the ORM. See Using the TableLocator for
more information.
If your table class is in a plugin, be sure to use the correct name for your table class. Failing to do so can result in
validation rules, or callbacks not being triggered as a default class is used instead of your actual class. To correctly load
plugin table classes use the following:
// Plugin table
$articlesTable = $this->fetchTable('PluginName.Articles');
Lifecycle Callbacks
As you have seen above table objects trigger a number of events. Events are useful if you want to hook into the ORM
and add logic in without subclassing or overriding methods. Event listeners can be defined in table or behavior classes.
You can also use a table’s event manager to bind listeners in.
When using callback methods behaviors attached in the initialize() method will have their listeners fired before
the table callback methods are triggered. This follows the same sequencing as controllers & components.
To add an event listener to a Table class or Behavior simply implement the method signatures as described below. See
the Events System for more detail on how to use the events subsystem:
// In a controller
$articles->save($article, ['customVariable1' => 'yourValue1']);
// In ArticlesTable.php
public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options)
{
$customVariable = $options['customVariable1']; // 'yourValue1'
$options['customVariable2'] = 'yourValue2';
}
{
$customVariable = $options['customVariable1']; // 'yourValue1'
$customVariable = $options['customVariable2']; // 'yourValue2'
}
Event List
• Model.initialize
• Model.beforeMarshal
• Model.afterMarshal
• Model.beforeFind
• Model.buildValidator
• Model.buildRules
• Model.beforeRules
• Model.afterRules
• Model.beforeSave
• Model.afterSave
• Model.afterSaveCommit
• Model.beforeDelete
• Model.afterDelete
• Model.afterDeleteCommit
initialize
The Model.initialize event is fired after the constructor and initialize methods are called. The Table classes do
not listen to this event by default, and instead use the initialize hook method.
To respond to the Model.initialize event you can create a listener class which implements
EventListenerInterface:
use Cake\Event\EventListenerInterface;
class ModelInitializeListener implements EventListenerInterface
{
public function implementedEvents()
{
return [
'Model.initialize' => 'initializeEvent',
];
}
use Cake\Event\EventManager;
$listener = new ModelInitializeListener();
EventManager::instance()->attach($listener);
This will call the initializeEvent when any Table class is constructed.
beforeMarshal
The Model.beforeMarshal event is fired before request data is converted into entities. See the Modifying Request
Data Before Building Entities documentation for more information.
afterMarshal
The Model.afterMarshal event is fired after request data is converted into entities. Event handlers will get the
converted entities, original request data and the options provided to the patchEntity() or newEntity() call.
beforeFind
The Model.beforeFind event is fired before each find operation. By stopping the event, and feeding the query with
a custom result set, you can bypass the find operation entirely:
{
if (/* ... */) {
$event->stopPropagation();
$query->setResult(new \Cake\Datasource\ResultSetDecorator([]));
return;
}
// ...
}
In this example, no further beforeFind events will be triggered on the related table or its attached behaviors (though
behavior events are usually invoked earlier given their default priorities), and the query will return the empty result set
that was passed via SelectQuery::setResult().
Any changes done to the $query instance will be retained for the rest of the find. The $primary parameter indicates
whether or not this is the root query, or an associated query. All associations participating in a query will have a
Model.beforeFind event triggered. For associations that use joins, a dummy query will be provided. In your event
listener you can set additional fields, conditions, joins or result formatters. These options/features will be copied onto
the root query.
In previous versions of CakePHP there was an afterFind callback, this has been replaced with the Modifying Results
with Map/Reduce features and entity constructors.
buildValidator
The Model.buildValidator event is fired when $name validator is created. Behaviors, can use this hook to add in
validation methods.
buildRules
The Model.buildRules event is fired after a rules instance has been created and after the table’s buildRules()
method has been called.
beforeRules
The Model.beforeRules event is fired before an entity has had rules applied. By stopping this event, you can halt
the rules checking and set the result of applying rules.
afterRules
The Model.afterRules event is fired after an entity has rules applied. By stopping this event, you can return the final
value of the rules checking operation.
beforeSave
The Model.beforeSave event is fired before each entity is saved. Stopping this event will abort the save operation.
When the event is stopped the result of the event will be returned.
afterSave
afterSaveCommit
The Model.afterSaveCommit event is fired after the transaction in which the save operation is wrapped has been
committed. It’s also triggered for non atomic saves where database operations are implicitly committed. The event is
triggered only for the primary table on which save() is directly called. The event is not triggered if a transaction is
started before calling save.
beforeDelete
The Model.beforeDelete event is fired before an entity is deleted. By stopping this event you will abort the delete
operation. When the event is stopped the result of the event will be returned.
afterDelete
afterDeleteCommit
The Model.afterDeleteCommit event is fired after the transaction in which the delete operation is wrapped has been
is committed. It’s also triggered for non atomic deletes where database operations are implicitly committed. The event
is triggered only for the primary table on which delete() is directly called. The event is not triggered if a transaction
is started before calling delete.
To prevent the save from continuing, simply stop event propagation in your callback:
{
if (...) {
$event->stopPropagation();
$event->setResult(false);
return;
}
...
}
Alternatively, you can return false from the callback. This has the same effect as stopping event propagation.
Callback priorities
When using events on your tables and behaviors be aware of the priority and the order listeners are attached. Behavior
events are attached before Table events are. With the default priorities this means that Behavior callbacks are triggered
before the Table event with the same name.
As an example, if your Table is using TreeBehavior the TreeBehavior::beforeDelete() method will be called
before your table’s beforeDelete() method, and you will not be able to work wth the child nodes of the record being
deleted in your Table’s method.
You can manage event priorities in one of a few ways:
1. Change the priority of a Behavior’s listeners using the priority option. This will modify the priority of all
callback methods in the Behavior:
2. Modify the priority in your Table class by using the Model.implementedEvents() method. This allows
you to assign a different priority per callback-function:
// In a Table class.
public function implementedEvents()
{
$events = parent::implementedEvents();
$events['Model.beforeDelete'] = [
'callable' => 'beforeDelete',
'priority' => 3
];
return $events;
}
Behaviors
Behaviors provide a way to create horizontally re-usable pieces of logic related to table classes. You may be wondering
why behaviors are regular classes and not traits. The primary reason for this is event listeners. While traits would allow
for re-usable pieces of logic, they would complicate binding events.
To add a behavior to your table you can call the addBehavior() method. Generally the best place to do this is in the
initialize() method:
namespace App\Model\Table;
use Cake\ORM\Table;
As with associations, you can use plugin syntax and provide additional configuration options:
namespace App\Model\Table;
use Cake\ORM\Table;
You can find out more about behaviors, including the behaviors provided by CakePHP in the chapter on Behaviors.
Configuring Connections
By default all table instances use the default database connection. If your application uses multiple database con-
nections you will want to configure which tables use which connections. This is the defaultConnectionName()
method:
namespace App\Model\Table;
use Cake\ORM\Table;
class Cake\ORM\TableLocator
As we’ve seen earlier, the TableLocator class provides a way to use a factory/registry for accessing your applications
table instances. It provides a few other useful features as well.
Cake\ORM\TableLocator::get($alias, $config)
When loading tables from the registry you can customize their dependencies, or use mock objects by providing an
$options array:
$articles = FactoryLocator::get('Table')->get('Articles', [
'className' => 'App\Custom\ArticlesTable',
'table' => 'my_articles',
(continues on next page)
Pay attention to the connection and schema configuration settings, they aren’t string values but objects. The connection
will take an object of Cake\Database\Connection and schema Cake\Database\Schema\Collection.
Note: If your table also does additional configuration in its initialize() method, those values will overwrite the
ones provided to the registry.
You can also pre-configure the registry using the setConfig() method. Configuration data is stored per alias, and
can be overridden by an object’s initialize() method:
Note: You can only configure a table before or during the first time you access that alias. Doing it after the registry
is populated will have no effect.
Cake\ORM\TableLocator::clear()
During test cases you may want to flush the registry. Doing so is often useful when you are using mock objects, or
modifying a table’s dependencies:
FactoryLocator::get('Table')->clear();
If you have not followed the conventions it is likely that your Table or Entity classes will not be detected by CakePHP.
In order to fix this, you can set a namespace with the Cake\Core\Configure::write method. As an example:
/src
/App
/My
/Namespace
/Model
/Entity
/Table
Cake\Core\Configure::write('App.namespace', 'App\My\Namespace');
Entities
class Cake\ORM\Entity
While Table Objects represent and provide access to a collection of objects, entities represent individual rows or domain
objects in your application. Entities contain methods to manipulate and access the data they contain. Fields can also
be accessed as properties on the object.
Entities are created for you each time you iterate the query instance returned by find() of a table object or when you
call all() or first() method of the query instance.
You don’t need to create entity classes to get started with the ORM in CakePHP. However, if you want to have custom
logic in your entities you will need to create classes. By convention entity classes live in src/Model/Entity/. If our
application had an articles table we could create the following entity:
// src/Model/Entity/Article.php
namespace App\Model\Entity;
use Cake\ORM\Entity;
Right now this entity doesn’t do very much. However, when we load data from our articles table, we’ll get instances of
this class.
Note: If you don’t define an entity class CakePHP will use the basic Entity class.
Creating Entities
use App\Model\Entity\Article;
When instantiating an entity you can pass the fields with the data you want to store in them:
use App\Model\Entity\Article;
The preferred way of getting new entities is using the newEmptyEntity() method from the Table objects:
use Cake\ORM\Locator\LocatorAwareTrait;
$article = $this->fetchTable('Articles')->newEmptyEntity();
$article = $this->fetchTable('Articles')->newEntity([
'id' => 1,
'title' => 'New Article',
'created' => new DateTime('now')
]);
Note: Prior to CakePHP 4.3 you need to use $this->getTableLocator->get('Articles') to get the table
instance.
Entities provide a few ways to access the data they contain. Most commonly you will access the data in an entity using
object notation:
use App\Model\Entity\Article;
Cake\ORM\Entity::get($field)
For example:
When using set() you can update multiple fields at once using an array:
$article->set([
'title' => 'My first post',
'body' => 'It is the best ever!'
]);
Warning: When updating entities with request data you should configure which fields can be set with mass
assignment.
You can check if fields are defined in your entities with has():
The has() method will return true if a field is defined. You can use isEmpty() and hasValue() to check if a field
contains a ‘non-empty’ value:
$article->has('user_id'); // true
$article->isEmpty('user_id'); // true
$article->hasValue('user_id'); // false
$article->has('text'); // true
$article->isEmpty('text'); // true
$article->hasValue('text'); // false
$article->has('links'); // true
$article->isEmpty('links'); // true
$article->hasValue('links'); // false
If you often partially load entities you should enable strict-property access behavior to ensure you’re not using properties
that haven’t been loaded. On a per-entity basis you can enable this behavior:
$article->requireFieldPresence();
Once enabled, accessing properties that are not defined will raise a CakeORMMissingPropertyException.
In addition to the simple get/set interface, entities allow you to provide accessors and mutator methods. These methods
let you customize how fields are read or set.
Accessors
Accessors let you customize how fields are read. They use the convention of _get(FieldName) with (FieldName)
being the CamelCased version (multiple words are joined together to a single word with the first letter of each word
capitalized) of the field name.
They receive the basic value stored in the _fields array as their only argument. For example:
namespace App\Model\Entity;
use Cake\ORM\Entity;
The example above converts the value of the title field to an uppercase version each time it is read. It would be run
when getting the field through any of these two ways:
Note: Code in your accessors is executed each time you reference the field. You can use a local variable to cache it if
you are performing a resource-intensive operation in your accessor like this: $myEntityProp = $entity->my_property.
Warning: Accessors will be used when saving entities, so be careful when defining methods that format data, as
the formatted data will be persisted.
Mutators
You can customize how fields get set by defining a mutator. They use the convention of _set(FieldName) with
(FieldName) being the CamelCased version of the field name.
Mutators should always return the value that should be stored in the field. You can also use mutators to set other fields.
When doing this, be careful to not introduce any loops, as CakePHP will not prevent infinitely looping mutator methods.
For example:
namespace App\Model\Entity;
use Cake\ORM\Entity;
(continues on next page)
return strtoupper($title);
}
}
The example above is doing two things: It stores a modified version of the given value in the slug field and stores an
uppercase version in the title field. It would be run when setting the field through any of these two ways:
$user->title = 'foo'; // sets slug field and stores FOO instead of foo
$user->set('title', 'foo'); // sets slug field and stores FOO instead of foo
Warning: Accessors are also run before entities are persisted to the database. If you want to transform fields but
not persist that transformation, we recommend using virtual fields as those are not persisted.
By defining accessors you can provide access to fields that do not actually exist. For example if your users table has
first_name and last_name you could create a method for the full name:
namespace App\Model\Entity;
use Cake\ORM\Entity;
You can access virtual fields as if they existed on the entity. The property name will be the lower case and underscored
version of the method (full_name):
echo $user->full_name;
echo $user->get('full_name');
Do bear in mind that virtual fields cannot be used in finds. If you want them to be part of JSON or array representations
of your entities, see Exposing Virtual Fields.
You may want to make code conditional based on whether or not fields have changed in an entity. For example, you
may only want to validate fields when they change:
You can also flag fields as being modified. This is handy when appending into array fields as this wouldn’t automatically
mark the field as dirty, only exchanging completely would.:
In addition you can also base your conditional code on the original field values by using the getOriginal() method.
This method will either return the original value of the field if it has been modified or its actual value.
You can also check for changes to any field in the entity:
To remove the dirty mark from fields in an entity, you can use the clean() method:
$article->clean();
When creating a new entity, you can avoid the fields from being marked as dirty by passing an extra option:
$dirtyFields = $entity->getDirty();
Validation Errors
After you save an entity any validation errors will be stored on the entity itself. You can access any validation errors
using the getErrors(), getError() or hasErrors() methods:
The setErrors() or setError() method can also be used to set the errors on an entity, making it easier to test code
that works with error messages:
Mass Assignment
While setting fields to entities in bulk is simple and convenient, it can create significant security issues. Bulk assigning
user data from the request into an entity allows the user to modify any and all columns. When using anonymous entity
classes or creating the entity class with the Bake Console CakePHP does not protect against mass-assignment.
The _accessible property allows you to provide a map of fields and whether or not they can be mass-assigned. The
values true and false indicate whether a field can or cannot be mass-assigned:
namespace App\Model\Entity;
use Cake\ORM\Entity;
In addition to concrete fields there is a special * field which defines the fallback behavior if a field is not specifically
named:
namespace App\Model\Entity;
use Cake\ORM\Entity;
When creating a new entity using the new keyword you can tell it to not protect itself against mass assignment:
use App\Model\Entity\Article;
$article = new Article(['id' => 1, 'title' => 'Foo'], ['guard' => false]);
You can modify the list of guarded fields at runtime using the setAccess() method:
Note: Modifying accessible fields affects only the instance the method is called on.
When using the newEntity() and patchEntity() methods in the Table objects you can customize mass assignment
protection with options. Please refer to the Changing Accessible Fields section for more information.
There are some situations when you want to allow mass-assignment to guarded fields:
By setting the guard option to false, you can ignore the accessible field list for a single call to set().
It is often necessary to know if an entity represents a row that is already in the database. In those situations use the
isNew() method:
if (!$article->isNew()) {
echo 'This article was saved already!';
}
If you are certain that an entity has already been persisted, you can use setNew():
$article->setNew(false);
$article->setNew(true);
While eager loading associations is generally the most efficient way to access your associations, there may be times
when you need to lazily load associated data. Before we get into how to lazy load associations, we should discuss the
differences between eager loading and lazy loading associations:
Eager loading
Eager loading uses joins (where possible) to fetch data from the database in as few queries as possible. When a
separate query is required, like in the case of a HasMany association, a single query is emitted to fetch all the
associated data for the current set of objects.
Lazy loading
Lazy loading defers loading association data until it is absolutely required. While this can save CPU time because
possibly unused data is not hydrated into objects, it can result in many more queries being emitted to the database.
For example looping over a set of articles & their comments will frequently emit N queries where N is the number
of articles being iterated.
While lazy loading is not included by CakePHP’s ORM, you can just use one of the community plugins to do so. We
recommend the LazyLoad Plugin127
After adding the plugin to your entity, you will be able to do the following:
$article = $this->Articles->findById($id);
You may find yourself needing the same logic in multiple entity classes. PHP’s traits are a great fit for this. You can
put your application’s traits in src/Model/Entity. By convention traits in CakePHP are suffixed with Trait so they
can be discernible from classes or interfaces. Traits are often a good complement to behaviors, allowing you to provide
functionality for the table and entity objects.
For example if we had SoftDeletable plugin, it could provide a trait. This trait could give methods for marking entities
as ‘deleted’, the method softDelete could be provided by a trait:
// SoftDelete/Model/Entity/SoftDeleteTrait.php
namespace SoftDelete\Model\Entity;
trait SoftDeleteTrait
{
public function softDelete()
{
$this->set('deleted', true);
}
}
You could then use this trait in your entity class by importing it and including it:
127 https://github.com/jeremyharris/cakephp-lazyload
namespace App\Model\Entity;
use Cake\ORM\Entity;
use SoftDelete\Model\Entity\SoftDeleteTrait;
Converting to Arrays/JSON
When building APIs, you may often need to convert entities into arrays or JSON data. CakePHP makes this simple:
// Get an array.
// Associations will be converted with toArray() as well.
$array = $user->toArray();
// Convert to JSON
// Associations will be converted with jsonSerialize hook as well.
$json = json_encode($user);
When converting an entity to an JSON, the virtual & hidden field lists are applied. Entities are recursively converted
to JSON as well. This means that if you eager loaded entities and their associations CakePHP will correctly handle
converting the associated data into the correct format.
By default virtual fields are not exported when converting entities to arrays or JSON. In order to expose virtual fields
you need to make them visible. When defining your entity class you can provide a list of virtual field that should be
exposed:
namespace App\Model\Entity;
use Cake\ORM\Entity;
$user->setVirtual(['full_name', 'is_admin']);
Hiding Fields
There are often fields you do not want exported in JSON or array formats. For example it is often unwise to expose
password hashes or account recovery questions. When defining an entity class, define which fields should be hidden:
namespace App\Model\Entity;
use Cake\ORM\Entity;
$user->setHidden(['password', 'recovery_question']);
Accessor & Mutator methods on entities are not intended to contain the logic for serializing and unserializing complex
data coming from the database. Refer to the Saving Complex Types section to understand how your application can
store more complex data types like arrays and objects.
Defining relations between different objects in your application should be a natural process. For example, an article may
have many comments, and belong to an author. Authors may have many articles and comments. The four association
types in CakePHP are: hasOne, hasMany, belongsTo, and belongsToMany.
Associations are defined during the initialize() method of your table object. Methods matching the association
type allow you to define the associations in your application. For example if we wanted to define a belongsTo association
in our ArticlesTable:
namespace App\Model\Table;
use Cake\ORM\Table;
The simplest form of any association setup takes the table alias you want to associate with. By default all of the details
of an association will use the CakePHP conventions. If you want to customize how your associations are handled you
can modify them with setters:
The property name will be the property key (of the associated entity) on the entity object, in this case:
$authorEntity = $articleEntity->author;
$this->belongsTo('Authors', [
'className' => 'Publishing.Authors',
'foreignKey' => 'author_id',
'propertyName' => 'author'
]);
However, arrays do not offer the typehinting and autocomplete benefits that the fluent interface does.
The same table can be used multiple times to define different types of associations. For example consider a case where
you want to separate approved comments and those that have not been moderated yet:
$this->hasMany('UnapprovedComments', [
'className' => 'Comments'
])
->setFinder('unapproved')
->setProperty('unapproved_comments');
}
}
As you can see, by specifying the className key, it is possible to use the same table as different associations for the
same table. You can even create self-associated tables to create parent-child relationships:
$this->belongsTo('ParentCategories', [
'className' => 'Categories',
]);
}
}
You can also setup associations in mass by making a single call to Table::addAssociations() which accepts an
array containing a set of table names indexed by association type as an argument:
Each association type accepts multiple associations where the keys are the aliases, and the values are association config
data. If numeric keys are used the values will be treated as association aliases.
HasOne Associations
Let’s set up a Users table with a hasOne relationship to the Addresses table.
First, your database tables need to be keyed correctly. For a hasOne relationship to work, one table has to contain a
foreign key that points to a record in the other table. In this case, the Addresses table will contain a field called ‘user_id’.
The basic pattern is:
hasOne: the other model contains the foreign key.
Relation Schema
Users hasOne Addresses addresses.user_id
Doctors hasOne Mentors mentors.doctor_id
Note: It is not mandatory to follow CakePHP conventions, you can override the name of any foreignKey in your
associations definitions. Nevertheless, sticking to conventions will make your code less repetitive, easier to read and to
maintain.
Once you create the UsersTable and AddressesTable classes, you can make the association with the following code:
If you need more control, you can define your associations using the setters. For example, you might want to limit the
association to include only certain records:
If you want to break different addresses into multiple associations, you can do something like:
$this->hasOne('WorkAddresses', [
'className' => 'Addresses'
])
->setProperty('work_address')
->setConditions(['WorkAddresses.label' => 'Work'])
->setDependent(true);
}
}
Note: If a column is shared by multiple hasOne associations, you must qualify it with the association alias. In the
above example, the ‘label’ column is qualified with the ‘HomeAddresses’ and ‘WorkAddresses’ aliases.
• foreignKey: The name of the foreign key column in the other table. The default value is the underscored, singular
name of the current model, suffixed with ‘_id’ such as ‘user_id’ in the above example.
• bindingKey: The name of the column in the current table used to match the foreignKey. The default value is
the primary key of the current table such as ‘id’ of Users in the above example.
• conditions: An array of find() compatible conditions such as ['Addresses.primary' => true]
• joinType: The type of the join used in the SQL query. Accepted values are ‘LEFT’ and ‘INNER’. You can use
‘INNER’ to get results only where the association is set. The default value is ‘LEFT’.
• dependent: When the dependent key is set to true, and an entity is deleted, the associated model records are
also deleted. In this case we set it to true so that deleting a User will also delete her associated Address.
• cascadeCallbacks: When this and dependent are true, cascaded deletes will load and delete entities so that
callbacks are properly triggered. When false, deleteAll() is used to remove associated data and no callbacks
are triggered.
• propertyName: The property name that should be filled with data from the associated table into the source table
results. By default this is the underscored & singular name of the association so address in our example.
• strategy: The query strategy used to load matching record from the other table. Accepted values are 'join'
and 'select'. Using 'select' will generate a separate query and can be useful when the other table is in
different database. The default is 'join'.
• finder: The finder method to use when loading associated records.
Once this association has been defined, find operations on the Users table can contain the Address record if it exists:
BelongsTo Associations
Now that we have Address data access from the User table, let’s define a belongsTo association in the Addresses table in
order to get access to related User data. The belongsTo association is a natural complement to the hasOne and hasMany
associations - it allows us to see related data from the other direction.
When keying your database tables for a belongsTo relationship, follow this convention:
belongsTo: the current model contains the foreign key.
Relation Schema
Addresses belongsTo Users addresses.user_id
Mentors belongsTo Doctors mentors.doctor_id
HasMany Associations
An example of a hasMany association is “Articles hasMany Comments”. Defining this association will allow us to
fetch an article’s comments when the article is loaded.
When creating your database tables for a hasMany relationship, follow this convention:
hasMany: the other model contains the foreign key.
Relation Schema
Articles hasMany Comments Comments.article_id
Products hasMany Options Options.product_id
Doctors hasMany Patients Patients.doctor_id
Relying on the example above, we have passed an array containing the desired composite keys to setForeignKey().
By default the bindingKey would be automatically defined as id and hash respectively, but let’s assume that you need
to specify different binding fields than the defaults. You can setup it manually with setBindingKey():
// Within ArticlesTable::initialize() call
$this->hasMany('Comments')
->setForeignKey([
'article_id',
'article_hash',
(continues on next page)
Like hasOne associations, foreignKey is in the other (Comments) table and bindingKey is in the current (Articles)
table.
Possible keys for hasMany association arrays include:
• className: The class name of the other table. This is the same name used when getting an instance of the table.
In the ‘Articles hasMany Comments’ example, it should be ‘Comments’. The default value is the name of the
association.
• foreignKey: The name of the foreign key column in the other table. The default value is the underscored, singular
name of the current model, suffixed with ‘_id’ such as ‘article_id’ in the above example.
• bindingKey: The name of the column in the current table used to match the foreignKey. The default value is
the primary key of the current table such as ‘id’ of Articles in the above example.
• conditions: an array of find() compatible conditions or SQL strings such as ['Comments.visible' => true].
It is recommended to use the finder option instead.
• sort: an array of find() compatible order clauses or SQL strings such as ['Comments.created' => 'ASC']
• dependent: When dependent is set to true, recursive model deletion is possible. In this example, Comment
records will be deleted when their associated Article record has been deleted.
• cascadeCallbacks: When this and dependent are true, cascaded deletes will load and delete entities so that
callbacks are properly triggered. When false, deleteAll() is used to remove associated data and no callbacks
are triggered.
• propertyName: The property name that should be filled with data from the associated table into the source table
results. By default this is the underscored & plural name of the association so comments in our example.
• strategy: Defines the query strategy to use. Defaults to ‘select’. The other valid value is ‘subquery’, which
replaces the IN list with an equivalent subquery.
• saveStrategy: Either ‘append’ or ‘replace’. Defaults to ‘append’. When ‘append’ the current records are ap-
pended to any records in the database. When ‘replace’ associated records not in the current set will be removed.
If the foreign key is a nullable column or if dependent is true records will be orphaned.
• finder: The finder method to use when loading associated records. See the Using Association Finders section
for more information.
Once this association has been defined, find operations on the Articles table can contain the Comment records if they
exist:
When the subquery strategy is used, SQL similar to the following will be generated:
You may want to cache the counts for your hasMany associations. This is useful when you often need to show the number
of associated records, but don’t want to load all the records just to count them. For example, the comment count on any
given article is often cached to make generating lists of articles more efficient. You can use the CounterCacheBehavior
to cache counts of associated records.
You should make sure that your database tables do not contain columns that match association property names. If
for example you have counter fields that conflict with association properties, you must either rename the association
property, or the column name.
BelongsToMany Associations
An example of a BelongsToMany association is “Article BelongsToMany Tags”, where the tags from one article are
shared with other articles. BelongsToMany is often referred to as “has and belongs to many”, and is a classic “many
to many” association.
The main difference between hasMany and BelongsToMany is that the link between the models in a BelongsToMany
association is not exclusive. For example, we are joining our Articles table with a Tags table. Using ‘funny’ as a Tag
for my Article, doesn’t “use up” the tag. I can also use it on the next article I write.
Three database tables are required for a BelongsToMany association. In the example above we would need tables
for articles, tags and articles_tags. The articles_tags table contains the data that links tags and articles
together. The joining table is named after the two tables involved, separated with an underscore by convention. In its
simplest form, this table consists of article_id and tag_id.
belongsToMany requires a separate join table that includes both model names.
// In src/Model/Table/ArticlesTable.php
class ArticlesTable extends Table
{
public function initialize(array $config): void
{
$this->belongsToMany('Tags');
}
}
// In src/Model/Table/TagsTable.php
class TagsTable extends Table
{
public function initialize(array $config): void
{
$this->belongsToMany('Articles');
}
}
// In src/Model/Table/TagsTable.php
class TagsTable extends Table
{
public function initialize(array $config): void
{
$this->belongsToMany('Articles', [
'joinTable' => 'articles_tags',
]);
}
}
• finder: The finder method to use when loading associated records. See the Using Association Finders section
for more information.
Once this association has been defined, find operations on the Articles table can contain the Tag records if they exist:
When the subquery strategy is used, SQL similar to the following will be generated:
If you plan on adding extra information to the join/pivot table, or if you need to use join columns outside of the
conventions, you will need to define the through option. The through option provides you full control over how the
belongsToMany association will be created.
It is sometimes desirable to store additional data with a many to many association. Consider the following:
A Student can take many Courses and a Course can be taken by many Students. This is a simple many to many
association. The following table would suffice:
id | student_id | course_id
Now what if we want to store the number of days that were attended by the student on the course and their final grade?
The table we’d want would be:
The way to implement our requirement is to use a join model, otherwise known as a hasMany through association.
That is, the association is a model itself. So, we can create a new model CoursesMemberships. Take a look at the
following models:
The CoursesMemberships join table uniquely identifies a given Student’s participation on a Course in addition to extra
meta-information.
When using a query object with a BelongsToMany relationship with a through model, add contain and matching
conditions for the association target table into your query object. The through table can then be referenced in other
conditions such as a where condition by designating the through table name before the field you are filtering on:
$query = $this->find(
'list',
valueField: 'studentFirstName', order: 'students.id'
)
->contain(['Courses'])
->matching('Courses')
->where(['CoursesMemberships.grade' => 'B']);
By default associations will load records based on the foreign key columns. If you want to define additional conditions
for associations, you can use a finder. When an association is loaded the ORM will use your custom finder to load, up-
date, or delete associated records. Using finders lets you encapsulate your queries and make them more reusable. There
are some limitations when using finders to load data in associations that are loaded using joins (belongsTo/hasOne).
Only the following aspects of the query will be applied to the root query:
• Where conditions.
• Additional joins.
• Contained associations.
Other aspects of the query, such as selected columns, order, group by, having and other sub-statements, will not be
applied to the root query. Associations that are not loaded through joins (hasMany/belongsToMany), do not have the
above restrictions and can also use result formatters or map/reduce functions.
Association Conventions
By default, associations should be configured and referenced using the CamelCase style. This enables property chains
to related tables in the following way:
$this->MyTableOne->MyTableTwo->find()->...;
Association properties on entities do not use CamelCase conventions though. Instead for a hasOne/belongsTo relation
like “User belongsTo Roles”, you would get a role property instead of Role or Roles:
Whereas for the other direction “Roles hasMany Users” it would be:
Loading Associations
Once you’ve defined your associations you can eager load associations when fetching results.
class Cake\ORM\Table
While table objects provide an abstraction around a ‘repository’ or collection of objects, when you query for individual
records you get ‘entity’ objects. While this section discusses the different ways you can find and load entities, you
should read the Entities section for more information on entities.
Since the ORM now returns Collections and Entities, debugging these objects can be more complicated than in previous
CakePHP versions. There are now various ways to inspect the data returned by the ORM.
• debug($query) Shows the SQL and bound parameters, does not show results.
• sql($query) Shows the final rendered SQL when DebugKit is installed.
• debug($query->all()) Shows the ResultSet properties (not the results).
• debug($query->toList()) Show results in an array.
• debug(iterator_to_array($query)) Shows query results in an array format.
• debug(json_encode($query, JSON_PRETTY_PRINT)) More human readable results.
• debug($query->first()) Show the properties of a single entity.
• debug((string)$query->first()) Show the properties of a single entity as JSON.
It is often convenient to load a single entity from the database when editing or viewing entities and their related data.
You can do this by using get():
If the get operation does not find any results a Cake\Datasource\Exception\RecordNotFoundException will be
raised. You can either catch this exception yourself, or allow CakePHP to convert it into a 404 error.
Like find(), get() also has caching integrated. You can use the cache option when calling get() to perform read-
through caching:
Optionally you can get() an entity using Custom Finder Methods. For example you may want to get all translations
for an entity. You can achieve that by using the finder option:
Before you can work with entities, you’ll need to load them. The easiest way to do this is using the find() method.
The find method provides a short and extensible way to find the data you are interested in:
The return value of any find() method is always a Cake\ORM\Query\SelectQuery object. The SelectQuery class
allows you to further refine a query after creating it. SelectQuery objects are evaluated lazily, and do not execute until
you start fetching rows, convert it to an array, or when the all() method is called:
Note: Once you’ve started a query you can use the Query Builder interface to build more complex queries, adding
additional conditions, limits, or include associations using the fluent interface.
$query->disableHydration();
The first() method allows you to fetch only the first row from a query. If the query has not been executed, a LIMIT
1 clause will be applied:
This approach replaces find('first') in previous versions of CakePHP. You may also want to use the get() method
if you are loading entities by primary key.
Note: The first() method will return null if no results are found.
Once you have created a query object, you can use the count() method to get a result count of that query:
See Returning the Total Count of Records for additional usage of the count() method.
It is often useful to generate an associative array of data from your application’s data. For example, this is very useful
when creating <select> elements. CakePHP provides a simple to use method for generating ‘lists’ of data:
With no additional options the keys of $data will be the primary key of your table, while the values will be the
‘displayField’ of the table. The default ‘displayField’ of the table is title or name. While, you can use the
setDisplayField() method on a table object to configure the display field of a table:
When calling list you can configure the fields used for the key and value with the keyField and valueField options
respectively:
Results can be grouped into nested sets. This is useful when you want bucketed sets, or want to build <optgroup>
elements with FormHelper:
$data = $query->toArray();
You can also create list data from associations that can be reached with joins:
The keyField, valueField, and groupField expression will operate on entity attribute paths not the database
columns. This means that you can use virtual fields in the results of find(list).
Lastly it is possible to use closures to access entity accessor methods in your list finds.
This example shows using the _getLabel() accessor method from the Author entity.
// In your finders/controller:
$query = $articles->find('list',
keyField: 'id',
valueField: function ($article) {
return $article->author->get('label');
}
)
->contain('Authors');
You can also fetch the label in the list directly using.
// In AuthorsTable::initialize():
$this->setDisplayField('label'); // Will utilize Author::_getLabel()
(continues on next page)
The find('threaded') finder returns nested entities that are threaded together through a key field. By default this
field is parent_id. This finder allows you to access data stored in an ‘adjacency list’ style table. All entities matching
a given parent_id are placed under the children attribute:
echo count($results[0]->children);
echo $results[0]->children[0]->comment;
The parentField and keyField keys can be used to define the fields that threading will occur on.
Tip: If you need to manage more advanced trees of data, consider using Tree instead.
The examples above show how to use the built-in all and list finders. However, it is possible and recommended
that you implement your own finder methods. Finder methods are the ideal way to package up commonly used queries,
allowing you to abstract query details into a simple to use method. Finder methods are defined by creating methods
following the convention of findFoo where Foo is the name of the finder you want to create. For example if we wanted
to add a finder to our articles table for finding articles written by a given user, we would do the following:
use App\Model\Entity\User;
use Cake\ORM\Query\SelectQuery;
use Cake\ORM\Table;
Finder methods can modify the query as required, or use the $options to customize the finder operation with relevant
application logic. You can also ‘stack’ finders, allowing you to express complex queries effortlessly. Assuming you
have both the ‘published’ and ‘recent’ finders, you could do the following:
$query = $articles->find('published')->find('recent');
While all the examples so far have shown finder methods on table classes, finder methods can also be defined on
Behaviors.
If you need to modify the results after they have been fetched you should use a Modifying Results with Map/Reduce
function to modify the results. The map reduce features replace the ‘afterFind’ callback found in previous versions of
CakePHP.
Dynamic Finders
CakePHP’s ORM provides dynamically constructed finder methods which allow you to express simple queries with no
additional code. For example if you wanted to find a user by username you could do:
// In a controller
// The following two calls are equal.
$query = $this->Users->findByUsername('joebob');
$query = $this->Users->findAllByUsername('joebob');
While you can use either OR or AND conditions, you cannot combine the two in a single dynamic finder. Other query op-
tions like contain are also not supported with dynamic finders. You should use Custom Finder Methods to encapsulate
more complex queries. Lastly, you can also combine dynamic finders with custom finders:
$query = $users->findTrollsByUsername('bro');
Once you have a query object from a dynamic finder, you’ll need to call first() if you want the first result.
Note: While dynamic finders make it simple to express queries, they add a small amount of overhead. You cannot
call findBy methods from a query object. When using a finder chain the dynamic finder must be called first.
When you want to grab associated data, or filter based on associated data, there are two ways:
• use CakePHP ORM query functions like contain() and matching()
• use join functions like innerJoin(), leftJoin(), and rightJoin()
You should use contain() when you want to load the primary model, and its associated data. While contain() will
let you apply additional conditions to the loaded associations, you cannot constrain the primary model based on the
associations. For more details on the contain(), look at Eager Loading Associations Via Contain.
You should use matching() when you want to restrict the primary model based on associations. For example, you
want to load all the articles that have a specific tag on them. For more details on the matching(), look at Filtering by
Associated Data Via Matching And Joins.
If you prefer to use join functions, you can look at Adding Joins for more information.
By default CakePHP does not load any associated data when using find(). You need to ‘contain’ or eager-load each
association you want loaded in your results.
Eager loading helps avoid many of the potential performance problems surrounding lazy-loading in an ORM. The
queries generated by eager loading can better leverage joins, allowing more efficient queries to be made. In CakePHP
you state which associations should be eager loaded using the ‘contain’ method:
// As an option to find()
$query = $articles->find('all', contain: ['Authors', 'Comments']);
The above will load the related author and comments for each article in the result set. You can load nested associations
using nested arrays to define the associations to be loaded:
$query = $articles->find()->contain([
'Authors' => ['Addresses'], 'Comments' => ['Authors']
]);
Alternatively, you can express nested associations using the dot notation:
$query = $articles->find()->contain([
'Authors.Addresses',
'Comments.Authors'
]);
$query = $products->find()->contain([
'Shops.Cities.Countries',
'Shops.Managers'
]);
$query = $products->find()->contain([
'Shops' => ['Cities.Countries', 'Managers']
]);
You can select fields from all associations with multiple contain() statements:
$query = $this->find()->select([
'Realestates.id',
'Realestates.title',
'Realestates.description'
])
->contain([
'RealestateAttributes' => [
'Attributes' => [
'fields' => [
// Aliased fields in contain() must include
// the model prefix to be mapped correctly.
'Attributes__name' => 'attr_name',
],
],
],
])
->contain([
'RealestateAttributes' => [
'fields' => [
'RealestateAttributes.realestate_id',
'RealestateAttributes.value',
],
],
])
->where($condition);
If you need to reset the containments on a query you can set the second argument to true:
$query = $articles->find();
$query->contain(['Authors', 'Comments'], true);
Note: Association names in contain() calls should use the same association casing as in your association definitions,
not the property name used to hold the association record(s). For example, if you have declared an association as
belongsTo('Users') then you must use contain('Users') and not contain('users') or contain('user').
When using contain() you are able to restrict the data returned by the associations and filter them by conditions. To
specify conditions, pass an anonymous function that receives as the first argument a query object, \Cake\ORM\Query\
SelectQuery:
$this->paginate['contain'] = [
'Comments' => function (SelectQuery $query) {
return $query->select(['body', 'author_id'])
->where(['Comments.approved' => true]);
}
];
Warning: If the results are missing association entities, make sure the foreign key columns are selected in the
query. Without the foreign keys, the ORM cannot find matching rows.
$query = $articles->find()->contain([
'Comments',
'Authors.Profiles' => function (SelectQuery $q) {
return $q->where(['Profiles.is_published' => true]);
}
]);
In the above example, you’ll still get authors even if they don’t have a published profile. To only get authors with a
published profile use matching(). If you have defined custom finders in your associations, you can use them inside
contain():
// Bring all articles, but only bring the comments that are approved and
// popular.
$query = $articles->find()->contain('Comments', function (SelectQuery $q) {
return $q->find('approved')->find('popular');
});
Note: With BelongsTo and HasOne associations only select and where clauses are valid in the contain() query.
With HasMany and BelongsToMany all clauses such as order() are valid.
You can control more than just the query clauses used by contain(). If you pass an array with the association, you
can override the foreignKey, joinType and strategy. See Associations - Linking Tables Together for details on
the default value and options for each association type.
You can pass false as the new foreignKey to disable foreign key constraints entirely. Use the queryBuilder option
to customize the query when using an array:
$query = $articles->find()->contain([
'Authors' => [
'foreignKey' => false,
'queryBuilder' => function (SelectQuery $q) {
return $q->where(/* ... */); // Full conditions for filtering
}
]
]);
If you have limited the fields you are loading with select() but also want to load fields off of contained associations,
you can pass the association object to select():
// Select id & title from articles, but all fields off of Users.
$query = $articles->find()
->select(['id', 'title'])
->select($articles->Users)
->contain(['Users']);
// Select id & title from articles, but all fields off of Users.
$query = $articles->find()
->select(['id', 'title'])
->contain(['Users' => function(SelectQuery $q) {
return $q->enableAutoFields();
}]);
When loading HasMany and BelongsToMany associations, you can use the sort option to sort the data in those asso-
ciations:
$query->contain([
'Comments' => [
'sort' => ['Comments.created' => 'DESC']
]
]);
A fairly common query case with associations is finding records ‘matching’ specific associated data. For example if
you have ‘Articles belongsToMany Tags’ you will probably want to find Articles that have the CakePHP tag. This is
extremely simple to do with the ORM in CakePHP:
$query = $articles->find();
$query->matching('Tags', function ($q) {
(continues on next page)
You can apply this strategy to HasMany associations as well. For example if ‘Authors HasMany Articles’, you could
find all the authors with recently published articles using the following:
$query = $authors->find();
$query->matching('Articles', function ($q) {
return $q->where(['Articles.created >=' => new DateTime('-10 days')]);
});
Filtering by deep associations uses the same predictable syntax from contain():
// Bring unique articles that were commented by `markstory` using passed variable
// Dotted matching paths should be used over nested matching() calls
$username = 'markstory';
$query = $articles->find()->matching('Comments.Users', function ($q) use ($username) {
return $q->where(['username' => $username]);
});
Note: As this function will create an INNER JOIN, you might want to consider calling distinct on the find query as
you might get duplicate rows if your conditions don’t exclude them already. This might be the case, for example, when
the same users comments more than once on a single article.
The data from the association that is ‘matched’ will be available on the _matchingData property of entities. If both
match and contain the same association, you can expect to get both the _matchingData and standard association
properties in your results.
Using innerJoinWith
Sometimes you need to match specific associated data but without actually loading the matching records like
matching(). You can create just the INNER JOIN that matching() uses with innerJoinWith():
$query = $articles->find();
$query->innerJoinWith('Tags', function ($q) {
return $q->where(['Tags.name' => 'CakePHP']);
});
$query = $products->find()->innerJoinWith(
'Shops.Cities.Countries', function ($q) {
return $q->where(['Countries.name' => 'Japan']);
(continues on next page)
You can combine innerJoinWith() and contain() with the same association when you want to match specific
records and load the associated data together. The example below matches Articles that have specific Tags and loads
the same Tags:
Note: If you use innerJoinWith() and want to select() fields from that association, you need to use an alias for
the field:
$query
->select(['country_name' => 'Countries.name'])
->innerJoinWith('Countries');
If you don’t use an alias, you will see the data in _matchingData as described by matching() above. This is an edge
case from matching() not knowing you manually selected the field.
Warning: You should not combine innerJoinWith() and matching() with the same association. This will
produce multiple INNER JOIN statements and might not create the query you expected.
Using notMatching
The opposite of matching() is notMatching(). This function will change the query so that it filters results that have
no relation to the specified association:
$query = $articlesTable
->find()
->notMatching('Tags', function ($q) {
return $q->where(['Tags.name' => 'boring']);
});
The above example will find all articles that were not tagged with the word boring. You can apply this method to
HasMany associations as well. You could, for example, find all the authors with no published articles in the last 10
days:
$query = $authorsTable
->find()
->notMatching('Articles', function ($q) {
return $q->where(['Articles.created >=' => new \DateTime('-10 days')]);
});
It is also possible to use this method for filtering out records not matching deep associations. For example, you could
find articles that have not been commented on by a certain user:
$query = $articlesTable
->find()
->notMatching('Comments.Users', function ($q) {
return $q->where(['username' => 'jose']);
});
Since articles with no comments at all also satisfy the condition above, you may want to combine matching() and
notMatching() in the same query. The following example will find articles having at least one comment, but not
commented by a certain user:
$query = $articlesTable
->find()
->notMatching('Comments.Users', function ($q) {
return $q->where(['username' => 'jose']);
})
->matching('Comments');
Note: As notMatching() will create a LEFT JOIN, you might want to consider calling distinct on the find query
as you can get duplicate rows otherwise.
Keep in mind that contrary to the matching() function, notMatching() will not add any data to the _matchingData
property in the results.
Using leftJoinWith
On certain occasions you may want to calculate a result based on an association, without having to load all the records
for it. For example, if you wanted to load the total number of comments an article has along with all the article data,
you can use the leftJoinWith() function:
$query = $articlesTable->find();
$query->select(['total_comments' => $query->func()->count('Comments.id')])
->leftJoinWith('Comments')
->groupBy(['Articles.id'])
->enableAutoFields(true);
The results for the above query will contain the article data and the total_comments property for each of them.
leftJoinWith() can also be used with deeply nested associations. This is useful, for example, for bringing the count
of articles tagged with a certain word, per author:
$query = $authorsTable
->find()
(continues on next page)
This function will not load any columns from the specified associations into the result set.
As mentioned in earlier, you can customize the strategy used by an association in a contain().
If you look at BelongsTo and HasOne association options, the default ‘join’ strategy and ‘INNER’ joinType can be
changed to ‘select’:
$query = $articles->find()->contain([
'Comments' => [
'strategy' => 'select',
]
]);
This can be useful when you need to add conditions that don’t work well in a join. This also makes it possible to query
tables that are not allowed in joins such as separate databases.
Usually, you set the strategy for an association when defining it in Table::initialize(), but you can permanently
change the strategy manually:
$articles->Comments->setStrategy('select');
As your tables grow in size, fetching associations from them can become slower, especially if you are querying big
batches at once. A good way of optimizing association loading for hasMany and belongsToMany associations is by
using the subquery strategy:
$query = $articles->find()->contain([
'Comments' => [
'strategy' => 'subquery',
'queryBuilder' => function ($q) {
return $q->where(['Comments.approved' => true]);
}
]
]);
The result will remain the same as with using the default strategy, but this can greatly improve the query and fetching
time in some databases, in particular it will allow to fetch big chunks of data at the same time in databases that limit
the amount of bound parameters per query, such as Microsoft SQL Server.
While CakePHP uses eager loading to fetch your associations, there may be cases where you need to lazy-load asso-
ciations. You should refer to the Lazy Loading Associations and Loading Additional Associations sections for more
information.
Once a query is executed with all(), you will get an instance of Cake\ORM\ResultSet. This object offers powerful
ways to manipulate the resulting data from your queries. ResultSets are a Collection and you can use any collection
method on ResultSet objects.
Result set objects will lazily load rows from the underlying prepared statement. By default results will be buffered in
memory allowing you to iterate a result set multiple times, or cache and iterate the results.
Result sets allow you to cache/serialize or JSON encode results for API results:
// In a controller or table method.
$results = $query->all();
// Serialized
$serialized = serialize($results);
// Json
$json = json_encode($results);
Both serializing and JSON encoding result sets work as you would expect. The serialized data can be unserialized into
a working result set. Converting to JSON respects hidden & virtual field settings on all entity objects within a result
set.
Result sets are a ‘Collection’ object and support the same methods that collection objects do. For example, you can
extract a list of unique tags on a collection of articles by running:
// In a controller or table method.
$query = $articles->find()->contain(['Tags']);
return $output;
};
$uniqueTags = $query->all()
->extract('tags.name')
->reduce($reducer, []);
Some other examples of the collection methods being used with result sets are:
// Filter the rows by a calculated property
$filtered = $results->filter(function ($row) {
return $row->is_recent;
});
The Collections chapter has more detail on what can be done with result sets using the collections features. The Adding
Calculated Fields section show how you can add calculated fields, or replace the result set.
You can use the first() and last() methods to get the respective records from a result set:
$result = $articles->find('all')->all();
You can use skip() and first() to get an arbitrary record from a ResultSet:
$result = $articles->find('all')->all();
You can use the isEmpty() method on a ResultSet object to see if it has any rows in it.:
// Check results
$results = $query->all();
$results->isEmpty();
Once you’ve created a result set, you may need to load additional associations. This is the perfect time to lazily eager
load data. You can load additional associations using loadInto():
$articles = $this->Articles->find()->all();
$withMore = $this->Articles->loadInto($articles, ['Comments', 'Users']);
It is possible to restrict the data returned by the associations and filter them by conditions. To specify conditions, pass
an anonymous function that receives as the first argument a query object, \Cake\ORM\Query:
$user = $this->Users->get($id);
$withMore = $this->Users->loadInto($user, ['Posts' => function (Query $query) {
return $query->where(['Posts.status' => 'published']);
}]);
You can eager load additional data into a single entity, or a collection of entities.
More often than not, find operations require post-processing the data that is found in the database. While entities’ getter
methods can take care of most of the virtual field generation or special data formatting, sometimes you need to change
the data structure in a more fundamental way.
For those cases, the SelectQuery object offers the mapReduce() method, which is a way of processing results once
they are fetched from the database.
A common example of changing the data structure is grouping results together based on certain conditions. For this
task we can use the mapReduce() function. We need two callable functions the $mapper and the $reducer. The
$mapper callable receives the current result from the database as first argument, the iteration key as second argument
and finally it receives an instance of the MapReduce routine it is running:
In the above example $mapper is calculating the status of an article, either published or unpublished, then it calls
emitIntermediate() on the MapReduce instance. This method stores the article in the list of articles labelled as
either published or unpublished.
The next step in the map-reduce process is to consolidate the final results. For each status created in the mapper, the
$reducer function will be called so you can do any extra processing. This function will receive the list of articles in
a particular “bucket” as the first parameter, the name of the “bucket” it needs to process as the second parameter, and
again, as in the mapper() function, the instance of the MapReduce routine as the third parameter. In our example, we
did not have to do any extra processing, so we just emit() the final results:
$articlesByStatus = $articles->find()
->where(['author_id' => 1])
->mapReduce($mapper, $reducer)
->all();
Of course, this is a simplistic example that could actually be solved in another way without the help of a map-reduce
process. Now, let’s take a look at another example in which the reducer function will be needed to do something more
than just emitting the results.
Calculating the most commonly mentioned words, where the articles contain information about CakePHP, as usual we
need a mapper function:
It first checks for whether the “cakephp” word is in the article’s body, and then breaks the body into individual words.
Each word will create its own bucket where each article id will be stored. Now let’s reduce our results to only extract
the count:
$wordCount = $articles->find()
->where(['published' => true])
->andWhere(['published_date >=' => new DateTime('2014-01-01')])
->disableHydration()
->mapReduce($mapper, $reducer)
->all()
->toArray();
This could return a very large array if we don’t clean stop words, but it could look something like this:
[
'cakephp' => 100,
'awesome' => 39,
'impressive' => 57,
'outstanding' => 10,
'mind-blowing' => 83
]
One last example and you will be a map-reduce expert. Imagine you have a friends table and you want to find “fake
friends” in our database, or better said, people who do not follow each other. Let’s start with our mapper() function:
[
1 => [2, 3, 4, 5, -3, -5],
2 => [-1],
3 => [-1, 1, 6],
4 => [-1],
5 => [-1, 1],
6 => [-3],
...
]
Positive numbers mean that a user, indicated with the first-level key, is following them, and negative numbers mean
that the user is followed by them.
Now it’s time to reduce it. For each call to the reducer, it will receive a list of followers per user:
if ($fakeFriends) {
$mr->emit($fakeFriends, $user);
}
};
$fakeFriends = $friends->find()
->disableHydration()
->mapReduce($mapper, $reducer)
->all()
->toArray();
[
1 => [2, 4],
3 => [6]
...
]
The resulting array means, for example, that user with id 1 follows users 2 and 4, but those do not follow 1 back.
Using mapReduce in a query will not execute it immediately. The operation will be registered to be run as soon as the
first result is attempted to be fetched. This allows you to keep chaining additional methods and filters to the query even
after adding a map-reduce routine:
$query = $articles->find()
->where(['published' => true])
->mapReduce($mapper, $reducer);
This is particularly useful for building custom finder methods as described in the Custom Finder Methods section:
$commonWords = $articles
->find('commonWords')
->find('published')
->find('recent');
Moreover, it is also possible to stack more than one mapReduce operation for a single query. For example, if we wanted
to have the most commonly used words for articles, but then filter it to only return words that were mentioned more
than 20 times across all articles:
$articles->find('commonWords')->mapReduce($mapper)->all();
Under some circumstances you may want to modify a SelectQuery object so that no mapReduce operations are
executed at all. This can be done by calling the method with both parameters as null and the third parameter (overwrite)
as true:
Validating Data
Before you save your data you will probably want to ensure the data is correct and consistent. In CakePHP we have
two stages of validation:
1. Before request data is converted into entities, validation rules around data types and formatting can be applied.
2. Before data is saved, domain or application rules can be applied. These rules help ensure that your application’s
data remains consistent.
When marshalling data into entities, you can validate data. Validating data allows you to check the type, shape and
size of data. By default request data will be validated before it is converted into entities. If any validation rules fail, the
returned entity will contain errors. The fields with errors will not be present in the returned entity:
$article = $articles->newEntity($this->request->getData());
if ($article->getErrors()) {
// Entity failed validation.
}
$article = $articles->newEntity(
$this->request->getData(),
['validate' => false]
);
Validation rules are defined in the Table classes for convenience. This defines what data should be validated in con-
junction with where it will be saved.
To create a default validation object in your table, create the validationDefault() function:
use Cake\ORM\Table;
use Cake\Validation\Validator;
$validator
->allowEmptyString('link')
->add('link', 'valid-url', ['rule' => 'url']);
...
return $validator;
}
}
The available validation methods and rules come from the Validator class and are documented in the Creating
Validators section.
Note: Validation objects are intended primarily for validating user input, i.e. forms and any other posted request data.
In addition to disabling validation you can choose which validation rule set you want applied:
$article = $articles->newEntity(
$this->request->getData(),
['validate' => 'update']
);
The above would call the validationUpdate() method on the table instance to build the required rules. By default
the validationDefault() method will be used. An example validator for our articles table would be:
return $validator;
}
}
You can have as many validation sets as necessary. See the validation chapter for more information on building vali-
dation rule-sets.
Validation sets can also be defined per association. When using the newEntity() or patchEntity() methods, you
can pass extra options to each of the associations to be converted:
$data = [
'title' => 'My title',
'body' => 'The text',
'user_id' => 1,
'user' => [
'username' => 'mark',
],
'comments' => [
['body' => 'First comment'],
['body' => 'Second comment'],
],
];
Combining Validators
Because of how validator objects are built, you can decompose their construction process into multiple reusable steps:
// UsersTable.php
return $validator;
}
return $validator;
}
Given the above setup, when using the hardened validation set, it will also contain the validation rules declared in the
default set.
Validation Providers
Validation rules can use functions defined on any known providers. By default CakePHP sets up a few providers:
1. Methods on the table class or its behaviors are available on the table provider.
2. The core Validation class is setup as the default provider.
When a validation rule is created you can name the provider of that rule. For example, if your table has an isValidRole
method you can use it as a validation rule:
use Cake\ORM\Table;
use Cake\Validation\Validator;
return $validator;
}
$validator->add('name', 'myRule', [
'rule' => function ($value, array $context) {
if ($value > 1) {
return true;
}
Validation methods can return error messages when they fail. This is a simple way to make error messages dynamic
based on the provided value.
Once you have created a few validation sets in your table class, you can get the resulting object by name:
$defaultValidator = $usersTable->getValidator('default');
$hardenedValidator = $usersTable->getValidator('hardened');
As stated above, by default the validation methods receive an instance of Cake\Validation\Validator. Instead, if
you want your custom validator’s instance to be used each time, you can use table’s $_validatorClass property:
While basic data validation is done when request data is converted into entities, many applications also have more
complex validation that should only be applied after basic validation has completed.
Where validation ensures the form or syntax of your data is correct, rules focus on comparing data against the existing
state of your application and/or network.
These types of rules are often referred to as ‘domain rules’ or ‘application rules’. CakePHP exposes this concept through
‘RulesCheckers’ which are applied before entities are persisted. Some example application rules are:
• Ensuring email uniqueness
• State transitions or workflow steps, for example, updating an invoice’s status.
• Preventing the modification of soft deleted items.
• Enforcing usage/rate limit caps.
Application rules are checked when calling the Table save() and delete() methods.
Rules checker classes are generally defined by the buildRules() method in your table class. Behaviors and other
event subscribers can use the Model.buildRules event to augment the rules checker for a given Table class:
use Cake\ORM\RulesChecker;
// In a table class
public function buildRules(RulesChecker $rules): RulesChecker
{
// Add a rule that is applied for create and update operations
$rules->add(function ($entity, $options) {
// Return a boolean to indicate pass/failure
}, 'ruleName');
return $rules;
}
Your rules functions can expect to get the Entity being checked and an array of options. The options array will contain
errorField, message, and repository. The repository option will contain the table class the rules are attached
to. Because rules accept any callable, you can also use instance functions:
or callable classes:
When adding rules you can define the field the rule is for and the error message as options:
The error will be visible when calling the getErrors() method on the entity:
Because unique rules are quite common, CakePHP includes a simple Rule class that allows you to define unique field
sets:
use Cake\ORM\Rule\IsUnique;
// A single field.
$rules->add($rules->isUnique(['email']));
// A list of fields
$rules->add($rules->isUnique(
['username', 'account_id'],
'This username & account_id combination has already been used.'
));
When setting rules on foreign key fields it is important to remember, that only the fields listed are used in the rule. The
unique set of rules will be found with find('all'). This means that setting $user->account->id will not trigger
the above rule.
Many database engines allow NULLs to be unique values in UNIQUE indexes. To simulate this, set the
allowMultipleNulls options to true:
$rules->add($rules->isUnique(
['username', 'account_id'],
['allowMultipleNulls' => true]
));
While you could rely on database errors to enforce constraints, using rules code can help provide a nicer user experience.
Because of this CakePHP includes an ExistsIn rule class:
// A single field.
$rules->add($rules->existsIn('article_id', 'Articles'));
The fields to check existence against in the related table must be part of the primary key.
You can enforce existsIn to pass when nullable parts of your composite foreign key are null:
// Allow this rule to pass, even if fields that are nullable, like parent_id, are null:
$rules->add($rules->existsIn(
['parent_id', 'site_id'], // Schema: parent_id NULL, site_id NOT NULL
'ParentNodes',
['allowNullableNulls' => true]
));
In most SQL databases multi-column UNIQUE indexes allow multiple null values to exist as NULL is not equal to itself.
While, allowing multiple null values is the default behavior of CakePHP, you can include null values in your unique
checks using allowMultipleNulls:
If you need to validate that a property or association contains the correct number of values, you can use the
validCount() rule:
When defining count based rules, the third parameter lets you define the comparison operator to use. ==, >=, <=, >, <,
and != are the accepted operators. To ensure a property’s count is within a range, use two rules:
Note that validCount returns false if the property is not countable or does not exist:
The LinkConstraint lets you emulate SQL constraints in databases that don’t support them, or when you want to
provide more user friendly error messages when constraints would fail. This rule enables you to check if an association
does or does not have related records depending on the mode used:
return false;
}, 'userExists');
Rules, being it custom callables, or rule objects, can either return a boolean, indicating whether they passed, or they
can return a string, which means that the rule did not pass, and that the returned string should be used as the error
message.
Possible existing error messages defined via the message option will be overwritten by the ones returned from the rule:
$rules->add(
function ($entity, $options) {
if (!$entity->length) {
return false;
}
return true;
},
'ruleName',
[
'errorField' => 'length',
'message' => 'Generic error message used when `false` is returned',
]
);
Note: Note that in order for the returned message to be actually used, you must also supply the errorField option,
otherwise the rule will just silently fail to pass, ie without an error message being set on the entity!
You may want to re-use custom domain rules. You can do so by creating your own invokable rule:
return $rules;
}
See the core rules for examples on how to create such rules.
If your application has rules that are commonly reused, it is helpful to package those rules into re-usable classes:
// in src/Model/Rule/CustomRule.php
namespace App\Model\Rule;
use Cake\Datasource\EntityInterface;
class CustomRule
(continues on next page)
By creating custom rule classes you can keep your code DRY and test your domain rules in isolation.
Disabling Rules
$validatedEntity = $articlesTable->newEntity(
$unsafeData,
['validate' => 'customName']
);
$validatedEntity = $articlesTable->patchEntity(
$entity,
$unsafeData,
['validate' => 'customName']
);
In the above example, we’ll use a ‘custom’ validator, which is defined using the validationCustomName() method:
return $validator;
}
Validation assumes strings or array are passed since that is what is received from any request:
// In src/Model/Table/UsersTable.php
public function validatePasswords($validator)
{
$validator->add('confirm_password', 'no-misspelling', [
'rule' => ['compareWith', 'password'],
'message' => 'Passwords are not equal',
]);
// ...
return $validator;
}
In the above example the entity will be saved as validation is only triggered for the newEntity() and patchEntity()
methods. The second level of validation is meant to address this situation.
Application rules as explained above will be checked whenever save() or delete() are called:
// In src/Model/Table/UsersTable.php
public function buildRules(RulesChecker $rules): RulesChecker
{
$rules->add($rules->isUnique(['email']));
return $rules;
}
While Validation is meant for direct user input, application rules are specific for data transitions generated inside your
application:
// In src/Model/Table/OrdersTable.php
public function buildRules(RulesChecker $rules): RulesChecker
{
$check = function($order) {
if ($order->shipping_mode !== 'free') {
return true;
}
return $rules;
}
In certain situations you may want to run the same data validation routines for data that was both generated by users
and inside your application. This could come up when running a CLI script that directly sets properties on entities:
// In src/Model/Table/UsersTable.php
public function validationDefault(Validator $validator): Validator
{
$validator->add('email', 'valid_email', [
'rule' => 'email',
'message' => 'Invalid email',
]);
// ...
return $validator;
}
return empty($errors);
});
// ...
return $rules;
}
When executed the save will fail thanks to the new application rule that was added:
Saving Data
class Cake\ORM\Table
After you have loaded your data you will probably want to update and save the changes.
Applications will usually have a couple of ways in which data is saved. The first one is obviously through web forms
and the other is by directly generating or changing data in the code to be sent to the database.
Inserting Data
The easiest way to insert data in the database is by creating a new entity and passing it to the save() method in the
Table class:
use Cake\ORM\Locator\LocatorAwareTrait;
$articlesTable = $this->fetchTable('Articles');
$article = $articlesTable->newEmptyEntity();
if ($articlesTable->save($article)) {
// The $article entity contains the id now
$id = $article->id;
}
Updating Data
use Cake\ORM\Locator\LocatorAwareTrait;
$articlesTable = $this->fetchTable('Articles');
$article = $articlesTable->get(12); // Return article with id 12
CakePHP will know whether to perform an insert or an update based on the return value of the isNew() method.
Entities that were retrieved with get() or find() will always return false when isNew() is called on them.
By default the save() method will also save one level of associations:
$articlesTable = $this->fetchTable('Articles');
$author = $articlesTable->Authors->findByUserName('mark')->first();
$article = $articlesTable->newEmptyEntity();
$article->title = 'An article by mark';
$article->author = $author;
if ($articlesTable->save($article)) {
// The foreign key value was set automatically.
echo $article->author_id;
}
The save() method is also able to create new records for associations:
$firstComment = $articlesTable->Comments->newEmptyEntity();
$firstComment->body = 'The CakePHP features are outstanding';
$secondComment = $articlesTable->Comments->newEmptyEntity();
$secondComment->body = 'CakePHP performance is terrific!';
$tag1 = $articlesTable->Tags->findByName('cakephp')->first();
$tag2 = $articlesTable->Tags->newEmptyEntity();
$tag2->name = 'awesome';
$article = $articlesTable->get(12);
$article->comments = [$firstComment, $secondComment];
$article->tags = [$tag1, $tag2];
$articlesTable->save($article);
The previous example demonstrates how to associate a few tags to an article. Another way of accomplishing the same
thing is by using the link() method in the association:
$tag1 = $articlesTable->Tags->findByName('cakephp')->first();
$tag2 = $articlesTable->Tags->newEmptyEntity();
$tag2->name = 'awesome';
$tags = $articlesTable
->Tags
->find()
->where(['name IN' => ['cakephp', 'awesome']])
->toList();
$articlesTable->Tags->unlink($article, $tags);
When modifying records by directly setting or changing the properties no validation happens, which is a problem when
accepting form data. The following sections will demonstrate how to efficiently convert form data into entities so that
they can be validated and saved.
Before editing and saving data back to your database, you’ll need to convert the request data from the array format held
in the request, and the entities that the ORM uses. The Table class provides an efficient way to convert one or many
entities from request data. You can convert a single entity using:
// In a controller
$articles = $this->fetchTable('Articles');
Note: If you are using newEntity() and the resulting entities are missing some or all of the data they were passed,
double check that the columns you want to set are listed in the $_accessible property of your entity. See Mass
Assignment.
The request data should follow the structure of your entities. For example if you have an article, which belonged to a
user, and had many comments, your request data should resemble:
$data = [
'title' => 'CakePHP For the Win',
'body' => 'Baking with CakePHP makes web development fun!',
'user_id' => 1,
'user' => [
'username' => 'mark',
],
'comments' => [
['body' => 'The CakePHP features are outstanding'],
['body' => 'CakePHP performance is terrific!'],
]
];
By default, the newEntity() method validates the data that gets passed to it, as explained in the Validating Data
Before Building Entities section. If you wish to bypass data validation pass the 'validate' => false option:
When building forms that save nested associations, you need to define which associations should be marshalled:
// In a controller
$articles = $this->fetchTable('Articles');
The above indicates that the ‘Tags’, ‘Comments’ and ‘Users’ for the Comments should be marshalled. Alternatively,
you can use dot notation for brevity:
// In a controller
$articles = $this->fetchTable('Articles');
You may also disable marshalling of possible nested associations like so:
Associated data is also validated by default unless told otherwise. You may also change the validation set to be used
per association:
// In a controller
$articles = $this->fetchTable('Articles');
The Using A Different Validation Set For Associations chapter has more information on how to use different validators
for associated marshalling.
The following diagram gives an overview of what happens inside the newEntity() or patchEntity() method:
You
can
al-
ways
count
on
get-
ting
an
en-
tity
back
from
newEntity().
If val-
idation
fails
your
entity
will
con-
tain
errors,
and
any
invalid
fields
will
not be
popu-
lated
in the
cre-
ated
entity.
If
you
are
saving
be-
longsToMany associations you can either use a list of entity data or a list of ids. When using a list of entity data your
request data should look like:
$data = [
'title' => 'My title',
'body' => 'The text',
'user_id' => 1,
'tags' => [
['name' => 'CakePHP'],
(continues on next page)
The above will create 2 new tags. If you want to link an article with existing tags you can use a list of ids. Your request
data should look like:
$data = [
'title' => 'My title',
'body' => 'The text',
'user_id' => 1,
'tags' => [
'_ids' => [1, 2, 3, 4],
],
];
If you need to link against some existing belongsToMany records, and create new ones at the same time you can use an
expanded format:
$data = [
'title' => 'My title',
'body' => 'The text',
'user_id' => 1,
'tags' => [
['name' => 'A new tag'],
['name' => 'Another new tag'],
['id' => 5],
['id' => 21],
],
];
When the above data is converted into entities, you will have 4 tags. The first two will be new objects, and the second
two will be references to existing records.
When converting belongsToMany data, you can disable entity creation, by using the onlyIds option:
When used, this option restricts belongsToMany association marshalling to only use the _ids data.
If you want to update existing hasMany associations and update their properties, you should first ensure your entity is
loaded with the hasMany association populated. You can then use request data similar to:
$data = [
'title' => 'My Title',
'body' => 'The text',
'comments' => [
['id' => 1, 'comment' => 'Update the first comment'],
(continues on next page)
If you are saving hasMany associations and want to link existing records to a new parent record you can use the _ids
format:
$data = [
'title' => 'My new article',
'body' => 'The text',
'user_id' => 1,
'comments' => [
'_ids' => [1, 2, 3, 4],
],
];
When converting hasMany data, you can disable the new entity creation, by using the onlyIds option. When enabled,
this option restricts hasMany marshalling to only use the _ids key and ignore all other data.
When creating forms that create/update multiple records at once you can use newEntities():
// In a controller.
$articles = $this->fetchTable('Articles');
$entities = $articles->newEntities($this->request->getData());
In this situation, the request data for multiple articles should look like:
$data = [
[
'title' => 'First post',
'published' => 1,
],
[
'title' => 'Second post',
'published' => 1,
],
];
Once you’ve converted request data into entities you can save:
// In a controller.
foreach ($entities as $entity) {
// Save entity
$articles->save($entity);
}
The above will run a separate transaction for each entity saved. If you’d like to process all the entities as a single
transaction you can use saveMany() or saveManyOrFail():
It’s also possible to allow newEntity() to write into non accessible fields. For example, id is usually absent from the
_accessible property. In such case, you can use the accessibleFields option. It could be useful to keep ids of
associated entities:
// In a controller
$articles = $this->fetchTable('Articles');
$entity = $articles->newEntity($this->request->getData(), [
'associated' => [
'Tags', 'Comments' => [
'associated' => [
'Users' => [
'accessibleFields' => ['id' => true],
],
],
],
],
]);
The above will keep the association unchanged between Comments and Users for the concerned entity.
Note: If you are using newEntity() and the resulting entities are missing some or all of the data they were passed,
double check that the columns you want to set are listed in the $_accessible property of your entity. See Mass
Assignment.
In order to update entities you may choose to apply request data directly to an existing entity. This has the advantage
that only the fields that actually changed will be saved, as opposed to sending all fields to the database to be persisted.
You can merge an array of raw data into an existing entity using the patchEntity() method:
// In a controller.
$articles = $this->fetchTable('Articles');
$article = $articles->get(1);
$articles->patchEntity($article, $this->request->getData());
$articles->save($article);
Similar to newEntity(), the patchEntity method will validate the data before it is copied to the entity. The mech-
anism is explained in the Validating Data Before Building Entities section. If you wish to disable validation while
patching an entity, pass the validate option as follows:
// In a controller.
$articles = $this->fetchTable('Articles');
$article = $articles->get(1);
$articles->patchEntity($article, $data, ['validate' => false]);
You may also change the validation set used for the entity or any of the associations:
$articles->patchEntity($article, $this->request->getData(), [
'validate' => 'custom',
'associated' => ['Tags', 'Comments.Users' => ['validate' => 'signup']]
]);
As explained in the previous section, the request data should follow the structure of your entity. The patchEntity()
method is equally capable of merging associations, by default only the first level of associations are merged, but if you
wish to control the list of associations to be merged or merge deeper to deeper levels, you can use the third parameter
of the method:
// In a controller.
$associated = ['Tags', 'Comments.Users'];
// or using nested arrays
$associated = ['Tags', 'Comments' => ['associated' => ['Users']]];
$article = $articles->get(1, ['contain' => $associated]);
$articles->patchEntity($article, $this->request->getData(), [
'associated' => $associated,
]);
$articles->save($article);
Associations are merged by matching the primary key field in the source entities to the corresponding fields in the data
array. Associations will construct new entities if no previous entity is found for the association’s target property.
For example give some request data like the following:
$data = [
'title' => 'My title',
'user' => [
'username' => 'mark',
],
];
Trying to patch an entity without an entity in the user property will create a new user entity:
// In a controller.
$entity = $articles->patchEntity(new Article, $data);
echo $entity->user->username; // Echoes 'mark'
The same can be said about hasMany and belongsToMany associations, with an important caveat:
Note: For belongsToMany associations, ensure the relevant entity has a property accessible for the associated entity.
Note: For hasMany and belongsToMany associations, if there were any entities that could not be matched by primary
key to a record in the data array, then those records will be discarded from the resulting entity.
Remember that using either patchEntity() or patchEntities() does not persist the data, it just edits (or creates)
the given entities. In order to save the entity you will have to call the table’s save() method.
$data = [
'title' => 'My title',
'body' => 'The text',
'comments' => [
['body' => 'First comment', 'id' => 1],
['body' => 'Second comment', 'id' => 2],
],
];
$entity = $articles->newEntity($data);
$articles->save($entity);
$newData = [
'comments' => [
['body' => 'Changed comment', 'id' => 1],
['body' => 'A new comment'],
],
];
$articles->patchEntity($entity, $newData);
$articles->save($entity);
At the end, if the entity is converted back to an array you will obtain the following result:
[
'title' => 'My title',
'body' => 'The text',
'comments' => [
['body' => 'Changed comment', 'id' => 1],
['body' => 'A new comment'],
],
];
As you can see, the comment with id 2 is no longer there, as it could not be matched to anything in the $newData array.
This happens because CakePHP is reflecting the new state described in the request data.
Some additional advantages of this approach is that it reduces the number of operations to be executed when persisting
the entity again.
Please note that this does not mean that the comment with id 2 was removed from the database, if you wish to remove
the comments for that article that are not present in the entity, you can collect the primary keys and execute a batch
delete for those not in the list:
// In a controller.
use Cake\Collection\Collection;
$comments = $this->fetchTable('Comments');
$present = (new Collection($entity->comments))->extract('id')->filter()->toList();
$comments->deleteAll([
'article_id' => $article->id,
'id NOT IN' => $present,
]);
As you can see, this also helps creating solutions where an association needs to be implemented like a single set.
You can also patch multiple entities at once. The consideration made for patching hasMany and belongsToMany
associations apply for patching multiple entities: Matches are done by the primary key field value and missing matches
in the original entities array will be removed and not present in the result:
// In a controller.
$articles = $this->fetchTable('Articles');
$list = $articles->find('popular')->toList();
$patched = $articles->patchEntities($list, $this->request->getData());
foreach ($patched as $entity) {
$articles->save($entity);
}
Similarly to using patchEntity(), you can use the third argument for controlling the associations that will be merged
in each of the entities in the array:
// In a controller.
$patched = $articles->patchEntities(
$list,
$this->request->getData(),
['associated' => ['Tags', 'Comments.Users']]
);
If you need to modify request data before it is converted into entities, you can use the Model.beforeMarshal event.
This event lets you manipulate the request data just before entities are created:
// Include use statements at the top of your file.
use Cake\Event\EventInterface;
use ArrayObject;
The $data parameter is an ArrayObject instance, so you don’t have to return it to change the data used to create
entities.
The main purpose of beforeMarshal is to assist the users to pass the validation process when simple mistakes can be
automatically resolved, or when data needs to be restructured so it can be put into the right fields.
The Model.beforeMarshal event is triggered just at the start of the validation process, one of the reasons is that
beforeMarshal is allowed to change the validation rules and the saving options, such as the field list. Validation is
triggered just after this event is finished. A common example of changing the data before it is validated is trimming all
fields before saving:
{
foreach ($data as $key => $value) {
if (is_string($value)) {
$data[$key] = trim($value);
}
}
}
Because of how the marshalling process works, if a field does not pass validation it will automatically be removed from
the data array and not be copied into the entity. This is to prevent inconsistent data from entering the entity object.
Moreover, the data in beforeMarshal is a copy of the passed data. This is because it is important to preserve the
original user input, as it may be used elsewhere.
The Model.afterMarshal event allows you to modify entities after they have been created or updated from request
data. It can be useful to apply additional validation logic that you cannot easily express through Validator methods:
The Validating Data chapter has more information on how to use the validation features of CakePHP to ensure your
data stays correct and consistent.
When creating or merging entities from request data you need to be careful of what you allow your users to change or
add in the entities. For example, by sending an array in the request containing the user_id an attacker could change
the owner of an article, causing undesirable effects:
There are two ways of protecting you against this problem. The first one is by setting the default columns that can be
safely set from a request using the Mass Assignment feature in the entities.
The second way is by using the fields option when creating or merging data into an entity:
You can also control which properties can be assigned for associations:
Using this feature is handy when you have many different functions your users can access and you want to let your users
edit different data based on their privileges.
Saving Entities
When saving request data to your database you need to first hydrate a new entity using newEntity() for passing into
save(). For example:
// In a controller
$articles = $this->fetchTable('Articles');
$article = $articles->newEntity($this->request->getData());
if ($articles->save($article)) {
// ...
}
The ORM uses the isNew() method on an entity to determine whether or not an insert or update should be performed.
If the isNew() method returns true and the entity has a primary key value, an ‘exists’ query will be issued. The
‘exists’ query can be suppressed by passing 'checkExisting' => false in the $options argument:
Once you’ve loaded some entities you’ll probably want to modify them and update your database. This is a pretty
simple exercise in CakePHP:
$articles = $this->fetchTable('Articles');
$article = $articles->find('all')->where(['id' => 2])->first();
When saving, CakePHP will apply your rules, and wrap the save operation in a database transaction. It will also only
update properties that have changed. The above save() call would generate SQL like:
8. Child associations are saved. For example, any listed hasMany, hasOne, or belongsToMany associations will be
saved.
9. The Model.afterSave event will be dispatched.
10. The Model.afterSaveCommit event will be dispatched.
The following diagram illustrates the above process:
See the Applying Application Rules section
for more information on creating and using
rules.
Warning: If no changes a
tity when it is saved, the cal
because no save is perform
//␣
˓→In a controller or table method.
$articles-
˓→>save($article, ['checkRules
Saving Associations
// In a controller.
// Only␣
˓→save the comments association
$articles->save($entity,␣
˓→['associated' => ['Comments']]);
$companies->save($entity, [
'associated' => [
'Employees',
'Employees.Addresses'
]
]);
$company-
˓→>author->name = 'Master Chef';
$company->setDirty('author',␣
˓→true);
When saving belongsTo associations, the ORM expects a single nested entity named with the singular, underscored
version of the association name. For example:
// In a controller.
$data = [
'title' => 'First Post',
'user' => [
'id' => 1,
'username' => 'mark',
],
];
$articles = $this->fetchTable('Articles');
$article = $articles->newEntity($data, [
'associated' => ['Users']
]);
$articles->save($article);
When saving hasOne associations, the ORM expects a single nested entity named with the singular, underscored version
of the association name. For example:
// In a controller.
$data = [
'id' => 1,
'username' => 'cakephp',
'profile' => [
'twitter' => '@cakephp',
],
];
$users = $this->fetchTable('Users');
$user = $users->newEntity($data, [
'associated' => ['Profiles']
]);
$users->save($user);
When saving hasMany associations, the ORM expects an array of entities named with the plural, underscored version
of the association name. For example:
// In a controller.
$data = [
'title' => 'First Post',
'comments' => [
['body' => 'Best post ever'],
['body' => 'I really like this.']
]
];
$articles = $this->fetchTable('Articles');
$article = $articles->newEntity($data, [
'associated' => ['Comments']
]);
$articles->save($article);
When saving hasMany associations, associated records will either be updated, or inserted. For the case that the record
already has associated records in the database, you have the choice between two saving strategies:
append
Associated records are updated in the database or, if not matching any existing record, inserted.
replace
Any existing records that do not match the records provided will be deleted from the database. Only provided
records will remain (or be inserted).
By default the append saving strategy is used. See HasMany Associations for details on defining the saveStrategy.
Whenever you add new records to an existing association you should always mark the association property as ‘dirty’.
This lets the ORM know that the association property has to be persisted:
$article->comments[] = $comment;
$article->setDirty('comments', true);
Without the call to setDirty() the updated comments will not be saved.
If you are creating a new entity, and want to add existing records to a has many/belongs to many association you need
to initialize the association property first:
$article->comments = [];
When saving belongsToMany associations, the ORM expects an array of entities named with the plural, underscored
version of the association name. For example:
// In a controller.
$data = [
'title' => 'First Post',
'tags' => [
['tag' => 'CakePHP'],
['tag' => 'Framework']
]
];
$articles = $this->fetchTable('Articles');
$article = $articles->newEntity($data, [
'associated' => ['Tags']
]);
$articles->save($article);
When converting request data into entities, the newEntity() and newEntities() methods will handle both arrays
of properties, as well as a list of ids at the _ids key. Using the _ids key makes it possible to building a select box or
checkbox based form controls for belongs to many associations. See the Converting Request Data into Entities section
for more information.
When saving belongsToMany associations, you have the choice between two saving strategies:
append
Only new links will be created between each side of this association. This strategy will not destroy existing links
even though they may not be present in the array of entities to be saved.
replace
When saving, existing links will be removed and new links will be created in the junction table. If there are
existing link in the database to some of the entities intended to be saved, those links will be updated, not deleted
and then re-saved.
See BelongsToMany Associations for details on defining the saveStrategy.
By default the replace strategy is used. Whenever you add new records into an existing association you should always
mark the association property as ‘dirty’. This lets the ORM know that the association property has to be persisted:
$article->tags[] = $tag;
$article->setDirty('tags', true);
Without the call to setDirty() the updated tags will not be saved.
Often you’ll find yourself wanting to make an association between two existing entities, eg. a user coauthoring an
article. This is done by using the method link(), like this:
$article = $this->Articles->get($articleId);
$user = $this->Users->get($userId);
$this->Articles->Users->link($article, [$user]);
When saving belongsToMany Associations, it can be relevant to save some additional data to the junction Table. In
the previous example of tags, it could be the vote_type of person who voted on that article. The vote_type can be
either upvote or downvote and is represented by a string. The relation is between Users and Articles.
Saving that association, and the vote_type is done by first adding some data to _joinData and then saving the
association with link(), example:
$article = $this->Articles->get($articleId);
$user = $this->Users->get($userId);
In some situations the table joining your BelongsToMany association, will have additional columns on it. CakePHP
makes it simple to save properties into these columns. Each entity in a belongsToMany association has a _joinData
property that contains the additional columns on the junction table. This data can be either an array or an Entity
instance. For example if Students BelongsToMany Courses, we could have a junction table that looks like:
When saving data you can populate the additional columns on the junction table by setting data to the _joinData
property:
$student->courses[0]->_joinData->grade = 80.12;
$student->courses[0]->_joinData->days_attended = 30;
$studentsTable->save($student);
The example above will only work if the property _joinData is already a reference to a Join Table Entity. If you don’t
already have a _joinData entity, you can create one using newEntity():
$coursesMembershipsTable = $this->fetchTable('CoursesMemberships');
$student->courses[0]->_joinData = $coursesMembershipsTable->newEntity([
'grade' => 80.12,
'days_attended' => 30
]);
$studentsTable->save($student);
The _joinData property can be either an entity, or an array of data if you are saving entities built from request data.
When saving junction table data from request data your POST data should look like:
$data = [
'first_name' => 'Sally',
'last_name' => 'Parker',
'courses' => [
[
'id' => 10,
'_joinData' => [
'grade' => 80.12,
'days_attended' => 30
]
],
// Other courses.
]
];
$student = $this->Students->newEntity($data, [
'associated' => ['Courses._joinData']
]);
See the Creating Inputs for Associated Data documentation for how to build inputs with FormHelper correctly.
Tables are capable of storing data represented in basic types, like strings, integers, floats, booleans, etc. But It can also
be extended to accept more complex types such as arrays or objects and serialize this data into simpler types that can
be saved in the database.
This functionality is achieved by using the custom types system. See the Adding Custom Types section to find out how
to build custom column Types:
use Cake\Database\TypeFactory;
TypeFactory::map('json', 'Cake\Database\Type\JsonType');
// In src/Model/Table/UsersTable.php
return $schema;
}
}
The code above maps the preferences column to the json custom type. This means that when retrieving data for
that column, it will be unserialized from a JSON string in the database and put into an entity as an array.
Likewise, when saved, the array will be transformed back into its JSON representation:
When using complex types it is important to validate that the data you are receiving from the end user is the correct
type. Failing to correctly handle complex data could result in malicious users being able to store data they would not
normally be able to.
Strict Saving
Note: If you use this method in a controller, be sure to catch the PersistenceFailedException that could be
raised.
If you want to track down the entity that failed to save, you can use the Cake\ORMException\
PersistenceFailedException::getEntity() method:
try {
$table->saveOrFail($entity);
} catch (\Cake\ORM\Exception\PersistenceFailedException $e) {
echo $e->getEntity();
}
As this internally performs a Cake\ORM\Table::save() call, all corresponding save events will be triggered.
Find an existing record based on $search or create a new record using the properties in $search and calling the
optional $callback. This method is ideal in scenarios where you need to reduce the chance of duplicate records:
$record = $table->findOrCreate(
['email' => '[email protected]'],
function ($entity) use ($otherData) {
// Only called when a new record is created.
$entity->name = $otherData['name'];
}
);
If your find conditions require custom order, associations or conditions, then the $search parameter can be a callable
or SelectQuery object. If you use a callable, it should take a SelectQuery as its argument.
The returned entity will have been saved if it was a new record. The supported options for this method are:
• atomic Should the find and save operation be done inside a transaction.
• defaults Set to false to not set $search properties into the created entity.
When handling UUID primary keys you often want to provide an externally generated value, and not have an an identifier
generated for you.
In this case make sure you are not passing the primary key as part of the marshalled data. Instead, assign the primary
key and then patch in the remaining entity data:
$record = $table->newEmptyEntity();
$record->id = $existingUuid;
$record = $table->patchEntity($record, $existingData);
$table->saveOrFail($record);
Using this method you can save multiple entities atomically. $entities can be an array of entities created using
newEntities() / patchEntities(). $options can have the same options as accepted by save():
$data = [
[
'title' => 'First post',
'published' => 1
],
[
'title' => 'Second post',
'published' => 1
],
];
$articles = $this->fetchTable('Articles');
$entities = $articles->newEntities($data);
$result = $articles->saveMany($entities);
Bulk Updates
Cake\ORM\Table::updateAll($fields, $conditions)
There may be times when updating rows individually is not efficient or necessary. In these cases it is more efficient to
use a bulk-update to modify many rows at once, by assigning the new field values, and conditions for the update:
If you need to do bulk updates and use SQL expressions, you will need to use an expression object as updateAll()
uses prepared statements under the hood:
use Cake\Database\Expression\QueryExpression;
...
function incrementCounters()
{
$expression = new QueryExpression('view_count = view_count + 1');
$this->updateAll([$expression], ['published' => true]);
}
Warning: updateAll will not trigger beforeSave/afterSave events. If you need those first load a collection of
records and update them.
updateAll() is for convenience only. You can use this more flexible interface as well:
Deleting Data
class Cake\ORM\Table
Once you’ve loaded an entity you can delete it by calling the originating table’s delete method:
// In a controller.
$entity = $this->Articles->get(2);
$result = $this->Articles->delete($entity);
Cascading Deletes
When deleting entities, associated data can also be deleted. If your HasOne and HasMany associations are configured
as dependent, delete operations will ‘cascade’ to those entities as well. By default entities in associated tables are
removed using Cake\ORM\Table::deleteAll(). You can elect to have the ORM load related entities, and delete
them individually by setting the cascadeCallbacks option to true. A sample HasMany association with both these
options enabled would be:
Note: Setting cascadeCallbacks to true, results in considerably slower deletes when compared to bulk deletes. The
cascadeCallbacks option should only be enabled when your application has important work handled by event listeners.
Bulk Deletes
If you have an array of entities you want to delete you can use deleteMany() to delete them in a single transaction:
The $options for these methods are the same as delete(). Deleting records with these method will trigger events.
Cake\ORM\Table::deleteAll($conditions)
There may be times when deleting rows one by one is not efficient or useful. In these cases it is more performant to
use a bulk-delete to remove many rows at once:
A bulk-delete will be considered successful if 1 or more rows are deleted. The function returns the number of deleted
records as an integer.
Warning: deleteAll will not trigger beforeDelete/afterDelete events. If you need callbacks triggered, first load the
entities with find() and delete them in a loop.
Strict Deletes
try {
$table->deleteOrFail($entity);
} catch (\Cake\ORM\Exception\PersistenceFailedException $e) {
echo $e->getEntity();
}
As this internally performs a Cake\ORM\Table::delete() call, all corresponding delete events will be triggered.
Behaviors
Behaviors are a way to organize and enable horizontal re-use of Model layer logic. Conceptually they are similar to
traits. However, behaviors are implemented as separate classes. This allows them to hook into the life-cycle callbacks
that models emit, while providing trait-like features.
Behaviors provide a convenient way to package up behavior that is common across many models. For example,
CakePHP includes a TimestampBehavior. Many models will want timestamp fields, and the logic to manage these
fields is not specific to any one model. It is these kinds of scenarios that behaviors are a perfect fit for.
Using Behaviors
Behaviors provide a way to create horizontally re-usable pieces of logic related to table classes. You may be wondering
why behaviors are regular classes and not traits. The primary reason for this is event listeners. While traits would allow
for re-usable pieces of logic, they would complicate binding events.
To add a behavior to your table you can call the addBehavior() method. Generally the best place to do this is in the
initialize() method:
namespace App\Model\Table;
use Cake\ORM\Table;
As with associations, you can use plugin syntax and provide additional configuration options:
namespace App\Model\Table;
use Cake\ORM\Table;
Core Behaviors
CounterCache
class Cake\ORM\Behavior\CounterCacheBehavior
Often times web applications need to display counts of related objects. For example, when showing a list of articles
you may want to display how many comments it has. Or when showing a user you might want to show how many
friends/followers she has. The CounterCache behavior is intended for these situations. CounterCache will update a
field in the associated models assigned in the options when it is invoked. The fields should exist in the database and be
of the type INT.
Basic Usage
You enable the CounterCache behavior like any other behavior, but it won’t do anything until you configure some
relations and the field counts that should be stored on each of them. Using our example below, we could cache the
comment count for each article with the following:
The CounterCache configuration should be a map of relation names and the specific configuration for that relation.
As you see you need to add the behavior on the “other side” of the association where you actually want the field to be
updated. In this example the behavior is added to the CommentsTable even though it updates the comment_count
field in the ArticlesTable.
The counter’s value will be updated each time an entity is saved or deleted. The counter will not be updated when you
• save the entity without changing data or
• use updateAll() or
• use deleteAll() or
• execute SQL you have written
Advanced Usage
If you need to keep a cached counter for less than all of the related records, you can supply additional conditions or
finder methods to generate a counter value:
If you don’t have a custom finder method you can provide an array of conditions to find records instead:
$this->addBehavior('CounterCache', [
'Articles' => [
'comment_count' => [
'conditions' => ['Comments.spam' => false],
],
],
]);
If you want CounterCache to update multiple fields, for example both showing a conditional count and a basic count
you can add these fields in the array:
$this->addBehavior('CounterCache', [
'Articles' => ['comment_count',
'published_comment_count' => [
'finder' => 'published',
],
],
]);
If you want to calculate the CounterCache field value on your own, you can set the ignoreDirty option to true. This
will prevent the field from being recalculated if you’ve set it dirty before:
$this->addBehavior('CounterCache', [
'Articles' => [
'comment_count' => [
'ignoreDirty' => true,
],
],
]);
Lastly, if a custom finder and conditions are not suitable you can provide a callback function. Your function must return
the count value to be stored:
$this->addBehavior('CounterCache', [
'Articles' => [
'rating_avg' => function ($event, $entity, $table, $original) {
return 4.5;
(continues on next page)
Your function can return false to skip updating the counter column, or a SelectQuery object that produced the
count value. If you return a SelectQuery object, your query will be used as a subquery in the update statement. The
$table parameter refers to the table object holding the behavior (not the target relation) for convenience. The callback
is invoked at least once with $original set to false. If the entity-update changes the association then the callback is
invoked a second time with true, the return value then updates the counter of the previously associated item.
Note: The CounterCache behavior works for belongsTo associations only. For example for “Comments belongsTo
Articles”, you need to add the CounterCache behavior to the CommentsTable in order to generate comment_count
for Articles table.
It is possible to use the CounterCache behavior in a belongsToMany association. First, you need to add the through
and cascadeCallbacks options to the belongsToMany association:
Also see Using the ‘through’ Option how to configure a custom join table.
The CommentsArticles is the name of the junction table classname. If you don’t have it you should create it with the
bake CLI tool.
In this src/Model/Table/CommentsArticlesTable.php you then need to add the behavior with the same code as
described above.:
$this->addBehavior('CounterCache', [
'Articles' => ['comments_count'],
]);
Finally clear all caches with bin/cake cache clear_all and try it out.
Timestamp
class Cake\ORM\Behavior\TimestampBehavior
The timestamp behavior allows your table objects to update one or more timestamps on each model event. This is
primarily used to populate data into created and modified fields. However, with some additional configuration, you
can update any timestamp/datetime column on any event a table publishes.
Basic Usage
If you need to modify fields with different names, or want to update additional timestamp fields on custom events you
can use some additional configuration:
As you can see above, in addition to the standard Model.beforeSave event, we are also updating the completed_at
column when orders are completed.
Sometimes you’ll want to update just the timestamps on an entity without changing any other properties. This is
sometimes referred to as ‘touching’ a record. In CakePHP you can use the touch() method to do exactly this:
To disable the automatic modification of the updated timestamp column when saving an entity you can mark the
attribute as ‘dirty’:
Translate
class Cake\ORM\Behavior\TranslateBehavior
The Translate behavior allows you to create and retrieve translated copies of your entities in multiple languages.
Warning: The TranslateBehavior does not support composite primary keys at this point in time.
Translation Strategies
The behavior offers two strategies for how the translations are stored.
1. Shadow table Strategy: This strategy uses a separate “shadow table” for each Table object to store translation of
all translated fields of that table. This is the default strategy.
2. Eav Strategy: This strategy uses a i18n table where it stores the translation for each of the fields of any given
Table object that it’s bound to.
Let’s assume we have an articles table and we want it’s title and body fields to be translated. For that we create
a shadow table articles_translations:
The shadow table needs id and locale columns which together form the primary key and other columns with same
name as primary table which need to be translated.
A note on language abbreviations: The Translate Behavior doesn’t impose any restrictions on the language identifier,
the possible values are only restricted by the locale column type/size. locale is defined as varchar(6) in case you
want to use abbreviations like es-419 (Spanish for Latin America, language abbreviation with area code UN M.49128 ).
Tip: It’s wise to use the same language abbreviations as required for Internationalization and Localization.
Thus you are consistent and switching the language works identical for both, the Translate behavior and
Internationalization and Localization.
So it’s recommended to use either the two letter ISO code of the language like en, fr, de or the full locale name such
as fr_FR, es_AR, da_DK which contains both the language and the country where it is spoken.
Eav Strategy
In order to use the Eav strategy, you need to create a i18n table with the correct schema. Currently the only way of
loading the i18n table is by manually running the following SQL script in your database:
Attaching the behavior can be done in the initialize() method in your Table class:
For shadow table strategy specifying the fields key is optional as the behavior can infer the fields from the shadow
table columns.
If you want to use the EavStrategy then you can configure the behavior as:
For EavStrategy you are required to pass the fields key in the configuration array. This list of fields is needed to
tell the behavior what columns will be able to store translations.
By default the locale specified in App.defaultLocale config is used as default locale for the TranslateBehavior.
You can override that by setting defaultLocale config of the behavior:
Quick tour
Regardless of the datastructure strategy you choose the behavior provides the same API to manage translations.
Now, select a language to be used for retrieving entities by changing the application language, which will affect all
translations:
$article = $this->Articles->get(12);
echo $article->title; // Echoes 'A title', not translated yet
$article = $this->Articles->get(12);
echo $article->title; // Echoes 'Un Artículo', yay piece of cake!
Working with multiple translations can be done by using a special trait in your Entity class:
use Cake\ORM\Behavior\Translate\TranslateTrait;
use Cake\ORM\Entity;
$article = $this->Articles->find('translations')->first();
echo $article->translation('es')->title; // 'Un Artículo'
If you want to go deeper on how it works or how to tune the behavior for your needs, keep on reading the rest of this
chapter.
If you wish to use a table other than i18n for translating a particular repository, you can specify the name of the table
class name for your custom table in the behavior’s configuration. This is common when you have multiple tables to
translate and you want a cleaner separation of the data that is stored for each different table:
You need to make sure that any custom table you use has the columns field, foreign_key, locale and model.
As shown above you can use the setLocale() method to choose the active translation for entities that are loaded:
This method works with any finder in your tables. For example, you can use TranslateBehavior with find('list'):
I18n::setLocale('es');
$data = $this->Articles->find('list')->toArray();
When building interfaces for updating translated content, it is often helpful to show one or more translation(s) at the
same time. You can use the translations finder for this:
In the example above you will get a list of entities back that have a _translations property set. This property will
contain a list of translation data entities. For example the following properties would be accessible:
// Outputs 'en'
echo $article->_translations['en']->locale;
// Outputs 'title'
echo $article->_translations['en']->field;
A more elegant way for dealing with this data is by adding a trait to the entity class that is used for your table:
use Cake\ORM\Behavior\Translate\TranslateTrait;
use Cake\ORM\Entity;
This trait contains a single method called translation, which lets you access or create new translation entities on the
fly:
// Outputs 'title'
echo $article->translation('en')->title;
You can limit the languages that are fetched from the database for a particular set of records:
Translation records can contain any string, if a record has been translated and stored as an empty string (‘’) the translate
behavior will take and use this to overwrite the original field value.
If this is undesired, you can ignore translations which are empty using the allowEmptyTranslations config key:
The above would only load translated data that had content.
It is also possible to find translations for any association in a single find operation:
$article = $this->Articles->find('translations')->contain([
'Categories' => function ($query) {
return $query->find('translations');
}
])->first();
// Outputs 'Programación'
echo $article->categories[0]->translation('es')->name;
This assumes that Categories has the TranslateBehavior attached to it. It simply uses the query builder function for
the contain clause to use the translations custom finder in the association.
calling I18n::setLocale('es'); changes the default locale for all translated finds, there may be times you wish to re-
trieve translated content without modifying the application’s state. For these scenarios use the behavior’s setLocale()
method:
// specific locale.
$this->Articles->setLocale('es');
$article = $this->Articles->get(12);
echo $article->title; // Echoes 'Un Artículo', yay piece of cake!
Note that this only changes the locale of the Articles table, it would not affect the language of associated data. To affect
associated data it’s necessary to call the method on each table, for example:
$this->Articles->setLocale('es');
$this->Articles->Categories->setLocale('es');
This example also assumes that Categories has the TranslateBehavior attached to it.
TranslateBehavior does not substitute find conditions by default. You need to use translationField() method to
compose find conditions on translated fields:
$this->Articles->setLocale('es');
$query = $this->Articles->find()->where([
$this->Articles->translationField('title') => 'Otro Título'
]);
The philosophy behind the TranslateBehavior is that you have an entity representing the default language, and multiple
translations that can override certain fields in such entity. Keeping this in mind, you can intuitively save translations
for any given entity. For example, given the following setup:
// in src/Model/Table/ArticlesTable.php
class ArticlesTable extends Table
{
public function initialize(array $config): void
{
$this->addBehavior('Translate', ['fields' => ['title', 'body']]);
}
}
// in src/Model/Entity/Article.php
class Article extends Entity
{
use TranslateTrait;
}
$this->Articles->save($article);
So, after you save your first article, you can now save a translation for it, there are a couple ways to do it. The first one
is setting the language directly into the entity:
$article->_locale = 'es';
$article->title = 'Mi primer Artículo';
$this->Articles->save($article);
After the entity has been saved, the translated field will be persisted as well, one thing to note is that values from the
default language that were not overridden will be preserved:
Once you override the value, the translation for that field will be saved and can be retrieved as usual:
The second way to use for saving entities in another language is to set the default language directly to the table:
$this->Articles->setLocale('es');
$this->Articles->save($article);
Setting the language directly in the table is useful when you need to both retrieve and save entities for the same language
or when you need to save multiple entities at once.
It is a common requirement to be able to add or edit multiple translations to any database record at the same time. This
can be done using the TranslateTrait:
use Cake\ORM\Behavior\Translate\TranslateTrait;
use Cake\ORM\Entity;
$translations = [
'fr' => ['title' => "Un article"],
'es' => ['title' => 'Un artículo'],
];
$this->Articles->save($article);
// In a view template.
<?= $this->Form->create($article); ?>
<fieldset>
<legend>French</legend>
<?= $this->Form->control('_translations.fr.title'); ?>
<?= $this->Form->control('_translations.fr.body'); ?>
</fieldset>
<fieldset>
<legend>Spanish</legend>
<?= $this->Form->control('_translations.es.title'); ?>
<?= $this->Form->control('_translations.es.body'); ?>
</fieldset>
$article = $this->Articles->newEntity($this->request->getData());
$this->Articles->save($article);
This will result in your article, the french and spanish translations all being persisted. You’ll need to remember to add
_translations into the $_accessible fields of your entity as well.
When attaching TranslateBehavior to a model, you can define the validator that should be used when translation
records are created/modified by the behavior during newEntity() or patchEntity():
The above will use the validator created by validationTranslated to validated translated entities.
Tree
class Cake\ORM\Behavior\TreeBehavior
It’s fairly common to want to store hierarchical data in a database table. Examples of such data might be categories
with unlimited subcategories, data related to a multilevel menu system or a literal representation of hierarchy such as
departments in a company.
Relational databases are usually not well suited for storing and retrieving this type of data, but there are a few known
techniques that can make them effective for working with multi-level information.
The TreeBehavior helps you maintain a hierarchical data structure in the database that can be queried without much
overhead and helps reconstruct the tree data for finding and displaying processes.
Requirements
Warning: The TreeBehavior does not support composite primary keys at this point in time.
A Quick Tour
You enable the Tree behavior by adding it to the Table you want to store hierarchical data in:
Once added, you can let CakePHP build the internal structure if the table is already holding some rows:
// In a controller
$categories = $this->getTableLocator()->get('Categories');
$categories->recover();
You can verify it works by getting any row from the table and asking for the count of descendants it has:
$node = $categories->get(1);
echo $categories->childCount($node);
129 https://www.sitepoint.com/hierarchical-data-database-2/
Getting a flat list of the descendants for a node can be done with:
$descendants = $categories
->find('children', for: 1)
->where(['name LIKE' => '%Foo%'])
->all();
If you instead need a threaded list, where children for each node are nested in a hierarchy, you can stack the ‘threaded’
finder:
$children = $categories
->find('children', for: 1)
->find('threaded')
->toArray();
While, if you’re using custom parent_id you need to pass it in the ‘threaded’ finder option (i.e. parentField) .
Note: For more information on ‘threaded’ finder options see Finding Threaded Data logic
Traversing threaded results usually requires recursive functions in, but if you only require a result set containing a single
field from each level so you can display a list, in an HTML select for example, it is better to use the treeList finder:
$list = $categories->find('treeList')->toArray();
My Categories
_Fun
__Sport
___Surfing
___Skating
_Trips
__National
__International
$query = $categories->find('treeList',
keyPath: 'url',
valuePath: 'id',
spacer: ' '
);
$query = $categories->find('treeList',
valuePath: function($entity){
return $entity->url . ' ' . $entity->id
}
);
One very common task is to find the tree path from a particular node to the root of the tree. This is useful, for example,
for adding the breadcrumbs list for a menu structure:
$nodeId = 5;
$crumbs = $categories->find('path', for: $nodeId)->all();
Trees constructed with the TreeBehavior cannot be sorted by any column other than lft, this is because the internal
representation of the tree depends on this sorting. Luckily, you can reorder the nodes inside the same level without
having to change their parent:
$node = $categories->get(5);
// Move the node to the top of the list inside the same level.
$categories->moveUp($node, true);
Configuration
If the default column names that are used by this behavior don’t match your own schema, you can provide aliases for
them:
Knowing the depth of tree nodes can be useful when you want to retrieve nodes only up to a certain level, for example,
when generating menus. You can use the level option to specify the field that will save level of each node:
$this->addBehavior('Tree', [
'level' => 'level', // Defaults to null, i.e. no level saving
]);
If you don’t want to cache the level using a db field you can use TreeBehavior::getLevel() method to get level of
a node.
Sometimes you want to persist more than one tree structure inside the same table, you can achieve that by using the
‘scope’ configuration. For example, in a locations table you may want to create one tree per country:
In the previous example, all tree operations will be scoped to only the rows having the column country_name set to
‘Brazil’. You can change the scoping on the fly by using the ‘config’ function:
Optionally, you can have a finer grain control of the scope by passing a closure as the scope:
Deletion Behavior
By enabling the cascadeCallbacks option, TreeBehavior will load all of the entities that are going to be deleted.
Once loaded, these entities will be deleted individually using Table::delete(). This enables ORM callbacks to be
fired when tree nodes are deleted:
$this->addBehavior('Tree', [
'cascadeCallbacks' => true,
]);
By default, recover() sorts the items using the primary key. This works great if this is a numeric (auto increment)
column, but can lead to weird results if you use UUIDs.
If you need custom sorting for the recovery, you can set a custom order clause in your config:
$this->addBehavior('Tree', [
'recoverOrder' => ['country_name' => 'DESC'],
]);
When using the Tree behavior, you usually don’t need to worry about the internal representation of the hierarchical
structure. The positions where nodes are placed in the tree are deduced from the parent_id column in each of your
entities:
$aCategory = $categoriesTable->get(10);
$aCategory->parent_id = 5;
$categoriesTable->save($aCategory);
Providing inexistent parent ids when saving or attempting to create a loop in the tree (making a node child of itself)
will throw an exception.
You can make a node into a root in the tree by setting the parent_id column to null:
$aCategory = $categoriesTable->get(10);
$aCategory->parent_id = null;
$categoriesTable->save($aCategory);
Deleting Nodes
Deleting a node and all its sub-tree (any children it may have at any depth in the tree) is trivial:
$aCategory = $categoriesTable->get(10);
$categoriesTable->delete($aCategory);
The TreeBehavior will take care of all internal deleting operations for you. It is also possible to only delete one node
and re-assign all children to the immediately superior parent node in the tree:
$aCategory = $categoriesTable->get(10);
$categoriesTable->removeFromTree($aCategory);
$categoriesTable->delete($aCategory);
All children nodes will be kept and a new parent will be assigned to them.
The deletion of a node is based off of the lft and rght values of the entity. This is important to note when looping
through the various children of a node for conditional deletes:
TreeBehavior will reorder the lft and rght values of records in the table when a node is deleted.
In our example above, the lft and rght values of the entities inside $descendants will be inaccurate. You will need
to reload existing entity objects if you need an accurate shape of the tree.
Creating a Behavior
In the following examples we will create a very simple SluggableBehavior. This behavior will allow us to populate
a slug field with the results of Text::slug() based on another field.
Before we create our behavior we should understand the conventions for behaviors:
• Behavior files are located in src/Model/Behavior, or MyPlugin\Model\Behavior.
• Behavior classes should be in the App\Model\Behavior namespace, or MyPlugin\Model\Behavior names-
pace.
• Behavior class names end in Behavior.
• Behaviors extend Cake\ORM\Behavior.
To create our sluggable behavior. Put the following into src/Model/Behavior/SluggableBehavior.php:
namespace App\Model\Behavior;
use Cake\ORM\Behavior;
(continues on next page)
Similar to tables, behaviors also have an initialize() hook where you can put your behavior’s initialization code,
if required:
We can now add this behavior to one of our table classes. In this example we’ll use an ArticlesTable, as articles
often have slug properties for creating friendly URLs:
namespace App\Model\Table;
use Cake\ORM\Table;
Our new behavior doesn’t do much of anything right now. Next, we’ll add a mixin method and an event listener so that
when we save entities we can automatically slug a field.
Any public method defined on a behavior will be added as a ‘mixin’ method on the table object it is attached to. If
you attach two behaviors that provide the same methods an exception will be raised. If a behavior provides the same
method as a table class, the behavior method will not be callable from the table. Behavior mixin methods will receive
the exact same arguments that are provided to the table. For example, if our SluggableBehavior defined the following
method:
When creating behaviors, there may be situations where you don’t want to expose public methods as mixin methods.
In these cases you can use the implementedMethods configuration key to rename or exclude mixin methods. For
example if we wanted to prefix our slug() method we could do the following:
protected $_defaultConfig = [
'implementedMethods' => [
'superSlug' => 'slug',
]
];
Applying this configuration will make slug() not callable, however it will add a superSlug() mixin method to the
table. Notably if our behavior implemented other public methods they would not be available as mixin methods with
the above configuration.
Since the exposed methods are decided by configuration you can also rename/remove mixin methods when adding a
behavior to a table. For example:
Now that our behavior has a mixin method to slug fields, we can implement a callback listener to automatically slug a
field when entities are saved. We’ll also modify our slug method to accept an entity instead of just a plain value. Our
behavior should now look like:
namespace App\Model\Behavior;
use ArrayObject;
use Cake\Datasource\EntityInterface;
use Cake\Event\EventInterface;
use Cake\ORM\Behavior;
use Cake\ORM\Entity;
use Cake\ORM\Query\SelectQuery;
use Cake\Utility\Text;
{
if (...) {
$event->stopPropagation();
$event->setResult(false);
return;
}
$this->slug($entity);
}
Alternatively, you can return false from the callback. This has the same effect as stopping event propagation.
Defining Finders
Now that we are able to save articles with slug values, we should implement a finder method so we can fetch articles
by their slug. Behavior finder methods, use the same conventions as Custom Finder Methods do. Our find('slug')
method would look like:
Once our behavior has the above method we can call it:
When creating behaviors, there may be situations where you don’t want to expose finder methods, or you need to rename
finders to avoid duplicated methods. In these cases you can use the implementedFinders configuration key to rename
or exclude finder methods. For example if we wanted to rename our find(slug) method we could do the following:
Applying this configuration will make find('slug') trigger an error. However it will make find('slugged')
available. Notably if our behavior implemented other finder methods they would not be available, as they are not
included in the configuration.
Since the exposed methods are decided by configuration you can also rename/remove finder methods when adding a
behavior to a table. For example:
Behaviors can define logic for how the custom fields they provide are marshalled by implementing the Cake\ORM\
PropertyMarshalInterface. This interface requires a single method to be implemented:
The TranslateBehavior has a non-trivial implementation of this interface that you might want to refer to.
To remove a behavior from your table you can call the removeBehavior() method:
Once you’ve attached behaviors to your Table instance you can introspect the loaded behaviors, or access specific
behaviors using the BehaviorRegistry:
To modify the configuration of an already loaded behavior you can combine the BehaviorRegistry::get command
with config command provided by the InstanceConfigTrait trait.
For example, if a parent class, such as AppTable, loaded the Timestamp behavior you could do the following to add,
modify or remove the configurations for the behavior. In this case, we will add an event we want Timestamp to respond
to:
namespace App\Model\Table;
Schema System
CakePHP features a schema system that is capable of reflecting and generating schema information for tables in SQL
datastores. The schema system can generate/reflect a schema for any SQL platform that CakePHP supports.
The main pieces of the schema system are Cake\Database\Schema\Collection and Cake\Database\Schema\
TableSchema. These classes give you access to database-wide and individual Table object features respectively.
The primary use of the schema system is for Fixtures. However, it can also be used in your application if required.
Schema\TableSchema Objects
class Cake\Database\Schema\TableSchema
The schema subsystem provides a simple TableSchema object to hold data about a table in a database. This object is
returned by the schema reflection features:
use Cake\Database\Schema\TableSchema;
Schema\TableSchema objects allow you to build up information about a table’s schema. It helps to normalize and
validate the data used to describe a table. For example, the following two forms are equivalent:
$schema->addColumn('title', 'string');
// and
$schema->addColumn('title', [
'type' => 'string'
]);
While equivalent, the 2nd form allows more detail and control. This emulates the existing features available in Schema
files + the fixture schema in 2.x.
Columns are either added as constructor arguments, or via addColumn(). Once fields are added information can be
fetched using column() or columns():
Indexes are added using the addIndex(). Constraints are added using addConstraint(). Indexes and constraints
cannot be added for columns that do not exist, as it would result in an invalid state. Indexes are different from constraints,
and exceptions will be raised if you try to mix types between the methods. An example of both methods is:
If you add a primary key constraint to a single integer column it will automatically be converted into a auto-
increment/serial column depending on the database platform:
In the above example the id column would generate the following SQL in MySQL:
If your primary key contains more than one column, none of them will automatically be converted to an auto-increment
value. Instead you will need to tell the table object which column in the composite key you want to auto-increment:
The autoIncrement option only works with integer and biginteger columns.
Indexes and constraints can be read out of a table object using accessor methods. Assuming that $schema is a populated
TableSchema instance you could do the following:
Some drivers (primarily MySQL) support and require additional table metadata. In the case of MySQL the CHARSET,
COLLATE and ENGINE properties are required for maintaining a table’s structure in MySQL. The following could be
used to add table options:
$schema->options([
'engine' => 'InnoDB',
'collate' => 'utf8_unicode_ci',
]);
Platform dialects only handle the keys they are interested in and ignore the rest. Not all options are supported on all
platforms.
Using the createSql() or dropSql() you can get platform specific SQL for creating or dropping a specific table:
$db = ConnectionManager::get('default');
$schema = new TableSchema('posts', $fields, $indexes);
// Create a table
$queries = $schema->createSql($db);
foreach ($queries as $sql) {
$db->execute($sql);
}
// Drop a table
$sql = $schema->dropSql($db);
$db->execute($sql);
By using a connection’s driver the schema data can be converted into platform specific SQL. The return of createSql
and dropSql is a list of SQL queries required to create a table and the required indexes. Some platforms may require
multiple statements to create tables with comments and/or indexes. An array of queries is always returned.
Schema Collections
class Cake\Database\Schema\Collection
Collection provides access to the various tables available on a connection. You can use it to get the list of tables or
reflect tables into TableSchema objects. Basic usage of the class looks like:
$db = ConnectionManager::get('default');
The SchemaCacheShell provides a simple CLI tool for managing your application’s metadata caches. In deployment
situations it is helpful to rebuild the metadata cache in-place without clearing the existing cache data. You can do this
by running:
This will rebuild the metadata cache for all tables on the default connection. If you only need to rebuild a single table
you can do that by providing its name:
In addition to building cached data, you can use the SchemaCacheShell to remove cached metadata as well:
Caching
class Cake\Cache\Cache
Caching can be used to make reading from expensive or slow resources faster, by maintaining a second copy of the
required data in a faster or closer storage system. For example, you can store the results of expensive queries, or remote
webservice access that doesn’t frequently change in a cache. Once in the cache, reading data from the cache is much
cheaper than accessing the remote resource.
Caching in CakePHP is facilitated by the Cache class. This class provides a static interface and uniform API to interact
with various Caching implementations. CakePHP provides several cache engines, and provides a simple interface if
you need to build your own backend. The built-in caching engines are:
• File File cache is a simple cache that uses local files. It is the slowest cache engine, and doesn’t provide as
many features for atomic operations. However, since disk storage is often quite cheap, storing large objects, or
elements that are infrequently written work well in files.
• Memcached Uses the Memcached130 extension.
• Redis Uses the phpredis131 extension. Redis provides a fast and persistent cache system similar to Memcached,
also provides atomic operations.
• Apcu APCu cache uses the PHP APCu132 extension. This extension uses shared memory on the webserver to
store objects. This makes it very fast, and able to provide atomic read/write features.
• Array Stores all data in an array. This engine does not provide persistent storage and is intended for use in
application test suites.
• Null The null engine doesn’t actually store anything and fails all read operations.
Regardless of the CacheEngine you choose to use, your application interacts with Cake\Cache\Cache.
130 https://php.net/memcached
131 https://github.com/phpredis/phpredis
132 https://php.net/apcu
505
CakePHP Book, Release 5.x
Your application can configure any number of ‘engines’ during its bootstrap process. Cache engine configurations are
defined in config/app.php.
For optimal performance CakePHP requires two cache engines to be defined.
• _cake_core_ is used for storing file maps, and parsed results of Internationalization & Localization files.
• _cake_model_, is used to store schema descriptions for your applications models.
Using multiple engine configurations also lets you incrementally change the storage as needed. For example in your
config/app.php you could put the following:
// ...
'Cache' => [
'short' => [
'className' => 'File',
'duration' => '+1 hours',
'path' => CACHE,
'prefix' => 'cake_short_',
],
// Using a fully namespaced name.
'long' => [
'className' => 'Cake\Cache\Engine\FileEngine',
'duration' => '+1 week',
'probability' => 100,
'path' => CACHE . 'long' . DS,
],
]
// ...
Configuration options can also be provided as a DSN string. This is useful when working with environment variables
or PaaS providers:
Cache::setConfig('short', [
'url' => 'memcached://user:password@cache-host/?timeout=3600&prefix=myapp_',
]);
When using a DSN string you can define any additional parameters/options as query string arguments.
You can also configure Cache engines at runtime:
The name of these engine configurations (‘short’ and ‘long’) are used as the $config parameter for Cake\Cache\
Cache::write() and Cake\Cache\Cache::read(). When configuring cache engines you can refer to the class
name using the following syntaxes:
// Full namespace
Cache::setConfig('long', ['className' => 'Cake\Cache\Engine\FileEngine']);
Note: When using the FileEngine you might need to use the mask option to ensure cache files are made with the
correct permissions.
Engine Options
FileEngine Options
RedisEngine Options
MemcacheEngine Options
In the event that an engine is not available, such as the FileEngine trying to write to an unwritable folder or the
RedisEngine failing to connect to Redis, the engine will fall back to the noop NullEngine and trigger a loggable
error. This prevents the application from throwing an uncaught exception due to cache failure.
You can configure Cache configurations to fall back to a specified config using the fallback configuration key:
Cache::setConfig('redis', [
'className' => 'Redis',
'duration' => '+1 hours',
'prefix' => 'cake_redis_',
'host' => '127.0.0.1',
'port' => 6379,
'fallback' => 'default',
]);
If initializing the RedisEngine instance fails, the redis cache configuration would fall back to using the default
cache configuration. If initializing the engine for the default cache configuration also fails, in this scenario the engine
would fall back once again to the NullEngine and prevent the application from throwing an uncaught exception.
You can turn off cache fallbacks with false:
Cache::setConfig('redis', [
'className' => 'Redis',
'duration' => '+1 hours',
'prefix' => 'cake_redis_',
'host' => '127.0.0.1',
'port' => 6379,
'fallback' => false
]);
static Cake\Cache\Cache::drop($key)
Once a configuration is created you cannot change it. Instead you should drop the configuration and re-create it using
Cake\Cache\Cache::drop() and Cake\Cache\Cache::setConfig(). Dropping a cache engine will remove the
config and destroy the adapter if it was constructed.
Writing to a Cache
Cache::write() will write a $value to the Cache. You can read or delete this value later by referring to it by $key.
You may specify an optional configuration to store the cache in as well. If no $config is specified, default will be
used. Cache::write() can store any type of object and is ideal for storing results of model finds:
$posts = Cache::read('posts');
if ($posts === null) {
(continues on next page)
Using Cache::write() and Cache::read() to reduce the number of trips made to the database to fetch posts.
Note: If you plan to cache the result of queries made with the CakePHP ORM, it is better to use the built-in cache
capabilities of the Query object as described in the Caching Loaded Results section
You may find yourself needing to write multiple cache keys at once. While you can use multiple calls to write(),
writeMany() allows CakePHP to use more efficient storage APIs where available. For example using writeMany()
save multiple network connections when using Memcached:
$result = Cache::writeMany([
'article-' . $slug => $article,
'article-' . $slug . '-comments' => $comments
]);
Atomic writes
Using Cache::add() will let you atomically set a key to a value if the key does not already exist in the cache. If the
key already exists in the cache backend or the write fails, add() will return false:
Cache helps with read-through caching. If the named cache key exists, it will be returned. If the key does not exist, the
callable will be invoked and the results stored in the cache at the provided key.
For example, you often want to cache remote service call results. You could use remember() to make this simple:
class IssueService
{
public function allIssues($repo)
{
return Cache::remember($repo . '-issues', function () use ($repo) {
return $this->fetchAll($repo);
});
}
}
Cache::read() is used to read the cached value stored under $key from the $config. If $config is null the default
config will be used. Cache::read() will return the cached value if it is a valid cache or null if the cache has expired
or doesn’t exist. Use strict comparison operators === or !== to check the success of the Cache::read() operation.
For example:
$cloud = Cache::read('cloud');
if ($cloud !== null) {
return $cloud;
}
return $cloud;
Or if you are using another cache configuration called short, you can specify it in Cache::read() and
Cache::write() calls as below:
return $cloud;
After you’ve written multiple keys at once, you’ll probably want to read them as well. While you could use multiple
calls to read(), readMany() allows CakePHP to use more efficient storage APIs where available. For example using
readMany() save multiple network connections when using Memcached:
$result = Cache::readMany([
'article-' . $slug,
'article-' . $slug . '-comments'
]);
// $result will contain
['article-first-post' => '...', 'article-first-post-comments' => '...']
Cache::delete() will allow you to completely remove a cached object from the store:
// Remove a key
Cache::delete('my_key');
As of 4.4.0, the RedisEngine also provides a deleteAsync() method which uses the UNLINK operation to remove
cache keys:
Cache::pool('redis')->deleteAsync('my_key');
After you’ve written multiple keys at once, you may want to delete them. While you could use multiple calls to
delete(), deleteMany() allows CakePHP to use more efficient storage APIs where available. For example using
deleteMany() save multiple network connections when using Memcached:
$result = Cache::deleteMany([
'article-' . $slug,
'article-' . $slug . '-comments'
]);
// $result will contain
['article-first-post' => true, 'article-first-post-comments' => true]
Destroy all cached values for a cache configuration. In engines like: Apcu, Memcached, the cache configuration’s
prefix is used to remove cache entries. Make sure that different cache configurations have different prefixes:
As of 4.4.0, the RedisEngine also provides a clearBlocking() method which uses the UNLINK operation to remove
cache keys:
Cache::pool('redis')->clearBlocking();
Note: Because APCu uses isolated caches for webserver and CLI they have to be cleared separately (CLI cannot clear
webserver and vice versa).
Counters in your application are good candidates for storage in a cache. As an example, a simple countdown for
remaining ‘slots’ in a contest could be stored in Cache. The Cache class exposes atomic ways to increment/decrement
counter values. Atomic operations are important for these values as it reduces the risk of contention, and ability for
two users to simultaneously lower the value by one, resulting in an incorrect value.
After setting an integer value you can manipulate it using increment() and decrement():
Cache::write('initial_count', 10);
// Later on
Cache::decrement('initial_count');
// Or
Cache::increment('initial_count');
Note: Incrementing and decrementing do not work with FileEngine. You should use APCu, Redis or Memcached
instead.
You can greatly improve the performance of your application by putting results that infrequently change, or that are
subject to heavy reads into the cache. A perfect example of this are the results from Cake\ORM\Table::find(). The
Query object allows you to cache results using the cache() method. See the Caching Loaded Results section for more
information.
Using Groups
Sometimes you will want to mark multiple cache entries to belong to certain group or namespace. This is a common
requirement for mass-invalidating keys whenever some information changes that is shared among all entries in the same
group. This is possible by declaring the groups in cache configuration:
Cache::setConfig('site_home', [
'className' => 'Redis',
'duration' => '+999 days',
'groups' => ['comment', 'article'],
]);
Let’s say you want to store the HTML generated for your homepage in cache, but would also want to automatically
invalidate this cache every time a comment or post is added to your database. By adding the groups comment and
article, we have effectively tagged any key stored into this cache configuration with both group names.
For instance, whenever a new post is added, we could tell the Cache engine to remove all entries associated to the
article group:
// src/Model/Table/ArticlesTable.php
public function afterSave($event, $entity, $options = [])
{
if ($entity->isNew()) {
Cache::clearGroup('article', 'site_home');
}
}
groupConfigs() can be used to retrieve mapping between group and configurations, i.e.: having the same group:
// src/Model/Table/ArticlesTable.php
/**
* A variation of previous example that clears all Cache configurations
* having the same group
*/
public function afterSave($event, $entity, $options = [])
{
if ($entity->isNew()) {
$configs = Cache::groupConfigs('article');
foreach ($configs['article'] as $config) {
Cache::clearGroup('article', $config);
(continues on next page)
Groups are shared across all cache configs using the same engine and same prefix. If you are using groups and want to
take advantage of group deletion, choose a common prefix for all your configs.
static Cake\Cache\Cache::disable
You may need to disable all Cache read & writes when trying to figure out cache expiration related issues. You can do
this using enable() and disable():
static Cake\Cache\Cache::enabled
If you need to check on the state of Cache, you can use enabled().
You can provide custom Cache engines in App\Cache\Engine as well as in plugins using $plugin\
Cache\Engine. Cache engines must be in a cache directory. If you had a cache engine named
MyCustomCacheEngine it would be placed in either src/Cache/Engine/MyCustomCacheEngine.php. Or in plu-
gins/MyPlugin/src/Cache/Engine/MyCustomCacheEngine.php as part of a plugin. Cache configs from plugins
need to use the plugin dot syntax:
Cache::setConfig('custom', [
'className' => 'MyPlugin.MyCustomCache',
// ...
]);
Custom Cache engines must extend Cake\Cache\CacheEngine which defines a number of abstract methods as well
as provides a few initialization methods.
The required API for a CacheEngine is
class Cake\Cache\CacheEngine
The base class for all cache engines used with Cache.
Cake\Cache\CacheEngine::write($key, $value)
Returns
boolean for success.
Write value for a key into cache, Return true if the data was successfully cached, false on failure.
Cake\Cache\CacheEngine::read($key)
Returns
The cached value or null for failure.
Read a key from the cache. Return null to indicate the entry has expired or does not exist.
Cake\Cache\CacheEngine::delete($key)
Returns
Boolean true on success.
Delete a key from the cache. Return false to indicate that the entry did not exist or could not be deleted.
Cake\Cache\CacheEngine::clear($check)
Returns
Boolean true on success.
Delete all keys from the cache. If $check is true, you should validate that each value is actually expired.
Cake\Cache\CacheEngine::clearGroup($group)
Returns
Boolean true on success.
Delete all keys from the cache belonging to the same group.
Cake\Cache\CacheEngine::decrement($key, $offset = 1)
Returns
Boolean true on success.
Decrement a number under the key and return decremented value
Cake\Cache\CacheEngine::increment($key, $offset = 1)
Returns
Boolean true on success.
Increment a number under the key and return incremented value
Bake Console
133 https://book.cakephp.org/bake/2.x/en/index.html
517
CakePHP Book, Release 5.x
Console Commands
In addition to a web framework, CakePHP also provides a console framework for creating command line tools &
applications. Console applications are ideal for handling a variety of background & maintenance tasks that leverage
your existing application configuration, models, plugins and domain logic.
CakePHP provides several console tools for interacting with CakePHP features like i18n and routing that enable you
to introspect your application and generate related files.
The CakePHP Console uses a dispatcher-type system to load commands, parse their arguments and invoke the correct
command. While the examples below use bash the CakePHP console is compatible with any *nix shell and windows.
A CakePHP application contains src/Command directory that contain its commands. It also comes with an executable
in the bin directory:
$ cd /path/to/app
$ bin/cake
Note: For Windows, the command needs to be bin\cake (note the backslash).
Running the Console with no arguments will list out available commands. You could then run the any of the listed
commands by using its name:
# run server command
bin/cake server
519
CakePHP Book, Release 5.x
Plugin commands can be invoked without a plugin prefix if the commands’s name does not overlap with an application
or framework command. In the case that two plugins provide a command with the same name, the first loaded plugin
will get the short alias. You can always use the plugin.command format to unambiguously reference a command.
Console Applications
By default CakePHP will automatically discover all the commands in your application and its plugins. You may
want to reduce the number of exposed commands, when building standalone console applications. You can use your
Application’s console() hook to limit which commands are exposed and rename commands that are exposed:
// in src/Application.php
namespace App;
use App\Command\UserCommand;
use App\Command\VersionCommand;
use Cake\Console\CommandCollection;
use Cake\Http\BaseApplication;
// Add instance
$commands->add('version', new VersionCommand());
return $commands;
}
}
In the above example, the only commands available would be help, version and user. See the Commands section
for how to add commands in your plugins.
Note: When adding multiple commands that use the same Command class, the help command will display the
shortest option.
Renaming Commands
There are cases where you will want to rename commands, to create nested commands or subcommands. While the
default auto-discovery of commands will not do this, you can register your commands to create any desired naming.
You can customize the command names by defining each command in your plugin:
return $commands;
}
When overriding the console() hook in your application, remember to call $commands->autoDiscover() to add
commands from CakePHP, your application, and plugins.
If you need to rename/remove any attached commands, you can use the Console.buildCommands event on your
application event manager to modify the available commands.
Commands
See the Command Objects chapter on how to create your first command. Then learn more about commands:
Command Objects
class Cake\Console\Command
CakePHP comes with a number of built-in commands for speeding up your development, and automating routine tasks.
You can use these same libraries to create commands for your application and plugins.
Creating a Command
Let’s create our first Command. For this example, we’ll create a simple Hello world command. In your application’s
src/Command directory create HelloCommand.php. Put the following code inside it:
<?php
namespace App\Command;
use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
return static::CODE_SUCCESS;
}
}
Command classes must implement an execute() method that does the bulk of their work. This method is called when
a command is invoked. Lets call our first command application directory, run:
bin/cake hello
Hello world.
Our execute() method isn’t very interesting let’s read some input from the command line:
<?php
namespace App\Command;
use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
{
$parser->addArgument('name', [
'help' => 'What is your name',
]);
return $parser;
}
return static::CODE_SUCCESS;
}
}
After saving this file, you should be able to run the following command:
# Outputs
Hello jillian
CakePHP will use conventions to generate the name your commands use on the command line. If you want to overwrite
the generated name implement the defaultName() method in your command:
The above would make our HelloCommand accessible by cake oh_hi instead of cake hello.
As we saw in the last example, we can use the buildOptionParser() hook method to define arguments. We can also
define options. For example, we could add a yell option to our HelloCommand:
// ...
protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
{
$parser
->addArgument('name', [
'help' => 'What is your name',
])
->addOption('yell', [
'help' => 'Shout the name',
'boolean' => true,
]);
return $parser;
}
return static::CODE_SUCCESS;
}
Commands 523
CakePHP Book, Release 5.x
Creating Output
Commands are provided a ConsoleIo instance when executed. This object allows you to interact with stdout, stderr
and create files. See the Command Input/Output section for more information.
You’ll often need access to your application’s business logic in console commands. You can load models in commands,
just as you would in a controller using $this->fetchTable() since command use the LocatorAwareTrait:
<?php
declare(strict_types=1);
namespace App\Command;
use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
return $parser;
}
$io->out(print_r($user, true));
return static::CODE_SUCCESS;
}
}
The above command, will fetch a user by username and display the information stored in the database.
When your commands hit an unrecoverable error you can use the abort() method to terminate execution:
// ...
public function execute(Arguments $args, ConsoleIo $io): int
{
$name = $args->getArgument('name');
if (strlen($name) < 5) {
// Halt execution, output to stderr, and set exit code to 1
$io->error('Name must be at least 4 characters long.');
$this->abort();
}
return static::CODE_SUCCESS;
}
You can also use abort() on the $io object to emit a message and code:
return static::CODE_SUCCESS;
}
Tip: Avoid exit codes 64 - 78, as they have specific meanings described by sysexits.h. Avoid exit codes above 127,
as these are used to indicate process exit by signal, such as SIGKILL or SIGSEGV.
You can read more about conventional exit codes in the sysexit manual page on most Unix systems (man sysexits),
or the System Error Codes help page in Windows.
You may need to call other commands from your command. You can use executeCommand to do that:
Note: When calling executeCommand() in a loop, it is recommended to pass in the parent command’s ConsoleIo
instance as the optional 3rd argument to avoid a potential “open files” limit that could occur in some environments.
Commands 525
CakePHP Book, Release 5.x
bin/cake
App:
- user
My custom description
Usage:
cake user [-h] [-q] [-v]
Testing Commands
To make testing console applications easier, CakePHP comes with a ConsoleIntegrationTestTrait trait that can
be used to test console applications and assert against their results.
To get started testing your console application, create a test case that uses the Cake\TestSuite\
ConsoleIntegrationTestTrait trait. This trait contains a method exec() that is used to execute your
command. You can pass the same string you would use in the CLI to this method.
namespace App\Command;
use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
return $parser;
}
}
To write an integration test for this command, we would create a test case
in tests/TestCase/Command/UpdateTableTest.php that uses the Cake\TestSuite\
ConsoleIntegrationTestTrait trait. This command doesn’t do much at the moment, but let’s just test that
our command’s description is displayed in stdout:
namespace App\Test\TestCase\Command;
use Cake\TestSuite\ConsoleIntegrationTestTrait;
use Cake\TestSuite\TestCase;
Our test passes! While this is very trivial example, it shows that creating an integration test case for console applications
can follow command line conventions. Let’s continue by adding more logic to our command:
namespace App\Command;
use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
use Cake\I18n\DateTime;
{
$parser
->setDescription('My cool console app')
->addArgument('table', [
'help' => 'Table to update',
'required' => true
]);
Commands 527
CakePHP Book, Release 5.x
return static::CODE_SUCCESS;
}
}
This is a more complete command that has required options and relevant logic. Modify your test case to the following
snippet of code:
namespace Cake\Test\TestCase\Command;
use Cake\Command\Command;
use Cake\I18n\DateTime;
use Cake\TestSuite\ConsoleIntegrationTestTrait;
use Cake\TestSuite\TestCase;
protected $fixtures = [
// assumes you have a UsersFixture
'app.Users',
];
$this->loadFixtures('Users');
$this->exec('update_table Users');
$this->assertExitCode(Command::CODE_SUCCESS);
$user = $this->getTableLocator()->get('Users')->get(1);
(continues on next page)
DateTime::setTestNow(null);
}
}
As you can see from the testUpdateModified method, we are testing that our command updates the table that we are
passing as the first argument. First, we assert that the command exited with the proper status code, 0. Then we check
that our command did its work, that is, updated the table we provided and set the modified column to the current time.
Remember, exec() will take the same string you type into your CLI, so you can include options and arguments in your
command string.
Consoles are often interactive. Testing interactive commands with the Cake\TestSuite\
ConsoleIntegrationTestTrait trait only requires passing the inputs you expect as the second parameter of
exec(). They should be included as an array in the order that you expect them.
Continuing with our example command, let’s add an interactive confirmation. Update the command class to the fol-
lowing:
namespace App\Command;
use Cake\Command\Command;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Console\ConsoleOptionParser;
use Cake\I18n\DateTime;
{
$parser
->setDescription('My cool console app')
->addArgument('table', [
'help' => 'Table to update',
'required' => true
]);
return $parser;
}
Commands 529
CakePHP Book, Release 5.x
return static::CODE_SUCCESS;
}
}
Now that we have an interactive command, we can add a test case that tests that we receive the proper response, and
one that tests that we receive an incorrect response. Remove the testUpdateModified method and, add the following
methods to tests/TestCase/Command/UpdateTableCommandTest.php:
$this->loadFixtures('Users');
$user = $this->getTableLocator()->get('Users')->get(1);
$this->assertSame($user->modified->timestamp, $now->timestamp);
DateTime::setTestNow(null);
}
$user = $this->getTableLocator()->get('Users')->get(1);
$this->assertSame($original, $user->timestamp);
}
In the first test case, we confirm the question, and records are updated. In the second test we don’t confirm and records
are not updated, and we can check that our error message was written to stderr.
Assertion methods
Lifecycle Callbacks
Like Controllers, Commands offer lifecycle events that allow you to observe the framework calling your application
code. Commands have:
• Command.beforeExecute Is called before a command’s execute() method is. The event is passed the
ConsoleArguments parameter as args. This event cannot be stopped or have its result replaced.
• Command.afterExecute Is called after a command’s execute() method is complete. The event contains
ConsoleArguments as args and the command result as result. This event cannot be stopped or have its result
replaced.
Command Input/Output
class Cake\Console\ConsoleIo
CakePHP provides the ConsoleIo object to commands so that they can interactively read user input and output infor-
mation to the user.
Commands 531
CakePHP Book, Release 5.x
Command Helpers
You can also get instances of helpers and call any public methods on them:
Creating Helpers
While CakePHP comes with a few command helpers you can create more in your application or plugins. As an example,
we’ll create a simple helper to generate fancy headings. First create the src/Command/Helper/HeadingHelper.php
and put the following in it:
<?php
namespace App\Command\Helper;
use Cake\Console\Helper;
We can then use this new helper in one of our shell commands by calling it:
Helpers generally implement the output() method which takes an array of parameters. However, because Console
Helpers are vanilla classes they can implement additional methods that take any form of arguments.
Built-In Helpers
Table Helper
The TableHelper assists in making well formatted ASCII art tables. Using it is pretty simple:
$data = [
['Header 1', 'Header', 'Long Header'],
['short', 'Longish thing', 'short'],
['Longer thing', 'short', 'Longest Value'],
];
$io->helper('Table')->output($data);
// Outputs
+--------------+---------------+---------------+
| Header 1 | Header | Long Header |
+--------------+---------------+---------------+
| short | Longish thing | short |
| Longer thing | short | Longest Value |
+--------------+---------------+---------------+
You can use the <text-right> formatting tag in tables to right align content:
$data = [
['Name', 'Total Price'],
['Cake Mix', '<text-right>1.50</text-right>'],
];
$io->helper('Table')->output($data);
// Outputs
+----------+-------------+
| Name 1 | Total Price |
+----------+-------------+
| Cake Mix | 1.50 |
+----------+-------------+
Progress Helper
The ProgressHelper can be used in two different ways. The simple mode lets you provide a callback that is invoked
until the progress is complete:
You can control the progress bar more by providing additional options:
• total The total number of items in the progress bar. Defaults to 100.
• width The width of the progress bar. Defaults to 80.
• callback The callback that will be called in a loop to advance the progress bar.
Commands 533
CakePHP Book, Release 5.x
$io->helper('Progress')->output([
'total' => 10,
'width' => 20,
'callback' => function ($progress) {
$progress->increment(2);
$progress->draw();
}
]);
The progress helper can also be used manually to increment and re-render the progress bar as necessary:
$progress = $io->helper('Progress');
$progress->init([
'total' => 10,
'width' => 20,
]);
$progress->increment(4);
$progress->draw();
When building interactive console applications you’ll need to get user input. CakePHP provides a way to do this:
Creating Files
Cake\Console\ConsoleIo::createFile($path, $contents)
Creating files is often important part of many console commands that help automate development and deployment.
The createFile() method gives you a simple interface for creating files with interactive confirmation:
Creating Output
// Write to stdout
$io->out('Normal message');
// Write to stderr
$io->err('Error message');
In addition to vanilla output methods, CakePHP provides wrapper methods that style output with appropriate ANSI
colors:
Color formatting will automatically be disabled if posix_isatty returns true, or if the NO_COLOR environment vari-
able is set.
ConsoleIo provides two convenience methods regarding the output level:
// Output 2 newlines
$io->out($io->nl(2));
Lastly, you can update the current line of text on the screen:
$io->out('Counting down');
$io->out('10', 0);
for ($i = 9; $i > 0; $i--) {
sleep(1);
$io->overwrite($i, 0, 2);
}
Commands 535
CakePHP Book, Release 5.x
Note: It is important to remember, that you cannot overwrite text once a new line has been output.
Output Levels
Console applications often need different levels of verbosity. For example, when running as a cron job, most output is
un-necessary. You can use output levels to flag output appropriately. The user of the shell, can then decide what level
of detail they are interested in by setting the correct flag when calling the command. There are 3 levels:
• QUIET - Only absolutely important information should be marked for quiet output.
• NORMAL - The default level, and normal usage.
• VERBOSE - Mark messages that may be too noisy for everyday use, but helpful for debugging as VERBOSE.
You can mark output as follows:
You can control the output level of commands, by using the --quiet and --verbose options. These options are added
by default, and allow you to consistently control output levels inside your CakePHP comands.
The --quiet and --verbose options also control how logging data is output to stdout/stderr. Normally info and
higher log messages are output to stdout/stderr. When --verbose is used, debug logs will be output to stdout. When
--quiet is used, only warning and higher log messages will be output to stderr.
Styling Output
Styling output is done by including tags - just like HTML - in your output. These tags will be replaced with the correct
ansi code sequence, or stripped if you are on a console that doesn’t support ansi codes. There are several built-in styles,
and you can create more. The built-in ones are
• success Success messages. Green text.
• error Error messages. Red text.
• warning Warning messages. Yellow text.
• info Informational messages. Cyan text.
• comment Additional text. Blue text.
• question Text that is a question, added automatically by shell.
You can create additional styles using $io->setStyle(). To declare a new output style you could do:
This would then allow you to use a <flashy> tag in your shell output, and if ansi colors are enabled, the following would
be rendered as blinking magenta text $this->out('<flashy>Whoooa</flashy> Something went wrong');.
When defining styles you can use the following colors for the text and background attributes:
• black
• blue
• cyan
• green
• magenta
• red
• white
• yellow
You can also use the following options as boolean switches, setting them to a truthy value enables them.
• blink
• bold
• reverse
• underline
Adding a style makes it available on all instances of ConsoleOutput as well, so you don’t have to redeclare styles for
both stdout and stderr objects.
Although coloring is pretty, there may be times when you want to turn it off, or force it on:
$io->outputAs(ConsoleOutput::RAW);
The above will put the output object into raw output mode. In raw output mode, no styling is done at all. There are
three modes you can use.
• ConsoleOutput::COLOR - Output with color escape codes in place.
• ConsoleOutput::PLAIN - Plain text output, known style tags will be stripped from the output.
• ConsoleOutput::RAW - Raw output, no styling or formatting will be done. This is a good mode to use if you
are outputting XML or, want to debug why your styling isn’t working.
By default on *nix systems ConsoleOutput objects default to color output. On Windows systems, plain output is the
default unless the ANSICON environment variable is present.
Commands 537
CakePHP Book, Release 5.x
Option Parsers
class Cake\Console\ConsoleOptionParser
Console applications typically take options and arguments as the primary way to get information from the terminal into
your commands.
Defining an OptionParser
Commands and Shells provide a buildOptionParser($parser) hook method that you can use to define the options
and arguments for your commands:
Shell classes use the getOptionParser() hook method to define their option parser:
Using Arguments
Positional arguments are frequently used in command line tools, and ConsoleOptionParser allows you to
define positional arguments as well as make them required. You can add arguments one at a time with
$parser->addArgument(); or multiple at once with $parser->addArguments();:
Arguments that have been marked as required will throw an exception when parsing the command if they have been
omitted. So you don’t have to handle that in your shell.
Cake\Console\ConsoleOptionParser::addArguments(array $args)
If you have an array with multiple arguments you can use $parser->addArguments() to add multiple arguments at
once.
$parser->addArguments([
'node' => ['help' => 'The node to create', 'required' => true],
'parent' => ['help' => 'The parent node', 'required' => true],
]);
As with all the builder methods on ConsoleOptionParser, addArguments can be used as part of a fluent method chain.
Validating Arguments
When creating positional arguments, you can use the required flag, to indicate that an argument must be present when
a shell is called. Additionally you can use choices to force an argument to be from a list of valid choices:
$parser->addArgument('type', [
'help' => 'The type of node to interact with.',
'required' => true,
'choices' => ['aro', 'aco'],
]);
The above will create an argument that is required and has validation on the input. If the argument is either missing,
or has an incorrect value an exception will be raised and the shell will be stopped.
Using Options
Options or flags are used in command line tools to provide unordered key/value arguments for your commands. Options
can define both verbose and short aliases. They can accept a value (e.g --connection=default) or be boolean options
(e.g --verbose). Options are defined with the addOption() method:
$parser->addOption('connection', [
'short' => 'c',
'help' => 'connection',
'default' => 'default',
]);
The above would allow you to use either cake myshell --connection=other, cake myshell --connection
other, or cake myshell -c other when invoking the shell.
Boolean switches do not accept or consume values, and their presence just enables them in the parsed parameters:
Commands 539
CakePHP Book, Release 5.x
This option when used like cake mycommand --no-commit something would have a value of true, and ‘some-
thing’ would be a treated as a positional argument.
When creating options you can use the following options to define the behavior of the option:
• short - The single letter variant for this option, leave undefined for none.
• help - Help text for this option. Used when generating help for the option.
• default - The default value for this option. If not defined the default will be true.
• boolean - The option uses no value, it’s just a boolean switch. Defaults to false.
• multiple - The option can be provided multiple times. The parsed option will be an array of values when this
option is enabled.
• choices - An array of valid choices for this option. If left empty all values are valid. An exception will be raised
when parse() encounters an invalid value.
Cake\Console\ConsoleOptionParser::addOptions(array $options)
If you have an array with multiple options you can use $parser->addOptions() to add multiple options at once.
$parser->addOptions([
'node' => ['short' => 'n', 'help' => 'The node to create'],
'parent' => ['short' => 'p', 'help' => 'The parent node'],
]);
As with all the builder methods on ConsoleOptionParser, addOptions can be used as part of a fluent method chain.
Validating Options
Options can be provided with a set of choices much like positional arguments can be. When an option has defined
choices, those are the only valid choices for an option. All other values will raise an InvalidArgumentException:
$parser->addOption('accept', [
'help' => 'What version to accept.',
'choices' => ['working', 'theirs', 'mine'],
]);
Options can be defined as boolean options, which are useful when you need to create some flag options. Like options
with defaults, boolean options always include themselves into the parsed parameters. When the flags are present they
are set to true, when they are absent they are set to false:
$parser->addOption('verbose', [
'help' => 'Enable verbose output.',
'boolean' => true
]);
The following option would always have a value in the parsed parameter. When not included its default value would
be false, and when defined it will be true.
Cake\Console\ConsoleOptionParser::buildFromArray($spec)
Option parsers can also be defined as arrays. Within the array, you can define keys for arguments, options,
description and epilog. The values for arguments, and options, should follow the format that Cake\Console\
ConsoleOptionParser::addArguments() and Cake\Console\ConsoleOptionParser::addOptions() use.
You can also use buildFromArray on its own, to build an option parser:
Cake\Console\ConsoleOptionParser::merge($spec)
When building a group command, you maybe want to combine several parsers for this:
$parser->merge($anotherParser);
Note that the order of arguments for each parser must be the same, and that options must also be compatible for it work.
So do not use keys for different things.
By defining your options and arguments with the option parser CakePHP can automatically generate rudimentary help
information and add a --help and -h to each of your commands. Using one of these options will allow you to see the
generated help content:
Would both generate the help for bake. You can also get help for nested commands:
The above would get you the help specific to bake’s model command.
Commands 541
CakePHP Book, Release 5.x
When building automated tools or development tools that need to interact with CakePHP shell commands, it’s nice to
have help available in a machine parse-able format. By providing the xml option when requesting help you can have
help content returned as XML:
The above would return an XML document with the generated help, options, and arguments for the selected shell. A
sample XML document would look like:
<?xml version="1.0"?>
<shell>
<command>bake fixture</command>
<description>Generate fixtures for use with the test suite. You can use
`bake fixture all` to bake all fixtures.</description>
<epilog>
Omitting all arguments and options will enter into an interactive
mode.
</epilog>
<options>
<option name="--help" short="-h" boolean="1">
<default/>
<choices/>
</option>
<option name="--verbose" short="-v" boolean="1">
<default/>
<choices/>
</option>
<option name="--quiet" short="-q" boolean="1">
<default/>
<choices/>
</option>
<option name="--count" short="-n" boolean="">
<default>10</default>
<choices/>
</option>
<option name="--connection" short="-c" boolean="">
<default>default</default>
<choices/>
</option>
<option name="--plugin" short="-p" boolean="">
<default/>
<choices/>
</option>
<option name="--records" short="-r" boolean="1">
<default/>
<choices/>
</option>
</options>
<arguments>
<argument name="name" help="Name of the fixture to bake.
(continues on next page)
You can further enrich the generated help content by adding a description, and epilog.
Cake\Console\ConsoleOptionParser::setDescription($text)
The description displays above the argument and option information. By passing in either an array or a string, you can
set the value of the description:
Cake\Console\ConsoleOptionParser::setEpilog($text)
Gets or sets the epilog for the option parser. The epilog is displayed after the argument and option information. By
passing in either an array or a string, you can set the value of the epilog:
A common thing to do with a shell is making it run as a cronjob to clean up the database once in a while or send
newsletters. This is trivial to setup, for example:
Commands 543
CakePHP Book, Release 5.x
On some shared hostings cd /full/path/to/root && bin/cake mycommand myparam might not work. Instead
you can use php /full/path/to/root/bin/cake.php mycommand myparam.
Note: register_argc_argv has to be turned on by including register_argc_argv = 1 in your php.ini. If you cannot
change register_argc_argv globally, you can tell the cron job to use your own configuration by specifying it with -d
register_argc_argv=1 parameter. Example: php -d register_argc_argv=1 /full/path/to/root/bin/
cake.php myshell myparam
Cache Tool
To help you better manage cached data from a CLI environment, a console command is available for clearing cached
data your application has:
Completion Tool
Working with the console gives the developer a lot of possibilities but having to completely know and write those
commands can be tedious. Especially when developing new shells where the commands differ per minute iteration.
The Completion Shells aids in this matter by providing an API to write completion scripts for shells like bash, zsh, fish
etc.
Sub Commands
The Completion Shell consists of a number of sub commands to assist the developer creating its completion script.
Each for a different step in the autocompletion process.
Commands
For the first step commands outputs the available Shell Commands, including plugin name when applicable. (All
returned possibilities, for this and the other sub commands, are separated by a space.) For example:
Returns:
acl api bake command_list completion console i18n schema server test testsuite upgrade
Your completion script can select the relevant commands from that list to continue with. (For this and the following
sub commands.)
subCommands
Once the preferred command has been chosen subCommands comes in as the second step and outputs the possible sub
command for the given shell command. For example:
Returns:
options
As the third and final options outputs options for the given (sub) command as set in getOptionParser. (Including the
default options inherited from Shell.) For example:
Returns:
You can also pass an additional argument being the shell sub-command : it will output the specific options of this
sub-command.
First, make sure the bash-completion library is installed. If not, you do it with the following command:
apt-get install bash-completion
Create a file named cake in /etc/bash_completion.d/ and put the Bash Completion file content inside it.
Save the file, then restart your console.
Note: If you are using MacOS X, you can install the bash-completion library using homebrew with the command
brew install bash-completion. The target directory for the cake file will be /usr/local/etc/bash_completion.d/.
This is the code you need to put inside the cake file in the correct location in order to get autocompletion when using
the CakePHP console:
#
# Bash completion file for CakePHP console
#
_cake()
{
local cur prev opts cake
COMPREPLY=()
cake="${COMP_WORDS[0]}"
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
if [[ "$cur" == -* ]] ; then
if [[ ${COMP_CWORD} = 1 ]] ; then
opts=$(${cake} Completion options)
elif [[ ${COMP_CWORD} = 2 ]] ; then
opts=$(${cake} Completion options "${COMP_WORDS[1]}")
else
opts=$(${cake} Completion options "${COMP_WORDS[1]}" "${COMP_WORDS[2]}")
fi
if [[ ${COMP_CWORD} = 1 ]] ; then
opts=$(${cake} Completion commands)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
if [[ ${COMP_CWORD} = 2 ]] ; then
opts=$(${cake} Completion subcommands $prev)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
(continues on next page)
Using autocompletion
Once enabled, the autocompletion can be used the same way than for other built-in commands, using the TAB key.
Three type of autocompletion are provided. The following output are from a fresh CakePHP install.
Commands
$ bin/cake <tab>
bake i18n schema_cache routes
console migrations plugin server
Subcommands
Options
I18N Tool
The i18n features of CakePHP use po files134 as their translation source. PO files integrate with commonly used
translation tools like Poedit135 .
The i18n commands provides a quick way to generate po template files. These templates files can then be given to
translators so they can translate the strings in your application. Once you have translations done, pot files can be
merged with existing translations to help update your translations.
POT files can be generated for an existing application using the extract command. This command will scan your
entire application for __() style function calls, and extract the message string. Each unique string in your application
will be combined into a single POT file:
The above will run the extraction command. The result of this command will be the file resources/locales/default.pot.
You use the pot file as a template for creating po files. If you are manually creating po files from the pot file, be sure to
correctly set the Plural-Forms header line.
This will generate the required POT files used in the plugins.
Sometimes, you might need to extract strings from more than one directory of your application. For instance, if you
are defining some strings in the config directory of your application, you probably want to extract strings from this
directory as well as from the src directory. You can do it by using the --paths option. It takes a comma-separated
list of absolute paths to extract:
134 https://en.wikipedia.org/wiki/GNU_gettext
135 https://www.poedit.net/
Excluding Folders
You can pass a comma separated list of folders that you wish to be excluded. Any path containing a path segment with
the provided values will be ignored:
By adding --overwrite, the shell script will no longer warn you if a POT file already exists and will overwrite by
default:
By default, the extract shell script will ask you if you like to extract the messages used in the CakePHP core libraries.
Set --extract-core to yes or no to set the default behavior:
// or
Plugin Tool
The plugin tool allows you to load and unload plugins via the command prompt. If you need help, run:
Loading Plugins
Via the Load task you are able to load plugins in your config/bootstrap.php. You can do this by running:
Unloading Plugins
Plugin Assets
CakePHP by default serves plugins assets using the AssetMiddleware middleware. While this is a good convenience,
it is recommended to symlink / copy the plugin assets under app’s webroot so that they can be directly served by the
web server without invoking PHP. You can do this by running:
Running the above command will symlink all plugins assets under app’s webroot. On Windows, which doesn’t support
symlinks, the assets will be copied in respective folders instead of being symlinked.
You can symlink assets of one particular plugin by specifying its name:
Routes Tool
The routes tool provides a simple to use CLI interface for testing and debugging routes. You can use it to test how
routes are parsed, and what URLs routing parameters will generate.
bin/cake routes
You can quickly see how a URL will be parsed using the check method:
If your route contains any query string parameters remember to surround the URL in quotes:
You can see the URL a routing array will generate using the generate method:
Server Tool
The ServerCommand lets you stand up a simple webserver using the built in PHP webserver. While this server is not
intended for production use it can be handy in development when you want to quickly try an idea out and don’t want to
spend time configuring Apache or Nginx. You can start the server command with:
bin/cake server
You should see the server boot up and attach to port 8765. You can visit the CLI server by visiting http://
localhost:8765 in your web-browser. You can close the server by pressing CTRL-C in your terminal.
Note: Try bin/cake server -H 0.0.0.0 if the server is unreachable from other hosts.
You can customize the port and document root using options:
CakePHP offers REPL(Read Eval Print Loop) plugin136 to let you explore some CakePHP and your application in an
interactive console.
Note: The plugin was shipped with the CakePHP app skeleton before 4.3.
bin/cake console
This will bootstrap your application and start an interactive console. At this point you can interact with your application
code and execute queries using your application’s models:
bin/cake console
136 https://github.com/cakephp/repl
Since your application has been bootstrapped you can also test routing using the REPL:
>>> Cake\Routing\Router::parse('/articles/view/1');
// [
// 'controller' => 'Articles',
// 'action' => 'view',
// 'pass' => [
// 0 => '1'
// ],
// 'plugin' => NULL
// ]
In command-line interface (CLI), specifically your console commands, env('HTTP_HOST') and other webbrowser
specific environment variables are not set.
If you generate reports or send emails that make use of Router::url() those will contain the default host http://
localhost/ and thus resulting in invalid URLs. In this case you need to specify the domain manually. You can do
that using the Configure value App.fullBaseUrl from your bootstrap or config, for example.
For sending emails, you should provide Email class with the host you want to send the email with:
use Cake\Mailer\Email;
This asserts that the generated message IDs are valid and fit to the domain the emails are sent from.
Debugging
Debugging is an inevitable and necessary part of any development cycle. While CakePHP doesn’t offer any tools that
directly connect with any IDE or editor, CakePHP does provide several tools to assist in debugging and exposing what
is running under the hood of your application.
Basic Debugging
The debug() function is a globally available function that works similarly to the PHP function print_r(). The
debug() function allows you to show the contents of a variable in a number of different ways. First, if you’d like data
to be shown in an HTML-friendly way, set the second parameter to true. The function also prints out the line and file
it is originating from by default.
Output from this function is only shown if the core $debug variable has been set to true.
Also see dd(), pr() and pj().
stackTrace()
The stackTrace() function is available globally, and allows you to output a stack trace wherever the function is called.
breakpoint()
If you have Psysh137 installed you can use this function in CLI environments to open an interactive console with the
current local scope:
// Some code
eval(breakpoint());
137 https://psysh.org/
553
CakePHP Book, Release 5.x
Will open an interactive console that can be used to check local variables and execute other code. You can exit the
interactive debugger and resume the original execution by running quit or q in the interactive session.
class Cake\Error\Debugger
To use the debugger, first ensure that Configure::read('debug') is set to true. You can use
filter_var(env('DEBUG', true), FILTER_VALIDATE_BOOLEAN), in config/app.php file to ensure that debug
is a boolean.
The following configuration options can be set in config/app.php to change how Debugger behaves:
• Debugger.editor Choose the which editor URL format you want to use. By default atom, emacs, macvim,
phpstorm, sublime, textmate, and vscode are available. You can add additional editor link formats using
Debugger::addEditor() during your application bootstrap.
• Debugger.outputMask A mapping of key to replacement values that Debugger should replace in dumped
data and logs generated by Debugger.
Outputting Values
Dump prints out the contents of a variable. It will print out all properties and methods (if any) of the supplied variable:
$foo = [1,2,3];
Debugger::dump($foo);
// Outputs
array(
1,
2,
3
)
// Simple object
$car = new Car();
Debugger::dump($car);
// Outputs
object(Car) {
color => 'red'
make => 'Toyota'
model => 'Camry'
mileage => (int)15000
}
Masking Data
When dumping data with Debugger or rendering error pages, you may want to hide sensitive keys like passwords or
API keys. In your config/bootstrap.php you can mask specific keys:
Debugger::setOutputMask([
'password' => 'xxxxx',
'awsKey' => 'yyyyy',
]);
As of 4.1.0 you can use the Debugger.outputMask configuration value to set output masks.
Creates a detailed stack trace log at the time of invocation. The log() method prints out data similar to that done by
Debugger::dump(), but to the debug.log instead of the output buffer. Note your tmp directory (and its contents) must
be writable by the web server for log() to work correctly.
static Cake\Error\Debugger::trace($options)
Returns the current stack trace. Each line of the trace includes the calling method, including which file and line the call
originated from:
// In PostsController::index()
pr(Debugger::trace());
// Outputs
PostsController::index() - APP/Controller/DownloadsController.php, line 48
Dispatcher::_invoke() - CORE/src/Routing/Dispatcher.php, line 265
Dispatcher::dispatch() - CORE/src/Routing/Dispatcher.php, line 237
[main] - APP/webroot/index.php, line 84
Above is the stack trace generated by calling Debugger::trace() in a controller action. Reading the stack trace
bottom to top shows the order of currently running functions (stack frames).
Grab an excerpt from the file at $path (which is an absolute filepath), highlights line number $line with $context number
of lines around it.
Although this method is used internally, it can be handy if you’re creating your own error messages or log entries for
custom situations.
static Cake\Error\Debugger::getType($var)
Get the type of a variable. Objects will return their class name
Editor Integration
Exception and error pages can contain URLs that directly open in your editor or IDE. CakePHP ships with URL formats
for several popular editors, and you can add additional editor formats if required during application bootstrap:
Logging messages is another good way to debug applications, and you can use Cake\Log\Log to do logging in your
application. All objects that use LogTrait have an instance method log() which can be used to log messages:
The above would write Got here into the debug log. You can use log entries to help debug methods that involve
redirects or complicated loops. You can also use Cake\Log\Log::write() to write log messages. This method can
be called statically anywhere in your application one Log has been loaded:
Debug Kit
DebugKit is a plugin that provides a number of good debugging tools. It primarily provides a toolbar in the rendered
HTML, that provides a plethora of information about your application and the current request. See the DebugKit
Documentation138 for how to install and use DebugKit.
138 https://book.cakephp.org/debugkit/
Deployment
Once your app is ready to be deployed there are a few things you should do.
Moving files
You can clone your repository onto your production server and then checkout the commit/tag you want to run. Then,
run composer install. While this requires some knowledge about git and an existing install of git and composer
this process will take care about library dependencies and file and folder permissions.
Be aware that when deploying via FTP you will have to fix file and folder permissions.
You can also use this deployment technique to setup a staging or demo-server (pre-production) and keep it in sync with
your local environment.
Adjusting Configuration
You’ll want to make a few adjustments to your application’s configuration for a production environment. The value of
debug is extremely important. Turning debug = false disables a number of development features that should never
be exposed to the Internet at large. Disabling debug changes the following features:
• Debug messages, created with pr(), debug() and dd() are disabled.
• Core CakePHP caches duration are defaulted to 365 days, instead of 10 seconds as in development.
• Error views are less informative, and generic error pages are displayed instead of detailed error messages with
stack traces.
• PHP Warnings and Errors are not displayed.
In addition to the above, many plugins and application extensions use debug to modify their behavior.
559
CakePHP Book, Release 5.x
You can check against an environment variable to set the debug level dynamically between environments. This will
avoid deploying an application with debug true and also save yourself from having to change the debug level each
time before deploying to a production environment.
For example, you can set an environment variable in your Apache configuration:
SetEnv CAKEPHP_DEBUG 1
And then you can set the debug level dynamically in app_local.php:
$debug = (bool)getenv('CAKEPHP_DEBUG');
return [
'debug' => $debug,
.....
];
It is recommended that you put configuration that is shared across all of your application’s environments in con-
fig/app.php. For configuration that varies between environments either use config/app_local.php or environment
variables.
If you’re throwing your application out into the wild, it’s a good idea to make sure it doesn’t have any obvious leaks:
• Ensure you are using the Cross Site Request Forgery (CSRF) Middleware component or middleware.
• You may want to enable the FormProtection component. It can help prevent several types of form tampering and
reduce the possibility of mass-assignment issues.
• Ensure your models have the correct Validation rules enabled.
• Check that only your webroot directory is publicly visible, and that your secrets (such as your app salt, and any
security keys) are private and unique as well.
Setting the document root correctly on your application is an important step to keeping your code secure and your
application safer. CakePHP applications should have the document root set to the application’s webroot. This makes
the application and configuration files inaccessible through a URL. Setting the document root is different for different
webservers. See the URL Rewriting documentation for webserver specific information.
In all cases you will want to set the virtual host/domain’s document to be webroot/. This removes the possibility of
files outside of the webroot directory being executed.
Class loading can take a big share of your application’s processing time. In order to avoid this problem, it is recom-
mended that you run this command in your production server once the application is deployed:
Since handling static assets, such as images, JavaScript and CSS files of plugins, through the Dispatcher is incred-
ibly inefficient, it is strongly recommended to symlink them for production. This can be done by using the plugin
command:
The above command will symlink the webroot directory of all loaded plugins to appropriate path in the app’s webroot
directory.
If your filesystem doesn’t allow creating symlinks the directories will be copied instead of being symlinked. You can
also explicitly copy the directories using:
CakePHP uses assert() internally to provide runtime type checking and provide better error messages during devel-
opment. You can have PHP skip these assertions by updating your php.ini to include:
Skipping code generation for assert() will yield faster runtime performance, and is recommended for applications
that have good test coverage or that are using a static analyzer.
Deploying an update
On each deploy you’ll likely have a few tasks to co-ordinate on your web server. Some typical ones are:
1. Install dependencies with composer install. Avoid using composer update when doing deploys as you
could get unexpected versions of packages.
2. Run database migrations with either the Migrations plugin or another tool.
3. Clear model schema cache with bin/cake schema_cache clear. The Schema Cache Tool has more infor-
mation on this command.
Mailer
Mailer is a convenience class for sending email. With this class you can send email from any place inside of your
application.
Basic Usage
use Cake\Mailer\Mailer;
After you’ve loaded Mailer, you can send an email with the following:
Since Mailer’s setter methods return the instance of the class, you are able to set its properties with method chaining.
Mailer has several methods for defining recipients - setTo(), setCc(), setBcc(), addTo(), addCc() and
addBcc(). The main difference being that the first three will overwrite what was already set and the latter will just add
more recipients to their respective field:
563
CakePHP Book, Release 5.x
When sending email on behalf of other people, it’s often a good idea to define the original sender using the Sender
header. You can do so using setSender():
Note: It’s also a good idea to set the envelope sender when sending mail on another person’s behalf. This prevents
them from getting any messages about deliverability.
Configuration
Mailer profiles and email transport settings are defined in your application’s configuration files. The Email and
EmailTransport keys define mailer profiles and email transport configurations respectively. During application
bootstrap configuration settings are passed from Configure into the Mailer and TransportFactory classes us-
ing setConfig(). By defining profiles and transports, you can keep your application code free of configuration data,
and avoid duplication that makes maintenance and deployment more difficult.
To load a predefined configuration, you can use the setProfile() method or pass it to the constructor of Mailer:
// Or in constructor
$mailer = new Mailer('default');
Instead of passing a string which matches a preset configuration name, you can also just load an array of options:
// Or in constructor
$mailer = new Mailer(['from' => '[email protected]', 'transport' => 'my_custom']);
Configuration Profiles
Defining delivery profiles allows you to consolidate common email settings into re-usable profiles. Your application
can have as many profiles as necessary. The following configuration keys are used:
• 'from': Mailer or array of sender. See Mailer::setFrom().
• 'sender': Mailer or array of real sender. See Mailer::setSender().
• 'to': Mailer or array of destination. See Mailer::setTo().
Note: The values of above keys using Mailer or array, like from, to, cc, etc will be passed as first parameter of corre-
sponding methods. The equivalent for: $mailer->setFrom('[email protected]', 'My Site') would be defined
as 'from' => ['[email protected]' => 'My Site'] in your config
Configuration 565
CakePHP Book, Release 5.x
Setting Headers
In Mailer you are free to set whatever headers you want. Do not forget to put the X- prefix for your custom headers.
See Mailer::setHeaders() and Mailer::addHeaders()
Emails are often much more than just a simple text message. In order to facilitate that, CakePHP provides a way to
send emails using CakePHP’s view layer.
The templates for emails reside in a special folder templates/email of your application. Mailer views can also use
layouts and elements just like normal views:
$mailer->deliver();
The above would use templates/email/html/welcome.php for the view and templates/layout/email/html/fancy.php
for the layout. You can send multipart templated email messages as well:
$mailer->deliver();
Or you can use the view builder methods ViewBuilder::setVar() and ViewBuilder::setVars().
In your email templates you can use these with:
You can use helpers in emails as well, much like you can in normal template files. By default only the HtmlHelper is
loaded. You can load additional helpers using the ViewBuilder::addHelpers() method:
When adding helpers be sure to include ‘Html’ or it will be removed from the helpers loaded in your email template.
Note: In versions prior to 4.3.0, you will need to use setHelpers() instead.
If you want to send email using templates in a plugin you can use the familiar plugin syntax to do so:
The above would use template and layout from the Blog plugin as an example.
In some cases, you might need to override the default template provided by plugins. You can do this using themes:
$mailer->viewBuilder()
->setTemplate('Blog.new_comment')
->setLayout('Blog.auto_message')
->setTheme('TestTheme');
This allows you to override the new_comment template in your theme without modify-
ing the Blog plugin. The template file needs to be created in the following path: tem-
plates/plugin/TestTheme/plugin/Blog/email/text/new_comment.php.
Sending Attachments
Cake\Mailer\Mailer::setAttachments($attachments)
You can attach files to email messages as well. There are a few different formats depending on what kind of files you
have, and how you want the filenames to appear in the recipient’s mail client:
1. Array: $mailer->setAttachments(['/full/file/path/file.png']) will attach this file with the name
file.png..
2. Array with key: $mailer->setAttachments(['photo.png' => '/full/some_hash.png']) will attach
some_hash.png with the name photo.png. The recipient will see photo.png, not some_hash.png.
3. Nested arrays:
$mailer->setAttachments([
'photo.png' => [
'file' => '/full/some_hash.png',
(continues on next page)
The above will attach the file with different mimetype and with custom Content ID (when set the content ID the
attachment is transformed to inline). The mimetype and contentId are optional in this form.
3.1. When you are using the contentId, you can use the file in the HTML body like <img
src="cid:my-content-id">.
3.2. You can use the contentDisposition option to disable the Content-Disposition header for an attach-
ment. This is useful when sending ical invites to clients using outlook.
3.3 Instead of the file option you can provide the file contents as a string using the data option. This allows
you to attach files without needing file paths to them.
Cake\Mailer\Mailer::setEmailPattern($pattern)
If you are having validation issues when sending to non-compliant addresses, you can relax the pattern used to validate
email addresses. This is sometimes necessary when dealing with some ISPs:
When sending emails within a CLI script (Shells, Tasks, . . . ) you should manually set the domain name for Mailer to
use. It will serve as the host name for the message id (since there is no host name in a CLI environment):
$mailer->setDomain('www.example.org');
// Results in message ids like ``<[email protected]>`` (valid)
// Instead of `<UUID@>`` (invalid)
Until now we have seen how to directly use the the Mailer class to create and send one emails. But main feature of
mailer is to allow creating reusable emails throughout your application. They can also be used to contain multiple email
configurations in one location. This helps keep your code DRYer and keeps email configuration noise out of other areas
in your application.
In this example we will be creating a Mailer that contains user-related emails. To create our UserMailer, create the
file src/Mailer/UserMailer.php. The contents of the file should look like the following:
namespace App\Mailer;
use Cake\Mailer\Mailer;
In our example we have created two methods, one for sending a welcome email, and another for sending a password
reset email. Each of these methods expect a user Entity and utilizes its properties for configuring each email.
We are now able to use our UserMailer to send out our user-related emails from anywhere in our application. For
example, if we wanted to send our welcome email we could do the following:
namespace App\Controller;
use Cake\Mailer\MailerAwareTrait;
If we wanted to completely separate sending a user their welcome email from our application’s code, we can have our
UserMailer subscribe to the Model.afterSave event. By subscribing to an event, we can keep our application’s
user-related classes completely free of email-related logic and instructions. For example, we could add the following
to our UserMailer:
{
if ($entity->isNew()) {
$this->send('welcome', [$entity]);
}
}
You can now register the mailer as an event listener and the onRegistration() method will be invoked every time
the Model.afterSave event is fired:
Note: For information on how to register event listener objects, please refer to the Registering Listeners documentation.
Configuring Transports
Email messages are delivered by transports. Different transports allow you to send messages via PHP’s mail() func-
tion, SMTP servers, or not at all which is useful for debugging. Configuring transports allows you to keep configuration
data out of your application code and makes deployment simpler as you can simply change the configuration data. An
example transport configuration looks like:
// In config/app.php
'EmailTransport' => [
// Sample Mail configuration
'default' => [
'className' => 'Mail',
],
// Sample SMTP configuration
'gmail' => [
'host' => 'smtp.gmail.com',
'port' => 587,
'username' => '[email protected]',
'password' => 'secret',
'className' => 'Smtp',
'tls' => true,
],
],
use Cake\Mailer\TransportFactory;
You can configure SSL SMTP servers, like Gmail. To do so, put the ssl:// prefix in the host and configure the port
value accordingly. You can also enable TLS SMTP using the tls option:
use Cake\Mailer\TransportFactory;
TransportFactory::setConfig('gmail', [
'host' => 'smtp.gmail.com',
'port' => 587,
'username' => '[email protected]',
'password' => 'secret',
'className' => 'Smtp',
'tls' => true
]);
The above configuration would enable TLS communication for email messages.
To configure your mailer to use a specific transport you can use Cake\Mailer\Mailer::setTransport() method
or have the transport in your configuration:
Warning: You will need to have access for less secure apps enabled in your Google account for this to work:
Allowing less secure apps to access your account139 .
Note: To use SSL + SMTP, you will need to have the SSL configured in your PHP install.
Configuration options can also be provided as a DSN string. This is useful when working with environment variables
or PaaS providers:
139 https://support.google.com/accounts/answer/6010255
140 https://support.google.com/a/answer/176600?hl=en
TransportFactory::setConfig('default', [
'url' => 'smtp://[email protected]:[email protected]:587?tls=true',
]);
When using a DSN string you can define any additional parameters/options as query string arguments.
static Cake\Mailer\Mailer::drop($key)
Once configured, transports cannot be modified. In order to modify a transport you must first drop it and then recon-
figure it.
You are able to create your custom transports for situations such as send email using services like SendGrid, Mail-
Gun or Postmark. To create your transport, first create the file src/Mailer/Transport/ExampleTransport.php (where
Example is the name of your transport). To start, your file should look like:
namespace App\Mailer\Transport;
use Cake\Mailer\AbstractTransport;
use Cake\Mailer\Message;
You must implement the method send(Message $message) with your custom logic.
The Mailer is a higher level abstraction class which acts as a bridge between the Cake\Mailer\Message, Cake\
Mailer\Renderer and Cake\Mailer\AbstractTransport classes to configure emails with a fluent interface.
If you want you can use these classes directly with the Mailer too.
For example:
You can even skip using the Renderer and set the message body directly using Message::setBodyText() and
Message::setBodyHtml() methods.
Testing Mailers
To test mailers, add Cake\TestSuite\EmailTrait to your test case. The MailerTrait uses PHPUnit hooks to
replace your application’s email transports with a proxy that intercepts email messages and allows you to do assertions
on the mail that would be delivered.
Add the trait to your test case to start testing emails, and load routes if your emails need to generate URLs:
namespace App\Test\TestCase\Mailer;
use App\Mailer\WelcomeMailer;
use App\Model\Entity\User;
use Cake\TestSuite\EmailTrait;
use Cake\TestSuite\TestCase;
Let’s assume we have a mailer that delivers welcome emails when a new user registers. We want to check that the
subject and body contain the user’s name:
$this->assertMailSentTo($user->email);
$this->assertMailContainsText('Hi ' . $user->name);
$this->assertMailContainsText('Welcome to CakePHP!');
}
Assertion methods
// Asserts an email contains the expected value within an Message getter (for example,
˓→"subject")
$this->assertMailSentWith($expected, $parameter);
// Asserts an email at a specific index contains the expected value within an Message␣
˓→getter (for example, "cc")
CakePHP applications come with error and exception handling setup for you. PHP errors are trapped and displayed or
logged. Uncaught exceptions are rendered into error pages automatically.
Configuration
Error configuration is done in your application’s config/app.php file. By default CakePHP uses Cake\Error\
ErrorTrap and Cake\Error\ExceptionTrap to handle both PHP errors and exceptions respectively. The error
configuration allows you to customize error handling for your application. The following options are supported:
• errorLevel - int - The level of errors you are interested in capturing. Use the built-in PHP error constants,
and bitmasks to select the level of error you are interested in. See Deprecation Warnings to disable deprecation
warnings.
• trace - bool - Include stack traces for errors in log files. Stack traces will be included in the log after each error.
This is helpful for finding where/when errors are being raised.
• exceptionRenderer - string - The class responsible for rendering uncaught exceptions. If you choose a custom
class you should place the file for that class in src/Error. This class needs to implement a render() method.
• log - bool - When true, exceptions + their stack traces will be logged to Cake\Log\Log.
• skipLog - array - An array of exception classnames that should not be logged. This is useful to remove Not-
FoundExceptions or other common, but uninteresting log messages.
• extraFatalErrorMemory - int - Set to the number of megabytes to increase the memory limit by when a fatal
error is encountered. This allows breathing room to complete logging or error handling.
• logger (prior to 4.4.0 use errorLogger) - Cake\Error\ErrorLoggerInterface - The class responsible for
logging errors and unhandled exceptions. Defaults to Cake\Error\ErrorLogger.
• errorRenderer - Cake\Error\ErrorRendererInterface - The class responsible for rendering errors. De-
fault is chosen based on PHP SAPI.
577
CakePHP Book, Release 5.x
• ignoredDeprecationPaths - array - A list of glob compatible paths that deprecation errors should be ignored
in. Added in 4.2.0
By default, PHP errors are displayed when debug is true, and logged when debug is false. The fatal error handler
will be called independent of debug level or errorLevel configuration, but the result will be different based on debug
level. The default behavior for fatal errors is show a page to internal server error (debug disabled) or a page with the
message, file and line (debug enabled).
Note: If you use a custom error handler, the supported options will depend on your handler.
Deprecation Warnings
CakePHP uses deprecation warnings to indicate when features have been deprecated. We also recommend this
system for use in your plugins and application code when useful. You can trigger deprecation warnings with
deprecationWarning():
deprecationWarning('5.0', 'The example() method is deprecated. Use getExample() instead.
˓→');
When upgrading CakePHP or plugins you may encounter new deprecation warnings. You can temporarily disable
deprecation warnings in one of a few ways:
1. Using the Error.errorLevel setting to E_ALL ^ E_USER_DEPRECATED to ignore all deprecation warnings.
2. Using the Error.ignoredDeprecationPaths configuration option to ignore deprecations with glob compat-
ible expressions. For example:
'Error' => [
'ignoredDeprecationPaths' => [
'vendors/company/contacts/*',
'src/Models/*',
],
],
Would ignore all deprecations from your Models directory and the Contacts plugin in your application.
Exception handling in CakePHP offers several ways to tailor how exceptions are handled. Each approach gives you
different amounts of control over the exception handling process.
1. Listen to events This allows you to be notified through CakePHP events when errors and exceptions have been
handled.
2. Custom templates This allows you to change the rendered view templates as you would any other template in
your application.
3. Custom Controller This allows you to control how exception pages are rendered.
4. Custom ExceptionRenderer This allows you to control how exception pages and logging are performed.
5. Create & register your own traps This gives you complete control over how errors & exceptions are handled,
logged and rendered. Use Cake\Error\ExceptionTrap and Cake\Error\ErrorTrap as reference when im-
plementing your traps.
Listen to Events
The ErrorTrap and ExceptionTrap handlers will trigger CakePHP events when they handle errors. You can listen
to the Error.beforeRender event to be notified of PHP errors. The Exception.beforeRender event is dispatched
when an exception is handled:
Custom Templates
The default exception trap renders all uncaught exceptions your application raises with the help of Cake\Error\
Renderer\WebExceptionRenderer, and your application’s ErrorController.
The error page views are located at templates/Error/. All 4xx errors use the error400.php template, and 5xx errors
use the error500.php. Your error templates will have the following variables available:
• message The exception message.
• code The exception code.
• url The request URL.
• error The exception object.
In debug mode if your error extends Cake\Core\Exception\CakeException the data returned by
getAttributes() will be exposed as view variables as well.
Note: You will need to set debug to false, to see your error404 and error500 templates. In debug mode, you’ll see
CakePHP’s development error page.
By default error templates use templates/layout/error.php for a layout. You can use the layout property to pick a
different layout:
// inside templates/Error/error400.php
$this->layout = 'my_error';
The above would use templates/layout/my_error.php as the layout for your error pages.
Many exceptions raised by CakePHP will render specific view templates in debug mode. With debug turned off all
exceptions raised by CakePHP will use either error400.php or error500.php based on their status code.
Custom Controller
The App\Controller\ErrorController class is used by CakePHP’s exception rendering to render the error page
view and receives all the standard request life-cycle events. By modifying this class you can control which components
are used and which templates are rendered.
If your application uses Prefix Routing you can create custom error controllers for each routing prefix. For example, if
you had an Admin prefix. You could create the following class:
namespace App\Controller\Admin;
use App\Controller\AppController;
use Cake\Event\EventInterface;
This controller would only be used when an error is encountered in a prefixed controller, and allows you to define prefix
specific logic/templates as needed.
Custom ExceptionRenderer
If you want to control the entire exception rendering and logging process you can use the Error.exceptionRenderer
option in config/app.php to choose a class that will render exception pages. Changing the ExceptionRenderer is useful
when you want to change the logic used to create an error controller, choose the template, or control the overall rendering
process.
Your custom exception renderer class should be placed in src/Error. Let’s assume our application uses App\
Exception\MissingWidgetException to indicate a missing widget. We could create an exception renderer that
renders specific error pages when this error is handled:
// In src/Error/AppExceptionRenderer.php
namespace App\Error;
use Cake\Error\Renderer\WebExceptionRenderer;
// In config/app.php
'Error' => [
'exceptionRenderer' => 'App\Error\AppExceptionRenderer',
// ...
],
// ...
The above would handle our MissingWidgetException, and allow us to provide custom display/handling logic for
those application exceptions.
Exception rendering methods receive the handled exception as an argument, and should return a Response object. You
can also implement methods to add additional logic when handling CakePHP errors:
// In src/Error/AppExceptionRenderer.php
namespace App\Error;
use Cake\Error\Renderer\WebExceptionRenderer;
The exception renderer dictates which controller is used for exception rendering. If you want to change which controller
is used to render exceptions, override the _getController() method in your exception renderer:
// in src/Error/AppExceptionRenderer
namespace App\Error;
use App\Controller\SuperCustomErrorController;
use Cake\Controller\Controller;
use Cake\Error\Renderer\WebExceptionRenderer;
// in config/app.php
'Error' => [
'exceptionRenderer' => 'App\Error\AppExceptionRenderer',
// ...
],
// ...
You can create your own application exceptions using any of the built in SPL exceptions141 , Exception itself, or
Cake\Core\Exception\Exception. If your application contained the following exception:
use Cake\Core\Exception\CakeException;
You could provide nice development errors, by creating templates/Error/missing_widget.php. When in production
mode, the above error would be treated as a 500 error and use the error500 template.
Exceptions that subclass Cake\Http\Exception\HttpException, will have their error code used as an HTTP status
code if the error code is between 400 and 506.
The constructor for Cake\Core\Exception\CakeException allows you to pass in additional data. This additional
data is interpolated into the the _messageTemplate. This allows you to create data rich exceptions, that provide more
context around your errors:
use Cake\Core\Exception\CakeException;
When rendered, this your view template would have a $widget variable set. If you cast the exception as a string or use
its getMessage() method you will get Seems that Pointy is missing..
Logging Exceptions
Using the built-in exception handling, you can log all the exceptions that are dealt with by ErrorTrap by setting the log
option to true in your config/app.php. Enabling this will log every exception to Cake\Log\Log and the configured
loggers.
Note: If you are using a custom exception handler this setting will have no effect. Unless you reference it inside your
implementation.
HTTP Exceptions
There are several built-in exceptions inside CakePHP, outside of the internal framework exceptions, there are several
exceptions for HTTP methods
exception Cake\Http\Exception\BadRequestException
exception Cake\Http\Exception\NotFoundException
use Cake\Http\Exception\NotFoundException;
By using exceptions for HTTP errors, you can keep your code both clean, and give RESTful responses to client appli-
cations and users.
142 https://datatracker.ietf.org/doc/html/rfc2616.html#section-10.4
143 https://datatracker.ietf.org/doc/html/rfc2616.html#section-10.5
You can throw any of the HTTP related exceptions from your controller actions to indicate failure states. For example:
use Cake\Network\Exception\NotFoundException;
The above would cause the configured exception handler to catch and process the NotFoundException. By default
this will create an error page, and log the exception.
exception Cake\Controller\Exception\PrivateActionException
Base exception class in CakePHP. All framework layer exceptions thrown by CakePHP will extend
this class.
These exception classes all extend Exception. By extending Exception, you can create your own ‘framework’ errors.
See Cake\Network\Request::header()
All Http and Cake exceptions extend the Exception class, which has a method to add headers to the response. For
instance when throwing a 405 MethodNotAllowedException the rfc2616 says:
By default PHP errors are rendered to console or HTML output, and also logged. If necessary, you can swap out
CakePHP’s error handling logic with your own.
Error handlers use instances of Cake\Error\ErrorLoggingInterface to create log messages and log them to the
appropriate place. You can replace the error logger using the Error.errorLogger configure value. An example error
logger:
namespace App\Error;
use Cake\Error\ErrorLoggerInterface;
use Cake\Error\PhpError;
use Psr\Http\Message\ServerRequestInterface;
use Throwable;
/**
* Log errors and unhandled exceptions to `Cake\Log\Log`
*/
class ErrorLogger implements ErrorLoggerInterface
{
/**
* @inheritDoc
*/
public function logError(
PhpError $error,
?ServerRequestInterface $request,
bool $includeTrace = false
): void {
// Log PHP Errors
}
/**
* @inheritDoc
*/
public function logException(
?ServerRequestInterface $request,
bool $includeTrace = false
): void {
// Log exceptions.
}
}
CakePHP includes error renderers for both web and console environments. If however, you would like to replace the
logic that renders errors you can create a class:
// src/Error/CustomErrorRenderer.php
namespace App\Error;
use Cake\Error\ErrorRendererInterface;
use Cake\Error\PhpError;
The constructor of your renderer will be passed an array of all the Error configuration. You connect your custom error
renderer to CakePHP via the Error.errorRenderer config value. When replacing error handling you will need to
account for both web and command line environments.
Events System
Creating maintainable applications is both a science and an art. It is well-known that a key for having good quality
code is making your objects loosely coupled and strongly cohesive at the same time. Cohesion means that all methods
and properties for a class are strongly related to the class itself and it is not trying to do the job other objects should
be doing, while loosely coupling is the measure of how little a class is “wired” to external objects, and how much that
class is depending on them.
There are certain cases where you need to cleanly communicate with other parts of an application, without having to
hard code dependencies, thus losing cohesion and increasing class coupling. Using the Observer pattern, which allows
objects to notify other objects and anonymous listeners about changes is a useful pattern to achieve this goal.
Listeners in the observer pattern can subscribe to events and choose to act upon them if they are relevant. If you have
used JavaScript, there is a good chance that you are already familiar with event driven programming.
CakePHP emulates several aspects of how events are triggered and managed in popular JavaScript libraries such as
jQuery. In the CakePHP implementation, an event object is dispatched to all listeners. The event object holds informa-
tion about the event, and provides the ability to stop event propagation at any point. Listeners can register themselves
or can delegate this task to other objects and have the chance to alter the state and the event itself for the rest of the
callbacks.
The event subsystem is at the heart of Model, Behavior, Controller, View and Helper callbacks. If you’ve ever used any
of them, you are already somewhat familiar with events in CakePHP.
589
CakePHP Book, Release 5.x
Let’s suppose you are building a Cart plugin, and you’d like to focus on just handling order logic. You don’t really want
to include shipping logic, emailing the user or decrementing the item from the stock, but these are important tasks to
the people using your plugin. If you were not using events, you may try to implement this by attaching behaviors to
models, or adding components to your controllers. Doing so represents a challenge most of the time, since you would
have to come up with the code for externally loading those behaviors or attaching hooks to your plugin controllers.
Instead, you can use events to allow you to cleanly separate the concerns of your code and allow additional concerns to
hook into your plugin using events. For example, in your Cart plugin you have an Orders model that deals with creating
orders. You’d like to notify the rest of the application that an order has been created. To keep your Orders model clean
you could use events:
// Cart/Model/Table/OrdersTable.php
namespace Cart\Model\Table;
use Cake\Event\Event;
use Cake\ORM\Table;
return true;
}
return false;
}
}
The above code allows you to notify the other parts of the application that an order has been created. You can then do
tasks like send email notifications, update stock, log relevant statistics and other tasks in separate objects that focus on
those concerns.
In CakePHP events are triggered against event managers. Event managers are available in every Table, View and
Controller using getEventManager():
$events = $this->getEventManager();
Each model has a separate event manager, while the View and Controller share one. This allows model events to be
self contained, and allow components or controllers to act upon events created in the view if necessary.
In addition to instance level event managers, CakePHP provides a global event manager that allows you to listen to any
event fired in an application. This is useful when attaching listeners to a specific instance might be cumbersome or
difficult. The global manager is a singleton instance of Cake\Event\EventManager. Listeners attached to the global
dispatcher will be fired before instance listeners at the same priority. You can access the global manager using a static
method:
// In any configuration file or piece of code that executes before the event
use Cake\Event\EventManager;
EventManager::instance()->on(
'Order.afterPlace',
$aCallback
);
One important thing you should consider is that there are events that will be triggered having the same name but different
subjects, so checking it in the event object is usually required in any function that gets attached globally in order to
prevent some bugs. Remember that with the flexibility of using the global manager, some additional complexity is
incurred.
Cake\Event\EventManager::dispatch() method accepts the event object as an argument and notifies all listener
and callbacks passing this object along. The listeners will handle all the extra logic around the afterPlace event, you
can log the time, send emails, update user statistics possibly in separate objects and even delegating it to offline tasks
if you have the need.
Tracking Events
To keep a list of events that are fired on a particular EventManager, you can enable event tracking. To do so, simply
attach an Cake\Event\EventList to the manager:
EventManager::instance()->setEventList(new EventList());
After firing an event on the manager, you can retrieve it from the event list:
$eventsFired = EventManager::instance()->getEventList();
$firstEvent = $eventsFired[0];
Core Events
There are a number of core events within the framework which your application can listen to. Each layer of CakePHP
emits events that you can use in your application.
• ORM/Model events
• Controller events
• View events
Registering Listeners
Listeners are the preferred way to register callbacks for an event. This is done by implementing the Cake\Event\
EventListenerInterface interface in any class you wish to register some callbacks. Classes implementing it need
to provide the implementedEvents() method. This method must return an associative array with all event names
that the class will handle.
To continue our previous example, let’s imagine we have a UserStatistic class responsible for calculating a user’s
purchasing history, and compiling into global site statistics. This is a great place to use a listener class. Doing so
allows you to concentrate the statistics logic in one place and react to events as necessary. Our UserStatistics
listener might start out like:
namespace App\Event;
use Cake\Event\EventListenerInterface;
// From your controller, attach the UserStatistic object to the Order's event manager
$statistics = new UserStatistic();
$this->Orders->getEventManager()->on($statistics);
As you can see in the above code, the on() function will accept instances of the EventListener interface. Internally,
the event manager will use implementedEvents() to attach the correct callbacks.
While event listener objects are generally a better way to implement listeners, you can also bind any callable as an
event listener. For example if we wanted to put any orders into the log files, we could use a simple anonymous function
to do so:
use Cake\Log\Log;
In addition to anonymous functions you can use any other callable type that PHP supports:
$events = [
'email-sending' => 'EmailSender::sendBuyEmail',
'inventory' => [$this->InventoryManager, 'decrement'],
];
foreach ($events as $callable) {
$eventManager->on('Order.afterPlace', $callable);
}
When working with plugins that don’t trigger specific events, you can leverage event listeners on the default events.
Lets take an example ‘UserFeedback’ plugin which handles feedback forms from users. From your application you
would like to know when a Feedback record has been saved and ultimately act on it. You can listen to the global
Model.afterSave event. However, you can take a more direct approach and only listen to the event you really need:
FactoryLocator::get('Table')->get('ThirdPartyPlugin.Feedbacks')
->getEventManager()
->on('Model.afterSave', function($event, $entity)
{
// For example we can send an email to the admin
$email = new Email('default');
$email->setFrom(['[email protected]' => 'Your Site'])
->setTo('[email protected]')
->setSubject('New Feedback - Your Site')
->send('Body of message');
});
Assuming several event listeners have been registered the presence or absence of a particular event pattern can be used
as the basis of some action.:
Establishing Priorities
In some cases you might want to control the order that listeners are invoked. For instance, if we go back to our user
statistics example. It would be ideal if this listener was called at the end of the stack. By calling it at the end of the
listener stack, we can ensure that the event was not cancelled, and that no other listeners raised exceptions. We can also
get the final state of the objects in the case that other listeners have modified the subject or event object.
Priorities are defined as an integer when adding a listener. The higher the number, the later the method will be fired.
The default priority for all listeners is 10. If you need your method to be run earlier, using any value below this default
will work. On the other hand if you desire to run the callback after the others, using a number above 10 will do.
If two callbacks happen to have the same priority value, they will be executed with a the order they were attached. You
set priorities using the on() method for callbacks, and declaring it in the implementedEvents() function for event
listeners:
As you see, the main difference for EventListener objects is that you need to use an array for specifying the callable
method and the priority preference. The callable key is a special array entry that the manager will read to know what
function in the class it should be calling.
When events have data provided in their constructor, the provided data is converted into arguments for the listeners.
An example from the View layer is the afterRender callback:
$this->getEventManager()
->dispatch(new Event('View.afterRender', $this, ['view' => $viewFileName]));
The listeners of the View.afterRender callback should have the following signature:
Each value provided to the Event constructor will be converted into function parameters in the order they appear in the
data array. If you use an associative array, the result of array_values will determine the function argument order.
Note: Unlike in 2.x, converting event data to listener arguments is the default behavior and cannot be disabled.
Dispatching Events
Once you have obtained an instance of an event manager you can dispatch events using dispatch(). This method
takes an instance of the Cake\Event\Event class. Let’s look at dispatching an event:
Cake\Event\Event accepts 3 arguments in its constructor. The first one is the event name, you should try to keep
this name as unique as possible, while making it readable. We suggest a convention as follows: Layer.eventName for
general events happening at a layer level (for example, Controller.startup, View.beforeRender) and Layer.
Class.eventName for events happening in specific classes on a layer, for example Model.User.afterRegister or
Controller.Courses.invalidAccess.
The second argument is the subject, meaning the object associated to the event, usually when it is the same class
triggering events about itself, using $this will be the most common case. Although a Component could trigger
controller events too. The subject class is important because listeners will get immediate access to the object properties
and have the chance to inspect or change them on the fly.
Finally, the third argument is any additional event data. This can be any data you consider useful to pass around so
listeners can act upon it. While this can be an argument of any type, we recommend passing an associative array.
The dispatch() method accepts an event object as an argument and notifies all subscribed listeners.
Stopping Events
Much like DOM events, you may want to stop an event to prevent additional listeners from being notified. You can see
this in action during model callbacks (for example, beforeSave) in which it is possible to stop the saving operation if
the code detects it cannot proceed any further.
In order to stop events you can either return false in your callbacks or call the stopPropagation() method on the
event object:
Stopping an event will prevent any additional callbacks from being called. Additionally the code triggering the event
may behave differently based on the event being stopped or not. Generally it does not make sense to stop ‘after’ events,
but stopping ‘before’ events is often used to prevent the entire operation from occurring.
To check if an event was stopped, you call the isStopped() method in the event object:
In the previous example the order would not get saved if the event is stopped during the beforePlace process.
Every time a callback returns a non-null non-false value, it gets stored in the $result property of the event object.
This is useful when you want to allow callbacks to modify the event execution. Let’s take again our beforePlace
example and let callbacks modify the $order data.
Event results can be altered either using the event object result property directly or returning the value in the callback
itself:
// A listener callback
public function doSomething($event)
{
(continues on next page)
return $alteredData;
}
It is possible to alter any event object property and have the new data passed to the next callback. In most of the cases,
providing objects as event data or result and directly altering the object is the best solution as the reference is kept the
same and modifications are shared across all callback calls.
If for any reason you want to remove any callback from the event manager just call the Cake\Event\
EventManager::off() method using as arguments the first two parameters you used for attaching it:
// Attaching a function
$this->getEventManager()->on('My.event', [$this, 'doSomething']);
// Adding a EventListener
$listener = new MyEventLister();
(continues on next page)
Events are a great way of separating concerns in your application and make classes both cohesive and decoupled from
each other. Events can be utilized to de-couple application code and make extensible plugins.
Keep in mind that with great power comes great responsibility. Using too many events can make debugging harder and
require additional integration testing.
Additional Reading
• Behaviors
• Command Objects
• Components
• Helpers
• Testing Events
One of the best ways for an application to reach a larger audience is to cater to multiple languages. This can often prove
to be a daunting task, but the internationalization and localization features in CakePHP make it much easier.
First, it’s important to understand some terminology. Internationalization refers to the ability of an application to
be localized. The term localization refers to the adaptation of an application to meet specific language (or culture)
requirements (i.e. a “locale”). Internationalization and localization are often abbreviated as i18n and l10n respectively;
18 and 10 are the number of characters between the first and last character.
Setting Up Translations
There are only a few steps to go from a single-language application to a multi-lingual application, the first of which is
to make use of the __() function in your code. Below is an example of some code for a single-language application:
<h2>Popular Articles</h2>
To internationalize your code, all you need to do is to wrap strings in __() like so:
Doing nothing else, these two code examples are functionally identical - they will both send the same content to the
browser. The __() function will translate the passed string if a translation is available, or return it unmodified.
599
CakePHP Book, Release 5.x
Language Files
Translations can be made available by using language files stored in the application. The default format for CakePHP
translation files is the Gettext144 format. Files need to be placed under resources/locales/ and within this directory,
there should be a subfolder for each language the application needs to support:
resources/
locales/
en_US/
default.po
en_GB/
default.po
validation.po
es/
default.po
The default domain is ‘default’, therefore the locale folder should at least contain the default.po file as shown above.
A domain refers to any arbitrary grouping of translation messages. When no group is used, then the default group is
selected.
The core strings messages extracted from the CakePHP library can be stored separately in a file named cake.po
in resources/locales/. The CakePHP localized library145 houses translations for the client-facing translated
strings in the core (the cake domain). To use these files, link or copy them into their expected location: re-
sources/locales/<locale>/cake.po. If your locale is incomplete or incorrect, please submit a PR in this repository
to fix it.
Plugins can also contain translation files, the convention is to use the under_scored version of the plugin name as the
domain for the translation messages:
MyPlugin/
resources/
locales/
fr/
my_plugin.po
additional.po
de/
my_plugin.po
Translation folders can be the two or three letter ISO code of the language or the full ICU locale name such as fr_FR,
es_AR, da_DK which contains both the language and the country where it is spoken.
See https://www.localeplanet.com/icu/ for the full list of locales.
Changed in version 4.5.0: As of 4.5.0 plugins can contain multiple translation domains. Use MyPlugin.additional
to reference plugin domains.
An example translation file could look like this:
144 https://en.wikipedia.org/wiki/Gettext
145 https://github.com/cakephp/localized
Note: Translations are cached - Make sure that you always clear the cache after making changes to translations! You
can either use the cache tool and run for example bin/cake cache clear _cake_core_, or manually clear the
tmp/cache/persistent folder (if using file based caching).
To create the pot files from __() and other internationalized types of messages that can be found in the application code,
you can use the i18n command. Please read the following chapter to learn more.
The default locale can be set in your config/app.php file by setting App.defaultLocale:
'App' => [
...
'defaultLocale' => env('APP_DEFAULT_LOCALE', 'en_US'),
...
]
This will control several aspects of the application, including the default translations language, the date format, number
format and currency whenever any of those is displayed using the localization libraries that CakePHP provides.
To change the language for translated strings you can call this method:
use Cake\I18n\I18n;
I18n::setLocale('de_DE');
This will also change how numbers and dates are formatted when using one of the localization tools.
CakePHP provides several functions that will help you internationalize your application. The most frequently used one
is __(). This function is used to retrieve a single translation message or return the same string if no translation was
found:
If you need to group your messages, for example, translations inside a plugin, you can use the __d() function to fetch
messages from another domain:
Note: If you want to translate plugins that are vendor namespaced, you must use the domain string vendor/
plugin_name. But the related language file will become plugins/<Vendor>/<PluginName>/resources/
locales/<locale>/plugin_name.po inside your plugin folder.
Sometimes translations strings can be ambiguous for people translating them. This can happen if two strings are
identical but refer to different things. For example, ‘letter’ has multiple meanings in English. To solve that problem,
you can use the __x() function:
The first argument is the context of the message and the second is the message to be translated.
Translation functions allow you to interpolate variables into the messages using special markers defined in the message
itself or in the translated string:
echo __("Hello, my name is {0}, I'm {1} years old", ['Sara', 12]);
Markers are numeric, and correspond to the keys in the passed array. You can also pass variables as independent
arguments to the function:
echo __("Small step for {0}, Big leap for {1}", 'Man', 'Humanity');
The ' (single quote) character acts as an escape code in translation messages. Any variables between single quotes will
not be replaced and is treated as literal text. For example:
These functions take advantage of the ICU MessageFormatter146 so you can translate messages and localize dates,
numbers and currency at the same time:
echo __(
'Hi {0}, your balance on the {1,date} is {2,number,currency}',
(continues on next page)
146 https://php.net/manual/en/messageformatter.format.php
// Returns
Hi Charles, your balance on the Jan 13, 2014, 11:12 AM is $ 1,354.37
Numbers in placeholders can be formatted as well with fine grain control of the output:
echo __(
'You have traveled {0,number} kilometers in {1,number,integer} weeks',
[5423.344, 5.1]
);
// Returns
You have traveled 5,423.34 kilometers in 5 weeks
// Returns
There are 6,100,000,000 people on earth
This is the list of formatter specifiers you can put after the word number:
• integer: Removes the decimal part
• currency: Puts the locale currency symbol and rounds decimals
• percent: Formats the number as a percentage
Dates can also be formatted by using the word date after the placeholder number. A list of extra options follows:
• short
• medium
• long
• full
The word time after the placeholder number is also accepted and it understands the same options as date.
You can also use named placeholders like {name} in the message strings. When using named placeholders, pass the
placeholder and replacement in an array using key/value pairs, for example:
Plurals
One crucial part of internationalizing your application is getting your messages pluralized correctly depending on the
language they are shown. CakePHP provides a couple ways to correctly select plurals in your messages.
The first one is taking advantage of the ICU message format that comes by default in the translation functions. In the
translations file you could have the following strings
And in the application use the following code to output either of the translations for such string:
A closer look to the format we just used will make it evident how messages are built:
The [count placeholder] can be the array key number of any of the variables you pass to the translation function.
It will be used for selecting the correct plural form.
Note that to reference [count placeholder] within {message} you have to use #.
You can of course use simpler message ids if you don’t want to type the full plural selection sequence in your code
msgid "search.results"
msgstr "{0,plural,=0{Ningún resultado} =1{1 resultado} other{{1} resultados}}"
The latter version has the downside that there is a need to have a translation messages file even for the default language,
but has the advantage that it makes the code more readable and leaves the complicated plural selection strings in the
translation files.
Sometimes using direct number matching in plurals is impractical. For example, languages like Arabic require a dif-
ferent plural when you refer to few things and other plural form for many things. In those cases you can use the ICU
matching aliases. Instead of writing:
Make sure you read the Language Plural Rules Guide147 to get a complete overview of the aliases you can use for each
language.
The second plural selection format accepted is using the built-in capabilities of Gettext. In this case, plurals will be
stored in the .po file by creating a separate message translation line per plural form:
When using this other format, you are required to use another translation function:
The number inside msgstr[] is the number assigned by Gettext for the plural form of the language. Some languages
have more than two plural forms, for example Croatian:
Please visit the Launchpad languages page148 for a detailed explanation of the plural form numbers for each language.
147 https://unicode-org.github.io/cldr-staging/charts/37/supplemental/language_plural_rules.html
148 https://translations.launchpad.net/+languages
If you need to diverge from CakePHP conventions regarding where and how translation messages are stored, you can
create your own translation message loader. The easiest way to create your own translator is by defining a loader for a
single domain and locale:
use Cake\I18n\Package;
// Prior to 4.2 you need to use Aura\Intl\Package
I18n::setTranslator('animals', function () {
$package = new Package(
'default', // The formatting strategy (ICU)
'default' // The fallback domain
);
$package->setMessages([
'Dog' => 'Chien',
'Cat' => 'Chat',
'Bird' => 'Oiseau'
...
]);
return $package;
}, 'fr_FR');
The above code can be added to your config/bootstrap.php so that translations can be found before any translation
function is used. The absolute minimum that is required for creating a translator is that the loader function should
return a Cake\I18n\Package object (prior to 4.2 it should be an Aura\Intl\Package object). Once the code is in
place you can use the translation functions as usual:
I18n::setLocale('fr_FR');
__d('animals', 'Dog'); // Returns "Chien"
As you see, Package objects take translation messages as an array. You can pass the setMessages() method however
you like: with inline code, including another file, calling another function, etc. CakePHP provides a few loader functions
you can reuse if you just need to change where messages are loaded. For example, you can still use .po files, but loaded
from another location:
It is possible to continue using the same conventions CakePHP uses, but use a message parser other than
PoFileParser. For example, if you wanted to load translation messages using YAML, you will first need to created the
parser class:
namespace App\I18n\Parser;
class YamlFileParser
{
public function parse($file)
{
return yaml_parse_file($file);
}
}
The file should be created in the src/I18n/Parser directory of your application. Next, create the translations file under
resources/locales/fr_FR/animals.yaml
Dog: Chien
Cat: Chat
Bird: Oiseau
And finally, configure the translation loader for the domain and locale:
I18n::setTranslator(
'animals',
new Loader('animals', 'fr_FR', 'yaml'),
'fr_FR'
);
Configuring translators by calling I18n::setTranslator() for each domain and locale you need to support can be
tedious, specially if you need to support more than a few different locales. To avoid this problem, CakePHP lets you
define generic translator loaders for each domain.
Imagine that you wanted to load all translations for the default domain and for any language from an external service:
use Cake\I18n\Package;
// Prior to 4.2 you need to use Aura\Intl\Package
The above example calls an example external service to load a JSON file with the translations and then just build a
Package object for any locale that is requested in the application.
If you’d like to change how packages are loaded for all packages, that don’t have specific loaders set you can replace
the fallback package loader by using the _fallback package:
The arrays used for setMessages() can be crafted to instruct the translator to store messages under different domains
or to trigger Gettext-style plural selection. The following is an example of storing translations for the same key in
different contexts:
[
'He reads the letter {0}' => [
'alphabet' => 'Él lee la letra {0}',
'written communication' => 'Él lee la carta {0}',
],
]
Similarly, you can express Gettext-style plurals using the messages array by having a nested array key per plural form:
[
'I have read one book' => 'He leído un libro',
'I have read {0} books' => [
'He leído un libro',
'He leído {0} libros',
],
]
In previous examples we have seen that Packages are built using default as first argument, and it was indicated with a
comment that it corresponded to the formatter to be used. Formatters are classes responsible for interpolating variables
in translation messages and selecting the correct plural form.
If you’re dealing with a legacy application, or you don’t need the power offered by the ICU message formatting,
CakePHP also provides the sprintf formatter:
The messages to be translated will be passed to the sprintf() function for interpolating the variables:
It is possible to set the default formatter for all translators created by CakePHP before they are used for the first time.
This does not include manually created translators using the setTranslator() and config() methods:
I18n::defaultFormatter('sprintf');
When outputting Dates and Numbers in your application, you will often need that they are formatted according to the
preferred format for the country or region that you wish your page to be displayed.
In order to change how dates and numbers are displayed you just need to change the current locale setting and use the
right classes:
use Cake\I18n\I18n;
use Cake\I18n\DateTime;
use Cake\I18n\Number;
I18n::setLocale('fr-FR');
Make sure you read the Date & Time and Number sections to learn more about formatting options.
By default dates returned for the ORM results use the Cake\I18n\DateTime class, so displaying them directly in you
application will be affected by changing the current locale.
When accepting localized data from the request, it is nice to accept datetime information in a user’s localized format.
In a controller, or /controllers/middleware you can configure the Date, Time, and DateTime types to parse localized
formats:
use Cake\Database\TypeFactory;
The default parsing format is the same as the default string format.
When handling data from users in different timezones you will need to convert the datetimes in request data into your
application’s timezone. You can use setUserTimezone() from a controller or /controllers/middleware to make this
process simpler:
Once set, when your application creates or updates entities from request data, the ORM will automatically convert
datetime values from the user’s timezone into your application’s timezone. This ensures that your application is always
working in the timezone defined in App.defaultTimezone.
If your application handles datetime information in a number of actions you can use a middleware to define both
timezone conversion and locale parsing:
namespace App\Middleware;
use Cake\Database\TypeFactory;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
return $handler->handle($request);
}
}
By using the LocaleSelectorMiddleware in your application, CakePHP will automatically set the locale based on
the current user:
// in src/Application.php
use Cake\I18n\Middleware\LocaleSelectorMiddleware;
The LocaleSelectorMiddleware will use the Accept-Language header to automatically set the user’s preferred
locale. You can use the locale list option to restrict which locales will automatically be used.
Translate Content/Entities
If you want to translate content/entities then you should look at the Translate Behavior.
Logging
While CakePHP core Configure Class settings can really help you see what’s happening under the hood, there are
certain times that you’ll need to log data to the disk in order to find out what’s going on. With technologies like SOAP,
AJAX, and REST APIs, debugging can be rather difficult.
Logging can also be a way to find out what’s been going on in your application over time. What search terms are being
used? What sorts of errors are my users being shown? How often is a particular query being executed?
Logging data in CakePHP is done with the log() function. It is provided by the LogTrait, which is the common
ancestor for many CakePHP classes. If the context is a CakePHP class (Controller, Component, View,. . . ), you can log
your data. You can also use Log::write() directly. See Writing to Logs.
Logging Configuration
Configuring Log should be done during your application’s bootstrap phase. The config/app.php file is intended for
just this. You can define as many or as few loggers as your application needs. Loggers should be configured using
Cake\Log\Log. An example would be:
use Cake\Log\Engine\FileLog;
use Cake\Log\Log;
// Short classname
(continues on next page)
613
CakePHP Book, Release 5.x
The above creates three loggers, named info, debug and error. Each is configured to handle different levels of
messages. They also store their log messages in separate files, so we can separate debug/notice/info logs from more
serious errors. See the section on Using Levels for more information on the different levels and what they mean.
Once a configuration is created you cannot change it. Instead you should drop the configuration and re-create it using
Cake\Log\Log::drop() and Cake\Log\Log::setConfig().
It is also possible to create loggers by providing a closure. This is useful when you need full control over how the logger
object is built. The closure has to return the constructed logger instance. For example:
Log::setConfig('special', function () {
return new \Cake\Log\Engine\FileLog(['path' => LOGS, 'file' => 'log']);
});
Configuration options can also be provided as a DSN string. This is useful when working with environment variables
or PaaS providers:
Log::setConfig('error', [
'url' => 'file:///full/path/to/logs/?levels[]=warning&levels[]=error&file=error',
]);
Warning: If you do not configure logging engines, log messages will not be stored.
Errors and Exceptions can also be logged. By configuring the corresponding values in your config/app.php file. Errors
will be displayed when debug is true and logged when debug is false. To log uncaught exceptions, set the log option
to true. See Configuration for more information.
Writing to Logs
Writing to the log files can be done in two different ways. The first is to use the static Cake\Log\Log::write()
method:
The second is to use the log() shortcut function available on any class using the LogTrait. Calling log() will
internally call Log::write():
All configured log streams are written to sequentially each time Cake\Log\Log::write() is called. If you have not
configured any logging engines log() will return false and no log messages will be written.
If you need to log dynamically defined data, you can use placeholders in your log messages and provide an array of
key/value pairs in the $context parameter:
Placeholders that do not have keys defined will not be replaced. If you need to use a literal braced word, you must
escape the placeholder:
If you include objects in your logging placeholders those objects must implement one of the following methods:
• __toString()
• toArray()
• __debugInfo()
Using Levels
CakePHP supports the standard POSIX set of logging levels. Each level represents an increasing level of severity:
• Emergency: system is unusable
• Alert: action must be taken immediately
• Critical: critical conditions
• Error: error conditions
• Warning: warning conditions
• Notice: normal but significant condition
• Info: informational messages
• Debug: debug-level messages
You can refer to these levels by name when configuring loggers, and when writing log messages. Alternatively, you
can use convenience methods like Cake\Log\Log::error() to clearly indicate the logging level. Using a level that
is not in the above levels will result in an exception.
Note: When levels is set to an empty value in a logger’s configuration, it will take messages of any level.
Logging Scopes
Often times you’ll want to configure different logging behavior for different subsystems or parts of your application.
Take for example an e-commerce shop. You’ll probably want to handle logging for orders and payments differently
than you do other less critical logs.
CakePHP exposes this concept as logging scopes. When log messages are written you can include a scope name. If
there is a configured logger for that scope, the log messages will be directed to those loggers. For example:
use Cake\Log\Engine\FileLog;
Scopes can also be passed as a single string or a numerically indexed array. Note that using this form will limit the
ability to pass more data as context:
Note: When scopes is set to an empty array or null in a logger’s configuration, it will take messages of any scope.
Setting it to false will only match messages without scope.
Logging to Files
As its name implies FileLog writes log messages to files. The level of log message being written determines the name
of the file the message is stored in. If a level is not supplied, LOG_ERR is used which writes to the error log. The default
log location is logs/$level.log:
The configured directory must be writable by the web server user in order for logging to work correctly.
You can configure additional/alternate FileLog locations when configuring a logger. FileLog accepts a path which
allows for custom paths to be used:
Log::setConfig('custom_path', [
'className' => 'File',
'path' => '/path/to/custom/place/'
]);
Note: Missing directories will be automatically created to avoid unnecessary errors thrown when using the FileEngine.
Logging to Syslog
In production environments it is highly recommended that you setup your system to use syslog instead of the file logger.
This will perform much better as any writes will be done in a (almost) non-blocking fashion and your operating system
logger can be configured separately to rotate files, pre-process writes or use a completely different storage for your logs.
Using syslog is pretty much like using the default FileLog engine, you just need to specify Syslog as the engine to be
used for logging. The following configuration snippet will replace the default logger with syslog, this should be done
in the config/bootstrap.php file:
Log::setConfig('default', [
'engine' => 'Syslog'
]);
The configuration array accepted for the Syslog logging engine understands the following keys:
• format: An sprintf template string with two placeholders, the first one for the error level, and the second for the
message itself. This key is useful to add additional information about the server or process in the logged message.
For example: %s - Web Server 1 - %s will look like error - Web Server 1 - An error occurred
in this request after replacing the placeholders. This option is deprecated. You should use Logging For-
matters instead.
• prefix: An string that will be prefixed to every logged message.
• flag: An integer flag to be used for opening the connection to the logger, by default LOG_ODELAY will be used.
See openlog documentation for more options
• facility: The logging slot to use in syslog. By default LOG_USER is used. See syslog documentation for
more options
Log engines can be part of your application, or part of plugins. If for example you had a database logger called
DatabaseLog. As part of your application it would be placed in src/Log/Engine/DatabaseLog.php. As part of a
plugin it would be placed in plugins/LoggingPack/src/Log/Engine/DatabaseLog.php. To configure log engine you
should use Cake\Log\Log::setConfig(). For example configuring our DatabaseLog would look like:
// For src/Log
Log::setConfig('otherFile', [
'className' => 'Database',
'model' => 'LogEntry',
// ...
]);
When configuring a log engine the className parameter is used to locate and load the log handler. All of the other
configuration properties are passed to the log engine’s constructor as an array.
namespace App\Log\Engine;
use Cake\Log\Engine\BaseLog;
CakePHP requires that all logging engine implement Psr\Log\LoggerInterface. The class
CakeLogEngineBaseLog is an easy way to satisfy the interface as it only requires you to implement the log()
method.
Logging Formatters
Logging formatters allow you to control how log messages are formatted independent of the storage engine. Each core
provided logging engine comes with a formatter configured to maintain backwards compatible output. However, you
can adjust the formatters to fit your requirements. Formatters are configured alongside the logging engine:
use Cake\Log\Engine\SyslogLog;
use App\Log\Formatter\CustomFormatter;
To implement your own logging formatter you need to extend Cake\Log\Format\AbstractFormatter or one of
its subclasses. The primary method you need to implement is format($level, $message, $context) which is
responsible for formatting log messages.
Log API
class Cake\Log\Log
A simple class for writing to logs.
static Cake\Log\Log::setConfig($key, $config)
Parameters
• $name (string) – Name for the logger being connected, used to drop a logger later on.
• $config (array) – Array of configuration information and constructor arguments for the
logger.
Get or set the configuration for a Logger. See Logging Configuration for more information.
static Cake\Log\Log::configured
Returns
An array of configured loggers.
Get the names of the configured loggers.
static Cake\Log\Log::drop($name)
Parameters
• $name (string) – Name of the logger you wish to no longer receive messages.
static Cake\Log\Log::write($level, $message, $scope = [])
Write a message into all the configured loggers. $level indicates the level of log message being created.
$message is the message of the log entry being written to. $scope is the scope(s) a log message is being
created in.
static Cake\Log\Log::levels
Call this method without arguments, eg: Log::levels() to obtain current level configuration.
Convenience Methods
The following convenience methods were added to log $message with the appropriate log level.
static Cake\Log\Log::emergency($message, $scope = [])
Logging Trait
trait Cake\Log\LogTrait
A trait that provides shortcut methods for logging
Cake\Log\LogTrait::log($msg, $level = LOG_ERR)
Log a message to the logs. By default messages are logged as ERROR messages.
Using Monolog
Monolog is a popular logger for PHP. Since it implements the same interfaces as the CakePHP loggers, you can use
them in your application as the default logger.
After installing Monolog using composer, configure the logger using the Log::setConfig() method:
// config/bootstrap.php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
Log::setConfig('default', function () {
$log = new Logger('app');
$log->pushHandler(new StreamHandler('path/to/your/combined.log'));
return $log;
});
Use similar methods if you want to configure a different logger for your console:
// config/bootstrap_cli.php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
Log::setConfig('default', function () {
$log = new Logger('cli');
$log->pushHandler(new StreamHandler('path/to/your/combined-cli.log'));
return $log;
});
Note: When using a console specific logger, make sure to conditionally configure your application logger. This will
prevent duplicate log entries.
Modelless Forms
class Cake\Form\Form
Most of the time you will have forms backed by ORM entities and ORM tables or other persistent stores, but there are
times when you’ll need to validate user input and then perform an action if the data is valid. The most common example
of this is a contact form.
Creating a Form
Generally when using the Form class you’ll want to use a subclass to define your form. This makes testing easier, and
lets you re-use your form. Forms are put into src/Form and usually have Form as a class suffix. For example, a simple
contact form would look like:
// in src/Form/ContactForm.php
namespace App\Form;
use Cake\Form\Form;
use Cake\Form\Schema;
use Cake\Validation\Validator;
623
CakePHP Book, Release 5.x
return $validator;
}
In the above example we see the 3 hook methods that forms provide:
• _buildSchema is used to define the schema data that is used by FormHelper to create an HTML form. You can
define field type, length, and precision.
• validationDefault Gets a Cake\Validation\Validator instance that you can attach validators to.
• _execute lets you define the behavior you want to happen when execute() is called and the data is valid.
You can always define additional public methods as you need as well.
Once you’ve defined your form, you can use it in your controller to process and validate request data:
// In a controller
namespace App\Controller;
use App\Controller\AppController;
use App\Form\ContactForm;
In the above example, we use the execute() method to run our form’s _execute() method only when the data is
valid, and set flash messages accordingly. If we want to use a non-default validation set we can use the validate
option:
if ($contact->execute($this->request->getData(), 'update')) {
// Handle form success.
}
$isValid = $form->validate($this->request->getData());
You can set default values for modelless forms using the setData() method. Values set with this method will overwrite
existing data in the form object:
// In a controller
namespace App\Controller;
use App\Controller\AppController;
use App\Form\ContactForm;
if ($this->request->is('get')) {
$contact->setData([
'name' => 'John Doe',
'email' => '[email protected]'
]);
}
$this->set('contact', $contact);
}
}
Values should only be defined if the request method is GET, otherwise you will overwrite your previous POST Data
which might have validation errors that need corrections. You can use set() to add or replace individual fields or a
subset of fields:
Once a form has been validated you can retrieve the errors from it:
$errors = $form->getErrors();
/* $errors contains
[
'name' => ['length' => 'Name must be at least two characters long'],
'email' => ['format' => 'A valid email address is required'],
]
*/
$error = $form->getError('email');
/* $error contains
[
'format' => 'A valid email address is required',
]
*/
It is possible to invalidate individual fields from the controller without the use of the Validator class. The most common
use case for this is when the validation is done on a remote server. In such case, you must manually invalidate the fields
accordingly to the feedback from the remote server:
// in src/Form/ContactForm.php
public function setErrors($errors)
{
$this->_errors = $errors;
}
According to how the validator class would have returned the errors, $errors must be in this format:
Now you will be able to invalidate form fields by setting the fieldName, then set the error messages:
// In a controller
$contact = new ContactForm();
$contact->setErrors(['email' => ['_required' => 'Your email is required']]);
Once you’ve created a Form class, you’ll likely want to create an HTML form for it. FormHelper understands Form
objects just like ORM entities:
echo $this->Form->create($contact);
echo $this->Form->control('name');
echo $this->Form->control('email');
echo $this->Form->control('body');
echo $this->Form->button('Submit');
echo $this->Form->end();
The above would create an HTML form for the ContactForm we defined earlier. HTML forms created with
FormHelper will use the defined schema and validator to determine field types, maxlengths, and validation errors.
Pagination
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 terse way to paginate
data.
Pagination in CakePHP controllers is done through the paginate() method. You then use PaginatorHelper in your
view templates to generate pagination controls.
Basic Usage
You can call paginate() using an ORM table instance or Query object:
629
CakePHP Book, Release 5.x
Advanced Usage
More complex use cases are supported by configuring the $paginate controller property or as the $settings argu-
ment to paginate(). These conditions serve as the basis for you pagination queries. They are augmented by the sort,
direction, limit, and page parameters passed in from the URL:
class ArticlesController extends AppController
{
protected array $paginate = [
'limit' => 25,
'order' => [
'Articles.title' => 'asc',
],
];
}
You can also use Custom Finder Methods in pagination by using the finder option:
If your finder method requires additional options you can pass those as values for the finder:
class ArticlesController extends AppController
{
// find articles by tag
public function tags()
{
$tags = $this->request->getParam('pass');
$customFinderOptions = [
'tags' => $tags
];
// We're using the $settings argument to paginate() here.
// But the same structure could be used in $this->paginate
//
// Our custom finder is called findTagged inside ArticlesTable.php
// which is why we're using `tagged` as the key.
// Our finder should look like:
// public function findTagged(Query $query, array $tagged = [])
$settings = [
'finder' => [
'tagged' => $customFinderOptions
]
];
$articles = $this->paginate($this->Articles, $settings);
(continues on next page)
In addition to defining general pagination values, you can define more than one set of pagination defaults in the con-
troller. The name of each model can be used as a key in the $paginate property:
The values of the Articles and Authors keys could contain all the keys that a basic $paginate array would.
Controller::paginate() returns an instance of Cake\Datasource\Paging\PaginatedResultSet which im-
plements the Cake\Datasource\Paging\PaginatedInterface.
This object contains the paginated records and the paging params.
Simple Pagination
];
}
When using the SimplePaginator you will not be able to generate page numbers, counter data, links to the last page,
or total record count controls.
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
protected array $paginate = [
'Articles' => ['scope' => 'article'],
'Tags' => ['scope' => 'tag']
];
(continues on next page)
// 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 the paginator 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.
To paginate the same model multiple times within a single controller action you need to define an alias for the model.:
// In a controller action
$this->paginate = [
'Articles' => [
'scope' => 'published_articles',
'limit' => 10,
'order' => [
'id' => 'desc',
],
],
'UnpublishedArticles' => [
'scope' => 'unpublished_articles',
'limit' => 10,
'order' => [
'id' => 'desc',
],
],
];
$publishedArticles = $this->paginate(
$this->Articles->find('all', scope: 'published_articles')
->where(['published' => true])
);
$unpublishedArticles = $this->paginate(
$unpublishedArticlesTable->find('all', scope: 'unpublished_articles')
->where(['published' => false])
);
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 allowed list of fields that can be sorted
using the sortableFields 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:
Any requests that attempt to sort on fields not in the allowed list will be ignored.
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:
If the request’s limit param is greater than this value, it will be reduced to the maxLimit value.
Controller::paginate() 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:
use Cake\Http\Exception\NotFoundException;
// Create a paginator
$paginator = new \Cake\Datasource\Paginator\NumericPaginator();
Check the PaginatorHelper documentation for how to create links for pagination navigation.
Plugins
CakePHP allows you to set up a combination of controllers, models, and views and release them as a pre-packaged
application plugin that others can use in their CakePHP applications. If you’ve created great user management, a
simple blog, or web service adapters in one of your applications, why not package it as a CakePHP plugin? This way
you can reuse it in your other applications, and share with the community!
A CakePHP plugin is separate from the host application itself and generally provides some well-defined functionality
that can be packaged up neatly, and reused with little effort in other applications. The application and the plugin operate
in their own respective spaces, but share the application’s configuration data (for example, database connections, email
transports)
Plugin should define their own top-level namespace. For example: DebugKit. By convention, plugins use their package
name as their namespace. If you’d like to use a different namespace, you can configure the plugin namespace, when
plugins are loaded.
Many plugins are available on Packagist149 and can be installed with Composer. To install DebugKit, you would do
the following:
This would install the latest version of DebugKit and update your composer.json, composer.lock file, update
vendor/cakephp-plugins.php, and update your autoloader.
149 https://packagist.org
635
CakePHP Book, Release 5.x
If the plugin you want to install is not available on packagist.org, you can clone or copy the plugin code into your plugins
directory. Assuming you want to install a plugin named ‘ContactManager’, you should have a folder in plugins named
‘ContactManager’. In this directory are the plugin’s src, tests and any other directories.
If you install your plugins via composer or bake you shouldn’t need to configure class autoloading for your plugins.
If you create a plugin manually under the plugins folder then will need to tell composer to refresh its autoloading
cache:
If you are using vendor namespaces for your plugins, you’ll have to add the namespace to path mapping to the
composer.json resembling the following before running the above composer command:
{
"autoload": {
"psr-4": {
"AcmeCorp\\Users\\": "plugins/AcmeCorp/Users/src/",
}
},
"autoload-dev": {
"psr-4": {
"AcmeCorp\\Users\\Test\\": "plugins/AcmeCorp/Users/tests/"
}
}
}
Loading a Plugin
If you want to use a plugin’s routes, console commands, middlewares, event listeners, templates or webroot assets you
will need to load the plugin.
If you just want to use helpers, behaviors or components from a plugin you do not need to explicitly load a plugin yet
it’s recommended to always do so.
There is also a handy console command to load the plugin. Execute the following line:
This would update the array in your application’s config/plugins.php with an entry similar to 'ContactManager'
=> [].
Plugins offer several hooks that allow a plugin to inject itself into the appropriate parts of your application. The hooks
are:
• bootstrap Used to load plugin default configuration files, define constants and other global functions.
• routes Used to load routes for a plugin. Fired after application routes are loaded.
• middleware Used to add plugin middleware to an application’s middleware queue.
• console Used to add console commands to an application’s command collection.
• services Used to register application container services
By default all plugins hooks are enabled. You can disable hooks by using the related options of the plugin load
command:
This would update the array in your application’s config/plugins.php with an entry similar to 'ContactManager'
=> ['routes' => false].
Apart from the options for plugin hooks the plugin load command has the following options to control plugin load-
ing:
• --only-debug Load the plugin only when debug mode is enabled.
• --only-cli Load the plugin only for CLI.
• --optional Do not throw an error if the plugin is not available.
Apart from the config array in config/plugins.php, plugins can also be loaded in your application’s bootstrap()
method:
// In src/Application.php
use Cake\Http\BaseApplication;
use ContactManager\ContactManagerPlugin;
You can configure hooks with array options, or the methods provided by plugin classes:
// In Application::bootstrap()
use ContactManager\ContactManagerPlugin;
$plugin->disable('bootstrap');
$plugin->enable('routes');
$this->addPlugin($plugin);
You can reference a plugin’s controllers, models, components, behaviors, and helpers by prefixing the name of the
plugin.
For example, say you wanted to use the ContactManager plugin’s ContactInfoHelper to output formatted contact
information in one of your views. In your controller, using addHelper() could look like this:
$this->viewBuilder()->addHelper('ContactManager.ContactInfo');
You would then be able to access the ContactInfoHelper just like any other helper in your view, such as:
echo $this->ContactInfo->address($contact);
Plugins can use the models, components, behaviors and helpers provided by the application, or other plugins if neces-
sary:
As a working example, let’s begin to create the ContactManager plugin referenced above. To start out, we’ll set up our
plugin’s basic directory structure. It should look like this:
/src
/plugins
/ContactManager
/config
/src
/ContactManagerPlugin.php
/Controller
/Component
/Model
/Table
/Entity
/Behavior
/View
/Helper
/templates
/layout
/tests
/TestCase
/Fixture
/webroot
Note the name of the plugin folder, ‘ContactManager’. It is important that this folder has the same name as the plugin.
Inside the plugin folder, you’ll notice it looks a lot like a CakePHP application, and that’s basically what it is. Just
instead of an Application.php you have a ContactManagerPlugin.php. You don’t have to include any of the
folders you are not using. Some plugins might only define a Component and a Behavior, and in that case they can
completely omit the ‘templates’ directory.
A plugin can also have basically any of the other directories that your application can, such as Config, Console, webroot,
etc.
Bake can be used to create classes in your plugin. For example to generate a plugin controller you could run:
Please refer to the chapter /bake/usage if you have any problems with using the command line. Be sure to re-generate
your autoloader once you’ve created your plugin:
Plugin Classes
Plugin classes allow a plugin author to define set-up logic, define default hooks, load routes, middleware and console
commands. Plugin classes live in src/{PluginName}Plugin.php. For our ContactManager plugin, our plugin class
could look like:
namespace ContactManager;
use Cake\Core\BasePlugin;
use Cake\Core\ContainerInterface;
use Cake\Core\PluginApplicationInterface;
use Cake\Console\CommandCollection;
use Cake\Http\MiddlewareQueue;
use Cake\Routing\RouteBuilder;
return $middleware;
}
/**
* @inheritDoc
*/
public function console(CommandCollection $commands): CommandCollection
{
// Add console commands here.
$commands = parent::console($commands);
return $commands;
}
/**
* @inheritDoc
*/
public function bootstrap(PluginApplicationInterface $app): void
{
(continues on next page)
/**
* @inheritDoc
*/
public function routes(RouteBuilder $routes): void
{
// Add routes.
// By default will load `config/routes.php` in the plugin.
parent::routes($routes);
}
/**
* Register application container services.
*
* @param \Cake\Core\ContainerInterface $container The Container to update.
* @return void
* @link https://book.cakephp.org/5/en/development/dependency-injection.html
˓→#dependency-injection
*/
public function services(ContainerInterface $container): void
{
// Add your services here
}
}
Plugin Routes
Plugins can provide routes files containing their routes. Each plugin can contain a config/routes.php file. This routes
file can be loaded when the plugin is added, or in the application’s routes file. To create the ContactManager plugin
routes, put the following into plugins/ContactManager/config/routes.php:
<?php
use Cake\Routing\Route\DashedRoute;
$routes->plugin(
'ContactManager',
['path' => '/contact-manager'],
function ($routes) {
$routes->setRouteClass(DashedRoute::class);
The above will connect default routes for your plugin. You can customize this file with more specific routes later on.
You can also load plugin routes in your application’s routes list. Doing this provides you more control on how plugin
routes are loaded and allows you to wrap plugin routes in additional scopes or prefixes:
Plugin Controllers
Controllers for our ContactManager plugin will be stored in plugins/ContactManager/src/Controller/. Since the
main thing we’ll be doing is managing contacts, we’ll need a ContactsController for this plugin.
So, we place our new ContactsController in plugins/ContactManager/src/Controller and it looks like so:
// plugins/ContactManager/src/Controller/ContactsController.php
namespace ContactManager\Controller;
use ContactManager\Controller\AppController;
// plugins/ContactManager/src/Controller/AppController.php
namespace ContactManager\Controller;
A plugin’s AppController can hold controller logic common to all controllers in a plugin but is not required if you
don’t want to use one.
If you want to access what we’ve got going thus far, visit /contact-manager/contacts. You should get a “Missing
Model” error because we don’t have a Contact model defined yet.
If your application includes the default routing CakePHP provides you will be able to access your plugin controllers
using URLs like:
If your application defines routing prefixes, CakePHP’s default routing will also connect routes that use the following
pattern:
/{prefix}/{plugin}/{controller}
/{prefix}/{plugin}/{controller}/{action}
See the section on Plugin Hook Configuration for information on how to load plugin specific route files.
Plugin Models
Models for the plugin are stored in plugins/ContactManager/src/Model. We’ve already defined a ContactsController
for this plugin, so let’s create the table and entity for that controller:
// plugins/ContactManager/src/Model/Entity/Contact.php:
namespace ContactManager\Model\Entity;
use Cake\ORM\Entity;
// plugins/ContactManager/src/Model/Table/ContactsTable.php:
namespace ContactManager\Model\Table;
use Cake\ORM\Table;
If you need to reference a model within your plugin when building associations or defining entity classes, you need to
include the plugin name with the class name, separated with a dot. For example:
// plugins/ContactManager/src/Model/Table/ContactsTable.php:
namespace ContactManager\Model\Table;
use Cake\ORM\Table;
If you would prefer that the array keys for the association not have the plugin prefix on them, use the alternative syntax:
// plugins/ContactManager/src/Model/Table/ContactsTable.php:
namespace ContactManager\Model\Table;
use Cake\ORM\Table;
You can use Cake\ORM\Locator\LocatorAwareTrait to load your plugin tables using the familiar plugin syntax:
$contacts = $this->fetchTable('ContactManager.Contacts');
Plugin Templates
Views behave exactly as they do in normal applications. Just place them in the right folder inside of the
plugins/[PluginName]/templates/ folder. For our ContactManager plugin, we’ll need a view for our
ContactsController::index() action, so let’s include that as well:
// plugins/ContactManager/templates/Contacts/index.php:
<h1>Contacts</h1>
<p>Following is a sortable list of your contacts</p>
<!-- A sortable list of contacts would go here....-->
Plugins can provide their own layouts. To add plugin layouts, place your template files inside plugins/
[PluginName]/templates/layout. To use a plugin layout in your controller you can do the following:
$this->viewBuilder()->setLayout('ContactManager.admin');
If the plugin prefix is omitted, the layout/view file will be located normally.
Note: For information on how to use elements from a plugin, look up Elements
You can override any plugin views from inside your app using special paths. If you have a plugin called ‘ContactMan-
ager’ you can override the template files of the plugin with application specific view logic by creating files using the
following template templates/plugin/[Plugin]/[Controller]/[view].php. For the Contacts controller you could make
the following file:
templates/plugin/ContactManager/Contacts/index.php
templates/plugin/TheVendor/ThePlugin/Custom/index.php
templates/plugin/ContactManager/Admin/ContactManager/index.php
Plugin Assets
A plugin’s web assets (but not PHP files) can be served through the plugin’s webroot directory, just like the main
application’s assets:
/plugins/ContactManager/webroot/
css/
js/
img/
flash/
pdf/
You may put any type of file in any directory, just like a regular webroot.
Warning: Handling static assets (such as images, JavaScript and CSS files) through the Dispatcher is very ineffi-
cient. See Improve Your Application’s Performance for more information.
You can use the plugin syntax when linking to plugin assets using the HtmlHelper’s script, image, or css methods:
Plugin assets are served using the AssetMiddleware middleware by default. This is only recommended for develop-
ment. In production you should symlink plugin assets to improve performance.
If you are not using the helpers, you can prepend /plugin-name/ to the beginning of the URL for an as-
set within that plugin to serve it. Linking to ‘/contact_manager/js/some_file.js’ would serve the asset plug-
ins/ContactManager/webroot/js/some_file.js.
A plugin can have Components, Helpers and Behaviors just like a CakePHP application. You can even create plugins
that consist only of Components, Helpers or Behaviors which can be a great way to build reusable components that can
be dropped into any project.
Building these components is exactly the same as building it within a regular application, with no special naming
convention.
Referring to your component from inside or outside of your plugin requires only that you prefix the plugin name before
the name of the component. For example:
use Cake\Controller\Component;
Commands
Plugins can register their commands inside the console() hook. By default all console commands in the plugin are
auto-discovered and added to the application’s command list. Plugin commands are prefixed with the plugin name. For
example, the UserCommand provided by the ContactManager plugin would be registered as both contact_manager.
user and user. The un-prefixed name will only be taken by a plugin if it is not used by the application, or another
plugin.
You can customize the command names by defining each command in your plugin:
return $commands;
}
If you are testing controllers or generating URLs, make sure your plugin connects routes tests/bootstrap.php.
For more information see testing plugins page.
CakePHP plugins should be published to the packagist150 . This way other people can use it as composer dependency.
You can also propose your plugin to the awesome-cakephp list151 .
Choose a semantically meaningful name for the package name. This should ideally be prefixed with the dependency,
in this case “cakephp” as the framework. The vendor name will usually be your GitHub username. Do not use the
CakePHP namespace (cakephp) as this is reserved to CakePHP owned plugins. The convention is to use lowercase
letters and dashes as separator.
So if you created a plugin “Logging” with your GitHub account “FooBar”, a good name would be foo-bar/cakephp-
logging. And the CakePHP owned “Localized” plugin can be found under cakephp/localized respectively.
When installing plugins via Composer, you may notice that vendor/cakephp-plugins.php is created. This configu-
ration file contains a map of plugin names and their paths on the filesystem. It makes it possible for plugins to be
installed into the standard vendor directory which is outside of the normal search paths. The Plugin class will use this
file to locate plugins when they are loaded with addPlugin(). You generally won’t need to edit this file by hand, as
Composer and the plugin-installer package will manage it for you.
Another way to discover and manage plugins into your CakePHP application is Mixer152 . It is a CakePHP plugin which
helps you to install plugins from Packagist. It also helps you to manage your existing plugins.
150 https://packagist.org
151 https://github.com/FriendsOfCake/awesome-cakephp
152 https://github.com/CakeDC/mixer
REST
REST is a foundational concept to the open web. CakePHP provides functionality to build applications that expose
REST APIs with low complexity abstractions and interfaces.
CakePHP provides methods for exposing your controller actions via HTTP methods, and serializing view variables
based on content-type negotiation. Content-Type negotiation allows clients of your application to send requests with
serialize data and receive responses with serialized data via the Accept and Content-Type headers, or URL exten-
sions.
Getting Started
To get started with adding a REST API to your application, we’ll first need a controller containing actions that we want
to expose as an API. A basic controller might look something like this:
// src/Controller/RecipesController.php
use Cake\View\JsonView;
649
CakePHP Book, Release 5.x
In our RecipesController, we have several actions that define the logic to create, edit, view and delete recipes. In
each of our actions we’re using the serialize option to tell CakePHP which view variables should be serialized when
making API responses. We’ll connect our controller to the application URLs with RESTful Routing:
// in config/routes.php
$routes->scope('/', function (RouteBuilder $routes): void {
$routes->setExtensions(['json']);
$routes->resources('Recipes');
});
These routes will enable URLs like /recipes.json to return a JSON encoded response. Clients could also make a
request to /recipes with the Content-Type: application/json header as well.
In the above controller, we’re defining a viewClasses() method. This method defines which views your controller
has available for content-negotitation. We’re including CakePHP’s JsonView which enables JSON based responses.
To learn more about it and Xml based views see JSON and XML views. is used by CakePHP to select a view class to
render a REST response with.
Next, we have several methods that expose basic logic to create, edit, view and delete recipes. In each of our ac-
tions we’re using the serialize option to tell CakePHP which view variables should be serialized when making API
responses.
If we wanted to modify the data before it is converted into JSON we should not define the serialize option, and instead
use template files. We would place the REST templates for our RecipesController inside templates/Recipes/json.
See the Content Type Negotiation for more information on how CakePHP’s response negotiation functionality.
Creating the logic for the edit action requires another step. Because our resources are serialized as JSON it would be
ergonomic if our requests also contained the JSON representation.
In our Application class ensure the following is present:
$middlewareQueue->add(new BodyParserMiddleware());
This middleware will use the content-type header to detect the format of request data and parse enabled formats. By
default only JSON parsing is enabled by default. You can enable XML support by enabling the xml constructor option.
When a request is made with a Content-Type of application/json, CakePHP will decode the request data and
update the request so that $request->getData() contains the parsed body.
You can also wire in additional deserializers for alternate formats if you need them, using
BodyParserMiddleware::addParser().
Security
CakePHP provides you some tools to secure your application. The following sections cover those tools:
Security Utility
class Cake\Utility\Security
The security library153 handles basic security measures such as providing methods for hashing and encrypting data.
Encrypt $text using AES-256. The $key should be a value with a lots of variance in the data much like a good
password. The returned result will be the encrypted value with an HMAC checksum.
The openssl154 extension is required for encrypting/decrypting.
An example use would be:
153 https://api.cakephp.org/5.x/class-Cake.Utility.Security.html
154 https://php.net/openssl
653
CakePHP Book, Release 5.x
If you do not supply an HMAC salt, the value of Security::getSalt() will be used. Encrypted values can be
decrypted using Cake\Utility\Security::decrypt().
This method should never be used to store passwords.
Decrypt a previously encrypted value. The $key and $hmacSalt parameters must match the values used to encrypt or
decryption will fail. An example use would be:
$cipher = $user->secrets;
$result = Security::decrypt($cipher, $key);
If the value cannot be decrypted due to changes in the key or HMAC salt false will be returned.
Hashing Data
Create a hash from string using given method. Fallback on next available method. If $salt is set to true, the appli-
cation’s salt value will be used:
Warning: You should not be using hash() for passwords in new applications. Instead you should use the
DefaultPasswordHasher class which uses bcrypt by default.
static Cake\Utility\Security::randomBytes($length)
Get $length number of bytes from a secure random source. This function draws data from one of the following
sources:
• PHP’s random_bytes function.
• openssl_random_pseudo_bytes from the SSL extension.
If neither source is available a warning will be emitted and an unsafe value will be used for backwards compatibility
reasons.
static Cake\Utility\Security::randomString($length)
Get a random string $length long from a secure random source. This method draws from the same random source as
randomBytes() and will encode the data as a hexadecimal string.
CSRF Protection
Cross-Site Request Forgeries (CSRF) are a class of exploit where unauthorized commands are performed on behalf of
an authenticated user without their knowledge or consent.
CakePHP offers two forms of CSRF protection:
• SessionCsrfProtectionMiddleware stores CSRF tokens in the session. This requires that your application
opens the session on every request with side-effects. The benefits of session based CSRF tokens is that they are
scoped to a specific user, and only valid for the duration a session is live.
• CsrfProtectionMiddleware stores CSRF tokens in a cookie. Using a cookie allows CSRF checks to be done
without any state on the server. Cookie values are verified for authenticity using an HMAC check. However, due
to their stateless nature, CSRF tokens are re-usable across users and sessions.
Note: You cannot use both of the following approaches together, you must choose only one. If you use both approaches
together, a CSRF token mismatch error will occur on every PUT and POST request
CSRF protection can be applied to your entire application, or to specific routing scopes. By applying a CSRF middle-
ware to your Application middleware stack you protect all the actions in application:
// in src/Application.php
// For Cookie based CSRF tokens.
use Cake\Http\Middleware\CsrfProtectionMiddleware;
$middlewareQueue->add($csrf);
return $middlewareQueue;
}
By applying CSRF protection to routing scopes, you can conditionally apply CSRF to specific groups of routes:
// in src/Application.php
use Cake\Http\Middleware\CsrfProtectionMiddleware;
use Cake\Routing\RouteBuilder;
// in config/routes.php
$routes->scope('/', function (RouteBuilder $routes) {
$routes->applyMiddleware('csrf');
});
$token = $this->request->getAttribute('csrfToken');
Should you need to rotate or replace the session CSRF token you can do so with:
$this->request = SessionCsrfProtectionMiddleware::replaceToken($this->request);
Both CSRF middleware implementations allow you to the skip check callback feature for more fine grained control
over URLs for which CSRF token check should be done:
// in src/Application.php
use Cake\Http\Middleware\CsrfProtectionMiddleware;
// Ensure routing middleware is added to the queue before CSRF protection middleware.
$middlewareQueue->add($csrf);
return $middlewareQueue;
}
Note: You should apply the CSRF protection middleware only for routes which handle stateful requests using cook-
ies/sessions. For example, when developing an API, stateless requests that do not use cookies for authentication are
not affected by CSRF so the middleware does not need to be applied for those routes.
The CsrfProtectionMiddleware 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 CSRF protection 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.
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.
The CSRF Token can be obtained in JavaScript via the Cookie csrfToken, or in PHP via the request object attribute
named csrfToken. Using the cookie might be easier when your JavaScript code resides in files separate from the
CakePHP view templates, and when you already have functionality for parsing cookies via JavaScript.
If you have separate JavaScript files but don’t want to deal with handling cookies, you could for example set the token
in a global JavaScript variable in your layout, by defining a script block like this:
echo $this->Html->scriptBlock(sprintf(
'var csrfToken = %s;',
json_encode($this->request->getAttribute('csrfToken'))
));
You can then access the token as csrfToken or window.csrfToken in any script file that is loaded after this script
block.
Another alternative would be to put the token in a custom meta tag like this:
which could then be accessed in your scripts by looking for the meta element with the name csrfToken, which could
be as simple as this when using jQuery:
The CspMiddleware makes it simpler to add Content-Security-Policy headers in your application. Before using it you
should install paragonie/csp-builder:
You can then configure the middleware using an array, or passing in a built CSPBuilder object:
use Cake\Http\Middleware\CspMiddleware;
$middlewareQueue->add($csp);
If you want to use a more strict CSP configuration, you can enable nonce based CSP rules with the scriptNonce
and styleNonce options. When enabled these options will modify your CSP policy and set the cspScriptNonce
and cspStyleNonce attributes in the request. These attributes are applied to the nonce attribute of all script and
CSS link elements created by HtmlHelper. This simplifies the adoption of policies that use a nonce-base64155 and
strict-dynamic for increased security and easier maintenance:
$policy = [
// Must exist even if empty to set nonce for for script-src
'script-src' => [],
'style-src' => [],
];
// Enable automatic nonce addition to script & CSS link tags.
$csp = new CspMiddleware($policy, [
'scriptNonce' => true,
'styleNonce' => true,
]);
$middlewareQueue->add($csp);
The SecurityHeaderMiddleware layer allows you to apply security related headers to your application. Once setup
the middleware can apply the following headers to responses:
• X-Content-Type-Options
• X-Download-Options
• X-Frame-Options
• Referrer-Policy
This middleware is configured using a fluent interface before it is applied to your application’s middleware stack:
use Cake\Http\Middleware\SecurityHeadersMiddleware;
$middlewareQueue->add($securityHeaders);
Here’s a list of common HTTP headers156 , and the Mozilla recommended settings157 for securing web applications.
If you want your application to only be available via HTTPS connections you can use the HttpsEnforcerMiddleware:
use Cake\Http\Middleware\HttpsEnforcerMiddleware;
If a non-HTTP request is received that does not use GET a BadRequestException will be raised.
NOTE: The Strict-Transport-Security header is ignored by the browser when your site has only been accessed using
HTTP. Once your site is accessed over HTTPS with no certificate errors, the browser knows your site is HTTPS capable
and will honor the Strict-Transport-Security header.
156 https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
157 https://infosec.mozilla.org/guidelines/web_security.html
Adding Strict-Transport-Security
When your application requires SSL it is a good idea to set the Strict-Transport-Security header. This header
value is cached in the browser, and informs browsers that they should always connect with HTTPS connections. You
can configure this header with the hsts option:
Sessions
CakePHP provides a wrapper and suite of utility features on top of PHP’s native session extension. Sessions allow
you to identify unique users across requests and store persistent data for specific users. Unlike Cookies, session data
is not available on the client side. Usage of $_SESSION is generally avoided in CakePHP, and instead usage of the
Session classes is preferred.
Session Configuration
663
CakePHP Book, Release 5.x
Configure::write('Session', [
'defaults' => 'php',
'ini' => [
'session.cookie_secure' => false
]
]);
CakePHP also sets the SameSite158 attribute to Lax by default for session cookies, which helps protect against CSRF
attacks. You can change the default value by setting session.cookie_samesite php.ini config:
Configure::write('Session', [
'defaults' => 'php',
'ini' => [
'session.cookie_samesite' => 'Strict',
],
]);
The session cookie path defaults to app’s base path. To change this you can use the session.cookie_path ini value.
For example if you want your session to persist across all subdomains you can do:
Configure::write('Session', [
'defaults' => 'php',
'ini' => [
'session.cookie_path' => '/',
'session.cookie_domain' => '.yourdomain.com',
],
]);
By default PHP sets the session cookie to expire as soon as the browser is closed, regardless of the configured Session.
timeout value. The cookie timeout is controlled by the session.cookie_lifetime ini value and can be configured
using:
Configure::write('Session', [
'defaults' => 'php',
'ini' => [
// Invalidate the cookie after 30 minutes
'session.cookie_lifetime' => 1800
]
]);
The difference between Session.timeout and the session.cookie_lifetime value is that the latter relies on the
client telling the truth about the cookie. If you require stricter timeout checking, without relying on what the client
reports, you should use Session.timeout.
Please note that Session.timeout corresponds to the total time of inactivity for a user (i.e. the time without visiting
any page where the session is used), and does not limit the total amount of minutes a user can stay active on the site.
158 https://owasp.org/www-community/SameSite
CakePHP comes with several built-in session configurations. You can either use these as the basis for your session
configuration, or you can create a fully custom solution. To use defaults, simply set the ‘defaults’ key to the name of
the default you want to use. You can then override any sub setting by declaring it in your Session config:
Configure::write('Session', [
'defaults' => 'php'
]);
The above will use the built-in ‘php’ session configuration. You could augment part or all of it by doing the following:
Configure::write('Session', [
'defaults' => 'php',
'cookie' => 'my_app',
'timeout' => 4320 // 3 days
]);
The above overrides the timeout and cookie name for the ‘php’ session configuration. The built-in configurations are:
• php - Saves sessions with the standard settings in your php.ini file.
• cake - Saves sessions as files inside tmp/sessions. This is a good option when on hosts that don’t allow you
to write outside your own home dir.
• database - Use the built-in database sessions. See below for more information.
• cache - Use the built-in cache sessions. See below for more information.
Session Handlers
Session handlers can also be defined in the session config array. By defining the ‘handler.engine’ config key,
you can name the class name, or provide a handler instance. The class/object must implement the native PHP
SessionHandlerInterface. Implementing this interface will allow Session to automatically map the methods
for the handler. Both the core Cache and Database session handlers use this method for saving sessions. Additional
settings for the handler should be placed inside the handler array. You can then read those values out from inside your
handler:
'Session' => [
'handler' => [
'engine' => 'DatabaseSession',
'model' => 'CustomSessions',
],
]
The above shows how you could setup the Database session handler with an application model. When using class
names as your handler.engine, CakePHP will expect to find your class in the Http\Session namespace. For example,
if you had an AppSessionHandler class, the file should be src/Http/Session/AppSessionHandler.php, and the class
name should be App\Http\Session\AppSessionHandler. You can also use session handlers from inside plugins.
By setting the engine to MyPlugin.PluginSessionHandler.
Database Sessions
If you need to use a database to store your session data, configure as follows:
'Session' => [
'defaults' => 'database'
]
You can find a copy of the schema for the sessions table in the application skeleton159 in config/schema/sessions.sql.
You can also use your own Table class to handle the saving of the sessions:
'Session' => [
'defaults' => 'database',
'handler' => [
'engine' => 'DatabaseSession',
'model' => 'CustomSessions',
],
]
The above will tell Session to use the built-in ‘database’ defaults, and specify that a Table called CustomSessions
will be the delegate for saving session information to the database.
Cache Sessions
The Cache class can be used to store sessions as well. This allows you to store sessions in a cache like APCu, or
Memcached. There are some caveats to using cache sessions, in that if you exhaust the cache space, sessions will start
to expire as records are evicted.
To use Cache based sessions you can configure you Session config like:
Configure::write('Session', [
'defaults' => 'cache',
'handler' => [
'config' => 'session',
],
]);
This will configure Session to use the CacheSession class as the delegate for saving the sessions. You can use the
‘config’ key which cache configuration to use. The default cache configuration is 'default'.
159 https://github.com/cakephp/app
Session Locking
The app skeleton comes preconfigured with a session config like this:
'Session' => [
'defaults' => 'php',
],
This means CakePHP will handle sessions via what is configured in your php.ini. In most cases this will be the
default configuration so PHP will save any newly created session as a file in e.g. /var/lib/php/session
But this also means any computationally heavy task like querying a large dataset combined with an active session will
lock that session file - therefore blocking users to e.g. open a second tab of your app to do something else in the
meantime.
To prevent this behavior you will have to change the way how sessions are being handled in CakePHP by using a
different session handler like Cache Sessions combined with the Redis Engine or another cache engine.
Tip: If you want to read more about Session Locking see here160
The built-in defaults attempt to provide a common base for session configuration. You may need to tweak specific
ini flags as well. CakePHP exposes the ability to customize the ini settings for both default configurations, as well as
custom ones. The ini key in the session settings, allows you to specify individual configuration values. For example
you can use it to control settings like session.gc_divisor:
Configure::write('Session', [
'defaults' => 'php',
'ini' => [
'session.cookie_name' => 'MyCookie',
'session.cookie_lifetime' => 1800, // Valid for 30 minutes
'session.gc_divisor' => 1000,
'session.cookie_httponly' => true
]
]);
Creating a custom session handler is straightforward in CakePHP. In this example we’ll create a session handler that
stores sessions both in the Cache (APC) and the database. This gives us the best of fast IO of APC, without having to
worry about sessions evaporating when the cache fills up.
First we’ll need to create our custom class and put it in src/Http/Session/ComboSession.php. The class should look
something like:
namespace App\Http\Session;
return parent::read($id);
}
// Destroy a session.
public function destroy($id): bool
{
Cache::delete($id, $this->cacheKey);
return parent::destroy($id);
}
Our class extends the built-in DatabaseSession so we don’t have to duplicate all of its logic and behavior. We wrap
each operation with a Cake\Cache\Cache operation. This lets us fetch sessions from the fast cache, and not have to
worry about what happens when we fill the cache. In config/app.php make the session block look like:
'Session' => [
'defaults' => 'database',
'handler' => [
'engine' => 'ComboSession',
'model' => 'Session',
'cache' => 'apc',
],
],
// Make sure to add a apc cache config
'Cache' => [
'apc' => ['engine' => 'Apc']
]
Now our application will start using our custom session handler for reading and writing session data.
class Session
You can access the session data any place you have access to a request object. This means the session is accessible
from:
• Controllers
• Views
• Helpers
• Cells
• Components
A basic example of session usage in controllers, views and cells would be:
$name = $this->request->getSession()->read('User.name');
You can read values from the session using Hash::extract() compatible syntax:
$session->read('Config.language', 'en');
Session::readOrFail($key)
$session->readOrFail('Config.language');
This is useful, when you know this key has to be set and you don’t want to have to check for the existence in code itself.
Session::write($key, $value)
$key should be the dot separated path you wish to write $value to:
$session->write('Config.language', 'en');
$session->write([
'Config.theme' => 'blue',
'Config.language' => 'en',
]);
Session::delete($key)
When you need to delete data from the session, you can use delete():
$session->delete('Some.value');
static Session::consume($key)
When you need to read and delete data from the session, you can use consume():
$session->consume('Some.value');
Session::check($key)
If you want to see if data exists in the session, you can use check():
if ($session->check('Config.language')) {
// Config.language exists and is not null.
}
Session::destroy()
Destroying the session is useful when users log out. To destroy a session, use the destroy() method:
$session->destroy();
Destroying a session will remove all serverside data in the session, but will not remove the session cookie.
Session::renew()
While the Authentication Plugin automatically renews the session id when users login and logout, you may need
to rotate the session id’s manually. To do this use the renew() method:
$session->renew();
Flash Messages
Flash messages are small messages displayed to end users once. They are often used to present error messages, or
confirm that actions took place successfully.
To set and display flash messages you should use FlashComponent and FlashHelper
Testing
CakePHP comes with comprehensive testing support built-in. CakePHP comes with integration for PHPUnit161 . In
addition to the features offered by PHPUnit, CakePHP offers some additional features to make testing easier. This
section will cover installing PHPUnit, and getting started with Unit Testing, and how you can use the extensions that
CakePHP offers.
Installing PHPUnit
CakePHP uses PHPUnit as its underlying test framework. PHPUnit is the de-facto standard for unit testing in PHP.
It offers a deep and powerful set of features for making sure your code does what you think it does. PHPUnit can be
installed through using either a PHAR package162 or Composer163 .
This will add the dependency to the require-dev section of your composer.json, and then install PHPUnit along
with any dependencies.
You can now run PHPUnit using:
$ vendor/bin/phpunit
161 https://phpunit.de
162 https://phpunit.de/#download
163 https://getcomposer.org
673
CakePHP Book, Release 5.x
After you have downloaded the phpunit.phar file, you can use it to run your tests:
php phpunit.phar
Tip: As a convenience you can make phpunit.phar available globally on Unix or Linux with the following:
chmod +x phpunit.phar
sudo mv phpunit.phar /usr/local/bin/phpunit
phpunit --version
Please refer to the PHPUnit documentation for instructions regarding Globally installing the PHPUnit PHAR on Win-
dows164 .
Remember to have debug enabled in your config/app_local.php file before running any tests. Before running any tests
you should be sure to add a test datasource configuration to config/app_local.php. This configuration is used by
CakePHP for fixture tables and data:
'Datasources' => [
'test' => [
'datasource' => 'Cake\Database\Driver\Mysql',
'persistent' => false,
'host' => 'dbhost',
'username' => 'dblogin',
'password' => 'dbpassword',
'database' => 'test_database',
],
],
Note: It’s a good idea to make the test database and your actual database different databases. This will prevent
embarrassing mistakes later.
After installing PHPUnit and setting up your test datasource configuration you can make sure you’re ready to write
and run your own tests by running your application’s tests:
# For phpunit.phar
$ php phpunit.phar
164 https://phpunit.de/manual/current/en/installation.html#installation.phar.windows
The above should run any tests you have, or let you know that no tests were run. To run a specific test you can supply
the path to the test as a parameter to PHPUnit. For example, if you had a test case for ArticlesTable class you could run
it with:
$ vendor/bin/phpunit tests/TestCase/Model/Table/ArticlesTableTest
You should see a green bar with some additional information about the tests run, and number passed.
Note: If you are on a Windows system you probably won’t see any colors.
Like most things in CakePHP, test cases have some conventions. Concerning tests:
1. PHP files containing tests should be in your tests/TestCase/[Type] directories.
2. The filenames of these files should end in Test.php instead of just .php.
3. The classes containing tests should extend Cake\TestSuite\TestCase, Cake\TestSuite\
IntegrationTestCase or \PHPUnit\Framework\TestCase.
4. Like other classnames, the test case classnames should match the filename. RouterTest.php should contain
class RouterTest extends TestCase.
5. The name of any method containing a test (i.e. containing an assertion) should begin with test, as in
testPublished(). You can also use the @test annotation to mark methods as test methods.
In the following example, we’ll create a test case for a very simple helper method. The helper we’re going to test will
be formatting progress bar HTML. Our helper looks like:
namespace App\View\Helper;
use Cake\View\Helper;
return sprintf(
'<div class="progress-container">
<div class="progress-bar" style="width: %s%%"></div>
</div>', $width);
}
}
This is a very simple example, but it will be useful to show how you can create a simple test case. After creating and
saving our helper, we’ll create the test case file in tests/TestCase/View/Helper/ProgressHelperTest.php. In that file
we’ll start with the following:
namespace App\Test\TestCase\View\Helper;
use App\View\Helper\ProgressHelper;
use Cake\TestSuite\TestCase;
use Cake\View\View;
We’ll flesh out this skeleton in a minute. We’ve added two methods to start with. First is setUp(). This method is
called before every test method in a test case class. Setup methods should initialize the objects needed for the test, and
do any configuration needed. In our setup method we’ll add the following:
Calling the parent method is important in test cases, as TestCase::setUp() does a number things like backing up
the values in Configure and, storing the paths in App.
Next, we’ll fill out the test method. We’ll use some assertions to ensure that our code creates the output we expect:
$result = $this->Progress->bar(33.3333333);
$this->assertStringContainsString('width: 33%', $result);
}
The above test is a simple one but shows the potential benefit of using test cases. We use
assertStringContainsString() to ensure that our helper is returning a string that contains the content we
expect. If the result did not contain the expected content the test would fail, and we would know that our code is
incorrect.
By using test cases you can describe the relationship between a set of known inputs and their expected output. This
helps you be more confident of the code you’re writing as you can ensure that the code you wrote fulfills the expectations
and assertions your tests make. Additionally because tests are code, they can be re-run whenever you make a change.
This helps prevent the creation of new bugs.
Note: EventManager is refreshed for each test method. This means that when running multiple tests at once, you will
lose your event listeners that were registered in config/bootstrap.php as the bootstrap is only executed once.
Running Tests
Once you have PHPUnit installed and some test cases written, you’ll want to run the test cases very frequently. It’s a
good idea to run tests before committing any changes to help ensure you haven’t broken anything.
By using phpunit you can run your application tests. To run your application’s tests you can simply run:
vendor/bin/phpunit
php phpunit.phar
If you have cloned the CakePHP source from GitHub165 and wish to run CakePHP’s unit-tests don’t forget to execute
the following Composer command prior to running phpunit so that any dependencies are installed:
composer install
From your application’s root directory. To run tests for a plugin that is part of your application source, first cd into the
plugin directory, then use phpunit command that matches how you installed phpunit:
cd plugins
../vendor/bin/phpunit
php ../phpunit.phar
To run tests on a standalone plugin, you should first install the project in a separate directory and install its dependencies:
When you have larger test cases, you will often want to run a subset of the test methods when you are trying to work
on a single failing case. With the CLI runner you can use an option to filter test methods:
The filter parameter is used as a case-sensitive regular expression for filtering which test methods to run.
165 https://github.com/cakephp/cakephp
You can generate code coverage reports from the command line using PHPUnit’s built-in code coverage tools. PHPUnit
will generate a set of static HTML files containing the coverage results. You can generate coverage for a test case by
doing the following:
This will put the coverage results in your application’s webroot directory. You should be able to view the results by
going to http://localhost/your_app/coverage.
You can also use phpdbg to generate coverage instead of xdebug. phpdbg is generally faster at generating coverage:
Often times your application will be composed of several plugins. In these situations it can be pretty tedious to run tests
for each plugin. You can make running tests for each of the plugins that compose your application by adding additional
<testsuite> sections to your application’s phpunit.xml.dist file:
<testsuites>
<testsuite name="app">
<directory>tests/TestCase/</directory>
</testsuite>
Any additional test suites added to the <testsuites> element will automatically be run when you use phpunit.
If you are using <testsuites> to use fixtures from plugins that you have installed with composer, the plugin’s
composer.json file should add the fixture namespace to the autoload section. Example:
"autoload-dev": {
"psr-4": {
"PluginName\\Test\\Fixture\\": "tests/Fixture/"
}
},
Test cases have a number of lifecycle callbacks you can use when doing testing:
• setUp is called before every test method. Should be used to create the objects that are going to be tested, and
initialize any data for the test. Always remember to call parent::setUp()
• tearDown is called after every test method. Should be used to cleanup after the test is complete. Always re-
member to call parent::tearDown().
• setupBeforeClass is called once before test methods in a case are started. This method must be static.
• tearDownAfterClass is called once after test methods in a case are started. This method must be static.
Fixtures
When testing code that depends on models and the database, one can use fixtures as a way to create initial state for your
application’s tests. By using fixture data you can reduce repetitive setup steps in your tests. Fixtures are well suited to
data that is common or shared amongst many or all of your tests. Data that is only needed in a subset of tests should
be created in tests as needed.
CakePHP uses the connection named test in your config/app.php configuration file. If this connection is not usable,
an exception will be raised and you will not be able to use database fixtures.
CakePHP performs the following during the course of a test run:
1. Creates tables for each of the fixtures needed.
2. Populates tables with data.
3. Runs test methods.
4. Empties the fixture tables.
The schema for fixtures is created at the beginning of a test run via migrations or a SQL dump file.
Test Connections
By default CakePHP will alias each connection in your application. Each connection defined in your application’s boot-
strap that does not start with test_ will have a test_ prefixed alias created. Aliasing connections ensures, you don’t
accidentally use the wrong connection in test cases. Connection aliasing is transparent to the rest of your application.
For example if you use the ‘default’ connection, instead you will get the test connection in test cases. If you use the
‘replica’ connection, the test suite will attempt to use ‘test_replica’.
PHPUnit Configuration
Before you can use fixtures you should double check that your phpunit.xml contains the fixture extension:
The extension is included in your application and plugins generated by bake by default.
You can generate test database schema either via CakePHP’s migrations, loading a SQL dump file or using another
external schema management tool. You should create your schema in your application’s tests/bootstrap.php file.
If you use CakePHP’s migrations plugin to manage your application’s schema, you can reuse those migrations to
generate your test database schema as well:
// in tests/bootstrap.php
use Migrations\TestSuite\Migrator;
If you need to run multiple sets of migrations, those can be run as follows:
$migrator->runMany([
// Run app migrations on test connection.
['connection' => 'test'],
// Run Contacts migrations on test connection.
['plugin' => 'Contacts'],
// Run Documents migrations on test_docs connection.
['plugin' => 'Documents', 'connection' => 'test_docs']
]);
Using runMany() will ensure that plugins that share a database don’t drop tables as each set of migrations is run.
The migrations plugin will only run unapplied migrations, and will reset migrations if your current migration head
differs from the applied migrations.
You can also configure how migrations should be run in tests in your datasources configuration. See the migrations
docs for more information.
For plugins that need to define schema in tests, but don’t need or want to have dependencies on migrations, you can
define schema as a structured array of tables. This format is not recommended for application development as it can
be time-consuming to maintain.
Each table can define columns, constraints, and indexes. An example table would be:
return [
'articles' => [
(continues on next page)
The options available to columns, indexes and constraints match the attributes that are available in CakePHP’s
schema reflection APIs. Tables are created incrementally and you must take care to ensure that tables are created before
foreign key references are made. Once you have created your schema file you can load it in your tests/bootstrap.
php with:
// in tests/bootstrap.php
use Cake\TestSuite\Fixture\SchemaLoader;
At the beginning of each test run SchemaLoader will drop all tables in the connection and rebuild tables based on the
provided schema file.
Fixtures 681
CakePHP Book, Release 5.x
By default CakePHP resets fixture state at the end of each test by truncating all the tables in the database. This operation
can become expensive as your application grows. By using TransactionStrategy each test method will be run inside
a transaction that is rolled back at the end of the test. This can yield improved performance but requires your tests not
heavily rely on static fixture data, as auto-increment values are not reset before each test.
The fixture state management strategy can be defined within the test case:
use Cake\TestSuite\TestCase;
use Cake\TestSuite\Fixture\FixtureStrategyInterface;
use Cake\TestSuite\Fixture\TransactionStrategy;
Creating Fixtures
Fixtures defines the records that will be inserted into the test database at the beginning of each test. Let’s create our first
fixture, that will be used to test our own Article model. Create a file named ArticlesFixture.php in your tests/Fixture
directory, with the following content:
namespace App\Test\Fixture;
use Cake\TestSuite\Fixture\TestFixture;
public $records = [
[
'title' => 'First Article',
'body' => 'First Article Body',
'published' => '1',
'created' => '2007-03-18 10:39:23',
'modified' => '2007-03-18 10:41:31'
],
[
'title' => 'Second Article',
'body' => 'Second Article Body',
'published' => '1',
'created' => '2007-03-18 10:41:23',
(continues on next page)
Note: It is recommended to not manually add values to auto incremental columns, as it interferes with the sequence
generation in PostgreSQL and SQLServer.
The $connection property defines the datasource of which the fixture will use. If your application uses multiple
datasources, you should make the fixtures match the model’s datasources but prefixed with test_. For example if your
model uses the mydb datasource, your fixture should use the test_mydb datasource. If the test_mydb connection
doesn’t exist, your models will use the default test datasource. Fixture datasources must be prefixed with test to
reduce the possibility of accidentally truncating all your application’s data when running tests.
We can define a set of records that will be populated after the fixture table is created. The format is fairly straight
forward, $records is an array of records. Each item in $records should be a single row. Inside each row, should be
an associative array of the columns and values for the row. Just keep in mind that each record in the $records array
must have the same keys as rows are bulk inserted.
Dynamic Data
To use functions or other dynamic data in your fixture records you can define your records in the fixture’s init()
method:
namespace App\Test\Fixture;
use Cake\TestSuite\Fixture\TestFixture;
Fixtures 683
CakePHP Book, Release 5.x
After you’ve created your fixtures, you’ll want to use them in your test cases. In each test case you should load the
fixtures you will need. You should load a fixture for every model that will have a query run against it. To load fixtures
you define the $fixtures property in your model:
As of 4.1.0 you can use getFixtures() to define your fixture list with a method:
The above will load the Article and Comment fixtures from the application’s Fixture directory. You can also load
fixtures from CakePHP core, or plugins:
Using the core prefix will load fixtures from CakePHP, and using a plugin name as the prefix, will load the fixture
from the named plugin.
You can load fixtures in subdirectories. Using multiple directories can make it easier to organize your fixtures if you
have a larger application. To load fixtures in subdirectories, simply include the subdirectory name in the fixture name:
Fixture Factories
As your application grows, so does the number and the size of your test fixtures. You might find it difficult to main-
tain them and to keep track of their content. The fixture factories plugin166 proposes an alternative for large sized
applications.
The plugin uses the test suite light plugin167 in order to truncate all dirty tables before each test.
The following command will help you bake your factories:
Once your factories are tuned168 , you are ready to create test fixtures in no time.
Unnecessary interaction with the database will slow down your tests as well as your application. You can create test
fixtures without persisting them which can be useful for testing methods without DB interaction:
$article = ArticleFactory::make()->getEntity();
In order to persist:
$article = ArticleFactory::make()->persist();
The factories help creating associated fixtures too. Assuming that articles belongs to many authors, we can now, for
example, create 5 articles each with 2 authors:
Note that the fixture factories do not require any fixture creation or declaration. Still, they are fully compatible with the
fixtures that come with cakephp. You will find additional insights and documentation here169 .
If you are testing mailers, controller components or other classes that require routes and resolving URLs, you will need
to load routes. During the setUp() of a class or during individual test methods you can use loadRoutes() to ensure
your application routes are loaded:
This method will build an instance of your Application and call the routes() method on it. If your Application
class requires specialized constructor parameters you can provide those to loadRoutes($constructorArgs).
166 https://github.com/vierge-noire/cakephp-fixture-factories
167 https://github.com/vierge-noire/cakephp-test-suite-light
168 https://github.com/vierge-noire/cakephp-fixture-factories/blob/main/docs/factories.md
169 https://github.com/vierge-noire/cakephp-fixture-factories
Sometimes it may be be necessary to dynamically add routes in tests, for example when developing plugins, or appli-
cations that are extensible.
Just like loading existing application routes, this can be done during setup() of a test method, and/or in the individual
test methods themselves:
use Cake\Routing\Route\DashedRoute;
use Cake\Routing\RouteBuilder;
use Cake\Routing\Router;
use Cake\TestSuite\TestCase;
$this->routeBuilder = Router::createRouteBuilder('/');
$this->routeBuilder->scope('/', function (RouteBuilder $routes) {
$routes->setRouteClass(DashedRoute::class);
$routes->get(
'/test/view/{id}',
['controller' => 'Tests', 'action' => 'view']
);
// ...
});
// ...
}
}
This will create a new route builder instance that will merge connected routes into the same route collection used by
all other route builder instances that may already exist, or are yet to be created in the environment.
If your application would dynamically load plugins, you can use loadPlugins() to load one or more plugins during
tests:
Let’s say we already have our Articles Table class defined in src/Model/Table/ArticlesTable.php, and it looks like:
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\ORM\Query\SelectQuery;
return $query;
}
}
We now want to set up a test that will test this table class. Let’s now create a file named ArticlesTableTest.php in your
tests/TestCase/Model/Table directory, with the following contents:
namespace App\Test\TestCase\Model\Table;
use App\Model\Table\ArticlesTable;
use Cake\TestSuite\TestCase;
In our test cases’ variable $fixtures we define the set of fixtures that we’ll use. You should remember to include all
the fixtures that will have queries run against them.
Let’s now add a method to test the function published() in the Articles table. Edit the file
tests/TestCase/Model/Table/ArticlesTableTest.php so it now looks like this:
namespace App\Test\TestCase\Model\Table;
use App\Model\Table\ArticlesTable;
use Cake\TestSuite\TestCase;
$this->assertEquals($expected, $result);
}
}
You can see we have added a method called testFindPublished(). We start by creating an instance of our
ArticlesTable class, and then run our find('published') method. In $expected we set what we expect should
be the proper result (that we know since we have defined which records are initially populated to the article table.) We
test that the result equals our expectation by using the assertEquals() method. See the Running Tests section for
more information on how to run your test case.
Using the fixture factories, the test would now look like this:
namespace App\Test\TestCase\Model\Table;
use App\Test\Factory\ArticleFactory;
use Cake\TestSuite\TestCase;
$result = ArticleFactory::find('published')->find('list')->toArray();
$expected = [
$articles[0]->id => $articles[0]->title,
$articles[1]->id => $articles[1]->title,
$articles[2]->id => $articles[2]->title,
];
$this->assertEquals($expected, $result);
}
}
No fixtures need to be loaded. The 5 articles created will exist only in this test. The static method ::find() will query
the database without using the table ArticlesTable and it’s events.
There will be times you’ll want to mock methods on models when testing them. You should use getMockForModel
to create testing mocks of table classes. It avoids issues with reflected properties that normal mocks have:
$model->verifyEmail('[email protected]');
}
$this->getTableLocator()->clear();
While you can test controller classes in a similar fashion to Helpers, Models, and Components, CakePHP offers a
specialized IntegrationTestTrait trait. Using this trait in your controller test cases allows you to test controllers
from a high level.
If you are unfamiliar with integration testing, it is a testing approach that allows you to test multiple units in concert. The
integration testing features in CakePHP simulate an HTTP request being handled by your application. For example,
testing your controller will also exercise any components, models and helpers that would be involved in handling a
given request. This gives you a more high level test of your application and all its working parts.
Say you have a typical ArticlesController, and its corresponding model. The controller code looks like:
namespace App\Controller;
use App\Controller\AppController;
$this->set([
'title' => 'Articles',
'articles' => $result
]);
}
}
Create a file named ArticlesControllerTest.php in your tests/TestCase/Controller directory and put the following
inside:
namespace App\Test\TestCase\Controller;
use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase;
$this->assertResponseOk();
// More asserts.
}
$this->assertResponseOk();
// More asserts.
}
$this->assertResponseOk();
$this->assertResponseContains('Articles');
// More asserts.
}
$this->assertResponseSuccess();
$articles = $this->getTableLocator()->get('Articles');
$query = $articles->find()->where(['title' => $data['title']]);
$this->assertEquals(1, $query->count());
}
}
This example shows a few of the request sending methods and a few of the assertions that IntegrationTestTrait
provides. Before you can do any assertions you’ll need to dispatch a request. You can use one of the following methods
to send a request:
• get() Sends a GET request.
• post() Sends a POST request.
• put() Sends a PUT request.
• delete() Sends a DELETE request.
• patch() Sends a PATCH request.
• options() Sends an OPTIONS request.
• head() Sends a HEAD request.
All of the methods except get() and delete() accept a second parameter that allows you to send a request body.
After dispatching a request you can use the various assertions provided by IntegrationTestTrait or PHPUnit to
ensure your request had the correct side-effects.
The IntegrationTestTrait trait comes with a number of helpers to to configure the requests you will send to your
application under test:
// Set cookies
$this->cookie('name', 'Uncle Bob');
// Configure headers
$this->configRequest([
'headers' => ['Accept' => 'application/json']
]);
The state set by these helper methods is reset in the tearDown() method.
When testing actions protected by either SecurityComponent or CsrfComponent you can enable automatic token gen-
eration to ensure your tests won’t fail due to token mismatches:
It is also important to enable debug in tests that use tokens to prevent the SecurityComponent from thinking the debug
token is being used in a non-debug environment. When testing with other methods like requireSecure() you can
use configRequest() to set the correct environment variables:
If your action requires unlocked fields you can declare them with setUnlockedFields():
$this->setUnlockedFields(['dynamic_field']);
Integration testing can also be used to test your entire PSR-7 application and /controllers/middleware. By default
IntegrationTestTrait will auto-detect the presence of an App\Application class and automatically enable inte-
gration testing of your Application.
You can customize the application class name used, and the constructor arguments, by using the
configApplication() method:
You should also take care to try and use application-bootstrap to load any plugins containing events/routes. Doing so
will ensure that your events/routes are connected for each test case. Alternatively if you wish to load plugins manually
in a test you can use the loadPlugins() method.
If you use the encrypted-cookie-middleware in your application, there are helper methods for setting encrypted cookies
in your test cases:
If you want to assert the presence of flash messages in the session and not the rendered HTML, you can use
enableRetainFlashMessages() in your tests to retain flash messages in the session so you can write assertions:
JSON is a friendly and common format to use when building a web service. Testing the endpoints of your web service
is very simple with CakePHP. Let us begin with a simple example controller that responds in JSON:
use Cake\View\JsonView;
Now we create the file tests/TestCase/Controller/MarkersControllerTest.php and make sure our web service is
returning the proper response:
$expected = [
['id' => 1, 'lng' => 66, 'lat' => 45],
];
$expected = json_encode($expected, JSON_PRETTY_PRINT);
$this->assertEquals($expected, (string)$this->_response->getBody());
}
}
We use the JSON_PRETTY_PRINT option as CakePHP’s built in JsonView will use that option when debug is enabled.
Simulating file uploads is straightforward when you use the default “uploaded files as objects” mode. You can sim-
ply create instances that implement \Psr\Http\Message\UploadedFileInterface170 (the default implementation currently
used by CakePHP is \Laminas\Diactoros\UploadedFile), and pass them in your test request data. In the CLI
environment such objects will by default pass validation checks that test whether the file was uploaded via HTTP. The
same is not true for array style data as found in $_FILES, it would fail that check.
In order to simulate exactly how the uploaded file objects would be present on a regular request, you not only
need to pass them in the request data, but you also need to pass them to the test request configuration via the
files option. It’s not technically necessary though unless your code accesses uploaded files via the Cake\Http\
ServerRequest::getUploadedFile() or Cake\Http\ServerRequest::getUploadedFiles() methods.
Let’s assume articles have a teaser image, and a Articles hasMany Attachments association, the form would look
like something like this accordingly, where one image file, and multiple attachments/files would be accepted:
170 https://www.php-fig.org/psr/psr-7/#16-uploaded-files
The test that would simulate the corresponding request could look like this:
public function testAddWithUploads(): void
{
$teaserImage = new \Laminas\Diactoros\UploadedFile(
'/path/to/test/file.jpg', // stream or path to file representing the temp file
12345, // the filesize in bytes
\UPLOAD_ERR_OK, // the upload/error status
'teaser.jpg', // the filename as sent by the client
'image/jpeg' // the mimetype as sent by the client
);
$this->assertResponseOk();
$this->assertFlashMessage('The article was saved successfully');
$this->assertFileExists('/path/to/uploads/teaser.jpg');
$this->assertFileExists('/path/to/uploads/attachment.txt');
$this->assertFileExists('/path/to/uploads/attachment.pdf');
}
Tip: If you configure the test request with files, then it must match the structure of your POST data (but only include
the uploaded file objects)!
Likewise you can simulate upload errors171 or otherwise invalid files that do not pass validation:
$this->configRequest([
'files' => [
'teaser_image' => $missingTeaserImageUpload,
'attachments' => [
0 => [
'file' => $uploadFailureAttachment,
],
1 => [
'file' => $invalidTypeAttachment,
],
],
],
]);
$postData = [
'title' => 'New Article',
'teaser_image' => $missingTeaserImageUpload,
'attachments' => [
0 => [
'file' => $uploadFailureAttachment,
'description' => 'Upload failure attachment',
],
1 => [
'file' => $invalidTypeAttachment,
'description' => 'Invalid type attachment',
],
],
];
$this->post('/articles/add', $postData);
$this->assertResponseOk();
$this->assertFlashMessage('The article could not be saved');
$this->assertResponseContains('A teaser image is required');
$this->assertResponseContains('Max allowed filesize exceeded');
$this->assertResponseContains('Unsupported file type');
$this->assertFileNotExists('/path/to/uploads/teaser.jpg');
$this->assertFileNotExists('/path/to/uploads/attachment.txt');
$this->assertFileNotExists('/path/to/uploads/attachment.exe');
}
When debugging tests that are failing because your application is encountering errors it can be helpful to
temporarily disable the error handling middleware to allow the underlying error to bubble up. You can use
disableErrorHandlerMiddleware() to do this:
In the above example, the test would fail and the underlying exception message and stack trace would be displayed
instead of the rendered error page being checked.
Assertion methods
The IntegrationTestTrait trait provides a number of assertion methods that make testing responses much simpler.
Some examples are:
// Assert layout
$this->assertLayout('default');
In addition to the above assertion methods, you can also use all of the assertions in TestSuite172 and those found in
PHPUnit173 .
172 https://api.cakephp.org/5.x/class-Cake.TestSuite.TestCase.html
173 https://phpunit.de/manual/current/en/appendixes.assertions.html
For some types of test, it may be easier to compare the result of a test to the contents of a file - for example, when
testing the rendered output of a view. The StringCompareTrait adds a simple assert method for this purpose.
Usage involves using the trait, setting the comparison base path and calling assertSameAsFile:
use Cake\TestSuite\StringCompareTrait;
use Cake\TestSuite\TestCase;
The above example will compare $result to the contents of the file APP/tests/comparisons/example.php.
A mechanism is provided to write/update test files, by setting the environment variable
UPDATE_TEST_COMPARISON_FILES, which will create and/or update test comparison files as they are referenced:
phpunit
...
FAILURES!
Tests: 6, Assertions: 7, Failures: 1
UPDATE_TEST_COMPARISON_FILES=1 phpunit
...
OK (6 tests, 7 assertions)
git status
...
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: tests/comparisons/example.php
See mocking-services-in-tests for how to replace services injected with the dependency injection container in your
integration tests.
Testing Views
Generally most applications will not directly test their HTML code. Doing so is often results in fragile, difficult to
maintain test suites that are prone to breaking. When writing functional tests using IntegrationTestTrait you can
inspect the rendered view content by setting the return option to ‘view’. While it is possible to test view content using
IntegrationTestTrait, a more robust and maintainable integration/view testing can be accomplished using tools
like Selenium webdriver174 .
Testing Components
Let’s pretend we have a component called PagematronComponent in our application. This component helps us
set the pagination limit value across all the controllers that use it. Here is our example component located in
src/Controller/Component/PagematronComponent.php:
Now we can write tests to ensure our paginate limit parameter is being set correctly by the adjust() method in our
component. We create the file tests/TestCase/Controller/Component/PagematronComponentTest.php:
namespace App\Test\TestCase\Controller\Component;
use App\Controller\Component\PagematronComponent;
use Cake\Controller\Controller;
use Cake\Controller\ComponentRegistry;
use Cake\Event\Event;
use Cake\Http\ServerRequest;
use Cake\Http\Response;
use Cake\TestSuite\TestCase;
$this->component->adjust('medium');
$this->assertEquals(50, $this->controller->paginate['limit']);
$this->component->adjust('long');
$this->assertEquals(100, $this->controller->paginate['limit']);
}
Testing Helpers
Since a decent amount of logic resides in Helper classes, it’s important to make sure those classes are covered by test
cases.
First we create an example helper to test. The CurrencyRendererHelper will help us display currencies in our views
and for simplicity only has one method usd():
// src/View/Helper/CurrencyRendererHelper.php
namespace App\View\Helper;
use Cake\View\Helper;
Here we set the decimal places to 2, decimal separator to dot, thousands separator to comma, and prefix the formatted
number with ‘USD’ string.
Now we create our tests:
// tests/TestCase/View/Helper/CurrencyRendererHelperTest.php
namespace App\Test\TestCase\View\Helper;
use App\View\Helper\CurrencyRendererHelper;
use Cake\TestSuite\TestCase;
(continues on next page)
Here, we call usd() with different parameters and tell the test suite to check if the returned values are equal to what is
expected.
Save this and execute the test. You should see a green bar and messaging indicating 1 pass and 4 assertions.
When you are testing a Helper which uses other helpers, be sure to mock the View clases loadHelpers method.
Testing Events
The Events System is a great way to decouple your application code, but sometimes when testing, you tend to test the
results of events in the test cases that execute those events. This is an additional form of coupling that can be removed
by using assertEventFired and assertEventFiredWith instead.
Expanding on the Orders example, say we have the following tables:
return true;
}
return false;
}
}
Note: To assert that events are fired, you must first enable Tracking Events on the event manager you wish to assert
against.
To test the OrdersTable above, we enable tracking in setUp() then assert that the event was fired, and assert that the
$order entity was passed in the event data:
namespace App\Test\TestCase\Model\Table;
use App\Model\Table\OrdersTable;
use Cake\Event\EventList;
use Cake\TestSuite\TestCase;
$this->assertTrue($this->Orders->place($order));
$this->assertEventFired('Model.Order.afterPlace', $this->Orders->
˓→ getEventManager());
$this->assertEventFiredWith('Model.Order.afterPlace', 'order', $order, $this->
˓→Orders->getEventManager());
}
}
By default, the global EventManager is used for assertions, so testing global events does not require passing the event
manager:
$this->assertEventFired('My.Global.Event');
$this->assertEventFiredWith('My.Global.Event', 'user', 1);
Testing Email
If you want several of your tests to run at the same time, you can create a test suite. A test suite is composed of several
test cases. You can either create test suites in your application’s phpunit.xml file. A simple example would be:
<testsuites>
<testsuite name="Models">
<directory>src/Model</directory>
<file>src/Service/UserServiceTest.php</file>
<exclude>src/Model/Cloud/ImagesTest.php</exclude>
</testsuite>
</testsuites>
Tests for plugins are created in their own directory inside the plugins folder.
/src
/plugins
/Blog
/tests
/TestCase
/Fixture
They work just like normal tests but you have to remember to use the naming conventions for plugins when importing
classes. This is an example of a testcase for the BlogPost model from the plugins chapter of this manual. A difference
from other tests is in the first line where ‘Blog.BlogPost’ is imported. You also need to prefix your plugin fixtures with
plugin.Blog.BlogPosts:
namespace Blog\Test\TestCase\Model\Table;
use Blog\Model\Table\BlogPostsTable;
use Cake\TestSuite\TestCase;
If you want to use plugin fixtures in the app tests you can reference them using plugin.pluginName.fixtureName
syntax in the $fixtures array. Additionally if you use vendor plugin name or fixture directories you can use the
following: plugin.vendorName/pluginName.folderName/fixtureName.
Before you can use fixtures you should ensure you have the fixture listener configured in your phpunit.xml file. You
should also ensure that your fixtures are loadable. Ensure the following is present in your composer.json file:
"autoload-dev": {
"psr-4": {
"MyPlugin\\Test\\": "plugins/MyPlugin/tests/"
}
}
Note: Remember to run composer.phar dumpautoload when adding new autoload mappings.
If you use bake to generate scaffolding, it will also generate test stubs. If you need to re-generate test case skeletons, or
if you want to generate test skeletons for code you wrote, you can use bake:
Validation
The validation package in CakePHP provides features to build validators that can validate arbitrary arrays of data with
ease. You can find a list of available Validation rules in the API175 .
Creating Validators
class Cake\Validation\Validator
Validator objects define the rules that apply to a set of fields. Validator objects contain a mapping between fields and
validation sets. In turn, the validation sets contain a collection of rules that apply to the field they are attached to.
Creating a validator is simple:
use Cake\Validation\Validator;
Once created, you can start defining sets of rules for the fields you want to validate:
$validator
->requirePresence('title')
->notEmptyString('title', 'Please fill this field')
->add('title', [
'length' => [
'rule' => ['minLength', 10],
'message' => 'Titles need to be at least 10 characters long',
]
])
->allowEmptyDateTime('published')
(continues on next page)
175 https://api.cakephp.org/5.x/class-Cake.Validation.Validation.html
709
CakePHP Book, Release 5.x
As seen in the example above, validators are built with a fluent interface that allows you to define rules for each field
you want to validate.
There were a few methods called in the example above, so let’s go over the various features. The add() method allows
you to add new rules to a validator. You can either add rules individually or in groups as seen above.
The requirePresence() method requires the field to be present in any validated array. If the field is absent, validation
will fail. The requirePresence() method has 4 modes:
• true The field’s presence is always required.
• false The field’s presence is not required.
• create The field’s presence is required when validating a create operation.
• update The field’s presence is required when validating an update operation.
By default, true is used. Key presence is checked by using array_key_exists() so that null values will count as
present. You can set the mode using the second parameter:
$validator->requirePresence('author_id', 'create');
If you have multiple fields that are required, you can define them as a list:
Validators offer several methods to control which fields accept empty values and which empty values are accepted and
not forwarded to other validation rules for the named field. CakePHP provides empty value support for different shapes
of data:
1. allowEmptyString() Should be used when you want to only accept an empty string.
2. allowEmptyArray() Should be used when you want to accept an array.
3. allowEmptyDate() Should be used when you want to accept an empty string, or an array that is marshalled
into a date field.
4. allowEmptyTime() Should be used when you want to accept an empty string, or an array that is marshalled
into a time field.
5. allowEmptyDateTime() Should be used when you want to accept an empty string or an array that is marshalled
into a datetime or timestamp field.
6. allowEmptyFile() Should be used when you want to accept an array that contains an empty uploaded file.
You also can use following specific validators: notEmptyString(), notEmptyArray(), notEmptyFile(),
notEmptyDate(), notEmptyTime(), notEmptyDateTime().
The allowEmpty* methods support a when parameter that allows you to control when a field can or cannot be empty:
• false The field is not allowed to be empty.
• create The field can be empty when validating a create operation.
• update The field can be empty when validating an update operation.
• A callback that returns true or false to indicate whether a field is allowed to be empty. See the Conditional
Validation section for examples on how to use this parameter.
An example of these methods in action is:
$validator->allowEmptyDateTime('published')
->allowEmptyString('title', 'Title cannot be empty', false)
->allowEmptyString('body', 'Body cannot be empty', 'update')
->allowEmptyFile('header_image', 'update');
->allowEmptyDateTime('posted', 'update');
The Validator class provides methods that make building validators simple and expressive. For example adding
validation rules to a username could look like:
See the Validator API documentation176 for the full set of validator methods.
176 https://api.cakephp.org/5.x/class-Cake.Validation.Validator.html
In addition to using methods on the Validator, and coming from providers, you can also use any callable, including
anonymous functions, as validation rules:
// Use a closure
$extra = 'Some additional value needed inside the closure';
$validator->add('title', 'custom', [
'rule' => function ($value, $context) use ($extra) {
// Custom logic that returns true/false
},
'message' => 'The title is not valid'
]);
Closures or callable methods will receive 2 arguments when called. The first will be the value for the field being
validated. The second is a context array containing data related to the validation process:
• data: The original data passed to the validation method, useful if you plan to create rules comparing values.
• providers: The complete list of rule provider objects, useful if you need to create complex rules by calling
multiple providers.
• newRecord: Whether the validation call is for a new record or a preexisting one.
Closures should return boolean true if the validation passes. If it fails, return boolean false or for a custom error message
return a string, see the Conditional/Dynamic Error Messages section for further details.
Validation rule methods, being it custom callables, or methods supplied by providers, can either return a boolean,
indicating whether the validation succeeded, or they can return a string, which means that the validation failed, and
that the returned string should be used as the error message.
Possible existing error messages defined via the message option will be overwritten by the ones returned from the
validation rule method:
$validator->add('length', 'custom', [
'rule' => function ($value, $context) {
if (!$value) {
return false;
}
return true;
},
'message' => 'Generic error message used when `false` is returned'
]);
Conditional Validation
When defining validation rules, you can use the on key to define when a validation rule should be applied. If left
undefined, the rule will always be applied. Other valid values are create and update. Using one of these values will
make the rule apply to only create or update operations.
Additionally, you can provide a callable function that will determine whether or not a particular rule should be applied:
$validator->add('picture', 'file', [
'rule' => ['mimeType', ['image/jpeg', 'image/png']],
'on' => function ($context) {
return !empty($context['data']['show_profile_picture']);
}
]);
You can access the other submitted field values using the $context['data'] array. The above example will make
the rule for ‘picture’ optional depending on whether the value for show_profile_picture is empty. You could also
use the uploadedFile validation rule to create optional file upload inputs:
$validator->add('picture', 'file', [
'rule' => ['uploadedFile', ['optional' => true]],
]);
The allowEmpty*, notEmpty* and requirePresence() methods will also accept a callback function as their last
argument. If present, the callback determines whether or not the rule should be applied. For example, a field is
sometimes allowed to be empty:
Likewise, a field can be required to be populated when certain conditions are met:
return !empty($context['data']['wants_newsletter']);
});
In the above example, the email_frequency field cannot be left empty if the the user wants to receive the newsletter.
Further it’s also possible to require a field to be present under certain conditions only:
return false;
});
$validator->requirePresence('email');
This would require the full_name field to be present only in case the user wants to create a subscription, while the
email field would always be required.
The $context parameter passed to custom conditional callbacks contains the following keys:
• data The data being validated.
• newRecord a boolean indicating whether a new or existing record is being validated.
• field The current field being validated.
• providers The validation providers attached to the current validator.
When fields have multiple rules, each validation rule will be run even if the previous one has failed. This allows you
to collect as many validation errors as you can in a single pass. If you want to stop execution after a specific rule has
failed, you can set the last option to true:
If the minLength rule fails in the example above, the maxLength rule will not be run.
You can have the last option automatically applied to each rule you can use the setStopOnFailure() method to
enable this behavior:
return $validator;
}
When enabled all fields will stop validation on the first failing rule instead of checking all possible rules. In this case
only a single error message will appear under the form field.
The Validator, ValidationSet and ValidationRule classes do not provide any validation methods themselves.
Validation rules come from ‘providers’. You can bind any number of providers to a Validator object. Validator instances
come with a ‘default’ provider setup automatically. The default provider is mapped to the Validation class. This
makes it simple to use the methods on that class as validation rules. When using Validators and the ORM together,
additional providers are configured for the table and entity objects. You can use the setProvider() method to add
any additional providers your application needs:
Validation providers can be objects, or class names. If a class name is used the methods must be static. To use a provider
other than ‘default’, be sure to set the provider key in your rule:
If you wish to add a provider to all Validator objects that are created in the future, you can use the
addDefaultProvider() method as follows:
use Cake\Validation\Validator;
Note: DefaultProviders must be added before the Validator object is created therefore config/bootstrap.php is the
best place to set up your default providers.
You can use the Localized plugin177 to get providers based on countries. With this plugin, you’ll be able to validate
model fields, depending on a country, ie:
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
return $validator;
}
}
The localized plugin uses the two letter ISO code of the countries for validation, like en, fr, de.
There are a few methods that are common to all classes, defined through the ValidationInterface interface178 :
177 https://github.com/cakephp/localized
178 https://github.com/cakephp/localized/blob/master/src/Validation/ValidationInterface.php
Nesting Validators
When validating Modelless Forms with nested data, or when working with models that contain array data types, it is
necessary to validate the nested data you have. CakePHP makes it simple to add validators to specific attributes. For
example, assume you are working with a non-relational database and need to store an article and its comments:
$data = [
'title' => 'Best article',
'comments' => [
['comment' => ''],
],
];
You can create 1:1 ‘relationships’ with addNested() and 1:N ‘relationships’ with addNestedMany(). With both
methods, the nested validator’s errors will contribute to the parent validator’s errors and influence the final result. Like
other validator features, nested validators support error messages and conditional application:
$validator->addNestedMany(
'comments',
$commentValidator,
'Invalid comment',
'create'
);
The error message for a nested validator can be found in the _nested key.
While defining validators inline where they are used makes for good example code, it doesn’t lead to maintainable
applications. Instead, you should create Validator sub-classes for your reusable validation logic:
// In src/Model/Validation/ContactValidator.php
namespace App\Model\Validation;
use Cake\Validation\Validator;
Validating Data
Now that you’ve created a validator and added the rules you want to it, you can start using it to validate data. Validators
are able to validate array data. For example, if you wanted to validate a contact form before creating and sending an
email you could do the following:
use Cake\Validation\Validator;
$errors = $validator->validate($this->request->getData());
if (empty($errors)) {
// Send an email.
}
The getErrors() method will return a non-empty array when there are validation failures. The returned array of
errors will be structured like:
$errors = [
'email' => ['E-mail must be valid'],
];
If you have multiple errors on a single field, an array of error messages will be returned per field. By default the
getErrors() method applies rules for the ‘create’ mode. If you’d like to apply ‘update’ rules you can do the following:
Note: If you need to validate entities you should use methods like newEntity(), newEntities(), patchEntity(),
patchEntities() as they are designed for that.
Validation is meant for checking request data coming from forms or other user interfaces used to populate the entities.
The request data is validated automatically when using the newEntity(), newEntities(), patchEntity() or
patchEntities() methods of Table class:
Similarly, when you need to validate multiple entities at a time, you can use the newEntities() method:
The newEntity(), patchEntity(), newEntities() and patchEntities() methods allow you to specify which
associations are validated, and which validation sets to apply using the options parameter:
$valid = $this->Articles->newEntity($article, [
'associated' => [
'Comments' => [
'associated' => ['User'],
'validate' => 'special',
],
],
]);
Apart from validating user provided data maintaining integrity of data regardless where it came from is important. To
solve this problem CakePHP offers a second level of validation which is called “application rules”. You can read more
about them in the Applying Application Rules section.
CakePHP provides a basic suite of validation methods in the Validation class. The Validation class contains a variety
of static methods that provide validators for several common validation situations.
The API documentation179 for the Validation class provides a good list of the validation rules that are available, and
their basic usage.
Some of the validation methods accept additional parameters to define boundary conditions or valid options. You can
provide these boundary conditions and options as follows:
179 https://api.cakephp.org/5.x/class-Cake.Validation.Validation.html
Core rules that take additional parameters should have an array for the rule key that contains the rule as the first
element, and the additional parameters as the remaining parameters.
App Class
class Cake\Core\App
The App class is responsible for resource location and path management.
Finding Classes
This method is used to resolve class names throughout CakePHP. It resolves the short form names CakePHP uses and
returns the fully resolved class name:
When resolving classes, the App namespace will be tried, and if the class does not exist the Cake namespace will be
attempted. If both class names do not exist, false will be returned.
721
CakePHP Book, Release 5.x
The same way you can retrieve paths for locales, plugins.
This can be done for all namespaces that are part of your application.
App::classPath() will only return the default path, and will not be able to provide any information about additional
paths the autoloader is configured for.
static Cake\Core\App::core(string $package)
Locating Themes
Since themes are plugins, you can use the methods above to get the path to a theme.
Ideally vendor files should be autoloaded with Composer, if you have vendor files that cannot be autoloaded or installed
with Composer you will need to use require to load them.
If you cannot install a library with Composer, it is best to install each library in a directory following Composer’s
convention of vendor/$author/$package. If you had a library called AcmeLib, you could install it into vendor/
Acme/AcmeLib. Assuming it did not use PSR-0 compatible classnames you could autoload the classes within it using
classmap in your application’s composer.json:
"autoload": {
"psr-4": {
"App\\": "src/",
"App\\Test\\": "tests/"
(continues on next page)
If your vendor library does not use classes, and instead provides functions, you can configure Composer to load these
files at the beginning of each request using the files autoloading strategy:
"autoload": {
"psr-4": {
"App\\": "src/",
"App\\Test\\": "tests/"
},
"files": [
"vendor/Acme/AcmeLib/functions.php"
]
}
After configuring the vendor libraries you will need to regenerate your application’s autoloader using:
If you happen to not be using Composer in your application, you will need to manually load all vendor libraries yourself.
Collections
class Cake\Collection\Collection
The collection classes provide a set of tools to manipulate arrays or Traversable objects. If you have ever used
underscore.js, you have an idea of what you can expect from the collection classes.
Collection instances are immutable; modifying a collection will instead generate a new collection. This makes working
with collection objects more predictable as operations are side-effect free.
Quick Example
Collections can be created using an array or Traversable object. You’ll also interact with collections every time you
interact with the ORM in CakePHP. A simple use of a Collection would be:
use Cake\Collection\Collection;
You can also use the collection() helper function instead of new Collection():
725
CakePHP Book, Release 5.x
The benefit of the helper method is that it is easier to chain off of than (new Collection($items)).
The CollectionTrait allows you to integrate collection-like features into any Traversable object you have in your
application as well.
List of Methods
Iterating
Cake\Collection\Collection::each($callback)
Collections can be iterated and/or transformed into new collections with the each() and map() methods. The each()
method will not create a new collection, but will allow you to modify any objects within the collection:
The return of each() will be the collection object. Each will iterate the collection immediately applying the callback
to each value in the collection.
Cake\Collection\Collection::map($callback)
The map() method will create a new collection based on the output of the callback being applied to each object in the
original collection:
The map() method will create a new iterator which lazily creates the resulting items when iterated.
Cake\Collection\Collection::extract($path)
One of the most common uses for a map() function is to extract a single column from a collection. If you are looking
to build a list of elements containing the values for a particular property, you can use the extract() method:
As with many other functions in the collection class, you are allowed to specify a dot-separated path for extracting
columns. This example will return a collection containing the author names from a list of articles:
$collection = new Collection($articles);
$names = $collection->extract('author.name');
Finally, if the property you are looking after cannot be expressed as a path, you can use a callback function to return it:
$collection = new Collection($articles);
$names = $collection->extract(function ($article) {
return $article->author->name . ', ' . $article->author->last_name;
});
Often, the properties you need to extract a common key present in multiple arrays or objects that are deeply nested
inside other structures. For those cases you can use the {*} matcher in the path key. This matcher is often helpful when
matching HasMany and BelongsToMany association data:
$data = [
[
'name' => 'James',
'phone_numbers' => [
['number' => 'number-1'],
['number' => 'number-2'],
['number' => 'number-3'],
],
],
[
'name' => 'James',
(continues on next page)
Iterating 727
CakePHP Book, Release 5.x
This last example uses toList() unlike other examples, which is important when we’re getting results with possibly
duplicate keys. By using toList() we’ll be guaranteed to get all values even if there are duplicate keys.
Unlike Cake\Utility\Hash::extract() this method only supports the {*} wildcard. All other wildcard and at-
tributes matchers are not supported.
Cake\Collection\Collection::combine($keyPath, $valuePath, $groupPath = null)
Collections allow you to create a new collection made from keys and values in an existing collection. Both the key and
value paths can be specified with dot notation paths:
$items = [
['id' => 1, 'name' => 'foo', 'parent' => 'a'],
['id' => 2, 'name' => 'bar', 'parent' => 'b'],
['id' => 3, 'name' => 'baz', 'parent' => 'a'],
];
$combined = (new Collection($items))->combine('id', 'name');
$result = $combined->toArray();
// $result contains
[
1 => 'foo',
2 => 'bar',
3 => 'baz',
];
You can also optionally use a groupPath to group results based on a path:
// $result contains
[
'a' => [1 => 'foo', 3 => 'baz'],
'b' => [2 => 'bar']
];
Finally you can use closures to build keys/values/groups paths dynamically, for example when working with entities
and dates (converted to I18n\DateTime instances by the ORM) you may want to group results by date:
// $result contains
[
'date string like 2015-05-01' => ['entity1->id' => entity1, 'entity2->id' => entity2,
˓→ ..., 'entityN->id' => entityN]
'date string like 2015-06-01' => ['entity1->id' => entity1, 'entity2->id' => entity2,
˓→ ..., 'entityN->id' => entityN]
]
Cake\Collection\Collection::stopWhen(callable $c)
You can stop the iteration at any point using the stopWhen() method. Calling it in a collection will create a new one
that will stop yielding results if the passed callable returns true for one of the elements:
Cake\Collection\Collection::unfold(callable $callback)
Sometimes the internal items of a collection will contain arrays or iterators with more items. If you wish to flatten the
internal structure to iterate once over all elements you can use the unfold() method. It will create a new collection
that will yield every single element nested in the collection:
When passing a callable to unfold() you can control what elements will be unfolded from each item in the original
collection. This is useful for returning data from paginated services:
$allPagesItems = $items->toList();
Iterating 729
CakePHP Book, Release 5.x
If you are using PHP 5.5+, you can use the yield keyword inside unfold() to return as many elements for each item
in the collection as you may need:
Cake\Collection\Collection::chunk($chunkSize)
When dealing with large amounts of items in a collection, it may make sense to process the elements in batches instead
of one by one. For splitting a collection into multiple arrays of a certain size, you can use the chunk() function:
The chunk function is particularly useful when doing batch processing, for example with a database result:
Cake\Collection\Collection::chunkWithKeys($chunkSize)
Much like chunk(), chunkWithKeys() allows you to slice up a collection into smaller batches but with keys preserved.
This is useful when chunking associative arrays:
// $result contains
[
['a' => 1, 'b' => 2],
['c' => 3, 'd' => [4, 5]]
]
Filtering
Cake\Collection\Collection::filter($callback)
Collections allow you to filter and create new collections based on the result of callback functions. You can use
filter() to create a new collection of elements matching a criteria callback:
Cake\Collection\Collection::reject(callable $c)
The inverse of filter() is reject(). This method does a negative filter, removing elements that match the filter
function:
Cake\Collection\Collection::every($callback)
You can do truth tests with filter functions. To see if every element in a collection matches a test you can use every():
Cake\Collection\Collection::some($callback)
You can see if the collection contains at least one element matching a filter function using the some() method:
Cake\Collection\Collection::match($conditions)
If you need to extract a new collection containing only the elements that contain a given set of properties, you should
use the match() method:
Cake\Collection\Collection::firstMatch($conditions)
The property name can be a dot-separated path. You can traverse into nested entities and match the values they contain.
When you only need the first matching element from a collection, you can use firstMatch():
Filtering 731
CakePHP Book, Release 5.x
As you can see from the above, both match() and firstMatch() allow you to provide multiple conditions to match
on. In addition, the conditions can be for different paths, allowing you to express complex conditions to match against.
Aggregation
Cake\Collection\Collection::reduce($callback, $initial)
The counterpart of a map() operation is usually a reduce. This function will help you build a single result out of all
the elements in a collection:
In the above example, $totalPrice will be the sum of all single prices contained in the collection. Note the second
argument for the reduce() function takes the initial value for the reduce operation you are performing:
To extract the minimum value for a collection based on a property, just use the min() function. This will return the
full element from the collection and not just the smallest value found:
echo $youngest->name;
You are also able to express the property to compare by providing a path or a callback function:
$personWithYoungestDad = $collection->min('dad.age');
The same can be applied to the max() function, which will return a single element from the collection having the
highest property value:
$personWithOldestDad = $collection->max('dad.age');
Cake\Collection\Collection::sumOf($path = null)
Finally, the sumOf() method will return the sum of a property of all elements:
$sumOfDadAges = $collection->sumOf('dad.age');
Cake\Collection\Collection::avg($path = null)
Calculate the average value of the elements in the collection. Optionally provide a matcher path, or function to extract
values to generate the average for:
$items = [
['invoice' => ['total' => 100]],
['invoice' => ['total' => 200]],
];
Cake\Collection\Collection::median($path = null)
Calculate the median value of a set of elements. Optionally provide a matcher path, or function to extract values to
generate the median for:
$items = [
['invoice' => ['total' => 400]],
['invoice' => ['total' => 500]],
['invoice' => ['total' => 100]],
['invoice' => ['total' => 333]],
['invoice' => ['total' => 200]],
];
Aggregation 733
CakePHP Book, Release 5.x
Cake\Collection\Collection::groupBy($callback)
Collection values can be grouped by different keys in a new collection when they share the same value for a property:
$students = [
['name' => 'Mark', 'grade' => 9],
['name' => 'Andrew', 'grade' => 10],
['name' => 'Stacy', 'grade' => 10],
['name' => 'Barbara', 'grade' => 9]
];
$collection = new Collection($students);
$studentsByGrade = $collection->groupBy('grade');
$result = $studentsByGrade->toArray();
// $result contains
[
10 => [
['name' => 'Andrew', 'grade' => 10],
['name' => 'Stacy', 'grade' => 10]
],
9 => [
['name' => 'Mark', 'grade' => 9],
['name' => 'Barbara', 'grade' => 9]
]
]
As usual, it is possible to provide either a dot-separated path for nested properties or your own callback function to
generate the groups dynamically:
$commentsByUserId = $comments->groupBy('user.id');
Cake\Collection\Collection::countBy($callback)
If you only wish to know the number of occurrences per group, you can do so by using the countBy() method. It takes
the same arguments as groupBy so it should be already familiar to you:
Cake\Collection\Collection::indexBy($callback)
There will be certain cases where you know an element is unique for the property you want to group by. If you wish a
single result per group, you can use the function indexBy():
$usersById = $users->indexBy('id');
As with the groupBy() function you can also use a property path or a callback:
$articlesByAuthorId = $articles->indexBy('author.id');
Cake\Collection\Collection::zip($items)
The elements of different collections can be grouped together using the zip() method. It will return a new collection
containing an array grouping the elements from each collection that are placed at the same position:
// $result contains
[
[2013, 1000, 0],
[2014, 1500, 500],
[2015, 2000, 500],
[2016, 2300, 300]
]
As you can already see, the zip() method is very useful for transposing multidimensional arrays:
$data = [
2014 => ['jan' => 100, 'feb' => 200],
2015 => ['jan' => 300, 'feb' => 500],
2016 => ['jan' => 400, 'feb' => 600],
];
Aggregation 735
CakePHP Book, Release 5.x
// $result contains
[
[100, 300, 400],
[200, 500, 600]
]
Sorting
Collection values can be sorted in ascending or descending order based on a column or custom function. To create a
new sorted collection out of the values of another one, you can use sortBy:
As seen above, you can sort by passing the name of a column or property that is present in the collection values. You
are also able to specify a property path instead using the dot notation. The next example will sort articles by their
author’s name:
The sortBy() method is flexible enough to let you specify an extractor function that will let you dynamically select
the value to use for comparing two different values in the collection:
In order to specify in which direction the collection should be sorted, you need to provide either SORT_ASC or
SORT_DESC as the second parameter for sorting in ascending or descending direction respectively. By default, col-
lections are sorted in descending direction:
Sometimes you will need to specify which type of data you are trying to compare so that you get consistent results. For
this purpose, you should supply a third argument in the sortBy() function with one of the following constants:
• SORT_NUMERIC: For comparing numbers
• SORT_STRING: For comparing string values
• SORT_NATURAL: For sorting string containing numbers and you’d like those numbers to be order in a natural
way. For example: showing “10” after “2”.
• SORT_LOCALE_STRING: For comparing strings based on the current locale.
Warning: It is often expensive to iterate sorted collections more than once. If you plan to do so, consider converting
the collection to an array or simply use the compile() method on it.
Not all data is meant to be represented in a linear way. Collections make it easier to construct and flatten hierarchical
or nested structures. Creating a nested structure where children are grouped by a parent identifier property can be done
with the nest() method.
Two parameters are required for this function. The first one is the property representing the item identifier. The second
parameter is the name of the property representing the identifier for the parent item:
// $result contains
[
[
'id' => 1,
'parent_id' => null,
'name' => 'Birds',
'children' => [
['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds', 'children' => []],
['id' => 3, 'parent_id' => 1, 'name' => 'Eagle', 'children' => []],
['id' => 4, 'parent_id' => 1, 'name' => 'Seagull', 'children' => []],
],
],
[
'id' => 6,
'parent_id' => null,
'name' => 'Fish',
'children' => [
['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish', 'children' => []],
],
],
];
Children elements are nested inside the children property inside each of the items in the collection. This type of data
representation is helpful for rendering menus or traversing elements up to certain level in the tree.
Cake\Collection\Collection::listNested($order = 'desc', $nestingKey = 'children')
The inverse of nest() is listNested(). This method allows you to flatten a tree structure back into a linear structure.
It takes two parameters; the first one is the traversing mode (asc, desc or leaves), and the second one is the name of the
property containing the children for each element in the collection.
Taking the input the nested collection built in the previous example, we can flatten it:
$result = $nested->listNested()->toList();
// $result contains
[
['id' => 1, 'parent_id' => null, 'name' => 'Birds', 'children' => [...]],
['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds'],
['id' => 3, 'parent_id' => 1, 'name' => 'Eagle'],
['id' => 4, 'parent_id' => 1, 'name' => 'Seagull'],
['id' => 6, 'parent_id' => null, 'name' => 'Fish', 'children' => [...]],
['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish']
]
By default, the tree is traversed from the root to the leaves. You can also instruct it to only return the leaf elements in
the tree:
$result = $nested->listNested('leaves')->toList();
// $result contains
[
['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds', 'children' => [], ],
['id' => 3, 'parent_id' => 1, 'name' => 'Eagle', 'children' => [], ],
['id' => 4, 'parent_id' => 1, 'name' => 'Seagull', 'children' => [], ],
['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish', 'children' => [], ],
]
Once you have converted a tree into a nested list, you can use the printer() method to configure how the list output
should be formatted:
// $result contains
[
1 => 'Birds',
2 => '--Land Birds',
3 => '--Eagle',
4 => '--Seagull',
6 => 'Fish',
5 => '--Clown Fish',
]
The printer() method also lets you use a callback to generate the keys and or values:
$nested->listNested()->printer(
function ($el) {
(continues on next page)
Other Methods
Cake\Collection\Collection::isEmpty()
Cake\Collection\Collection::contains($value)
Collections allow you to quickly check if they contain one particular value: by using the contains() method:
Comparisons are performed using the === operator. If you wish to do looser comparison types you can use the some()
method.
Cake\Collection\Collection::shuffle()
Sometimes you may wish to show a collection of values in a random order. In order to create a new collection that will
return each value in a randomized position, use the shuffle:
Cake\Collection\Collection::transpose()
When you transpose a collection, you get a new collection containing a row made of the each of the original columns:
$items = [
['Products', '2012', '2013', '2014'],
['Product A', '200', '100', '50'],
['Product B', '300', '200', '100'],
['Product C', '400', '300', '200'],
];
(continues on next page)
// $result contains
[
['Products', 'Product A', 'Product B', 'Product C'],
['2012', '200', '300', '400'],
['2013', '100', '200', '300'],
['2014', '50', '100', '200'],
]
Withdrawing Elements
Cake\Collection\Collection::sample($length = 10)
Shuffling a collection is often useful when doing quick statistical analysis. Another common operation when doing
this sort of task is withdrawing a few random values out of a collection so that more tests can be performed on those.
For example, if you wanted to select 5 random users to which you’d like to apply some A/B tests to, you can use the
sample() function:
sample() will take at most the number of values you specify in the first argument. If there are not enough elements in
the collection to satisfy the sample, the full collection in a random order is returned.
Cake\Collection\Collection::take($length, $offset)
Whenever you want to take a slice of a collection use the take() function, it will create a new collection with at most
the number of values you specify in the first argument, starting from the position passed in the second argument:
$topFive = $collection->sortBy('age')->take(5);
While the second argument of take() can help you skip some elements before getting them from the collection, you
can also use skip() for the same purpose as a way to take the rest of the elements after a certain position:
Cake\Collection\Collection::first()
One of the most common uses of take() is getting the first element in the collection. A shortcut method for achieving
the same goal is using the first() method:
Cake\Collection\Collection::last()
Similarly, you can get the last element of a collection using the last() method:
Expanding Collections
Cake\Collection\Collection::append(array|Traversable $items)
You can compose multiple collections into a single one. This enables you to gather data from various sources, concate-
nate it, and apply other collection functions to it very smoothly. The append() method will return a new collection
containing the values from both sources:
Cake\Collection\Collection::appendItem($value, $key)
Allows you to append an item with an optional key to the collection. If you specify a key that already exists in the
collection, the value will not be overwritten:
Cake\Collection\Collection::prepend($items)
The prepend() method will return a new collection containing the values from both sources:
Cake\Collection\Collection::prependItem($value, $key)
Allows you to prepend an item with an optional key to the collection. If you specify a key that already exists in the
collection, the value will not be overwritten:
Warning: When appending from different sources, you can expect some keys from both collections to be the
same. For example, when appending two simple arrays. This can present a problem when converting a collection
to an array using toArray(). If you do not want values from one collection to override others in the previous one
based on their key, make sure that you call toList() in order to drop the keys and preserve all values.
Modifiying Elements
Cake\Collection\Collection::insert($path, $items)
At times, you may have two separate sets of data that you would like to insert the elements of one set into each of the
elements of the other set. This is a very common case when you fetch data from a data source that does not support
data-merging or joins natively.
Collections offer an insert() method that will allow you to insert each of the elements in one collection into a property
inside each of the elements of another collection:
$users = [
['username' => 'mark'],
['username' => 'juan'],
['username' => 'jose']
];
$languages = [
['PHP', 'Python', 'Ruby'],
['Bash', 'PHP', 'Javascript'],
['Javascript', 'Prolog']
];
// $result contains
[
['username' => 'mark', 'skills' => ['PHP', 'Python', 'Ruby']],
['username' => 'juan', 'skills' => ['Bash', 'PHP', 'Javascript']],
['username' => 'jose', 'skills' => ['Javascript', 'Prolog']]
];
The first parameter for the insert() method is a dot-separated path of properties to follow so that the elements can
be inserted at that position. The second argument is anything that can be converted to a collection object.
Please observe that elements are inserted by the position they are found, thus, the first element of the second collection
is merged into the first element of the first collection.
If there are not enough elements in the second collection to insert into the first one, then the target property will not be
present:
$languages = [
['PHP', 'Python', 'Ruby'],
['Bash', 'PHP', 'Javascript']
];
// $result contains
[
['username' => 'mark', 'skills' => ['PHP', 'Python', 'Ruby']],
['username' => 'juan', 'skills' => ['Bash', 'PHP', 'Javascript']],
['username' => 'jose']
];
The insert() method can operate array elements or objects implementing the ArrayAccess interface.
Using closures for collection methods is great when the work to be done is small and focused, but it can get messy
very quickly. This becomes more obvious when a lot of different methods need to be called or when the length of the
closure methods is more than just a few lines.
There are also cases when the logic used for the collection methods can be reused in multiple parts of your application.
It is recommended that you consider extracting complex collection logic to separate classes. For example, imagine a
lengthy closure like this one:
$collection
->map(function ($row, $key) {
if (!empty($row['items'])) {
$row['total'] = collection($row['items'])->sumOf('price');
}
if (!empty($row['total'])) {
$row['tax_amount'] = $row['total'] * 0.25;
}
return $modifiedRow;
});
class TotalOrderCalculator
{
public function __invoke($row, $key)
{
if (!empty($row['items'])) {
$row['total'] = collection($row['items'])->sumOf('price');
}
if (!empty($row['total'])) {
$row['tax_amount'] = $row['total'] * 0.25;
}
return $modifiedRow;
}
}
Cake\Collection\Collection::through($callback)
Sometimes a chain of collection method calls can become reusable in other parts of your application, but only if they
are called in that specific order. In those cases you can use through() in combination with a class implementing
$collection
->map(new ShippingCostCalculator)
->map(new TotalOrderCalculator)
->map(new GiftCardPriceReducer)
->buffered()
...
The above method calls can be extracted into a new class so they don’t need to be repeated every time:
class FinalCheckOutRowProcessor
{
public function __invoke($collection)
{
return $collection
->map(new ShippingCostCalculator)
->map(new TotalOrderCalculator)
->map(new GiftCardPriceReducer)
->buffered()
...
}
}
// Now you can use the through() method to call all methods at once
$collection->through(new FinalCheckOutRowProcessor);
Optimizing Collections
Cake\Collection\Collection::buffered()
Collections often perform most operations that you create using its functions in a lazy way. This means that even though
you can call a function, it does not mean it is executed right away. This is true for a great deal of functions in this class.
Lazy evaluation allows you to save resources in situations where you don’t use all the values in a collection. You might
not use all the values when iteration stops early, or when an exception/failure case is reached early.
Additionally, lazy evaluation helps speed up some operations. Consider the following example:
Had the collections not been lazy, we would have executed one million operations, even though we only wanted to show
30 elements out of it. Instead, our map operation was only applied to the 30 elements we used. We can also derive
benefits from this lazy evaluation for smaller collections when we do more than one operation on them. For example:
calling map() twice and then filter().
Lazy evaluation comes with its downside too. You could be doing the same operations more than once if you optimize
a collection prematurely. Consider this example:
$ages = $collection->extract('age');
If we iterate both youngerThan30 and olderThan30, the collection would unfortunately execute the extract()
operation twice. This is because collections are immutable and the lazy-extracting operation would be done for both
filters.
Luckily we can overcome this issue with a single function. If you plan to reuse the values from certain operations more
than once, you can compile the results into another collection using the buffered() function:
$ages = $collection->extract('age')->buffered();
$youngerThan30 = ...
$olderThan30 = ...
Now, when both collections are iterated, they will only call the extracting operation once.
The buffered() method is also useful for converting non-rewindable iterators into collections that can be iterated
more than once:
// In PHP 5.5+
public function results()
{
...
foreach ($transientElements as $e) {
yield $e;
}
}
$rewindable = (new Collection(results()))->buffered();
Cloning Collections
Cake\Collection\Collection::compile($preserveKeys = true)
Sometimes you need to get a clone of the elements from another collection. This is useful when you need to iterate
the same set from different places at the same time. In order to clone a collection out of another use the compile()
method:
$ages = $collection->extract('age')->compile();
Hash
class Cake\Utility\Hash
Array management, if done right, can be a very powerful and useful tool for building smarter, more optimized code.
CakePHP offers a very useful set of static utilities in the Hash class that allow you to do just that.
CakePHP’s Hash class can be called from any model or controller in the same way Inflector is called. Example:
Hash::combine().
The path syntax described below is used by all the methods in Hash. Not all parts of the path syntax are available in
all methods. A path expression is made of any number of tokens. Tokens are composed of two groups. Expressions,
are used to traverse the array data, while matchers are used to qualify elements. You apply matchers to expression
elements.
Expression Types
Expression Definition
{n} Represents a numeric key. Will match any string or numeric key.
{s} Represents a string. Will match any string value including numeric string values.
{*} Matches any value.
Foo Matches keys with the exact same value.
All expression elements are supported by all methods. In addition to expression elements, you can use attribute match-
ing with certain methods. They are extract(), combine(), format(), check(), map(), reduce(), apply(),
sort(), insert(), remove() and nest().
747
CakePHP Book, Release 5.x
Matcher Definition
[id] Match elements with a given array key.
[id=2] Match elements with id equal to 2.
[id!=2] Match elements with id not equal to 2.
[id>2] Match elements with id greater than 2.
[id>=2] Match elements with id greater than or equal to 2.
[id<2] Match elements with id less than 2
[id<=2] Match elements with id less than or equal to 2.
[text=/.../] Match elements that have values matching the regular expression inside ....
// Common Usage:
$users = [
['id' => 1, 'name' => 'mark'],
['id' => 2, 'name' => 'jane'],
['id' => 3, 'name' => 'sally'],
['id' => 4, 'name' => 'jose'],
];
$results = Hash::extract($users, '{n}.id');
// $results equals:
// [1,2,3,4];
$a = [
'pages' => ['name' => 'page']
];
$result = Hash::insert($a, 'files', ['name' => 'files']);
// $result now looks like:
[
[pages] => [
[name] => page
]
[files] => [
[name] => files
]
]
You can use paths using {n}, {s} and {*} to insert data into multiple points:
$data = [
0 => ['up' => true, 'Item' => ['id' => 1, 'title' => 'first']],
1 => ['Item' => ['id' => 2, 'title' => 'second']],
2 => ['Item' => ['id' => 3, 'title' => 'third']],
3 => ['up' => true, 'Item' => ['id' => 4, 'title' => 'fourth']],
4 => ['Item' => ['id' => 5, 'title' => 'fifth']],
];
$result = Hash::insert($data, '{n}[up].Item[id=4].new', 9);
/* $result now looks like:
[
['up' => true, 'Item' => ['id' => 1, 'title' => 'first']],
['Item' => ['id' => 2, 'title' => 'second']],
['Item' => ['id' => 3, 'title' => 'third']],
['up' => true, 'Item' => ['id' => 4, 'title' => 'fourth', 'new' => 9]],
['Item' => ['id' => 5, 'title' => 'fifth']],
]
*/
$a = [
'pages' => ['name' => 'page'],
'files' => ['name' => 'files']
];
$result = Hash::remove($a, 'files');
/* $result now looks like:
[
[pages] => [
[name] => page
]
]
*/
Using {n}, {s} and {*} will allow you to remove multiple values at once. You can also use attribute matchers
with remove():
$data = [
0 => ['clear' => true, 'Item' => ['id' => 1, 'title' => 'first']],
1 => ['Item' => ['id' => 2, 'title' => 'second']],
2 => ['Item' => ['id' => 3, 'title' => 'third']],
3 => ['clear' => true, 'Item' => ['id' => 4, 'title' => 'fourth']],
4 => ['Item' => ['id' => 5, 'title' => 'fifth']],
];
$result = Hash::remove($data, '{n}[clear].Item[id=4]');
/* $result now looks like:
[
['clear' => true, 'Item' => ['id' => 1, 'title' => 'first']],
(continues on next page)
$a = [
[
'User' => [
'id' => 2,
'group_id' => 1,
'Data' => [
'user' => 'mariano.iglesias',
'name' => 'Mariano Iglesias'
]
]
],
[
'User' => [
'id' => 14,
'group_id' => 2,
'Data' => [
'user' => 'phpnut',
'name' => 'Larry E. Masters'
]
]
],
];
You can provide arrays for both $keyPath and $valuePath. If you do this, the first value will be used as a
format string, for values extracted by the other paths:
$result = Hash::combine(
$a,
'{n}.User.id',
['%s: %s', '{n}.User.Data.user', '{n}.User.Data.name'],
'{n}.User.group_id'
);
/* $result now looks like:
[
[1] => [
[2] => mariano.iglesias: Mariano Iglesias
]
[2] => [
[14] => phpnut: Larry E. Masters
]
]
*/
$result = Hash::combine(
$a,
['%s: %s', '{n}.User.Data.user', '{n}.User.Data.name'],
'{n}.User.id'
);
/* $result now looks like:
[
[mariano.iglesias: Mariano Iglesias] => 2
[phpnut: Larry E. Masters] => 14
]
*/
$data = [
[
'Person' => [
'first_name' => 'Nate',
'last_name' => 'Abele',
'city' => 'Boston',
'state' => 'MA',
'something' => '42'
(continues on next page)
/*
[
[0] => 42, Nate
[1] => 0, Larry
[2] => 0, Garrett
]
*/
/*
[
[0] => Nate, 42
[1] => Larry, 0
[2] => Garrett, 0
]
*/
$a = [
0 => ['name' => 'main'],
1 => ['name' => 'about']
];
$b = [
0 => ['name' => 'main'],
1 => ['name' => 'about'],
(continues on next page)
$set = [
'My Index 1' => ['First' => 'The first item']
];
$result = Hash::check($set, 'My Index 1.First');
// $result == true
$set = [
'My Index 1' => [
'First' => [
'Second' => [
'Third' => [
'Fourth' => 'Heavy. Nesting.'
]
]
]
]
];
$result = Hash::check($set, 'My Index 1.First.Second');
// $result == true
$data = [
'0',
(continues on next page)
$arr = [
[
'Post' => ['id' => '1', 'title' => 'First Post'],
'Author' => ['id' => '1', 'user' => 'Kyle'],
],
[
'Post' => ['id' => '2', 'title' => 'Second Post'],
'Author' => ['id' => '3', 'user' => 'Crystal'],
],
];
$res = Hash::flatten($arr);
/* $res now looks like:
[
[0.Post.id] => 1
[0.Post.title] => First Post
[0.Author.id] => 1
[0.Author.user] => Kyle
[1.Post.id] => 2
[1.Post.title] => Second Post
[1.Author.id] => 3
[1.Author.user] => Crystal
]
*/
$data = [
'0.Post.id' => 1,
(continues on next page)
Note: This function will work with an unlimited amount of arguments and typecasts non-array parameters into
arrays.
$array = [
[
'id' => '48c2570e-dfa8-4c32-a35e-0d71cbdd56cb',
'name' => 'mysql raleigh-workshop-08 < 2008-09-05.sql ',
'description' => 'Importing an sql dump',
],
[
'id' => '48c257a8-cf7c-4af2-ac2f-114ecbdd56cb',
'name' => 'pbpaste | grep -i Unpaid | pbcopy',
'description' => 'Remove all lines that say "Unpaid".',
]
];
$arrayB = 4;
$arrayC = [0 => "test array", "cats" => "dogs", "people" => 1267];
$arrayD = ["cats" => "felines", "dog" => "angry"];
$res = Hash::merge($array, $arrayB, $arrayC, $arrayD);
$data = ['one'];
$res = Hash::numeric(array_keys($data));
// $res is true
$data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => '3.1.1']];
$result = Hash::dimensions($data);
// $result == 2
$data = ['1' => '1.1', '2', '3' => ['3.1' => '3.1.1']];
$result = Hash::dimensions($data);
// $result == 1
$data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => ['3.1.1' => '3.1.1.1']]];
$result = Hash::dimensions($data);
// $result == 2
the array:
$data = ['1' => '1.1', '2', '3' => ['3.1' => '3.1.1']];
$result = Hash::maxDimensions($data);
// $result == 2
$data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => ['3.1.1' => '3.1.1.1']]];
$result = Hash::maxDimensions($data);
// $result == 3
$data = [
['date' => '01-01-2016', 'booked' => true],
['date' => '01-01-2016', 'booked' => false],
['date' => '02-01-2016', 'booked' => true]
];
$result = Hash::apply($data, '{n}[booked=true].date', 'array_count_values');
/* $result now looks like:
[
'01-01-2016' => 1,
'02-01-2016' => 1,
]
*/
$a = [
0 => ['Person' => ['name' => 'Jeff']],
1 => ['Shirt' => ['color' => 'black']]
];
$result = Hash::sort($a, '{n}.Person.name', 'asc');
/* $result now looks like:
(continues on next page)
$dir can be either asc or desc. $type can be one of the following values:
• regular for regular sorting.
• numeric for sorting values as their numeric equivalents.
• string for sorting values as their string value.
• natural for sorting values in a human friendly way. Will sort foo10 below foo2 as an example.
static Cake\Utility\Hash::diff(array $data, array $compare)
Computes the difference between two arrays:
$a = [
0 => ['name' => 'main'],
1 => ['name' => 'about']
];
$b = [
0 => ['name' => 'main'],
1 => ['name' => 'about'],
2 => ['name' => 'contact']
];
$array1 = ['ModelOne' => ['id' => 1001, 'field_one' => 'a1.m1.f1', 'field_two' =>
˓→'a1.m1.f2']];
$array2 = ['ModelOne' => ['id' => 1003, 'field_one' => 'a3.m1.f1', 'field_two' =>
˓→'a3.m1.f2', 'field_three' => 'a3.m1.f3']];
Example 2
$a = ['Tree', 'CounterCache',
'Upload' => [
'folder' => 'products',
'fields' => ['image_1_id', 'image_2_id']
]
];
$result = Hash::normalize($a);
/* $result now looks like:
[
[Tree] => null
[CounterCache] => null
[Upload] => [
[folder] => products
[fields] => [
[0] => image_1_id
[1] => image_2_id
(continues on next page)
$b = [
'Cacheable' => ['enabled' => false],
'Limit',
'Bindable',
'Validator',
'Transactional',
];
$result = Hash::normalize($b);
/* $result now looks like:
[
[Cacheable] => [
[enabled] => false
]
$data = [
['ThreadPost' => ['id' => 1, 'parent_id' => null]],
['ThreadPost' => ['id' => 2, 'parent_id' => 1]],
['ThreadPost' => ['id' => 3, 'parent_id' => 1]],
['ThreadPost' => ['id' => 4, 'parent_id' => 1]],
['ThreadPost' => ['id' => 5, 'parent_id' => 1]],
['ThreadPost' => ['id' => 6, 'parent_id' => null]],
['ThreadPost' => ['id' => 7, 'parent_id' => 6]],
['ThreadPost' => ['id' => 8, 'parent_id' => 6]],
['ThreadPost' => ['id' => 9, 'parent_id' => 6]],
(continues on next page)
Http Client
CakePHP includes a PSR-18 compliant HTTP client which can be used for making requests. It is a great way to
communicate with webservices, and remote APIs.
Doing Requests
Doing requests is simple and straight forward. Doing a GET request looks like:
use Cake\Http\Client;
// Simple get
$response = $http->get('http://example.com/test.html');
763
CakePHP Book, Release 5.x
If you have created a PSR-7 request object you can send it using sendRequest():
use Cake\Http\Client;
use Cake\Http\Client\Request as ClientRequest;
You can include files in request bodies by including a filehandle in the array:
$http = new Client();
$response = $http->post('http://example.com/api', [
'image' => fopen('/path/to/a/file', 'r'),
]);
The filehandle will be read until its end; it will not be rewound before being read.
There may be times when you need to build a request body in a very specific way. In these situations you can often use
Cake\Http\Client\FormData to craft the specific multipart HTTP request you want:
use Cake\Http\Client\FormData;
When dealing with REST APIs you often need to send request bodies that are not form encoded. Http\Client exposes
this through the type option:
The type key can either be a one of ‘json’, ‘xml’ or a full mime type. When using the type option, you should provide
the data as a string. If you’re doing a GET request that needs both querystring parameters and a request body you can
do the following:
Each HTTP method takes an $options parameter which is used to provide addition request information. The following
keys can be used in $options:
• headers - Array of additional headers
• cookie - Array of cookies to use.
• proxy - Array of proxy information.
• auth - Array of authentication data, the type key is used to delegate to an authentication strategy. By default
Basic auth is used.
• ssl_verify_peer - defaults to true. Set to false to disable SSL certification verification (not recommended).
• ssl_verify_peer_name - defaults to true. Set to false to disable host name verification when verifying SSL
certificates (not recommended).
• ssl_verify_depth - defaults to 5. Depth to traverse in the CA chain.
• ssl_verify_host - defaults to true. Validate the SSL certificate against the host name.
• ssl_cafile - defaults to built in cafile. Overwrite to use custom CA bundles.
• timeout - Duration to wait before timing out in seconds.
• type - Send a request body in a custom content type. Requires $data to either be a string, or the _content
option to be set when doing GET requests.
• redirect - Number of redirects to follow. Defaults to false.
• curl - An array of additional curl options (if the curl adapter is used), for example, [CURLOPT_SSLKEY =>
'key.pem'].
The options parameter is always the 3rd parameter in each of the HTTP methods. They can also be used when con-
structing Client to create scoped clients.
Authentication
Cake\Http\Client supports a few different authentication systems. Different authentication strategies can be added
by developers. Auth strategies are called before the request is sent, and allow headers to be added to the request context.
By default Cake\Http\Client will use basic authentication if there is no 'type' key in the auth option.
By setting the ‘type’ key to ‘digest’, you tell the authentication subsystem to use digest authentication. Digest authen-
tication supports the following algorithms:
• MD5
• SHA-256
• SHA-512-256
• MD5-sess
• SHA-256-sess
• SHA-512-256-sess
The algorithm will be automatically chosen based on the server challenge.
OAuth 1 Authentication
Many modern web-services require OAuth authentication to access their APIs. The included OAuth authentication
assumes that you already have your consumer key and consumer secret:
Authentication 767
CakePHP Book, Release 5.x
OAuth 2 Authentication
Because OAuth2 is often a single header, there is not a specialized authentication adapter. Instead you can create a
client with the access token:
Proxy Authentication
Some proxies require authentication to use them. Generally this authentication is Basic, but it can be implemented by
any authentication adapter. By default Http\Client will assume Basic authentication, unless the type key is set:
The second proxy parameter must be a string with an IP or a domain without protocol. The username and
password information will be passed through the request headers, while the proxy string will be passed through
stream_context_create()180 .
Having to re-type the domain name, authentication and proxy settings can become tedious & error prone. To reduce
the chance for mistake and relieve some of the tedium, you can create scoped clients:
// Do a request to api.example.com
$response = $http->get('/test.php');
If your scoped client only needs information from the URL you can use createFromUrl():
$http = Client::createFromUrl('https://api.example.com/v1/test');
The above would create a client instance with the protocol, host, and basePath options set.
The following information can be used when creating a scoped client:
180 https://php.net/manual/en/function.stream-context-create.php
• host
• basePath
• scheme
• proxy
• auth
• port
• cookies
• timeout
• ssl_verify_peer
• ssl_verify_depth
• ssl_verify_host
Any of these options can be overridden by specifying them when doing requests. host, scheme, proxy, port are over-
ridden in the request URL:
The above will replace the domain, scheme, and port. However, this request will continue using all the other op-
tions defined when the scoped client was created. See Request Method Options for more information on the options
supported.
Http\Client can also accept cookies when making requests. In addition to accepting cookies, it will also automatically
store valid cookies set in responses. Any response with cookies, will have them stored in the originating instance of
Http\Client. The cookies stored in a Client instance are automatically included in future requests to domain + path
combinations that match:
You can always override the auto-included cookies by setting them in the request’s $options parameters:
You can add cookie objects to the client after creating it using the addCookie() method:
use Cake\Http\Cookie\Cookie;
Response Objects
class Cake\Http\Client\Response
Response objects have a number of methods for inspecting the response data.
You can also access the stream object for the response and use its methods:
Since JSON and XML responses are commonly used, response objects provide a way to use accessors to read decoded
data. JSON data is decoded into an array, while XML data is decoded into a SimpleXMLElement tree:
The decoded response data is stored in the response object, so accessing it multiple times has no additional cost.
You can access headers through a few different methods. Header names are always treated as case-insensitive values
when accessing them through methods:
You can read cookies with a few different methods depending on how much data you need about the cookies:
By default Http\Client will prefer using a curl based transport adapter. If the curl extension is not available a stream
based adapter will be used instead. You can force select a transport adapter using a constructor option:
use Cake\Http\Client\Adapter\Stream;
Testing
trait Cake\Http\TestSuite\HttpClientTrait
In tests you will often want to create mock responses to external APIs. You can use the HttpClientTrait to define
responses to the requests your application is making:
use Cake\Http\TestSuite\HttpClientTrait;
use Cake\TestSuite\TestCase;
There are methods to mock the most commonly used HTTP methods:
As seen above you can use the newClientResponse() method to create responses for the requests your application
will make. The headers need to be a list of strings:
$headers = [
'Content-Type: application/json',
(continues on next page)
Testing 773
CakePHP Book, Release 5.x
Inflector
class Cake\Utility\Inflector
The Inflector class takes a string and can manipulate it to handle word variations such as pluralization or camelizing
and is normally accessed statically. Example: Inflector::pluralize('example') returns “examples”.
You can try out the inflections online at inflector.cakephp.org181 or sandbox.dereuromark.de182 .
Quick summary of the Inflector built-in methods and the results they output when provided a multi-word argument:
181 https://inflector.cakephp.org/
182 https://sandbox.dereuromark.de/sandbox/inflector
775
CakePHP Book, Release 5.x
static Cake\Utility\Inflector::singularize($singular)
static Cake\Utility\Inflector::pluralize($singular)
Both pluralize and singularize() work on most English nouns. If you need to support other languages, you can
use Inflection Configuration to customize the rules used:
// Apples
echo Inflector::pluralize('Apple');
Note: pluralize() should not be used on a noun that is already in its plural form.
// Person
echo Inflector::singularize('People');
Note: singularize() should not be used on a noun that is already in its singular form.
static Cake\Utility\Inflector::camelize($underscored)
static Cake\Utility\Inflector::underscore($camelCase)
These methods are useful when creating class names, or property names:
// ApplePie
Inflector::camelize('Apple_pie')
// apple_pie
Inflector::underscore('ApplePie');
It should be noted that underscore will only convert camelCase formatted words. Words that contains spaces will be
lower-cased, but will not contain an underscore.
static Cake\Utility\Inflector::humanize($underscored)
This method is useful when converting underscored forms into “Title Case” forms for human readable values:
// Apple Pie
Inflector::humanize('apple_pie');
static Cake\Utility\Inflector::classify($underscored)
static Cake\Utility\Inflector::dasherize($dashed)
static Cake\Utility\Inflector::tableize($camelCase)
When generating code, or using CakePHP’s conventions you may need to inflect table names or class names:
// UserProfileSetting
Inflector::classify('user_profile_settings');
// user-profile-setting
Inflector::dasherize('UserProfileSetting');
// user_profile_settings
Inflector::tableize('UserProfileSetting');
static Cake\Utility\Inflector::variable($underscored)
Variable names are often useful when doing meta-programming tasks that involve generating code or doing work based
on conventions:
// applePie
Inflector::variable('apple_pie');
Inflection Configuration
CakePHP’s naming conventions can be really nice - you can name your database table big_boxes, your model
BigBoxes, your controller BigBoxesController, and everything just works together automatically. The way
CakePHP knows how to tie things together is by inflecting the words between their singular and plural forms.
There are occasions (especially for our non-English speaking friends) where you may run into situations where
CakePHP’s inflector (the class that pluralizes, singularizes, camelCases, and under_scores) might not work as you’d
like. If CakePHP won’t recognize your Foci or Fish, you can tell CakePHP about your special cases.
Define new inflection and transliteration rules for Inflector to use. Often, this method is used in your con-
fig/bootstrap.php:
Inflector::rules('uninflected', ['singulars']);
Inflector::rules('irregular', ['phylum' => 'phyla']); // The key is singular form, value␣
˓→is plural form
The supplied rules will be merged into the respective inflection sets defined in Cake/Utility/Inflector, with the
added rules taking precedence over the core rules. You can use Inflector::reset() to clear rules and restore the
original Inflector state.
Number
class Cake\I18n\Number
If you need NumberHelper functionalities outside of a View, use the Number class:
namespace App\Controller;
use Cake\I18n\Number;
}
}
}
All of these functions return the formatted number; they do not automatically echo the output into the view.
779
CakePHP Book, Release 5.x
This method is used to display a number in common currency formats (EUR, GBP, USD), based on the 3-letter ISO
4217 currency code. Usage in a view looks like:
// Called as NumberHelper
echo $this->Number->currency($value, $currency);
// Called as Number
echo Number::currency($value, $currency);
The first parameter, $value, should be a floating point number that represents the amount of money you are expressing.
The second parameter is a string used to choose a predefined currency formatting scheme:
The third parameter is an array of options for further defining the output. The following options are available:
Option Description
before Text to display before the rendered number.
after Text to display after the rendered number.
zero The text to use for zero values; can be a string or a number. ie. 0, ‘Free!’.
places Number of decimal places to use, ie. 2
precision Maximal number of decimal places to use, ie. 2
locale The locale name to use for formatting number, ie. “fr_FR”.
fractionSymbol String to use for fraction numbers, ie. ‘ cents’.
fractionPosition Either ‘before’ or ‘after’ to place the fraction symbol.
pattern An ICU number pattern to use for formatting the number ie. #,###.00
useIntlCode Set to true to replace the currency symbol with the international currency code.
If $currency value is null, the default currency will be retrieved from Cake\I18n\Number::defaultCurrency().
To format currencies in an accounting format you should set the currency format:
Number::setDefaultCurrencyFormat(Number::FORMAT_CURRENCY_ACCOUNTING);
Cake\I18n\Number::setDefaultCurrency($currency)
Setter for the default currency. This removes the need to always pass the currency to Cake\I18n\
Number::currency() and change all currency outputs by setting other default. If $currency is set to null, it
will clear the currently stored value.
Cake\I18n\Number::getDefaultCurrency()
Getter for the default currency. If default currency was set earlier using setDefaultCurrency(), then that value will
be returned. By default, it will retrieve the intl.default_locale ini value if set and 'en_US' if not.
This method displays a number with the specified amount of precision (decimal places). It will round in order to
maintain the level of precision defined.
// Called as NumberHelper
echo $this->Number->precision(456.91873645, 2);
// Outputs
456.92
// Called as Number
echo Number::precision(456.91873645, 2);
Formatting Percentages
Option Description
multiply Boolean to indicate whether the value has to be multiplied by 100. Useful for decimal percentages.
Like Cake\I18n\Number::precision(), this method formats a number according to the supplied precision (where
numbers are rounded to meet the given precision). This method also expresses the number as a percentage and appends
the output with a percent sign.
Cake\I18n\Number::toReadableSize(string $size)
This method formats data sizes in human readable forms. It provides a shortcut way to convert bytes to KB, MB, GB,
and TB. The size is displayed with a two-digit precision level, according to the size of data supplied (i.e. higher sizes
are expressed in larger terms):
// Called as NumberHelper
echo $this->Number->toReadableSize(0); // 0 Byte
echo $this->Number->toReadableSize(1024); // 1 KB
echo $this->Number->toReadableSize(1321205.76); // 1.26 MB
echo $this->Number->toReadableSize(5368709120); // 5 GB
// Called as Number
echo Number::toReadableSize(0); // 0 Byte
echo Number::toReadableSize(1024); // 1 KB
echo Number::toReadableSize(1321205.76); // 1.26 MB
echo Number::toReadableSize(5368709120); // 5 GB
Formatting Numbers
This method gives you much more control over the formatting of numbers for use in your views (and is used as the
main method by most of the other NumberHelper methods). Using this method might looks like:
// Called as NumberHelper
$this->Number->format($value, $options);
// Called as Number
Number::format($value, $options);
The $value parameter is the number that you are planning on formatting for output. With no $options supplied, the
number 1236.334 would output as 1,236. Note that the default precision is zero decimal places.
The $options parameter is where the real magic for this method resides.
• If you pass an integer then this becomes the amount of precision or places for the function.
• If you pass an associated array, you can use the following keys:
Option Description
places Number of decimal places to use, ie. 2
precision Maximum number of decimal places to use, ie. 2
pattern An ICU number pattern to use for formatting the number ie. #,###.00
locale The locale name to use for formatting number, ie. “fr_FR”.
before Text to display before the rendered number.
after Text to display after the rendered number.
Example:
// Called as NumberHelper
echo $this->Number->format('123456.7890', [
'places' => 2,
'before' => '¥ ',
'after' => ' !'
]);
// Output '¥ 123,456.79 !'
echo $this->Number->format('123456.7890', [
'locale' => 'fr_FR'
]);
// Output '123 456,79 !'
// Called as Number
echo Number::format('123456.7890', [
'places' => 2,
'before' => '¥ ',
'after' => ' !'
]);
// Output '¥ 123,456.79 !'
echo Number::format('123456.7890', [
'locale' => 'fr_FR'
]);
// Output '123 456,79 !'
echo Number::ordinal(1);
// Output '1st'
echo Number::ordinal(2);
// Output '2nd'
echo Number::ordinal(2, [
'locale' => 'fr_FR'
]);
// Output '2e'
echo Number::ordinal(410);
// Output '410th'
Format Differences
// Called as NumberHelper
$this->Number->formatDelta($value, $options);
// Called as Number
Number::formatDelta($value, $options);
The $value parameter is the number that you are planning on formatting for output. With no $options supplied, the
number 1236.334 would output as 1,236. Note that the default precision is zero decimal places.
The $options parameter takes the same keys as Number::format() itself:
Option Description
places Number of decimal places to use, ie. 2
precision Maximum number of decimal places to use, ie. 2
locale The locale name to use for formatting number, ie. “fr_FR”.
before Text to display before the rendered number.
after Text to display after the rendered number.
Example:
// Called as NumberHelper
echo $this->Number->formatDelta('123456.7890', [
'places' => 2,
'before' => '[',
'after' => ']'
]);
// Output '[+123,456.79]'
// Called as Number
echo Number::formatDelta('123456.7890', [
'places' => 2,
'before' => '[',
'after' => ']'
]);
// Output '[+123,456.79]'
Configure formatters
This method allows you to configure formatter defaults which persist across calls to various methods.
Example:
Number::config('en_IN', \NumberFormatter::CURRENCY, [
'pattern' => '#,##,##0'
]);
Registry Objects
The registry classes provide a simple way to create and retrieve loaded instances of a given object type. There are
registry classes for Components, Helpers, Tasks, and Behaviors.
While the examples below will use Components, the same behavior can be expected for Helpers, Behaviors, and Tasks
in addition to Components.
Loading Objects
$this->loadComponent('Acl.Acl');
$this->addHelper('Flash')
This will result in the Acl property and Flash helper being loaded. Configuration can also be set on-the-fly. Example:
Any keys and values provided will be passed to the Component’s constructor. The one exception to this rule is
className. Classname is a special key that is used to alias objects in a registry. This allows you to have compo-
nent names that do not reflect the classnames, which can be helpful when extending core components:
787
CakePHP Book, Release 5.x
Triggering Callbacks
Callbacks are not provided by registry objects. You should use the events system to dispatch any events/callbacks for
your application.
Disabling Callbacks
In previous versions, collection objects provided a disable() method to disable objects from receiving callbacks.
You should use the features in the events system to accomplish this now. For example, you could disable component
callbacks in the following way:
Text
class Cake\Utility\Text
The Text class includes convenience methods for creating and manipulating strings and is normally accessed statically.
Example: Text::uuid().
If you need Cake\View\Helper\TextHelper functionalities outside of a View, use the Text class:
namespace App\Controller;
use Cake\Utility\Text;
789
CakePHP Book, Release 5.x
Transliterate by default converts all characters in provided string into equivalent ASCII characters. The method expects
UTF-8 encoding. The character conversion can be controlled using transliteration identifiers which you can pass using
the $transliteratorId argument or change the default identifier string using Text::setTransliteratorId().
ICU transliteration identifiers are basically of form <source script>:<target script> and you can specify mul-
tiple conversion pairs separated by ;. You can find more info about transliterator identifiers here183 :
// apple puree
Text::transliterate('apple purée');
Slug transliterates all characters into ASCII versions and converting unmatched characters and spaces to dashes. The
slug method expects UTF-8 encoding.
You can provide an array of options that controls slug. $options can also be a string in which case it will be used as
replacement string. The supported options are:
• replacement Replacement string, defaults to ‘-‘.
• transliteratorId A valid tranliterator id string. If default null Text::$_defaultTransliteratorId to
be used. If false no transliteration will be done, only non words will be removed.
• preserve Specific non-word character to preserve. Defaults to null. For example, this option can be set to ‘.’
to generate clean file names:
// apple-puree
Text::slug('apple purée');
// apple_puree
Text::slug('apple purée', '_');
// foo-bar.tar.gz
Text::slug('foo bar.tar.gz', ['preserve' => '.']);
183 https://unicode-org.github.io/icu/userguide/transforms/general/#transliterator-identifiers
Generating UUIDs
static Cake\Utility\Text::uuid
The UUID method is used to generate unique identifiers as per RFC 4122184 . The UUID is a 128-bit string in the
format of 485fc381-e790-47a3-9794-1337c0a8fe68.
Text::uuid(); // 485fc381-e790-47a3-9794-1337c0a8fe68
Tokenizes a string using $separator, ignoring any instance of $separator that appears between $leftBound and
$rightBound.
This method can be useful when splitting up data that has regular formatting such as tag lists:
This method unformats a number from a human-readable byte size to an integer number of bytes:
$int = Text::parseFileSize('2GB');
Formatting Strings
The insert method is used to create string templates and to allow for key/value replacements:
Text::insert(
'My name is :name and I am :age years old.',
['name' => 'Bob', 'age' => '65']
);
// Returns: "My name is Bob and I am 65 years old."
Cleans up a Text::insert formatted string with given $options depending on the ‘clean’ key in $options. The
default method used is text but html is also available. The goal of this function is to replace all whitespace and unneeded
markup around placeholders that did not get replaced by Text::insert.
You can use the following options in the options array:
184 https://datatracker.ietf.org/doc/html/rfc4122.html
$options = [
'clean' => [
'method' => 'text', // or html
],
'before' => '',
'after' => ''
];
Wrapping Text
Wraps a block of text to a set width and indents blocks as well. Can intelligently wrap text so words are not sliced
across lines:
// Returns
This is the song that
never ends.
You can provide an array of options that control how wrapping is done. The supported options are:
• width The width to wrap to. Defaults to 72.
• wordWrap Whether or not to wrap whole words. Defaults to true.
• indent The character to indent lines with. Defaults to ‘’.
• indentAt The line number to start indenting text. Defaults to 0.
static Cake\Utility\Text::wrapBlock($text, $options = [])
If you need to ensure that the total width of the generated block won’t exceed a certain length even with internal
indentation, you need to use wrapBlock() instead of wrap(). This is particularly useful to generate text for the
console for example. It accepts the same options as wrap():
$text = 'This is the song that never ends. This is the song that never ends.';
$result = Text::wrapBlock($text, [
'width' => 22,
'indent' => ' → ',
'indentAt' => 1
]);
// Returns
This is the song that
→ never ends. This
→ is the song that
→ never ends.
Highlighting Substrings
Highlights $needle in $haystack using the $options['format'] string specified or a default string.
Options:
• format string - The piece of HTML with the phrase that will be highlighted
• html bool - If true, will ignore any HTML tags, ensuring that only the correct text is highlighted
Example:
// Called as TextHelper
echo $this->Text->highlight(
$lastSentence,
'using',
['format' => '<span class="highlight">\1</span>']
);
// Called as Text
use Cake\Utility\Text;
echo Text::highlight(
$lastSentence,
'using',
['format' => '<span class="highlight">\1</span>']
);
Output:
Removing Links
Cake\Utility\Text::stripLinks($text)
Truncating Text
If $text is longer than $length, this method truncates it at $length and adds a suffix consisting of 'ellipsis',
if defined. If 'exact' is passed as false, the truncation will occur at the first whitespace after the point at which
$length is exceeded. If 'html' is passed as true, HTML tags will be respected and will not be cut off.
$options is used to pass all extra parameters, and has the following possible keys by default, all of which are optional:
[
'ellipsis' => '...',
'exact' => true,
'html' => false
]
Example:
// Called as TextHelper
echo $this->Text->truncate(
'The killer crept forward and tripped on the rug.',
22,
[
'ellipsis' => '...',
'exact' => false
]
);
// Called as Text
use Cake\Utility\Text;
echo Text::truncate(
'The killer crept forward and tripped on the rug.',
22,
[
'ellipsis' => '...',
'exact' => false
]
);
Output:
If $text is longer than $length, this method removes an initial substring with length consisting of the difference and
prepends a prefix consisting of 'ellipsis', if defined. If 'exact' is passed as false, the truncation will occur at
the first whitespace prior to the point at which truncation would otherwise take place.
$options is used to pass all extra parameters, and has the following possible keys by default, all of which are optional:
[
'ellipsis' => '...',
'exact' => true
]
Example:
$sampleText = 'I packed my bag and in it I put a PSP, a PS3, a TV, ' .
'a C# program that can divide by zero, death metal t-shirts'
// Called as TextHelper
echo $this->Text->tail(
$sampleText,
70,
(continues on next page)
// Called as Text
use Cake\Utility\Text;
echo Text::tail(
$sampleText,
70,
[
'ellipsis' => '...',
'exact' => false
]
);
Output:
...a TV, a C# program that can divide by zero, death metal t-shirts
Extracting an Excerpt
Extracts an excerpt from $haystack surrounding the $needle with a number of characters on each side determined
by $radius, and prefix/suffix with $ellipsis. This method is especially handy for search results. The query string
or keywords can be shown within the resulting document.
// Called as TextHelper
echo $this->Text->excerpt($lastParagraph, 'method', 50, '...');
// Called as Text
use Cake\Utility\Text;
Output:
Creates a comma-separated list where the last two items are joined with ‘and’:
// Called as TextHelper
echo $this->Text->toList($colors);
// Called as Text
use Cake\Utility\Text;
echo Text::toList($colors);
Output:
class Cake\I18n\DateTime
If you need TimeHelper functionalities outside of a View, use the DateTime class:
use Cake\I18n\DateTime;
Under the hood, CakePHP uses Chronos185 to power its DateTime utility. Anything you can do with Chronos and
PHP’s DateTimeImmutable, you can do with DateTime.
For more details on Chronos please see the API documentation186 .
185 https://github.com/cakephp/chronos
186 https://api.cakephp.org/chronos/
797
CakePHP Book, Release 5.x
DateTime are immutable objects as immutability prevents accidental changes to data, and avoids order based depen-
dency issues.
There are a few ways to create DateTime instances:
use Cake\I18n\DateTime;
The DateTime class constructor can take any parameter that the internal DateTimeImmutable PHP class can. When
passing a number or numeric string, it will be interpreted as a UNIX timestamp.
In test cases, you can mock out now() using setTestNow():
// Fixate time.
$time = new DateTime('2021-01-31 22:11:30');
DateTime::setTestNow($time);
Manipulation
Remember, DateTime instance always return a new instance from setters instead of modifying itself:
$time = DateTime::now();
You can also use the methods provided by PHP’s built-in DateTime class:
Failing to reassign the new DateTime instances will result in the original, unmodified instance being used:
$time->year(2013)
->month(10)
->day(31);
// Outputs '2021-01-31 22:11:30'
echo $time->i18nFormat('yyyy-MM-dd HH:mm:ss');
You can create another instance with modified dates, through subtraction and addition of their components:
You can get the internal components of a date by accessing its properties:
Manipulation 799
CakePHP Book, Release 5.x
Formatting
static Cake\I18n\DateTime::setJsonEncodeFormat($format)
This method sets the default format used when converting an object to json:
Date::setJsonEncodeFormat(static function($time) {
return $time->format(DATE_ATOM);
});
Note: Be aware that this is not a PHP Datetime string format! You need to use a ICU date formatting
string as specified in the following resource: https://unicode-org.github.io/icu/userguide/format_parse/datetime/
#datetime-format-syntax.
A very common thing to do with Time instances is to print out formatted dates. CakePHP makes this a snap:
// Use the full date and time format. Outputs 'Sunday, January 31, 2021 at 10:11:30 PM␣
˓→Eastern Standard Time'
echo $time->i18nFormat(\IntlDateFormatter::FULL);
// Use full date but short time format. Outputs 'Sunday, January 31, 2021 at 10:11 PM'
echo $time->i18nFormat([\IntlDateFormatter::FULL, \IntlDateFormatter::SHORT]);
It is possible to specify the desired format for the string to be displayed. You can either pass IntlDateFormatter con-
stants187 as the first argument of this function, or pass a full ICU date formatting string as specified in the following
resource: https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax.
You can also format dates with non-gregorian calendars:
187 https://www.php.net/manual/en/class.intldateformatter.php
// Outputs 'Sunday, Twelfth Month 19, 2020(geng-zi) at 10:11:30 PM Eastern Standard Time'
echo $time->i18nFormat(\IntlDateFormatter::FULL, null, 'en-CN@calendar=chinese');
Note: For constant strings i.e. IntlDateFormatter::FULL Intl uses ICU library that feeds its data from CLDR
(https://cldr.unicode.org/) which version may vary depending on PHP installation and give different results.
Cake\I18n\DateTime::nice()
You can alter the timezone in which the date is displayed without altering the DateTime object itself. This is useful
when you store dates in one timezone, but want to display them in a user’s own timezone:
Formatting 801
CakePHP Book, Release 5.x
Leaving the first parameter as null will use the default formatting string:
The default locale in which dates are displayed when using nice i18nFormat is taken from the directive
intl.default_locale188 . You can, however, modify this default at runtime:
DateTime::setDefaultLocale('es-ES');
Date::setDefaultLocale('es-ES');
From now on, datetimes will be displayed in the Spanish preferred format unless a different locale is specified directly
in the formatting method.
Likewise, it is possible to alter the default formatting string to be used for i18nFormat:
It is recommended to always use the constants instead of directly passing a date format string.
188 https://www.php.net/manual/en/intl.configuration.php#ini.intl.default-locale
Note: Be aware that this is not a PHP Datetime string format! You need to use a ICU date formatting
string as specified in the following resource: https://unicode-org.github.io/icu/userguide/format_parse/datetime/
#datetime-format-syntax.
The end option lets you define at which point after which relative times should be formatted using the format option.
The accuracy option lets us control what level of detail should be used for each interval range:
By setting accuracy to a string, you can specify what is the maximum level of detail you want output:
Conversion
Cake\I18n\DateTime::toQuarter()
Once created, you can convert DateTime instances into timestamps or quarter values:
Conversion 803
CakePHP Book, Release 5.x
Cake\I18n\DateTime::isYesterday()
Cake\I18n\DateTime::isThisWeek()
Cake\I18n\DateTime::isThisMonth()
Cake\I18n\DateTime::isThisYear()
You can compare a DateTime instance with the present in a variety of ways:
debug($time->isYesterday());
debug($time->isThisWeek());
debug($time->isThisMonth());
debug($time->isThisYear());
Each of the above methods will return true/false based on whether or not the DateTime instance matches the present.
Cake\I18n\DateTime::isWithinNext($interval)
You can see if a DateTime instance falls within a given range using wasWithinLast() and isWithinNext():
Cake\I18n\DateTime::wasWithinLast($interval)
You can also compare a DateTime instance within a range in the past:
Date
class Cake\I18n\Date
The immutable Date class in CakePHP represents calendar dates unaffected by time and timezones. The Date class
wraps the Cake\\Chronos\\ChronosDate class.
Note: Unlike the DateTime class, Date does not extends the DateTimeInterface. So you cannot cannot directly
compare a Date instance with a DateTime instance. But you can do comparisons like $dateTime->toNative() >
$date->toNative().
Time
class Cake\I18n\Time
The Time class represents clock times independent of date or time zones Similar to the DateTime and `Date classes,
the Time class is also immutable. It wraps the Cake\\Chronos\\ChronosTime class.
When creating text inputs that manipulate dates, you’ll probably want to accept and parse localized datetime strings.
See the Parsing Localized Datetime Data.
Supported Timezones
CakePHP supports all valid PHP timezones. For a list of supported timezones, see this page189 .
189 https://php.net/manual/en/timezones.php
Date 805
CakePHP Book, Release 5.x
Xml
class Cake\Utility\Xml
The Xml class allows you to transform arrays into SimpleXMLElement or DOMDocument objects, and back into arrays
again.
You can load XML-ish data using Xml::build(). Depending on your $options parameter, this method will return
a SimpleXMLElement (default) or DOMDocument object. You can use Xml::build() to build XML objects from a
variety of sources. For example, you can load XML from strings:
You can also build Xml objects from local files by overriding the default option:
// Local file
$xml = Xml::build('/home/awesome/unicorns.xml', ['readFile' => true]);
807
CakePHP Book, Release 5.x
$data = [
'post' => [
'id' => 1,
'title' => 'Best post',
'body' => ' ... ',
]
];
$xml = Xml::build($data);
Note: DOMDocument190 and SimpleXML191 implement different APIs. Be sure to use the correct methods on the
object you request from Xml.
HTML documents can be parsed into SimpleXmlElement or DOMDocument objects with loadHtml():
By default entity loading and huge document parsing are disabled. These modes can be enabled with the
loadEntities and parseHuge options respectively.
toArray($obj);
Converting XML strings into arrays is simple with the Xml class as well. By default you’ll get a SimpleXml object
back:
Your array must have only one element in the “top level” and it can not be numeric. If the array is not in this format,
Xml will throw an exception. Examples of invalid arrays:
By default array values will be output as XML tags. If you want to define attributes or text values you can prefix the
keys that are supposed to be attributes with @. For value text, use @ as the key:
$xmlArray = [
'project' => [
'@id' => 1,
'name' => 'Name of project, as tag',
'@' => 'Value of project',
],
];
$xmlObject = Xml::fromArray($xmlArray);
$xmlString = $xmlObject->asXML();
<?xml version="1.0"?>
<project id="1">Value of project<name>Name of project, as tag</name></project>
Using Namespaces
To use XML Namespaces, create a key in your array with the name xmlns: in a generic namespace or input the prefix
xmlns: in a custom namespace. See the samples:
$xmlArray = [
'root' => [
'xmlns:' => 'https://cakephp.org',
'child' => 'value',
]
];
$xml1 = Xml::fromArray($xmlArray);
<?xml version="1.0"?>
<root xmlns="https://cakephp.org"><child>value</child>
<?xml version="1.0"?>
<root><tag xmlns:pref="https://cakephp.org"><pref:item>item 1</pref:item><pref:item>item␣
˓→2</pref:item></tag></root>
Creating a Child
After you have created your XML document, you just use the native interfaces for your document type to add, remove,
or manipulate child nodes:
// Using SimpleXML
$myXmlOriginal = '<?xml version="1.0"?><root><child>value</child></root>';
$xml = Xml::build($myXmlOriginal);
$xml->root->addChild('young', 'new value');
// Using DOMDocument
$myXmlOriginal = '<?xml version="1.0"?><root><child>value</child></root>';
$xml = Xml::build($myXmlOriginal, ['return' => 'domdocument']);
$child = $xml->createElement('young', 'new value');
$xml->firstChild->appendChild($child);
Tip: After manipulating your XML using SimpleXMLElement or DomDocument you can use Xml::toArray()
without a problem.
While most of your day-to-day work in CakePHP will be utilizing core classes and methods, CakePHP features a
number of global convenience functions that may come in handy. Many of these functions are for use with CakePHP
classes (loading model or component classes), but many others make working with arrays or strings a little easier.
We’ll also cover some of the constants available in CakePHP applications. Using these constants will help make
upgrades more smooth, but are also convenient ways to point to certain files or directories in your CakePHP application.
Global Functions
Here are CakePHP’s globally available functions. Most of them are just convenience wrappers for other CakePHP func-
tionality, such as debugging and translating content. By default only namespaced functions are autoloaded, however
you can optionally load global aliases by adding:
To your application’s config/bootstrap.php. Doing this will load global aliases for all functions listed below.
\_\_(string $string_id, [$formatArgs])
This function handles localization in CakePHP applications. The $string_id identifies the ID for a translation.
You can supply additional arguments to replace placeholders in your string:
Note: Check out the Internationalization & Localization section for more information.
811
CakePHP Book, Release 5.x
Note: Make sure to use the underscored version of the plugin name here as domain.
__dn(string $domain, string $singular, string $plural, integer $count, mixed $args = null)
Allows you to override the current domain for a single plural message lookup. Returns correct plural form of
message identified by $singular and $plural for count $count from domain $domain.
__dx(string $domain, string $context, string $msg, mixed $args = null)
Allows you to override the current domain for a single message lookup. It also allows you to specify a context.
The context is a unique identifier for the translations string that makes it unique within the same domain.
__dxn(string $domain, string $context, string $singular, string $plural, integer $count, mixed $args = null)
Allows you to override the current domain for a single plural message lookup. It also allows you to specify a
context. Returns correct plural form of message identified by $singular and $plural for count $count from
domain $domain. Some languages have more than one form for plural messages dependent on the count.
The context is a unique identifier for the translations string that makes it unique within the same domain.
__n(string $singular, string $plural, integer $count, mixed $args = null)
Returns correct plural form of message identified by $singular and $plural for count $count. Some lan-
guages have more than one form for plural messages dependent on the count.
__x(string $context, string $msg, mixed $args = null)
The context is a unique identifier for the translations string that makes it unique within the same domain.
__xn(string $context, string $singular, string $plural, integer $count, mixed $args = null)
Returns correct plural form of message identified by $singular and $plural for count $count from domain
$domain. It also allows you to specify a context. Some languages have more than one form for plural messages
dependent on the count.
The context is a unique identifier for the translations string that makes it unique within the same domain.
collection(mixed $items)
Convenience wrapper for instantiating a new Cake\Collection\Collection object, wrapping the passed ar-
gument. The $items parameter takes either a Traversable object or an array.
debug(mixed $var, boolean $showHtml = null, $showFrom = true)
If the core $debug variable is true, $var is printed out. If $showHTML is true or left as null, the data is
rendered to be browser-friendly. If $showFrom is not set to false, the debug output will start with the line from
which it was called. Also see Debugging
dd(mixed $var, boolean $showHtml = null)
It behaves like debug(), but execution is also halted. If the core $debug variable is true, $var is printed. If
$showHTML is true or left as null, the data is rendered to be browser-friendly. Also see Debugging
pr(mixed $var)
Convenience wrapper for print_r(), with the addition of wrapping <pre> tags around the output.
pj(mixed $var)
JSON pretty print convenience function, with the addition of wrapping <pre> tags around the output.
It is meant for debugging the JSON representation of objects and arrays.
constant Cake\Core\TESTS
Path to the tests directory.
constant Cake\Core\TMP
Path to the temporary files directory.
constant Cake\Core\WWW_ROOT
Full path to the webroot.
constant Cake\Core\TIME_START
Unix timestamp in microseconds as a float from when the application started.
Chronos
192 https://book.cakephp.org/chronos/2.x/en/
815
CakePHP Book, Release 5.x
Debug Kit
193 https://book.cakephp.org/debugkit/5.x/en/
817
CakePHP Book, Release 5.x
Migrations
194 https://book.cakephp.org/migrations/4/
819
CakePHP Book, Release 5.x
ElasticSearch
195 https://book.cakephp.org/elasticsearch/3/en/
821
CakePHP Book, Release 5.x
Appendices
Appendices contain information regarding the new features introduced in each version and the migration path between
versions.
If you need/want to shim 4.x behavior, or partially migrate in steps, check out the Shim plugin196 that can help mitigate
some BC breaking changes.
Forwards compatibility shimming can prepare your 4.x app for the next major release (5.x).
If you already want to shim 5.x behavior into 4.x, check out the Shim plugin197 . This plugin aims to mitigate some
backwards compatibility breakage and help backport features from 5.x to 4.x. The closer your 3.x app is to 4.x, the
smaller will be the diff of changes, and the smoother will be the final upgrade.
196 https://github.com/dereuromark/cakephp-shim
197 https://github.com/dereuromark/cakephp-shim
823
CakePHP Book, Release 5.x
General Information
Major Releases
Major releases introduce new features and can remove functionality deprecated in an earlier release. These releases
live in next branches that match their version number such as 5.next. Once released they are promoted into master
and then 5.next branch is used for future feature releases.
Feature Releases
Feature releases are where new features or extensions to existing features are shipped. Each release series receiving
updates will have a next branch. For example 4.next. If you would like to contribute a new feature please target these
branches.
Patch Releases
Patch releases fix bugs in existing code/documentation and should always be compatible with earlier patch releases
from the same feature release. These releases are created from the stable branches. Stable branches are often named
after the release series such as 3.x.
Release Cadence
• Major Releases are delivered approximately every two to three years. This timeframe forces us to be deliberate
and considerate with our breaking changes and gives time for the community to keep up without feeling like they
are being left behind.
• Feature Releases are delivered every five to eight months.
• Patch Releases Are initially delivered every two weeks. As a feature release matures this cadence relaxes to a
monthly schedule.
198 https://semver.org/
Deprecation Policy
Before a feature can be removed in a major release it needs to be deprecated. When a behavior is deprecated in release
A.x it will continue to work for remainder of all A.x releases. Deprecations are generally indicated via PHP warnings.
You can enable deprecation warnings by adding E_USER_DEPRECATED to your application’s Error.level value.
Once deprecated behavior is not removed until the next major release. For example behavior deprecated in 4.1 will be
removed in 5.0.
Glossary
CDN
Content Delivery Network. A 3rd party vendor you can pay to help distribute your content to data centers around
the world. This helps put your static assets closer to geographically distributed users.
columns
Used in the ORM when referring to the table columns in an database table.
CSRF
Cross Site Request Forgery. Prevents replay attacks, double submissions and forged requests from other domains.
DI Container
In Application::services() you can configure application services and their dependencies. Ap-
plication services are automatically injected into Controller actions, and Command Constructors. See
/development/dependency-injection.
DSN
Data Source Name. A connection string format that is formed like a URI. CakePHP supports DSNs for Cache,
Database, Log and Email connections.
dot notation
Dot notation defines an array path, by separating nested levels with . For example:
Cache.default.engine
[
'Cache' => [
'default' => [
'engine' => 'File'
]
]
]
DRY
Don’t repeat yourself. Is a principle of software development aimed at reducing repetition of information of all
kinds. In CakePHP DRY is used to allow you to code things once and re-use them across your application.
fields
A generic term used to describe both entity properties, or database columns. Often used in conjunction with the
FormHelper.
HTML attributes
An array of key => values that are composed into HTML attributes. For example:
// Given
['class' => 'my-class', 'target' => '_blank']
// Would generate
class="my-class" target="_blank"
If an option can be minimized or accepts its name as the value, then true can be used:
// Given
['checked' => true]
// Would generate
checked="checked"
PaaS
Platform as a Service. Platform as a Service providers will provide cloud based hosting, database and caching
resources. Some popular providers include Heroku, EngineYard and PagodaBox
properties
Used when referencing columns mapped onto an ORM entity.
plugin syntax
Plugin syntax refers to the dot separated class name indicating classes are part of a plugin:
routes.php
A file in config directory that contains routing configuration. This file is included before each request is pro-
cessed. It should connect all the routes your application needs so requests can be routed to the correct controller
+ action.
routing array
An array of attributes that are passed to Router::url(). They typically look like:
c
Cake\Cache, 505
Cake\Collection, 812
Cake\Console, 538
Cake\Console\Exception, 586
Cake\Controller, 185
Cake\Controller\Component, 196
Cake\Controller\Exception, 585
Cake\Core, 126
Cake\Core\Exception, 586
Cake\Database, 328
Cake\Database\Exception, 586
Cake\Database\Schema, 499
Cake\Datasource, 328
Cake\Datasource\Exception, 586
Cake\Error, 554
Cake\Form, 623
Cake\Http, 763
Cake\Http\Client, 770
Cake\Http\Cookie, 181
Cake\Http\Exception, 583
Cake\Http\TestSuite, 772
Cake\I18n, 797
Cake\Log, 620
Cake\Mailer, 563
Cake\ORM, 381
Cake\ORM\Behavior, 488
Cake\ORM\Exception, 586
Cake\ORM\Query\SelectQuery, 341
Cake\Routing, 133
Cake\Routing\Exception, 586
Cake\Utility, 807
Cake\Validation, 709
Cake\View, 207
Cake\View\Exception, 585
Cake\View\Helper, 310
827
CakePHP Book, Release 5.x
829
CakePHP Book, Release 5.x
beforeLayout() (Helper method), 319 Cake\ORM\Behavior (namespace), 474, 476, 478, 488
beforeMarshal() (Cake\ORM\Table method), 384 Cake\ORM\Exception (namespace), 586
beforeRender() (Cake\Controller\Controller method), Cake\ORM\Query\SelectQuery (namespace), 341
194 Cake\Routing (namespace), 133
beforeRender() (Helper method), 319 Cake\Routing\Exception (namespace), 586
beforeRenderFile() (Helper method), 319 Cake\Utility (namespace), 653, 747, 775, 789, 807
beforeRules() (Cake\ORM\Table method), 386 Cake\Validation (namespace), 709
beforeSave() (Cake\ORM\Table method), 386 Cake\View (namespace), 207
BreadcrumbsHelper (class in Cake\View\Helper), 230 Cake\View\Exception (namespace), 585
breakpoint() (global function), 553 Cake\View\Helper (namespace), 230, 234, 235, 277,
buffered() (Cake\Collection\Collection method), 744 290, 295, 305, 309, 310
build() (Cake\Utility\Xml method), 807 camelize() (Cake\Utility\Inflector method), 777
build() (Cake\View\Helper\UrlHelper method), 310 CDN, 825
buildFromArray() (Cake\Console\ConsoleOptionParser charset() (Cake\View\Helper\HtmlHelper method),
method), 541 278
buildFromPath() (Cake\View\Helper\UrlHelper check() (Cake\Core\Configure method), 127
method), 312 check() (Cake\Utility\Hash method), 754
buildRules() (Cake\ORM\Table method), 385 check() (Session method), 670
buildValidator() (Cake\ORM\Table method), 385 checkbox() (Cake\View\Helper\FormHelper method),
button() (Cake\View\Helper\FormHelper method), 266 252
CheckHttpCacheComponent (class), 200
C checkNotModified() (Cake\Http\Response method),
Cache (class in Cake\Cache), 505 180
CACHE (constant in Cake\Core), 813 chunk() (Cake\Collection\Collection method), 730
cache() (Cake\View\View method), 218 chunkWithKeys() (Cake\Collection\Collection
CacheEngine (class in Cake\Cache), 515 method), 730
CAKE (constant in Cake\Core), 813 classify() (Cake\Utility\Inflector method), 777
CAKE_CORE_INCLUDE_PATH (constant in Cake\Core), className() (Cake\Core\App method), 721
813 classPath() (Cake\Core\App method), 722
Cake\Cache (namespace), 505 cleanInsert() (Cake\Utility\Text method), 791
Cake\Collection (namespace), 725, 812 clear() (Cake\Cache\Cache method), 513
Cake\Console (namespace), 519, 521, 531, 538 clear() (Cake\Cache\CacheEngine method), 516
Cake\Console\Exception (namespace), 586 clear() (Cake\ORM\TableLocator method), 390
Cake\Controller (namespace), 185 clearGroup() (Cake\Cache\Cache method), 514
Cake\Controller\Component (namespace), 196 clearGroup() (Cake\Cache\CacheEngine method), 516
Cake\Controller\Exception (namespace), 585 Client (class in Cake\Http), 763
Cake\Core (namespace), 126, 721, 812 clientIp() (Cake\Http\ServerRequest method), 171
Cake\Core\Exception (namespace), 586 Collection (class in Cake\Collection), 725
Cake\Database (namespace), 328 Collection (class in Cake\Database\Schema), 502
Cake\Database\Exception (namespace), 586 collection() (function in Cake\Collection), 812
Cake\Database\Schema (namespace), 499 columns, 825
Cake\Datasource (namespace), 328 combine() (Cake\Collection\Collection method), 728
Cake\Datasource\Exception (namespace), 586 combine() (Cake\Utility\Hash method), 750
Cake\Error (namespace), 554 Command (class in Cake\Console), 521
Cake\Form (namespace), 623 compile() (Cake\Collection\Collection method), 745
Cake\Http (namespace), 163, 763 CONFIG (constant in Cake\Core), 813
Cake\Http\Client (namespace), 770 config() (Cake\I18n\Number method), 784
Cake\Http\Cookie (namespace), 181 configuration, 121
Cake\Http\Exception (namespace), 583 Configure (class in Cake\Core), 126
Cake\Http\TestSuite (namespace), 772 configured() (Cake\Log\Log method), 620
Cake\I18n (namespace), 779, 797, 811 ConflictException, 584
Cake\Log (namespace), 620 Connection (class in Cake\Database), 336
Cake\Mailer (namespace), 563 ConnectionManager (class in Cake\Datasource), 328
Cake\ORM (namespace), 381, 391, 413, 448, 471 ConsoleException, 586
830 Index
CakePHP Book, Release 5.x
Index 831
CakePHP Book, Release 5.x
832 Index
CakePHP Book, Release 5.x
J MissingEntityException, 586
JsonView (class), 229 MissingExtensionException, 586
MissingHelperException, 585
L MissingLayoutException, 585
MissingRouteException, 586
label() (Cake\View\Helper\FormHelper method), 262
MissingTableException, 586
last() (Cake\Collection\Collection method), 741
MissingTemplateException, 585
last() (Cake\View\Helper\PaginatorHelper method),
MissingViewException, 585
300
month() (Cake\View\Helper\FormHelper method), 261
levels() (Cake\Log\Log method), 620
limitControl() (Cake\View\Helper\PaginatorHelper
method), 302
N
link() (Cake\View\Helper\HtmlHelper method), 282 namespaceSplit() (function in Cake\Core), 813
linkFromPath() (Cake\View\Helper\HtmlHelper nest() (Cake\Collection\Collection method), 737
method), 283 nest() (Cake\Utility\Hash method), 761
listNested() (Cake\Collection\Collection method), nested commands, 520
738 nestedList() (Cake\View\Helper\HtmlHelper method),
load() (Cake\Core\Configure method), 129 286
loadComponent() (Cake\Controller\Controller newClientResponse()
method), 194 (Cake\Http\TestSuite\HttpClientTrait method),
Log (class in Cake\Log), 620 772
log() (Cake\Error\Debugger method), 555 next() (Cake\View\Helper\PaginatorHelper method),
log() (Cake\Log\LogTrait method), 621 300
LOGS (constant in Cake\Core), 813 nice() (Cake\I18n\DateTime method), 801
LogTrait (trait in Cake\Log), 621 normalize() (Cake\Utility\Hash method), 760
NotAcceptableException, 584
M NotFoundException, 583
notice() (Cake\Log\Log method), 620
Mailer (class in Cake\Mailer), 563
NotImplementedException, 584
map() (Cake\Collection\Collection method), 726
Number (class in Cake\I18n), 779
map() (Cake\Database\TypeFactory method), 331
NumberHelper (class in Cake\View\Helper), 290
map() (Cake\Utility\Hash method), 758
numbers() (Cake\View\Helper\PaginatorHelper
match() (Cake\Collection\Collection method), 731
method), 298
max() (Cake\Collection\Collection method), 732
numeric() (Cake\Utility\Hash method), 757
maxDimensions() (Cake\Utility\Hash method), 757
media() (Cake\View\Helper\HtmlHelper method), 284 O
median() (Cake\Collection\Collection method), 733
merge() (Cake\Console\ConsoleOptionParser method), options() (Cake\View\Helper\PaginatorHelper
541 method), 302
merge() (Cake\Utility\Hash method), 756 ordinal() (Cake\I18n\Number method), 783
mergeDiff() (Cake\Utility\Hash method), 759 ordinal() (Cake\View\Helper\NumberHelper method),
meta() (Cake\View\Helper\HtmlHelper method), 279 294
MethodNotAllowedException, 584
middleware() (Cake\Controller\Controller method),
P
195 PaaS, 826
min() (Cake\Collection\Collection method), 732 paginate() (Cake\Controller\Controller method), 193
MissingActionException, 585 PaginatorHelper (class in Cake\View\Helper), 295
MissingBehaviorException, 586 parseFileSize() (Cake\Utility\Text method), 791
MissingCellException, 585 passed arguments, 153
MissingCellViewException, 585 password() (Cake\View\Helper\FormHelper method),
MissingComponentException, 585 247
MissingConnectionException, 586 path() (Cake\Core\App method), 722
MissingControllerException, 586 PersistenceFailedException, 586
MissingDriverException, 586 php:attr (directive), 87
MissingElementException, 585 php:attr (role), 88
php:class (directive), 86
Index 833
CakePHP Book, Release 5.x
834 Index
CakePHP Book, Release 5.x
Index 835
CakePHP Book, Release 5.x
V
Validator (class in Cake\Validation), 709
variable() (Cake\Utility\Inflector method), 778
vendor/cakephp-plugins.php, 647
View (class in Cake\View), 207
W
warning() (Cake\Log\Log method), 620
wasWithinLast() (Cake\I18n\DateTime method), 804
withBody() (Cake\Http\Response method), 176
withCache() (Cake\Http\Response method), 177
withCharset() (Cake\Http\Response method), 177
withDisabledCache() (Cake\Http\Response method),
177
withEtag() (Cake\Http\Response method), 179
withExpires() (Cake\Http\Response method), 178
withFile() (Cake\Http\Response method), 174
withHeader() (Cake\Http\Response method), 175
withModified() (Cake\Http\Response method), 179
withSharable() (Cake\Http\Response method), 178
withStringBody() (Cake\Http\Response method), 176
withType() (Cake\Http\Response method), 174
withUploadedFiles() (Cake\Http\ServerRequest
method), 166
withVary() (Cake\Http\Response method), 180
wrap() (Cake\Utility\Text method), 792
wrapBlock() (Cake\Utility\Text method), 792
write() (Cake\Cache\Cache method), 509
write() (Cake\Cache\CacheEngine method), 515
write() (Cake\Core\Configure method), 126
write() (Cake\Log\Log method), 620
write() (Session method), 670
writeMany() (Cake\Cache\Cache method), 510
WWW_ROOT (constant in Cake\Core), 814
X
Xml (class in Cake\Utility), 807
XmlView (class), 228
Y
year() (Cake\View\Helper\FormHelper method), 261
Z
zip() (Cake\Collection\Collection method), 735
836 Index