0% found this document useful (0 votes)
47 views828 pages

Cake PHPBook

The CakePHP Book, Release 5.x, provides comprehensive documentation on the CakePHP framework, covering key concepts such as the model, view, and controller layers, as well as the request cycle. It includes quick start guides, migration guides for upgrading versions, and tutorials for building applications. Additionally, the book offers information on contributing to the framework and installation procedures.

Uploaded by

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

Cake PHPBook

The CakePHP Book, Release 5.x, provides comprehensive documentation on the CakePHP framework, covering key concepts such as the model, view, and controller layers, as well as the request cycle. It includes quick start guides, migration guides for upgrading versions, and tutorials for building applications. Additionally, the book offers information on contributing to the framework and installation procedures.

Uploaded by

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

CakePHP Book

Release 5.x

Cake Software Foundation

Apr 23, 2025


Contents

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

2 Quick Start Guide 13


Content Management Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
CMS Tutorial - Creating the Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
CMS Tutorial - Creating our First Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
CMS Tutorial - Creating the Articles Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

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

4 Tutorials & Examples 47


Content Management Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
CMS Tutorial - Creating the Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
CMS Tutorial - Creating our First Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
CMS Tutorial - Creating the Articles Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
CMS Tutorial - Tags and Users . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
CMS Tutorial - Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
CMS Tutorial - Authorization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77

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

9 Request & Response Objects 161


Request . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
Response . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
Common Mistakes with Immutable Responses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
Cookie Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179

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

12 Database Access & ORM 315


Quick Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
More Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317

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

14 Bake Console 507

15 Console Commands 509


The CakePHP Console . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 509
Console Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510
Renaming Commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510
Commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 511
CakePHP Provided Commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 534
Routing in the Console Environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 541

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

19 Error & Exception Handling 565


Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 565
Deprecation Warnings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 566
Changing Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 566
Listen to Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 567
Custom Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 567
Custom Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 568
Custom ExceptionRenderer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 569
Creating your own Application Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 570
Built in Exceptions for CakePHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 571
Customizing PHP Error Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 575

20 Events System 577


Example Event Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 577
Accessing Event Managers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 578
Core Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 579
Registering Listeners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 580
Dispatching Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 584
Additional Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 587

21 Internationalization & Localization 589


Setting Up Translations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 589
Using Translation Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 591
Creating Your Own Translators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 595
Localizing Dates and Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 598
Automatically Choosing the Locale Based on Request Data . . . . . . . . . . . . . . . . . . . . . . . . . . 600
Translate Content/Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 600

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

23 Modelless Forms 611


Creating a Form . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 611

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

31 App Class 705


Finding Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 705
Finding Paths to Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 706
Finding Paths to Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 706
Locating Themes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 706
Loading Vendor Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 706

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

34 Http Client 747


Doing Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 747
Creating Multipart Requests with Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 748
Sending Request Bodies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 749
Request Method Options . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 749
Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 750
Creating Scoped Clients . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 752
Setting and Managing Cookies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 753
Client Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 753
Response Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 753
Changing Transport Adapters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 755
Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 755
Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 756

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

37 Registry Objects 769


Loading Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 769
Triggering Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 769
Disabling Callbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 770

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

41 Constants & Functions 793


Global Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 793
Core Definition Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 795
Timing Definition Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 796

42 Chronos 797

43 Debug Kit 799

44 Migrations 801

45 ElasticSearch 803

46 Appendices 805
Migration Guides . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805
Backwards Compatibility Shimming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805
Forwards Compatibility Shimming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805
General Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805

PHP Namespace Index 809

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.

Conventions Over Configuration


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

The Model Layer


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

use Cake\ORM\Locator\LocatorAwareTrait;

$users = $this->fetchTable('Users');
$resultset = $users->find()->all();
(continues on next page)

1
CakePHP Book, Release 5.x

(continued from previous page)


foreach ($resultset as $row) {
echo $row->username;
}

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


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

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


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

The View layer provides a number of extension points like View Templates, Elements and View Cells to let you re-use
your presentation logic.
The View layer is not only limited to HTML or text representation of the data. It can be used to deliver common data
formats like JSON, XML, and through a pluggable architecture any other format you may need, such as CSV.

The Controller Layer


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

public function add()


{
$user = $this->Users->newEmptyEntity();
if ($this->request->is('post')) {
$user = $this->Users->patchEntity($user, $this->request->getData());
if ($this->Users->save($user, ['validate' => 'registration'])) {
(continues on next page)

2 Chapter 1. CakePHP at a Glance


CakePHP Book, Release 5.x

(continued from previous page)


$this->Flash->success(__('You are now registered.'));
} else {
$this->Flash->error(__('There were some problems.'));
}
}
$this->set('user', $user);
}

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

CakePHP Request Cycle


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

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.

CakePHP Request Cycle 3


CakePHP Book, Release 5.x

3. Your application’s middleware is initialized.


4. A request and response is dispatched through the PSR-7 Middleware that your application uses. Typically this
includes error trapping and routing.
5. If no response is returned from the middleware and the request contains routing information, a controller & action
are selected.
6. The controller’s action is called and the controller interacts with the required Models and Components.
7. The controller delegates response creation to the View to generate the output resulting from the model data.
8. The view uses Helpers and Cells to generate the response body and headers.
9. The response is sent back out through the /controllers/middleware.
10. The HttpServer emits the response to the webserver.

Just the Start


Hopefully this quick overview has piqued your interest. Some other great features in CakePHP are:
• A caching framework that integrates with Memcached, Redis and other backends.
• Powerful code generation tools so you can start immediately.
• Integrated testing framework so you can ensure your code works perfectly.
The next obvious steps are to download CakePHP, read the tutorial and build something awesome.

Additional Reading
Where to Get Help
The Official CakePHP website
https://cakephp.org
The Official CakePHP website is always a great place to visit. It features links to oft-used developer tools, screencasts,
donation opportunities, and downloads.

The Cookbook
https://book.cakephp.org
This manual should probably be the first place you go to get answers. As with many other open source projects, we
get new folks regularly. Try your best to answer your questions on your own first. Answers may come slower, but will
remain longer – and you’ll also be lightening our support load. Both the manual and the API have an online component.

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.

4 Chapter 1. CakePHP at a Glance


CakePHP Book, Release 5.x

The API
https://api.cakephp.org/
Straight to the point and straight from the core developers, the CakePHP API (Application Programming Interface) is
the most comprehensive documentation around for all the nitty gritty details of the internal workings of the framework.
It’s a straight forward code reference, so bring your propeller hat.

The Test Cases


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

tests/TestCase/

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.

Official CakePHP Forum


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

Stackoverflow
https://stackoverflow.com/7
Tag your questions with cakephp and the specific version you are using to enable existing users of stackoverflow to
find your questions.

Where to get Help in your Language


Danish

• Danish CakePHP Slack Channel8

French

• French CakePHP Slack Channel9


4 https://cakesf.slack.com/messages/german/
5 https://discord.com/invite/k4trEMPebj
6 https://discourse.cakephp.org
7 https://stackoverflow.com/questions/tagged/cakephp/
8 https://cakesf.slack.com/messages/denmark/
9 https://cakesf.slack.com/messages/french/

Additional Reading 5
CakePHP Book, Release 5.x

German

• German CakePHP Slack Channel10


• German CakePHP Facebook Group11

Dutch

• Dutch CakePHP Slack Channel12

Japanese

• Japanese CakePHP Slack Channel13


• Japanese CakePHP Facebook Group14

Portuguese

• Portuguese CakePHP Slack Channel15

Spanish

• Spanish CakePHP Slack Channel16

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.

URL Considerations for Controller Names

As you’ve just seen, single word controllers map to a simple lower case URL path. For example, UsersController
(which would be defined in the file name UsersController.php) is accessed from http://example.com/users.
While you can route multiple word controllers in any way you like, the convention is that your URLs are 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/

6 Chapter 1. CakePHP at a Glance


CakePHP Book, Release 5.x

