Cake PHPBook
Cake PHPBook
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 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Additional Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
3 Migration Guides 31
5.0 Upgrade Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
5.0 Migration Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
5.1 Migration Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
5.2 Migration Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
PHPUnit 10 Upgrade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
5 Contributing 83
Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
Tickets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
i
Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Coding Standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
Backwards Compatibility Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
6 Installation 109
Installing CakePHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Permissions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
Development Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
Production . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
Fire It Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
URL Rewriting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
7 Configuration 121
Configuring your Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Environment Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
Additional Class Paths . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
Inflection Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Configure Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Reading and writing configuration files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
8 Routing 131
Quick Tour . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
Connecting Routes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
Route Scoped Middleware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
RESTful Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Passed Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
Generating URLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
Generating Asset URLs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
Redirect Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
Entity Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
Custom Route Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
Creating Persistent URL Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
10 Controllers 183
The App Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
Request Flow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
Controller Actions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
Interacting with Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
Content Type Negotiation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
Content Type Negotiation Fallbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
Using AjaxView . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
Redirecting to Other Pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
Loading Additional Tables/Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
Paginating a Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
Configuring Components to Load . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
Request Life-cycle Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
Controller Middleware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
More on Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
ii
11 Views 205
The App View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
View Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
Extending Layouts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
Using View Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
Layouts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
View Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
Creating Your Own View Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
More About Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
13 Caching 495
Configuring Cache Engines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 496
Writing to a Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 499
Reading From a Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 501
Deleting From a Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 502
Clearing Cached Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 502
Using Cache to Store Counters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 503
Using Cache to Store Common Query Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 503
Using Groups . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 503
Globally Enable or Disable Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 504
Creating a Cache Engine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505
16 Debugging 543
Basic Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 543
Using the Debugger Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 544
Outputting Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 544
Logging With Stack Traces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 545
Generating Stack Traces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 545
Getting an Excerpt From a File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 545
Editor Integration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 546
Using Logging to Debug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 546
Debug Kit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 547
17 Deployment 549
Moving files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 549
Adjusting Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 549
Check Your Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 550
Set Document Root . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 550
Improve Your Application’s Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 550
Deploying an update . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 551
iii
18 Mailer 553
Basic Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 553
Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 554
Setting Headers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 555
Sending Templated Emails . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 555
Sending Attachments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 557
Sending Emails from CLI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 558
Creating Reusable Emails . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 558
Configuring Transports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 560
Sending emails without using Mailer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 562
Testing Mailers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 562
22 Logging 601
Logging Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 601
Error and Exception Logging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 602
Writing to Logs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 602
Logging to Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 605
Logging to Syslog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 605
Creating Log Engines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 606
Logging Formatters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 607
Testing Logs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 607
Log API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 608
Logging Trait . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 609
Using Monolog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 609
iv
Processing Request Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 612
Setting Form Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 613
Getting Form Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 614
Invalidating Individual Form Fields from Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 614
Creating HTML with FormHelper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 615
24 Pagination 617
Basic Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 617
Advanced Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 618
Simple Pagination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 619
Paginating Multiple Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 619
Control which Fields Used for Ordering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 621
Limit the Maximum Number of Rows per Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 621
Out of Range Page Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 621
Using a paginator class directly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 622
Pagination in the View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 622
25 Plugins 623
Installing a Plugin With Composer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 623
Manually Installing a Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 624
Loading a Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 624
Plugin Hook Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 624
Plugin Loading Options . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 625
Loading plugins through Application::bootstrap() . . . . . . . . . . . . . . . . . . . . . . . . . . . 625
Using Plugin Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 626
Creating Your Own Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 627
Plugin Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 628
Plugin Routes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 629
Plugin Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 630
Plugin Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 631
Plugin Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 632
Plugin Assets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 633
Components, Helpers and Behaviors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 634
Commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 634
Testing your Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 634
Publishing your Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 635
Plugin Map File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 635
Manage Your Plugins using Mixer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 635
26 REST 637
Getting Started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 637
Encoding Response Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 639
Parsing Request Bodies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 639
27 Security 641
Security Utility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 641
CSRF Protection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 643
Content Security Policy Middleware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 646
Security Header Middleware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 647
HTTPS Enforcer Middleware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 647
28 Sessions 649
Session Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 649
Built-in Session Handlers & Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 650
Setting ini directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 653
v
Creating a Custom Session Handler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 653
Accessing the Session Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 655
Reading & Writing Session Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 655
Destroying the Session . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 656
Rotating Session Identifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 656
Flash Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 656
29 Testing 657
Installing PHPUnit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 657
Test Database Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 658
Checking the Test Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 658
Test Case Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 659
Creating Your First Test Case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 659
Running Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 661
Test Case Lifecycle Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 662
Fixtures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 663
Loading Routes in Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 670
Testing Table Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 671
Controller Integration Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 673
Console Integration Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 684
Mocking Injected Dependencies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 685
Mocking HTTP Client Responses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 685
Testing Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 685
Testing Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 685
Testing Helpers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 687
Testing Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 688
Testing Email . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 690
Testing Logging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 690
Creating Test Suites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 690
Creating Tests for Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 690
Generating Tests with Bake . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 691
30 Validation 693
Creating Validators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 693
Make Rules ‘last’ by default . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 698
Validating Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 701
Validating Entity Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 702
Core Validation Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 703
32 Collections 709
Quick Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 709
List of Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 710
Iterating . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 710
Filtering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 715
Aggregation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 716
Sorting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 720
Working with Tree Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 721
Other Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 723
vi
33 Hash 731
Hash Path Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 731
35 Inflector 759
Summary of Inflector Methods and Their Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 759
Creating Plural & Singular Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 760
Creating CamelCase and under_scored Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 760
Creating Human Readable Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 761
Creating Table and Class Name Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 761
Creating Variable Names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 761
Inflection Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 762
36 Number 763
Formatting Currency Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 764
Setting the Default Currency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 764
Getting the Default Currency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 765
Formatting Floating Point Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 765
Formatting Percentages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 765
Interacting with Human Readable Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 765
Formatting Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 766
Format Differences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 767
Configure formatters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 768
38 Text 771
Convert Strings into ASCII . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 772
Creating URL Safe Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 772
Generating UUIDs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 772
Simple String Parsing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 773
Formatting Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 773
Wrapping Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 773
Highlighting Substrings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 774
Truncating Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 775
Truncating the Tail of a String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 776
Extracting an Excerpt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 777
Converting an Array to Sentence Form . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 777
vii
39 Date & Time 779
Creating DateTime Instances . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 780
Manipulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 780
Formatting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 781
Conversion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 785
Comparing With the Present . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 785
Comparing With Intervals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 786
Date . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 786
Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 786
Accepting Localized Request Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 787
Supported Timezones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 787
40 Xml 789
Loading XML documents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 789
Loading HTML documents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 790
Transforming a XML String in Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 790
Transforming an Array into a String of XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 790
42 Chronos 797
44 Migrations 801
45 ElasticSearch 803
46 Appendices 805
Migration Guides . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805
Backwards Compatibility Shimming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805
Forwards Compatibility Shimming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805
General Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805
Index 811
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.
use Cake\ORM\Locator\LocatorAwareTrait;
$users = $this->fetchTable('Users');
$resultset = $users->find()->all();
(continues on next page)
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 provides a number of extension points like View Templates, Elements and View Cells to let you re-use
your presentation logic.
The View layer is not only limited to HTML or text representation of the data. It can be used to deliver common data
formats like JSON, XML, and through a pluggable architecture any other format you may need, such as CSV.
You may notice that we never explicitly rendered a view. CakePHP’s conventions will take care of selecting the right
view and rendering it with the view data we prepared with set().
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.
Additional Reading
Where to Get Help
The Official CakePHP website
https://cakephp.org
The Official CakePHP website is always a great place to visit. It features links to oft-used developer tools, screencasts,
donation opportunities, and downloads.
The Cookbook
https://book.cakephp.org
This manual should probably be the first place you go to get answers. As with many other open source projects, we
get new folks regularly. Try your best to answer your questions on your own first. Answers may come slower, but will
remain longer – and you’ll also be lightening our support load. Both the manual and the API have an online component.
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.
tests/TestCase/
Slack
CakePHP Slack Support Channel4
If you’re stumped, give us a holler in the CakePHP Slack support channel. We’d love to hear from you, whether you
need some help, want to find users in your area, or would like to donate your brand new sports car.
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.
French
Additional Reading 5
CakePHP Book, Release 5.x
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.
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:
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/
$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.
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.
Additional Reading 7
CakePHP Book, Release 5.x
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
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.
17 https://github.com/FriendsOfCake/awesome-cakephp/blob/master/CONTRIBUTING.md#tips-for-creating-cakephp-plugins
Additional Reading 9
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.
ò Note
The folder Command is not present by default. You can add it when you need it.
Additional Reading 11
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.
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.
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:
13
CakePHP Book, Release 5.x
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
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.
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:
bin/cake migrations migrate
Fill the seed data above into the new UsersSeed and ArticlesSeed classes, then:
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 ArticleTable.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:
bin/cake bake model articles
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.
<?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.
<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.
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.
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;
(continues on next page)
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.
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 features23 . 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 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:
<?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.
// 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>
// 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.
// 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->deleteLink(
'Delete',
['action' => 'delete', $article->slug],
['confirm' => 'Are you sure?'])
?>
</td>
</tr>
<?php endforeach; ?>
</table>
Using deleteLink() will create a link that uses JavaScript to do a DELETE request deleting our article. Prior to
CakePHP 5.2 you need to use postLink() instead.
ò Note
This view code also uses the FormHelper to prompt the user with a JavaScript confirmation dialog before they
attempt to delete an article.
Tip
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 5.x minor releases.
'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.
ò Note
31
CakePHP Book, Release 5.x
ò 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:
Breaking Changes
In addition to the removal of deprecated features there have been breaking changes made:
25 https://github.com/cakephp/app/blob/5.x/composer.json
26 https://github.com/cakephp/app/blob/5.x/
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
• BaseCommand::__construct() was removed.
• ConsoleIntegrationTestTrait::useCommandRunner() was removed since it’s no longer needed.
• Shell has been removed and should be replaced with Command29
27 https://book.cakephp.org/authentication/3/en/index.html
28 https://book.cakephp.org/authorization/3/en/index.html
29 https://book.cakephp.org/5/en/console-commands/commands.html
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
• 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.
30 https://book.cakephp.org/4/en/appendices/4-4-migration-guide.html#requesthandlercomponent
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.
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()
31 https://book.cakephp.org/4/en/appendices/4-4-migration-guide.html#errorhandler-consoleerrorhandler
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
• FrozenDate was renamed to Date and FrozenTime was renamed to DateTime.
• Time now extends Cake\Chronos\ChronosTime and is therefore immutable.
• Date objects do not extend DateTimeInterface anymore - therefore you can’t compare them with DateTime
objects. See the cakephp/chronos release documentation32 for more information.
• Date::parseDateTime() was removed.
• Date::parseTime() was removed.
• Date::setToStringFormat() and Date::setJsonEncodeFormat() no longer accept an array.
• Date::i18nFormat() and Date::nice() no longer accept a timezone parameter.
• 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
• Email has been removed. Use Mailer33 instead.
• cake.mailer has been added as an alternative to the email scope
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.
32 https://github.com/cakephp/chronos/releases/tag/3.0.2
33 https://book.cakephp.org/5/en/core-libraries/email.html
• 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.
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
• ViewBuilder options are now truly associative (string keys).
• NumberHelper and TextHelper no longer accept an engine config.
• ViewBuilder::setHelpers() parameter $merge was removed. Use ViewBuilder::addHelpers() in-
stead.
• Inside View::initialize(), prefer using addHelper() instead of loadHelper(). All configured helpers
will be loaded afterwards, anyway.
• View\Widget\FileWidget is no longer compatible with PHP file upload shaped arrays. This is aligned with
ServerRequest and Validation changes.
• FormHelper no longer sets autocomplete=off on CSRF token fields. This was a workaround for a Safari bug
that is no longer relevant.
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
Improved type checking
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
• The services() method was added to PluginInterface.
• PluginCollection::addFromConfig() has been added to simplify plugin loading.
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
Required Entity Fields
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.
return $query;
}
{
if ($categoryId) {
$query->where(['category_id' => $categoryId]);
}
if ($userId) {
$query->where(['user_id' => $userId]);
}
(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.
Behavior Changes
• Connection now creates unique read and write drivers if the keys read or write are present in the config re-
gardless of values.
• FormHelper no longer generates aria-required attributes on input elements that also have the required
attribute set. The aria-required attribute is redundant on these elements and generates HTML validation
warnings. If you are using aria-required attribute in styling or scripting you’ll need to update your application.
• Adding associations with duplicate names will now raise exceptions. You can use
$table->associations()->has() to conditionally define associations if required.
• Text Utility and TextHelper methods around truncation and maximum length are using a UTF-8 character for
ellipsis instead of ... legacy characters.
• TableSchema::setColumnType() now throws an exception if the specified column does not exist.
• PluginCollection::addPlugin() now throws an exception if a plugin of the same name is already added.
• TestCase::loadPlugins() will now clear out any previously loaded plugins. So you must specify all plugins
required for any subsequent tests.
• The hashing algorithm for Cache configurations that use groups. Any keys will have new group prefix hashes
generated which will cause cache misses. Consider an incremental deploy to avoid operating on an entirely cold
cache.
• FormHelper::getFormProtector() now returns null in addition to its previous types. This allows dynamic
view code to run with fewer errors and shouldn’t impact most applications.
• The default value for valueSeparator in Table::findList() is now a single space instead of ;.
• ErrorLogger uses Psr\Log\LogTrait now.
• Database\QueryCompiler::$_orderedUnion was removed.
Deprecations
I18n
• The _cake_core_ cache config key has been renamed to _cake_translations_.
Mailer
• Mailer::setMessage() is deprecated. It has unintuitive behavior and very low usage.
New Features
Cache
• RedisEngine now supports a tls option that enables connecting to redis over a TLS connection. You can use
the ssl_ca, ssl_cert and ssl_key options to define the TLS context for redis.
Command
• bin/cake plugin list has been added to list all available plugins, their load configuration and version.
• Optional Command arguments can now have a default value.
• BannerHelper was added. This command helper can format text as a banner with a coloured background and
padding.
• Additional default styles for info.bg, warning.bg, error.bg and success.bg were added to
ConsoleOutput.
Console
• Arguments::getBooleanOption() and Arguments::getMultipleOption() were added.
• Arguments::getArgument() will now raise an exception if an unknown argument name is provided. This
helps prevent mixing up option/argument names.
Controller
• Components can now use the DI container to have dependencies resolved and provided as constructor parameters
just like Controllers and Commands do.
Core
• PluginConfig was added. Use this class to get all available plugins, their load config and versions.
• The toString, toInt, toBool functions were added. They give you a typesafe way to cast request data or other
input and return null when conversion fails.
• pathCombine() was added to help build paths without worrying about duplicate and trailing slashes.
• A new events hook was added to the BaseApplication as well as the BasePlugin class. This hook is the
recommended way to register global event listeners for you application. See Registering Listeners
Database
• Support for point, linestring, polygon and geometry types were added. These types are useful when
working with geospatial or cartesian co-ordinates. Sqlite support uses text columns under the hood and lacks
functions to manipulate data as geospatial values.
• SelectQuery::__debugInfo() now includes which connection role the query is for.
• SelectQuery::intersect() and SelectQuery::intersectAll() were added. These methods enable
queries using INTERSECT and INTERSECT ALL conjunctions to be expressed.
• New supports features were added for intersect, intersect-all and set-operations-order-by features.
• The ability to fetch records without buffering which existed in 4.x has been restored. Methods
SelectQuery::enableBufferedResults(), SelectQuery::disableBufferedResults() and
SelectQuery::isBufferedResultsEnabled() have been re-added.
Datasource
• RulesChecker::remove(), removeCreate(), removeUpdate(), and removeDelete() methods were
added. These methods allow you to remove rules by name.
Http
• SecurityHeadersMiddleware::setPermissionsPolicy() was added. This method adds the ability to de-
fine permissions-policy header values.
• Client now emits HttpClient.beforeSend and HttpClient.afterSend events when requests are sent.
You can use these events to perform logging, caching or collect telemetry.
• Http\Server::terminate() was added. This method triggers the Server.terminate event which can be
used to run logic after the response has been sent in fastcgi environments. In other environments the Server.
terminate event runs before the response has been sent.
I18n
• Number::formatter() and currency() now accept a roundingMode option to override how rounding is
done.
• The toDate, and toDateTime functions were added. They give you a typesafe way to cast request data or other
input and return null when conversion fails.
ORM
• Setting the preserveKeys option on association finder queries. This can be used with formatResults() to
replace association finder results with an associative array.
• SQLite columns with names containing json can now be mapped to JsonType. This is currently an opt-in
feature which is enabled by setting the ORM.mapJsonTypeForSqlite configure value to true in your app.
TestSuite
• CakePHP as well as the app template have been updated to use PHPUnit ^10.5.5 || ^11.1.3".
• ConnectionHelper methods are now all static. This class has no state and its methods were updated to be static.
• LogTestTrait was added. This new trait makes it easy to capture logs in your tests and make assertions on the
presence or absence of log messages.
• IntegrationTestTrait::replaceRequest() was added.
Utility
• Hash::insert() and Hash::remove() now accept ArrayAccess objects along with array data.
Validation
• Validation::enum() and Validator::enum() were added. These validation methods simplify validating
backed enum values.
• Validation::enumOnly() and Validation::enumExcept() were added to check for specific cases and
further simplify validating backed enum values.
View
• View cells now emit events around their actions Cell.beforeAction and Cell.afterAction.
• NumberHelper::format() now accepts a roundingMode option to override how rounding is done.
Helpers
• TextHelper::autoLinkUrls() has options added for better link label printing: * stripProtocol: Strips
http:// and https:// from the beginning of the link. Default off. * maxLength: The maximum length of the
link label. Default off. * ellipsis: The string to append to the end of the link label. Defaults to UTF8 version.
• HtmlHelper::meta() can now create a meta tag containing the current CSRF token using
meta('csrfToken').
Behavior Changes
• ValidationSet::add() will now raise errors when a rule is added with a name that is already defined. This
change aims to prevent rules from being overwritten by accident.
• Http\Session will now raise an exception when an invalid session preset is used.
• FormProtectionComponent now raises Cake\Controller\Exception\FormProtectionException.
This class is a subclass of BadRequestException, and offers the benefit of being filterable from logging.
• NumericPaginator::paginate() now uses the finder option even when a SelectQuery instance is passed
to it.
Deprecations
Console
• Arguments::getMultipleOption() is deprecated. Use getArrayOption() instead.
Datasource
• The ability to cast an EntityInterface instance to string has been deprecated. You should json_encode()
the entity instead.
• Mass assigning multiple entity fields using EntityInterface::set() is deprecated. Use
EntityInterface::patch() instead. For e.g. change usage like $entity->set(['field1' =>
'value1', 'field2' => 'value2']) to $entity->patch(['field1' => 'value1', 'field2'
=> 'value2']).
Event
• Returning values from event listeners / callbacks is deprecated. Use $event->setResult() instead or
$event->stopPropogation() to just stop the event propogation.
View
• The errorClass option of FormHelper has been deprecated in favour of using a template string. To upgrade
move your errorClass definition to a template set. See Creating DELETE Links.
New Features
Console
• The cake counter_cache command was added. This command can be used to regenerate counters for models
that use CounterCacheBehavior.
• ConsoleIntegrationTestTrait::debugOutput() makes it easier to debug integration tests for console
commands.
• ConsoleInputArgument now supports a separator option. This option allows positional arguments to be
delimited with a character sequence like ,. CakePHP will split the positional argument into an array when
arguments are parsed.
• Arguments::getArrayArgumentAt(), and Arguments::getArrayArgument() were added. These meth-
ods allow you to read separator delimitered positional arguments as arrays.
• ConsoleInputOption now supports a separator option. This option allows option values to be delimited
with a character sequence like ,. CakePHP will split the option value into an array when arguments are parsed.
• Arguments::getArrayArgumentAt(), Arguments::getArrayArgument(), and
Arguments::getArrayOption() were added. These methods allow you to read separator delimitered
positional arguments as arrays.
Database
• The nativeuuid type was added. This type enables uuid columns to be used in Mysql connections with Mari-
aDB. In all other drivers, nativeuuid is an alias for uuid.
• Cake\Database\Type\JsonType::setDecodingOptions() was added. This method lets you define the
value for the $flags argument of json_decode().
• CounterCacheBehavior::updateCounterCache() was added. This method allows you to update the
counter cache values for all records of the configured associations. CounterCacheCommand was also added
to do the same through the console.
• Cake\Database\Driver::quote() was added. This method provides a way to quote values to be used in SQL
queries where prepared statements cannot be used.
Datasource
• Application rules can now use Closure to define the validation message. This allows you to create dynamic
validation messages based on the entity state and validation rule options.
Error
• Custom exceptions can have specific error handling logic defined in ErrorController.
ORM
• CounterCacheBehavior::updateCounterCache() has been added. This method allows you to update the
counter cache values for all records of the configured associations.
• BelongsToMany::setJunctionProperty() and getJunctionProperty() were added. These methods al-
low you to customize the _joinData property that is used to hydrate junction table records.
• Table::findOrCreate() now accepts an array as second argument to directly pass data in.
TestSuite
• TestFixture::$strictFields was added. Enabling this property will make fixtures raise an error if a fix-
ture’s record list contains fields that do not exist in the schema.
View
• FormHelper::deleteLink() has been added as convenience wrapper for delete links in templates using
DELETE method.
• HtmlHelper::importmap() was added. This method allows you to define import maps for your JavaScript
files.
• FormHelper now uses the containerClass template to apply a class to the form control div. The default value
is input.
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.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 command!
With this command out of the way your phpunit.xml already has most of the recommended changes present.
PHPUnit 10 Upgrade 45
CakePHP Book, Release 5.x
<extensions>
<extension class="Cake\TestSuite\Fixture\PHPUnitExtension"/>
</extensions>
to:
<extensions>
<bootstrap class="Cake\TestSuite\Fixture\Extension\PHPUnitExtension"/>
</extensions>
->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
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.
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/
47
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
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.
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:
bin/cake migrations migrate
Fill the seed data above into the new UsersSeed and ArticlesSeed classes, then:
<?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 ArticleTable.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:
bin/cake bake model articles
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.
<?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.
<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>
(continues on next page)
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.
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.
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.
• If for some reason it doesn’t save, just render the view. This gives us a chance to show the user validation errors
or other warnings.
Every CakePHP request includes a request object which is accessible using $this->request. The request object
contains information regarding the request that was just received. We use the Cake\Http\ServerRequest::is()
method to check that the request is a HTTP POST request.
Our POST data is available in $this->request->getData(). You can use the pr() or debug() functions to print
it out if you want to see what it looks like. To save our data, we first ‘marshal’ the POST data into an Article Entity.
The Entity is then persisted using the ArticlesTable we created earlier.
After saving our new article we use FlashComponent’s success() method to set a message into the 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
<?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.
// 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:
<!-- File: templates/Articles/index.php (edit links added) -->
<h1>Articles</h1>
<p><?= $this->Html->link("Add Article", ['action' => 'add']) ?></p>
<table>
<tr>
<th>Title</th>
<th>Created</th>
<th>Action</th>
</tr>
<!-- Here's where we iterate through our $articles query object, printing out article␣
˓→info -->
</td>
(continues on next page)
</table>
// 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.
// 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>
(continues on next page)
</table>
Using deleteLink() will create a link that uses JavaScript to do a DELETE request deleting our article. Prior to
CakePHP 5.2 you need to use postLink() instead.
ò Note
This view code also uses the FormHelper to prompt the user with a JavaScript confirmation dialog before they
attempt to delete an article.
Tip
With a basic articles management setup, we’ll create the basic actions for our Tags and Users tables.
cd /path/to/our/app
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.
<?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.
<?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:
// 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.
<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’.
// add this use statement right below the namespace declaration to import
// the Collection class
use Cake\Collection\Collection;
(continues on next page)
This will let us access the $article->tag_string computed property. We’ll use this property in controls later on.
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
// 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.
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.
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
46 https://codahale.com/how-to-safely-store-a-password/
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:
// 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())
(continues on next page)
47 https://www.php.net/manual/en/function.password-hash.php
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
Add the logout action to the UsersController class:
// 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();
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.
Load the plugin by adding the following statement to the bootstrap() method in src/Application.php:
$this->addPlugin('Authorization');
use Authorization\AuthorizationService;
use Authorization\AuthorizationServiceInterface;
use Authorization\AuthorizationServiceProviderInterface;
use Authorization\Middleware\AuthorizationMiddleware;
use Authorization\Policy\OrmResolver;
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.
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.
$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:
// 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'])) {
(continues on next page)
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.
83
CakePHP Book, Release 5.x
• installation.rst
• /intro folder
• /tutorials-and-examples folder
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,...
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).
50 https://en.wikipedia.org/wiki/Register#Linguistics
84 Chapter 5. Contributing
CakePHP Book, Release 5.x
• 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.
Line Length
Lines of text should be wrapped at 80 columns. The only exception should be long URLs, and code snippets.
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.
51 https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references
52 https://en.wikipedia.org/wiki/ReStructuredText
Documentation 85
CakePHP Book, Release 5.x
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
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
86 Chapter 5. Contributing
CakePHP Book, Release 5.x
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>`.
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.
53 https://php.net
54 https://pypi.org/project/sphinxcontrib-phpdomain/
Documentation 87
CakePHP Book, Release 5.x
.. php:const:: name
This directive declares a new PHP constant, you can also use it nested inside a class directive to create class
constants.
.. php:exception:: name
This directive declares a new Exception in the current namespace. The signature can include constructor argu-
ments.
.. php:class:: name
Describes a class. Methods, attributes, and constants belonging to the class should be inside this directive’s body:
.. php:class:: MyClass
Class description
.. php:method:: method($argument)
Method description
Attributes, methods and constants don’t need to be nested. They can also just follow the class declaration:
.. php:class:: MyClass
.. php:method:: methodName()
ã See also
.. 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.
88 Chapter 5. Contributing
CakePHP Book, Release 5.x
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:
: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()
(continues on next page)
Documentation 89
CakePHP Book, Release 5.x
Literal text is not modified or formatted, save that one level of indentation is removed.
.. note::
Samples
Tip
ò Note
. Warning
It could be dangerous.
Added 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
90 Chapter 5. Contributing
CakePHP Book, Release 5.x
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 .
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
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 91
CakePHP Book, Release 5.x
ò Note
If you are new to Git, we highly recommend you to read the excellent and free ProGit61 book.
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.
61 https://git-scm.com/book/
62 https://github.com
63 https://github.com/cakephp/cakephp
92 Chapter 5. Contributing
CakePHP Book, Release 5.x
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.
This will fetch + merge in any changes that have happened in CakePHP since you started. It will then rebase - or replay
your changes on top of the current code. You might encounter a conflict during the rebase. If the rebase quits early you
can see which files are conflicted/un-merged with git status. Resolve each conflict, and then continue the rebase:
git add <filename> # do this for each conflicted file.
git rebase --continue
Check that all your tests continue to pass. Then push your branch to your fork:
git push origin <branch-name>
If you’ve rebased after pushing your branch, you’ll need to use force push:
git push --force origin <branch-name>
Once your branch is on GitHub, you can submit a pull request on GitHub.
ò 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 .
Code 93
CakePHP Book, Release 5.x
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.
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
Four spaces will be used for indentation.
So, indentation should look like this:
// base level
// level 1
// level 2
// level 1
// base level
Or:
$booleanVariable = true;
$stringVariable = 'moose';
if ($booleanVariable) {
echo 'Boolean value is true';
if ($stringVariable === 'moose') {
echo 'We have encountered a moose';
}
}
In cases where you’re using a multi-line function call use the following guidelines:
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
68 https://github.com/dereuromark/cakephp-ide-helper
94 Chapter 5. Contributing
CakePHP Book, Release 5.x
• 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)));
$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.
Coding Standards 95
CakePHP Book, Release 5.x
// wrong = no brackets
if (expr)
statement;
// good
if (expr) {
statement;
}
// good
$variable = Class::function();
if ($variable) {
statement;
}
Ternary Operator
Ternary operators are permissible when the entire ternary operation fits on one line. Longer ternaries should be split
into if else statements. Ternary operators should not ever be nested. Optionally parentheses can be used around the
condition check of the ternary for clarity:
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; ?>
96 Chapter 5. Contributing
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
Example of a 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:
Coding Standards 97
CakePHP Book, Release 5.x
if (!($dnsInfo) || !($dnsInfo['phpType'])) {
return $this->addError();
}
return true;
}
Bail Early
Try to avoid unnecessary nesting by bailing 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.
*/
public function foo(Table $table, array $array, callable $callback, $boolean)
{
}
98 Chapter 5. Contributing
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)
{
}
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
• @ignore73
• @internal74
• @link75
• @see76
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
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
Coding Standards 99
CakePHP Book, Release 5.x
• @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.
Variable Types
Variable types for use in DocBlocks:
Type
Description
mixed
A variable with undefined (or multiple) type.
int
Integer type variable (whole number).
float
Float type (point number).
bool
Logical type (true or false).
77 https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/since.html
78 https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/version.html
string
String type (any value in “ “ or ‘ ‘).
null
Null type. Usually used in conjunction with another type.
array
Array type.
object
Object type. A specific class name should be used if possible.
resource
Resource type (returned by for example mysql_connect()). Remember that when you specify the type as mixed,
you should indicate whether it is unknown, or what the possible types are.
callable
Callable function.
You can also combine types using the pipe char:
int|bool
For more than two types it is usually best to just use mixed.
When returning the object itself (for example, for chaining), one should use $this instead:
/**
* Foo function.
*
* @return $this
*/
public function foo()
{
return $this;
}
Including Files
include, require, include_once and require_once do not have parentheses:
// wrong = parentheses
require_once('ClassFileName.php');
require_once ($class);
// good = no parentheses
require_once 'ClassFileName.php';
require_once $class;
When including files with classes or libraries, use only and always the require_once79 function.
PHP Tags
Always use long tags (<?php ?>) instead of short tags (<? ?>). The short echo should be used in template files where
appropriate.
79 https://php.net/require_once
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
Write all functions in camelBack:
function longFunctionName()
{
}
Classes
Class names should be written in CamelCase, for example:
class ExampleClass
{
}
Variables
Variable names should be as descriptive as possible, but also as short as possible. All variables should start with a
lowercase letter, and should be written in camelBack in case of multiple words. Variables referencing objects should
in some way associate to the class the variable is an object of. Example:
$user = 'John';
$users = ['John', 'Hans', 'Arne'];
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
Casting
For casting we use:
Type
Description
(bool)
Cast to boolean.
(int)
Cast to integer.
(float)
Cast to float.
(string)
Cast to string.
(array)
Cast to array.
(object)
Cast to object.
Please use (int)$var instead of intval($var) and (float)$var instead of floatval($var) when applicable.
Constants
Constants should be defined in capital letters:
define('CONSTANT', 1);
If a constant name consists of multiple words, they should be separated by an underscore character, for example:
define('LONG_NAMED_CONSTANT', 2);
Enums
Enum cases are defined in CamelCase style:
80 https://datatracker.ietf.org/doc/html/rfc2606.html
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) {
// ...
}
}
ò Note
Deprecations are removed with the next major version of the framework. It is advised that you adapt to deprecations
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.
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.
81 https://semver.org/
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,
]
// ...
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.
2 You can change a class/method name as long as the old name remains available. This is generally avoided unless renaming has significant
benefit.
3 Avoid whenever possible. Any removals need to be documented in the migration guide.
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)
109
CakePHP Book, Release 5.x
• SQLite 3
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
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.
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 environment
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
the following commands from your application directory just once in your project to ensure that permissions will be
setup properly:
88 https://ddev.com/
89 https://ddev.readthedocs.io/
Permissions 111
CakePHP Book, Release 5.x
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 develop-
ment 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.
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.)
Production 113
CakePHP Book, Release 5.x
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
RewriteRule ^ index.php [L]
</IfModule>
If your CakePHP site still has problems with mod_rewrite, you might want to try modifying settings for Virtual
Hosts. On Ubuntu, edit the file /etc/apache2/sites-available/default (location is distribution-dependent). In this
file, ensure that AllowOverride None is changed to AllowOverride All, so you have:
<Directory />
Options FollowSymLinks
AllowOverride All
</Directory>
(continues on next page)
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:
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:
90 https://clickontyler.com/virtualhostx/
location / {
try_files $uri $uri/ /index.php?$args;
}
server {
listen 80;
listen [::]:80;
server_name www.example.com;
return 301 http://example.com$request_uri;
}
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 .p