Version 4.7.0¶
Release Date: February 1, 2026
4.7.0 release of CodeIgniter4
Highlights¶
- Update minimum PHP requirement to
8.2. - Added experimental FrankenPHP Worker Mode support for improved performance.
BREAKING¶
Behavior Changes¶
Validation Rules¶
Placeholders in the regex_match validation rule must now use double curly braces. If you previously used single braces like regex_match[/^{placeholder}$/], you must update it to use double braces: regex_match[/^{{placeholder}}$/].
This change was introduced to avoid ambiguity with regular expression syntax, where single curly braces (e.g., {1,3}) are used for quantifiers.
BaseModel¶
The insertBatch() and updateBatch() methods now honor model settings like updateOnlyChanged and allowEmptyInserts. This change ensures consistent handling across all insert/update operations.
Primary Key Validation¶
The insert() and insertBatch() (when useAutoIncrement is disabled), update(), and delete() methods now validate primary key values before executing database queries. Invalid primary key values will now throw InvalidArgumentException instead of DatabaseException.
What Changed:
-
Exception type: Invalid primary keys now throw
InvalidArgumentExceptionwith specific error messages instead of genericDatabaseExceptionfrom the database layer. -
Validation timing:
- For
update()anddelete(): Validation happens before thebeforeUpdate/beforeDeleteevents and before any database queries are executed. - For
insert()andinsertBatch()(when auto-increment is disabled): Validation happens after thebeforeInsertevent but before database queries are executed.
- For
-
Invalid values: The following values are now explicitly rejected as primary keys:
null(for insert when auto-increment is disabled)0(integer zero)'0'(string zero)''(empty string)trueandfalse(booleans)[](empty array)- Nested arrays (e.g.,
[[1, 2]]) - Arrays containing any of the above invalid values
Note:
RawSqlobjects are allowed as primary key values for complex scenarios where you need to use raw SQL expressions.
This change improves error reporting by providing specific validation messages (e.g., "Invalid primary key: 0 is not allowed") instead of generic database errors, and prevents invalid queries from reaching the database.
If you need to allow some of these values for the primary key, you can override the validateID() method in your model.
Entity¶
The Entity::hasChanged() and Entity::syncOriginal() methods now perform deep comparison for objects and arrays instead of shallow comparison. This means:
- Objects and arrays are now JSON-encoded and normalized for comparison, detecting changes in nested structures, object properties, and array elements.
- Enums (both
BackedEnumandUnitEnum) are properly tracked by their backing value or case name. - DateTime objects (
DateTimeInterface) are compared using their ISO 8601 representation including timezone information. - Collections (
Traversable) such asArrayObjectandArrayIteratorare converted to arrays for comparison. - Value objects with a
__toString()method are compared by their string representation when properties are not accessible (fallback for objects with private properties). - Nested entities (using
toRawArray()),JsonSerializableobjects, and objects withtoArray()methods are recursively normalized for accurate change detection. - Scalar values (strings, integers, floats, booleans, null) continue to use direct comparison with an optimization when all entity attributes are scalars.
Previously, changing an object property or an array containing objects would not be detected as a change because only reference comparison was performed. Now, any modification to the internal state of objects or arrays will be properly detected. If you relied on the old shallow comparison behavior, you will need to update your code accordingly.
The Entity::toRawArray() method now properly converts arrays of entities when the $recursive parameter is true. Previously, properties containing arrays were not recursively processed. If you were relying on the old behavior where arrays remained unconverted, you will need to update your code.
Entity and DataCaster¶
Previously, the DataCaster object was always initialized, even when type casting was not configured (an empty $casts = [] array).
DataCaster is now created on demand and will be null when type casting is not configured. While this change should not affect typical usage, developers should be aware that $dataCaster may now be nullable in some cases.
Encryption Handlers¶
The OpenSSLHandler and SodiumHandler no longer modify the handler's $key property when encryption/decryption parameters are passed via the $params argument. Keys passed through $params are now used as local variables, ensuring the handler's state remains unchanged.
What Changed:
- Previously, passing a key via
$paramstoencrypt()ordecrypt()would permanently modify the handler's internal$keyproperty. - Now, the handler's
$keyproperty is only set during handler creation viaConfig\Encryption. Passing keys through$paramsuses them as temporary local variables without modifying the handler's state. SodiumHandler::encrypt()no longer callssodium_memzero($this->key), which previously destroyed the encryption key after the first use, preventing handler reuse.
Impact:
You are only affected if you passed a key via $params to encrypt() or decrypt() and expected the key to persist for subsequent operations. Most users are not affected:
- Not affected: You always pass the key via
$paramsfor each operation - Not affected: You never use
$paramsand always configure keys viaConfig\Encryption - Affected: You passed a key via
$paramsonce and expected it to be remembered
If affected, configure the key properly via Config\Encryption or pass a custom config to the service instead of relying on $params side effects.
Example of affected code:
$config = config('Encryption');
$config->key = 'your-encryption-key';
$handler = service('encrypter', $config);
$handler->encrypt($data, 'temporary-key');
// Old: $handler->key is now 'temporary-key'
// New: $handler->key remains unchanged ('your-encryption-key')
$handler->encrypt($moreData);
// Old: Would use 'temporary-key'
// New: Uses default key ('your-encryption-key')
Migration:
To use a different encryption key permanently, pass a custom config when creating the service:
$config = config('Encryption');
$config->key = 'your-custom-encryption-key';
// Get a new handler instance with the custom config (not shared)
$handler = service('encrypter', $config, false);
Interface Changes¶
NOTE: If you've implemented your own classes that implement these interfaces from scratch, you will need to update your implementations to include the new methods to ensure compatibility.
- Cache: The
CacheInterfacenow includes thedeleteMatching()method. - Cache: The
CacheInterfacenow includes theremember()method. All built-in cache handlers inherit this method viaBaseHandler, so no changes are required for them. - Database: The
QueryInterfacenow includes thegetOriginalQuery()method. - Images: The
ImageHandlerInterfacenow includes a new method:clearMetadata().
Method Signature Changes¶
- BaseModel: The type of the
$rowparameter for thecleanValidationRules()method has been changed from?array $row = nulltoarray $row. - PageCache: The
PageCachefilter constructor now accepts an optionalCacheconfiguration parameter:__construct(?Cache $config = null). This allows dependency injection for testing purposes. While this is technically a breaking change if you extend thePageCacheclass with your own constructor, it should not affect most users as the parameter has a default value. - Added the
SensitiveParameterattribute to various methods to conceal sensitive information from stack traces. Affected methods are:CodeIgniter\Encryption\EncrypterInterface::encrypt()CodeIgniter\Encryption\EncrypterInterface::decrypt()CodeIgniter\Encryption\Handlers\OpenSSLHandler::encrypt()CodeIgniter\Encryption\Handlers\OpenSSLHandler::decrypt()CodeIgniter\Encryption\Handlers\SodiumHandler::encrypt()CodeIgniter\Encryption\Handlers\SodiumHandler::decrypt()CodeIgniter\HTTP\CURLRequest::setAuth()CodeIgniter\HTTP\URI::setUserInfo()CodeIgniter\Security\Security::derandomize()
- Added native types to
CacheInterfacemethods that were missing them. The following methods were updated:initialize()save()delete()increment()decrement()clean()getCacheInfo()getMetaData()
- Added native parameter and return types to
CodeIgniter\Database\QueryInterfacemethods:setQuery(string $sql, mixed $binds = null, bool $setEscape = true): selfgetQuery(): stringsetDuration(float $start, ?float $end = null): selfsetError(int $code, string $error): selfswapPrefix(string $orig, string $swap): self
- Added native return types to
CodeIgniter\Debug\Toolbarmethods:prepare(): voidrespond(): void
- Methods previously having the
#[ReturnTypeWillChange]attribute have been updated to include the native return types:CodeIgniter\Cookie\Cookie::offsetGet($offset): bool|int|stringCodeIgniter\Entity\Entity::jsonSerialize(): arrayCodeIgniter\Files\File::getSize(): false|intCodeIgniter\I18n\TimeLegacy::setTimestamp($timestamp): staticCodeIgniter\I18n\TimeTrait::createFromFormat($format, $time, $timezone = null): staticCodeIgniter\I18n\TimeTrait::setTimezone($timezone): staticCodeIgniter\Session\Handlers\Database\PostgreHandler::gc($max_lifetime): false|intCodeIgniter\Session\Handlers\ArrayHandler::read($id): stringCodeIgniter\Session\Handlers\ArrayHandler::gc($max_lifetime): intCodeIgniter\Session\Handlers\DatabaseHandler::read($id): false|stringCodeIgniter\Session\Handlers\DatabaseHandler::gc($max_lifetime): false|intCodeIgniter\Session\Handlers\FileHandler::read($id): false|stringCodeIgniter\Session\Handlers\FileHandler::gc($max_lifetime): false|intCodeIgniter\Session\Handlers\MemcachedHandler::read($id): false|stringCodeIgniter\Session\Handlers\MemcachedHandler::gc($max_lifetime): intCodeIgniter\Session\Handlers\RedisHandler::read($id): false|stringCodeIgniter\Session\Handlers\RedisHandler::gc($max_lifetime): int
Property Signature Changes¶
- Entity: The protected property
CodeIgniter\Entity\Entity::$dataCastertype has been changed fromDataCasterto?DataCaster(nullable).
Removed Deprecated Items¶
- BaseModel: The deprecated method
transformDataRowToArray()has been removed. - Cache: The deprecated return type
falseforCodeIgniter\Cache\CacheInterface::getMetaData()has been replaced withnulltype. - CodeIgniter: The deprecated
CodeIgniter\CodeIgniter::resolvePlatformExtensions()has been removed. - Entity: The deprecated
CodeIgniter\Entity\Entity::setAttributes()has been removed. UseCodeIgniter\Entity\Entity::injectRawData()instead. - IncomingRequest: The following deprecated methods have been removed:
CodeIgniter\HTTP\IncomingRequest\detectURI()CodeIgniter\HTTP\IncomingRequest\detectPath()CodeIgniter\HTTP\IncomingRequest\parseRequestURI()CodeIgniter\HTTP\IncomingRequest\parseQueryString()
- IncomingRequest: The deprecated
$configparameter has been removed fromCodeIgniter\HTTP\IncomingRequest::setPath(), and the method visibility has been changed frompublictoprivate. - Session: The following deprecated properties in
CodeIgniter\Session\Sessionhave been removed:CodeIgniter\Session\Session::$sessionDriverNameCodeIgniter\Session\Session::$sessionCookieNameCodeIgniter\Session\Session::$sessionExpirationCodeIgniter\Session\Session::$sessionSavePathCodeIgniter\Session\Session::$sessionMatchIPCodeIgniter\Session\Session::$sessionTimeToUpdateCodeIgniter\Session\Session::$sessionRegenerateDestroy
- Session: The deprecated method
CodeIgniter\Session\Session::stop()has been removed. - Text Helper: The deprecated types
basic,md5, andsha1in therandom_string()function have been removed.
Enhancements¶
Libraries¶
- API Transformers: This new feature provides a structured way to transform data for API responses. See API Transformers for details.
- CLI: Added
SignalTraitto provide unified handling of operating system signals in CLI commands. - Cache: Added
asyncandpersistentconfig items to Predis handler. - Cache: Added
persistentconfig item to Redis handler. - Cache: Added support for HTTP status in
ResponseCache. - Cache: Added
Config\Cache::$cacheStatusCodesto control which HTTP status codes are allowed to be cached by thePageCachefilter. Defaults to[](all status codes for backward compatibility). Recommended value:[200]to only cache successful responses. See Setting $cacheStatusCodes for details. - Cache: Added APCu caching driver.
- CURLRequest: Added
shareConnectionconfig item to change the default shared connection. - CURLRequest: Added
dns_cache_timeoutoption to change the default DNS cache timeout. - CURLRequest: Added
fresh_connectoption to enable/disable a fresh connection for the request. - DataConverter: Added
EnumCastcaster for database and entity. - Email: Added support for choosing the SMTP authorization method. You can change it via the
Config\Email::$SMTPAuthMethodoption. - Encryption: Added
Config\Encryption::$previousKeysconfiguration option to support encryption key rotation. When decryption with the current key fails, the system automatically falls back to previous keys, allowing you to rotate encryption keys without losing access to old encrypted data. - Image: The
ImageMagickHandlerhas been rewritten to rely solely on the PHPimagickextension. - Image: Added
ImageMagickHandler::clearMetadata()method to remove image metadata for privacy protection. - ResponseTrait: Added
paginate()method to simplify paginated API responses. See ResponseTrait::paginate() for details. - Session: Added
persistentconfig item toRedisHandler. - Session: Added connection persistence for Redis and Memcached session handlers via
PersistsConnectiontrait, improving performance in worker mode by reusing connections across requests. - Time: Added methods
Time::addCalendarMonths()andTime::subCalendarMonths() - Time: Added
Time::isPast()andTime::isFuture()convenience methods. See isPast and isFuture for details. - Toolbar: Fixed an issue where the Debug Toolbar was incorrectly injected into responses generated by third-party libraries (e.g., Dompdf) that use native PHP headers instead of the framework's Response object.
- UserAgents: Expanded the list of recognized
robotswith new search engine and service crawlers. - View: Added the ability to override namespaced views (e.g., from modules/packages) by placing a matching file structure within the app/Views/overrides directory. See Overriding Namespaced Views for details.
Commands¶
- Added
php spark worker:installcommand to generate FrankenPHP worker mode files (Caddyfile and worker entry point). - Added
php spark worker:uninstallcommand to remove worker mode files.
Database¶
- BaseConnection: Added
ping()method to check if the database connection is still alive. OCI8 usesSELECT 1 FROM DUAL, other drivers useSELECT 1. - BaseConnection: The
reconnect()method now usesping()to check if the connection is alive before reconnecting. Previously, some drivers would always close and reconnect unconditionally, and OCI8'sreconnect()did nothing. - Exception Logging: All DB drivers now log database exceptions uniformly. Previously, each driver had its own log format.
Migrations¶
- MigrationRunner: Added distributed locking support to prevent concurrent migrations in multi-process environments. Enable with
Config\Migrations::$lock = true.
HTTP¶
Content Security Policy¶
-
The
script-srcandstyle-srcdirectives can now use SHA-256, SHA-384, and SHA-512 digests as their source expressions. -
Added support for the new CSP Level 3 keywords:
'strict-dynamic''unsafe-hashes''report-sample''unsafe-allow-redirects''wasm-unsafe-eval''trusted-types-eval''report-sha256''report-sha384''report-sha512'
-
Added support for the following CSP Level 3 directives:
report-toscript-src-elemscript-src-attrstyle-src-elemstyle-src-attrworker-src
Update your CSP configuration in app/Config/ContentSecurityPolicy.php to include these new directives.
Others¶
- Controller Attributes: Added support for PHP Attributes to define filters and other metadata on controller classes and methods. See Controller Attributes for details.
Message Changes¶
- Added
Email.invalidSMTPAuthMethod,Email.failureSMTPAuthMethod,CLI.signals.noPcntlExtension,CLI.signals.noPosixExtensionandCLI.signals.failedSignal. - Deprecated
Email.failedSMTPLoginandImage.libPathInvalid. - Changed
Session.missingDatabaseTable.
Changes¶
- Boot: Added
Boot::bootWorker()method for one-time worker initialization. - CodeIgniter: Added
CodeIgniter::resetForWorkerMode()method to reset request-specific state between worker requests. - Config: Added
app/Config/WorkerMode.phpfor worker mode configuration (persistent services, garbage collection). - Cookie: The
CookieInterface::EXPIRES_FORMAThas been changed toD, d M Y H:i:s Tto follow the recommended format in RFC 7231. - DatabaseConfig: Added
Config::reconnectForWorkerMode()andConfig::cleanupForWorkerMode()methods for database connection management in worker mode. - Events: Added
Events::cleanupForWorkerMode()method. - Format: Added support for configuring
json_encode()maximum depth viaConfig\Format::$jsonEncodeDepth. - Paths: Added support for changing the location of the
.envfile via thePaths::$envDirectoryproperty. - Services: Added
Services::resetForWorkerMode()andServices::reconnectCacheForWorkerMode()methods. - Toolbar: Added
$disableOnHeadersproperty to app/Config/Toolbar.php. - Toolbar: Added
Toolbar::reset()method to reset debug toolbar collectors.
Deprecations¶
- ContentSecurityPolicy:
- The
CodeIgniter\HTTP\ContentSecurityPolicy::$noncesproperty has been deprecated. It was never used.
- The
- Encryption:
- The method
CodeIgniter\Encryption\Handlers\SodiumHandler::parseParams()has been deprecated. Parameters are now handled directly in theencrypt()anddecrypt()methods.
- The method
- Image:
- The config property
Config\Image::libraryPathhas been deprecated. No longer used. - The exception method
CodeIgniter\Images\Exceptions\ImageException::forInvalidImageLibraryPathhas been deprecated. No longer used.
- The config property
Bugs Fixed¶
- Cookie: The
CookieInterface::SAMESITE_STRICT,CookieInterface::SAMESITE_LAX, andCookieInterface::SAMESITE_NONEconstants are now written in ucfirst style to be consistent with usage in the rest of the framework. - Cache: Changed
WincacheHandler::increment()andWincacheHandler::decrement()to returnboolinstead ofmixed. - Entity: Calling
CodeIgniter\Entity\Entity::toArray()always changed the value of$_casttotrue, instead of restoring the initial value. - Toolbar: Fixed Maximum call stack size exceeded crash when AJAX-like requests (HTMX, Turbo, Unpoly, etc.) were made on pages with Debug Toolbar enabled.
See the repo's CHANGELOG.md for a complete list of bugs fixed.