$this->Html->link('link-title', [
'prefix' => 'MyPrefix' // CamelCased
'plugin' => 'MyPlugin', // CamelCased
'controller' => 'ControllerName', // CamelCased
'action' => 'actionName' // camelBacked
]

For more information on CakePHP URLs and parameter handling, see Connecting Routes.

File and Class Name Conventions


In general, filenames match the class names, and follow the PSR-4 standard for autoloading. The following are some
examples of class names and their filenames:
• The Controller class LatestArticlesController would be found in a file named LatestArticlesCon-
troller.php
• The Component class MyHandyComponent would be found in a file named MyHandyComponent.php
• The Table class OptionValuesTable would be found in a file named OptionValuesTable.php.
• The Entity class OptionValue would be found in a file named OptionValue.php.
• The Behavior class EspeciallyFunkableBehavior would be found in a file named EspeciallyFunkableBe-
havior.php
• The View class SuperSimpleView would be found in a file named SuperSimpleView.php
• The Helper class BestEverHelper would be found in a file named BestEverHelper.php
Each file would be located in the appropriate folder/namespace in your app folder.

Database Conventions
Table names corresponding to CakePHP models are plural and underscored. For example users, menu_links, and
user_favorite_pages respectively. Table name whose name contains multiple words should only pluralize the last
word, for example, menu_links.
Column names with two or more words are underscored, for example, first_name.
Foreign keys in hasMany, belongsTo/hasOne relationships are recognized by default as the (singular) name of the
related table followed by _id. So if Users hasMany Articles, the articles table will refer to the users table via a
user_id foreign key. For a table like menu_links whose name contains multiple words, the foreign key would be
menu_link_id.
Join (or “junction”) tables are used in BelongsToMany relationships between models. These should be named for the
tables they connect. The names should be pluralized and sorted alphabetically: articles_tags, not tags_articles
or article_tags. The bake command will not work if this convention is not followed. If the junction table holds any
data other than the linking foreign keys, you should create a concrete entity/table class for the table.
In addition to using an auto-incrementing integer as primary keys, you can also use UUID columns. CakePHP will
create UUID values automatically using (Cake\Utility\Text::uuid()) whenever you save new records using the
Table::save() method.

Model Conventions
Table class names are plural, CamelCased and end in Table. UsersTable, MenuLinksTable, and
UserFavoritePagesTable are all examples of table class names matching the users, menu_links and
user_favorite_pages tables respectively.

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

See awesome list recommendations17 for details.

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

8 Chapter 1. CakePHP at a Glance


CakePHP Book, Release 5.x

Ex- articles menu_links


am-
ple
Databasearticles menu_links Table names corresponding to CakePHP models are plural and un-
Ta- derscored.
ble
File ArticlesCon- MenuLinksCon-
troller.php troller.php
Ta- Arti- MenuLinksTable.phpTable class names are plural, CamelCased and end in Table
ble clesTable.php
En- Article.php MenuLink.php Entity class names are singular, CamelCased: Article and
tity MenuLink
Class ArticlesCon- MenuLinksCon-
troller troller
Con- ArticlesCon- MenuLinksCon- Plural, CamelCased, end in Controller
troller troller troller
Tem- Arti- MenuLinks/index.phpView template files are named after the controller functions they
plates cles/index.php MenuLinks/add.php display, in an underscored form
Arti- MenuLinks/get_list.php
cles/add.php
Arti-
cles/get_list.php
Be- ArticlesBehav- MenuLinksBe-
hav- ior.php havior.php
ior
View Arti- MenuLinksView.php
clesView.php
Helper Arti- MenuLinksHelper.php
clesHelper.php
Com- ArticlesCom- MenuLinksCom-
po- ponent.php ponent.php
nent
Plu- Bad: cakephp/menu- Useful to prefix a CakePHP plugin with “cakephp-” in the package
gin cakephp/articles links name. Do not use the CakePHP namespace (cakephp) as vendor
Good: you/cakephp- name as this is reserved to CakePHP owned plugins. The convention
you/cakephp- menu-links is to use lowercase letters and dashes as separator.
articles
Each file would be located in the appropriate folder/namespace in your app folder.

Additional Reading 9
CakePHP Book, Release 5.x

Database Convention Summary

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.

CakePHP Folder Structure


After you’ve downloaded the CakePHP application skeleton, there are a few top level folders you should see:
• The bin folder holds the Cake console executables.
• The config folder holds the Configuration files CakePHP uses. Database connection details, bootstrapping, core
configuration files and more should be stored here.
• The plugins folder is where the Plugins your application uses are stored.
• The logs folder normally contains your log files, depending on your log configuration.
• The src folder will be where your application’s source files will be placed.
• The templates folder has presentational files placed here: elements, error pages, layouts, and view template files.
• The resources folder has sub folder for various types of resource files. The locales sub folder stores language
files for internationalization.
• The tests folder will be where you put the test cases for your application.
• The tmp folder is where CakePHP stores temporary data. The actual data it stores depends on how you have
CakePHP configured, but this folder is usually used to store translation messages, model descriptions and some-
times session information.
• The vendor folder is where CakePHP and other application dependencies will be installed by Composer18 . Edit-
ing these files is not advised, as Composer will overwrite your changes next time you update.
• The webroot directory is the public document root of your application. It contains all the files you want to be
publicly reachable.
Make sure that the tmp and logs folders exist and are writable, otherwise the performance of your application
will be severely impacted. In debug mode, CakePHP will warn you, if these directories are not writable.
18 https://getcomposer.org

10 Chapter 1. CakePHP at a Glance


CakePHP Book, Release 5.x

The src Folder


CakePHP’s src folder is where you will do most of your application development. Let’s look a little closer at the folders
inside src.
Command
Contains your application’s console commands. See Command Objects to learn more.
Console
Contains the installation script executed by Composer.
Controller
Contains your application’s Controllers and their components.
Middleware
Stores any /controllers/middleware for your application.
Model
Contains your application’s tables, entities and behaviors.
View
Presentational classes are placed here: views, cells, helpers.

ò Note

The folder Command is not present by default. You can add it when you need it.

Additional Reading 11
CakePHP Book, Release 5.x

12 Chapter 1. CakePHP at a Glance


CHAPTER 2

Quick Start Guide

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

Content Management Tutorial


This tutorial will walk you through the creation of a simple CMS (Content Management System) application. To start
with, we’ll be installing CakePHP, creating our database, and building simple article management.
Here’s what you’ll need:
1. A database server. We’re going to be using MySQL server in this tutorial. You’ll need to know enough about
SQL in order to create a database, and run SQL snippets from the tutorial. CakePHP will handle building all the
queries your application needs. Since we’re using MySQL, also make sure that you have pdo_mysql enabled in
PHP.
2. Basic PHP knowledge.
Before starting you should make sure that you’re using a supported PHP version:

php -v

You should at least have got installed PHP 8.1 (CLI) or higher. Your webserver’s PHP version must also be of 8.1 or
higher, and should be the same version your command line interface (CLI) PHP is.

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:

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

13
CakePHP Book, Release 5.x

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


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

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

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

composer self-update && composer create-project --prefer-dist cakephp/app:5.* cms

The advantage to using Composer is that it will automatically complete some important set up tasks, such as setting
the correct file permissions and creating your config/app.php file for you.
There are other ways to install CakePHP. If you cannot or don’t want to use Composer, check out the Installation section.
Regardless of how you downloaded and installed CakePHP, once your set up is completed, your directory setup should
look 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.

Checking our Installation


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

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

14 Chapter 2. Quick Start Guide


CakePHP Book, Release 5.x

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.

CMS Tutorial - Creating the Database


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

CREATE DATABASE cake_cms;

USE cake_cms;

CREATE TABLE users (


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

CREATE TABLE articles (


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

CREATE TABLE tags (


id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(191),
created DATETIME,
modified DATETIME,
UNIQUE KEY (title)
(continues on next page)

CMS Tutorial - Creating the Database 15


CakePHP Book, Release 5.x

(continued from previous page)


) CHARSET=utf8mb4;

CREATE TABLE articles_tags (


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

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


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

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

If you are using PostgreSQL, connect to the cake_cms database and execute the following SQL instead:

CREATE TABLE users (


id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
created TIMESTAMP,
modified TIMESTAMP
);

CREATE TABLE articles (


id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
slug VARCHAR(191) NOT NULL,
body TEXT,
published BOOLEAN DEFAULT FALSE,
created TIMESTAMP,
modified TIMESTAMP,
UNIQUE (slug),
FOREIGN KEY (user_id) REFERENCES users(id)
);

CREATE TABLE tags (


id SERIAL PRIMARY KEY,
title VARCHAR(191),
created TIMESTAMP,
modified TIMESTAMP,
UNIQUE (title)
);

CREATE TABLE articles_tags (


article_id INT NOT NULL,
tag_id INT NOT NULL,
(continues on next page)

16 Chapter 2. Quick Start Guide


CakePHP Book, Release 5.x

(continued from previous page)


PRIMARY KEY (article_id, tag_id),
FOREIGN KEY (tag_id) REFERENCES tags(id),
FOREIGN KEY (article_id) REFERENCES articles(id)
);

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


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

INSERT INTO articles (user_id, title, slug, body, published, created, modified)
VALUES
(1, 'First Post', 'first-post', 'This is the first post.', 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.

CMS Tutorial - Creating the Database 17


CakePHP Book, Release 5.x

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.

bin/cake bake migration CreateUsers email:string password:string created modified


bin/cake bake migration CreateArticles user_id:integer title:string␣
˓→slug:string[191]:unique body:text published:boolean created modified

bin/cake bake migration CreateTags title:string[191]:unique created modified


bin/cake bake migration CreateArticlesTags article_id:integer:primary tag_
˓→id:integer:primary created modified

ò 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

Likewise, the starter data records can be done with seeds.

bin/cake bake seed Users


bin/cake bake seed Articles

Fill the seed data above into the new UsersSeed and ArticlesSeed classes, then:

bin/cake migrations seed

Read more about building migrations and data seeding: Migrations22


With the database built, we can now build Models.

CMS Tutorial - Creating our First Model


Models are the heart of CakePHP applications. They enable us to read and modify our data. They allow us to build
relations between our data, validate data, and apply application rules. Models provide the foundation necessary to
create our controller actions and templates.
22 https://book.cakephp.org/migrations/4/

18 Chapter 2. Quick Start Guide


CakePHP Book, Release 5.x

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;

class ArticlesTable extends Table


{
public function initialize(array $config): void
{
parent::initialize($config);
$this->addBehavior('Timestamp');
}
}

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

ò Note

CakePHP will dynamically create a model object for you if it cannot find a corresponding file in src/Model/Table.
This also means that if you accidentally name your file wrong (i.e. articlestable.php or 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;

class Article extends Entity


{
protected array $_accessible = [
'user_id' => true,
'title' => true,
'slug' => true,
'body' => true,
'published' => true,
'created' => true,
'modified' => true,
(continues on next page)

CMS Tutorial - Creating our First Model 19


CakePHP Book, Release 5.x

(continued from previous page)


'user' => true,
'tags' => true,
];
}

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.

CMS Tutorial - Creating the Articles Controller


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

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

namespace App\Controller;

class ArticlesController extends AppController


{
}

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

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

namespace App\Controller;

class ArticlesController extends AppController


{
public function index()
{
$articles = $this->paginate($this->Articles);
$this->set(compact('articles'));
}
}

20 Chapter 2. Quick Start Guide


CakePHP Book, Release 5.x

By defining function index() in our ArticlesController, users can now access the logic there by requesting
www.example.com/articles/index. Similarly, if we were to define a function called foobar(), users would be able to
access that at www.example.com/articles/foobar. You may be tempted to name your controllers and actions in a way
that allows you to obtain specific URLs. Resist that temptation. Instead, follow the CakePHP Conventions creating
readable, meaningful action names. You can then use Routing to connect the URLs you want to the actions you’ve
created.
Our controller action is very simple. It fetches a paginated set of articles from the database, using the Articles Model
that is automatically loaded via naming conventions. It then uses set() to pass the articles into the Template (which
we’ll create soon). CakePHP will automatically render the template after our controller action completes.

Create the Article List Template


Now that we have our controller pulling data from the model, and preparing our view context, let’s create a view
template for our index action.
CakePHP view templates are presentation-flavored PHP code that is inserted inside the application’s layout. While
we’ll be creating HTML here, Views can also generate JSON, CSV or even binary files like PDFs.
A layout is presentation code that is wrapped around a view. Layout files contain common site elements like headers,
footers and navigation elements. Your application can have multiple layouts, and you can switch between them, but for
now, let’s just use the default layout.
CakePHP’s template files are stored in templates inside a folder named after the controller they correspond to. So we’ll
have to create a folder named ‘Articles’ in this case. Add the following code to your application:

<!-- File: templates/Articles/index.php -->

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

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

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


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

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

In the last section we assigned the ‘articles’ variable to the view using set(). Variables passed into the view are
available in the view templates as local variables which we used in the above code.
You might have noticed the use of an object called $this->Html. This is an instance of the CakePHP HtmlHelper.
CakePHP comes with a set of view helpers that make tasks like creating links, forms, and pagination buttons. You can

CMS Tutorial - Creating the Articles Controller 21


CakePHP Book, Release 5.x

learn more about Helpers in their chapter, but what’s important to note here is that the link() method will generate
an HTML link with the given link text (the first parameter) and URL (the second parameter).
When specifying URLs in CakePHP, it is recommended that you use arrays or named routes. These syntaxes allow you
to leverage the reverse routing features CakePHP offers.
At this point, you should be able to point your browser to http://localhost:8765/articles/index. You should see your
list view, correctly formatted with the title and table listing of the articles.

Create the View Action


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

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

public function view($slug = null)


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

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.

Create the View Template


Let’s create the view for our new ‘view’ action and place it in templates/Articles/view.php

<!-- File: templates/Articles/view.php -->

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


<p><?= h($article->body) ?></p>
<p><small>Created: <?= $article->created->format(DATE_RFC850) ?></small></p>
<p><?= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?></p>

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

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

<?php
// src/Controller/ArticlesController.php
namespace App\Controller;

use App\Controller\AppController;
(continues on next page)

22 Chapter 2. Quick Start Guide


CakePHP Book, Release 5.x

(continued from previous page)

class ArticlesController extends AppController


{
public function index()
{
$articles = $this->paginate($this->Articles);
$this->set(compact('articles'));
}

public function view($slug)


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

public function add()


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

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


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

if ($this->Articles->save($article)) {
$this->Flash->success(__('Your article has been saved.'));

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


}
$this->Flash->error(__('Unable to add your article.'));
}
$this->set('article', $article);
}
}

ò Note

You need to include the Flash component in any controller where you will use it. Often it makes sense to include
it in your AppController, which is there already for this tutorial.

Here’s what the add() action does:


• If the HTTP method of the request was POST, try to save the data using the Articles model.
• If for some reason it doesn’t save, just render the view. This gives us a chance to show the user validation errors
or other warnings.
Every CakePHP request includes a request object which is accessible using $this->request. The request object
contains information regarding the request that was just received. We use the Cake\Http\ServerRequest::is()
method to check that the request is a HTTP POST request.
Our POST data is available in $this->request->getData(). You can use the pr() or debug() functions to print

CMS Tutorial - Creating the Articles Controller 23


CakePHP Book, Release 5.x

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.

Create Add Template


Here’s our add view template:

<!-- File: templates/Articles/add.php -->

<h1>Add Article</h1>
<?php
echo $this->Form->create($article);
// Hard code the user for now.
echo $this->Form->control('user_id', ['type' => 'hidden', 'value' => 1]);
echo $this->Form->control('title');
echo $this->Form->control('body', ['rows' => '3']);
echo $this->Form->button(__('Save Article'));
echo $this->Form->end();
?>

We use the FormHelper to generate the opening tag for an HTML form. Here’s the HTML that
$this->Form->create() generates:

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

Because we called create() without a URL option, FormHelper assumes we want the form to submit back to the
current action.
The $this->Form->control() method is used to create form elements of the same name. The first parameter tells
CakePHP which field they correspond to, and the second parameter allows you to specify a wide array of options - in
this case, the number of rows for the textarea. There’s a bit of introspection and conventions used here. The control()
will output different form elements based on the model field specified, and use inflection to generate the label text. You
can customize the label, the input or any other aspect of the form controls using options. The $this->Form->end()
call closes the form.
Now let’s go back and update our templates/Articles/index.php view to include a new “Add Article” link. Before the
<table>, add the following line:

<?= $this->Html->link('Add Article', ['action' => 'add']) ?>

Adding Simple Slug Generation


If we were to save an Article right now, saving would fail as we are not creating a slug attribute, and the column is NOT
NULL. Slug values are typically a URL-safe version of an article’s title. We can use the beforeSave() callback of the
ORM to populate our slug:
23 https://php.net/manual/en/language.oop5.overloading.php#object.call
24 https://api.cakephp.org

24 Chapter 2. Quick Start Guide


CakePHP Book, Release 5.x

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

// Add the following method.

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


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

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

Add Edit Action


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

// in src/Controller/ArticlesController.php

// Add the following method.

public function edit($slug)


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

if ($this->request->is(['post', 'put'])) {
$this->Articles->patchEntity($article, $this->request->getData());
if ($this->Articles->save($article)) {
$this->Flash->success(__('Your article has been updated.'));

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


}
$this->Flash->error(__('Unable to update your article.'));
}

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

This action first ensures that the user has tried to access an existing record. If they haven’t passed in an $slug parameter,
or the article does not exist, a RecordNotFoundException will be thrown, and the CakePHP ErrorHandler will render
the appropriate error page.

CMS Tutorial - Creating the Articles Controller 25


CakePHP Book, Release 5.x

Next the action checks whether the request is either a POST or a PUT request. If it is, then we use the POST/PUT data
to update our article entity by using the patchEntity() method. Finally, we call save(), set the appropriate flash
message, and either redirect or display validation errors.

Create Edit Template


The edit template should look like this:

<!-- File: templates/Articles/edit.php -->

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

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


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

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

</table>

26 Chapter 2. Quick Start Guide


CakePHP Book, Release 5.x

Update Validation Rules for Articles


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

// src/Model/Table/ArticlesTable.php

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

// Add the following method.


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

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

Add Delete Action


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

// src/Controller/ArticlesController.php

// Add the following method.

public function delete($slug)


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

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

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


}
}

This logic deletes the article specified by $slug, and uses $this->Flash->success() to show the user a confir-

CMS Tutorial - Creating the Articles Controller 27


CakePHP Book, Release 5.x

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:

<!-- File: templates/Articles/index.php (delete links added) -->

<h1>Articles</h1>
<p><?= $this->Html->link("Add Article", ['action' => 'add']) ?></p>
<table>
<tr>
<th>Title</th>
<th>Created</th>
<th>Action</th>
</tr>

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

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


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

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

28 Chapter 2. Quick Start Guide


CakePHP Book, Release 5.x

ò 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

The ArticlesController can also be built with bake:


/bin/cake bake controller articles

However, this does not build the templates/Articles/*.php files.

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

CMS Tutorial - Creating the Articles Controller 29


CakePHP Book, Release 5.x

30 Chapter 2. Quick Start Guide


CHAPTER 3

Migration Guides

Migration guides contain information regarding the new features introduced in each version and the migration path
between 5.x minor releases.

5.0 Upgrade Guide


First, check that your application is running on latest CakePHP 4.x version.

Fix Deprecation Warnings


Once your application is running on latest CakePHP 4.x, enable deprecation warnings in config/app.php:

'Error' => [
'errorLevel' => E_ALL,
]

Now that you can see all the warnings, make sure these are fixed before proceeding with the upgrade.
Some potentially impactful deprecations you should make sure you have addressed are:
• Table::query() was deprecated in 4.5.0. Use selectQuery(), updateQuery(), insertQuery() and
deleteQuery() instead.

Upgrade to PHP 8.1


If you are not running on PHP 8.1 or higher, you will need to upgrade PHP before updating CakePHP.

ò Note

CakePHP 5.0 requires a minimum of PHP 8.1.

31
CakePHP Book, Release 5.x

Use the Upgrade Tool

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

# Install the upgrade tool


git clone https://github.com/cakephp/upgrade
cd upgrade
git checkout 5.x
composer install --no-dev

With the upgrade tool installed you can now run it on your application or plugin:

bin/cake upgrade rector --rules cakephp50 <path/to/app/src>


bin/cake upgrade rector --rules chronos3 <path/to/app/src>

Update CakePHP Dependency


After applying rector refactorings you need to upgrade CakePHP, its plugins, PHPUnit and maybe other dependencies
in your composer.json. This process heavily depends on your application so we recommend you compare your
composer.json with what is present in cakephp/app25 .
After the version strings are adjusted in your composer.json execute composer update -W and check its output.

Update app files based upon latest app template


Next, ensure the rest of your application has been updated to be based upon the latest version of cakephp/app26 .

5.0 Migration Guide


CakePHP 5.0 contains breaking changes, and is not backwards compatible with 4.x releases. Before attempting to
upgrade to 5.0, first upgrade to 4.5 and resolve all deprecation warnings.
Refer to the 5.0 Upgrade Guide for step by step instructions on how to upgrade to 5.0.

Deprecated Features Removed


All methods, properties and functionality that were emitting deprecation warnings as of 4.5 have been removed.

Breaking Changes
In addition to the removal of deprecated features there have been breaking changes made:
25 https://github.com/cakephp/app/blob/5.x/composer.json
26 https://github.com/cakephp/app/blob/5.x/

32 Chapter 3. Migration Guides


CakePHP Book, Release 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

5.0 Migration Guide 33


CakePHP Book, Release 5.x

• ConsoleOptionParser::addSubcommand() was removed alongside the removal of Shell. Subcommands


should be replaced with Command classes that implement Command::defaultName() to define the necessary
command name.
• BaseCommand now emits Command.beforeExecute and Command.afterExecute events around the com-
mand’s execute() method being invoked by the framework.

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

34 Chapter 3. Migration Guides


CakePHP Book, Release 5.x

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

5.0 Migration Guide 35


CakePHP Book, Release 5.x

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

36 Chapter 3. Migration Guides


CakePHP Book, Release 5.x

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

5.0 Migration Guide 37


CakePHP Book, Release 5.x

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

38 Chapter 3. Migration Guides


CakePHP Book, Release 5.x

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.

Typed Finder Parameters


Table finders can now have typed arguments as required instead of an options array. For e.g. a finder for fetching posts
by category or user:

public function findByCategoryOrUser(SelectQuery $query, array $options)


{
if (isset($options['categoryId'])) {
$query->where(['category_id' => $options['categoryId']]);
}
if (isset($options['userId'])) {
$query->where(['user_id' => $options['userId']]);
}

return $query;
}

can now be written as:

public function findByCategoryOrUser(SelectQuery $query, ?int $categoryId = null, ?int


˓→$userId = null)

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

5.0 Migration Guide 39


CakePHP Book, Release 5.x

(continued from previous page)

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:

public function view(int $id)


{
$author = $this->Authors->get($id, [
'contain' => ['Books'],
'finder' => 'latest',
]);
}

can now be written as:

public function view(int $id)


{
$author = $this->Authors->get($id, contain: ['Books'], finder: 'latest');
}

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.

5.1 Migration Guide


The 5.1.0 release is a backwards compatible with 5.0. It adds new functionality and introduces new deprecations. Any
functionality deprecated in 5.x will be removed in 6.0.0.

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.

40 Chapter 3. Migration Guides


CakePHP Book, Release 5.x

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

5.1 Migration Guide 41


CakePHP Book, Release 5.x

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.

42 Chapter 3. Migration Guides


CakePHP Book, Release 5.x

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

5.2 Migration Guide


The 5.2.0 release is a backwards compatible with 5.0. It adds new functionality and introduces new deprecations. Any
functionality deprecated in 5.x will be removed in 6.0.0.

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.

5.2 Migration Guide 43


CakePHP Book, Release 5.x

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.

44 Chapter 3. Migration Guides


CakePHP Book, Release 5.x

• 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

New event system


PHPUnit 10 removed the old hook system and introduced a new Event system36 which requires the following code in
your phpunit.xml to be adjusted from:

<extensions>
<extension class="Cake\TestSuite\Fixture\PHPUnitExtension"/>
</extensions>

to:

<extensions>
<bootstrap class="Cake\TestSuite\Fixture\Extension\PHPUnitExtension"/>
</extensions>

->withConsecutive() has been removed


You can convert the removed ->withConsecutive() method to a working interim solution like you can see here:

->withConsecutive(['firstCallArg'], ['secondCallArg'])

should be converted to:

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

data providers have to be static


If your testcases leverage the data provider feature of PHPUnit then you have to adjust your data providers to be static:

public function myProvider(): array

should be converted to:

public static function myProvider(): array

36 https://docs.phpunit.de/en/10.5/extending-phpunit.html#extending-the-test-runner

46 Chapter 3. Migration Guides


CHAPTER 4

Tutorials & Examples

In this section, you can walk through typical CakePHP applications to see how all of the pieces come together.
Alternatively, you can refer to the non-official CakePHP plugin repository CakePackages37 and the Bakery38 for existing
applications and components.

Content Management Tutorial


This tutorial will walk you through the creation of a simple CMS application. To start with, we’ll be installing CakePHP,
creating our database, and building simple article management.
Here’s what you’ll need:
1. A database server. We’re going to be using MySQL server in this tutorial. You’ll need to know enough about
SQL in order to create a database, and run SQL snippets from the tutorial. CakePHP will handle building all the
queries your application needs. Since we’re using MySQL, also make sure that you have pdo_mysql enabled in
PHP.
2. Basic PHP knowledge.
Before starting you should make sure that you’re using a supported PHP version:

php -v

You should at least have got installed PHP 8.1 (CLI) or higher. Your webserver’s PHP version must also be of 8.1 or
higher, and should be the same version your command line interface (CLI) PHP is.
37 https://plugins.cakephp.org/
38 https://bakery.cakephp.org/

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:

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

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


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

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

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

composer self-update && composer create-project --prefer-dist cakephp/app:5.* cms

The advantage to using Composer is that it will automatically complete some important set up tasks, such as setting
the correct file permissions and creating your config/app.php file for you.
There are other ways to install CakePHP. If you cannot or don’t want to use Composer, check out the Installation section.
Regardless of how you downloaded and installed CakePHP, once your set up is completed, your directory setup should
look 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

48 Chapter 4. Tutorials & Examples


CakePHP Book, Release 5.x

Checking our Installation


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

cd /path/to/our/app

bin/cake server

ò Note

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

This will start PHP’s built-in webserver on port 8765. Open up http://localhost:8765 in your web browser to see
the welcome page. All the bullet points should be green chef hats other than CakePHP being able to connect to your
database. If not, you may need to install additional PHP extensions, or set directory permissions.
Next, we will build our Database.

CMS Tutorial - Creating the Database


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

CREATE DATABASE cake_cms;

USE cake_cms;

CREATE TABLE users (


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

CREATE TABLE articles (


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

CREATE TABLE tags (


id INT AUTO_INCREMENT PRIMARY KEY,
(continues on next page)

CMS Tutorial - Creating the Database 49


CakePHP Book, Release 5.x

(continued from previous page)


title VARCHAR(191),
created DATETIME,
modified DATETIME,
UNIQUE KEY (title)
) CHARSET=utf8mb4;

CREATE TABLE articles_tags (


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

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


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

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

If you are using PostgreSQL, connect to the cake_cms database and execute the following SQL instead:

CREATE TABLE users (


id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
created TIMESTAMP,
modified TIMESTAMP
);

CREATE TABLE articles (


id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
slug VARCHAR(191) NOT NULL,
body TEXT,
published BOOLEAN DEFAULT FALSE,
created TIMESTAMP,
modified TIMESTAMP,
UNIQUE (slug),
FOREIGN KEY (user_id) REFERENCES users(id)
);

CREATE TABLE tags (


id SERIAL PRIMARY KEY,
title VARCHAR(191),
created TIMESTAMP,
modified TIMESTAMP,
UNIQUE (title)
);
(continues on next page)

50 Chapter 4. Tutorials & Examples


CakePHP Book, Release 5.x

(continued from previous page)

CREATE TABLE articles_tags (


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

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


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

INSERT INTO articles (user_id, title, slug, body, published, created, modified)
VALUES
(1, 'First Post', 'first-post', 'This is the first post.', 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

CMS Tutorial - Creating the Database 51


CakePHP Book, Release 5.x

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.

bin/cake bake migration CreateUsers email:string password:string created modified


bin/cake bake migration CreateArticles user_id:integer title:string␣
˓→slug:string[191]:unique body:text published:boolean created modified

bin/cake bake migration CreateTags title:string[191]:unique created modified


bin/cake bake migration CreateArticlesTags article_id:integer:primary tag_
˓→id:integer:primary created modified

ò 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

Likewise, the starter data records can be done with seeds.

bin/cake bake seed Users


bin/cake bake seed Articles

Fill the seed data above into the new UsersSeed and ArticlesSeed classes, then:

bin/cake migrations seed

Read more about building migrations and data seeding: Migrations42


With the database built, we can now build Models.
42 https://book.cakephp.org/migrations/4/

52 Chapter 4. Tutorials & Examples


CakePHP Book, Release 5.x

CMS Tutorial - Creating our First Model


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

<?php
// src/Model/Table/ArticlesTable.php
declare(strict_types=1);

namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table


{
public function initialize(array $config): void
{
parent::initialize($config);
$this->addBehavior('Timestamp');
}
}

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

ò Note

CakePHP will dynamically create a model object for you if it cannot find a corresponding file in src/Model/Table.
This also means that if you accidentally name your file wrong (i.e. articlestable.php or 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;

class Article extends Entity


{
protected array $_accessible = [
'user_id' => true,
(continues on next page)

CMS Tutorial - Creating our First Model 53


CakePHP Book, Release 5.x

(continued from previous page)


'title' => true,
'slug' => true,
'body' => true,
'published' => true,
'created' => true,
'modified' => true,
'user' => true,
'tags' => true,
];
}

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.

CMS Tutorial - Creating the Articles Controller


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

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

namespace App\Controller;

class ArticlesController extends AppController


{
}

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

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

namespace App\Controller;

class ArticlesController extends AppController


{
public function index()
(continues on next page)

54 Chapter 4. Tutorials & Examples


CakePHP Book, Release 5.x

(continued from previous page)


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

By defining function index() in our ArticlesController, users can now access the logic there by requesting
www.example.com/articles/index. Similarly, if we were to define a function called foobar(), users would be able to
access that at www.example.com/articles/foobar. You may be tempted to name your controllers and actions in a way
that allows you to obtain specific URLs. Resist that temptation. Instead, follow the CakePHP Conventions creating
readable, meaningful action names. You can then use Routing to connect the URLs you want to the actions you’ve
created.
Our controller action is very simple. It fetches a paginated set of articles from the database, using the Articles Model
that is automatically loaded via naming conventions. It then uses set() to pass the articles into the Template (which
we’ll create soon). CakePHP will automatically render the template after our controller action completes.

Create the Article List Template


Now that we have our controller pulling data from the model, and preparing our view context, let’s create a view
template for our index action.
CakePHP view templates are presentation-flavored PHP code that is inserted inside the application’s layout. While
we’ll be creating HTML here, Views can also generate JSON, CSV or even binary files like PDFs.
A layout is presentation code that is wrapped around a view. Layout files contain common site elements like headers,
footers and navigation elements. Your application can have multiple layouts, and you can switch between them, but for
now, let’s just use the default layout.
CakePHP’s template files are stored in templates inside a folder named after the controller they correspond to. So we’ll
have to create a folder named ‘Articles’ in this case. Add the following code to your application:

<!-- File: templates/Articles/index.php -->

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

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

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


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

</td>
<td>
<?= $article->created->format(DATE_RFC850) ?>
</td>
</tr>
(continues on next page)

CMS Tutorial - Creating the Articles Controller 55


CakePHP Book, Release 5.x

(continued from previous page)


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

Create the View Action


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

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

public function view($slug = null)


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

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.

Create the View Template


Let’s create the view for our new ‘view’ action and place it in templates/Articles/view.php

<!-- File: templates/Articles/view.php -->

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


<p><?= h($article->body) ?></p>
<p><small>Created: <?= $article->created->format(DATE_RFC850) ?></small></p>
<p><?= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?></p>

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

56 Chapter 4. Tutorials & Examples


CakePHP Book, Release 5.x

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;

class ArticlesController extends AppController


{
public function index()
{
$articles = $this->paginate($this->Articles);
$this->set(compact('articles'));
}

public function view($slug)


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

public function add()


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

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


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

if ($this->Articles->save($article)) {
$this->Flash->success(__('Your article has been saved.'));

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


}
$this->Flash->error(__('Unable to add your article.'));
}
$this->set('article', $article);
}
}

ò Note

You need to include the Flash component in any controller where you will use it. Often it makes sense to include
it in your AppController, which is there already for this tutorial.

Here’s what the add() action does:


• If the HTTP method of the request was POST, try to save the data using the Articles model.

CMS Tutorial - Creating the Articles Controller 57


CakePHP Book, Release 5.x

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

Create Add Template


Here’s our add view template:

<!-- File: templates/Articles/add.php -->

<h1>Add Article</h1>
<?php
echo $this->Form->create($article);
// Hard code the user for now.
echo $this->Form->control('user_id', ['type' => 'hidden', 'value' => 1]);
echo $this->Form->control('title');
echo $this->Form->control('body', ['rows' => '3']);
echo $this->Form->button(__('Save Article'));
echo $this->Form->end();
?>

We use the FormHelper to generate the opening tag for an HTML form. Here’s the HTML that
$this->Form->create() generates:

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

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

58 Chapter 4. Tutorials & Examples


CakePHP Book, Release 5.x

<?= $this->Html->link('Add Article', ['action' => 'add']) ?>

Adding Simple Slug Generation


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

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

// Add the following method.

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


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

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

Add Edit Action


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

// in src/Controller/ArticlesController.php

// Add the following method.

public function edit($slug)


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

if ($this->request->is(['post', 'put'])) {
$this->Articles->patchEntity($article, $this->request->getData());
if ($this->Articles->save($article)) {
$this->Flash->success(__('Your article has been updated.'));

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


}
(continues on next page)

CMS Tutorial - Creating the Articles Controller 59


CakePHP Book, Release 5.x

(continued from previous page)


$this->Flash->error(__('Unable to update your article.'));
}

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

This action first ensures that the user has tried to access an existing record. If they haven’t passed in an $slug parameter,
or the article does not exist, a 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.

Create Edit Template


The edit template should look like this:

<!-- File: templates/Articles/edit.php -->

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

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


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

</td>
(continues on next page)

60 Chapter 4. Tutorials & Examples


CakePHP Book, Release 5.x

(continued from previous page)


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

</table>

Update Validation Rules for Articles


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

// src/Model/Table/ArticlesTable.php

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

// Add the following method.


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

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

Add Delete Action


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

// src/Controller/ArticlesController.php

// Add the following method.


(continues on next page)

CMS Tutorial - Creating the Articles Controller 61


CakePHP Book, Release 5.x

(continued from previous page)

public function delete($slug)


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

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

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


}
}

This logic deletes the article specified by $slug, and uses $this->Flash->success() to show the user a 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:

<!-- File: templates/Articles/index.php (delete links added) -->

<h1>Articles</h1>
<p><?= $this->Html->link("Add Article", ['action' => 'add']) ?></p>
<table>
<tr>
<th>Title</th>
<th>Created</th>
<th>Action</th>
</tr>

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

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


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

</td>
<td>
<?= $article->created->format(DATE_RFC850) ?>
</td>
<td>
(continues on next page)

62 Chapter 4. Tutorials & Examples


CakePHP Book, Release 5.x

(continued from previous page)


<?= $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

The ArticlesController can also be built with bake:


/bin/cake bake controller articles

However, this does not build the templates/Articles/*.php files.

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

CMS Tutorial - Tags and Users


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

cd /path/to/our/app

# You can overwrite any existing files.


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

These 3 commands will generate:


• The Table, Entity, Fixture files.
• The Controller
• The CRUD templates.

CMS Tutorial - Tags and Users 63


CakePHP Book, Release 5.x

• Test cases for each generated class.


Bake will also use the CakePHP conventions to infer the associations, and validation your models have.

Adding Tagging to Articles


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

# Generate all the code at once.


bin/cake bake all tags

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

public function initialize(array $config): void


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

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

Updating Articles to Enable Tagging


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

<?php
// in src/Controller/ArticlesController.php
namespace App\Controller;

use App\Controller\AppController;

class ArticlesController extends AppController


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

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


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

if ($this->Articles->save($article)) {
$this->Flash->success(__('Your article has been saved.'));

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


}
$this->Flash->error(__('Unable to add your article.'));
(continues on next page)

64 Chapter 4. Tutorials & Examples


CakePHP Book, Release 5.x

(continued from previous page)


}
// Get a list of tags.
$tags = $this->Articles->Tags->find('list')->all();

// Set tags to the view context


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

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

// Other actions
}

The added lines load a list of tags as an associative array of id => title. This format will let us create a new tag
input in our template. Add the following to the PHP block of controls in templates/Articles/add.php:

echo $this->Form->control('tags._ids', ['options' => $tags]);

This will render a multiple select element that uses the $tags variable to generate the select box options. You should
now create a couple new articles that have tags, as in the following section we’ll be adding the ability to find articles
by tags.
You should also update the edit method to allow adding or editing tags. The edit method should now look like:

public function edit($slug)


{
$article = $this->Articles
->findBySlug($slug)
->contain('Tags') // load associated Tags
->firstOrFail();
if ($this->request->is(['post', 'put'])) {
$this->Articles->patchEntity($article, $this->request->getData());
if ($this->Articles->save($article)) {
$this->Flash->success(__('Your article has been updated.'));

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


}
$this->Flash->error(__('Unable to update your article.'));
}

// Get a list of tags.


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

// Set tags to the view context


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

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

Remember to add the new tags multiple select control we added to the add.php template to the tem-
plates/Articles/edit.php template as well.

CMS Tutorial - Tags and Users 65


CakePHP Book, Release 5.x

Finding Articles By Tags


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

<?php
use Cake\Routing\Route\DashedRoute;
use Cake\Routing\RouteBuilder;

$routes->setRouteClass(DashedRoute::class);

$routes->scope('/', function (RouteBuilder $builder) {


$builder->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
$builder->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']);

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

public function tags()


{
// The 'pass' key is provided by CakePHP and contains all
// the passed URL path segments in the request.
$tags = $this->request->getParam('pass');

// Use the ArticlesTable to find tagged articles.


$articles = $this->Articles->find('tagged', tags: $tags)
->all();

// Pass variables into the view template context.


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

To access other parts of the request data, consult the Request section.
Since passed arguments are passed as method parameters, you could also write the action using PHP’s variadic argu-

66 Chapter 4. Tutorials & Examples


CakePHP Book, Release 5.x

ment:

public function tags(...$tags)


{
// Use the ArticlesTable to find tagged articles.
$articles = $this->Articles->find('tagged', tags: $tags)
->all();

// Pass variables into the view template context.


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

Creating the Finder Method


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

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

// The $query argument is a query builder instance.


// The $options array will contain the 'tags' option we passed
// to find('tagged') in our controller action.
public function findTagged(SelectQuery $query, array $tags = []): SelectQuery
{
$columns = [
'Articles.id', 'Articles.user_id', 'Articles.title',
'Articles.body', 'Articles.published', 'Articles.created',
'Articles.slug',
];

$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

CMS Tutorial - Tags and Users 67


CakePHP Book, Release 5.x

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.

Creating the View


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

<!-- In templates/Articles/tags.php -->


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

<section>
<?php foreach ($articles as $article): ?>
<article>
<!-- Use the HtmlHelper to create a link -->
<h4><?= $this->Html->link(
$article->title,
['controller' => 'Articles', 'action' => 'view', $article->slug]
) ?></h4>
<span><?= h($article->created) ?></span>
</article>
<?php endforeach; ?>
</section>

In the above code we use the Html and Text helpers to assist in generating our view output. We also use the h shortcut
function to HTML encode output. You should remember to always use h() when outputting data to prevent HTML
injection issues.
The tags.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’.

Improving the Tagging Experience


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

Adding a Computed Field


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

// add this use statement right below the namespace declaration to import
// the Collection class
use Cake\Collection\Collection;
(continues on next page)

68 Chapter 4. Tutorials & Examples


CakePHP Book, Release 5.x

(continued from previous page)

// Update the accessible property to contain `tag_string`


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

protected function _getTagString()


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

return trim($str, ', ');


}

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

Updating the Views


With the entity updated we can add a new control for our tags. In templates/Articles/add.php and tem-
plates/Articles/edit.php, replace the existing tags._ids control with the following:

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

We’ll also need to update the article view template. In templates/Articles/view.php add the line as shown:

<!-- File: templates/Articles/view.php -->

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


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

You should also update the view method to allow retrieving existing tags:

// src/Controller/ArticlesController.php file

public function view($slug = null)


{
// Update retrieving tags with contain()
$article = $this->Articles
->findBySlug($slug)
->contain('Tags')
->firstOrFail();
(continues on next page)

CMS Tutorial - Tags and Users 69


CakePHP Book, Release 5.x

(continued from previous page)


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

Persisting the Tag String


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

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


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

// Other code
}

protected function _buildTags($tagString)


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

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

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


foreach ($tags->extract('title') as $existing) {
$index = array_search($existing, $newTags);
if ($index !== false) {
unset($newTags[$index]);
}
}
// Add existing tags.
foreach ($tags as $tag) {
$out[] = $tag;
}
// Add new tags.
foreach ($newTags as $tag) {
$out[] = $this->Tags->newEntity(['title' => $tag]);
}

return $out;
}

If you now create or edit articles, you should be able to save tags as a comma separated list of tags, and have the tags
and linking records automatically created.

70 Chapter 4. Tutorials & Examples


CakePHP Book, Release 5.x

While this code is a bit more complicated than what we’ve done so far, it helps to showcase how powerful the ORM
in CakePHP is. You can manipulate query results using the Collections methods, and handle scenarios where you are
creating entities on the fly with ease.

Auto-populating the Tag String


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

public function initialize(array $config): void


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

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

public function edit($slug)


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

public function view($slug = null)


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

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

CMS Tutorial - Authentication


Now that our CMS has users, we can enable them to login using the cakephp/authentication45 plugin. We’ll start off by
ensuring passwords are stored securely in our database. Then we are going to provide a working login and logout, and
45 https://book.cakephp.org/authentication/

CMS Tutorial - Authentication 71


CakePHP Book, Release 5.x

enable new users to register.

Installing Authentication Plugin


Use composer to install the Authentication Plugin:

composer require "cakephp/authentication:~3.0"

Adding Password Hashing


You need to have created the Controller, Table, Entity and templates for the users table in your database. You
can do this manually like you did before for the ArticlesController, or you can use the bake shell to generate the classes
for you using:

bin/cake bake all users

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;

use Authentication\PasswordHasher\DefaultPasswordHasher; // Add this line


use Cake\ORM\Entity;

class User extends Entity


{
// Code from bake.

// Add this method


protected function _setPassword(string $password) : ?string
{
if (strlen($password) > 0) {
return (new DefaultPasswordHasher())->hash($password);
}
return null;
}
}

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/

72 Chapter 4. Tutorials & Examples


CakePHP Book, Release 5.x

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 add the following imports


use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Cake\Routing\Router;
use Psr\Http\Message\ServerRequestInterface;

Then implement the authentication interface on your Application class:

// in src/Application.php
class Application extends BaseApplication
implements AuthenticationServiceProviderInterface
{

Then add the following:

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

CMS Tutorial - Authentication 73


CakePHP Book, Release 5.x

(continued from previous page)


// Add the AuthenticationMiddleware. It should be after routing and body parser.
->add(new AuthenticationMiddleware($this));

return $middlewareQueue;
}

public function getAuthenticationService(ServerRequestInterface $request):␣


˓→AuthenticationServiceInterface

{
$authenticationService = new AuthenticationService([
'unauthenticatedRedirect' => Router::url('/users/login'),
'queryParam' => 'redirect',
]);

// Load identifiers, ensure we check email and password fields


$authenticationService->loadIdentifier('Authentication.Password', [
'fields' => [
'username' => 'email',
'password' => 'password',
],
]);

// Load the authenticators, you want session first


$authenticationService->loadAuthenticator('Authentication.Session');
// Configure form data check to pick email and password
$authenticationService->loadAuthenticator('Authentication.Form', [
'fields' => [
'username' => 'email',
'password' => 'password',
],
'loginUrl' => Router::url('/users/login'),
]);

return $authenticationService;
}

In your AppController class add the following code:

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

74 Chapter 4. Tutorials & Examples


CakePHP Book, Release 5.x

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)

In your UsersController, add the following code:


public function beforeFilter(\Cake\Event\EventInterface $event)
{
parent::beforeFilter($event);
// Configure the login action to not require authentication, preventing
// the infinite redirect loop issue
$this->Authentication->addUnauthenticatedActions(['login']);
}

public function login()


{
$this->request->allowMethod(['get', 'post']);
$result = $this->Authentication->getResult();
// regardless of POST or GET, redirect if user is logged in
if ($result && $result->isValid()) {
// redirect to /articles after login success
$redirect = $this->request->getQuery('redirect', [
'controller' => 'Articles',
'action' => 'index',
]);

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

Add the template logic for your login action:


<!-- in /templates/Users/login.php -->
<div class="users form">
<?= $this->Flash->render() ?>
<h3>Login</h3>
<?= $this->Form->create() ?>
<fieldset>
<legend><?= __('Please enter your username and password') ?></legend>
<?= $this->Form->control('email', ['required' => true]) ?>
<?= $this->Form->control('password', ['required' => true]) ?>
</fieldset>
<?= $this->Form->submit(__('Login')); ?>
<?= $this->Form->end() ?>
(continues on next page)

CMS Tutorial - Authentication 75


CakePHP Book, Release 5.x

(continued from previous page)

<?= $this->Html->link("Add User", ['action' => 'add']) ?>


</div>

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

return $this->redirect(['controller' => 'Users', 'action' => 'login']);


}
}

Now you can visit /users/logout to log out. You should then be sent to the login page.

76 Chapter 4. Tutorials & Examples


CakePHP Book, Release 5.x

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:

// Add to the beforeFilter method of UsersController


$this->Authentication->addUnauthenticatedActions(['login', 'add']);

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.

CMS Tutorial - Authorization


With users now able to login to our CMS, we want to apply authorization rules to ensure that each user only edits the
posts they own. We’ll use the authorization plugin48 to do this.

Installing Authorization Plugin


Use composer to install the Authorization Plugin:

composer require "cakephp/authorization:^3.0"

Load the plugin by adding the following statement to the bootstrap() method in src/Application.php:

$this->addPlugin('Authorization');

Enabling the Authorization Plugin


The Authorization plugin integrates into your application as a middleware layer and optionally a component to make
checking authorization easier. First, lets apply the middleware. In src/Application.php add the following to the class
imports:

use Authorization\AuthorizationService;
use Authorization\AuthorizationServiceInterface;
use Authorization\AuthorizationServiceProviderInterface;
use Authorization\Middleware\AuthorizationMiddleware;
use Authorization\Policy\OrmResolver;

Add the AuthorizationServiceProviderInterface to the implemented interfaces on your application:

class Application extends BaseApplication


implements AuthenticationServiceProviderInterface,
AuthorizationServiceProviderInterface

Then add the following to your middleware() method:

// Add authorization **after** authentication


$middlewareQueue->add(new AuthorizationMiddleware($this));

48 https://book.cakephp.org/authorization/2

CMS Tutorial - Authorization 77


CakePHP Book, Release 5.x

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:

public function getAuthorizationService(ServerRequestInterface $request):␣


˓→AuthorizationServiceInterface

{
$resolver = new OrmResolver();

return new AuthorizationService($resolver);


}

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:

// In the add, login, and logout methods


$this->Authorization->skipAuthorization();

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.

Creating our First Policy


The Authorization plugin models authorization and permissions as Policy classes. These classes implement the logic
to check whether or not a identity is allowed to perform an action on a given resource. Our identity is going to be
our logged in user, and our resources are our ORM entities and queries. Lets use bake to generate a basic policy:

bin/cake bake policy --type entity Article

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

public function canEdit(IdentityInterface $user, Article $article)


(continues on next page)

78 Chapter 4. Tutorials & Examples


CakePHP Book, Release 5.x

(continued from previous page)


{
// logged in users can edit their own articles.
return $this->isAuthor($user, $article);
}

public function canDelete(IdentityInterface $user, Article $article)


{
// logged in users can delete their own articles.
return $this->isAuthor($user, $article);
}

protected function isAuthor(IdentityInterface $user, Article $article)


{
return $article->user_id === $user->getIdentifier();
}
}

While we’ve defined some very simple rules, you can use as complex logic as your application requires in your policies.

Checking Authorization in the ArticlesController


With our policy created we can start checking authorization in each controller action. If we forget to check or skip
authorization in an controller action the Authorization plugin will raise an exception letting us know we forgot to apply
authorization. In src/Controller/ArticlesController.php add the following to the add, edit and delete methods:

public function add()


{
$article = $this->Articles->newEmptyEntity();
$this->Authorization->authorize($article);
// Rest of the method
}

public function edit($slug)


{
$article = $this->Articles
->findBySlug($slug)
->contain('Tags') // load associated Tags
->firstOrFail();
$this->Authorization->authorize($article);
// Rest of the method.
}

public function delete($slug)


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

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

CMS Tutorial - Authorization 79


CakePHP Book, Release 5.x

$this->Authorization->authorize($article, 'update');

Lastly add the following to the tags, view, and index methods on the ArticlesController:

// View, index and tags actions are public methods


// and don't require authorization checks.
$this->Authorization->skipAuthorization();

Fixing the Add & Edit Actions


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

// in src/Controller/ArticlesController.php

public function add()


{
$article = $this->Articles->newEmptyEntity();
$this->Authorization->authorize($article);

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

// Changed: Set the user_id from the current user.


$article->user_id = $this->request->getAttribute('identity')->getIdentifier();

if ($this->Articles->save($article)) {
$this->Flash->success(__('Your article has been saved.'));

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


}
$this->Flash->error(__('Unable to add your article.'));
}
$tags = $this->Articles->Tags->find('list')->all();
$this->set(compact('article', 'tags'));
}

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

// in src/Controller/ArticlesController.php

public function edit($slug)


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

if ($this->request->is(['post', 'put'])) {
(continues on next page)

80 Chapter 4. Tutorials & Examples


CakePHP Book, Release 5.x

(continued from previous page)


$this->Articles->patchEntity($article, $this->request->getData(), [
// Added: Disable modification of user_id.
'accessibleFields' => ['user_id' => false]
]);
if ($this->Articles->save($article)) {
$this->Flash->success(__('Your article has been updated.'));

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


}
$this->Flash->error(__('Unable to update your article.'));
}
$tags = $this->Articles->Tags->find('list')->all();
$this->set(compact('article', 'tags'));
}

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.

CMS Tutorial - Authorization 81


CakePHP Book, Release 5.x

82 Chapter 4. Tutorials & Examples


CHAPTER 5

Contributing

There are a number of ways you can contribute to CakePHP. The following sections cover the various ways you can
contribute to CakePHP:

Documentation
Contributing to the documentation is simple. The files are hosted on https://github.com/cakephp/docs. Feel free to
fork the repo, add your changes/improvements/translations and give back by issuing a pull request. You can even edit
the docs online with GitHub, without ever downloading the files – the “Improve this Doc” button on any given page
will direct you to GitHub’s online editor for that page.
CakePHP documentation is continuously 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.

New Translation Language


We want to provide translations that are as complete as possible. However, there may be times where a translation file
is not up-to-date. You should always consider the English version as the authoritative version.
If your language is not in the current languages, please contact us through Github and we will consider creating a
skeleton folder for it. The following sections are the first one you should consider translating as these files don’t change
often:
• index.rst
• intro.rst
• quickstart.rst
49 https://en.wikipedia.org/wiki/Continuous_integration

83
CakePHP Book, Release 5.x

• installation.rst
• /intro folder
• /tutorials-and-examples folder

Reminder for Docs Administrators


The structure of all language folders should mirror the English folder structure. If the structure changes for the English
version, we should apply those changes in the other languages.
For example, if a new English file is created in en/file.rst, we should:
• Add the file in all other languages : fr/file.rst, zh/file.rst, . . .
• Delete the content, but keeping the title, meta information and eventual toc-tree elements. The following
note will be added while nobody has translated the file:

File Title
##########

.. note::
The documentation is not currently supported in XX language for this
page.

Please feel free to send us a pull request on


`Github <https://github.com/cakephp/docs>`_ or use the **Improve This Doc**
button to directly propose your changes.

You can refer to the English version in the select top menu to have
information about this page's topic.

// If toc-tree elements are in the English version


.. toctree::
:maxdepth: 1

one-toc-file
other-toc-file

.. meta::
:title lang=xx: File Title
:keywords lang=xx: title, description,...

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.

Documentation Formatting Guide


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

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

Headings and Sections


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

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

Inline Markup
• One asterisk: text for emphasis (italics) We’ll use it for general highlighting/emphasis.
– *text*.
• Two asterisks: text for strong emphasis (boldface) We’ll use it for working directories, bullet list subject, table
names and excluding the following word “table”.
– **/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

* Back to the first level.

Definition lists can be created by doing the following:

term
definition
CakePHP
An MVC framework for PHP

Terms cannot be more than one line, but definitions can be multi-line and all lines should be indented consistently.

Links
There are several kinds of links, each with their own uses.

External Links

Links to external documents can be done with the following:

`External Link to php.net <https://php.net>`_

86 Chapter 5. Contributing
CakePHP Book, Release 5.x

The resulting link would look like this: External Link to php.net53

Links to Other Pages

:doc:
Other pages in the documentation can be linked to using the :doc: role. You can link to the specified document
using either an absolute or relative path reference. You should omit the .rst extension. For example, if the 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.

Cross Referencing Links

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

.. _label-name:

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

More content here.

Elsewhere you could reference the above section using :ref:`label-name`. The link’s text would be the title
that the link preceded. You can also provide custom link text using :ref:`Link text <label-name>`.

Prevent Sphinx to Output Warnings

Sphinx will output warnings if a file is not referenced in a toc-tree. It’s a great way to ensure that all files have a link
directed to them, but sometimes, you don’t need to insert a link for a file, eg. for our epub-contents and pdf-contents
files. In those cases, you can add :orphan: at the top of the file, to suppress warnings that the file is not in the toc-tree.

Describing Classes and their Contents


The CakePHP documentation uses the phpdomain54 to provide custom directives for describing PHP objects and con-
structs. Using these directives and roles is required to give proper indexing and cross referencing features.

Describing Classes and Constructs


Each directive populates the index, and or the namespace index.
.. php:global:: name
This directive declares a new PHP global variable.
.. php:function:: name(signature)
Defines a new global function outside of a class.

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

Text about the class

.. php:method:: methodName()

Text about the method

ã See also

php:method, php:attr, php:const

.. php:method:: name(signature)
Describe a class method, its arguments, return value, and exceptions:

.. php:method:: instanceMethod($one, $two)

:param string $one: The first parameter.


:param string $two: The second parameter.
:returns: An array of stuff.
:throws: InvalidArgumentException

This is an instance method.

.. php:staticmethod:: ClassName::methodName(signature)
Describe a static method, its arguments, return value and exceptions, see php:method for options.
.. php:attr:: name
Describe an property/attribute on a class.

88 Chapter 5. Contributing
CakePHP Book, Release 5.x

Prevent Sphinx to Output Warnings

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

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


:noindex:

Cross Referencing

The following roles refer to PHP objects and links are generated if a matching directive is found:
:php:func:
Reference a PHP function.
:php:global:
Reference a global variable whose name has $ prefix.
:php:const:
Reference either a global constant, or a class constant. Class constants should be preceded by the owning class:

DateTime has an :php:const:`DateTime::ATOM` constant.

:php:class:
Reference a class by name:

:php:class:`ClassName`

:php:meth:
Reference a method of a class. This role supports both kinds of methods:

:php:meth:`DateTime::setDate`
:php:meth:`Classname::staticMethod`

:php:attr:
Reference a property on an object:

:php:attr:`ClassName::$propertyName`

:php:exc:
Reference an exception.

Source Code
Literal code blocks are created by ending a paragraph with ::. The literal block must be indented, and like all paragraphs
be separated by single lines:

This is a paragraph::

while ($i--) {
doStuff()
(continues on next page)

Documentation 89
CakePHP Book, Release 5.x

(continued from previous page)


}

This is regular text again.

Literal text is not modified or formatted, save that one level of indentation is removed.

Notes and Warnings


There are often times when you want to inform the reader of an important tip, special note or a potential hazard.
Admonitions in sphinx are used for just that. There are fives kinds of admonitions.
• .. tip:: Tips are used to document or re-iterate interesting or important information. The content of the
directive should be written in complete sentences and include all appropriate punctuation.
• .. note:: Notes are used to document an especially important piece of information. The content of the direc-
tive should be written in complete sentences and include all appropriate punctuation.
• .. warning:: Warnings are used to document potential stumbling blocks, or information pertaining to security.
The content of the directive should be written in complete sentences and include all appropriate punctuation.
• .. versionadded:: X.Y.Z “Version added” admonitions are used to display notes specific to new features
added at a specific version, X.Y.Z being the version on which the said feature was added.
• .. deprecated:: X.Y.Z As opposed to “version added” admonitions, “deprecated” admonition are used to
notify of a deprecated feature, X.Y.Z being the version on which the said feature was deprecated.
All admonitions are made the same:

.. note::

Indented and preceded and followed by a blank line. Just like a


paragraph.

This text is not part of the note.

Samples

 Tip

This is a helpful tid-bit you probably forgot.

ò Note

You should pay attention here.

. Warning

It could be dangerous.

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 .

Reporting Security Issues


If you’ve found a security issue in CakePHP, please use the following procedure instead of the normal bug reporting
system. Instead of using the bug tracker, mailing list or IRC please send an email to security [at] cakephp.org. Emails
sent to this address go to the CakePHP core team on a private mailing list.
For each report, we try to first confirm the vulnerability. Once confirmed, the CakePHP team will take the following
actions:
• Acknowledge to the reporter that we’ve received the issue, and are working on a fix. We ask that the reporter
keep the issue confidential until we announce it.
• Get a fix/patch prepared.
• Prepare a post describing the vulnerability, and the possible exploits.
• Release new versions of all affected versions.
• Prominently feature the problem in the release announcement.

Code
Patches and pull requests are a great way to contribute code back to CakePHP. Pull requests can be created in GitHub,
and are preferred over patch files in ticket comments.

Initial Setup
Before working on patches for CakePHP, it’s a good idea to get your environment setup. You’ll need the following
software:
• 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

• PHP 8.1 or greater


• PHPUnit 5.7.0 or greater
Set up your user information with your name/handle and working email address:

git config --global user.name 'Bob Barker'


git config --global user.email '[email protected]'

ò Note

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

Get a clone of the CakePHP source code from GitHub:


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

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

Add the original CakePHP repository as a remote repository. You’ll use this later to fetch changes from the CakePHP
repository. This will let you stay up to date with CakePHP:

cd cakephp
git remote add upstream git://github.com/cakephp/cakephp.git

Now that you have CakePHP setup you should be able to define a $test database connection, and run all the tests.

Working on a Patch
Each time you want to work on a bug, feature or enhancement create a topic branch.
The branch you create should be based on the version that your fix/enhancement is for. For example if you are fixing a
bug in 3.x you would want to use the master branch as the base for your branch. If your change is a bug fix for the
2.x release series, you should use the 2.x branch:

# fixing a bug on 3.x


git fetch upstream
git checkout -b ticket-1234 upstream/master

# fixing a bug on 2.x


git fetch upstream
git checkout -b ticket-1234 upstream/2.x

 Tip

Use a descriptive name for your branch. Referencing the ticket or feature name is a good convention. 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.

Submitting a Pull Request


Once your changes are done and you’re ready for them to be merged into CakePHP, you’ll want to update your branch:
# Rebase fix on top of master
git checkout master
git fetch upstream
git merge upstream/master
git checkout <branch_name>
git rebase master

This will fetch + merge in any changes that have happened in CakePHP since you started. It will then rebase - or replay
your changes on top of the current code. You might encounter a conflict during the rebase. If the rebase quits early you
can see which files are conflicted/un-merged with git status. Resolve each conflict, and then continue the rebase:
git add <filename> # do this for each conflicted file.
git rebase --continue

Check that all your tests continue to pass. Then push your branch to your fork:
git push origin <branch-name>

If you’ve rebased after pushing your branch, you’ll need to use force push:
git push --force origin <branch-name>

Once your branch is on GitHub, you can submit a pull request on GitHub.

Choosing Where Your Changes will be Merged Into


When making pull requests you should make sure you select the correct base branch, as you cannot edit it once the pull
request is created.
• If your change is a bugfix and doesn’t introduce new functionality and only corrects existing behavior that is
present in the current release. Then choose master as your merge target.
• If your change is a new feature or an addition to the framework, then you should choose the branch with the next
version number. For example if the current stable release is 4.0.0, the branch accepting new features will be
4.next.
• If your change is a breaks existing functionality, or APIs then you’ll have to choose then next major release. For
example, if the current release is 4.0.0 then the next time existing behavior can be broken will be in 5.x so you
should target that branch.

ò Note

Remember that all code you contribute to CakePHP will be licensed under the MIT License, and the Cake Software
Foundation64 will become the owner of any contributed code. Contributors should follow the CakePHP Community
Guidelines65 .

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.

Adding New Features


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

IDE Setup
Please make sure your IDE is set up to “trim right” on whitespaces. There should be no trailing spaces per line.
Most modern IDEs also support an .editorconfig file. The CakePHP app skeleton ships with it by default. It already
contains best practise defaults.
We recommend to use the 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)));

