Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3a1f080
Fix deprecation error in I18n (#71)
pbarabe May 22, 2024
4837233
Update README.md
kamilwylegala May 22, 2024
8773173
False to array conversion fix. (#73)
kamilwylegala May 24, 2024
7343bc8
Intl recommendation + `PHP81_BC\strftime` + Symfony ICU Polyfill (#69)
kamilwylegala Jun 5, 2024
da10e28
Fix potential CSRF circumvention with custom HTTP methods (#76)
MarkusBauer Jul 24, 2024
0b67e9f
Update README.md
kamilwylegala Jul 24, 2024
20f8875
Remove constructs that deprecate with PHP 8.4 (#75)
MarkusBauer Jul 24, 2024
b026059
Update README.md
kamilwylegala Jul 24, 2024
c00fe46
Added PDOException wrapper to avoid creating dynamic property. (#77)
kamilwylegala Sep 21, 2024
9fc1597
Fixed errorInfo in pdo exception wrapper. (#78)
kamilwylegala Sep 21, 2024
97562d6
inflector fix. (#80)
kamilwylegala Nov 16, 2024
725ba6c
Php8.4 (#83)
kamilwylegala Feb 4, 2025
1a9bdf8
Fixing deprecation in PHP 8.5 (#86)
kamilwylegala Feb 22, 2026
4220ed4
Fix readFile bypass for https urls in Xml::build() (#87)
iglocska Sep 18, 2026
67db9ab
PHP 8.5 compatibility: zero deprecations on 8.1-8.5, CI matrix, docs …
alenarmona Oct 16, 2026
dc18015
Fix PHP 8.4+ CI errors in Redis and Memcache cache engine tests
alenarmona Oct 16, 2026
33238a3
Bump version to 2.11.0 for the PHP 8.5 branch
alenarmona Oct 16, 2026
e4543fb
MemcacheEngineTest: use a recording fake instead of mocking Memcache
alenarmona Oct 16, 2026
cf9909a
Fix PHP 8.1+ deprecations hit only by the MySQL and Postgres test jobs
alenarmona Oct 16, 2026
4e44e9c
Remove stray certificate bundle added by a local Homebrew run
alenarmona Oct 16, 2026
c05d404
Skip the use_trans_sid redirect test from PHP 8.4, where enabling it …
alenarmona Oct 17, 2026
774f5e0
CakeSocketTest: skip the context tests when smtp.gmail.com is unreach…
alenarmona Oct 19, 2026
509d676
CI: publish PHPUnit failures as GitHub annotations
alenarmona Oct 19, 2026
e2cea0b
CI annotations: skip risky tests, show the failure message
alenarmona Oct 19, 2026
1dac863
CI: keep the upstream matrix plus PHP 8.5 only
alenarmona Oct 19, 2026
c47255e
CI annotations: only skip risky tests; PHP warnings are real errors
alenarmona Oct 19, 2026
f4f9d2a
CI annotations: publish PHPUnit's console summary as well
alenarmona Oct 19, 2026
9c2d2da
CI annotations: still publish the console summary when JUnit output i…
alenarmona Oct 19, 2026
50cc46d
Restore app/tmp/cache/views placeholder
alenarmona Oct 19, 2026
4b361f0
CI: drop the annotation step
alenarmona Oct 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 0 additions & 54 deletions .github/workflows/phpcs.yml

This file was deleted.

12 changes: 9 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ jobs:
db-type: sqlite
- php-version: '8.1'
db-type: mysql
- php-version: '8.5'
db-type: mysql
- php-version: '8.5'
db-type: pgsql
- php-version: '8.5'
db-type: sqlite

services:
mysql:
Expand Down Expand Up @@ -61,13 +67,13 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Install PHP with extensions
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: apcu, memcache, memcached, redis, mcrypt, pdo_mysql, pdo_pgsql, pdo_sqlite
extensions: apcu, memcache, memcached, redis, intl, pdo_mysql, pdo_pgsql, pdo_sqlite
ini-values: |
assert.exception=1,
zend.assertions=1,
Expand Down Expand Up @@ -105,7 +111,7 @@ jobs:
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

- name: Cache Composer
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.json') }}
Expand Down
98 changes: 95 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,56 @@

Unfortunately branch 2.x in original repository was taken down.

## Incoming breaking changes
## Zumba fork: PHP 8.5 branch (`cakephp2-php85`)

The `strftime` function is widely used in the framework codebase. We plan to replace it with `IntlDateFormatter::format()`, this method comes from the `Intl` extension. CakePHP2 doesn't use it, so it would be a **breaking change**. We'd like to start rolling it out in early **May 2024**.
This repository (`zumba/cakephp2-php8`) is Zumba's fork of [kamilwylegala/cakephp2-php8](https://github.com/kamilwylegala/cakephp2-php8).
The `cakephp2-php85` branch is the version our applications use on PHP 8.5 containers. It contains:

If you have any concerns, let's discuss it under: https://github.com/kamilwylegala/cakephp2-php8/issues/65
- Every upstream fix up to and including the CSRF hardening (upstream #76), the PHP 8.4 fixes (#75, #83) and the PHP 8.5 fixes (#86).
- A CI matrix that runs the framework test suite on PHP 8.0, 8.1 and 8.5. The test bootstrap converts every deprecation into a failure, so a green build means no deprecation notices on those versions.
- A local `docker/web` image based on `php:8.5-apache`.

The code stays compatible with PHP 8.0+; no PHP 8.5-only syntax is used.

### Requirements on PHP 8.5

- `ext-intl` must be installed in the container. `strftime()` was replaced by `IntlDateFormatter` (via `php81_bc/strftime`), which requires it.
- `ext-mcrypt` is no longer needed; `Security::encrypt()`/`decrypt()` use OpenSSL.

### Using the branch in an application

Point `composer.json` at this repository and at the branch:

```
{
"require": {
"cakephp/cakephp": "dev-cakephp2-php85 as 2.11.0"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/zumba/cakephp2-php8"
}
]
}
```

### Update procedure

Updating an application to the latest state of the branch is a standard composer update:

```
composer update cakephp/cakephp
```

Then run the application's own test suite under PHP 8.5. Commit the resulting `composer.lock`.

### Syncing new upstream fixes into the branch

1. `git fetch upstream` (where `upstream` is `https://github.com/kamilwylegala/cakephp2-php8.git`).
2. Bring the new upstream commits onto a feature branch cut from `cakephp2-php85` (merge or cherry-pick).
3. Run the test suite on PHP 8.5 (locally via `docker compose up` or by pushing and letting CI run the matrix).
4. Open a PR into `cakephp2-php85`.

## Why I created this fork? 🤔

Expand Down Expand Up @@ -38,6 +83,10 @@ Here are steps I took to migrate my project through all versions to PHP 8.1, may
- ~~Due to lack of tests ☝️~~ - **you also need to rely** on tests in your application after integrating with this fork.
- If after integration you spot any issues related to framework please let me know by creating an issue or pull request with fix.

### Breaking changes

- In order to get rid of `strftime()` deprecation notices, it's required to switch to `IntlDateFormatter` class. This class is available in `intl` extension. Fork doesn't require it explicitly but to be able to use its functions Symfony ICU Polyfill is installed. To provide `strftime` behavior compatibility, `PHP81_BC\strftime` is used. `PHP81_BC` doesn't fully cover strftime, your code should work but there is a chance you'll get slightly different results. Discussed (here)[https://github.com/kamilwylegala/cakephp2-php8/pull/64] and (here)[https://github.com/kamilwylegala/cakephp2-php8/issues/65].

## Installation

This repository **is not** available in packagist, therefore your project's `composer.json` must be changed to point to custom repository.
Expand All @@ -61,6 +110,49 @@ It means that composer will look at `master` branch of repository configured und

## Changelog

### 2026-10-16 (Zumba, branch `cakephp2-php85`)

- Version bumped to **2.11.0** (`lib/Cake/VERSION.txt`): new runtime requirement (`ext-intl`) and behaviour changes (exceptions instead of `E_USER_ERROR`, OpenSSL fallback) warrant a minor bump; upstream stays at 2.10.24, so the number also identifies this branch. Alias it as `dev-cakephp2-php85 as 2.11.0` in applications.
- Synced all upstream fixes up to `kamilwylegala/cakephp2-php8` #87 (includes the CSRF fix #76 and the PHP 8.4/8.5 fixes #75, #83, #86).
- CI matrix now runs PHP 8.0, 8.1 and 8.5; the test `app/Config/core.php` reports `E_ALL` so any deprecation fails the suite.
- PHP 8.5: `Security::cipher()` no longer casts the out-of-range `Security.cipherSeed` float to int (same seed value, no deprecation); `limit()`/`offset()` clamp out-of-range values instead of casting; `ConsoleLog` no longer calls `posix_isatty(null)`; `ReflectionProperty::setAccessible()` only on PHP < 8.1; null array offsets in `I18n`, `Helper`, `CakeResponse`, `ConsoleOptionParser`, `RequestHandlerComponent`, `Set`, `Configure`, `CakeSession` replaced by explicit checks; `libxml_disable_entity_loader()` calls removed.
- PHP 8.4: `trigger_error(..., E_USER_ERROR)` replaced by exceptions (`ConfigureException`, `CakeException`, `RuntimeException` in the webroot/console bootstrap scripts) or `ErrorHandler::handleFatalError()`.
- PHP 8.2: undeclared (dynamic) properties declared on `Scaffold`, `ConsoleOptionParser`, `HelpFormatter`, `MemcachedEngine`, `CakeTestCase`, `CakeTestFixture`, `CakeFixtureManager`, `ControllerTestDispatcher` and ~100 test classes; `ConnectionManager::create()` stores runtime connections on a `#[\AllowDynamicProperties]` subclass of `DATABASE_CONFIG`; `CakeRequest` allows dynamic properties.
- PHP 8.1: remaining "passing null to non-nullable internal parameter" cases fixed across the framework (strlen/strpos/strtolower/preg_*/trim/substr/… with `(string)` casts or null checks).
- `Security::encrypt()`/`decrypt()` fall back to OpenSSL when ext-mcrypt is missing.
- `SessionHandlerAdapter` methods marked `#[\ReturnTypeWillChange]`.
- Local `docker/web` image moved to `php:8.5-apache` (with `intl`, without `mcrypt`).

### 2025-02-04

- Fixes for PHP 8.4: `session_set_save_handler` accepts object, removed `E_STRICT` reference.
- Removed github action with php code sniffer. It's quite painful to work with. Need to migrate to something newer, that will affect code base as little as possible.

### 2024-11-16

- Inflector fix: str_place with null.

### 2024-09-21

- Added wrapper for PDOException to avoid creating dynamic property `queryString`.

### 2024-07-24

- Csrf vulnerabity fix back ported from Cake PHP 3
- Explicit type hint definition of nullable parameters.

### 2024-06-05

- Removed usage of `strftime`, replaced with Intl extension.

### 2024-05-24

- Fix deprecation error in Model: `Automatic conversion of false to array is deprecated`

### 2024-05-22

- Fix deprecation error in I18n: `Automatic conversion of false to array is deprecated`

### 2024-02-02

- `str_len` deprecation warning fix in CakeResponse (passing null instead of `string`)
Expand Down
2 changes: 1 addition & 1 deletion app/Config/core.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
*/
Configure::write('Error', array(
'handler' => 'ErrorHandler::handleError',
'level' => E_ALL & ~E_DEPRECATED,
'level' => E_ALL,
'trace' => true
));

Expand Down
2 changes: 1 addition & 1 deletion app/Console/cake.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
}

if (!include $dispatcher) {
trigger_error('Could not locate CakePHP core files.', E_USER_ERROR);
throw new RuntimeException('Could not locate CakePHP core files.');
}
unset($dispatcher);

Expand Down
2 changes: 1 addition & 1 deletion app/webroot/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
$failed = true;
}
if (!empty($failed)) {
trigger_error("CakePHP core could not be found. Check the value of CAKE_CORE_INCLUDE_PATH in APP/webroot/index.php. It should point to the directory containing your " . DS . "cake core directory and your " . DS . "vendors root directory.", E_USER_ERROR);
throw new RuntimeException("CakePHP core could not be found. Check the value of CAKE_CORE_INCLUDE_PATH in APP/webroot/index.php. It should point to the directory containing your " . DS . "cake core directory and your " . DS . "vendors root directory.");
}

App::uses('Dispatcher', 'Routing');
Expand Down
2 changes: 1 addition & 1 deletion app/webroot/test.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
}
}
if (!empty($failed)) {
trigger_error("CakePHP core could not be found. Check the value of CAKE_CORE_INCLUDE_PATH in APP/webroot/test.php. It should point to the directory containing your " . DS . "cake core directory and your " . DS . "vendors root directory.", E_USER_ERROR);
throw new RuntimeException("CakePHP core could not be found. Check the value of CAKE_CORE_INCLUDE_PATH in APP/webroot/test.php. It should point to the directory containing your " . DS . "cake core directory and your " . DS . "vendors root directory.");
}

if (Configure::read('debug') < 1) {
Expand Down
13 changes: 7 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@
"source": "https://github.com/cakephp/cakephp"
},
"require": {
"php": "^8.0"
"php": "^8.0",
"ext-mbstring": "*",
"symfony/polyfill-intl-icu": "^1.29",
"php81_bc/strftime": "^0.7.5"
},
"suggest": {
"ext-openssl": "You need to install ext-openssl or ext-mcrypt to use AES-256 encryption",
"ext-mcrypt": "You need to install ext-openssl or ext-mcrypt to use AES-256 encryption"
"ext-mcrypt": "You need to install ext-openssl or ext-mcrypt to use AES-256 encryption",
"ext-intl": "Required to use IntlDateFormatter instead of strftime, if not Symfony polyfill will be used."
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"cakephp/cakephp-codesniffer": "^1.0.0"
"phpunit/phpunit": "^9.5"
},
"config": {
"vendor-dir": "vendors/",
Expand All @@ -37,10 +40,8 @@
],
"scripts": {
"check": [
"@cs-check",
"@test"
],
"cs-check": "./vendors/bin/phpcs -p --extensions=php --standard=CakePHP ./lib/Cake",
"test": "./lib/Cake/Console/cake test core AllTests --stderr --verbose"
}
}
10 changes: 5 additions & 5 deletions docker/web/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
FROM php:8.2-apache
FROM php:8.5-apache

RUN apt-get update && apt-get install -y --no-install-recommends \
&& apt-get install -y libpq-dev libzip-dev unzip openssl libmcrypt-dev libmemcached-dev locales \
&& apt-get install -y libpq-dev libzip-dev unzip openssl libmcrypt-dev libmemcached-dev libicu-dev locales \
&& rm -rf /var/lib/apt/lists/*

RUN docker-php-ext-install pdo_mysql pdo_pgsql zip \
&& pecl install apcu redis memcached mcrypt xdebug \
&& docker-php-ext-enable redis memcached mcrypt \
RUN docker-php-ext-install pdo_mysql pdo_pgsql zip intl \
&& pecl install apcu redis memcached xdebug \
&& docker-php-ext-enable redis memcached \
&& docker-php-ext-enable xdebug \
&& echo "extension=apcu.so" >> /usr/local/etc/php/php.ini \
&& echo "apc.enable_cli = 1" >> /usr/local/etc/php/php.ini
Expand Down
8 changes: 8 additions & 0 deletions lib/Cake/Cache/Engine/MemcachedEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ class MemcachedEngine extends CacheEngine {
*/
public $settings = array();

/**
* Contains the compiled group names
* (prefixed with the global configuration prefix)
*
* @var array
*/
protected $_compiledGroupNames = array();

/**
* List of available serializer engines
*
Expand Down
2 changes: 1 addition & 1 deletion lib/Cake/Cache/Engine/RedisEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ public function decrement($key, $offset = 1) {
* @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed
*/
public function delete($key) {
return $this->_Redis->delete($key) > 0;
return $this->_Redis->del($key) > 0;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion lib/Cake/Console/Command/SchemaShell.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public function startup() {
$name = $this->params['name'] = $this->args[0];
}

if (strpos($name, '.')) {
if ($name !== null && strpos($name, '.')) {
list($this->params['plugin'], $splitName) = pluginSplit($name);
$name = $this->params['name'] = $splitName;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/Cake/Console/Command/Task/CommandTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public function subCommands($commandName) {
public function getShell($commandName) {
list($pluginDot, $name) = pluginSplit($commandName, true);

if (in_array(strtolower($pluginDot), array('app.', 'core.'))) {
if (in_array(strtolower((string)$pluginDot), array('app.', 'core.'))) {
$commandName = $name;
$pluginDot = '';
}
Expand Down
4 changes: 2 additions & 2 deletions lib/Cake/Console/Command/Task/ExtractTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ public function execute() {
$this->_extractCore = !(strtolower($this->params['extract-core']) === 'no');
} else {
$response = $this->in(__d('cake_console', 'Would you like to extract the messages from the CakePHP core?'), array('y', 'n'), 'n');
$this->_extractCore = strtolower($response) === 'y';
$this->_extractCore = strtolower((string)$response) === 'y';
}

if (!empty($this->params['exclude-plugins']) && $this->_isExtractingApp()) {
Expand Down Expand Up @@ -221,7 +221,7 @@ public function execute() {
} else {
$this->out();
$response = $this->in(__d('cake_console', 'Would you like to merge all domain and category strings into the default.pot file?'), array('y', 'n'), 'n');
$this->_merge = strtolower($response) === 'y';
$this->_merge = strtolower((string)$response) === 'y';
}

if (empty($this->_files)) {
Expand Down
4 changes: 2 additions & 2 deletions lib/Cake/Console/Command/Task/FixtureTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ public function importOptions($modelName) {
if (!isset($options['records']) && $this->interactive) {
$prompt = __d('cake_console', "Would you like to build this fixture with data from %s's table?", $modelName);
$fromTable = $this->in($prompt, array('y', 'n'), 'n');
if (strtolower($fromTable) === 'y') {
if (strtolower((string)$fromTable) === 'y') {
$options['fromTable'] = true;
}
}
Expand Down Expand Up @@ -248,7 +248,7 @@ public function bake($model, $useTable = false, $importOptions = array()) {
$this->_Schema = new CakeSchema();
$data = $this->_Schema->read(array('models' => false, 'connection' => $this->connection));
if (!isset($data['tables'][$useTable])) {
$this->err("<warning>Warning:</warning> Could not find the '${useTable}' table for ${model}.");
$this->err("<warning>Warning:</warning> Could not find the '{$useTable}' table for {$model}.");
return null;
}

Expand Down
Loading
Loading