Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ jobs:
db-type: sqlite
- php-version: '8.4'
db-type: mysql
- php-version: '8.5'
db-type: mysql

services:
mysql:
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Here are steps I took to migrate my project through all versions to PHP 8.1, may

## Before using this fork ⚠️

- ~~Tests of CakePHP framework aren't refactored yet to support PHP 8. Main issue is old version of PHPUnit that is tightly coupled to framework's tests. Issue for fixing this situation is here: https://github.com/kamilwylegala/cakephp2-php8/issues/7~~ Framework tests are migrated to PHPUnit 9.*. Github actions are running tests on PHP 8.0, 8.4.
- ~~Tests of CakePHP framework aren't refactored yet to support PHP 8. Main issue is old version of PHPUnit that is tightly coupled to framework's tests. Issue for fixing this situation is here: https://github.com/kamilwylegala/cakephp2-php8/issues/7~~ Framework tests are migrated to PHPUnit 9.*. Github actions are running tests on PHP 8.0, 8.4, 8.5.
- ~~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.

Expand Down Expand Up @@ -59,6 +59,13 @@ It means that composer will look at `master` branch of repository configured und

## Changelog

### 2026-10-03

- Fixes for PHP 8.5: avoided out-of-range float to int casts, which warn on PHP 8.5.
- `Security::cipher()` reduces `Security.cipherSeed` with `fmod()` before seeding. The effective seed and the ciphertext are unchanged, so existing data (e.g. `CookieComponent` cookies) can still be decrypted.
- `DboSource::limit()` and the `Postgres` / `Sqlite` / `Sqlserver` overrides clamp float limit / offset values that do not fit in an int to `PHP_INT_MAX`. BC note: such floats (≥ 2^63) used to wrap around in the generated SQL (e.g. 2^64 became `0`); they now become `PHP_INT_MAX`.
- Added PHP 8.5 to CI.

### 2026-09-30