Use this instead:

$matches = array_intersect_key(
$this->_listeners,
array_flip(
preg_grep($matchPattern, array_keys($this->_listeners), 0)
)
);

Line Length
It is recommended to keep lines at approximately 100 characters long for better code readability. A limit of 80 or 120
characters makes it necessary to distribute complex logic or expressions by function, as well as give functions and
objects shorter, more expressive names. Lines must not be longer than 120 characters.
In short:
• 100 characters is the soft limit.
• 120 characters is the hard limit.

Control Structures
Control structures are for example “if”, “for”, “foreach”, “while”, “switch” etc. Below, an example with “if”:

if ((expr_1) || (expr_2)) {
// action_1;
} elseif (!(expr_3) && (expr_4)) {
// action_2;
} else {
// default_action;
}

• In the control structures there should be 1 (one) space before the first parenthesis and 1 (one) space between the
last parenthesis and the opening bracket.
• Always use curly brackets in control structures, even if they are not needed. They increase the readability of the
code, and they give you fewer logical errors.
• 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, badly placed statement


if (expr) statement;

// wrong = no brackets
if (expr)
statement;

// good
if (expr) {
statement;
}

// wrong = inline assignment


if ($variable = Class::function()) {
statement;
}

// good
$variable = Class::function();
if ($variable) {
statement;
}

