diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
index 3653cf1349c2..6a86ebe205e5 100644
--- a/.php-cs-fixer.dist.php
+++ b/.php-cs-fixer.dist.php
@@ -43,7 +43,35 @@
__DIR__ . '/spark',
]);
-$overrides = [];
+$overrides = [
+ 'phpdoc_no_alias_tag' => [
+ 'replacements' => [
+ 'type' => 'var',
+ 'link' => 'see',
+ ],
+ ],
+ 'phpdoc_align' => [
+ 'align' => 'vertical',
+ 'spacing' => 1,
+ 'tags' => [
+ 'method',
+ 'param',
+ 'phpstan-assert',
+ 'phpstan-assert-if-true',
+ 'phpstan-assert-if-false',
+ 'phpstan-param',
+ 'phpstan-property',
+ 'phpstan-return',
+ 'property',
+ 'property-read',
+ 'property-write',
+ 'return',
+ 'throws',
+ 'type',
+ 'var',
+ ],
+ ],
+];
$options = [
'cacheFile' => 'build/.php-cs-fixer.cache',
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c7c4e4dd3254..6b867cb672ef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,36 @@
# Changelog
+## [v4.4.6](https://github.com/codeigniter4/CodeIgniter4/tree/v4.4.6) (2024-02-24)
+[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.4.5...v4.4.6)
+
+### Breaking Changes
+
+* fix: Time::createFromTimestamp() returns Time with UTC by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/8544
+
+### Fixed Bugs
+
+* fix: [OCI8] getFieldData() returns incorrect `default` value by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/8459
+* fix: [SQLite3] getFieldData() returns incorrect `primary_key` values by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/8460
+* fix: [OCI8][Postgre][SQLSRV][SQLite3] change order of properties returned by getFieldData() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/8481
+* docs: fix supported SQL Server version by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/8489
+* fix: [SQLite3] Forge::modifyColumn() messes up table by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/8457
+* docs: fix incorrect @return type in `ResultInterface-getCustomRowObject()` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/8503
+* fix: [Postgre] updateBatch() breaks `char` type data by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/8524
+* fix: DebugBar block by CSP by @YapsBridging in https://github.com/codeigniter4/CodeIgniter4/pull/8411
+* docs: fix `@phpstan-type` in Model by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/8543
+* fix: [CURLRequest] Multiple HTTP 100 return by API. by @ping-yee in https://github.com/codeigniter4/CodeIgniter4/pull/8466
+* fix: PHPDoc types in controller.tpl.php by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/8561
+* fix: [Session] Redis session race condition by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/8323
+
+### Refactoring
+
+* test: refactor ImageMagickHandlerTest by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/8461
+* test: refactor GetFieldDataTest by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/8480
+* refactor: use ternary operators in Helpers by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/8529
+* refactor: use official site URLs by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/8541
+* refactor: remove redundant URL helper loading by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/8556
+* refactor: small improvement in `loadInNamespace` Autoloader by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/8553
+
## [v4.4.5](https://github.com/codeigniter4/CodeIgniter4/tree/v4.4.5) (2024-01-27)
[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.4.4...v4.4.5)
diff --git a/README.md b/README.md
index e8a48b0e0205..1508d10b28a6 100644
--- a/README.md
+++ b/README.md
@@ -24,16 +24,16 @@ More information about the plans for version 4 can be found in [CodeIgniter 4](h
### Documentation
-The [User Guide](https://codeigniter4.github.io/userguide/) is the primary documentation for CodeIgniter 4.
+The [User Guide](https://codeigniter.com/user_guide/) is the primary documentation for CodeIgniter 4.
-The current **in-progress** User Guide can be found [here](https://codeigniter4.github.io/CodeIgniter4/).
+You will also find the [current **in-progress** User Guide](https://codeigniter4.github.io/CodeIgniter4/).
As with the rest of the framework, it is a work in progress, and will see changes over time to structure, explanations, etc.
You might also be interested in the [API documentation](https://codeigniter4.github.io/api/) for the framework components.
## Important Change with index.php
-index.php is no longer in the root of the project! It has been moved inside the *public* folder,
+`index.php` is no longer in the root of the project! It has been moved inside the *public* folder,
for better security and separation of components.
This means that you should configure your web server to "point" to your project's *public* folder, and
@@ -48,9 +48,10 @@ CodeIgniter is developed completely on a volunteer basis. As such, please give u
for your issues to be reviewed. If you haven't heard from one of the team in that time period,
feel free to leave a comment on the issue so that it gets brought back to our attention.
-We use GitHub issues to track **BUGS** and to track approved **DEVELOPMENT** work packages.
-We use our [forum](http://forum.codeigniter.com) to provide SUPPORT and to discuss
-FEATURE REQUESTS.
+> [!IMPORTANT]
+> We use GitHub issues to track **BUGS** and to track approved **DEVELOPMENT** work packages.
+> We use our [forum](http://forum.codeigniter.com) to provide SUPPORT and to discuss
+> FEATURE REQUESTS.
If you raise an issue here that pertains to support or a feature request, it will
be closed! If you are not sure if you have found a bug, raise a thread on the forum first -
@@ -91,10 +92,11 @@ PHP version 7.4 or higher is required, with the following extensions installed:
- [intl](http://php.net/manual/en/intl.requirements.php)
- [mbstring](http://php.net/manual/en/mbstring.installation.php)
-> **Warning**
-> The end of life date for PHP 7.4 was November 28, 2022. If you are
-> still using PHP 7.4, you should upgrade immediately. The end of life date
-> for PHP 8.0 will be November 26, 2023.
+> [!WARNING]
+> The end of life date for PHP 7.4 was November 28, 2022.
+> The end of life date for PHP 8.0 was November 26, 2023.
+> If you are still using PHP 7.4 or 8.0, you should upgrade immediately.
+> The end of life date for PHP 8.1 will be November 25, 2024.
Additionally, make sure that the following extensions are enabled in your PHP:
diff --git a/admin/css/debug-toolbar/toolbar.scss b/admin/css/debug-toolbar/toolbar.scss
index 5d809874ebee..9c5b149d392c 100644
--- a/admin/css/debug-toolbar/toolbar.scss
+++ b/admin/css/debug-toolbar/toolbar.scss
@@ -398,6 +398,11 @@
text-align: right;
}
}
+
+ // show tab
+ &>.debug-bar-dblock {
+ display: block;
+ }
}
@@ -467,7 +472,6 @@
@import '_theme-light';
}
-
// LAYOUT HELPERS
// ========================================================================== */
@@ -510,3 +514,47 @@
.debug-bar-noverflow {
overflow: hidden;
}
+
+.debug-bar-dtableRow {
+ display: table-row;
+}
+
+.debug-bar-dinlineBlock {
+ display: inline-block;
+}
+
+.debug-bar-pointer {
+ cursor: pointer;
+}
+
+.debug-bar-mleft4 {
+ margin-left: 4px;
+}
+
+.debug-bar-level-0 {
+ --level: 0;
+}
+
+.debug-bar-level-1 {
+ --level: 1;
+}
+
+.debug-bar-level-2 {
+ --level: 2;
+}
+
+.debug-bar-level-3 {
+ --level: 3;
+}
+
+.debug-bar-level-4 {
+ --level: 4;
+}
+
+.debug-bar-level-5 {
+ --level: 5;
+}
+
+.debug-bar-level-6 {
+ --level: 6;
+}
diff --git a/admin/framework/README.md b/admin/framework/README.md
index 00cdf704c2f4..30843d0d9716 100644
--- a/admin/framework/README.md
+++ b/admin/framework/README.md
@@ -11,8 +11,8 @@ It has been built from the
More information about the plans for version 4 can be found in [CodeIgniter 4](https://forum.codeigniter.com/forumdisplay.php?fid=28) on the forums.
-The user guide corresponding to the latest version of the framework can be found
-[here](https://codeigniter4.github.io/userguide/).
+You can read the [user guide](https://codeigniter.com/user_guide/)
+corresponding to the latest version of the framework.
## Important Change with index.php
@@ -47,10 +47,11 @@ PHP version 7.4 or higher is required, with the following extensions installed:
- [intl](http://php.net/manual/en/intl.requirements.php)
- [mbstring](http://php.net/manual/en/mbstring.installation.php)
-> **Warning**
-> The end of life date for PHP 7.4 was November 28, 2022. If you are
-> still using PHP 7.4, you should upgrade immediately. The end of life date
-> for PHP 8.0 will be November 26, 2023.
+> [!WARNING]
+> The end of life date for PHP 7.4 was November 28, 2022.
+> The end of life date for PHP 8.0 was November 26, 2023.
+> If you are still using PHP 7.4 or 8.0, you should upgrade immediately.
+> The end of life date for PHP 8.1 will be November 25, 2024.
Additionally, make sure that the following extensions are enabled in your PHP:
diff --git a/admin/starter/README.md b/admin/starter/README.md
index 11d1cf13eec4..976259fc14c1 100644
--- a/admin/starter/README.md
+++ b/admin/starter/README.md
@@ -11,8 +11,8 @@ It has been built from the
More information about the plans for version 4 can be found in [CodeIgniter 4](https://forum.codeigniter.com/forumdisplay.php?fid=28) on the forums.
-The user guide corresponding to the latest version of the framework can be found
-[here](https://codeigniter4.github.io/userguide/).
+You can read the [user guide](https://codeigniter.com/user_guide/)
+corresponding to the latest version of the framework.
## Installation & updates
@@ -55,10 +55,11 @@ PHP version 7.4 or higher is required, with the following extensions installed:
- [intl](http://php.net/manual/en/intl.requirements.php)
- [mbstring](http://php.net/manual/en/mbstring.installation.php)
-> **Warning**
-> The end of life date for PHP 7.4 was November 28, 2022. If you are
-> still using PHP 7.4, you should upgrade immediately. The end of life date
-> for PHP 8.0 will be November 26, 2023.
+> [!WARNING]
+> The end of life date for PHP 7.4 was November 28, 2022.
+> The end of life date for PHP 8.0 was November 26, 2023.
+> If you are still using PHP 7.4 or 8.0, you should upgrade immediately.
+> The end of life date for PHP 8.1 will be November 25, 2024.
Additionally, make sure that the following extensions are enabled in your PHP:
diff --git a/admin/starter/tests/README.md b/admin/starter/tests/README.md
index 473d0f8c140e..fc40e447301e 100644
--- a/admin/starter/tests/README.md
+++ b/admin/starter/tests/README.md
@@ -7,14 +7,14 @@ use to test your application. Those details can be found in the documentation.
## Resources
-* [CodeIgniter 4 User Guide on Testing](https://codeigniter4.github.io/userguide/testing/index.html)
+* [CodeIgniter 4 User Guide on Testing](https://codeigniter.com/user_guide/testing/index.html)
* [PHPUnit docs](https://phpunit.de/documentation.html)
* [Any tutorials on Unit testing in CI4?](https://forum.codeigniter.com/showthread.php?tid=81830)
## Requirements
It is recommended to use the latest version of PHPUnit. At the time of this
-writing we are running version 9.x. Support for this has been built into the
+writing, we are running version 9.x. Support for this has been built into the
**composer.json** file that ships with CodeIgniter and can easily be installed
via [Composer](https://getcomposer.org/) if you don't already have it installed globally.
@@ -35,10 +35,10 @@ for code coverage to be calculated successfully. After installing `XDebug`, you
A number of the tests use a running database.
In order to set up the database edit the details for the `tests` group in
-**app/Config/Database.php** or **phpunit.xml**.
+**app/Config/Database.php** or **.env**.
Make sure that you provide a database engine that is currently running on your machine.
More details on a test database setup are in the
-[Testing Your Database](https://codeigniter4.github.io/userguide/testing/database.html) section of the documentation.
+[Testing Your Database](https://codeigniter.com/user_guide/testing/database.html) section of the documentation.
## Running the tests
@@ -92,12 +92,11 @@ HTML code coverage reports.
## Test Cases
Every test needs a *test case*, or class that your tests extend. CodeIgniter 4
-provides a few that you may use directly:
-* `CodeIgniter\Test\CIUnitTestCase` - for basic tests with no other service needs
-* `CodeIgniter\Test\DatabaseTestTrait` - for tests that need database access
+provides one class that you may use directly:
+* `CodeIgniter\Test\CIUnitTestCase`
-Most of the time you will want to write your own test cases to hold functions and services
-common to your test suites.
+Most of the time you will want to write your own test cases that extend `CIUnitTestCase`
+to hold functions and services common to your test suites.
## Creating Tests
@@ -112,11 +111,8 @@ Review the links above and always pay attention to your code coverage.
### Database Tests
-Tests can include migrating, seeding, and testing against a mock or live1 database.
+Tests can include migrating, seeding, and testing against a mock or live database.
Be sure to modify the test case (or create your own) to point to your seed and migrations
and include any additional steps to be run before tests in the `setUp()` method.
-
-1 Note: If you are using database tests that require a live database connection
-you will need to rename **phpunit.xml.dist** to **phpunit.xml**, uncomment the database
-configuration lines and add your connection details. Prevent **phpunit.xml** from being
-tracked in your repo by adding it to **.gitignore**.
+See [Testing Your Database](https://codeigniter.com/user_guide/testing/database.html)
+for details.
diff --git a/app/Config/App.php b/app/Config/App.php
index 6ae678625e7b..21b4df205286 100644
--- a/app/Config/App.php
+++ b/app/Config/App.php
@@ -14,7 +14,7 @@ class App extends BaseConfig
* URL to your CodeIgniter root. Typically, this will be your base URL,
* WITH a trailing slash:
*
- * http://example.com/
+ * E.g., http://example.com/
*/
public string $baseURL = 'http://localhost:8080/';
@@ -22,10 +22,10 @@ class App extends BaseConfig
* Allowed Hostnames in the Site URL other than the hostname in the baseURL.
* If you want to accept multiple Hostnames, set this.
*
- * E.g. When your site URL ($baseURL) is 'http://example.com/', and your site
- * also accepts 'http://media.example.com/' and
- * 'http://accounts.example.com/':
- * ['media.example.com', 'accounts.example.com']
+ * E.g.,
+ * When your site URL ($baseURL) is 'http://example.com/', and your site
+ * also accepts 'http://media.example.com/' and 'http://accounts.example.com/':
+ * ['media.example.com', 'accounts.example.com']
*
* @var list
*/
@@ -36,9 +36,9 @@ class App extends BaseConfig
* Index File
* --------------------------------------------------------------------------
*
- * Typically this will be your index.php file, unless you've renamed it to
- * something else. If you are using mod_rewrite to remove the page set this
- * variable so that it is blank.
+ * Typically, this will be your `index.php` file, unless you've renamed it to
+ * something else. If you have configured your web server to remove this file
+ * from your site URIs, set this variable to an empty string.
*/
public string $indexPage = 'index.php';
@@ -48,12 +48,12 @@ class App extends BaseConfig
* --------------------------------------------------------------------------
*
* This item determines which server global should be used to retrieve the
- * URI string. The default setting of 'REQUEST_URI' works for most servers.
+ * URI string. The default setting of 'REQUEST_URI' works for most servers.
* If your links do not seem to work, try one of the other delicious flavors:
*
- * 'REQUEST_URI' Uses $_SERVER['REQUEST_URI']
- * 'QUERY_STRING' Uses $_SERVER['QUERY_STRING']
- * 'PATH_INFO' Uses $_SERVER['PATH_INFO']
+ * 'REQUEST_URI': Uses $_SERVER['REQUEST_URI']
+ * 'QUERY_STRING': Uses $_SERVER['QUERY_STRING']
+ * 'PATH_INFO': Uses $_SERVER['PATH_INFO']
*
* WARNING: If you set this to 'PATH_INFO', URIs will always be URL-decoded!
*/
@@ -94,7 +94,7 @@ class App extends BaseConfig
*
* IncomingRequest::setLocale() also uses this list.
*
- * @var string[]
+ * @var list
*/
public array $supportedLocales = ['en'];
@@ -106,7 +106,8 @@ class App extends BaseConfig
* The default timezone that will be used in your application to display
* dates with the date helper, and can be retrieved through app_timezone()
*
- * @see https://www.php.net/manual/en/timezones.php for list of timezones supported by PHP.
+ * @see https://www.php.net/manual/en/timezones.php for list of timezones
+ * supported by PHP.
*/
public string $appTimezone = 'UTC';
@@ -130,7 +131,7 @@ class App extends BaseConfig
* If true, this will force every request made to this application to be
* made via a secure connection (HTTPS). If the incoming request is not
* secure, the user will be redirected to a secure version of the page
- * and the HTTP Strict Transport Security header will be set.
+ * and the HTTP Strict Transport Security (HSTS) header will be set.
*/
public bool $forceGlobalSecureRequests = false;
diff --git a/app/Config/Routing.php b/app/Config/Routing.php
index 8d3c773157cf..c0234da8df91 100644
--- a/app/Config/Routing.php
+++ b/app/Config/Routing.php
@@ -63,13 +63,12 @@ class Routing extends BaseRouting
/**
* Sets the class/method that should be called if routing doesn't
- * find a match. It can be either a closure or the controller/method
- * name exactly like a route is defined: Users::index
+ * find a match. It can be the controller/method name like: Users::index
*
* This setting is passed to the Router class and handled there.
*
* If you want to use a closure, you will have to set it in the
- * class constructor or the routes file by calling:
+ * routes file by calling:
*
* $routes->set404Override(function() {
* // Do something here
diff --git a/app/Views/welcome_message.php b/app/Views/welcome_message.php
index 6d5da23f1224..919629fc77ef 100644
--- a/app/Views/welcome_message.php
+++ b/app/Views/welcome_message.php
@@ -150,6 +150,11 @@
.further h2:first-of-type {
padding-top: 0;
}
+ .svg-stroke {
+ fill: none;
+ stroke: #000;
+ stroke-width: 32px;
+ }
footer {
background-color: rgba(221, 72, 20, .8);
text-align: center;
@@ -206,14 +211,14 @@
The User Guide contains an introduction, tutorial, a number of "how to"
guides, and then reference documentation for the components that make up
- the framework. Check the User Guide !
-
+
Discuss
@@ -274,7 +279,7 @@
target="_blank">chat on Slack !
diff --git a/system/Debug/Toolbar/Views/toolbarloader.js b/system/Debug/Toolbar/Views/toolbarloader.js
index 7e5914354481..a8bacb4c69cd 100644
--- a/system/Debug/Toolbar/Views/toolbarloader.js
+++ b/system/Debug/Toolbar/Views/toolbarloader.js
@@ -74,8 +74,11 @@ function newXHR() {
let h2 = document.querySelector('#ci-history > h2');
if (h2) {
- h2.innerHTML = 'History You have new debug data. ';
+ h2.innerHTML = 'History You have new debug data. ';
document.querySelector('a[data-tab="ci-history"] > span > .badge').className += ' active';
+ document.getElementById('ci-history-update').addEventListener('click', function () {
+ loadDoc(debugbarTime);
+ }, false)
}
}
}
diff --git a/system/Email/Email.php b/system/Email/Email.php
index 42835c3d404e..183ac945047c 100644
--- a/system/Email/Email.php
+++ b/system/Email/Email.php
@@ -1665,7 +1665,9 @@ protected function unwrapSpecials()
/**
* Strip line-breaks via callback
*
- * @param string $matches
+ * @used-by unwrapSpecials()
+ *
+ * @param list $matches
*
* @return string
*/
diff --git a/system/HTTP/CLIRequest.php b/system/HTTP/CLIRequest.php
index 01823dbfc4c5..684b1dbfea15 100644
--- a/system/HTTP/CLIRequest.php
+++ b/system/HTTP/CLIRequest.php
@@ -315,7 +315,7 @@ public function getLocale(): string
/**
* Checks this request type.
*
- * @param string $type HTTP verb or 'json' or 'ajax'
+ * @param string $type HTTP verb or 'json' or 'ajax'
* @phpstan-param string|'get'|'post'|'put'|'delete'|'head'|'patch'|'options'|'json'|'ajax' $type
*/
public function is(string $type): bool
diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php
index 500b425d2dc5..dfc9ba97f7b2 100644
--- a/system/HTTP/CURLRequest.php
+++ b/system/HTTP/CURLRequest.php
@@ -389,7 +389,7 @@ public function send(string $method, string $url)
// Set the string we want to break our response from
$breakString = "\r\n\r\n";
- if (strpos($output, 'HTTP/1.1 100 Continue') === 0) {
+ while (strpos($output, 'HTTP/1.1 100 Continue') === 0) {
$output = substr($output, strpos($output, $breakString) + 4);
}
diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php
index 8bc246f7956d..499fa0b1cf06 100755
--- a/system/HTTP/IncomingRequest.php
+++ b/system/HTTP/IncomingRequest.php
@@ -400,7 +400,7 @@ public function negotiate(string $type, array $supported, bool $strictMatch = fa
/**
* Checks this request type.
*
- * @param string $type HTTP verb or 'json' or 'ajax'
+ * @param string $type HTTP verb or 'json' or 'ajax'
* @phpstan-param string|'get'|'post'|'put'|'delete'|'head'|'patch'|'options'|'json'|'ajax' $type
*/
public function is(string $type): bool
diff --git a/system/HTTP/RequestTrait.php b/system/HTTP/RequestTrait.php
index 6db903ddacec..9c1f4c28a52c 100644
--- a/system/HTTP/RequestTrait.php
+++ b/system/HTTP/RequestTrait.php
@@ -215,9 +215,9 @@ public function getEnv($index = null, $filter = null, $flags = null)
/**
* Allows manually setting the value of PHP global, like $_GET, $_POST, etc.
*
- * @param string $name Supergrlobal name (lowercase)
+ * @param string $name Supergrlobal name (lowercase)
* @phpstan-param 'get'|'post'|'request'|'cookie'|'server' $name
- * @param mixed $value
+ * @param mixed $value
*
* @return $this
*/
@@ -238,11 +238,11 @@ public function setGlobal(string $name, $value)
*
* http://php.net/manual/en/filter.filters.sanitize.php
*
- * @param string $name Supergrlobal name (lowercase)
+ * @param string $name Supergrlobal name (lowercase)
* @phpstan-param 'get'|'post'|'request'|'cookie'|'server' $name
- * @param array|string|null $index
- * @param int|null $filter Filter constant
- * @param array|int|null $flags Options
+ * @param array|string|null $index
+ * @param int|null $filter Filter constant
+ * @param array|int|null $flags Options
*
* @return array|bool|float|int|object|string|null
*/
@@ -332,7 +332,7 @@ public function fetchGlobal(string $name, $index = null, ?int $filter = null, $f
* Saves a copy of the current state of one of several PHP globals,
* so we can retrieve them later.
*
- * @param string $name Superglobal name (lowercase)
+ * @param string $name Superglobal name (lowercase)
* @phpstan-param 'get'|'post'|'request'|'cookie'|'server' $name
*
* @return void
diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php
index 6d56507314e7..8daa5ab2c0cd 100644
--- a/system/HTTP/SiteURI.php
+++ b/system/HTTP/SiteURI.php
@@ -83,10 +83,10 @@ class SiteURI extends URI
private string $routePath;
/**
- * @param string $relativePath URI path relative to baseURL. May include
- * queries or fragments.
- * @param string|null $host Optional current hostname.
- * @param string|null $scheme Optional scheme. 'http' or 'https'.
+ * @param string $relativePath URI path relative to baseURL. May include
+ * queries or fragments.
+ * @param string|null $host Optional current hostname.
+ * @param string|null $scheme Optional scheme. 'http' or 'https'.
* @phpstan-param 'http'|'https'|null $scheme
*/
public function __construct(
@@ -369,8 +369,10 @@ protected function applyParts(array $parts): void
/**
* For base_url() helper.
*
- * @param array|string $relativePath URI string or array of URI segments
- * @param string|null $scheme URI scheme. E.g., http, ftp
+ * @param array|string $relativePath URI string or array of URI segments.
+ * @param string|null $scheme URI scheme. E.g., http, ftp. If empty
+ * string '' is set, a protocol-relative
+ * link is returned.
*/
public function baseUrl($relativePath = '', ?string $scheme = null): string
{
@@ -406,9 +408,11 @@ private function stringifyRelativePath($relativePath): string
/**
* For site_url() helper.
*
- * @param array|string $relativePath URI string or array of URI segments
- * @param string|null $scheme URI scheme. E.g., http, ftp
- * @param App|null $config Alternate configuration to use
+ * @param array|string $relativePath URI string or array of URI segments.
+ * @param string|null $scheme URI scheme. E.g., http, ftp. If empty
+ * string '' is set, a protocol-relative
+ * link is returned.
+ * @param App|null $config Alternate configuration to use.
*/
public function siteUrl($relativePath = '', ?string $scheme = null, ?App $config = null): string
{
diff --git a/system/Helpers/array_helper.php b/system/Helpers/array_helper.php
index b8bd1a4d956f..4c1477f57d4a 100644
--- a/system/Helpers/array_helper.php
+++ b/system/Helpers/array_helper.php
@@ -72,12 +72,8 @@ function _array_search_dot(array $indexes, array $array)
$answer = array_filter($answer, static fn ($value) => $value !== null);
if ($answer !== []) {
- if (count($answer) === 1) {
- // If array only has one element, we return that element for BC.
- return current($answer);
- }
-
- return $answer;
+ // If array only has one element, we return that element for BC.
+ return count($answer) === 1 ? current($answer) : $answer;
}
return null;
diff --git a/system/Helpers/form_helper.php b/system/Helpers/form_helper.php
index d3abd7e247b0..b8ad34770464 100644
--- a/system/Helpers/form_helper.php
+++ b/system/Helpers/form_helper.php
@@ -656,13 +656,7 @@ function set_radio(string $field, string $value = '', bool $default = false): st
$postInput = $request->getPost($field);
- if ($oldInput !== null) {
- $input = $oldInput;
- } elseif ($postInput !== null) {
- $input = $postInput;
- } else {
- $input = $default;
- }
+ $input = $oldInput ?? $postInput ?? $default;
if (is_array($input)) {
// Note: in_array('', array(0)) returns TRUE, do not use it
diff --git a/system/Helpers/html_helper.php b/system/Helpers/html_helper.php
index 075dcd510b43..0f7e154a02bf 100755
--- a/system/Helpers/html_helper.php
+++ b/system/Helpers/html_helper.php
@@ -70,9 +70,9 @@ function _list(string $type = 'ul', $list = [], $attributes = '', int $depth = 0
$out .= $val;
} else {
$out .= $key
- . "\n"
- . _list($type, $val, '', $depth + 4)
- . str_repeat(' ', $depth + 2);
+ . "\n"
+ . _list($type, $val, '', $depth + 4)
+ . str_repeat(' ', $depth + 2);
}
$out .= "\n";
diff --git a/system/Helpers/url_helper.php b/system/Helpers/url_helper.php
index 8099558aa343..f992eac391f8 100644
--- a/system/Helpers/url_helper.php
+++ b/system/Helpers/url_helper.php
@@ -23,9 +23,11 @@
/**
* Returns a site URL as defined by the App config.
*
- * @param array|string $relativePath URI string or array of URI segments
- * @param string|null $scheme URI scheme. E.g., http, ftp
- * @param App|null $config Alternate configuration to use
+ * @param array|string $relativePath URI string or array of URI segments.
+ * @param string|null $scheme URI scheme. E.g., http, ftp. If empty
+ * string '' is set, a protocol-relative
+ * link is returned.
+ * @param App|null $config Alternate configuration to use.
*/
function site_url($relativePath = '', ?string $scheme = null, ?App $config = null): string
{
@@ -42,8 +44,10 @@ function site_url($relativePath = '', ?string $scheme = null, ?App $config = nul
* Returns the base URL as defined by the App config.
* Base URLs are trimmed site URLs without the index page.
*
- * @param array|string $relativePath URI string or array of URI segments
- * @param string|null $scheme URI scheme. E.g., http, ftp
+ * @param array|string $relativePath URI string or array of URI segments.
+ * @param string|null $scheme URI scheme. E.g., http, ftp. If empty
+ * string '' is set, a protocol-relative
+ * link is returned.
*/
function base_url($relativePath = '', ?string $scheme = null): string
{
diff --git a/system/I18n/TimeTrait.php b/system/I18n/TimeTrait.php
index 93f03d8a84bd..a2a92b8fc94d 100644
--- a/system/I18n/TimeTrait.php
+++ b/system/I18n/TimeTrait.php
@@ -266,7 +266,8 @@ public static function createFromFormat($format, $datetime, $timezone = null)
public static function createFromTimestamp(int $timestamp, $timezone = null, ?string $locale = null)
{
$time = new self(gmdate('Y-m-d H:i:s', $timestamp), 'UTC', $locale);
- $timezone ??= 'UTC';
+
+ $timezone ??= date_default_timezone_get();
return $time->setTimezone($timezone);
}
@@ -1152,7 +1153,7 @@ public function __toString(): string
*
* @param string $name
*
- * @return array|bool|DateTimeInterface|DateTimeZone|int|intlCalendar|self|string|null
+ * @return array|bool|DateTimeInterface|DateTimeZone|int|IntlCalendar|self|string|null
*/
public function __get($name)
{
diff --git a/system/Images/Handlers/ImageMagickHandler.php b/system/Images/Handlers/ImageMagickHandler.php
index 7dd5e5d755ee..cb8ce9bae763 100644
--- a/system/Images/Handlers/ImageMagickHandler.php
+++ b/system/Images/Handlers/ImageMagickHandler.php
@@ -77,7 +77,7 @@ public function _resize(bool $maintainRatio = false)
/**
* Crops the image.
*
- * @return bool|\CodeIgniter\Images\Handlers\ImageMagickHandler
+ * @return bool|ImageMagickHandler
*
* @throws Exception
*/
diff --git a/system/Log/Logger.php b/system/Log/Logger.php
index e5f482cb63d2..2246fc8c2017 100644
--- a/system/Log/Logger.php
+++ b/system/Log/Logger.php
@@ -37,7 +37,7 @@ class Logger implements LoggerInterface
* Used by the logThreshold Config setting to define
* which errors to show.
*
- * @var array
+ * @var array
*/
protected $logLevels = [
'emergency' => 1,
diff --git a/system/Model.php b/system/Model.php
index 5dba810c8c59..2596417c12e6 100644
--- a/system/Model.php
+++ b/system/Model.php
@@ -179,7 +179,7 @@ public function setTable(string $table)
* @param bool $singleton Single or multiple results
* @param array|int|string|null $id One primary key or an array of primary keys
*
- * @return array|object|null The resulting row of data, or null.
+ * @return array|object|null The resulting row of data, or null.
* @phpstan-return ($singleton is true ? row_array|null|object : list)
*/
protected function doFind(bool $singleton, $id = null)
@@ -211,7 +211,7 @@ protected function doFind(bool $singleton, $id = null)
*
* @param string $columnName Column Name
*
- * @return array|null The resulting row of data, or null if no data found.
+ * @return array|null The resulting row of data, or null if no data found.
* @phpstan-return list|null
*/
protected function doFindColumn(string $columnName)
@@ -227,7 +227,7 @@ protected function doFindColumn(string $columnName)
* @param int $limit Limit
* @param int $offset Offset
*
- * @return array
+ * @return array
* @phpstan-return list
*/
protected function doFindAll(int $limit = 0, int $offset = 0)
@@ -248,7 +248,7 @@ protected function doFindAll(int $limit = 0, int $offset = 0)
* Query Builder calls into account when determining the result set.
* This method works only with dbCalls.
*
- * @return array|object|null
+ * @return array|object|null
* @phpstan-return row_array|object|null
*/
protected function doFirst()
@@ -274,7 +274,7 @@ protected function doFirst()
* Inserts data into the current table.
* This method works only with dbCalls.
*
- * @param array $row Row data
+ * @param array $row Row data
* @phpstan-param row_array $row
*
* @return bool
@@ -364,9 +364,9 @@ protected function doInsertBatch(?array $set = null, ?bool $escape = null, int $
* Updates a single record in $this->table.
* This method works only with dbCalls.
*
- * @param array|int|string|null $id
- * @param array|null $row Row data
- * @phpstan-param row_array|null $row
+ * @param array|int|string|null $id
+ * @param array|null $row Row data
+ * @phpstan-param row_array|null $row
*/
protected function doUpdate($id = null, $row = null): bool
{
@@ -483,9 +483,9 @@ protected function doOnlyDeleted()
* Compiles a replace into string and runs the query
* This method works only with dbCalls.
*
- * @param array|null $row Data
+ * @param array|null $row Data
* @phpstan-param row_array|null $row
- * @param bool $returnSQL Set to true to return Query String
+ * @param bool $returnSQL Set to true to return Query String
*
* @return BaseResult|false|Query|string
*/
@@ -531,7 +531,7 @@ protected function idValue($data)
/**
* Returns the id value for the data array or object
*
- * @param array|object $row Row data
+ * @param array|object $row Row data
* @phpstan-param row_array|object $row
*
* @return array|int|string|null
@@ -718,11 +718,11 @@ protected function shouldUpdate($row): bool
* Inserts data into the database. If an object is provided,
* it will attempt to convert it to an array.
*
- * @param array|object|null $row
+ * @param array|object|null $row
* @phpstan-param row_array|object|null $row
- * @param bool $returnID Whether insert ID should be returned or not.
+ * @param bool $returnID Whether insert ID should be returned or not.
*
- * @return bool|int|string
+ * @return bool|int|string
* @phpstan-return ($returnID is true ? int|string|false : bool)
*
* @throws ReflectionException
@@ -751,7 +751,7 @@ public function insert($row = null, bool $returnID = true)
* @used-by insert() to protect against mass assignment vulnerabilities.
* @used-by insertBatch() to protect against mass assignment vulnerabilities.
*
- * @param array $row Row data
+ * @param array $row Row data
* @phpstan-param row_array $row
*
* @throws DataException
@@ -784,8 +784,8 @@ protected function doProtectFieldsForInsert(array $row): array
* Updates a single record in the database. If an object is provided,
* it will attempt to convert it into an array.
*
- * @param array|int|string|null $id
- * @param array|object|null $row
+ * @param array|int|string|null $id
+ * @param array|object|null $row
* @phpstan-param row_array|object|null $row
*
* @throws ReflectionException
diff --git a/system/RESTful/ResourceController.php b/system/RESTful/ResourceController.php
index 6ffb602c8bd1..bd0356268142 100644
--- a/system/RESTful/ResourceController.php
+++ b/system/RESTful/ResourceController.php
@@ -104,7 +104,7 @@ public function delete($id = null)
/**
* Set/change the expected response representation for returned objects
*
- * @param string $format json/xml
+ * @param string $format json/xml
* @phpstan-param 'json'|'xml' $format
*
* @return void
diff --git a/system/Router/AutoRouter.php b/system/Router/AutoRouter.php
index 87a5b3ac3bc1..d7516afb0a15 100644
--- a/system/Router/AutoRouter.php
+++ b/system/Router/AutoRouter.php
@@ -13,6 +13,7 @@
use Closure;
use CodeIgniter\Exceptions\PageNotFoundException;
+use CodeIgniter\HTTP\ResponseInterface;
/**
* Router for Auto-Routing
@@ -22,7 +23,7 @@ final class AutoRouter implements AutoRouterInterface
/**
* List of CLI routes that do not contain '*' routes.
*
- * @var array [routeKey => handler]
+ * @var array [routeKey => handler]
*/
private array $cliRoutes;
diff --git a/system/Router/RouteCollection.php b/system/Router/RouteCollection.php
index 4407c1076f3d..8df31c9073bb 100644
--- a/system/Router/RouteCollection.php
+++ b/system/Router/RouteCollection.php
@@ -13,6 +13,7 @@
use Closure;
use CodeIgniter\Autoloader\FileLocator;
+use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Router\Exceptions\RouterException;
use Config\App;
use Config\Modules;
@@ -87,7 +88,7 @@ class RouteCollection implements RouteCollectionInterface
* A callable that will be shown
* when the route cannot be matched.
*
- * @var Closure|string
+ * @var (Closure(string): (ResponseInterface|string|void))|string
*/
protected $override404;
@@ -497,7 +498,7 @@ public function set404Override($callable = null): RouteCollectionInterface
* Returns the 404 Override setting, which can be null, a closure
* or the controller/string.
*
- * @return Closure|string|null
+ * @return (Closure(string): (ResponseInterface|string|void))|string|null
*/
public function get404Override()
{
@@ -658,7 +659,7 @@ public function map(array $routes = [], ?array $options = null): RouteCollection
* Example:
* $routes->add('news', 'Posts::index');
*
- * @param array|Closure|string $to
+ * @param array|(Closure(mixed...): (ResponseInterface|string|void))|string $to
*/
public function add(string $from, $to, ?array $options = null): RouteCollectionInterface
{
@@ -992,7 +993,7 @@ public function presenter(string $name, ?array $options = null): RouteCollection
* Example:
* $route->match( ['get', 'post'], 'users/(:num)', 'users/$1);
*
- * @param array|Closure|string $to
+ * @param array|(Closure(mixed...): (ResponseInterface|string|void))|string $to
*/
public function match(array $verbs = [], string $from = '', $to = '', ?array $options = null): RouteCollectionInterface
{
@@ -1012,7 +1013,7 @@ public function match(array $verbs = [], string $from = '', $to = '', ?array $op
/**
* Specifies a route that is only available to GET requests.
*
- * @param array|Closure|string $to
+ * @param array|(Closure(mixed...): (ResponseInterface|string|void))|string $to
*/
public function get(string $from, $to, ?array $options = null): RouteCollectionInterface
{
@@ -1024,7 +1025,7 @@ public function get(string $from, $to, ?array $options = null): RouteCollectionI
/**
* Specifies a route that is only available to POST requests.
*
- * @param array|Closure|string $to
+ * @param array|(Closure(mixed...): (ResponseInterface|string|void))|string $to
*/
public function post(string $from, $to, ?array $options = null): RouteCollectionInterface
{
@@ -1036,7 +1037,7 @@ public function post(string $from, $to, ?array $options = null): RouteCollection
/**
* Specifies a route that is only available to PUT requests.
*
- * @param array|Closure|string $to
+ * @param array|(Closure(mixed...): (ResponseInterface|string|void))|string $to
*/
public function put(string $from, $to, ?array $options = null): RouteCollectionInterface
{
@@ -1048,7 +1049,7 @@ public function put(string $from, $to, ?array $options = null): RouteCollectionI
/**
* Specifies a route that is only available to DELETE requests.
*
- * @param array|Closure|string $to
+ * @param array|(Closure(mixed...): (ResponseInterface|string|void))|string $to
*/
public function delete(string $from, $to, ?array $options = null): RouteCollectionInterface
{
@@ -1060,7 +1061,7 @@ public function delete(string $from, $to, ?array $options = null): RouteCollecti
/**
* Specifies a route that is only available to HEAD requests.
*
- * @param array|Closure|string $to
+ * @param array|(Closure(mixed...): (ResponseInterface|string|void))|string $to
*/
public function head(string $from, $to, ?array $options = null): RouteCollectionInterface
{
@@ -1072,7 +1073,7 @@ public function head(string $from, $to, ?array $options = null): RouteCollection
/**
* Specifies a route that is only available to PATCH requests.
*
- * @param array|Closure|string $to
+ * @param array|(Closure(mixed...): (ResponseInterface|string|void))|string $to
*/
public function patch(string $from, $to, ?array $options = null): RouteCollectionInterface
{
@@ -1084,7 +1085,7 @@ public function patch(string $from, $to, ?array $options = null): RouteCollectio
/**
* Specifies a route that is only available to OPTIONS requests.
*
- * @param array|Closure|string $to
+ * @param array|(Closure(mixed...): (ResponseInterface|string|void))|string $to
*/
public function options(string $from, $to, ?array $options = null): RouteCollectionInterface
{
@@ -1096,7 +1097,7 @@ public function options(string $from, $to, ?array $options = null): RouteCollect
/**
* Specifies a route that is only available to command-line requests.
*
- * @param array|Closure|string $to
+ * @param array|(Closure(mixed...): (ResponseInterface|string|void))|string $to
*/
public function cli(string $from, $to, ?array $options = null): RouteCollectionInterface
{
@@ -1416,7 +1417,7 @@ private function replaceLocale(string $route, ?string $locale = null): string
* the request method(s) that this route will work for. They can be separated
* by a pipe character "|" if there is more than one.
*
- * @param array|Closure|string $to
+ * @param array|(Closure(mixed...): (ResponseInterface|string|void))|string $to
*
* @return void
*/
@@ -1767,7 +1768,7 @@ public function getRegisteredControllers(?string $verb = '*'): array
}
/**
- * @param Closure|string $handler Handler
+ * @param (Closure(mixed...): (ResponseInterface|string|void))|string $handler Handler
*
* @return string|null Controller classname
*/
diff --git a/system/Router/RouteCollectionInterface.php b/system/Router/RouteCollectionInterface.php
index 00f1d4c8bdc2..c9c8610009fc 100644
--- a/system/Router/RouteCollectionInterface.php
+++ b/system/Router/RouteCollectionInterface.php
@@ -12,6 +12,7 @@
namespace CodeIgniter\Router;
use Closure;
+use CodeIgniter\HTTP\ResponseInterface;
/**
* Interface RouteCollectionInterface
@@ -28,9 +29,9 @@ interface RouteCollectionInterface
/**
* Adds a single route to the collection.
*
- * @param string $from The route path (with placeholders or regex)
- * @param array|Closure|string $to The route handler
- * @param array|null $options The route options
+ * @param string $from The route path (with placeholders or regex)
+ * @param array|(Closure(mixed...): (ResponseInterface|string|void))|string $to The route handler
+ * @param array|null $options The route options
*
* @return RouteCollectionInterface
*/
@@ -111,7 +112,7 @@ public function set404Override($callable = null): self;
* Returns the 404 Override setting, which can be null, a closure
* or the controller/string.
*
- * @return Closure|string|null
+ * @return (Closure(string): (ResponseInterface|string|void))|string|null
*/
public function get404Override();
diff --git a/system/Router/Router.php b/system/Router/Router.php
index 723c292cb931..634f3e61a035 100644
--- a/system/Router/Router.php
+++ b/system/Router/Router.php
@@ -15,6 +15,7 @@
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\Exceptions\RedirectException;
use CodeIgniter\HTTP\Request;
+use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Router\Exceptions\RouterException;
use Config\App;
use Config\Feature;
@@ -44,7 +45,7 @@ class Router implements RouterInterface
/**
* The name of the controller class.
*
- * @var Closure|string
+ * @var (Closure(mixed...): (ResponseInterface|string|void))|string
*/
protected $controller;
@@ -163,7 +164,7 @@ public function __construct(RouteCollectionInterface $routes, ?Request $request
*
* @param string|null $uri URI path relative to baseURL
*
- * @return Closure|string Controller classname or Closure
+ * @return (Closure(mixed...): (ResponseInterface|string|void))|string Controller classname or Closure
*
* @throws PageNotFoundException
* @throws RedirectException
@@ -237,7 +238,7 @@ public function getFilters(): array
/**
* Returns the name of the matched controller.
*
- * @return Closure|string Controller classname or Closure
+ * @return (Closure(mixed...): (ResponseInterface|string|void))|string Controller classname or Closure
*/
public function controllerName()
{
diff --git a/system/Router/RouterInterface.php b/system/Router/RouterInterface.php
index ffed59ca8aac..ccdef3d6d9be 100644
--- a/system/Router/RouterInterface.php
+++ b/system/Router/RouterInterface.php
@@ -13,6 +13,7 @@
use Closure;
use CodeIgniter\HTTP\Request;
+use CodeIgniter\HTTP\ResponseInterface;
/**
* Expected behavior of a Router.
@@ -29,14 +30,14 @@ public function __construct(RouteCollectionInterface $routes, ?Request $request
*
* @param string|null $uri URI path relative to baseURL
*
- * @return Closure|string Controller classname or Closure
+ * @return (Closure(mixed...): (ResponseInterface|string|void))|string Controller classname or Closure
*/
public function handle(?string $uri = null);
/**
* Returns the name of the matched controller.
*
- * @return Closure|string Controller classname or Closure
+ * @return (Closure(mixed...): (ResponseInterface|string|void))|string Controller classname or Closure
*/
public function controllerName();
diff --git a/system/Session/Handlers/BaseHandler.php b/system/Session/Handlers/BaseHandler.php
index 086cdc2f2276..572ec9070576 100644
--- a/system/Session/Handlers/BaseHandler.php
+++ b/system/Session/Handlers/BaseHandler.php
@@ -41,7 +41,7 @@ abstract class BaseHandler implements SessionHandlerInterface
* Cookie prefix
*
* The Config\Cookie::$prefix setting is completely ignored.
- * See https://codeigniter4.github.io/CodeIgniter4/libraries/sessions.html#session-preferences
+ * See https://codeigniter.com/user_guide/libraries/sessions.html#session-preferences
*
* @var string
*/
diff --git a/system/Session/Handlers/RedisHandler.php b/system/Session/Handlers/RedisHandler.php
index b429792929b4..5a81e6a4da00 100644
--- a/system/Session/Handlers/RedisHandler.php
+++ b/system/Session/Handlers/RedisHandler.php
@@ -115,6 +115,8 @@ protected function setSavePath(): void
*
* @param string $path The path where to store/retrieve the session
* @param string $name The session name
+ *
+ * @throws RedisException
*/
public function open($path, $name): bool
{
@@ -124,12 +126,20 @@ public function open($path, $name): bool
$redis = new Redis();
- if (! $redis->connect($this->savePath['protocol'] . '://' . $this->savePath['host'], ($this->savePath['host'][0] === '/' ? 0 : $this->savePath['port']), $this->savePath['timeout'])) {
+ if (
+ ! $redis->connect(
+ $this->savePath['protocol'] . '://' . $this->savePath['host'],
+ ($this->savePath['host'][0] === '/' ? 0 : $this->savePath['port']),
+ $this->savePath['timeout']
+ )
+ ) {
$this->logger->error('Session: Unable to connect to Redis with the configured settings.');
} elseif (isset($this->savePath['password']) && ! $redis->auth($this->savePath['password'])) {
$this->logger->error('Session: Unable to authenticate to Redis instance.');
} elseif (isset($this->savePath['database']) && ! $redis->select($this->savePath['database'])) {
- $this->logger->error('Session: Unable to select Redis database with index ' . $this->savePath['database']);
+ $this->logger->error(
+ 'Session: Unable to select Redis database with index ' . $this->savePath['database']
+ );
} else {
$this->redis = $redis;
@@ -146,6 +156,8 @@ public function open($path, $name): bool
*
* @return false|string Returns an encoded string of the read data.
* If nothing was read, it must return false.
+ *
+ * @throws RedisException
*/
#[ReturnTypeWillChange]
public function read($id)
@@ -168,7 +180,7 @@ public function read($id)
return $data;
}
- return '';
+ return false;
}
/**
@@ -176,6 +188,8 @@ public function read($id)
*
* @param string $id The session ID
* @param string $data The encoded session data
+ *
+ * @throws RedisException
*/
public function write($id, $data): bool
{
@@ -222,8 +236,8 @@ public function close(): bool
$pingReply = $this->redis->ping();
if (($pingReply === true) || ($pingReply === '+PONG')) {
- if (isset($this->lockKey)) {
- $this->releaseLock();
+ if (isset($this->lockKey) && ! $this->releaseLock()) {
+ return false;
}
if (! $this->redis->close()) {
@@ -246,12 +260,16 @@ public function close(): bool
* Destroys a session
*
* @param string $id The session ID being destroyed
+ *
+ * @throws RedisException
*/
public function destroy($id): bool
{
if (isset($this->redis, $this->lockKey)) {
if (($result = $this->redis->del($this->keyPrefix . $id)) !== 1) {
- $this->logger->debug('Session: Redis::del() expected to return 1, got ' . var_export($result, true) . ' instead.');
+ $this->logger->debug(
+ 'Session: Redis::del() expected to return 1, got ' . var_export($result, true) . ' instead.'
+ );
}
return $this->destroyCookie();
@@ -278,6 +296,8 @@ public function gc($max_lifetime)
* Acquires an emulated lock.
*
* @param string $sessionID Session ID
+ *
+ * @throws RedisException
*/
protected function lockSession(string $sessionID): bool
{
@@ -287,41 +307,40 @@ protected function lockSession(string $sessionID): bool
// so we need to check here if the lock key is for the
// correct session ID.
if ($this->lockKey === $lockKey) {
+ // If there is the lock, make the ttl longer.
return $this->redis->expire($this->lockKey, 300);
}
$attempt = 0;
do {
- $ttl = $this->redis->ttl($lockKey);
- assert(is_int($ttl));
+ $result = $this->redis->set(
+ $lockKey,
+ (string) Time::now()->getTimestamp(),
+ // NX -- Only set the key if it does not already exist.
+ // EX seconds -- Set the specified expire time, in seconds.
+ ['nx', 'ex' => 300]
+ );
- if ($ttl > 0) {
- sleep(1);
+ if (! $result) {
+ usleep(100000);
continue;
}
- if (! $this->redis->setex($lockKey, 300, (string) Time::now()->getTimestamp())) {
- $this->logger->error('Session: Error while trying to obtain lock for ' . $this->keyPrefix . $sessionID);
-
- return false;
- }
-
$this->lockKey = $lockKey;
break;
- } while (++$attempt < 30);
+ } while (++$attempt < 300);
- if ($attempt === 30) {
- log_message('error', 'Session: Unable to obtain lock for ' . $this->keyPrefix . $sessionID . ' after 30 attempts, aborting.');
+ if ($attempt === 300) {
+ $this->logger->error(
+ 'Session: Unable to obtain lock for ' . $this->keyPrefix . $sessionID
+ . ' after 300 attempts, aborting.'
+ );
return false;
}
- if ($ttl === -1) {
- log_message('debug', 'Session: Lock for ' . $this->keyPrefix . $sessionID . ' had no TTL, overriding.');
- }
-
$this->lock = true;
return true;
@@ -329,6 +348,8 @@ protected function lockSession(string $sessionID): bool
/**
* Releases a previously acquired lock
+ *
+ * @throws RedisException
*/
protected function releaseLock(): bool
{
diff --git a/system/Test/TestLogger.php b/system/Test/TestLogger.php
index 240f5c6d95b5..fa2f35683ecd 100644
--- a/system/Test/TestLogger.php
+++ b/system/Test/TestLogger.php
@@ -18,13 +18,16 @@
*/
class TestLogger extends Logger
{
+ /**
+ * @var list
+ */
protected static $op_logs = [];
/**
* The log method is overridden so that we can store log history during
* the tests to allow us to check ->assertLogged() methods.
*
- * @param string $level
+ * @param mixed $level
* @param string $message
*/
public function log($level, $message, array $context = []): bool
@@ -92,6 +95,8 @@ public static function didLog(string $level, $message, bool $useExactComparison
*
* @param string $file
*
+ * @return string
+ *
* @deprecated No longer needed as underlying protected method is also deprecated.
*/
public function cleanup($file)
diff --git a/system/Test/bootstrap.php b/system/Test/bootstrap.php
index 3e2d54fdb564..57196190f527 100644
--- a/system/Test/bootstrap.php
+++ b/system/Test/bootstrap.php
@@ -88,7 +88,4 @@
$env = new DotEnv(ROOTPATH);
$env->load();
-// Always load the URL helper, it should be used in most of apps.
-helper('url');
-
Services::routes()->loadRoutes();
diff --git a/system/Traits/ConditionalTrait.php b/system/Traits/ConditionalTrait.php
index 620bdd88508b..5b8273ee78bc 100644
--- a/system/Traits/ConditionalTrait.php
+++ b/system/Traits/ConditionalTrait.php
@@ -18,10 +18,10 @@ trait ConditionalTrait
*
* @template TWhen of mixed
*
- * @phpstan-param TWhen $condition
- * @phpstan-param callable(self, TWhen): mixed $callback
- * @phpstan-param (callable(self): mixed)|null $defaultCallback
- * @param array|bool|float|int|object|resource|string|null $condition
+ * @phpstan-param TWhen $condition
+ * @phpstan-param callable(self, TWhen): mixed $callback
+ * @phpstan-param (callable(self): mixed)|null $defaultCallback
+ * @param array|bool|float|int|object|resource|string|null $condition
*
* @return $this
*/
@@ -41,10 +41,10 @@ public function when($condition, callable $callback, ?callable $defaultCallback
*
* @template TWhenNot of mixed
*
- * @phpstan-param TWhenNot $condition
- * @phpstan-param callable(self, TWhenNot): mixed $callback
- * @phpstan-param (callable(self): mixed)|null $defaultCallback
- * @param array|bool|float|int|object|resource|string|null $condition
+ * @phpstan-param TWhenNot $condition
+ * @phpstan-param callable(self, TWhenNot): mixed $callback
+ * @phpstan-param (callable(self): mixed)|null $defaultCallback
+ * @param array|bool|float|int|object|resource|string|null $condition
*
* @return $this
*/
diff --git a/system/View/Filters.php b/system/View/Filters.php
index fa03a1919a2b..4b89b1265b1c 100644
--- a/system/View/Filters.php
+++ b/system/View/Filters.php
@@ -74,7 +74,7 @@ public static function default($value, string $default): string
/**
* Escapes the given value with our `esc()` helper function.
*
- * @param string $value
+ * @param string $value
* @phpstan-param 'html'|'js'|'css'|'url'|'attr'|'raw' $context
*/
public static function esc($value, string $context = 'html'): string
diff --git a/system/View/Parser.php b/system/View/Parser.php
index 87f0e96bdce9..ff5656a53863 100644
--- a/system/View/Parser.php
+++ b/system/View/Parser.php
@@ -185,8 +185,9 @@ public function renderString(string $template, ?array $options = null, ?bool $sa
* so that the variable is correctly handled within the
* parsing itself, and contexts (including raw) are respected.
*
- * @param non-empty-string|null $context The context to escape it for: html, css, js, url, raw
- * If 'raw', no escaping will happen
+ * @param non-empty-string|null $context The context to escape it for.
+ * If 'raw', no escaping will happen.
+ * @phpstan-param null|'html'|'js'|'css'|'url'|'attr'|'raw' $context
*/
public function setData(array $data = [], ?string $context = null): RendererInterface
{
diff --git a/system/View/RendererInterface.php b/system/View/RendererInterface.php
index a0f093b67326..c4edeb9550f4 100644
--- a/system/View/RendererInterface.php
+++ b/system/View/RendererInterface.php
@@ -22,10 +22,10 @@ interface RendererInterface
* Builds the output based upon a file name and any
* data that has already been set.
*
- * @param array $options Reserved for 3rd-party uses since
- * it might be needed to pass additional info
- * to other template engines.
- * @param bool $saveData Whether to save data for subsequent calls
+ * @param array|null $options Reserved for 3rd-party uses since
+ * it might be needed to pass additional info
+ * to other template engines.
+ * @param bool $saveData Whether to save data for subsequent calls
*/
public function render(string $view, ?array $options = null, bool $saveData = false): string;
@@ -33,19 +33,19 @@ public function render(string $view, ?array $options = null, bool $saveData = fa
* Builds the output based upon a string and any
* data that has already been set.
*
- * @param string $view The view contents
- * @param array $options Reserved for 3rd-party uses since
- * it might be needed to pass additional info
- * to other template engines.
- * @param bool $saveData Whether to save data for subsequent calls
+ * @param string $view The view contents
+ * @param array|null $options Reserved for 3rd-party uses since
+ * it might be needed to pass additional info
+ * to other template engines.
+ * @param bool $saveData Whether to save data for subsequent calls
*/
public function renderString(string $view, ?array $options = null, bool $saveData = false): string;
/**
* Sets several pieces of view data at once.
*
- * @param string $context The context to escape it for: html, css, js, url
- * If 'raw', no escaping will happen
+ * @param non-empty-string|null $context The context to escape it for.
+ * If 'raw', no escaping will happen.
* @phpstan-param null|'html'|'js'|'css'|'url'|'attr'|'raw' $context
*
* @return RendererInterface
@@ -55,9 +55,9 @@ public function setData(array $data = [], ?string $context = null);
/**
* Sets a single piece of view data.
*
- * @param mixed $value
- * @param string $context The context to escape it for: html, css, js, url
- * If 'raw' no escaping will happen
+ * @param mixed $value
+ * @param non-empty-string|null $context The context to escape it for.
+ * If 'raw', no escaping will happen.
* @phpstan-param null|'html'|'js'|'css'|'url'|'attr'|'raw' $context
*
* @return RendererInterface
diff --git a/system/View/View.php b/system/View/View.php
index 2e2823fa2332..ad5c38de9b2f 100644
--- a/system/View/View.php
+++ b/system/View/View.php
@@ -330,8 +330,8 @@ public function excerpt(string $string, int $length = 20): string
/**
* Sets several pieces of view data at once.
*
- * @param string|null $context The context to escape it for: html, css, js, url
- * If null, no escaping will happen
+ * @param non-empty-string|null $context The context to escape it for.
+ * If 'raw', no escaping will happen.
* @phpstan-param null|'html'|'js'|'css'|'url'|'attr'|'raw' $context
*/
public function setData(array $data = [], ?string $context = null): RendererInterface
@@ -349,9 +349,9 @@ public function setData(array $data = [], ?string $context = null): RendererInte
/**
* Sets a single piece of view data.
*
- * @param mixed $value
- * @param string|null $context The context to escape it for: html, css, js, url
- * If null, no escaping will happen
+ * @param mixed $value
+ * @param non-empty-string|null $context The context to escape it for.
+ * If 'raw', no escaping will happen.
* @phpstan-param null|'html'|'js'|'css'|'url'|'attr'|'raw' $context
*/
public function setVar(string $name, $value = null, ?string $context = null): RendererInterface
diff --git a/tests/README.md b/tests/README.md
index 52ddc1e6711f..0e5dbbe3312a 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -7,7 +7,7 @@ use to test your application. Those details can be found in the documentation.
## Resources
-* [CodeIgniter 4 User Guide on Testing](https://codeigniter4.github.io/userguide/testing/index.html)
+* [CodeIgniter 4 User Guide on Testing](https://codeigniter.com/user_guide/testing/index.html)
* [PHPUnit docs](https://phpunit.de/documentation.html)
* [Any tutorials on Unit testing in CI4?](https://forum.codeigniter.com/showthread.php?tid=81830)
diff --git a/tests/system/Database/Live/AbstractGetFieldDataTest.php b/tests/system/Database/Live/AbstractGetFieldDataTest.php
index 570061cac3e3..42ced8a69d52 100644
--- a/tests/system/Database/Live/AbstractGetFieldDataTest.php
+++ b/tests/system/Database/Live/AbstractGetFieldDataTest.php
@@ -26,6 +26,14 @@ abstract class AbstractGetFieldDataTest extends CIUnitTestCase
protected $db;
protected Forge $forge;
+ protected string $table = 'test1';
+
+ public static function setUpBeforeClass(): void
+ {
+ parent::setUpBeforeClass();
+
+ helper('array');
+ }
protected function setUp(): void
{
@@ -34,7 +42,6 @@ protected function setUp(): void
$this->db = Database::connect($this->DBGroup);
$this->createForge();
- $this->createTable();
}
/**
@@ -46,12 +53,12 @@ protected function tearDown(): void
{
parent::tearDown();
- $this->forge->dropTable('test1', true);
+ $this->forge->dropTable($this->table, true);
}
- protected function createTable()
+ protected function createTableForDefault()
{
- $this->forge->dropTable('test1', true);
+ $this->forge->dropTable($this->table, true);
$this->forge->addField([
'id' => [
@@ -88,8 +95,77 @@ protected function createTable()
],
]);
$this->forge->addKey('id', true);
- $this->forge->createTable('test1');
+ $this->forge->createTable($this->table);
}
- abstract public function testGetFieldData(): void;
+ protected function createTableForType()
+ {
+ $this->forge->dropTable($this->table, true);
+
+ // missing types:
+ // TINYINT,MEDIUMINT,BIT,YEAR,BINARY,VARBINARY,TINYTEXT,LONGTEXT,
+ // JSON,Spatial data types
+ // `id` must be INTEGER else SQLite3 error on not null for autoincrement field.
+ $fields = [
+ 'id' => ['type' => 'INTEGER', 'constraint' => 20, 'auto_increment' => true],
+ 'type_varchar' => ['type' => 'VARCHAR', 'constraint' => 40, 'null' => true],
+ 'type_char' => ['type' => 'CHAR', 'constraint' => 10, 'null' => true],
+ // TEXT should not be used on SQLSRV. It is deprecated.
+ 'type_text' => ['type' => 'TEXT', 'null' => true],
+ 'type_smallint' => ['type' => 'SMALLINT', 'null' => true],
+ 'type_integer' => ['type' => 'INTEGER', 'null' => true],
+ 'type_float' => ['type' => 'FLOAT', 'null' => true],
+ 'type_numeric' => ['type' => 'NUMERIC', 'constraint' => '18,2', 'null' => true],
+ 'type_date' => ['type' => 'DATE', 'null' => true],
+ 'type_time' => ['type' => 'TIME', 'null' => true],
+ // On SQLSRV `datetime2` is recommended.
+ 'type_datetime' => ['type' => 'DATETIME', 'null' => true],
+ 'type_timestamp' => ['type' => 'TIMESTAMP', 'null' => true],
+ 'type_bigint' => ['type' => 'BIGINT', 'null' => true],
+ 'type_real' => ['type' => 'REAL', 'null' => true],
+ 'type_enum' => ['type' => 'ENUM', 'constraint' => ['appel', 'pears'], 'null' => true],
+ 'type_set' => ['type' => 'SET', 'constraint' => ['one', 'two'], 'null' => true],
+ 'type_mediumtext' => ['type' => 'MEDIUMTEXT', 'null' => true],
+ 'type_double' => ['type' => 'DOUBLE', 'null' => true],
+ 'type_decimal' => ['type' => 'DECIMAL', 'constraint' => '18,4', 'null' => true],
+ 'type_blob' => ['type' => 'BLOB', 'null' => true],
+ 'type_boolean' => ['type' => 'BOOLEAN', 'null' => true],
+ ];
+
+ if ($this->db->DBDriver === 'Postgre') {
+ unset(
+ $fields['type_enum'],
+ $fields['type_set'],
+ $fields['type_mediumtext'],
+ $fields['type_double'],
+ $fields['type_blob']
+ );
+ }
+
+ if ($this->db->DBDriver === 'SQLSRV') {
+ unset(
+ $fields['type_set'],
+ $fields['type_mediumtext'],
+ $fields['type_double'],
+ $fields['type_blob']
+ );
+ }
+
+ $this->forge->addField($fields);
+ $this->forge->addKey('id', true);
+ $this->forge->createTable($this->table);
+ }
+
+ abstract public function testGetFieldDataDefault(): void;
+
+ protected function assertSameFieldData(array $expected, array $actual)
+ {
+ $expectedArray = json_decode(json_encode($expected), true);
+ array_sort_by_multiple_keys($expectedArray, ['name' => SORT_ASC]);
+
+ $fieldsArray = json_decode(json_encode($actual), true);
+ array_sort_by_multiple_keys($fieldsArray, ['name' => SORT_ASC]);
+
+ $this->assertSame($expectedArray, $fieldsArray);
+ }
}
diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php
index 626660ee7246..bdc988dbb4d8 100644
--- a/tests/system/Database/Live/ForgeTest.php
+++ b/tests/system/Database/Live/ForgeTest.php
@@ -953,30 +953,30 @@ public function testAddFields(): void
0 => [
'name' => 'id',
'type' => 'integer',
+ 'max_length' => '32',
'nullable' => false,
'default' => "nextval('db_forge_test_fields_id_seq'::regclass)",
- 'max_length' => '32',
],
1 => [
'name' => 'username',
'type' => 'character varying',
+ 'max_length' => '255',
'nullable' => false,
'default' => null,
- 'max_length' => '255',
],
2 => [
'name' => 'name',
'type' => 'character varying',
+ 'max_length' => '255',
'nullable' => true,
'default' => null,
- 'max_length' => '255',
],
3 => [
'name' => 'active',
'type' => 'integer',
+ 'max_length' => '32',
'nullable' => false,
'default' => '0',
- 'max_length' => '32',
],
];
} elseif ($this->db->DBDriver === 'SQLite3') {
@@ -985,33 +985,33 @@ public function testAddFields(): void
'name' => 'id',
'type' => 'INTEGER',
'max_length' => null,
- 'default' => null,
- 'primary_key' => true,
'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 1,
],
1 => [
'name' => 'username',
'type' => 'VARCHAR',
'max_length' => null,
- 'default' => null,
- 'primary_key' => false,
'nullable' => false,
+ 'default' => null,
+ 'primary_key' => 0,
],
2 => [
'name' => 'name',
'type' => 'VARCHAR',
'max_length' => null,
- 'default' => null,
- 'primary_key' => false,
'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
],
3 => [
'name' => 'active',
'type' => 'INTEGER',
'max_length' => null,
- 'default' => '0',
- 'primary_key' => false,
'nullable' => false,
+ 'default' => '0',
+ 'primary_key' => 0,
],
];
} elseif ($this->db->DBDriver === 'SQLSRV') {
@@ -1019,30 +1019,30 @@ public function testAddFields(): void
0 => [
'name' => 'id',
'type' => 'int',
- 'default' => null,
'max_length' => 10,
'nullable' => false,
+ 'default' => null,
],
1 => [
'name' => 'username',
'type' => 'varchar',
- 'default' => null,
'max_length' => 255,
'nullable' => false,
+ 'default' => null,
],
2 => [
'name' => 'name',
'type' => 'varchar',
- 'default' => null,
'max_length' => 255,
'nullable' => true,
+ 'default' => null,
],
3 => [
'name' => 'active',
'type' => 'int',
- 'default' => '((0))', // Why?
'max_length' => 10,
'nullable' => false,
+ 'default' => '((0))', // Why?
],
];
} elseif ($this->db->DBDriver === 'OCI8') {
@@ -1051,29 +1051,29 @@ public function testAddFields(): void
'name' => 'id',
'type' => 'NUMBER',
'max_length' => '11',
- 'default' => '"ORACLE"."ISEQ$$_80229".nextval', // Sequence id may change
'nullable' => false,
+ 'default' => '"ORACLE"."ISEQ$$_80229".nextval', // Sequence id may change
],
1 => [
'name' => 'username',
'type' => 'VARCHAR2',
'max_length' => '255',
- 'default' => '',
'nullable' => false,
+ 'default' => null,
],
2 => [
'name' => 'name',
'type' => 'VARCHAR2',
'max_length' => '255',
- 'default' => null,
'nullable' => true,
+ 'default' => null,
],
3 => [
'name' => 'active',
'type' => 'NUMBER',
'max_length' => '11',
- 'default' => '0 ', // Why?
'nullable' => false,
+ 'default' => '0 ', // Why?
],
];
@@ -1289,8 +1289,18 @@ public function testModifyColumnRename(): void
'unsigned' => false,
'auto_increment' => true,
],
+ 'int' => [
+ 'type' => 'INT',
+ 'constraint' => 10,
+ 'null' => false,
+ ],
+ 'varchar' => [
+ 'type' => 'VARCHAR',
+ 'constraint' => 7,
+ 'null' => false,
+ ],
'name' => [
- 'type' => 'varchar',
+ 'type' => 'VARCHAR',
'constraint' => 255,
'null' => true,
],
@@ -1304,7 +1314,7 @@ public function testModifyColumnRename(): void
$this->forge->modifyColumn('forge_test_three', [
'name' => [
'name' => 'altered',
- 'type' => 'varchar',
+ 'type' => 'VARCHAR',
'constraint' => 255,
'null' => true,
],
@@ -1312,9 +1322,23 @@ public function testModifyColumnRename(): void
$this->db->resetDataCache();
+ $fieldData = $this->db->getFieldData('forge_test_three');
+ $fields = [];
+
+ foreach ($fieldData as $obj) {
+ $fields[$obj->name] = $obj;
+ }
+
$this->assertFalse($this->db->fieldExists('name', 'forge_test_three'));
$this->assertTrue($this->db->fieldExists('altered', 'forge_test_three'));
+ $this->assertTrue($fields['altered']->nullable);
+ $this->assertFalse($fields['int']->nullable);
+ $this->assertFalse($fields['varchar']->nullable);
+ $this->assertNull($fields['altered']->default);
+ $this->assertNull($fields['int']->default);
+ $this->assertNull($fields['varchar']->default);
+
$this->forge->dropTable('forge_test_three', true);
}
diff --git a/tests/system/Database/Live/MySQLi/GetFieldDataTest.php b/tests/system/Database/Live/MySQLi/GetFieldDataTest.php
index aacc7fa84b99..93c1bddc9183 100644
--- a/tests/system/Database/Live/MySQLi/GetFieldDataTest.php
+++ b/tests/system/Database/Live/MySQLi/GetFieldDataTest.php
@@ -46,70 +46,249 @@ private function isOldMySQL(): bool
);
}
- public function testGetFieldData(): void
+ public function testGetFieldDataDefault(): void
{
- $fields = $this->db->getFieldData('test1');
+ $this->createTableForDefault();
- $this->assertJsonStringEqualsJsonString(
- json_encode([
- (object) [
- 'name' => 'id',
- 'type' => 'int',
- 'max_length' => $this->isOldMySQL() ? 11 : null,
- 'default' => null, // The default value is not defined.
- 'primary_key' => 1,
- 'nullable' => false,
- ],
- (object) [
- 'name' => 'text_not_null',
- 'type' => 'varchar',
- 'max_length' => 64,
- 'default' => null, // The default value is not defined.
- 'primary_key' => 0,
- 'nullable' => false,
- ],
- (object) [
- 'name' => 'text_null',
- 'type' => 'varchar',
- 'max_length' => 64,
- 'default' => null, // The default value is not defined.
- 'primary_key' => 0,
- 'nullable' => true,
- ],
- (object) [
- 'name' => 'int_default_0',
- 'type' => 'int',
- 'max_length' => $this->isOldMySQL() ? 11 : null,
- 'default' => '0', // int 0
- 'primary_key' => 0,
- 'nullable' => false,
- ],
- (object) [
- 'name' => 'text_default_null',
- 'type' => 'varchar',
- 'max_length' => 64,
- 'default' => null, // NULL value
- 'primary_key' => 0,
- 'nullable' => true,
- ],
- (object) [
- 'name' => 'text_default_text_null',
- 'type' => 'varchar',
- 'max_length' => 64,
- 'default' => 'null', // string "null"
- 'primary_key' => 0,
- 'nullable' => false,
- ],
- (object) [
- 'name' => 'text_default_abc',
- 'type' => 'varchar',
- 'max_length' => 64,
- 'default' => 'abc', // string "abc"
- 'primary_key' => 0,
- 'nullable' => false,
- ],
- ]),
- json_encode($fields)
- );
+ $fields = $this->db->getFieldData($this->table);
+
+ $expected = [
+ (object) [
+ 'name' => 'id',
+ 'type' => 'int',
+ 'max_length' => $this->isOldMySQL() ? 11 : null,
+ 'nullable' => false,
+ 'default' => null, // The default value is not defined.
+ 'primary_key' => 1,
+ ],
+ (object) [
+ 'name' => 'text_not_null',
+ 'type' => 'varchar',
+ 'max_length' => 64,
+ 'nullable' => false,
+ 'default' => null, // The default value is not defined.
+ 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'text_null',
+ 'type' => 'varchar',
+ 'max_length' => 64,
+ 'nullable' => true,
+ 'default' => null, // The default value is not defined.
+ 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'int_default_0',
+ 'type' => 'int',
+ 'max_length' => $this->isOldMySQL() ? 11 : null,
+ 'nullable' => false,
+ 'default' => '0', // int 0
+ 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'text_default_null',
+ 'type' => 'varchar',
+ 'max_length' => 64,
+ 'nullable' => true,
+ 'default' => null, // NULL value
+ 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'text_default_text_null',
+ 'type' => 'varchar',
+ 'max_length' => 64,
+ 'nullable' => false,
+ 'default' => 'null', // string "null"
+ 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'text_default_abc',
+ 'type' => 'varchar',
+ 'max_length' => 64,
+ 'nullable' => false,
+ 'default' => 'abc', // string "abc"
+ 'primary_key' => 0,
+ ],
+ ];
+ $this->assertSameFieldData($expected, $fields);
+ }
+
+ public function testGetFieldDataType(): void
+ {
+ $this->createTableForType();
+
+ $fields = $this->db->getFieldData($this->table);
+
+ $expected = [
+ 0 => (object) [
+ 'name' => 'id',
+ 'type' => 'int',
+ 'max_length' => $this->isOldMySQL() ? 20 : null,
+ 'nullable' => false,
+ 'default' => null,
+ 'primary_key' => 1,
+ ],
+ 1 => (object) [
+ 'name' => 'type_varchar',
+ 'type' => 'varchar',
+ 'max_length' => 40,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 2 => (object) [
+ 'name' => 'type_char',
+ 'type' => 'char',
+ 'max_length' => 10,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 3 => (object) [
+ 'name' => 'type_text',
+ 'type' => 'text',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 4 => (object) [
+ 'name' => 'type_smallint',
+ 'type' => 'smallint',
+ 'max_length' => $this->isOldMySQL() ? 6 : null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 5 => (object) [
+ 'name' => 'type_integer',
+ 'type' => 'int',
+ 'max_length' => $this->isOldMySQL() ? 11 : null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 6 => (object) [
+ 'name' => 'type_float',
+ 'type' => 'float',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 7 => (object) [
+ 'name' => 'type_numeric',
+ 'type' => 'decimal',
+ 'max_length' => 18,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 8 => (object) [
+ 'name' => 'type_date',
+ 'type' => 'date',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 9 => (object) [
+ 'name' => 'type_time',
+ 'type' => 'time',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 10 => (object) [
+ 'name' => 'type_datetime',
+ 'type' => 'datetime',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 11 => (object) [
+ 'name' => 'type_timestamp',
+ 'type' => 'timestamp',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 12 => (object) [
+ 'name' => 'type_bigint',
+ 'type' => 'bigint',
+ 'max_length' => $this->isOldMySQL() ? 20 : null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 13 => (object) [
+ 'name' => 'type_real',
+ 'type' => 'double',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 14 => (object) [
+ 'name' => 'type_enum',
+ 'type' => 'enum',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 15 => (object) [
+ 'name' => 'type_set',
+ 'type' => 'set',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 16 => (object) [
+ 'name' => 'type_mediumtext',
+ 'type' => 'mediumtext',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 17 => (object) [
+ 'name' => 'type_double',
+ 'type' => 'double',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 18 => (object) [
+ 'name' => 'type_decimal',
+ 'type' => 'decimal',
+ 'max_length' => 18,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 19 => (object) [
+ 'name' => 'type_blob',
+ 'type' => 'blob',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 20 => (object) [
+ 'name' => 'type_boolean',
+ 'type' => 'tinyint',
+ 'max_length' => 1,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ ];
+ $this->assertSameFieldData($expected, $fields);
}
}
diff --git a/tests/system/Database/Live/OCI8/GetFieldDataTest.php b/tests/system/Database/Live/OCI8/GetFieldDataTest.php
new file mode 100644
index 000000000000..ec985252214f
--- /dev/null
+++ b/tests/system/Database/Live/OCI8/GetFieldDataTest.php
@@ -0,0 +1,281 @@
+
+ *
+ * For the full copyright and license information, please view
+ * the LICENSE file that was distributed with this source code.
+ */
+
+namespace CodeIgniter\Database\Live\OCI8;
+
+use CodeIgniter\Database\Live\AbstractGetFieldDataTest;
+use Config\Database;
+use LogicException;
+use stdClass;
+
+/**
+ * @group DatabaseLive
+ *
+ * @internal
+ */
+final class GetFieldDataTest extends AbstractGetFieldDataTest
+{
+ protected function createForge(): void
+ {
+ if ($this->db->DBDriver !== 'OCI8') {
+ $this->markTestSkipped('This test is only for OCI8.');
+ }
+
+ $this->forge = Database::forge($this->db);
+ }
+
+ private function getFieldMetaData(string $column, string $table): stdClass
+ {
+ $fields = $this->db->getFieldData($table);
+
+ $name = array_search(
+ $column,
+ array_column($fields, 'name'),
+ true
+ );
+
+ if ($name === false) {
+ throw new LogicException('Field not found: ' . $column);
+ }
+
+ return $fields[$name];
+ }
+
+ public function testGetFieldDataDefault(): void
+ {
+ $this->createTableForDefault();
+
+ $fields = $this->db->getFieldData($this->table);
+
+ $idDefault = $this->getFieldMetaData('id', $this->table)->default;
+ $this->assertMatchesRegularExpression('/"ORACLE"."ISEQ\$\$_[0-9]+".nextval/', $idDefault);
+
+ $expected = [
+ (object) [
+ 'name' => 'id',
+ 'type' => 'NUMBER',
+ 'max_length' => '11',
+ 'nullable' => false,
+ 'default' => $idDefault, // The default value is not defined.
+ // 'primary_key' => 1,
+ ],
+ (object) [
+ 'name' => 'text_not_null',
+ 'type' => 'VARCHAR2',
+ 'max_length' => '64',
+ 'nullable' => false,
+ 'default' => null, // The default value is not defined.
+ // 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'text_null',
+ 'type' => 'VARCHAR2',
+ 'max_length' => '64',
+ 'nullable' => true,
+ 'default' => null, // The default value is not defined.
+ // 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'int_default_0',
+ 'type' => 'NUMBER',
+ 'max_length' => '11',
+ 'nullable' => false,
+ 'default' => '0 ', // int 0
+ // 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'text_default_null',
+ 'type' => 'VARCHAR2',
+ 'max_length' => '64',
+ 'nullable' => true,
+ 'default' => 'NULL ', // NULL value
+ // 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'text_default_text_null',
+ 'type' => 'VARCHAR2',
+ 'max_length' => '64',
+ 'nullable' => false,
+ 'default' => "'null' ", // string "null"
+ // 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'text_default_abc',
+ 'type' => 'VARCHAR2',
+ 'max_length' => '64',
+ 'nullable' => false,
+ 'default' => "'abc' ", // string "abc"
+ // 'primary_key' => 0,
+ ],
+ ];
+ $this->assertSameFieldData($expected, $fields);
+ }
+
+ public function testGetFieldDataType(): void
+ {
+ $this->createTableForType();
+
+ $fields = $this->db->getFieldData($this->table);
+
+ $expected = [
+ 0 => (object) [
+ 'name' => 'id',
+ 'type' => 'NUMBER',
+ 'max_length' => '20',
+ 'nullable' => false,
+ 'default' => $this->getFieldMetaData('id', $this->table)->default,
+ ],
+ 1 => (object) [
+ 'name' => 'type_varchar',
+ 'type' => 'VARCHAR2',
+ 'max_length' => '40',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 2 => (object) [
+ 'name' => 'type_char',
+ 'type' => 'CHAR',
+ 'max_length' => '10',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 3 => (object) [
+ 'name' => 'type_text',
+ 'type' => 'VARCHAR2',
+ 'max_length' => '4000',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 4 => (object) [
+ 'name' => 'type_smallint',
+ 'type' => 'NUMBER',
+ 'max_length' => '5',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 5 => (object) [
+ 'name' => 'type_integer',
+ 'type' => 'NUMBER',
+ 'max_length' => '11',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 6 => (object) [
+ 'name' => 'type_float',
+ 'type' => 'FLOAT',
+ 'max_length' => '126',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 7 => (object) [
+ 'name' => 'type_numeric',
+ 'type' => 'NUMBER',
+ 'max_length' => '18',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 8 => (object) [
+ 'name' => 'type_date',
+ 'type' => 'DATE',
+ 'max_length' => '7',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 9 => (object) [
+ 'name' => 'type_time',
+ 'type' => 'DATE',
+ 'max_length' => '7',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 10 => (object) [
+ 'name' => 'type_datetime',
+ 'type' => 'DATE',
+ 'max_length' => '7',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 11 => (object) [
+ 'name' => 'type_timestamp',
+ 'type' => 'TIMESTAMP(6)',
+ 'max_length' => '11',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 12 => (object) [
+ 'name' => 'type_bigint',
+ 'type' => 'NUMBER',
+ 'max_length' => '19',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 13 => (object) [
+ 'name' => 'type_real',
+ 'type' => 'FLOAT',
+ 'max_length' => '63',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 14 => (object) [
+ 'name' => 'type_enum',
+ 'type' => 'VARCHAR2',
+ 'max_length' => '5',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 15 => (object) [
+ 'name' => 'type_set',
+ 'type' => 'VARCHAR2',
+ 'max_length' => '3',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 16 => (object) [
+ 'name' => 'type_mediumtext',
+ 'type' => 'VARCHAR2',
+ 'max_length' => '4000',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 17 => (object) [
+ 'name' => 'type_double',
+ 'type' => 'FLOAT',
+ 'max_length' => '126',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 18 => (object) [
+ 'name' => 'type_decimal',
+ 'type' => 'NUMBER',
+ 'max_length' => '18',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 19 => (object) [
+ 'name' => 'type_blob',
+ 'type' => 'BLOB',
+ 'max_length' => '4000',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 20 => (object) [
+ 'name' => 'type_boolean',
+ 'type' => 'NUMBER',
+ 'max_length' => '1',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ ];
+ $this->assertSameFieldData($expected, $fields);
+ }
+}
diff --git a/tests/system/Database/Live/Postgre/GetFieldDataTest.php b/tests/system/Database/Live/Postgre/GetFieldDataTest.php
index 64caac6126b3..7ff4d57b5bce 100644
--- a/tests/system/Database/Live/Postgre/GetFieldDataTest.php
+++ b/tests/system/Database/Live/Postgre/GetFieldDataTest.php
@@ -32,70 +32,193 @@ protected function createForge(): void
$this->forge = Database::forge($this->db);
}
- public function testGetFieldData(): void
+ public function testGetFieldDataDefault(): void
{
+ $this->createTableForDefault();
+
$fields = $this->db->getFieldData('test1');
- $this->assertJsonStringEqualsJsonString(
- json_encode([
- (object) [
- 'name' => 'id',
- 'type' => 'integer',
- 'max_length' => '32',
- 'default' => "nextval('db_test1_id_seq'::regclass)", // The default value is not defined.
- // 'primary_key' => 1,
- 'nullable' => false,
- ],
- (object) [
- 'name' => 'text_not_null',
- 'type' => 'character varying',
- 'max_length' => '64',
- 'default' => null, // The default value is not defined.
- // 'primary_key' => 0,
- 'nullable' => false,
- ],
- (object) [
- 'name' => 'text_null',
- 'type' => 'character varying',
- 'max_length' => '64',
- 'default' => null, // The default value is not defined.
- // 'primary_key' => 0,
- 'nullable' => true,
- ],
- (object) [
- 'name' => 'int_default_0',
- 'type' => 'integer',
- 'max_length' => '32',
- 'default' => '0', // int 0
- // 'primary_key' => 0,
- 'nullable' => false,
- ],
- (object) [
- 'name' => 'text_default_null',
- 'type' => 'character varying',
- 'max_length' => '64',
- 'default' => 'NULL::character varying', // NULL value
- // 'primary_key' => 0,
- 'nullable' => true,
- ],
- (object) [
- 'name' => 'text_default_text_null',
- 'type' => 'character varying',
- 'max_length' => '64',
- 'default' => "'null'::character varying", // string "null"
- // 'primary_key' => 0,
- 'nullable' => false,
- ],
- (object) [
- 'name' => 'text_default_abc',
- 'type' => 'character varying',
- 'max_length' => '64',
- 'default' => "'abc'::character varying", // string "abc"
- // 'primary_key' => 0,
- 'nullable' => false,
- ],
- ]),
- json_encode($fields)
- );
+ $expected = [
+ (object) [
+ 'name' => 'id',
+ 'type' => 'integer',
+ 'max_length' => '32',
+ 'nullable' => false,
+ // 'primary_key' => 1,
+ 'default' => "nextval('db_test1_id_seq'::regclass)", // The default value is not defined.
+ ],
+ (object) [
+ 'name' => 'text_not_null',
+ 'type' => 'character varying',
+ 'max_length' => '64',
+ 'nullable' => false,
+ // 'primary_key' => 0,
+ 'default' => null, // The default value is not defined.
+ ],
+ (object) [
+ 'name' => 'text_null',
+ 'type' => 'character varying',
+ 'max_length' => '64',
+ 'nullable' => true,
+ // 'primary_key' => 0,
+ 'default' => null, // The default value is not defined.
+ ],
+ (object) [
+ 'name' => 'int_default_0',
+ 'type' => 'integer',
+ 'max_length' => '32',
+ 'nullable' => false,
+ // 'primary_key' => 0,
+ 'default' => '0', // int 0
+ ],
+ (object) [
+ 'name' => 'text_default_null',
+ 'type' => 'character varying',
+ 'max_length' => '64',
+ 'nullable' => true,
+ // 'primary_key' => 0,
+ 'default' => 'NULL::character varying', // NULL value
+ ],
+ (object) [
+ 'name' => 'text_default_text_null',
+ 'type' => 'character varying',
+ 'max_length' => '64',
+ 'nullable' => false,
+ // 'primary_key' => 0,
+ 'default' => "'null'::character varying", // string "null"
+ ],
+ (object) [
+ 'name' => 'text_default_abc',
+ 'type' => 'character varying',
+ 'max_length' => '64',
+ 'nullable' => false,
+ // 'primary_key' => 0,
+ 'default' => "'abc'::character varying", // string "abc"
+ ],
+ ];
+ $this->assertSameFieldData($expected, $fields);
+ }
+
+ public function testGetFieldDataType(): void
+ {
+ $this->createTableForType();
+
+ $fields = $this->db->getFieldData($this->table);
+
+ $expected = [
+ 0 => (object) [
+ 'name' => 'id',
+ 'type' => 'integer',
+ 'max_length' => '32',
+ 'nullable' => false,
+ 'default' => 'nextval(\'db_test1_id_seq\'::regclass)',
+ ],
+ 1 => (object) [
+ 'name' => 'type_varchar',
+ 'type' => 'character varying',
+ 'max_length' => '40',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 2 => (object) [
+ 'name' => 'type_char',
+ 'type' => 'character',
+ 'max_length' => '10',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 3 => (object) [
+ 'name' => 'type_text',
+ 'type' => 'text',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 4 => (object) [
+ 'name' => 'type_smallint',
+ 'type' => 'smallint',
+ 'max_length' => '16',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 5 => (object) [
+ 'name' => 'type_integer',
+ 'type' => 'integer',
+ 'max_length' => '32',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 6 => (object) [
+ 'name' => 'type_float',
+ 'type' => 'double precision',
+ 'max_length' => '53',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 7 => (object) [
+ 'name' => 'type_numeric',
+ 'type' => 'numeric',
+ 'max_length' => '18',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 8 => (object) [
+ 'name' => 'type_date',
+ 'type' => 'date',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 9 => (object) [
+ 'name' => 'type_time',
+ 'type' => 'time without time zone',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 10 => (object) [
+ 'name' => 'type_datetime',
+ 'type' => 'timestamp without time zone',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 11 => (object) [
+ 'name' => 'type_timestamp',
+ 'type' => 'timestamp without time zone',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 12 => (object) [
+ 'name' => 'type_bigint',
+ 'type' => 'bigint',
+ 'max_length' => '64',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 13 => (object) [
+ 'name' => 'type_real',
+ 'type' => 'real',
+ 'max_length' => '24',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 14 => (object) [
+ 'name' => 'type_decimal',
+ 'type' => 'numeric',
+ 'max_length' => '18',
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 15 => (object) [
+ 'name' => 'type_boolean',
+ 'type' => 'boolean',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ ];
+ $this->assertSameFieldData($expected, $fields);
}
}
diff --git a/tests/system/Database/Live/SQLSRV/GetFieldDataTest.php b/tests/system/Database/Live/SQLSRV/GetFieldDataTest.php
index 07a41cd1467e..e6f924a28aba 100644
--- a/tests/system/Database/Live/SQLSRV/GetFieldDataTest.php
+++ b/tests/system/Database/Live/SQLSRV/GetFieldDataTest.php
@@ -32,70 +32,200 @@ protected function createForge(): void
$this->forge = Database::forge($this->db);
}
- public function testGetFieldData(): void
+ public function testGetFieldDataDefault(): void
{
- $fields = $this->db->getFieldData('test1');
+ $this->createTableForDefault();
- $this->assertJsonStringEqualsJsonString(
- json_encode([
- (object) [
- 'name' => 'id',
- 'type' => 'int',
- 'max_length' => 10,
- 'default' => null, // The default value is not defined.
- // 'primary_key' => 1,
- 'nullable' => false,
- ],
- (object) [
- 'name' => 'text_not_null',
- 'type' => 'varchar',
- 'max_length' => 64,
- 'default' => null, // The default value is not defined.
- // 'primary_key' => 0,
- 'nullable' => false,
- ],
- (object) [
- 'name' => 'text_null',
- 'type' => 'varchar',
- 'max_length' => 64,
- 'default' => null, // The default value is not defined.
- // 'primary_key' => 0,
- 'nullable' => true,
- ],
- (object) [
- 'name' => 'int_default_0',
- 'type' => 'int',
- 'max_length' => 10,
- 'default' => '((0))', // int 0
- // 'primary_key' => 0,
- 'nullable' => false,
- ],
- (object) [
- 'name' => 'text_default_null',
- 'type' => 'varchar',
- 'max_length' => 64,
- 'default' => '(NULL)', // NULL value
- // 'primary_key' => 0,
- 'nullable' => true,
- ],
- (object) [
- 'name' => 'text_default_text_null',
- 'type' => 'varchar',
- 'max_length' => 64,
- 'default' => "('null')", // string "null"
- // 'primary_key' => 0,
- 'nullable' => false,
- ],
- (object) [
- 'name' => 'text_default_abc',
- 'type' => 'varchar',
- 'max_length' => 64,
- 'default' => "('abc')", // string "abc"
- // 'primary_key' => 0,
- 'nullable' => false,
- ],
- ]),
- json_encode($fields)
- );
+ $fields = $this->db->getFieldData($this->table);
+
+ $expected = [
+ (object) [
+ 'name' => 'id',
+ 'type' => 'int',
+ 'max_length' => 10,
+ 'nullable' => false,
+ 'default' => null, // The default value is not defined.
+ // 'primary_key' => 1,
+ ],
+ (object) [
+ 'name' => 'text_not_null',
+ 'type' => 'varchar',
+ 'max_length' => 64,
+ 'nullable' => false,
+ 'default' => null, // The default value is not defined.
+ // 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'text_null',
+ 'type' => 'varchar',
+ 'max_length' => 64,
+ 'nullable' => true,
+ 'default' => null, // The default value is not defined.
+ // 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'int_default_0',
+ 'type' => 'int',
+ 'max_length' => 10,
+ 'nullable' => false,
+ 'default' => '((0))', // int 0
+ // 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'text_default_null',
+ 'type' => 'varchar',
+ 'max_length' => 64,
+ 'nullable' => true,
+ 'default' => '(NULL)', // NULL value
+ // 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'text_default_text_null',
+ 'type' => 'varchar',
+ 'max_length' => 64,
+ 'nullable' => false,
+ 'default' => "('null')", // string "null"
+ // 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'text_default_abc',
+ 'type' => 'varchar',
+ 'max_length' => 64,
+ 'nullable' => false,
+ 'default' => "('abc')", // string "abc"
+ // 'primary_key' => 0,
+ ],
+ ];
+ $this->assertSameFieldData($expected, $fields);
+ }
+
+ public function testGetFieldDataType(): void
+ {
+ $this->createTableForType();
+
+ $fields = $this->db->getFieldData($this->table);
+
+ $expected = [
+ 0 => (object) [
+ 'name' => 'id',
+ 'type' => 'int',
+ 'max_length' => 10,
+ 'nullable' => false,
+ 'default' => null,
+ ],
+ 1 => (object) [
+ 'name' => 'type_varchar',
+ 'type' => 'varchar',
+ 'max_length' => 40,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 2 => (object) [
+ 'name' => 'type_char',
+ 'type' => 'char',
+ 'max_length' => 10,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 3 => (object) [
+ 'name' => 'type_text',
+ 'type' => 'text',
+ 'max_length' => 2_147_483_647,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 4 => (object) [
+ 'name' => 'type_smallint',
+ 'type' => 'smallint',
+ 'max_length' => 5,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 5 => (object) [
+ 'name' => 'type_integer',
+ 'type' => 'int',
+ 'max_length' => 10,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 6 => (object) [
+ 'name' => 'type_float',
+ 'type' => 'float',
+ 'max_length' => 53,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 7 => (object) [
+ 'name' => 'type_numeric',
+ 'type' => 'numeric',
+ 'max_length' => 18,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 8 => (object) [
+ 'name' => 'type_date',
+ 'type' => 'date',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 9 => (object) [
+ 'name' => 'type_time',
+ 'type' => 'time',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 10 => (object) [
+ 'name' => 'type_datetime',
+ 'type' => 'datetime',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 11 => (object) [
+ 'name' => 'type_timestamp',
+ 'type' => 'datetime',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 12 => (object) [
+ 'name' => 'type_bigint',
+ 'type' => 'bigint',
+ 'max_length' => 19,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 13 => (object) [
+ 'name' => 'type_real',
+ 'type' => 'real',
+ 'max_length' => 24,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 14 => (object) [
+ 'name' => 'type_enum',
+ 'type' => 'text',
+ 'max_length' => 2_147_483_647,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 15 => (object) [
+ 'name' => 'type_decimal',
+ 'type' => 'decimal',
+ 'max_length' => 18,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ 16 => (object) [
+ 'name' => 'type_boolean',
+ 'type' => 'bit',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ ],
+ ];
+ $this->assertSameFieldData($expected, $fields);
}
}
diff --git a/tests/system/Database/Live/SQLite3/AlterTableTest.php b/tests/system/Database/Live/SQLite3/AlterTableTest.php
index 4a06a8ada091..a738484009c1 100644
--- a/tests/system/Database/Live/SQLite3/AlterTableTest.php
+++ b/tests/system/Database/Live/SQLite3/AlterTableTest.php
@@ -89,17 +89,17 @@ public function testFromTableFillsDetails(): void
$this->assertCount(5, $fields);
$this->assertArrayHasKey('id', $fields);
- $this->assertNull($fields['id']['default']);
+ $this->assertArrayNotHasKey('default', $fields['id']);
$this->assertTrue($fields['id']['null']);
$this->assertSame('integer', strtolower($fields['id']['type']));
$this->assertArrayHasKey('name', $fields);
- $this->assertNull($fields['name']['default']);
+ $this->assertArrayNotHasKey('default', $fields['name']);
$this->assertFalse($fields['name']['null']);
$this->assertSame('varchar', strtolower($fields['name']['type']));
$this->assertArrayHasKey('email', $fields);
- $this->assertNull($fields['email']['default']);
+ $this->assertArrayNotHasKey('default', $fields['email']);
$this->assertTrue($fields['email']['null']);
$this->assertSame('varchar', strtolower($fields['email']['type']));
diff --git a/tests/system/Database/Live/SQLite3/ForgeModifyColumnTest.php b/tests/system/Database/Live/SQLite3/ForgeModifyColumnTest.php
new file mode 100644
index 000000000000..1237cb6da28e
--- /dev/null
+++ b/tests/system/Database/Live/SQLite3/ForgeModifyColumnTest.php
@@ -0,0 +1,115 @@
+
+ *
+ * For the full copyright and license information, please view
+ * the LICENSE file that was distributed with this source code.
+ */
+
+namespace CodeIgniter\Database\Live\SQLite3;
+
+use CodeIgniter\Database\Forge;
+use CodeIgniter\Test\CIUnitTestCase;
+use Config\Database;
+
+/**
+ * @group DatabaseLive
+ *
+ * @internal
+ */
+final class ForgeModifyColumnTest extends CIUnitTestCase
+{
+ private Forge $forge;
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->db = Database::connect($this->DBGroup);
+
+ if ($this->db->DBDriver !== 'SQLite3') {
+ $this->markTestSkipped('This test is only for SQLite3.');
+ }
+
+ $this->forge = Database::forge($this->DBGroup);
+ }
+
+ public function testModifyColumnRename(): void
+ {
+ $table = 'forge_test_three';
+
+ $this->forge->dropTable($table, true);
+
+ $this->forge->addField([
+ 'id' => [
+ 'type' => 'INTEGER',
+ 'constraint' => 11,
+ 'auto_increment' => true,
+ ],
+ 'int' => [
+ 'type' => 'INT',
+ 'constraint' => 10,
+ 'null' => false,
+ 'default' => 0,
+ ],
+ 'varchar' => [
+ 'type' => 'VARCHAR',
+ 'constraint' => 7,
+ 'null' => false,
+ ],
+ 'decimal' => [
+ 'type' => 'DECIMAL',
+ 'constraint' => '10,5',
+ 'default' => 0.1,
+ ],
+ 'name' => [
+ 'type' => 'VARCHAR',
+ 'constraint' => 255,
+ 'null' => true,
+ ],
+ ]);
+
+ $this->forge->addKey('id', true);
+ $this->forge->createTable($table);
+
+ $this->assertTrue($this->db->fieldExists('name', $table));
+
+ $this->forge->modifyColumn($table, [
+ 'name' => [
+ 'name' => 'altered',
+ 'type' => 'VARCHAR',
+ 'constraint' => 255,
+ 'null' => true,
+ ],
+ ]);
+
+ $this->db->resetDataCache();
+
+ $fieldData = $this->db->getFieldData($table);
+ $fields = [];
+
+ foreach ($fieldData as $obj) {
+ $fields[$obj->name] = $obj;
+ }
+
+ $this->assertFalse($this->db->fieldExists('name', $table));
+ $this->assertTrue($this->db->fieldExists('altered', $table));
+
+ $this->assertFalse($fields['int']->nullable);
+ $this->assertSame('0', $fields['int']->default);
+
+ $this->assertFalse($fields['varchar']->nullable);
+ $this->assertNull($fields['varchar']->default);
+
+ $this->assertFalse($fields['decimal']->nullable);
+ $this->assertSame('0.1', $fields['decimal']->default);
+
+ $this->assertTrue($fields['altered']->nullable);
+ $this->assertNull($fields['altered']->default);
+
+ $this->forge->dropTable($table, true);
+ }
+}
diff --git a/tests/system/Database/Live/SQLite3/GetFieldDataTest.php b/tests/system/Database/Live/SQLite3/GetFieldDataTest.php
index 70586f10fbdd..319706a90e53 100644
--- a/tests/system/Database/Live/SQLite3/GetFieldDataTest.php
+++ b/tests/system/Database/Live/SQLite3/GetFieldDataTest.php
@@ -36,70 +36,306 @@ protected function createForge(): void
$this->forge = Database::forge($config);
}
- public function testGetFieldData(): void
+ public function testGetFieldDataDefault(): void
{
- $fields = $this->db->getFieldData('test1');
-
- $this->assertJsonStringEqualsJsonString(
- json_encode([
- (object) [
- 'name' => 'id',
- 'type' => 'INTEGER',
- 'max_length' => null,
- 'default' => null, // The default value is not defined.
- 'primary_key' => true,
- 'nullable' => true,
- ],
- (object) [
- 'name' => 'text_not_null',
- 'type' => 'VARCHAR',
- 'max_length' => null,
- 'default' => null, // The default value is not defined.
- 'primary_key' => false,
- 'nullable' => false,
- ],
- (object) [
- 'name' => 'text_null',
- 'type' => 'VARCHAR',
- 'max_length' => null,
- 'default' => null, // The default value is not defined.
- 'primary_key' => false,
- 'nullable' => true,
- ],
- (object) [
- 'name' => 'int_default_0',
- 'type' => 'INT',
- 'max_length' => null,
- 'default' => '0', // int 0
- 'primary_key' => false,
- 'nullable' => false,
- ],
- (object) [
- 'name' => 'text_default_null',
- 'type' => 'VARCHAR',
- 'max_length' => null,
- 'default' => 'NULL', // NULL value
- 'primary_key' => false,
- 'nullable' => true,
- ],
- (object) [
- 'name' => 'text_default_text_null',
- 'type' => 'VARCHAR',
- 'max_length' => null,
- 'default' => "'null'", // string "null"
- 'primary_key' => false,
- 'nullable' => false,
- ],
- (object) [
- 'name' => 'text_default_abc',
- 'type' => 'VARCHAR',
- 'max_length' => null,
- 'default' => "'abc'", // string "abc"
- 'primary_key' => false,
- 'nullable' => false,
- ],
- ]),
- json_encode($fields)
- );
+ $this->createTableForDefault();
+
+ $fields = $this->db->getFieldData($this->table);
+
+ $expected = [
+ (object) [
+ 'name' => 'id',
+ 'type' => 'INTEGER',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null, // The default value is not defined.
+ 'primary_key' => 1,
+ ],
+ (object) [
+ 'name' => 'text_not_null',
+ 'type' => 'VARCHAR',
+ 'max_length' => null,
+ 'nullable' => false,
+ 'default' => null, // The default value is not defined.
+ 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'text_null',
+ 'type' => 'VARCHAR',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null, // The default value is not defined.
+ 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'int_default_0',
+ 'type' => 'INT',
+ 'max_length' => null,
+ 'nullable' => false,
+ 'default' => '0', // int 0
+ 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'text_default_null',
+ 'type' => 'VARCHAR',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => 'NULL', // NULL value
+ 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'text_default_text_null',
+ 'type' => 'VARCHAR',
+ 'max_length' => null,
+ 'nullable' => false,
+ 'default' => "'null'", // string "null"
+ 'primary_key' => 0,
+ ],
+ (object) [
+ 'name' => 'text_default_abc',
+ 'type' => 'VARCHAR',
+ 'max_length' => null,
+ 'nullable' => false,
+ 'default' => "'abc'", // string "abc"
+ 'primary_key' => 0,
+ ],
+ ];
+ $this->assertSameFieldData($expected, $fields);
+ }
+
+ protected function createTableCompositePrimaryKey()
+ {
+ $this->forge->dropTable($this->table, true);
+
+ $this->forge->addField([
+ 'pk1' => [
+ 'type' => 'VARCHAR',
+ 'constraint' => 64,
+ ],
+ 'pk2' => [
+ 'type' => 'VARCHAR',
+ 'constraint' => 64,
+ ],
+ 'text' => [
+ 'type' => 'VARCHAR',
+ 'constraint' => 64,
+ ],
+ ]);
+ $this->forge->addPrimaryKey(['pk1', 'pk2']);
+ $this->forge->createTable($this->table);
+ }
+
+ public function testGetFieldDataCompositePrimaryKey(): void
+ {
+ $this->createTableCompositePrimaryKey();
+
+ $fields = $this->db->getFieldData($this->table);
+
+ $expected = [
+ (object) [
+ 'name' => 'pk1',
+ 'type' => 'VARCHAR',
+ 'max_length' => null,
+ 'nullable' => false,
+ 'default' => null,
+ 'primary_key' => 1,
+ ],
+ (object) [
+ 'name' => 'pk2',
+ 'type' => 'VARCHAR',
+ 'max_length' => null,
+ 'nullable' => false,
+ 'default' => null,
+ 'primary_key' => 1,
+ ],
+ (object) [
+ 'name' => 'text',
+ 'type' => 'VARCHAR',
+ 'max_length' => null,
+ 'nullable' => false,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ ];
+ $this->assertSameFieldData($expected, $fields);
+ }
+
+ public function testGetFieldDataType(): void
+ {
+ $this->createTableForType();
+
+ $fields = $this->db->getFieldData($this->table);
+
+ $expected = [
+ 0 => (object) [
+ 'name' => 'id',
+ 'type' => 'INTEGER',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 1,
+ ],
+ 1 => (object) [
+ 'name' => 'type_varchar',
+ 'type' => 'VARCHAR',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 2 => (object) [
+ 'name' => 'type_char',
+ 'type' => 'CHAR',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 3 => (object) [
+ 'name' => 'type_text',
+ 'type' => 'TEXT',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 4 => (object) [
+ 'name' => 'type_smallint',
+ 'type' => 'SMALLINT',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 5 => (object) [
+ 'name' => 'type_integer',
+ 'type' => 'INTEGER',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 6 => (object) [
+ 'name' => 'type_float',
+ 'type' => 'FLOAT',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 7 => (object) [
+ 'name' => 'type_numeric',
+ 'type' => 'NUMERIC',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 8 => (object) [
+ 'name' => 'type_date',
+ 'type' => 'DATE',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 9 => (object) [
+ 'name' => 'type_time',
+ 'type' => 'TIME',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 10 => (object) [
+ 'name' => 'type_datetime',
+ 'type' => 'DATETIME',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 11 => (object) [
+ 'name' => 'type_timestamp',
+ 'type' => 'TIMESTAMP',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 12 => (object) [
+ 'name' => 'type_bigint',
+ 'type' => 'BIGINT',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 13 => (object) [
+ 'name' => 'type_real',
+ 'type' => 'REAL',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 14 => (object) [
+ 'name' => 'type_enum',
+ 'type' => 'TEXT',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 15 => (object) [
+ 'name' => 'type_set',
+ 'type' => 'TEXT',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 16 => (object) [
+ 'name' => 'type_mediumtext',
+ 'type' => 'MEDIUMTEXT',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 17 => (object) [
+ 'name' => 'type_double',
+ 'type' => 'DOUBLE',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 18 => (object) [
+ 'name' => 'type_decimal',
+ 'type' => 'DECIMAL',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 19 => (object) [
+ 'name' => 'type_blob',
+ 'type' => 'BLOB',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ 20 => (object) [
+ 'name' => 'type_boolean',
+ 'type' => 'INT',
+ 'max_length' => null,
+ 'nullable' => true,
+ 'default' => null,
+ 'primary_key' => 0,
+ ],
+ ];
+ $this->assertSameFieldData($expected, $fields);
}
}
diff --git a/tests/system/Database/Live/UpdateTest.php b/tests/system/Database/Live/UpdateTest.php
index f8b0ced088db..fe270ac53da8 100644
--- a/tests/system/Database/Live/UpdateTest.php
+++ b/tests/system/Database/Live/UpdateTest.php
@@ -125,7 +125,7 @@ public function testUpdateBatch(string $constraints, array $data, array $expecte
for ($i = 1; $i < 4; $i++) {
$builder->insert([
'type_varchar' => 'test' . $i,
- 'type_char' => 'char',
+ 'type_char' => 'char' . $i,
'type_text' => 'text',
'type_smallint' => 32767,
'type_integer' => 2_147_483_647,
@@ -159,6 +159,7 @@ public static function provideUpdateBatch(): iterable
[
[
'type_varchar' => 'test1', // Key
+ 'type_char' => 'updated',
'type_text' => 'updated',
'type_smallint' => 9999,
'type_integer' => 9_999_999,
@@ -170,6 +171,7 @@ public static function provideUpdateBatch(): iterable
],
[
'type_varchar' => 'test2', // Key
+ 'type_char' => 'updated',
'type_text' => 'updated',
'type_smallint' => 9999,
'type_integer' => 9_999_999,
@@ -183,6 +185,7 @@ public static function provideUpdateBatch(): iterable
[
[
'type_varchar' => 'test1',
+ 'type_char' => 'updated',
'type_text' => 'updated',
'type_smallint' => 9999,
'type_integer' => 9_999_999,
@@ -193,6 +196,7 @@ public static function provideUpdateBatch(): iterable
],
[
'type_varchar' => 'test2',
+ 'type_char' => 'updated',
'type_text' => 'updated',
'type_smallint' => 9999,
'type_integer' => 9_999_999,
diff --git a/tests/system/HTTP/CURLRequestTest.php b/tests/system/HTTP/CURLRequestTest.php
index 4893266e5009..3115f86aedbb 100644
--- a/tests/system/HTTP/CURLRequestTest.php
+++ b/tests/system/HTTP/CURLRequestTest.php
@@ -1109,4 +1109,30 @@ public function testUserAgentOption(): void
$this->assertArrayHasKey(CURLOPT_USERAGENT, $options);
$this->assertSame($agent, $options[CURLOPT_USERAGENT]);
}
+
+ /**
+ * @see https://github.com/codeigniter4/CodeIgniter4/issues/8347
+ */
+ public function testMultipleHTTP100(): void
+ {
+ $jsonBody = '{"name":"John Doe","age":30}';
+
+ $output = "HTTP/1.1 100 Continue
+Mark bundle as not supporting multiuse
+HTTP/1.1 100 Continue
+Mark bundle as not supporting multiuse
+HTTP/1.1 200 OK
+Server: Werkzeug/2.2.2 Python/3.7.17
+Date: Sun, 28 Jan 2024 06:05:36 GMT
+Content-Type: application/json
+Content-Length: 33\r\n\r\n" . $jsonBody;
+
+ $this->request->setOutput($output);
+
+ $response = $this->request->request('GET', 'http://example.com');
+
+ $this->assertSame($jsonBody, $response->getBody());
+
+ $this->assertSame(200, $response->getStatusCode());
+ }
}
diff --git a/tests/system/I18n/TimeTest.php b/tests/system/I18n/TimeTest.php
index ac2eb4facd61..d60fb4410a03 100644
--- a/tests/system/I18n/TimeTest.php
+++ b/tests/system/I18n/TimeTest.php
@@ -259,17 +259,21 @@ public function testCreateFromFormatWithInvalidFormat(): void
public function testCreateFromTimestamp(): void
{
- // Set the timezone temporarily to UTC to make sure the test timestamp is correct
+ // Save the current timezone.
$tz = date_default_timezone_get();
- date_default_timezone_set('UTC');
- $timestamp = strtotime('2017-03-18 midnight');
+ // Change the timezone other than UTC.
+ date_default_timezone_set('Asia/Tokyo'); // +09:00
- date_default_timezone_set($tz);
+ $timestamp = strtotime('2017-03-18 midnight');
$time = Time::createFromTimestamp($timestamp);
- $this->assertSame(date('2017-03-18 00:00:00'), $time->toDateTimeString());
+ $this->assertSame('Asia/Tokyo', $time->getTimezone()->getName());
+ $this->assertSame('2017-03-18 00:00:00', $time->format('Y-m-d H:i:s'));
+
+ // Restore timezone.
+ date_default_timezone_set($tz);
}
public function testCreateFromTimestampWithTimezone(): void
diff --git a/tests/system/Images/ImageMagickHandlerTest.php b/tests/system/Images/ImageMagickHandlerTest.php
index 69a5df6e687c..b313fef96275 100644
--- a/tests/system/Images/ImageMagickHandlerTest.php
+++ b/tests/system/Images/ImageMagickHandlerTest.php
@@ -30,6 +30,8 @@
* @internal
*
* @group Others
+ *
+ * @requires extension imagick
*/
final class ImageMagickHandlerTest extends CIUnitTestCase
{
@@ -40,10 +42,6 @@ final class ImageMagickHandlerTest extends CIUnitTestCase
protected function setUp(): void
{
- if (! extension_loaded('imagick')) {
- $this->markTestSkipped('The ImageMagick extension is not available.');
- }
-
$this->root = WRITEPATH . 'cache/';
// cleanup everything
@@ -55,11 +53,28 @@ protected function setUp(): void
$this->path = $this->origin . 'ci-logo.png';
- $handlerConfig = new Images();
- if (is_file('/usr/bin/convert')) {
- $handlerConfig->libraryPath = '/usr/bin/convert';
+ // get our locally available `convert`
+ $config = new Images();
+ $found = false;
+
+ foreach ([
+ '/usr/bin/convert',
+ trim((string) shell_exec('which convert')),
+ $config->libraryPath,
+ ] as $convert) {
+ if (is_file($convert)) {
+ $config->libraryPath = $convert;
+
+ $found = true;
+ break;
+ }
+ }
+
+ if (! $found) {
+ $this->markTestSkipped('Cannot test imagick as there is no available convert program.');
}
- $this->handler = Services::image('imagick', $handlerConfig, false);
+
+ $this->handler = Services::image('imagick', $config, false);
}
public function testGetVersion(): void
diff --git a/user_guide_src/source/changelogs/index.rst b/user_guide_src/source/changelogs/index.rst
index a8dc37b18aa6..2409b869c420 100644
--- a/user_guide_src/source/changelogs/index.rst
+++ b/user_guide_src/source/changelogs/index.rst
@@ -12,6 +12,7 @@ See all the changes.
.. toctree::
:titlesonly:
+ v4.4.6
v4.4.5
v4.4.4
v4.4.3
diff --git a/user_guide_src/source/changelogs/v4.4.6.rst b/user_guide_src/source/changelogs/v4.4.6.rst
new file mode 100644
index 000000000000..01096133bbc4
--- /dev/null
+++ b/user_guide_src/source/changelogs/v4.4.6.rst
@@ -0,0 +1,38 @@
+#############
+Version 4.4.6
+#############
+
+Release Date: February 24, 2024
+
+**4.4.6 release of CodeIgniter4**
+
+.. contents::
+ :local:
+ :depth: 3
+
+********
+BREAKING
+********
+
+Time::createFromTimestamp()
+===========================
+
+A bug that caused :ref:`Time::createFromTimestamp() `
+to return a Time instance with a timezone of UTC has been fixed.
+
+Starting with this version, when you do not specify a timezone, a Time instance
+with the app's timezone is returned by default.
+
+**********
+Bugs Fixed
+**********
+
+- **Session:** Fixed a bug in Redis session handler that caused locking to fail
+ and the session data to be cleared.
+- **DB Forge:** Fixed a bug in SQLite3 Forge that caused ``Forge::modifyColumn()``
+ to incorrectly modify table definitions.
+- **CSP:** Fixed a bug that CSP blocked some elements in the Debug Toolbar.
+
+See the repo's
+`CHANGELOG.md `_
+for a complete list of bugs fixed.
diff --git a/user_guide_src/source/conf.py b/user_guide_src/source/conf.py
index 86a3517b9d43..122e54fad4e8 100644
--- a/user_guide_src/source/conf.py
+++ b/user_guide_src/source/conf.py
@@ -26,7 +26,7 @@
version = '4.4'
# The full version, including alpha/beta/rc tags.
-release = '4.4.5'
+release = '4.4.6'
# -- General configuration ---------------------------------------------------
diff --git a/user_guide_src/source/database/events.rst b/user_guide_src/source/database/events.rst
index 8cefcad22dbf..53d1246cfd36 100644
--- a/user_guide_src/source/database/events.rst
+++ b/user_guide_src/source/database/events.rst
@@ -15,14 +15,19 @@ uses this to collect the queries to display in the Toolbar.
The Events
**********
+.. _database-events-dbquery:
+
DBQuery
=======
-This event is triggered whenever a new query has been run, whether successful or not. The only parameter is
-a :doc:`Query ` instance of the current query. You could use this to display all queries
-in STDOUT, or logging to a file, or even creating tools to do automatic query analysis to help you spot
-potentially missing indexes, slow queries, etc.
+This event is triggered whenever a new query has been run, whether successful or
+not. The only parameter is a :doc:`Query ` instance of the
+current query.
+
+You could use this to display all queries in STDOUT, or logging to a file, or
+even creating tools to do automatic query analysis to help you spot potentially
+missing indexes, slow queries, etc.
-An example usage might be:
+An example to log all queries:
.. literalinclude:: events/001.php
diff --git a/user_guide_src/source/database/metadata.rst b/user_guide_src/source/database/metadata.rst
index 607a1025be8b..9ae08e5aae8d 100644
--- a/user_guide_src/source/database/metadata.rst
+++ b/user_guide_src/source/database/metadata.rst
@@ -94,23 +94,29 @@ Usage example:
.. literalinclude:: metadata/006.php
-If you have run a query already you can use the result object instead of
-supplying the table name:
-
-.. literalinclude:: metadata/007.php
-
The following data is available from this function if supported by your
database:
-- name - column name
-- type - the type of the column
-- max_length - maximum length of the column
-- primary_key - integer ``1`` if the column is a primary key (all integer ``1``, even if there are multiple primary keys), otherwise integer ``0`` (This field is currently only available for MySQL and SQLite3)
-- nullable - boolean ``true`` if the column is nullable, otherwise boolean ``false``
-- default - the default value
+- ``name`` - column name
+- ``type`` - the type of the column
+- ``max_length`` - maximum length of the column
+- ``nullable`` - boolean ``true`` if the column is nullable, otherwise boolean ``false``
+- ``default`` - the default value
+- ``primary_key`` - integer ``1`` if the column is a primary key (all integer ``1``, even if there are multiple primary keys), otherwise integer ``0`` (This field is currently only available for ``MySQLi`` and ``SQLite3``)
.. note:: Since v4.4.0, SQLSRV supported ``nullable``.
+$query->getFieldData()
+----------------------
+
+If you have run a query already you can use the result object instead of
+supplying the table name:
+
+.. literalinclude:: metadata/007.php
+
+.. note:: The data returned is different from the data from ``$db->getFieldData()``.
+ If you cannot get the data you need, use ``$db->getFieldData()``.
+
List the Indexes in a Table
===========================
diff --git a/user_guide_src/source/database/results/016.php b/user_guide_src/source/database/results/016.php
index f6d0e2325ce6..b3356729d39f 100644
--- a/user_guide_src/source/database/results/016.php
+++ b/user_guide_src/source/database/results/016.php
@@ -1,3 +1,3 @@
getCustomRowObject(0, \App\Entities\User::class);
+$row = $query->getRow(0, \App\Entities\User::class);
diff --git a/user_guide_src/source/general/common_functions.rst b/user_guide_src/source/general/common_functions.rst
index 75d2cbebd13c..46e433158d0d 100755
--- a/user_guide_src/source/general/common_functions.rst
+++ b/user_guide_src/source/general/common_functions.rst
@@ -197,7 +197,8 @@ Service Accessors
.. literalinclude:: common_functions/004.php
- For more details, see the :doc:`Views ` page.
+ For more details, see the :doc:`Views <../outgoing/views>` and
+ :doc:`../outgoing/view_renderer` page.
.. php:function:: view_cell($library[, $params = null[, $ttl = 0[, $cacheName = null]]])
@@ -382,7 +383,7 @@ Miscellaneous Functions
:returns: a route path (URI path relative to baseURL)
:rtype: string
- .. note:: This function requires the controller/method to have a route defined in **app/Config/routes.php**.
+ .. note:: This function requires the controller/method to have a route defined in **app/Config/Routes.php**.
.. important:: ``route_to()`` returns a *route* path, not a full URI path for your site.
If your **baseURL** contains sub folders, the return value is not the same
diff --git a/user_guide_src/source/general/configuration.rst b/user_guide_src/source/general/configuration.rst
index 45ecce84108d..f7d6c2d78d34 100644
--- a/user_guide_src/source/general/configuration.rst
+++ b/user_guide_src/source/general/configuration.rst
@@ -222,14 +222,15 @@ Since v4.1.5, you can also write with underscores::
Environment Variables as Replacements for Data
==============================================
-It is very important to always remember that environment variables contained in your **.env** are
-**only replacements for existing data**.
+It is very important to always remember that environment variables contained in
+your **.env** are **only replacements for existing scalar values**.
-Simply put, you can change only the property value that exists in the Config class
-by setting it in your **.env**.
+Simply put, you can change only the property's scalar value that exists in the
+Config class by setting it in your **.env**.
-You cannot add a property that is not defined in the Config class, nor can you
-change it to an array if the value of the defined property is a scalar.
+ 1. You cannot add a property that is not defined in the Config class.
+ 2. You cannot change a scalar value in a property to an array.
+ 3. You cannot add an element to an existing array.
For example, you cannot just put ``app.myNewConfig = foo`` in your **.env** and
expect your ``Config\App`` to magically have that property and value at run time.
diff --git a/user_guide_src/source/helpers/array_helper.rst b/user_guide_src/source/helpers/array_helper.rst
index 730dae957222..8a9c795d335f 100644
--- a/user_guide_src/source/helpers/array_helper.rst
+++ b/user_guide_src/source/helpers/array_helper.rst
@@ -15,6 +15,7 @@ Loading this Helper
This helper is loaded using the following code:
.. literalinclude:: array_helper/001.php
+ :lines: 2-
Available Functions
===================
@@ -29,24 +30,28 @@ The following functions are available:
:rtype: mixed
This method allows you to use dot-notation to search through an array for a specific-key,
- and allows the use of a the '*' wildcard. Given the following array:
+ and allows the use of a the ``*`` wildcard. Given the following array:
.. literalinclude:: array_helper/002.php
+ :lines: 2-
- We can locate the value of 'fizz' by using the search string "foo.buzz.fizz". Likewise, the value
- of baz can be found with "foo.bar.baz":
+ We can locate the value of ``fizz`` by using the search string ``foo.buzz.fizz``. Likewise, the value
+ of ``baz`` can be found with ``foo.bar.baz``:
.. literalinclude:: array_helper/003.php
+ :lines: 2-
- You can use the asterisk as a wildcard to replace any of the segments. When found, it will search through all
+ You can use the asterisk (``*``) as a wildcard to replace any of the segments. When found, it will search through all
of the child nodes until it finds it. This is handy if you don't know the values, or if your values
have a numeric index:
.. literalinclude:: array_helper/004.php
+ :lines: 2-
- If the array key contains a dot, then the key can be escaped with a backslash:
+ If the array key contains a dot (``.``), then the key can be escaped with a backslash (``\``):
.. literalinclude:: array_helper/005.php
+ :lines: 2-
.. note:: Prior to v4.2.0, ``dot_array_search('foo.bar.baz', ['foo' => ['bar' => 23]])`` returned ``23``
due to a bug. v4.2.0 and later returns ``null``.
@@ -73,21 +78,24 @@ The following functions are available:
from, e.g., the ``find()`` function of a model:
.. literalinclude:: array_helper/006.php
+ :lines: 2-
Now sort this array by two keys. Note that the method supports the dot-notation
to access values in deeper array levels, but does not support wildcards:
.. literalinclude:: array_helper/007.php
+ :lines: 2-
- The ``$players`` array is now sorted by the 'order' value in each players'
- 'team' subarray. If this value is equal for several players, these players
- will be ordered by their 'position'. The resulting array is:
+ The ``$players`` array is now sorted by the ``order`` value in each players'
+ ``team`` subarray. If this value is equal for several players, these players
+ will be ordered by their ``position``. The resulting array is:
.. literalinclude:: array_helper/008.php
+ :lines: 2-
In the same way, the method can also handle an array of objects. In the example
- above it is further possible that each 'player' is represented by an array,
- while the 'teams' are objects. The method will detect the type of elements in
+ above it is further possible that each ``player`` is represented by an array,
+ while the ``teams`` are objects. The method will detect the type of elements in
each nesting level and handle it accordingly.
.. php:function:: array_flatten_with_dots(iterable $array[, string $id = '']): array
@@ -101,16 +109,19 @@ The following functions are available:
as separators for the keys.
.. literalinclude:: array_helper/009.php
+ :lines: 2-
On inspection, ``$flattened`` is equal to:
.. literalinclude:: array_helper/010.php
+ :lines: 2-
Users may use the ``$id`` parameter on their own, but are not required to do so.
The function uses this parameter internally to track the flattened keys. If users
will be supplying an initial ``$id``, it will be prepended to all keys.
.. literalinclude:: array_helper/011.php
+ :lines: 2-
.. php:function:: array_group_by(array $array, array $indexes[, bool $includeEmpty = false]): array
@@ -126,12 +137,15 @@ The following functions are available:
The example shows some data (i.e. loaded from an API) with nested arrays.
.. literalinclude:: array_helper/012.php
-
- We want to group them first by "gender", then by "hr.department" (max depth = 2).
+ :lines: 2-
+
+ We want to group them first by ``gender``, then by ``hr.department`` (max depth = 2).
First the result when excluding empty values:
.. literalinclude:: array_helper/013.php
-
+ :lines: 2-
+
And here the same code, but this time we want to include empty values:
.. literalinclude:: array_helper/014.php
+ :lines: 2-
diff --git a/user_guide_src/source/helpers/url_helper.rst b/user_guide_src/source/helpers/url_helper.rst
index eec0c7067d54..9278539b3aea 100644
--- a/user_guide_src/source/helpers/url_helper.rst
+++ b/user_guide_src/source/helpers/url_helper.rst
@@ -20,9 +20,9 @@ The following functions are available:
.. php:function:: site_url([$uri = ''[, $protocol = null[, $altConfig = null]]])
- :param array|string $uri: URI string or array of URI segments
- :param string $protocol: Protocol, e.g., 'http' or 'https'
- :param \\Config\\App $altConfig: Alternate configuration to use
+ :param array|string $uri: URI string or array of URI segments.
+ :param string $protocol: Protocol, e.g., 'http' or 'https'. If empty string '' is set, a protocol-relative link is returned.
+ :param \\Config\\App $altConfig: Alternate configuration to use.
:returns: Site URL
:rtype: string
@@ -56,8 +56,8 @@ The following functions are available:
.. php:function:: base_url([$uri = ''[, $protocol = null]])
- :param array|string $uri: URI string or array of URI segments
- :param string $protocol: Protocol, e.g., 'http' or 'https'
+ :param array|string $uri: URI string or array of URI segments.
+ :param string $protocol: Protocol, e.g., 'http' or 'https'. If empty string '' is set, a protocol-relative link is returned.
:returns: Base URL
:rtype: string
@@ -83,6 +83,11 @@ The following functions are available:
The above example would return something like:
**http://example.com/blog/post/123**
+ If you pass an empty string ``''`` as the second parameter, it returns
+ the protocol-relative link:
+
+ .. literalinclude:: url_helper/026.php
+
This is useful because unlike :php:func:`site_url()`, you can supply a
string to a file, such as an image or stylesheet. For example:
@@ -377,7 +382,7 @@ The following functions are available:
:returns: Absolute URL
:rtype: string
- .. note:: This function requires the controller/method to have a route defined in **app/Config/routes.php**.
+ .. note:: This function requires the controller/method to have a route defined in **app/Config/Routes.php**.
Builds an absolute URL to a controller method in your app. Example:
diff --git a/user_guide_src/source/helpers/url_helper/004.php b/user_guide_src/source/helpers/url_helper/004.php
index e820085b3c59..8bc5192b803e 100644
--- a/user_guide_src/source/helpers/url_helper/004.php
+++ b/user_guide_src/source/helpers/url_helper/004.php
@@ -1,3 +1,4 @@
set404Override('App\Errors::show404');
-
-// Will display a custom view
-$routes->set404Override(static function () {
- echo view('my_errors/not_found.html');
-});
diff --git a/user_guide_src/source/incoming/routing/069.php b/user_guide_src/source/incoming/routing/069.php
new file mode 100644
index 000000000000..24d6366840a3
--- /dev/null
+++ b/user_guide_src/source/incoming/routing/069.php
@@ -0,0 +1,13 @@
+set404Override('App\Errors::show404');
+
+// Will display a custom view.
+$routes->set404Override(static function () {
+ // If you want to get the URI segments.
+ $segments = request()->getUri()->getSegments();
+
+ return view('my_errors/not_found.html');
+});
diff --git a/user_guide_src/source/installation/installing_composer.rst b/user_guide_src/source/installation/installing_composer.rst
index d1b4cebf33a7..5994c4d9c1a2 100644
--- a/user_guide_src/source/installation/installing_composer.rst
+++ b/user_guide_src/source/installation/installing_composer.rst
@@ -5,22 +5,23 @@ Composer Installation
:local:
:depth: 2
-Composer can be used in several ways to install CodeIgniter4 on your system.
+Composer can be used in two ways to install CodeIgniter4 on your system.
.. important:: CodeIgniter4 requires Composer 2.0.14 or later.
.. note:: If you are not familiar with Composer, we recommend you read
`Basic usage `_ first.
-The first technique describes creating a skeleton project
+The first technique describes creating a skeleton project (app starter)
using CodeIgniter4, that you would then use as the base for a new webapp.
The second technique described below lets you add CodeIgniter4 to an existing
webapp,
.. note:: If you are using a Git repository to store your code, or for
- collaboration with others, then the **vendor** folder would normally
- be "git ignored". In such a case, you will need to do a ``composer update``
- when you clone the repository to a new system.
+ collaboration with others, then the **vendor** folder would normally
+ be "git ignored". In such a case, you will need to do a ``composer install``
+ (or ``composer update`` if you want to update all Composer dependencies) when
+ you clone the repository to a new system.
App Starter
===========
@@ -139,11 +140,11 @@ Next Minor Version
If you want to use the next minor version branch, after using the ``builds`` command
edit **composer.json** manually.
-If you try the ``4.4`` branch, change the version to ``4.4.x-dev``::
+If you try the ``4.5`` branch, change the version to ``4.5.x-dev``::
"require": {
- "php": "^7.4 || ^8.0",
- "codeigniter4/codeigniter4": "4.4.x-dev"
+ "php": "^8.1",
+ "codeigniter4/codeigniter4": "4.5.x-dev"
},
And run ``composer update`` to sync your vendor
@@ -182,9 +183,9 @@ In your project root:
.. important:: When you deploy to your production server, don't forget to run the
following command:
-.. code-block:: console
+ .. code-block:: console
- composer install --no-dev
+ composer install --no-dev
The above command will remove the Composer packages only for development
that are not needed in the production environment. This will greatly reduce
diff --git a/user_guide_src/source/installation/running.rst b/user_guide_src/source/installation/running.rst
index 2538cfee2d9a..69f89cfbdbd0 100644
--- a/user_guide_src/source/installation/running.rst
+++ b/user_guide_src/source/installation/running.rst
@@ -49,9 +49,10 @@ Open the **app/Config/App.php** file with a text editor.
Configure Database Connection Settings
======================================
-If you intend to use a database, open the
-**app/Config/Database.php** file with a text editor and set your
-database settings. Alternately, these could be set in your **.env** file.
+If you intend to use a database, open the **app/Config/Database.php** file with
+a text editor and set your database settings. Alternately, these could be set in
+your **.env** file. See :ref:`Database Configuration `
+for details.
Set to Development Mode
=======================
@@ -425,6 +426,85 @@ Setting Environment
See :ref:`Handling Multiple Environments `.
+
+.. _deployment-to-shared-hosting-services:
+
+*************************************
+Deployment to Shared Hosting Services
+*************************************
+
+.. important::
+ **index.php** is no longer in the root of the project! It has been moved inside
+ the **public** folder, for better security and separation of components.
+
+ This means that you should configure your web server to "point" to your project's
+ **public** folder, and not to the project root.
+
+Specifying the Document Root
+============================
+
+The best way is to set the document root to the **public** folder in the server
+configuration::
+
+ └── example.com/ (project folder)
+ └── public/ (document root)
+
+Check with your hosting service provider to see if you can change the document root.
+Unfortunately, if you cannot change the document root, go to the next way.
+
+Using Two Directories
+=====================
+
+The second way is to use two directories, and adjust the path.
+One is for the application and the other is the default document root.
+
+Upload the contents of the **public** folder to **public_html** (the default
+document root) and the other files to the directory for the application::
+
+ ├── example.com/ (for the application)
+ │ ├── app/
+ │ ├── vendor/ (or system/)
+ │ └── writable/
+ └── public_html/ (the default document root)
+ ├── .htaccess
+ ├── favicon.ico
+ ├── index.php
+ └── robots.txt
+
+See
+`Install CodeIgniter 4 on Shared Hosting (cPanel) `_
+for details.
+
+Adding .htaccess
+================
+
+The last resort is to add **.htaccess** to the project root.
+
+It is not recommended that you place the project folder in the document root.
+However, if you have no other choice, you can use this.
+
+Place your project folder as follows, where **public_html** is the document root,
+and create the **.htaccess** file::
+
+ └── public_html/ (the default document root)
+ └── example.com/ (project folder)
+ ├── .htaccess
+ └── public/
+
+And edit **.htaccess** as follows:
+
+.. code-block:: apache
+
+
+ RewriteEngine On
+ RewriteRule ^(.*)$ public/$1 [L]
+
+
+
+ Require all denied
+ Satisfy All
+
+
*********************
Bootstrapping the App
*********************
diff --git a/user_guide_src/source/installation/troubleshooting.rst b/user_guide_src/source/installation/troubleshooting.rst
index 8ed12924732b..ed6db186e47d 100644
--- a/user_guide_src/source/installation/troubleshooting.rst
+++ b/user_guide_src/source/installation/troubleshooting.rst
@@ -102,9 +102,4 @@ After that, reload the page. You will see the error and the back trace.
CodeIgniter Error Logs
----------------------
-CodeIgniter logs error messages, according to the settings in **app/Config/Logger.php**.
-
-You can adjust the error threshold to see more or fewer messages. See :ref:`Logging ` for details.
-
-The default configuration has daily log files stored in **writable/logs**.
-It would be a good idea to check them if things aren't working the way you expect!
+See :ref:`codeigniter-error-logs`.
diff --git a/user_guide_src/source/installation/upgrade_446.rst b/user_guide_src/source/installation/upgrade_446.rst
new file mode 100644
index 000000000000..7e7f927b5f75
--- /dev/null
+++ b/user_guide_src/source/installation/upgrade_446.rst
@@ -0,0 +1,51 @@
+#############################
+Upgrading from 4.4.5 to 4.4.6
+#############################
+
+Please refer to the upgrade instructions corresponding to your installation method.
+
+- :ref:`Composer Installation App Starter Upgrading `
+- :ref:`Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading `
+- :ref:`Manual Installation Upgrading `
+
+.. contents::
+ :local:
+ :depth: 2
+
+****************
+Breaking Changes
+****************
+
+Time::createFromTimestamp() Timezone Change
+===========================================
+
+When you do not specify a timezone, now
+:ref:`Time::createFromTimestamp() ` returns a Time
+instance with the app's timezone is returned.
+
+If you want to keep the timezone UTC, you need to call ``setTimezone('UTC')``::
+
+ use CodeIgniter\I18n\Time;
+
+ $time = Time::createFromTimestamp(1501821586)->setTimezone('UTC');
+
+*************
+Project Files
+*************
+
+Some files in the **project space** (root, app, public, writable) received updates. Due to
+these files being outside of the **system** scope they will not be changed without your intervention.
+
+There are some third-party CodeIgniter modules available to assist with merging changes to
+the project space: `Explore on Packagist `_.
+
+All Changes
+===========
+
+This is a list of all files in the **project space** that received changes;
+many will be simple comments or formatting that have no effect on the runtime:
+
+- app/Config/App.php
+- app/Config/Routing.php
+- app/Views/welcome_message.php
+- composer.json
diff --git a/user_guide_src/source/installation/upgrade_4xx.rst b/user_guide_src/source/installation/upgrade_4xx.rst
index 8f9770e7fe3d..1253e783bb2f 100644
--- a/user_guide_src/source/installation/upgrade_4xx.rst
+++ b/user_guide_src/source/installation/upgrade_4xx.rst
@@ -47,6 +47,15 @@ Namespaces
Application Structure
=====================
+.. important::
+ **index.php** is no longer in the root of the project! It has been moved inside
+ the **public** folder, for better security and separation of components.
+
+ This means that you should configure your web server to "point" to your project's
+ **public** folder, and not to the project root.
+
+ If you would use Shared Hosting, see :ref:`deployment-to-shared-hosting-services`.
+
- The **application** folder is renamed as **app** and the framework still has **system** folders,
with the same interpretation as before.
- The framework now provides for a **public** folder, intended as the document root for your app.
diff --git a/user_guide_src/source/installation/upgrade_file_upload/001.php b/user_guide_src/source/installation/upgrade_file_upload/001.php
index c4c6d50db7b4..ca2c85372ca2 100644
--- a/user_guide_src/source/installation/upgrade_file_upload/001.php
+++ b/user_guide_src/source/installation/upgrade_file_upload/001.php
@@ -11,7 +11,7 @@ public function index()
public function do_upload()
{
- $this->validate([
+ $this->validateData([], [
'userfile' => [
'uploaded[userfile]',
'max_size[userfile,100]',
diff --git a/user_guide_src/source/installation/upgrade_validations.rst b/user_guide_src/source/installation/upgrade_validations.rst
index 8343b989a699..7f42cf8e1f70 100644
--- a/user_guide_src/source/installation/upgrade_validations.rst
+++ b/user_guide_src/source/installation/upgrade_validations.rst
@@ -32,9 +32,10 @@ Upgrade Guide
2. Within the controller you have to change the following:
- - ``$this->load->helper(array('form', 'url'));`` to ``helper(['form', 'url']);``
+ - ``$this->load->helper(array('form', 'url'));`` to ``helper('form');``
- remove the line ``$this->load->library('form_validation');``
- - ``if ($this->form_validation->run() == FALSE)`` to ``if (! $this->validate([]))``
+ - ``if ($this->form_validation->run() == FALSE)`` to ``if (! $this->validateData($data, $rules))``
+ where ``$data`` is the data to validate, typically, the POST data ``$this->request->getPost()``.
- ``$this->load->view('myform');`` to ``return view('myform', ['validation' => $this->validator,]);``
3. You have to change the validation rules. The new syntax is to set the rules as array in the controller:
diff --git a/user_guide_src/source/installation/upgrade_validations/001.php b/user_guide_src/source/installation/upgrade_validations/001.php
index 36ec03941532..0ba9b034708f 100644
--- a/user_guide_src/source/installation/upgrade_validations/001.php
+++ b/user_guide_src/source/installation/upgrade_validations/001.php
@@ -1,6 +1,6 @@
validate([
+$isValid = $this->validateData($data, [
'name' => 'required|min_length[3]',
'email' => 'required|valid_email',
'phone' => 'required|numeric|max_length[10]',
diff --git a/user_guide_src/source/installation/upgrade_validations/002.php b/user_guide_src/source/installation/upgrade_validations/002.php
index 38e81eb202d9..b3cd4f9db13d 100644
--- a/user_guide_src/source/installation/upgrade_validations/002.php
+++ b/user_guide_src/source/installation/upgrade_validations/002.php
@@ -8,9 +8,11 @@ class Form extends Controller
{
public function index()
{
- helper(['form', 'url']);
+ helper('form');
- if (! $this->validate([
+ $data = $this->request->getPost();
+
+ if (! $this->validateData($data, [
// Validation rules
])) {
return view('myform');
diff --git a/user_guide_src/source/installation/upgrading.rst b/user_guide_src/source/installation/upgrading.rst
index b11fbfc92cfb..2c23fdd025fa 100644
--- a/user_guide_src/source/installation/upgrading.rst
+++ b/user_guide_src/source/installation/upgrading.rst
@@ -16,6 +16,7 @@ See also :doc:`./backward_compatibility_notes`.
backward_compatibility_notes
+ upgrade_446
upgrade_445
upgrade_444
upgrade_443
diff --git a/user_guide_src/source/intro/index.rst b/user_guide_src/source/intro/index.rst
index 4daafc228876..69917b5d9067 100644
--- a/user_guide_src/source/intro/index.rst
+++ b/user_guide_src/source/intro/index.rst
@@ -2,20 +2,26 @@
Welcome to CodeIgniter4
#######################
-CodeIgniter is an Application Development Framework - a toolkit - for
+CodeIgniter is a PHP full-stack web framework that is light, fast, flexible and
+secure.
+
+It is an Application Development Framework - a toolkit - for
people who build web sites using PHP. Its goal is to enable you to
develop projects much faster than you could if you were writing code
from scratch, by providing a rich set of libraries for commonly needed
tasks, as well as a simple interface and logical structure to access
-these libraries. CodeIgniter lets you creatively focus on your project
-by minimizing the amount of code needed for a given task.
+these libraries.
+
+CodeIgniter lets you creatively focus on your project by minimizing the amount
+of code needed for a given task.
Where possible, CodeIgniter has been kept as flexible as possible,
allowing you to work in the way you want, not being forced into working
any certain way. The framework can have core parts easily extended
or completely replaced to make the system work the way you need it to.
-In short, CodeIgniter is the malleable framework that tries to provide
-the tools you need while staying out of the way.
+
+In short, CodeIgniter is the malleable framework that tries to provide the tools
+you need while staying out of the way.
*****************************
Is CodeIgniter Right for You?
@@ -25,12 +31,14 @@ CodeIgniter is right for you if:
- You want a framework with a small footprint.
- You need exceptional performance.
+- You want a framework that incorporates a number of features and techniques to
+ enable you to do good security practices easily.
+- You want a non-magical framework that you can easily find and read the source
+ code.
- You want a framework that requires nearly zero configuration.
-- You want a framework that does not require you to use the command
- line.
- You want a framework that does not require you to adhere to
restrictive coding rules.
-- You are not interested in large-scale monolithic libraries like PEAR.
+- You want a framework that makes it easy to create test code.
- You do not want to be forced to learn a templating language (although
a template parser is optionally available if you desire one).
- You eschew complexity, favoring simple solutions.
diff --git a/user_guide_src/source/intro/requirements.rst b/user_guide_src/source/intro/requirements.rst
index e025198a8cc0..a21f8b2a9cdc 100644
--- a/user_guide_src/source/intro/requirements.rst
+++ b/user_guide_src/source/intro/requirements.rst
@@ -56,7 +56,7 @@ Currently supported databases are:
- MySQL via the ``MySQLi`` driver (version 5.1 and above only)
- PostgreSQL via the ``Postgre`` driver (version 7.4 and above only)
- SQLite3 via the ``SQLite3`` driver
- - Microsoft SQL Server via the ``SQLSRV`` driver (version 2005 and above only)
+ - Microsoft SQL Server via the ``SQLSRV`` driver (version 2012 and above only)
- Oracle Database via the ``OCI8`` driver (version 12.1 and above only)
Not all of the drivers have been converted/rewritten for CodeIgniter4.
diff --git a/user_guide_src/source/libraries/cookies/004.php b/user_guide_src/source/libraries/cookies/004.php
index cce1ea2c726d..7804d276b2f6 100644
--- a/user_guide_src/source/libraries/cookies/004.php
+++ b/user_guide_src/source/libraries/cookies/004.php
@@ -22,7 +22,7 @@
$cookie->getName(); // 'remember_token'
$cookie->getPrefix(); // '__Secure-'
$cookie->getPrefixedName(); // '__Secure-remember_token'
-$cookie->getExpiresTimestamp(); // Unix timestamp
+$cookie->getExpiresTimestamp(); // UNIX timestamp
$cookie->getExpiresString(); // 'Fri, 14-Feb-2025 00:00:00 GMT'
$cookie->isExpired(); // false
$cookie->getMaxAge(); // the difference from time() to expires
diff --git a/user_guide_src/source/libraries/time.rst b/user_guide_src/source/libraries/time.rst
index 2175a8c681a3..668a7bebbe7c 100644
--- a/user_guide_src/source/libraries/time.rst
+++ b/user_guide_src/source/libraries/time.rst
@@ -114,6 +114,8 @@ and returns a ``Time`` instance, instead of DateTimeImmutable:
.. literalinclude:: time/011.php
+.. _time-createfromtimestamp:
+
createFromTimestamp()
=====================
@@ -121,6 +123,9 @@ This method takes a UNIX timestamp and, optionally, the timezone and locale, to
.. literalinclude:: time/012.php
+.. note:: Due to a bug, prior to v4.4.6, this method returned a Time instance
+ in timezone UTC when you do not specify a timezone.
+
createFromInstance()
====================
diff --git a/user_guide_src/source/libraries/uploaded_files/002.php b/user_guide_src/source/libraries/uploaded_files/002.php
index 44b33a854a61..af304c239186 100644
--- a/user_guide_src/source/libraries/uploaded_files/002.php
+++ b/user_guide_src/source/libraries/uploaded_files/002.php
@@ -27,7 +27,7 @@ public function upload()
],
],
];
- if (! $this->validate($validationRule)) {
+ if (! $this->validateData([], $validationRule)) {
$data = ['errors' => $this->validator->getErrors()];
return view('upload_form', $data);
diff --git a/user_guide_src/source/libraries/validation.rst b/user_guide_src/source/libraries/validation.rst
index 710cd4965dd8..c7c8449562a5 100644
--- a/user_guide_src/source/libraries/validation.rst
+++ b/user_guide_src/source/libraries/validation.rst
@@ -998,7 +998,7 @@ file upload related rules::
// In the controller
- $this->validate([
+ $this->validateData([], [
'avatar' => 'uploaded[avatar]|max_size[avatar,1024]',
]);
diff --git a/user_guide_src/source/libraries/validation/045.php b/user_guide_src/source/libraries/validation/045.php
index 1becb578dc21..401897f0e927 100644
--- a/user_guide_src/source/libraries/validation/045.php
+++ b/user_guide_src/source/libraries/validation/045.php
@@ -2,7 +2,7 @@
// In Controller.
-if (! $this->validate([
+if (! $this->validateData($data, [
'username' => 'required',
'password' => 'required|min_length[10]',
])) {
diff --git a/user_guide_src/source/models/entities.rst b/user_guide_src/source/models/entities.rst
index 072d41e85793..6e683dcafe0b 100644
--- a/user_guide_src/source/models/entities.rst
+++ b/user_guide_src/source/models/entities.rst
@@ -309,7 +309,7 @@ like ``type[param1, param2]``.
.. literalinclude:: entities/021.php
-.. note:: If the casting type is marked as nullable ``?bool`` and the passed value is not null, then the parameter with
+.. note:: If the casting type is marked as nullable like ``?bool`` and the passed value is not null, then the parameter with
the value ``nullable`` will be passed to the casting type handler.
If casting type has predefined parameters, then ``nullable`` will be added to the end of the list.
diff --git a/user_guide_src/source/models/entities/018.php b/user_guide_src/source/models/entities/018.php
index 2bb8750affe1..1469f5627138 100644
--- a/user_guide_src/source/models/entities/018.php
+++ b/user_guide_src/source/models/entities/018.php
@@ -6,7 +6,7 @@
class MyEntity extends Entity
{
- // Specifying the type for the field
+ // Specify the type for the field
protected $casts = [
'key' => 'base64',
];
diff --git a/user_guide_src/source/models/entities/020.php b/user_guide_src/source/models/entities/020.php
index 7d3708b044d8..bedf6d12f947 100644
--- a/user_guide_src/source/models/entities/020.php
+++ b/user_guide_src/source/models/entities/020.php
@@ -6,7 +6,7 @@
class MyEntity extends Entity
{
- // Defining a type with parameters
+ // Define a type with parameters
protected $casts = [
'some_attribute' => 'class[App\SomeClass, param2, param3]',
];
diff --git a/user_guide_src/source/models/model.rst b/user_guide_src/source/models/model.rst
index 74225cd7869c..d40c07ba6e6b 100644
--- a/user_guide_src/source/models/model.rst
+++ b/user_guide_src/source/models/model.rst
@@ -184,7 +184,7 @@ $dateFormat
This value works with `$useTimestamps`_ and `$useSoftDeletes`_ to ensure that the correct type of
date value gets inserted into the database. By default, this creates DATETIME values, but
-valid options are: ``'datetime'``, ``'date'``, or ``'int'`` (a PHP timestamp). Using `$useSoftDeletes`_ or
+valid options are: ``'datetime'``, ``'date'``, or ``'int'`` (a UNIX timestamp). Using `$useSoftDeletes`_ or
`$useTimestamps`_ with an invalid or missing `$dateFormat`_ will cause an exception.
$createdField
diff --git a/user_guide_src/source/outgoing/view_renderer.rst b/user_guide_src/source/outgoing/view_renderer.rst
index 618a90c4a516..06e15a809e00 100644
--- a/user_guide_src/source/outgoing/view_renderer.rst
+++ b/user_guide_src/source/outgoing/view_renderer.rst
@@ -116,11 +116,18 @@ View Renderer Options
Several options can be passed to the ``render()`` or ``renderString()`` methods:
-- ``cache`` - the time in seconds, to save a view's results; ignored for renderString()
-- ``cache_name`` - the ID used to save/retrieve a cached view result; defaults to the viewpath; ignored for ``renderString()``
-- ``saveData`` - true if the view data parameters should be retained for subsequent calls
-
-.. note:: ``saveData()`` as defined by the interface must be a boolean, but implementing
+- ``$options``
+
+ - ``cache`` - the time in seconds, to save a view's results; ignored for
+ ``renderString()``.
+ - ``cache_name`` - the ID used to save/retrieve a cached view result; defaults
+ to the ``$viewPath``; ignored for ``renderString()``.
+ - ``debug`` - can be set to false to disable the addition of debug code for
+ :ref:`Debug Toolbar `.
+- ``$saveData`` - true if the view data parameters should be retained for
+ subsequent calls.
+
+.. note:: ``$saveData`` as defined by the interface must be a boolean, but implementing
classes (like ``View`` below) may extend this to include ``null`` values.
***************
diff --git a/user_guide_src/source/outgoing/views.rst b/user_guide_src/source/outgoing/views.rst
index 70bbadcc7504..fabd811de87a 100644
--- a/user_guide_src/source/outgoing/views.rst
+++ b/user_guide_src/source/outgoing/views.rst
@@ -37,7 +37,8 @@ Then save the file in your **app/Views** directory.
Displaying a View
=================
-To load and display a particular view file you will use the following code in your controller:
+To load and display a particular view file you will use the :php:func:`view()`
+function like following code in your controller:
.. literalinclude:: views/001.php
:lines: 2-
@@ -64,8 +65,10 @@ If you visit your site, you should see your new view. The URL was similar to thi
Loading Multiple Views
======================
-CodeIgniter will intelligently handle multiple calls to ``view()`` from within a controller. If more than one
-call happens they will be appended together. For example, you may wish to have a header view, a menu view, a
+CodeIgniter will intelligently handle multiple calls to :php:func:`view()` from
+within a controller. If more than one call happens they will be appended together.
+
+For example, you may wish to have a header view, a menu view, a
content view, and a footer view. That might look something like this:
.. literalinclude:: views/003.php
@@ -101,8 +104,8 @@ example, you could load the **blog_view.php** file from **example/blog/Views** b
Caching Views
=============
-You can cache a view with the ``view()`` function by passing a ``cache`` option with the number of seconds to cache
-the view for, in the third parameter:
+You can cache a view with the :php:func:`view()` function by passing a ``cache``
+option with the number of seconds to cache the view for, in the third parameter:
.. literalinclude:: views/006.php
:lines: 2-
@@ -116,7 +119,9 @@ along ``cache_name`` and the cache ID you wish to use:
Adding Dynamic Data to the View
===============================
-Data is passed from the controller to the view by way of an array in the second parameter of the ``view()`` function.
+Data is passed from the controller to the view by way of an array in the second
+parameter of the :php:func:`view()` function.
+
Here's an example:
.. literalinclude:: views/008.php
@@ -142,8 +147,9 @@ Then load the page at the URL you've been using and you should see the variables
The saveData Option
-------------------
-The data passed in is retained for subsequent calls to ``view()``. If you call the function multiple times
-in a single request, you will not have to pass the desired data to each ``view()``.
+The data passed in is retained for subsequent calls to :php:func:`view()`. If you
+call the function multiple times in a single request, you will not have to pass
+the desired data to each ``view()``.
But this might not keep any data from "bleeding" into
other views, potentially causing issues. If you would prefer to clean the data after one call, you can pass the ``saveData`` option
diff --git a/user_guide_src/source/testing/controllers/001.php b/user_guide_src/source/testing/controllers/001.php
index 9162fa0aeabd..ae51187f949d 100644
--- a/user_guide_src/source/testing/controllers/001.php
+++ b/user_guide_src/source/testing/controllers/001.php
@@ -1,12 +1,12 @@
withURI('http://example.com/categories')
- ->controller(\App\Controllers\ForumController::class)
+ ->controller(ForumController::class)
->execute('showCategories');
$this->assertTrue($result->isOK());
diff --git a/user_guide_src/source/testing/controllers/011.php b/user_guide_src/source/testing/controllers/011.php
index f2ab558b8da2..e53aa42c8768 100644
--- a/user_guide_src/source/testing/controllers/011.php
+++ b/user_guide_src/source/testing/controllers/011.php
@@ -1,11 +1,11 @@
`_
-debugging tool for PHP. This goes way beyond your usual tool, providing many alternate pieces of data, like formatting
-timestamps into recognizable dates, showing you hexcodes as colors, display array data like a table for easy reading,
-and much, much more.
+.. _codeigniter-error-logs:
+
+CodeIgniter Error Logs
+======================
+
+CodeIgniter logs error messages, according to the settings in **app/Config/Logger.php**.
+
+The default configuration has daily log files stored in **writable/logs**.
+It would be a good idea to check them if things aren't working the way you expect!
+
+You can adjust the error threshold to see more or fewer messages. See
+:ref:`Logging ` for details.
+
+Logging All SQL Queries
+=======================
+
+All SQL queries issued by CodeIgniter can be logged.
+See :ref:`Database Events ` for details.
+
+********************
+Replacing var_dump()
+********************
+
+While using Xdebug and a good IDE can be indispensable to debug your application,
+sometimes a quick ``var_dump()`` is all you need. CodeIgniter makes that even
+better by bundling in the excellent `Kint `_
+debugging tool for PHP.
+
+This goes way beyond your usual tool, providing many alternate pieces of data,
+like formatting timestamps into recognizable dates, showing you hexcodes as colors,
+display array data like a table for easy reading, and much, much more.
Enabling Kint
=============
@@ -33,6 +59,7 @@ The ``d()`` method dumps all of the data it knows about the contents passed as t
allows the script to continue executing:
.. literalinclude:: debugging/001.php
+ :lines: 2-
dd()
----
@@ -45,14 +72,15 @@ trace()
This provides a backtrace to the current execution point, with Kint's own unique spin:
.. literalinclude:: debugging/002.php
+ :lines: 2-
For more information, see `Kint's page `_.
.. _the-debug-toolbar:
-=================
+*****************
The Debug Toolbar
-=================
+*****************
The Debug Toolbar provides at-a-glance information about the current page request, including benchmark results,
queries you have run, request and response data, and more. This can all prove very useful during development
diff --git a/user_guide_src/source/testing/fabricator.rst b/user_guide_src/source/testing/fabricator.rst
index da621c83b233..e2a4e5910ead 100644
--- a/user_guide_src/source/testing/fabricator.rst
+++ b/user_guide_src/source/testing/fabricator.rst
@@ -3,7 +3,7 @@ Generating Test Data
####################
Often you will need sample data for your application to run its tests. The ``Fabricator`` class
-uses fzaninotto's `Faker `_ to turn models into generators
+uses `Faker `_ to turn models into generators
of random data. Use fabricators in your seeds or test cases to stage fake data for your unit tests.
.. contents::
@@ -56,6 +56,7 @@ method where you can define exactly what the faked data should look like:
Notice in this example how the first three values are equivalent to the formatters from before. However for ``avatar``
we have requested an image size other than the default and ``login`` uses a conditional based on app configuration,
neither of which are possible using the ``$formatters`` parameter.
+
You may want to keep your test data separate from your production models, so it is a good practice to define
a child class in your test support folder:
diff --git a/user_guide_src/source/testing/fabricator/005.php b/user_guide_src/source/testing/fabricator/005.php
index c61fa527125e..5a601cebc050 100644
--- a/user_guide_src/source/testing/fabricator/005.php
+++ b/user_guide_src/source/testing/fabricator/005.php
@@ -2,6 +2,8 @@
namespace App\Models;
+use Faker\Generator;
+
class UserModel
{
// ...
@@ -9,10 +11,10 @@ class UserModel
public function fake(Generator &$faker)
{
return [
- 'first' => $faker->firstName,
- 'email' => $faker->email,
- 'phone' => $faker->phoneNumber,
- 'avatar' => Faker\Provider\Image::imageUrl(800, 400),
+ 'first' => $faker->firstName(),
+ 'email' => $faker->email(),
+ 'phone' => $faker->phoneNumber(),
+ 'avatar' => \Faker\Provider\Image::imageUrl(800, 400),
'login' => config('Auth')->allowRemembering ? date('Y-m-d') : null,
];
@@ -20,10 +22,10 @@ public function fake(Generator &$faker)
* Or you can return a return type object.
return new User([
- 'first' => $faker->firstName,
- 'email' => $faker->email,
- 'phone' => $faker->phoneNumber,
- 'avatar' => Faker\Provider\Image::imageUrl(800, 400),
+ 'first' => $faker->firstName(),
+ 'email' => $faker->email(),
+ 'phone' => $faker->phoneNumber(),
+ 'avatar' => \Faker\Provider\Image::imageUrl(800, 400),
'login' => config('Auth')->allowRemembering ? date('Y-m-d') : null,
]);
diff --git a/user_guide_src/source/testing/fabricator/006.php b/user_guide_src/source/testing/fabricator/006.php
index 56f34781622c..4cbe439a19a3 100644
--- a/user_guide_src/source/testing/fabricator/006.php
+++ b/user_guide_src/source/testing/fabricator/006.php
@@ -3,10 +3,11 @@
namespace Tests\Support\Models;
use App\Models\UserModel;
+use Faker\Generator;
class UserFabricator extends UserModel
{
- public function fake(&$faker)
+ public function fake(Generator &$faker)
{
// ...
}
diff --git a/user_guide_src/source/testing/fabricator/008.php b/user_guide_src/source/testing/fabricator/008.php
index 1a0d56cf95e2..e39dbe5208cb 100644
--- a/user_guide_src/source/testing/fabricator/008.php
+++ b/user_guide_src/source/testing/fabricator/008.php
@@ -1,7 +1,8 @@
make();
print_r($testUser);
diff --git a/user_guide_src/source/testing/fabricator/021.php b/user_guide_src/source/testing/fabricator/021.php
index 236dd553d492..af6297f53946 100644
--- a/user_guide_src/source/testing/fabricator/021.php
+++ b/user_guide_src/source/testing/fabricator/021.php
@@ -2,6 +2,9 @@
namespace App\Models;
+use CodeIgniter\Test\Fabricator;
+use Faker\Generator;
+
class UserModel
{
protected $table = 'users';
@@ -9,8 +12,8 @@ class UserModel
public function fake(Generator &$faker)
{
return [
- 'first' => $faker->firstName,
- 'email' => $faker->email,
+ 'first' => $faker->firstName(),
+ 'email' => $faker->email(),
'group_id' => mt_rand(1, Fabricator::getCount('groups')),
];
}
diff --git a/user_guide_src/source/testing/feature.rst b/user_guide_src/source/testing/feature.rst
index f113eab68cce..00100f8a7885 100644
--- a/user_guide_src/source/testing/feature.rst
+++ b/user_guide_src/source/testing/feature.rst
@@ -29,18 +29,19 @@ Requesting a Page
Essentially, feature tests simply allows you to call an endpoint on your application and get the results back.
To do this, you use the ``call()`` method.
-1. The first parameter is the HTTP method to use (most frequently either GET or POST).
+1. The first parameter is the HTTP method to use (most frequently either ``GET`` or ``POST``).
2. The second parameter is the URI path on your site to test.
3. The third parameter ``$params`` accepts an array that is used to populate the
superglobal variables for the HTTP verb you are using. So, a method of **GET**
- would have the **$_GET** variable populated, while a **POST** request would
- have the **$_POST** array populated. The ``$params`` is also used in
+ would have the ``$_GET`` variable populated, while a **POST** request would
+ have the ``$_POST`` array populated. The ``$params`` is also used in
:ref:`feature-formatting-the-request`.
.. note:: The ``$params`` array does not make sense for every HTTP verb, but is
included for consistency.
.. literalinclude:: feature/002.php
+ :lines: 2-
Shorthand Methods
-----------------
@@ -48,6 +49,7 @@ Shorthand Methods
Shorthand methods for each of the HTTP verbs exist to ease typing and make things clearer:
.. literalinclude:: feature/003.php
+ :lines: 2-
Setting Different Routes
------------------------
@@ -56,6 +58,7 @@ You can use a custom collection of routes by passing an array of "routes" into t
override any existing routes in the system:
.. literalinclude:: feature/004.php
+ :lines: 2-
Each of the "routes" is a 3 element array containing the HTTP verb (or "add" for all),
the URI to match, and the routing destination.
@@ -68,6 +71,7 @@ of key/value pairs that should exist within the ``$_SESSION`` variable when this
that the current values of ``$_SESSION`` should be used. This is handy for testing authentication and more.
.. literalinclude:: feature/005.php
+ :lines: 2-
Setting Headers
---------------
@@ -76,6 +80,7 @@ You can set header values with the ``withHeaders()`` method. This takes an array
passed as a header into the call:
.. literalinclude:: feature/006.php
+ :lines: 2-
Bypassing Events
----------------
@@ -84,6 +89,7 @@ Events are handy to use in your application, but can be problematic during testi
to send out emails. You can tell the system to skip any event handling with the ``skipEvents()`` method:
.. literalinclude:: feature/007.php
+ :lines: 2-
.. _feature-formatting-the-request:
@@ -100,6 +106,7 @@ body of the request in the given format.
This will also set the `Content-Type` header for your request accordingly.
.. literalinclude:: feature/008.php
+ :lines: 2-
.. _feature-setting-the-body:
diff --git a/user_guide_src/source/testing/feature/001.php b/user_guide_src/source/testing/feature/001.php
index 0ab640465052..9884535d41f2 100644
--- a/user_guide_src/source/testing/feature/001.php
+++ b/user_guide_src/source/testing/feature/001.php
@@ -1,11 +1,12 @@
HTTP Testing
response
+ Mocking
benchmark
debugging
- Mocking
diff --git a/user_guide_src/source/testing/overview.rst b/user_guide_src/source/testing/overview.rst
index 6cd60c100e73..b625e95671a4 100644
--- a/user_guide_src/source/testing/overview.rst
+++ b/user_guide_src/source/testing/overview.rst
@@ -60,24 +60,31 @@ Testing Your Application
PHPUnit Configuration
=====================
-The framework has a ``phpunit.xml.dist`` file in the project root. This controls unit
-testing of the framework itself. If you provide your own ``phpunit.xml``, it will
-over-ride this.
+In your CodeIgniter project root, there is the ``phpunit.xml.dist`` file. This
+controls unit testing of your application. If you provide your own ``phpunit.xml``,
+it will over-ride this.
-Your ``phpunit.xml`` should exclude the ``system`` folder, as well as any ``vendor`` or
-``ThirdParty`` folders, if you are unit testing your application.
+By default, test files are placed under the **tests** directory in the project root.
The Test Class
==============
-In order to take advantage of the additional tools provided, your tests must extend ``CIUnitTestCase``. All tests
-are expected to be located in the **tests/app** directory by default.
+In order to take advantage of the additional tools provided, your tests must extend
+``CodeIgniter\Test\CIUnitTestCase``.
-To test a new library, **Foo**, you would create a new file at **tests/app/Libraries/FooTest.php**:
+There are no rules for how test files must be placed. However, we recommend that
+you establish placement rules in advance so that you can quickly understand where
+the test files are located.
+
+In this document, we will place the test files corresponding to the classes in
+the **app** directory in the **tests/app** directory. To test a new library,
+**app/Libraries/Foo.php**, you would create a new file at
+**tests/app/Libraries/FooTest.php**:
.. literalinclude:: overview/001.php
-To test one of your models, you might end up with something like this in **tests/app/Models/OneOfMyModelsTest.php**:
+To test one of your models, **app/Models/UserMode.php**, you might end up with
+something like this in **tests/app/Models/UserModelTest.php**:
.. literalinclude:: overview/002.php
@@ -104,7 +111,7 @@ to help with staging and clean up::
The static methods ``setUpBeforeClass()`` and ``tearDownAfterClass()`` run before and after the entire test case, whereas the protected methods ``setUp()`` and ``tearDown()`` run
between each test.
-If you implement any of these special functions make sure you run their
+If you implement any of these special functions, make sure you run their
parent as well so extended test cases do not interfere with staging:
.. literalinclude:: overview/003.php
diff --git a/user_guide_src/source/testing/overview/002.php b/user_guide_src/source/testing/overview/002.php
index eca8ef906203..db746b62a3b1 100644
--- a/user_guide_src/source/testing/overview/002.php
+++ b/user_guide_src/source/testing/overview/002.php
@@ -4,7 +4,7 @@
use CodeIgniter\Test\CIUnitTestCase;
-class OneOfMyModelsTest extends CIUnitTestCase
+class UserModelTest extends CIUnitTestCase
{
public function testFooNotBar()
{
diff --git a/user_guide_src/source/testing/overview/003.php b/user_guide_src/source/testing/overview/003.php
index fe9f163246b1..ee39231cb382 100644
--- a/user_guide_src/source/testing/overview/003.php
+++ b/user_guide_src/source/testing/overview/003.php
@@ -4,7 +4,7 @@
use CodeIgniter\Test\CIUnitTestCase;
-final class OneOfMyModelsTest extends CIUnitTestCase
+final class UserModelTest extends CIUnitTestCase
{
protected function setUp(): void
{
diff --git a/user_guide_src/source/testing/overview/016.php b/user_guide_src/source/testing/overview/016.php
index d3a769d8379c..8598a4a7a3c5 100644
--- a/user_guide_src/source/testing/overview/016.php
+++ b/user_guide_src/source/testing/overview/016.php
@@ -1,5 +1,8 @@
getMockBuilder('CodeIgniter\HTTP\CURLRequest')
- ->setMethods(['request'])
+ $curlrequest = $this->getMockBuilder(CURLRequest::class)
+ ->onlyMethods(['request'])
->getMock();
Services::injectMock('curlrequest', $curlrequest);
diff --git a/user_guide_src/source/testing/overview/017.php b/user_guide_src/source/testing/overview/017.php
index f5e436377383..d989ed7e7da1 100644
--- a/user_guide_src/source/testing/overview/017.php
+++ b/user_guide_src/source/testing/overview/017.php
@@ -2,6 +2,7 @@
namespace Tests;
+use App\Models\UserModel;
use CodeIgniter\Config\Factories;
use CodeIgniter\Test\CIUnitTestCase;
use Tests\Support\Mock\MockUserModel;
@@ -13,6 +14,6 @@ protected function setUp(): void
parent::setUp();
$model = new MockUserModel();
- Factories::injectMock('models', 'App\Models\UserModel', $model);
+ Factories::injectMock('models', UserModel::class, $model);
}
}
diff --git a/user_guide_src/source/testing/overview/018.php b/user_guide_src/source/testing/overview/018.php
index 09f1f4e42f2d..cddef79a9ef2 100644
--- a/user_guide_src/source/testing/overview/018.php
+++ b/user_guide_src/source/testing/overview/018.php
@@ -1,5 +1,7 @@