- Fix the PHP 8.5 `PDO::MYSQL_ATTR_*` deprecations in the MySQL datasource: on PHP >= 8.4, `Mysql::connect()` uses the `Pdo\Mysql::ATTR_*` constants, including the SSL ones (based on kamilwylegala/cakephp2-php8#86). Apps passing `PDO::MYSQL_ATTR_*` in the datasource `flags` option should switch to `Pdo\Mysql::ATTR_*` on PHP >= 8.4 as well.
Expand Down
4 changes: 2 additions & 2 deletions lib/Cake/Model/Datasource/Database/Postgres.php
Original file line number Diff line number Diff line change
Expand Up @@ -681,9 +681,9 @@ protected function _alterIndexes($table, $indexes) {
*/
public function limit($limit, $offset = null) {
if ($limit) {
$rt = sprintf(' LIMIT %u', $limit);
$rt = sprintf(' LIMIT %u', $this->_clampLimitValue($limit));
if ($offset) {
$rt .= sprintf(' OFFSET %u', $offset);
$rt .= sprintf(' OFFSET %u', $this->_clampLimitValue($offset));
}
return $rt;
}
Expand Down
4 changes: 2 additions & 2 deletions lib/Cake/Model/Datasource/Database/Sqlite.php
Original file line number Diff line number Diff line change
Expand Up @@ -393,9 +393,9 @@ public function fetchResult() {
*/
public function limit($limit, $offset = null) {
if ($limit) {
$rt = sprintf(' LIMIT %u', $limit);
$rt = sprintf(' LIMIT %u', $this->_clampLimitValue($limit));
if ($offset) {
$rt .= sprintf(' OFFSET %u', $offset);
$rt .= sprintf(' OFFSET %u', $this->_clampLimitValue($offset));
}
return $rt;
}
Expand Down
4 changes: 2 additions & 2 deletions lib/Cake/Model/Datasource/Database/Sqlserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -414,9 +414,9 @@ public function limit($limit, $offset = null) {
if (!strpos(strtolower($limit), 'top') || strpos(strtolower($limit), 'top') === 0) {
$rt = ' TOP';
}
$rt .= sprintf(' %u', $limit);
$rt .= sprintf(' %u', $this->_clampLimitValue($limit));
if ((is_int($offset) || ctype_digit($offset)) && $offset > 0) {
$rt = sprintf(' OFFSET %u ROWS FETCH FIRST %u ROWS ONLY', $offset, $limit);
$rt = sprintf(' OFFSET %u ROWS FETCH FIRST %u ROWS ONLY', $this->_clampLimitValue($offset), $this->_clampLimitValue($limit));
}
return $rt;
}
Expand Down
20 changes: 18 additions & 2 deletions lib/Cake/Model/Datasource/DboSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -3068,15 +3068,31 @@ public function limit($limit, $offset = null) {
$rt = ' LIMIT';

if ($offset) {
$rt .= sprintf(' %u,', $offset);
$rt .= sprintf(' %u,', $this->_clampLimitValue($offset));
}

$rt .= sprintf(' %u', $limit);
$rt .= sprintf(' %u', $this->_clampLimitValue($limit));
return $rt;
}
return null;
}

/**
* Clamps a float LIMIT/OFFSET value that does not fit in an int.
*
* sprintf('%u') wraps such floats around (2^64 becomes 0), and PHP 8.5+ warns about
* that cast. Numeric strings are already saturated by the same cast.
*
* @param mixed $value Limit or offset value.
* @return mixed PHP_INT_MAX for floats that do not fit in an int, $value otherwise.
*/
protected function _clampLimitValue($value) {
if (is_float($value) && $value >= PHP_INT_MAX) {
return PHP_INT_MAX;
}
return $value;
}

/**
* Returns an ORDER BY clause as a string.
*
Expand Down
10 changes: 8 additions & 2 deletions lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1150,8 +1150,14 @@ public function testLimit() {
$this->assertEquals(' LIMIT 20 OFFSET 10', $result);

$result = $db->limit(10, 300000000000000000000000000000);
$scientificNotation = sprintf('%.1E', 300000000000000000000000000000);
$this->assertStringNotContainsString($scientificNotation, $result);
$this->assertSame(' LIMIT 10 OFFSET ' . PHP_INT_MAX, $result);

// Offset of a page clamped to PHP_INT_MAX by PaginatorComponent. It must not wrap around to 0.
$result = $db->limit(20, (PHP_INT_MAX - 1) * 20);
$this->assertSame(' LIMIT 20 OFFSET ' . PHP_INT_MAX, $result);

$result = $db->limit(18446744073709551615);
$this->assertSame(' LIMIT ' . PHP_INT_MAX, $result);
}

/**
Expand Down
10 changes: 8 additions & 2 deletions lib/Cake/Test/Case/Model/Datasource/Database/SqliteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -595,8 +595,14 @@ public function testLimit() {
$this->assertEquals(' LIMIT 20 OFFSET 10', $result);

$result = $db->limit(10, 300000000000000000000000000000);
$scientificNotation = sprintf('%.1E', 300000000000000000000000000000);
$this->assertStringNotContainsString($scientificNotation, $result);
$this->assertSame(' LIMIT 10 OFFSET ' . PHP_INT_MAX, $result);

// Offset of a page clamped to PHP_INT_MAX by PaginatorComponent. It must not wrap around to 0.
$result = $db->limit(20, (PHP_INT_MAX - 1) * 20);
$this->assertSame(' LIMIT 20 OFFSET ' . PHP_INT_MAX, $result);

$result = $db->limit(18446744073709551615);
$this->assertSame(' LIMIT ' . PHP_INT_MAX, $result);
}

/**
Expand Down
10 changes: 8 additions & 2 deletions lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1783,8 +1783,14 @@ public function testLimit() {
$this->assertEquals(' LIMIT 10, 20', $result);

$result = $db->limit(10, 300000000000000000000000000000);
$scientificNotation = sprintf('%.1E', 300000000000000000000000000000);
$this->assertStringNotContainsString($scientificNotation, $result);
$this->assertSame(' LIMIT ' . PHP_INT_MAX . ', 10', $result);

// Offset of a page clamped to PHP_INT_MAX by PaginatorComponent. It must not wrap around to 0.
$result = $db->limit(20, (PHP_INT_MAX - 1) * 20);
$this->assertSame(' LIMIT ' . PHP_INT_MAX . ', 20', $result);

$result = $db->limit(18446744073709551615);
$this->assertSame(' LIMIT ' . PHP_INT_MAX, $result);
}

/**
Expand Down
28 changes: 28 additions & 0 deletions lib/Cake/Test/Case/Utility/SecurityTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,34 @@ public function testCipher() {
$this->assertEquals($txt, Security::cipher($result, $key));
}

/**
* Known-answer vectors for Security::cipher(), generated with the former (int)(float)
* seed cast (identical on PHP 8.0, 8.4 and 8.5): a seed within the int range, one
* beyond PHP_INT_MAX and the default seed.
*
* @return array
*/
public static function cipherKnownAnswerProvider() {
return array(
array('1234567890', 'fff77dcb883732592dd235ad622645e18405cb'),
array('12345678901234567890', 'b209c722829c974771a534cebda99e52d70f26'),
array('76859309657453542496749683645', 'a0236698a8b6c059f17f023f2666bfbe328fee'),
);
}

/**
* Test that Security::cipher() output does not change, so existing data can still be decrypted.
*
* @dataProvider cipherKnownAnswerProvider
* @param string $seed Security.cipherSeed value.
* @param string $expected Expected ciphertext as hex.
* @return void
*/
public function testCipherKnownAnswer($seed, $expected) {
Configure::write('Security.cipherSeed', $seed);
$this->assertSame($expected, bin2hex(Security::cipher('The quick brown fox', 'my_key')));
}

/**
* testCipherEmptyKey method
*
Expand Down
7 changes: 6 additions & 1 deletion lib/Cake/Utility/Security.php
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,12 @@ public static function cipher($text, $key) {
return '';
}

srand((int)(float)Configure::read('Security.cipherSeed'));
// mt_srand() only uses the low 32 bits of the seed, and fmod() is exact. This keeps
// the seed of the former (int)(float) cast without casting an out-of-range float
// to int (the seed usually exceeds PHP_INT_MAX, and PHP 8.5+ warns on such casts).
// A non-finite seed maps to 0, as that cast did.
$seed = (float)Configure::read('Security.cipherSeed');
srand(is_finite($seed) ? (int)fmod($seed, 4294967296) : 0);
$out = '';
$keyLength = strlen($key);
for ($i = 0, $textLength = strlen($text); $i < $textLength; $i++) {
Expand Down
Loading