Ternary Operator
Ternary operators are permissible when the entire ternary operation fits on one line. Longer ternaries should be split
into if else statements. Ternary operators should not ever be nested. Optionally parentheses can be used around the
condition check of the ternary for clarity:

// Good, simple and readable


$variable = isset($options['variable']) ? $options['variable'] : true;

// Nested ternaries are bad


$variable = isset($options['variable']) ? isset($options['othervar']) ? true : false :␣
˓→false;

Template Files
In template files 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:

if ($value === null) {


// ...
}

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

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

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

Function Calls
Functions should be called without space between function’s name and starting parenthesis. There should be one space
between every parameter of a function call:

$var = foo($bar, $bar2, $bar3);

As you can see above there should be one space on both sides of equals sign (=).

Method Definition
Example of a method definition:

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


{
if (expr) {
statement;
}

return $var;
}

Parameters with a default value, should be placed last in function definition. Try to make your functions return some-
thing, at least true or false, so it can be determined whether the function call was successful:

public function connection($dns, $persistent = false)


{
if (is_array($dns)) {
$dnsInfo = $dns;
} else {
$dnsInfo = BD::parseDNS($dns);
}
(continues on next page)

Coding Standards 97
CakePHP Book, Release 5.x

(continued from previous page)

if (!($dnsInfo) || !($dnsInfo['phpType'])) {
return $this->addError();
}

return true;
}

