Version 4.7.0
Release Date: February 1, 2026
4.7.0 release of CodeIgniter4
Highlights
Update minimal 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.
- 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
__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 that the key will persist for subsequent operations. Most users are not affected:
Not affected: You always pass the key via
$paramsfor each operationNot affected: You never use
$paramsand always configure keys viaConfig\EncryptionAffected: 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 the
- Added native types to
CacheInterfacemethods that were missing them. The following methods were updated: initialize()save()delete()increment()decrement()clean()getCacheInfo()getMetaData()
- Added native types to
- 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 parameter and return types to
- Added native return types to
CodeIgniter\Debug\Toolbarmethods: prepare(): voidrespond(): void
- Added native return types to
- Methods previously having
#[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
- Methods previously having
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 deprecated methods has 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 deprecated properties in
CodeIgniter\Session\Sessionhas 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 properties in
Session: The deprecated method
CodeIgniter\Session\Session::stop()has been removed.Text Helper: The deprecated types in
random_string()function:basic,md5, andsha1has 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 item 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 default share connection.CURLRequest: Added
dns_cache_timeoutoption to change default DNS cache timeout.CURLRequest: Added
fresh_connectoptions to enable/disable request fresh connection.DataConverter: Added
EnumCastcaster for database and entity.Email: Added support for choosing the SMTP authorization method. You can change it via
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.
- Encryption:
The method
CodeIgniter\Encryption\Handlers\SodiumHandler::parseParams()has been deprecated. Parameters are now handled directly inencrypt()anddecrypt()methods.
- 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.
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.