user_guide_src/source/changelogs/v4.7.0.rst
############# Version 4.7.0 #############
Release Date: February 1, 2026
4.7.0 release of CodeIgniter4
.. contents:: :local: :depth: 3
Highlights
8.2.BREAKING
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.
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 InvalidArgumentException with
specific error messages instead of generic DatabaseException from the database layer.
Validation timing:
update() and delete(): Validation happens before the beforeUpdate/beforeDelete
events and before any database queries are executed.insert() and insertBatch() (when auto-increment is disabled): Validation happens
after the beforeInsert event 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)true and false (booleans)[] (empty array)[[1, 2]])Note: RawSql objects 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.
The Entity::hasChanged() and Entity::syncOriginal() methods now perform deep comparison
for objects and arrays instead of shallow comparison. This means:
BackedEnum and UnitEnum) are properly tracked by their backing value
or case name.DateTimeInterface) are compared using their ISO 8601 representation
including timezone information.Traversable) such as ArrayObject and ArrayIterator are converted
to arrays for comparison.__toString() method are compared by their string representation when
properties are not accessible (fallback for objects with private properties).toRawArray()), JsonSerializable objects, and objects with
toArray() methods are recursively normalized for accurate change detection.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.
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.
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:
$params to encrypt() or decrypt() would permanently
modify the handler's internal $key property.$key property is only set during handler creation via Config\Encryption.
Passing keys through $params uses them as temporary local variables without modifying the handler's state.SodiumHandler::encrypt() no longer calls sodium_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:
$params for each operation$params and always configure keys via Config\Encryption$params once and expected it to be rememberedIf 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:
.. code-block:: php
$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:
.. code-block:: php
$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);
.. _v470-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.
CacheInterface now includes the deleteMatching() method.CacheInterface now includes the remember() method. All built-in cache handlers inherit this method via BaseHandler, so no changes are required for them.QueryInterface now includes the getOriginalQuery() method.ImageHandlerInterface now includes a new method: clearMetadata()... _v470-method-signature-changes:
$row parameter for the cleanValidationRules() method has been changed from ?array $row = null to array $row.PageCache filter constructor now accepts an optional Cache configuration parameter: __construct(?Cache $config = null). This allows dependency injection for testing purposes. While this is technically a breaking change if you extend the PageCache class with your own constructor, it should not affect most users as the parameter has a default value.SensitiveParameter attribute 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()CacheInterface methods that were missing them. The following methods were updated:
initialize()save()delete()increment()decrement()clean()getCacheInfo()getMetaData()CodeIgniter\Database\QueryInterface methods:
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): selfCodeIgniter\Debug\Toolbar methods:
prepare(): voidrespond(): void#[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.. _v470-property-signature-changes:
CodeIgniter\Entity\Entity::$dataCaster type has been changed from DataCaster to ?DataCaster (nullable)... _v470-removed-deprecated-items:
transformDataRowToArray() has been removed.false for CodeIgniter\Cache\CacheInterface::getMetaData() has been replaced with null type.CodeIgniter\CodeIgniter::resolvePlatformExtensions() has been removed.CodeIgniter\Entity\Entity::setAttributes() has been removed. Use CodeIgniter\Entity\Entity::injectRawData() instead.CodeIgniter\HTTP\IncomingRequest\detectURI()CodeIgniter\HTTP\IncomingRequest\detectPath()CodeIgniter\HTTP\IncomingRequest\parseRequestURI()CodeIgniter\HTTP\IncomingRequest\parseQueryString()$config parameter has been removed from CodeIgniter\HTTP\IncomingRequest::setPath(), and the method visibility has been changed from public to private.CodeIgniter\Session\Session has been removed:
CodeIgniter\Session\Session::$sessionDriverNameCodeIgniter\Session\Session::$sessionCookieNameCodeIgniter\Session\Session::$sessionExpirationCodeIgniter\Session\Session::$sessionSavePathCodeIgniter\Session\Session::$sessionMatchIPCodeIgniter\Session\Session::$sessionTimeToUpdateCodeIgniter\Session\Session::$sessionRegenerateDestroyCodeIgniter\Session\Session::stop() has been removed.random_string() function: basic, md5, and sha1 has been removed.Enhancements
API Transformers <api_transformers> for details.SignalTrait to provide unified handling of operating system signals in CLI commands.async and persistent config item to Predis handler.persistent config item to Redis handler.ResponseCache.Config\Cache::$cacheStatusCodes to control which HTTP status codes are allowed to be cached by the PageCache filter. Defaults to [] (all status codes for backward compatibility). Recommended value: [200] to only cache successful responses. See :ref:Setting $cacheStatusCodes <web_page_caching_cache_status_codes> for details.APCu <https://www.php.net/apcu>_ caching driver.shareConnection config item to change default share connection.dns_cache_timeout option to change default DNS cache timeout.fresh_connect options to enable/disable request fresh connection.EnumCast caster for database and entity.Config\Email::$SMTPAuthMethod option.Config\Encryption::$previousKeys configuration 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.ImageMagickHandler has been rewritten to rely solely on the PHP imagick extension.ImageMagickHandler::clearMetadata() method to remove image metadata for privacy protection.ResponseTrait::paginate() <api_response_trait_paginate> for details.persistent config item to RedisHandler.PersistsConnection trait, improving performance in worker mode by reusing connections across requests.Time::addCalendarMonths() and Time::subCalendarMonths()Time::isPast() and Time::isFuture() convenience methods. See :ref:isPast <time-comparing-two-times-isPast> and :ref:isFuture <time-comparing-two-times-isFuture> for details.robots with new search engine and service crawlers.Overriding Namespaced Views <views-overriding-namespaced-views> for details.php spark worker:install command to generate FrankenPHP worker mode files (Caddyfile and worker entry point).php spark worker:uninstall command to remove worker mode files.ping() method to check if the database connection is still alive. OCI8 uses SELECT 1 FROM DUAL, other drivers use SELECT 1.reconnect() method now uses ping() to check if the connection is alive before reconnecting. Previously, some drivers would always close and reconnect unconditionally, and OCI8's reconnect() did nothing.Config\Migrations::$lock = true.The script-src and style-src directives 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-srcUpdate your CSP configuration in app/Config/ContentSecurityPolicy.php to include these new directives.
Controller Attributes <incoming/controller_attributes> for details.Message Changes
Email.invalidSMTPAuthMethod, Email.failureSMTPAuthMethod, CLI.signals.noPcntlExtension, CLI.signals.noPosixExtension and CLI.signals.failedSignal.Email.failedSMTPLogin and Image.libPathInvalid.Session.missingDatabaseTable.Changes
Boot::bootWorker() method for one-time worker initialization.CodeIgniter::resetForWorkerMode() method to reset request-specific state between worker requests.app/Config/WorkerMode.php for worker mode configuration (persistent services, garbage collection).CookieInterface::EXPIRES_FORMAT has been changed to D, d M Y H:i:s T to follow the recommended format in RFC 7231.Config::reconnectForWorkerMode() and Config::cleanupForWorkerMode() methods for database connection management in worker mode.Events::cleanupForWorkerMode() method.json_encode() maximum depth via Config\Format::$jsonEncodeDepth..env file via the Paths::$envDirectory property.Services::resetForWorkerMode() and Services::reconnectCacheForWorkerMode() methods.$disableOnHeaders property to app/Config/Toolbar.php.Toolbar::reset() method to reset debug toolbar collectors.Deprecations
CodeIgniter\HTTP\ContentSecurityPolicy::$nonces property has been deprecated. It was never used.CodeIgniter\Encryption\Handlers\SodiumHandler::parseParams() has been deprecated. Parameters are now handled directly in encrypt() and decrypt() methods.Config\Image::libraryPath has been deprecated. No longer used.CodeIgniter\Images\Exceptions\ImageException::forInvalidImageLibraryPath has been deprecated. No longer used.Bugs Fixed
CookieInterface::SAMESITE_STRICT, CookieInterface::SAMESITE_LAX, and CookieInterface::SAMESITE_NONE constants are now written in ucfirst style to be consistent with usage in the rest of the framework.WincacheHandler::increment() and WincacheHandler::decrement() to return bool instead of mixed.CodeIgniter\Entity\Entity::toArray() always changed the value of $_cast to true, instead of restoring the initial value.See the repo's
CHANGELOG.md <https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.md>_
for a complete list of bugs fixed.