There are spaces on both side of the equals sign.

Bail Early
Try to avoid unnecessary nesting by bailing early:

public function run(array $data)


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

...
}

public function check(array $data)


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

...
}

This helps to keep the logic sequential which improves readability.

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

Anonymous Functions (Closures)


Defining anonymous functions follows the PSR-1269 coding style guide, where they are declared with a space after the
function keyword, and a space before and after the use keyword:

$closure = function ($arg1, $arg2) use ($var1, $var2) {


// code
};

Method Chaining
Method chaining should have multiple methods spread across separate lines, and indented with four spaces:

$email->from('[email protected]')
->to('[email protected]')
->subject('A great message')
->send();

Commenting Code
All comments should be written in English, and should in a clear way describe the commented block of code.
Comments can include the following 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

100 Chapter 5. Contributing


CakePHP Book, Release 5.x

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

Coding Standards 101


CakePHP Book, Release 5.x

Short Echo
The short echo should be used in template files in place of <?php echo. It should be immediately followed by a single
space, the variable or function value to echo, a single space, and the php closing tag:

// wrong = semicolon, no spaces


<td><?=$name;?></td>

// good = spaces, no semicolon


<td><?= $name ?></td>

As of PHP 5.4 the short echo tag (<?=) is no longer to be consider a ‘short tag’ is always available regardless of the
short_open_tag ini directive.

Naming Convention
Functions
Write all functions in camelBack:

function longFunctionName()
{
}

Classes
Class names should be written in CamelCase, for example:

class ExampleClass
{
}

Variables
Variable names should be as descriptive as possible, but also as short as possible. All variables should start with a
lowercase letter, and should be written in camelBack in case of multiple words. Variables referencing objects should
in some way associate to the class the variable is an object of. Example:

$user = 'John';
$users = ['John', 'Hans', 'Arne'];

$dispatcher = new Dispatcher();

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

Example Addresses
For all example URL and mail addresses use “example.com”, “example.org” and “example.net”, for example:
• Email: [email protected]
• WWW: http://www.example.com
• FTP: ftp://ftp.example.com

102 Chapter 5. Contributing


CakePHP Book, Release 5.x

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:

enum ArticleStatus: string


{
case Published = 'Y';
case NotPublishedYet = 'N';
}

80 https://datatracker.ietf.org/doc/html/rfc2606.html

Coding Standards 103


CakePHP Book, Release 5.x

Careful when using empty()/isset()


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

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

// Use boolean type coercion


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

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

class Thing
{
private $property; // Defined

public function readProperty()


{
// Not recommended as the property is defined in the class
if (!isset($this->property)) {
// ...
}
// Recommended
if ($this->property === null) {

}
}
}

When working with arrays, it is better to merge in defaults over using empty() checks. By merging in defaults, you
can ensure that required keys are defined:

function doWork(array $array)


{
// Merge defaults to remove need for empty checks.
$array += [
'key' => null,
];

// Not recommended, the key is already set


if (isset($array['key'])) {
// ...
(continues on next page)

104 Chapter 5. Contributing


CakePHP Book, Release 5.x

(continued from previous page)


}

// Recommended
if ($array['key'] !== null) {
// ...
}
}

Backwards Compatibility Guide


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

ò Note

Deprecations are removed with the next major version of the framework. It is advised that you adapt to 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/

Backwards Compatibility Guide 105


CakePHP Book, Release 5.x

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:

If you. . . Backwards compatibility?


Typehint against the class Yes
Create a new instance Yes
Extend the class Yes
Access a public property Yes
Call a public method Yes
Extend a class and. . .
Override a public property Yes
Access a protected property No1
Override a protected property No1
Override a protected method No1
Call a protected method No1
Add a public property No
Add a public method No
Add an argument to an overridden method No1
Add a default argument value to an existing method argument Yes

Working on CakePHP
If you are helping make CakePHP even better please keep the following guidelines in mind when adding/changing
functionality:
In a minor release you can:
1 Your code may be broken by minor releases. Check the migration guide for details.

106 Chapter 5. Contributing


CakePHP Book, Release 5.x

In a minor release can you. . .


Classes
Remove a class No
Remove an interface No
Remove a trait No
Make final No
Make abstract No
Change name Yes2
Properties
Add a public property Yes
Remove a public property No
Add a protected property Yes
Remove a protected property Yes3
Methods
Add a public method Yes
Remove a public method No
Add a protected method Yes
Move to parent class Yes
Remove a protected method Yes3
Reduce visibility No
Change method name Yes2
Add a new argument with default value Yes
Add a new required argument to an existing method. No
Remove a default value from an existing argument No
Change method type void Yes

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

Will disable runtime deprecation warnings.

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.

Backwards Compatibility Guide 107


CakePHP Book, Release 5.x

108 Chapter 5. Contributing


CHAPTER 6

Installation

CakePHP has a few system requirements:


• HTTP Server. For example: Apache. Having mod_rewrite is preferred, but by no means required. You can also
use nginx, or Microsoft IIS if you prefer.
• Minimum PHP 8.1 (8.4 supported).
• mbstring PHP extension
• intl PHP extension
• SimpleXML PHP extension
• PDO PHP extension

ò Note

In XAMPP, intl extension is included but you have to uncomment extension=php_intl.dll (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

• Installing Composer on Windows


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

Create a CakePHP Project


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

composer create-project --prefer-dist cakephp/app:~5.0 my_app_name

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

110 Chapter 6. Installation


CakePHP Book, Release 5.x

Keeping Up To Date with the Latest CakePHP Changes


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

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

Installation using DDEV


Another quick way to install CakePHP is via DDEV88 . It is an open source tool for launching local web development
environments.
If you want to configure a new project, you just need:

mkdir my-cakephp-app
cd my-cakephp-app
ddev config --project-type=cakephp --docroot=webroot
ddev composer create --prefer-dist cakephp/app:~5.0
ddev launch

If you have an existing project:

git clone <your-cakephp-repo>


cd <your-cakephp-project>
ddev config --project-type=cakephp --docroot=webroot
ddev composer install
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

HTTPDUSER=`ps aux | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root␣


˓→| head -1 | cut -d\ -f1`
setfacl -R -m u:${HTTPDUSER}:rwx tmp
setfacl -R -d -m u:${HTTPDUSER}:rwx tmp
setfacl -R -m u:${HTTPDUSER}:rwx logs
setfacl -R -d -m u:${HTTPDUSER}:rwx logs

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

chmod +x bin/cake

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

php bin/cake.php

Development Server
A development installation is the fastest way to setup CakePHP. In this example, we use CakePHP’s console to run
PHP’s built-in web server which will make your application available at http://host:port. From the app directory,
execute:

bin/cake server

By default, without any arguments provided, this will serve your application at http://localhost:8765/.
If there is conflict with localhost or port 8765, you can tell the CakePHP console to run the web server on a specific
host and/or port utilizing the following arguments:

bin/cake server -H 192.168.13.37 -p 5673

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


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

ò Note

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

. Warning

The development server should never be used in a production environment. It is only intended as a basic 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.

112 Chapter 6. Installation


CakePHP Book, Release 5.x

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:

LoadModule rewrite_module libexec/apache2/mod_rewrite.so

In many systems these will be commented out by default, so you may just need to remove the leading # symbols.
After you make changes, restart Apache to make sure the settings are active.
Verify that your .htaccess files are actually in the right directories. Some operating systems treat files that start
with ‘.’ as hidden and therefore won’t copy them.
3. Make sure your copy of CakePHP comes from the downloads section of the site or our Git repository, and has
been unpacked correctly, by checking for .htaccess files.
CakePHP app directory (will be copied to the top directory of your application by bake):

<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule ^$ webroot/ [L]
RewriteRule (.*) webroot/$1 [L]
</IfModule>

CakePHP webroot directory (will be copied to your application’s web root by bake):

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>

If your CakePHP site still has problems with mod_rewrite, you might want to try modifying settings for Virtual
Hosts. On Ubuntu, edit the file /etc/apache2/sites-available/default (location is distribution-dependent). In this
file, ensure that AllowOverride None is changed to AllowOverride All, so you have:

<Directory />
Options FollowSymLinks
AllowOverride All
</Directory>
(continues on next page)

114 Chapter 6. Installation


CakePHP Book, Release 5.x

(continued from previous page)


<Directory /var/www>
Options FollowSymLinks
AllowOverride All
Order Allow,Deny
Allow from all
</Directory>

On macOS, another solution is to use the tool virtualhostx90 to make a Virtual Host to point to your folder.
For many hosting services (GoDaddy, 1and1), your web server is being served from a user directory that al-
ready uses mod_rewrite. If you are installing CakePHP into a user directory (http://example.com/~username/
cakephp/), or any other URL structure that already utilizes mod_rewrite, you’ll need to add RewriteBase state-
ments to the .htaccess files CakePHP uses (.htaccess, webroot/.htaccess).
This can be added to the same section with the RewriteEngine directive, so for example, your webroot .htaccess
file would look like:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /path/to/app
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>

The details of those changes will depend on your setup, and can include additional things that are not related to
CakePHP. Please refer to Apache’s online documentation for more information.
4. (Optional) To improve production setup, you should prevent invalid assets from being parsed by CakePHP. Mod-
ify your webroot .htaccess to something like:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /path/to/app/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !^/(webroot/)?(img|css|js)/(.*)$
RewriteRule ^ index.php [L]
</IfModule>

The above will prevent incorrect assets from being sent to index.php and instead display your web server’s 404
page.
Additionally you can create a matching HTML 404 page, or use the default built-in CakePHP 404 by adding an
ErrorDocument directive:

ErrorDocument 404 /404-not-found

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/

URL Rewriting 115


CakePHP Book, Release 5.x

location / {
try_files $uri $uri/ /index.php?$args;
}

A sample of the server directive is as follows:

server {
listen 80;
listen [::]:80;
server_name www.example.com;
return 301 http://example.com$request_uri;
}

server {
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