diff --git a/.gitattributes b/.gitattributes
index c41e331ff63c..3f67f2e35167 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -17,7 +17,6 @@ CONTRIBUTING.md export-ignore
# contributor/development files
tests/ export-ignore
-tools/ export-ignore
utils/ export-ignore
.php-cs-fixer.dist.php export-ignore
.php-cs-fixer.no-header.php export-ignore
diff --git a/.github/workflows/reusable-phpunit-test.yml b/.github/workflows/reusable-phpunit-test.yml
index 4943d3f3ac2c..84ade1d08862 100644
--- a/.github/workflows/reusable-phpunit-test.yml
+++ b/.github/workflows/reusable-phpunit-test.yml
@@ -95,13 +95,13 @@ jobs:
mssql:
image: mcr.microsoft.com/mssql/server:2022-latest
env:
- SA_PASSWORD: 1Secure*Password1
+ MSSQL_SA_PASSWORD: 1Secure*Password1
ACCEPT_EULA: Y
MSSQL_PID: Developer
ports:
- 1433:1433
options: >-
- --health-cmd="/opt/mssql-tools/bin/sqlcmd -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q 'SELECT @@VERSION'"
+ --health-cmd="/opt/mssql-tools18/bin/sqlcmd -C -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q 'SELECT @@VERSION'"
--health-interval=10s
--health-timeout=5s
--health-retries=3
diff --git a/.github/workflows/test-coding-standards.yml b/.github/workflows/test-coding-standards.yml
index 7e5f18820288..f30475a76dba 100644
--- a/.github/workflows/test-coding-standards.yml
+++ b/.github/workflows/test-coding-standards.yml
@@ -57,14 +57,5 @@ jobs:
- name: Install dependencies
run: composer update --ansi --no-interaction
- - name: Run lint on `app/`, `admin/`, `public/`
- run: vendor/bin/php-cs-fixer fix --verbose --ansi --dry-run --config=.php-cs-fixer.no-header.php --using-cache=no --diff
-
- - name: Run lint on `system/`, `utils/`, and root PHP files
- run: vendor/bin/php-cs-fixer fix --verbose --ansi --dry-run --using-cache=no --diff
-
- - name: Run lint on `tests`
- run: vendor/bin/php-cs-fixer fix --verbose --ansi --dry-run --config=.php-cs-fixer.tests.php --using-cache=no --diff
-
- - name: Run lint on `user_guide_src/source/`
- run: vendor/bin/php-cs-fixer fix --verbose --ansi --dry-run --config=.php-cs-fixer.user-guide.php --using-cache=no --diff
+ - name: Run lint
+ run: composer cs
diff --git a/.github/workflows/test-phpstan.yml b/.github/workflows/test-phpstan.yml
index 360b403ac923..aca331f30481 100644
--- a/.github/workflows/test-phpstan.yml
+++ b/.github/workflows/test-phpstan.yml
@@ -85,4 +85,4 @@ jobs:
run: composer update --ansi --no-interaction
- name: Run static analysis
- run: vendor/bin/phpstan analyse
+ run: composer phpstan:check
diff --git a/.github/workflows/test-psalm.yml b/.github/workflows/test-psalm.yml
index a623109fbb15..b1f5891a46d9 100644
--- a/.github/workflows/test-psalm.yml
+++ b/.github/workflows/test-psalm.yml
@@ -24,7 +24,7 @@ jobs:
build:
name: Psalm Analysis
runs-on: ubuntu-latest
- if: "!contains(github.event.head_commit.message, '[ci skip]')"
+ if: (! contains(github.event.head_commit.message, '[ci skip]'))
steps:
- name: Checkout
@@ -68,4 +68,4 @@ jobs:
fi
- name: Run Psalm analysis
- run: vendor/bin/psalm
+ run: utils/vendor/bin/psalm
diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
index 4ce5e25fbc1d..a50b126db075 100644
--- a/.php-cs-fixer.dist.php
+++ b/.php-cs-fixer.dist.php
@@ -28,8 +28,6 @@
'ThirdParty',
'Validation/Views',
])
- ->notPath([
- ])
->notName('#Foobar.php$#')
->append([
__FILE__,
@@ -41,17 +39,12 @@
__DIR__ . '/spark',
]);
-$overrides = [
- // for updating to coding-standard
- 'modernize_strpos' => true,
- 'ordered_attributes' => ['order' => [], 'sort_algorithm' => 'alpha'],
- 'php_unit_attributes' => true,
-];
+$overrides = [];
$options = [
'cacheFile' => 'build/.php-cs-fixer.cache',
'finder' => $finder,
- 'customFixers' => FixerGenerator::create('vendor/nexusphp/cs-config/src/Fixer', 'Nexus\\CsConfig\\Fixer'),
+ 'customFixers' => FixerGenerator::create('utils/vendor/nexusphp/cs-config/src/Fixer', 'Nexus\\CsConfig\\Fixer'),
'customRules' => [
NoCodeSeparatorCommentFixer::name() => true,
],
diff --git a/.php-cs-fixer.no-header.php b/.php-cs-fixer.no-header.php
index e7d9647e317e..f3bc97dc78ac 100644
--- a/.php-cs-fixer.no-header.php
+++ b/.php-cs-fixer.no-header.php
@@ -11,12 +11,12 @@
* the LICENSE file that was distributed with this source code.
*/
-use CodeIgniter\CodingStandard\CodeIgniter4;
-use Nexus\CsConfig\Factory;
-use Nexus\CsConfig\Fixer\Comment\NoCodeSeparatorCommentFixer;
-use Nexus\CsConfig\FixerGenerator;
+use PhpCsFixer\ConfigInterface;
use PhpCsFixer\Finder;
+/** @var ConfigInterface $config */
+$config = require __DIR__ . '/.php-cs-fixer.dist.php';
+
$finder = Finder::create()
->files()
->in([
@@ -30,19 +30,10 @@
]);
$overrides = [
- // for updating to coding-standard
- 'modernize_strpos' => true,
- 'ordered_attributes' => ['order' => [], 'sort_algorithm' => 'alpha'],
- 'php_unit_attributes' => true,
-];
-
-$options = [
- 'cacheFile' => 'build/.php-cs-fixer.no-header.cache',
- 'finder' => $finder,
- 'customFixers' => FixerGenerator::create('vendor/nexusphp/cs-config/src/Fixer', 'Nexus\\CsConfig\\Fixer'),
- 'customRules' => [
- NoCodeSeparatorCommentFixer::name() => true,
- ],
+ 'header_comment' => false,
];
-return Factory::create(new CodeIgniter4(), $overrides, $options)->forProjects();
+return $config
+ ->setFinder($finder)
+ ->setCacheFile('build/.php-cs-fixer.no-header.cache')
+ ->setRules(array_merge($config->getRules(), $overrides));
diff --git a/.php-cs-fixer.tests.php b/.php-cs-fixer.tests.php
index 1b4bfd71e30d..28a7124909e5 100644
--- a/.php-cs-fixer.tests.php
+++ b/.php-cs-fixer.tests.php
@@ -11,47 +11,29 @@
* the LICENSE file that was distributed with this source code.
*/
-use CodeIgniter\CodingStandard\CodeIgniter4;
-use Nexus\CsConfig\Factory;
-use Nexus\CsConfig\Fixer\Comment\NoCodeSeparatorCommentFixer;
-use Nexus\CsConfig\FixerGenerator;
+use PhpCsFixer\ConfigInterface;
use PhpCsFixer\Finder;
+/** @var ConfigInterface $config */
+$config = require __DIR__ . '/.php-cs-fixer.dist.php';
+
$finder = Finder::create()
->files()
->in([
__DIR__ . '/tests',
])
- ->exclude([
- ])
->notPath([
'_support/View/Cells/multiplier.php',
'_support/View/Cells/colors.php',
'_support/View/Cells/addition.php',
])
- ->notName('#Foobar.php$#')
- ->append([
- ]);
+ ->notName('#Foobar.php$#');
$overrides = [
'void_return' => true,
- // for updating to coding-standard
- 'modernize_strpos' => true,
- 'ordered_attributes' => ['order' => [], 'sort_algorithm' => 'alpha'],
- 'php_unit_attributes' => true,
-];
-
-$options = [
- 'cacheFile' => 'build/.php-cs-fixer.tests.cache',
- 'finder' => $finder,
- 'customFixers' => FixerGenerator::create('vendor/nexusphp/cs-config/src/Fixer', 'Nexus\\CsConfig\\Fixer'),
- 'customRules' => [
- NoCodeSeparatorCommentFixer::name() => true,
- ],
];
-return Factory::create(new CodeIgniter4(), $overrides, $options)->forLibrary(
- 'CodeIgniter 4 framework',
- 'CodeIgniter Foundation',
- 'admin@codeigniter.com'
-);
+return $config
+ ->setFinder($finder)
+ ->setCacheFile('build/.php-cs-fixer.tests.cache')
+ ->setRules(array_merge($config->getRules(), $overrides));
diff --git a/.php-cs-fixer.user-guide.php b/.php-cs-fixer.user-guide.php
index 3be7cd45c708..6b925ee8b0b7 100644
--- a/.php-cs-fixer.user-guide.php
+++ b/.php-cs-fixer.user-guide.php
@@ -11,12 +11,12 @@
* the LICENSE file that was distributed with this source code.
*/
-use CodeIgniter\CodingStandard\CodeIgniter4;
-use Nexus\CsConfig\Factory;
-use Nexus\CsConfig\Fixer\Comment\NoCodeSeparatorCommentFixer;
-use Nexus\CsConfig\FixerGenerator;
+use PhpCsFixer\ConfigInterface;
use PhpCsFixer\Finder;
+/** @var ConfigInterface $config */
+$config = require __DIR__ . '/.php-cs-fixer.dist.php';
+
$finder = Finder::create()
->files()
->in([
@@ -32,6 +32,7 @@
$overrides = [
'echo_tag_syntax' => false,
+ 'header_comment' => false,
'php_unit_internal_class' => false,
'no_unused_imports' => false,
'class_attributes_separation' => false,
@@ -39,19 +40,9 @@
'import_symbols' => false,
'leading_backslash_in_global_namespace' => true,
],
- // for updating to coding-standard
- 'modernize_strpos' => true,
- 'ordered_attributes' => ['order' => [], 'sort_algorithm' => 'alpha'],
- 'php_unit_attributes' => true,
-];
-
-$options = [
- 'cacheFile' => 'build/.php-cs-fixer.user-guide.cache',
- 'finder' => $finder,
- 'customFixers' => FixerGenerator::create('vendor/nexusphp/cs-config/src/Fixer', 'Nexus\\CsConfig\\Fixer'),
- 'customRules' => [
- NoCodeSeparatorCommentFixer::name() => true,
- ],
];
-return Factory::create(new CodeIgniter4(), $overrides, $options)->forProjects();
+return $config
+ ->setFinder($finder)
+ ->setCacheFile('build/.php-cs-fixer.user-guide.cache')
+ ->setRules(array_merge($config->getRules(), $overrides));
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9351b205e637..1d2778139204 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,28 @@
# Changelog
+## [v4.5.4](https://github.com/codeigniter4/CodeIgniter4/tree/v4.5.3) (2024-07-27)
+[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.5.3...v4.5.4)
+
+### Fixed Bugs
+
+* fix: [OCI8] Easy Connect string validation by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/9006
+* fix: [QueryBuilder] select() with RawSql may cause TypeError by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/9009
+* fix: [QueryBuilder] `select()` does not escape after `NULL` by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/9010
+* fix: allow string as parameter to CURLRequest version by @tangix in https://github.com/codeigniter4/CodeIgniter4/pull/9021
+* fix: `spark phpini:check` may cause TypeError by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/9026
+* fix: Prevent invalid session handlers by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9036
+* fix: DebugBar CSS for daisyUI by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/9046
+* docs: `referrer` is undefined by @totoprayogo1916 in https://github.com/codeigniter4/CodeIgniter4/pull/9059
+* fix: filters passed to the ``$routes->group()`` are not merged into the filters passed to the inner routes by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/9064
+
+### Refactoring
+
+* refactor: use first class callable on function call by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/9004
+* refactor: enable AddClosureVoidReturnTypeWhereNoReturnRector to add void return on closure by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/9008
+* refactor: enable AddFunctionVoidReturnTypeWhereNoReturnRector to add void to functions by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/9014
+* refactor: Enable phpunit 10 attribute Rector rules by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/9015
+* refactor: fix `Throttler::check()` $tokens by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9067
+
## [v4.5.3](https://github.com/codeigniter4/CodeIgniter4/tree/v4.5.3) (2024-06-25)
[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.5.2...v4.5.3)
diff --git a/admin/RELEASE.md b/admin/RELEASE.md
index 8c5f463355fb..4f199a821177 100644
--- a/admin/RELEASE.md
+++ b/admin/RELEASE.md
@@ -6,16 +6,22 @@
>
> -MGatner, kenjis
-## Merge `develop` branch into next minor version branch `4.x`
+## Notation
+
+- `4.x.x`: The new release version. (e.g., `4.5.3`)
+- `4.y`: The next minor version. (e.g., `4.6`)
+- `4.z`: The next next minor version. (e.g., `4.7`)
+
+## Merge `develop` branch into next minor version branch `4.y`
Before starting release process, if there are commits in `develop` branch that
-are not merged into `4.x` branch, merge them. This is because if conflicts occur,
+are not merged into `4.y` branch, merge them. This is because if conflicts occur,
merging will take time.
```console
git fetch upstream
-git switch 4.x
-git merge upstream/4.x
+git switch 4.y
+git merge upstream/4.y
git merge upstream/develop
git push upstream HEAD
```
@@ -24,11 +30,10 @@ git push upstream HEAD
If you release a new minor version.
-* [ ] Create PR to merge `4.x` into `develop` and merge it
+* [ ] Create PR to merge `4.y` into `develop` and merge it
* [ ] Rename the current minor version (e.g., `4.5`) in Setting > Branches >
"Branch protection rules" to the next minor version. E.g. `4.5` → `4.6`
-* [ ] Delete the merged `4.x` branch (This closes all PRs to the branch)
-* Do the regular release process. Go to the next "Changelog" section
+* [ ] Delete the merged `4.y` branch (This closes all PRs to the branch)
## Changelog
@@ -90,8 +95,8 @@ Work off direct clones of the repos so the release branches persist for a time.
* [ ] Replace **CHANGELOG.md** with the new version generated above
* [ ] Update **user_guide_src/source/changelogs/v4.x.x.rst**
* Remove the section titles that have no items
-* [ ] Update **user_guide_src/source/installation/upgrade_{ver}.rst**
- * [ ] fill in the "All Changes" section, and add it to **upgrading.rst**
+* [ ] Update **user_guide_src/source/installation/upgrade_4xx.rst**
+ * [ ] fill in the "All Changes" section, and add it to **upgrade_4xx.rst**
* git diff --name-status origin/master -- . ':!system' ':!tests' ':!user_guide_src'
* Note: `tests/` is not used for distribution repos. See `admin/starter/tests/`
* [ ] Remove the section titles that have no items
@@ -137,7 +142,7 @@ Work off direct clones of the repos so the release branches persist for a time.
## New Contributors
*
- **Full Changelog**: https://github.com/codeigniter4/CodeIgniter4/compare/v4.x.x...v4.x.x
+ **Full Changelog**: https://github.com/codeigniter4/CodeIgniter4/compare/v4.x.w...v4.x.x
```
Click the "Generate release notes" button, and get the "New Contributors".
* [ ] Watch for the "Deploy Distributable Repos" action to make sure **framework**,
@@ -164,19 +169,19 @@ Work off direct clones of the repos so the release branches persist for a time.
git merge origin/master
git push origin HEAD
```
-* [ ] Update the next minor version branch `4.x`:
+* [ ] Update the next minor version branch `4.y`:
```console
git fetch origin
- git checkout 4.x
- git merge origin/4.x
+ git checkout 4.y
+ git merge origin/4.y
git merge origin/develop
git push origin HEAD
```
-* [ ] [Minor version only] Create the new next minor version branch `4.x`:
+* [ ] [Minor version only] Create the new next minor version branch `4.z`:
```console
git fetch origin
git switch develop
- git switch -c 4.x
+ git switch -c 4.z
git push origin HEAD
```
* [ ] Request CVEs and Publish any Security Advisories that were resolved from private forks
diff --git a/admin/css/debug-toolbar/toolbar.scss b/admin/css/debug-toolbar/toolbar.scss
index 97a92690b937..eec3d25546e3 100644
--- a/admin/css/debug-toolbar/toolbar.scss
+++ b/admin/css/debug-toolbar/toolbar.scss
@@ -95,6 +95,7 @@
}
h2 {
+ font-weight: bold;
font-size: $base-size;
margin: 0;
padding: 5px 0 10px 0;
@@ -292,6 +293,8 @@
// The tabs container
.tab {
+ height: fit-content;
+ text-align: left;
bottom: 35px;
display: none;
left: 0;
@@ -306,6 +309,8 @@
// The "Timeline" tab
.timeline {
+ position: static;
+ display: table;
margin-left: 0;
width: 100%;
diff --git a/admin/pre-commit b/admin/pre-commit
index f64268277e72..19bec4f4726b 100644
--- a/admin/pre-commit
+++ b/admin/pre-commit
@@ -24,39 +24,10 @@ if [ "$FILES" != "" ]; then
echo "Running PHP CS Fixer..."
# Run on whole codebase to skip on unnecessary filtering
- # Run first on app, admin, public
- if [ -d /proc/cygdrive ]; then
- ./vendor/bin/php-cs-fixer fix --verbose --dry-run --diff --config=.php-cs-fixer.no-header.php
- else
- php ./vendor/bin/php-cs-fixer fix --verbose --dry-run --diff --config=.php-cs-fixer.no-header.php
- fi
-
- if [ $? != 0 ]; then
- echo "Files in app, admin, or public are not following the coding standards. Please fix them before commit."
- exit 1
- fi
-
- # Next, run on system, tests, utils, and root PHP files
- if [ -d /proc/cygdrive ]; then
- ./vendor/bin/php-cs-fixer fix --verbose --dry-run --diff
- else
- php ./vendor/bin/php-cs-fixer fix --verbose --dry-run --diff
- fi
-
- if [ $? != 0 ]; then
- echo "Files in system, tests, utils, or root are not following the coding standards. Please fix them before commit."
- exit 1
- fi
-
- # Next, run on user_guide_src/source PHP files
- if [ -d /proc/cygdrive ]; then
- ./vendor/bin/php-cs-fixer fix --verbose --dry-run --diff --config=.php-cs-fixer.user-guide.php
- else
- php ./vendor/bin/php-cs-fixer fix --verbose --dry-run --diff --config=.php-cs-fixer.user-guide.php
- fi
+ composer cs
if [ $? != 0 ]; then
- echo "Files in user_guide_src/source are not following the coding standards. Please fix them before commit."
+ echo "There are PHP files which are not following the coding standards. Please fix them before commit."
exit 1
fi
fi
diff --git a/admin/starter/.github/workflows/phpunit.yml b/admin/starter/.github/workflows/phpunit.yml
index f43435df9765..2be22ec16095 100644
--- a/admin/starter/.github/workflows/phpunit.yml
+++ b/admin/starter/.github/workflows/phpunit.yml
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
- if: "!contains(github.event.head_commit.message, '[ci skip]')"
+ if: (! contains(github.event.head_commit.message, '[ci skip]'))
steps:
- name: Checkout
diff --git a/admin/starter/composer.json b/admin/starter/composer.json
index b84684d82a4f..38a51e29fb64 100644
--- a/admin/starter/composer.json
+++ b/admin/starter/composer.json
@@ -21,7 +21,7 @@
"autoload": {
"psr-4": {
"App\\": "app/",
- "Config\\": "app/Config"
+ "Config\\": "app/Config/"
},
"exclude-from-classmap": [
"**/Database/Migrations/**"
diff --git a/app/Config/Events.php b/app/Config/Events.php
index 993abd24ebc7..62a7b86d46c8 100644
--- a/app/Config/Events.php
+++ b/app/Config/Events.php
@@ -23,7 +23,7 @@
* Events::on('create', [$myInstance, 'myMethod']);
*/
-Events::on('pre_system', static function () {
+Events::on('pre_system', static function (): void {
if (ENVIRONMENT !== 'testing') {
if (ini_get('zlib.output_compression')) {
throw FrameworkException::forEnabledZlibOutputCompression();
@@ -47,7 +47,7 @@
Services::toolbar()->respond();
// Hot Reload route - for framework use on the hot reloader.
if (ENVIRONMENT === 'development') {
- Services::routes()->get('__hot-reload', static function () {
+ Services::routes()->get('__hot-reload', static function (): void {
(new HotReloader())->run();
});
}
diff --git a/composer.json b/composer.json
index 76020a1f6ea5..6915af74aa8f 100644
--- a/composer.json
+++ b/composer.json
@@ -17,23 +17,18 @@
"psr/log": "^3.0"
},
"require-dev": {
- "codeigniter/coding-standard": "^1.7",
"codeigniter/phpstan-codeigniter": "^1.4",
- "ergebnis/composer-normalize": "^2.28",
"fakerphp/faker": "^1.9",
- "friendsofphp/php-cs-fixer": "^3.47.1",
"kint-php/kint": "^5.0.4",
"mikey179/vfsstream": "^1.6",
- "nexusphp/cs-config": "^3.6",
"nexusphp/tachycardia": "^2.0",
- "phpstan/extension-installer": "^1.3",
+ "phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "^1.11",
"phpstan/phpstan-strict-rules": "^1.6",
"phpunit/phpcov": "^9.0.2",
"phpunit/phpunit": "^10.5.16",
"predis/predis": "^1.1 || ^2.0",
- "rector/rector": "1.1.1",
- "vimeo/psalm": "^5.0"
+ "rector/rector": "1.2.2"
},
"replace": {
"codeigniter4/framework": "self.version"
@@ -72,12 +67,11 @@
"autoload-dev": {
"psr-4": {
"CodeIgniter\\": "tests/system/",
- "Utils\\": "utils/"
+ "Utils\\": "utils/src/"
}
},
"config": {
"allow-plugins": {
- "ergebnis/composer-normalize": true,
"phpstan/extension-installer": true
},
"optimize-autoloader": true,
@@ -91,29 +85,33 @@
},
"scripts": {
"post-update-cmd": [
- "CodeIgniter\\ComposerScripts::postUpdate",
- "composer update --working-dir=tools/phpmetrics"
+ "CodeIgniter\\ComposerScripts::postUpdate"
+ ],
+ "post-autoload-dump": [
+ "@composer update --working-dir=utils"
],
"analyze": [
"Composer\\Config::disableProcessTimeout",
- "bash -c \"XDEBUG_MODE=off phpstan analyse\"",
- "rector process --dry-run"
+ "@phpstan:check",
+ "vendor/bin/rector process --dry-run"
],
"cs": [
"Composer\\Config::disableProcessTimeout",
- "php-cs-fixer fix --ansi --verbose --dry-run --diff --config=.php-cs-fixer.user-guide.php",
- "php-cs-fixer fix --ansi --verbose --dry-run --diff --config=.php-cs-fixer.no-header.php",
- "php-cs-fixer fix --ansi --verbose --dry-run --diff --config=.php-cs-fixer.tests.php",
- "php-cs-fixer fix --ansi --verbose --dry-run --diff"
+ "utils/vendor/bin/php-cs-fixer check --ansi --verbose --diff --config=.php-cs-fixer.user-guide.php",
+ "utils/vendor/bin/php-cs-fixer check --ansi --verbose --diff --config=.php-cs-fixer.no-header.php",
+ "utils/vendor/bin/php-cs-fixer check --ansi --verbose --diff --config=.php-cs-fixer.tests.php",
+ "utils/vendor/bin/php-cs-fixer check --ansi --verbose --diff"
],
"cs-fix": [
"Composer\\Config::disableProcessTimeout",
- "php-cs-fixer fix --ansi --verbose --diff --config=.php-cs-fixer.user-guide.php",
- "php-cs-fixer fix --ansi --verbose --diff --config=.php-cs-fixer.no-header.php",
- "php-cs-fixer fix --ansi --verbose --diff --config=.php-cs-fixer.tests.php",
- "php-cs-fixer fix --ansi --verbose --diff"
+ "utils/vendor/bin/php-cs-fixer fix --ansi --verbose --diff --config=.php-cs-fixer.user-guide.php",
+ "utils/vendor/bin/php-cs-fixer fix --ansi --verbose --diff --config=.php-cs-fixer.no-header.php",
+ "utils/vendor/bin/php-cs-fixer fix --ansi --verbose --diff --config=.php-cs-fixer.tests.php",
+ "utils/vendor/bin/php-cs-fixer fix --ansi --verbose --diff"
],
- "metrics": "tools/phpmetrics/vendor/bin/phpmetrics --config=phpmetrics.json",
+ "metrics": "utils/vendor/bin/phpmetrics --config=phpmetrics.json",
+ "phpstan:baseline": "vendor/bin/phpstan analyse --ansi --generate-baseline=phpstan-baseline.php",
+ "phpstan:check": "vendor/bin/phpstan analyse --verbose --ansi",
"sa": "@analyze",
"style": "@cs-fix",
"test": "phpunit"
@@ -123,6 +121,8 @@
"cs": "Check the coding style",
"cs-fix": "Fix the coding style",
"metrics": "Run PhpMetrics",
+ "phpstan:baseline": "Run PHPStan then dump all errors to baseline",
+ "phpstan:check": "Run PHPStan with support for identifiers",
"test": "Run unit tests"
}
}
diff --git a/phpdoc.dist.xml b/phpdoc.dist.xml
index 5e5a31cb483a..e67b1b29ed55 100644
--- a/phpdoc.dist.xml
+++ b/phpdoc.dist.xml
@@ -10,7 +10,7 @@
api/cache/
-
+ system
diff --git a/phpstan-baseline.php b/phpstan-baseline.php
index 5a39d840a54d..1b36c4a8f8ee 100644
--- a/phpstan-baseline.php
+++ b/phpstan-baseline.php
@@ -1819,12 +1819,6 @@
'count' => 1,
'path' => __DIR__ . '/system/Database/BaseBuilder.php',
];
-$ignoreErrors[] = [
- // identifier: missingType.iterableValue
- 'message' => '#^Method CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:select\\(\\) has parameter \\$select with no value type specified in iterable type array\\.$#',
- 'count' => 1,
- 'path' => __DIR__ . '/system/Database/BaseBuilder.php',
-];
$ignoreErrors[] = [
// identifier: missingType.iterableValue
'message' => '#^Method CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:set\\(\\) has parameter \\$key with no value type specified in iterable type array\\.$#',
@@ -1993,12 +1987,6 @@
'count' => 1,
'path' => __DIR__ . '/system/Database/BaseBuilder.php',
];
-$ignoreErrors[] = [
- // identifier: missingType.iterableValue
- 'message' => '#^Property CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:\\$QBNoEscape type has no value type specified in iterable type array\\.$#',
- 'count' => 1,
- 'path' => __DIR__ . '/system/Database/BaseBuilder.php',
-];
$ignoreErrors[] = [
// identifier: missingType.iterableValue
'message' => '#^Property CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:\\$QBOptions type has no value type specified in iterable type array\\.$#',
@@ -7465,12 +7453,6 @@
'count' => 1,
'path' => __DIR__ . '/system/Helpers/test_helper.php',
];
-$ignoreErrors[] = [
- // identifier: empty.notAllowed
- 'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#',
- 'count' => 1,
- 'path' => __DIR__ . '/system/Helpers/text_helper.php',
-];
$ignoreErrors[] = [
// identifier: missingType.iterableValue
'message' => '#^Function strip_slashes\\(\\) has parameter \\$str with no value type specified in iterable type array\\.$#',
@@ -18811,11 +18793,5 @@
'count' => 1,
'path' => __DIR__ . '/tests/system/View/ViewTest.php',
];
-$ignoreErrors[] = [
- // identifier: method.childParameterType
- 'message' => '#^Parameter \\#1 \\$node \\(PhpParser\\\\Node\\\\Stmt\\) of method Utils\\\\PHPStan\\\\CheckUseStatementsAfterLicenseRule\\:\\:processNode\\(\\) should be contravariant with parameter \\$node \\(PhpParser\\\\Node\\) of method PHPStan\\\\Rules\\\\Rule\\\\:\\:processNode\\(\\)$#',
- 'count' => 1,
- 'path' => __DIR__ . '/utils/PHPStan/CheckUseStatementsAfterLicenseRule.php',
-];
return ['parameters' => ['ignoreErrors' => $ignoreErrors]];
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index eccd0f88db75..c67aa93fcb04 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -18,7 +18,7 @@ parameters:
- app
- system
- tests
- - utils/PHPStan
+ - utils/src/PHPStan
excludePaths:
- app/Views/errors/cli/*
- app/Views/errors/html/*
diff --git a/rector.php b/rector.php
index 1a12e38f3223..d8ab56f3cc8e 100644
--- a/rector.php
+++ b/rector.php
@@ -42,66 +42,61 @@
use Rector\EarlyReturn\Rector\Return_\PreparedValueToEarlyReturnRector;
use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector;
use Rector\Php70\Rector\FuncCall\RandomFunctionRector;
-use Rector\Php80\Rector\Class_\AnnotationToAttributeRector;
use Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector;
use Rector\Php80\Rector\FunctionLike\MixedTypeRector;
use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector;
-use Rector\PHPUnit\AnnotationsToAttributes\Rector\Class_\AnnotationWithValueToAttributeRector;
-use Rector\PHPUnit\AnnotationsToAttributes\Rector\Class_\CoversAnnotationWithValueToAttributeRector;
-use Rector\PHPUnit\AnnotationsToAttributes\Rector\ClassMethod\DataProviderAnnotationToAttributeRector;
-use Rector\PHPUnit\AnnotationsToAttributes\Rector\ClassMethod\DependsAnnotationWithValueToAttributeRector;
use Rector\PHPUnit\CodeQuality\Rector\Class_\YieldDataProviderRector;
use Rector\PHPUnit\Set\PHPUnitSetList;
use Rector\Privatization\Rector\Property\PrivatizeFinalClassPropertyRector;
-use Rector\Set\ValueObject\LevelSetList;
-use Rector\Set\ValueObject\SetList;
use Rector\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector;
use Rector\Strict\Rector\If_\BooleanInIfConditionRuleFixerRector;
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnNeverTypeRector;
+use Rector\TypeDeclaration\Rector\Closure\AddClosureVoidReturnTypeWhereNoReturnRector;
use Rector\TypeDeclaration\Rector\Empty_\EmptyOnNullableObjectToInstanceOfRector;
+use Rector\TypeDeclaration\Rector\Function_\AddFunctionVoidReturnTypeWhereNoReturnRector;
use Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector;
use Utils\Rector\PassStrictParameterToFunctionParameterRector;
use Utils\Rector\RemoveErrorSuppressInTryCatchStmtsRector;
use Utils\Rector\UnderscoreToCamelCaseVariableNameRector;
-return static function (RectorConfig $rectorConfig): void {
- $rectorConfig->sets([
- SetList::DEAD_CODE,
- LevelSetList::UP_TO_PHP_81,
+return RectorConfig::configure()
+ ->withPhpSets(php81: true)
+ ->withPreparedSets(deadCode: true)
+ ->withSets([
PHPUnitSetList::PHPUNIT_CODE_QUALITY,
PHPUnitSetList::PHPUNIT_100,
- ]);
-
- $rectorConfig->parallel(120, 8, 10);
-
- // Github action cache
- $rectorConfig->cacheClass(FileCacheStorage::class);
- if (is_dir('/tmp')) {
- $rectorConfig->cacheDirectory('/tmp/rector');
- }
-
+ ])
+ ->withParallel(120, 8, 10)
+ ->withCache(
+ // Github action cache or local
+ is_dir('/tmp') ? '/tmp/rector' : null,
+ FileCacheStorage::class
+ )
// paths to refactor; solid alternative to CLI arguments
- $rectorConfig->paths([__DIR__ . '/app', __DIR__ . '/system', __DIR__ . '/tests', __DIR__ . '/utils']);
-
+ ->withPaths([
+ __DIR__ . '/app',
+ __DIR__ . '/system',
+ __DIR__ . '/tests',
+ __DIR__ . '/utils/src',
+ ])
// do you need to include constants, class aliases or custom autoloader? files listed will be executed
- $rectorConfig->bootstrapFiles([
+ ->withBootstrapFiles([
__DIR__ . '/system/Test/bootstrap.php',
- ]);
-
- $rectorConfig->phpstanConfigs([
+ ])
+ ->withPHPStanConfigs([
__DIR__ . '/phpstan.neon.dist',
__DIR__ . '/vendor/codeigniter/phpstan-codeigniter/extension.neon',
__DIR__ . '/vendor/phpstan/phpstan-strict-rules/rules.neon',
- ]);
-
+ ])
// is there a file you need to skip?
- $rectorConfig->skip([
+ ->withSkip([
__DIR__ . '/system/Debug/Toolbar/Views/toolbar.tpl.php',
__DIR__ . '/system/ThirdParty',
__DIR__ . '/tests/system/Config/fixtures',
__DIR__ . '/tests/system/Filters/fixtures',
__DIR__ . '/tests/_support/Commands/Foobar.php',
__DIR__ . '/tests/_support/View',
+ __DIR__ . '/tests/system/View/Views',
YieldDataProviderRector::class,
@@ -180,55 +175,46 @@
// Unnecessary (string) is inserted
NullToStrictStringFuncCallArgRector::class,
-
- // PHPUnit 10 (requires PHP 8.1) features
- DataProviderAnnotationToAttributeRector::class,
- DependsAnnotationWithValueToAttributeRector::class,
- AnnotationWithValueToAttributeRector::class,
- AnnotationToAttributeRector::class,
- CoversAnnotationWithValueToAttributeRector::class,
- ]);
-
+ ])
// auto import fully qualified class names
- $rectorConfig->importNames();
- $rectorConfig->removeUnusedImports();
-
- $rectorConfig->rule(DeclareStrictTypesRector::class);
- $rectorConfig->rule(UnderscoreToCamelCaseVariableNameRector::class);
- $rectorConfig->rule(SimplifyUselessVariableRector::class);
- $rectorConfig->rule(RemoveAlwaysElseRector::class);
- $rectorConfig->rule(PassStrictParameterToFunctionParameterRector::class);
- $rectorConfig->rule(CountArrayToEmptyArrayComparisonRector::class);
- $rectorConfig->rule(ChangeNestedForeachIfsToEarlyContinueRector::class);
- $rectorConfig->rule(ChangeIfElseValueAssignToEarlyReturnRector::class);
- $rectorConfig->rule(SimplifyStrposLowerRector::class);
- $rectorConfig->rule(CombineIfRector::class);
- $rectorConfig->rule(SimplifyIfReturnBoolRector::class);
- $rectorConfig->rule(InlineIfToExplicitIfRector::class);
- $rectorConfig->rule(PreparedValueToEarlyReturnRector::class);
- $rectorConfig->rule(ShortenElseIfRector::class);
- $rectorConfig->rule(SimplifyIfElseToTernaryRector::class);
- $rectorConfig->rule(UnusedForeachValueToArrayKeysRector::class);
- $rectorConfig->rule(ChangeArrayPushToArrayAssignRector::class);
- $rectorConfig->rule(UnnecessaryTernaryExpressionRector::class);
- $rectorConfig->rule(RemoveErrorSuppressInTryCatchStmtsRector::class);
- $rectorConfig->rule(FuncGetArgsToVariadicParamRector::class);
- $rectorConfig->rule(MakeInheritedMethodVisibilitySameAsParentRector::class);
- $rectorConfig->rule(SimplifyEmptyArrayCheckRector::class);
- $rectorConfig->rule(SimplifyEmptyCheckOnEmptyArrayRector::class);
- $rectorConfig->rule(TernaryEmptyArrayArrayDimFetchToCoalesceRector::class);
- $rectorConfig->rule(EmptyOnNullableObjectToInstanceOfRector::class);
- $rectorConfig->rule(DisallowedEmptyRuleFixerRector::class);
- $rectorConfig->rule(PrivatizeFinalClassPropertyRector::class);
- $rectorConfig->rule(CompleteDynamicPropertiesRector::class);
- $rectorConfig->rule(BooleanInIfConditionRuleFixerRector::class);
- $rectorConfig->rule(SingleInArrayToCompareRector::class);
- $rectorConfig->rule(VersionCompareFuncCallToConstantRector::class);
- $rectorConfig->rule(ExplicitBoolCompareRector::class);
-
- $rectorConfig
- ->ruleWithConfiguration(StringClassNameToClassConstantRector::class, [
- // keep '\\' prefix string on string '\Foo\Bar'
- StringClassNameToClassConstantRector::SHOULD_KEEP_PRE_SLASH => true,
- ]);
-};
+ ->withImportNames(removeUnusedImports: true)
+ ->withRules([
+ DeclareStrictTypesRector::class,
+ UnderscoreToCamelCaseVariableNameRector::class,
+ SimplifyUselessVariableRector::class,
+ RemoveAlwaysElseRector::class,
+ PassStrictParameterToFunctionParameterRector::class,
+ CountArrayToEmptyArrayComparisonRector::class,
+ ChangeNestedForeachIfsToEarlyContinueRector::class,
+ ChangeIfElseValueAssignToEarlyReturnRector::class,
+ SimplifyStrposLowerRector::class,
+ CombineIfRector::class,
+ SimplifyIfReturnBoolRector::class,
+ InlineIfToExplicitIfRector::class,
+ PreparedValueToEarlyReturnRector::class,
+ ShortenElseIfRector::class,
+ SimplifyIfElseToTernaryRector::class,
+ UnusedForeachValueToArrayKeysRector::class,
+ ChangeArrayPushToArrayAssignRector::class,
+ UnnecessaryTernaryExpressionRector::class,
+ RemoveErrorSuppressInTryCatchStmtsRector::class,
+ FuncGetArgsToVariadicParamRector::class,
+ MakeInheritedMethodVisibilitySameAsParentRector::class,
+ SimplifyEmptyArrayCheckRector::class,
+ SimplifyEmptyCheckOnEmptyArrayRector::class,
+ TernaryEmptyArrayArrayDimFetchToCoalesceRector::class,
+ EmptyOnNullableObjectToInstanceOfRector::class,
+ DisallowedEmptyRuleFixerRector::class,
+ PrivatizeFinalClassPropertyRector::class,
+ CompleteDynamicPropertiesRector::class,
+ BooleanInIfConditionRuleFixerRector::class,
+ SingleInArrayToCompareRector::class,
+ VersionCompareFuncCallToConstantRector::class,
+ ExplicitBoolCompareRector::class,
+ AddClosureVoidReturnTypeWhereNoReturnRector::class,
+ AddFunctionVoidReturnTypeWhereNoReturnRector::class,
+ ])
+ ->withConfiguredRule(StringClassNameToClassConstantRector::class, [
+ // keep '\\' prefix string on string '\Foo\Bar'
+ StringClassNameToClassConstantRector::SHOULD_KEEP_PRE_SLASH => true,
+ ]);
diff --git a/system/Autoloader/Autoloader.php b/system/Autoloader/Autoloader.php
index 78d31eae0d7f..28c183f12552 100644
--- a/system/Autoloader/Autoloader.php
+++ b/system/Autoloader/Autoloader.php
@@ -507,7 +507,7 @@ private function autoloadKint(): void
{
// If we have KINT_DIR it means it's already loaded via composer
if (! defined('KINT_DIR')) {
- spl_autoload_register(function ($class) {
+ spl_autoload_register(function ($class): void {
$class = explode('\\', $class);
if (array_shift($class) !== 'Kint') {
diff --git a/system/CLI/BaseCommand.php b/system/CLI/BaseCommand.php
index 86ba504feaa6..afeb80eb5267 100644
--- a/system/CLI/BaseCommand.php
+++ b/system/CLI/BaseCommand.php
@@ -163,7 +163,7 @@ public function showHelp()
if ($this->arguments !== []) {
CLI::newLine();
CLI::write(lang('CLI.helpArguments'), 'yellow');
- $length = max(array_map('strlen', array_keys($this->arguments)));
+ $length = max(array_map(strlen(...), array_keys($this->arguments)));
foreach ($this->arguments as $argument => $description) {
CLI::write(CLI::color($this->setPad($argument, $length, 2, 2), 'green') . $description);
@@ -173,7 +173,7 @@ public function showHelp()
if ($this->options !== []) {
CLI::newLine();
CLI::write(lang('CLI.helpOptions'), 'yellow');
- $length = max(array_map('strlen', array_keys($this->options)));
+ $length = max(array_map(strlen(...), array_keys($this->options)));
foreach ($this->options as $option => $description) {
CLI::write(CLI::color($this->setPad($option, $length, 2, 2), 'green') . $description);
diff --git a/system/CLI/CLI.php b/system/CLI/CLI.php
index 7badf1807dcd..bfe1511eed02 100644
--- a/system/CLI/CLI.php
+++ b/system/CLI/CLI.php
@@ -393,7 +393,7 @@ private static function isZeroOptions(array $options): void
private static function printKeysAndValues(array $options): void
{
// +2 for the square brackets around the key
- $keyMaxLength = max(array_map('mb_strwidth', array_keys($options))) + 2;
+ $keyMaxLength = max(array_map(mb_strwidth(...), array_keys($options))) + 2;
foreach ($options as $key => $description) {
$name = str_pad(' [' . $key . '] ', $keyMaxLength + 4, ' ');
@@ -857,7 +857,7 @@ public static function wrap(?string $string = null, int $max = 0, int $padLeft =
$first = true;
- array_walk($lines, static function (&$line) use ($padLeft, &$first) {
+ array_walk($lines, static function (&$line) use ($padLeft, &$first): void {
if (! $first) {
$line = str_repeat(' ', $padLeft) . $line;
} else {
diff --git a/system/CLI/GeneratorTrait.php b/system/CLI/GeneratorTrait.php
index 065197be5090..6a061a36b8b3 100644
--- a/system/CLI/GeneratorTrait.php
+++ b/system/CLI/GeneratorTrait.php
@@ -325,7 +325,7 @@ private function normalizeInputClassName(): string
implode(
'\\',
array_map(
- 'pascalize',
+ pascalize(...),
explode('\\', str_replace('/', '\\', trim($class)))
)
),
diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php
index 95feae8160a1..bcfb7bd67976 100644
--- a/system/CodeIgniter.php
+++ b/system/CodeIgniter.php
@@ -56,7 +56,7 @@ class CodeIgniter
/**
* The current version of CodeIgniter Framework
*/
- public const CI_VERSION = '4.5.3';
+ public const CI_VERSION = '4.5.4';
/**
* App startup time.
@@ -253,7 +253,7 @@ private function autoloadKint(): void
{
// If we have KINT_DIR it means it's already loaded via composer
if (! defined('KINT_DIR')) {
- spl_autoload_register(function ($class) {
+ spl_autoload_register(function ($class): void {
$class = explode('\\', $class);
if (array_shift($class) !== 'Kint') {
diff --git a/system/Commands/ListCommands.php b/system/Commands/ListCommands.php
index 87c3e96b824a..3411b103430d 100644
--- a/system/Commands/ListCommands.php
+++ b/system/Commands/ListCommands.php
@@ -101,7 +101,7 @@ protected function listFull(array $commands)
$groups[$command['group']][$title] = $command;
}
- $length = max(array_map('strlen', array_keys($commands)));
+ $length = max(array_map(strlen(...), array_keys($commands)));
ksort($groups);
diff --git a/system/Commands/Utilities/Routes.php b/system/Commands/Utilities/Routes.php
index f9575c063973..9adcc1bc39e7 100644
--- a/system/Commands/Utilities/Routes.php
+++ b/system/Commands/Utilities/Routes.php
@@ -119,8 +119,8 @@ public function run(array $params)
$route['route'],
$routeName,
$route['handler'],
- implode(' ', array_map('class_basename', $filters['before'])),
- implode(' ', array_map('class_basename', $filters['after'])),
+ implode(' ', array_map(class_basename(...), $filters['before'])),
+ implode(' ', array_map(class_basename(...), $filters['after'])),
];
}
@@ -166,8 +166,8 @@ public function run(array $params)
// There is no `AUTO` method, but it is intentional not to get route filters.
$filters = $filterCollector->get('AUTO', $uriGenerator->get($routes[1]));
- $routes[] = implode(' ', array_map('class_basename', $filters['before']));
- $routes[] = implode(' ', array_map('class_basename', $filters['after']));
+ $routes[] = implode(' ', array_map(class_basename(...), $filters['before']));
+ $routes[] = implode(' ', array_map(class_basename(...), $filters['after']));
}
}
diff --git a/system/Commands/Utilities/Routes/AutoRouterImproved/AutoRouteCollector.php b/system/Commands/Utilities/Routes/AutoRouterImproved/AutoRouteCollector.php
index 0d426a3b1716..579969346637 100644
--- a/system/Commands/Utilities/Routes/AutoRouterImproved/AutoRouteCollector.php
+++ b/system/Commands/Utilities/Routes/AutoRouterImproved/AutoRouteCollector.php
@@ -125,8 +125,8 @@ private function addFilters($routes)
$filters['before'] = array_intersect($filtersLongest['before'], $filtersShortest['before']);
$filters['after'] = array_intersect($filtersLongest['after'], $filtersShortest['after']);
- $route['before'] = implode(' ', array_map('class_basename', $filters['before']));
- $route['after'] = implode(' ', array_map('class_basename', $filters['after']));
+ $route['before'] = implode(' ', array_map(class_basename(...), $filters['before']));
+ $route['after'] = implode(' ', array_map(class_basename(...), $filters['after']));
}
return $routes;
diff --git a/system/Common.php b/system/Common.php
index f96e9f100f00..49b3d2896753 100644
--- a/system/Common.php
+++ b/system/Common.php
@@ -766,10 +766,8 @@ function lang(string $line, array $args = [], ?string $locale = null)
* - notice
* - info
* - debug
- *
- * @return void
*/
- function log_message(string $level, string $message, array $context = [])
+ function log_message(string $level, string $message, array $context = []): void
{
// When running tests, we want to always ensure that the
// TestLogger is running, which provides utilities for
diff --git a/system/Config/BaseService.php b/system/Config/BaseService.php
index 5bd55403beba..687044be0713 100644
--- a/system/Config/BaseService.php
+++ b/system/Config/BaseService.php
@@ -75,6 +75,7 @@
use Config\Optimize;
use Config\Pager as ConfigPager;
use Config\Services as AppServices;
+use Config\Session as ConfigSession;
use Config\Toolbar as ConfigToolbar;
use Config\Validation as ConfigValidation;
use Config\View as ConfigView;
@@ -130,7 +131,7 @@
* @method static Router router(RouteCollectionInterface $routes = null, Request $request = null, $getShared = true)
* @method static RouteCollection routes($getShared = true)
* @method static Security security(App $config = null, $getShared = true)
- * @method static Session session(App $config = null, $getShared = true)
+ * @method static Session session(ConfigSession $config = null, $getShared = true)
* @method static SiteURIFactory siteurifactory(App $config = null, Superglobals $superglobals = null, $getShared = true)
* @method static Superglobals superglobals(array $server = null, array $get = null, bool $getShared = true)
* @method static Throttler throttler($getShared = true)
diff --git a/system/Config/Services.php b/system/Config/Services.php
index 422799e0b87c..e37b1278e9a7 100644
--- a/system/Config/Services.php
+++ b/system/Config/Services.php
@@ -51,6 +51,7 @@
use CodeIgniter\Router\RouteCollectionInterface;
use CodeIgniter\Router\Router;
use CodeIgniter\Security\Security;
+use CodeIgniter\Session\Handlers\BaseHandler as SessionBaseHandler;
use CodeIgniter\Session\Handlers\Database\MySQLiHandler;
use CodeIgniter\Session\Handlers\Database\PostgreHandler;
use CodeIgniter\Session\Handlers\DatabaseHandler;
@@ -88,6 +89,7 @@
use Config\Toolbar as ToolbarConfig;
use Config\Validation as ValidationConfig;
use Config\View as ViewConfig;
+use InvalidArgumentException;
use Locale;
/**
@@ -674,17 +676,24 @@ public static function session(?SessionConfig $config = null, bool $getShared =
if ($driverName === DatabaseHandler::class) {
$DBGroup = $config->DBGroup ?? config(Database::class)->defaultGroup;
- $db = Database::connect($DBGroup);
- $driver = $db->getPlatform();
+ $driverPlatform = Database::connect($DBGroup)->getPlatform();
- if ($driver === 'MySQLi') {
+ if ($driverPlatform === 'MySQLi') {
$driverName = MySQLiHandler::class;
- } elseif ($driver === 'Postgre') {
+ } elseif ($driverPlatform === 'Postgre') {
$driverName = PostgreHandler::class;
}
}
+ if (! class_exists($driverName) || ! is_a($driverName, SessionBaseHandler::class, true)) {
+ throw new InvalidArgumentException(sprintf(
+ 'Invalid session handler "%s" provided.',
+ $driverName
+ ));
+ }
+
+ /** @var SessionBaseHandler $driver */
$driver = new $driverName($config, AppServices::get('request')->getIPAddress());
$driver->setLogger($logger);
diff --git a/system/Cookie/Cookie.php b/system/Cookie/Cookie.php
index 78dc44258eb9..01b10db19b65 100644
--- a/system/Cookie/Cookie.php
+++ b/system/Cookie/Cookie.php
@@ -283,7 +283,7 @@ public function getPrefixedName(): string
$name .= $this->getName();
} else {
$search = str_split(self::$reservedCharsList);
- $replace = array_map('rawurlencode', $search);
+ $replace = array_map(rawurlencode(...), $search);
$name .= str_replace($search, $replace, $this->getName());
}
diff --git a/system/DataCaster/DataCaster.php b/system/DataCaster/DataCaster.php
index 29de3987faa9..5a6786a03331 100644
--- a/system/DataCaster/DataCaster.php
+++ b/system/DataCaster/DataCaster.php
@@ -156,7 +156,7 @@ public function castAs(mixed $value, string $field, string $method = 'get'): mix
// type[param, param2,param3]
if (preg_match('/\A(.+)\[(.+)\]\z/', $type, $matches)) {
$type = $matches[1];
- $params = array_map('trim', explode(',', $matches[2]));
+ $params = array_map(trim(...), explode(',', $matches[2]));
}
if ($isNullable && ! $this->strict) {
diff --git a/system/DataConverter/DataConverter.php b/system/DataConverter/DataConverter.php
index b79ede5b08ff..43b42a08bbdf 100644
--- a/system/DataConverter/DataConverter.php
+++ b/system/DataConverter/DataConverter.php
@@ -140,7 +140,7 @@ public function reconstruct(string $classname, array $row): object
return $classObj;
}
- $classSet = Closure::bind(function ($key, $value) {
+ $classSet = Closure::bind(function ($key, $value): void {
$this->{$key} = $value;
}, $classObj, $classname);
diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php
index 4e6e422b2a6f..649a201112e4 100644
--- a/system/Database/BaseBuilder.php
+++ b/system/Database/BaseBuilder.php
@@ -124,9 +124,9 @@ class BaseBuilder
protected array $QBUnion = [];
/**
- * QB NO ESCAPE data
+ * Whether to protect identifiers in SELECT
*
- * @var array
+ * @var list true=protect, false=not protect
*/
public $QBNoEscape = [];
@@ -390,7 +390,8 @@ public function ignore(bool $ignore = true)
/**
* Generates the SELECT portion of the query
*
- * @param array|RawSql|string $select
+ * @param list|RawSql|string $select
+ * @param bool|null $escape Whether to protect identifiers
*
* @return $this
*/
@@ -402,16 +403,21 @@ public function select($select = '*', ?bool $escape = null)
}
if ($select instanceof RawSql) {
- $this->QBSelect[] = $select;
-
- return $this;
+ $select = [$select];
}
if (is_string($select)) {
- $select = $escape === false ? [$select] : explode(',', $select);
+ $select = ($escape === false) ? [$select] : explode(',', $select);
}
foreach ($select as $val) {
+ if ($val instanceof RawSql) {
+ $this->QBSelect[] = $val;
+ $this->QBNoEscape[] = false;
+
+ continue;
+ }
+
$val = trim($val);
if ($val !== '') {
@@ -424,8 +430,10 @@ public function select($select = '*', ?bool $escape = null)
* This prevents NULL being escaped
* @see https://github.com/codeigniter4/CodeIgniter4/issues/1169
*/
- if (mb_stripos(trim($val), 'NULL') === 0) {
- $escape = false;
+ if (mb_stripos($val, 'NULL') === 0) {
+ $this->QBNoEscape[] = false;
+
+ continue;
}
$this->QBNoEscape[] = $escape;
@@ -3054,15 +3062,17 @@ protected function compileSelect($selectOverride = false): string
if (empty($this->QBSelect)) {
$sql .= '*';
- } elseif ($this->QBSelect[0] instanceof RawSql) {
- $sql .= (string) $this->QBSelect[0];
} else {
// Cycle through the "select" portion of the query and prep each column name.
// The reason we protect identifiers here rather than in the select() function
// is because until the user calls the from() function we don't know if there are aliases
foreach ($this->QBSelect as $key => $val) {
- $noEscape = $this->QBNoEscape[$key] ?? null;
- $this->QBSelect[$key] = $this->db->protectIdentifiers($val, false, $noEscape);
+ if ($val instanceof RawSql) {
+ $this->QBSelect[$key] = (string) $val;
+ } else {
+ $protect = $this->QBNoEscape[$key] ?? null;
+ $this->QBSelect[$key] = $this->db->protectIdentifiers($val, false, $protect);
+ }
}
$sql .= implode(', ', $this->QBSelect);
diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php
index 6581dc7c5ac0..dce399bdefb5 100644
--- a/system/Database/BaseConnection.php
+++ b/system/Database/BaseConnection.php
@@ -1519,7 +1519,7 @@ public function tableExists(string $tableName, bool $cached = true): bool
if (! empty($this->dataCache['table_names'])) {
$key = array_search(
strtolower($tableName),
- array_map('strtolower', $this->dataCache['table_names']),
+ array_map(strtolower(...), $this->dataCache['table_names']),
true
);
diff --git a/system/Database/Forge.php b/system/Database/Forge.php
index 797b685e861c..595cd6156b5f 100644
--- a/system/Database/Forge.php
+++ b/system/Database/Forge.php
@@ -306,7 +306,7 @@ public function dropDatabase(string $dbName): bool
if (! empty($this->db->dataCache['db_names'])) {
$key = array_search(
strtolower($dbName),
- array_map('strtolower', $this->db->dataCache['db_names']),
+ array_map(strtolower(...), $this->db->dataCache['db_names']),
true
);
if ($key !== false) {
@@ -667,7 +667,7 @@ public function dropTable(string $tableName, bool $ifExists = false, bool $casca
if ($query && ! empty($this->db->dataCache['table_names'])) {
$key = array_search(
strtolower($this->db->DBPrefix . $tableName),
- array_map('strtolower', $this->db->dataCache['table_names']),
+ array_map(strtolower(...), $this->db->dataCache['table_names']),
true
);
@@ -729,7 +729,7 @@ public function renameTable(string $tableName, string $newTableName)
if ($result && ! empty($this->db->dataCache['table_names'])) {
$key = array_search(
strtolower($this->db->DBPrefix . $tableName),
- array_map('strtolower', $this->db->dataCache['table_names']),
+ array_map(strtolower(...), $this->db->dataCache['table_names']),
true
);
diff --git a/system/Database/MigrationRunner.php b/system/Database/MigrationRunner.php
index 719d45c15592..055b8ba6eaf3 100644
--- a/system/Database/MigrationRunner.php
+++ b/system/Database/MigrationRunner.php
@@ -686,7 +686,7 @@ public function getBatches(): array
->get()
->getResultArray();
- return array_map('intval', array_column($batches, 'batch'));
+ return array_map(intval(...), array_column($batches, 'batch'));
}
/**
diff --git a/system/Database/OCI8/Connection.php b/system/Database/OCI8/Connection.php
index f7983a695c0c..2b870b755b44 100644
--- a/system/Database/OCI8/Connection.php
+++ b/system/Database/OCI8/Connection.php
@@ -53,10 +53,24 @@ class Connection extends BaseConnection
];
protected $validDSNs = [
- 'tns' => '/^\(DESCRIPTION=(\(.+\)){2,}\)$/', // TNS
- // Easy Connect string (Oracle 10g+)
- 'ec' => '/^(\/\/)?[a-z0-9.:_-]+(:[1-9][0-9]{0,4})?(\/[a-z0-9$_]+)?(:[^\/])?(\/[a-z0-9$_]+)?$/i',
- 'in' => '/^[a-z0-9$_]+$/i', // Instance name (defined in tnsnames.ora)
+ // TNS
+ 'tns' => '/^\(DESCRIPTION=(\(.+\)){2,}\)$/',
+ // Easy Connect string (Oracle 10g+).
+ // https://docs.oracle.com/en/database/oracle/oracle-database/23/netag/configuring-naming-methods.html#GUID-36F3A17D-843C-490A-8A23-FB0FE005F8E8
+ // [//]host[:port][/[service_name][:server_type][/instance_name]]
+ 'ec' => '/^
+ (\/\/)?
+ (\[)?[a-z0-9.:_-]+(\])? # Host or IP address
+ (:[1-9][0-9]{0,4})? # Port
+ (
+ (\/)
+ ([a-z0-9.$_]+)? # Service name
+ (:[a-z]+)? # Server type
+ (\/[a-z0-9$_]+)? # Instance name
+ )?
+ $/ix',
+ // Instance name (defined in tnsnames.ora)
+ 'in' => '/^[a-z0-9$_]+$/i',
];
/**
diff --git a/system/Database/OCI8/Forge.php b/system/Database/OCI8/Forge.php
index 69a42ed1dbeb..5a10363be769 100644
--- a/system/Database/OCI8/Forge.php
+++ b/system/Database/OCI8/Forge.php
@@ -202,7 +202,7 @@ protected function _processColumn(array $processedField): string
$constraint = ' CHECK(' . $this->db->escapeIdentifiers($processedField['name'])
. ' IN ' . $processedField['length'] . ')';
- $processedField['length'] = '(' . max(array_map('mb_strlen', explode("','", mb_substr($processedField['length'], 2, -2)))) . ')' . $constraint;
+ $processedField['length'] = '(' . max(array_map(mb_strlen(...), explode("','", mb_substr($processedField['length'], 2, -2)))) . ')' . $constraint;
} elseif (isset($this->primaryKeys['fields']) && count($this->primaryKeys['fields']) === 1 && $processedField['name'] === $this->primaryKeys['fields'][0]) {
$processedField['unique'] = '';
}
diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php
index 857760930275..1071dfedf06b 100644
--- a/system/Database/Postgre/Builder.php
+++ b/system/Database/Postgre/Builder.php
@@ -159,7 +159,7 @@ public function replace(?array $set = null)
$table = $this->QBFrom[0];
$set = $this->binds;
- array_walk($set, static function (array &$item) {
+ array_walk($set, static function (array &$item): void {
$item = $item[0];
});
diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php
index d3af2380156e..ec886dbd4d8b 100644
--- a/system/Database/Postgre/Connection.php
+++ b/system/Database/Postgre/Connection.php
@@ -58,8 +58,7 @@ class Connection extends BaseConnection
/**
* Connect to the database.
*
- * @return false|resource
- * @phpstan-return false|PgSqlConnection
+ * @return false|PgSqlConnection
*/
public function connect(bool $persistent = false)
{
@@ -197,8 +196,7 @@ public function getVersion(): string
/**
* Executes the query against the database.
*
- * @return false|resource
- * @phpstan-return false|PgSqlResult
+ * @return false|PgSqlResult
*/
protected function execute(string $sql)
{
diff --git a/system/Database/Postgre/PreparedQuery.php b/system/Database/Postgre/PreparedQuery.php
index 832108675ab9..fbea6ac14f3a 100644
--- a/system/Database/Postgre/PreparedQuery.php
+++ b/system/Database/Postgre/PreparedQuery.php
@@ -95,8 +95,7 @@ public function _execute(array $data): bool
/**
* Returns the result object for the prepared query or false on failure.
*
- * @return resource|null
- * @phpstan-return PgSqlResult|null
+ * @return PgSqlResult|null
*/
public function _getResult()
{
diff --git a/system/Database/Query.php b/system/Database/Query.php
index 11085a91f6a0..ba8e9066063f 100644
--- a/system/Database/Query.php
+++ b/system/Database/Query.php
@@ -118,7 +118,7 @@ public function setQuery(string $sql, $binds = null, bool $setEscape = true)
}
if ($setEscape) {
- array_walk($binds, static function (&$item) {
+ array_walk($binds, static function (&$item): void {
$item = [
$item,
true,
@@ -141,7 +141,7 @@ public function setQuery(string $sql, $binds = null, bool $setEscape = true)
public function setBinds(array $binds, bool $setEscape = true)
{
if ($setEscape) {
- array_walk($binds, static function (&$item) {
+ array_walk($binds, static function (&$item): void {
$item = [$item, true];
});
}
diff --git a/system/Database/SQLSRV/Builder.php b/system/Database/SQLSRV/Builder.php
index e2301f7ee2ab..a6d0b8e85ccd 100644
--- a/system/Database/SQLSRV/Builder.php
+++ b/system/Database/SQLSRV/Builder.php
@@ -420,7 +420,7 @@ protected function _replace(string $table, array $keys, array $values): string
// Get the binds
$binds = $this->binds;
- array_walk($binds, static function (&$item) {
+ array_walk($binds, static function (&$item): void {
$item = $item[0];
});
diff --git a/system/Database/SQLite3/Forge.php b/system/Database/SQLite3/Forge.php
index 32e447829c29..48e9abcc425c 100644
--- a/system/Database/SQLite3/Forge.php
+++ b/system/Database/SQLite3/Forge.php
@@ -101,7 +101,7 @@ public function dropDatabase(string $dbName): bool
}
if (! empty($this->db->dataCache['db_names'])) {
- $key = array_search(strtolower($dbName), array_map('strtolower', $this->db->dataCache['db_names']), true);
+ $key = array_search(strtolower($dbName), array_map(strtolower(...), $this->db->dataCache['db_names']), true);
if ($key !== false) {
unset($this->db->dataCache['db_names'][$key]);
}
diff --git a/system/Database/SQLite3/Result.php b/system/Database/SQLite3/Result.php
index 70d8dc47d2b6..aa5abf28682a 100644
--- a/system/Database/SQLite3/Result.php
+++ b/system/Database/SQLite3/Result.php
@@ -147,7 +147,7 @@ protected function fetchObject(string $className = 'stdClass')
return $classObj->injectRawData($row);
}
- $classSet = Closure::bind(function ($key, $value) {
+ $classSet = Closure::bind(function ($key, $value): void {
$this->{$key} = $value;
}, $classObj, $className);
diff --git a/system/Debug/Toolbar.php b/system/Debug/Toolbar.php
index 115016225062..9f20f987f59c 100644
--- a/system/Debug/Toolbar.php
+++ b/system/Debug/Toolbar.php
@@ -294,7 +294,7 @@ protected function collectTimelineData($collectors): array
array_multisort(...$sortArray);
// Add end time to each element
- array_walk($data, static function (&$row) {
+ array_walk($data, static function (&$row): void {
$row['end'] = $row['start'] + $row['duration'];
});
diff --git a/system/Debug/Toolbar/Views/toolbar.css b/system/Debug/Toolbar/Views/toolbar.css
index 2e165b825e99..b9f13c51ff2e 100644
--- a/system/Debug/Toolbar/Views/toolbar.css
+++ b/system/Debug/Toolbar/Views/toolbar.css
@@ -60,6 +60,7 @@
margin-right: 5px;
}
#debug-bar h2 {
+ font-weight: bold;
font-size: 16px;
margin: 0;
padding: 5px 0 10px 0;
@@ -213,6 +214,8 @@
white-space: nowrap;
}
#debug-bar .tab {
+ height: fit-content;
+ text-align: left;
bottom: 35px;
display: none;
left: 0;
@@ -225,6 +228,8 @@
z-index: 9999;
}
#debug-bar .timeline {
+ position: static;
+ display: table;
margin-left: 0;
width: 100%;
}
diff --git a/system/Files/File.php b/system/Files/File.php
index 02c8f181b19b..15b53262e9c2 100644
--- a/system/Files/File.php
+++ b/system/Files/File.php
@@ -59,7 +59,8 @@ public function __construct(string $path, bool $checkFile = false)
*
* Implementations SHOULD return the value stored in the "size" key of
* the file in the $_FILES array if available, as PHP calculates this based
- * on the actual size transmitted.
+ * on the actual size transmitted. A RuntimeException will be thrown if the file
+ * does not exist or an error occurs.
*
* @return false|int The file size in bytes, or false on failure
*/
diff --git a/system/Filters/Filters.php b/system/Filters/Filters.php
index d8e22835e2ed..1255e0c4055d 100644
--- a/system/Filters/Filters.php
+++ b/system/Filters/Filters.php
@@ -522,7 +522,7 @@ private function getCleanName(string $name): array
[$name, $arguments] = explode(':', $name);
$arguments = explode(',', $arguments);
- array_walk($arguments, static function (&$item) {
+ array_walk($arguments, static function (&$item): void {
$item = trim($item);
});
}
diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php
index 4b1c9c625399..9da231c17716 100644
--- a/system/HTTP/CURLRequest.php
+++ b/system/HTTP/CURLRequest.php
@@ -651,11 +651,12 @@ protected function setCURLOptions(array $curlOptions = [], array $config = [])
// version
if (! empty($config['version'])) {
- if ($config['version'] === 1.0) {
+ $version = sprintf('%.1F', $config['version']);
+ if ($version === '1.0') {
$curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
- } elseif ($config['version'] === 1.1) {
+ } elseif ($version === '1.1') {
$curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
- } elseif ($config['version'] === 2.0) {
+ } elseif ($version === '2.0') {
$curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
}
}
diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php
index ec13e40f6ff5..c905de0a3ae3 100644
--- a/system/HTTP/IncomingRequest.php
+++ b/system/HTTP/IncomingRequest.php
@@ -586,7 +586,7 @@ public function getJsonVar($index = null, bool $assoc = false, ?int $filter = nu
) {
if (is_array($data)) {
// Iterate over array and append filter and flags
- array_walk_recursive($data, static function (&$val) use ($filter, $flags) {
+ array_walk_recursive($data, static function (&$val) use ($filter, $flags): void {
$valType = gettype($val);
$val = filter_var($val, $filter, $flags);
@@ -672,7 +672,7 @@ public function getRawInputVar($index = null, ?int $filter = null, $flags = null
)
) {
// Iterate over array and append filter and flags
- array_walk_recursive($output, static function (&$val) use ($filter, $flags) {
+ array_walk_recursive($output, static function (&$val) use ($filter, $flags): void {
$val = filter_var($val, $filter, $flags);
});
diff --git a/system/HTTP/RequestTrait.php b/system/HTTP/RequestTrait.php
index cfdc52b55f69..43a4f23a0e87 100644
--- a/system/HTTP/RequestTrait.php
+++ b/system/HTTP/RequestTrait.php
@@ -322,7 +322,7 @@ public function fetchGlobal(string $name, $index = null, ?int $filter = null, $f
)
) {
// Iterate over array and append filter and flags
- array_walk_recursive($value, static function (&$val) use ($filter, $flags) {
+ array_walk_recursive($value, static function (&$val) use ($filter, $flags): void {
$val = filter_var($val, $filter, $flags);
});
diff --git a/system/Helpers/cookie_helper.php b/system/Helpers/cookie_helper.php
index 49e568346b68..27007104e68e 100644
--- a/system/Helpers/cookie_helper.php
+++ b/system/Helpers/cookie_helper.php
@@ -35,8 +35,6 @@
* @param bool|null $httpOnly True makes the cookie accessible via http(s) only (no javascript)
* @param string|null $sameSite The cookie SameSite value
*
- * @return void
- *
* @see \CodeIgniter\HTTP\Response::setCookie()
*/
function set_cookie(
@@ -49,7 +47,7 @@ function set_cookie(
?bool $secure = null,
?bool $httpOnly = null,
?string $sameSite = null
- ) {
+ ): void {
$response = service('response');
$response->setCookie($name, $value, $expire, $domain, $path, $prefix, $secure, $httpOnly, $sameSite);
}
@@ -92,11 +90,9 @@ function get_cookie($index, bool $xssClean = false, ?string $prefix = '')
* @param string $path the cookie path
* @param string $prefix the cookie prefix
*
- * @return void
- *
* @see \CodeIgniter\HTTP\Response::deleteCookie()
*/
- function delete_cookie($name, string $domain = '', string $path = '/', string $prefix = '')
+ function delete_cookie($name, string $domain = '', string $path = '/', string $prefix = ''): void
{
service('response')->deleteCookie($name, $domain, $path, $prefix);
}
diff --git a/system/Helpers/kint_helper.php b/system/Helpers/kint_helper.php
index 075c162315aa..1f89dc78c55b 100644
--- a/system/Helpers/kint_helper.php
+++ b/system/Helpers/kint_helper.php
@@ -24,7 +24,7 @@
*
* @codeCoverageIgnore Can't be tested ... exits
*/
- function dd(...$vars)
+ function dd(...$vars): void
{
// @codeCoverageIgnoreStart
Kint::$aliases[] = 'dd';
@@ -71,10 +71,8 @@ function d(...$vars)
*/
/**
* trace function
- *
- * @return void
*/
- function trace()
+ function trace(): void
{
Kint::$aliases[] = 'trace';
Kint::trace();
diff --git a/system/Helpers/text_helper.php b/system/Helpers/text_helper.php
index 7f5fb2cf2d3f..ae6b6f111c81 100644
--- a/system/Helpers/text_helper.php
+++ b/system/Helpers/text_helper.php
@@ -310,7 +310,7 @@ function convert_accented_characters(string $str): string
if (! is_array($arrayFrom)) {
$config = new ForeignCharacters();
- if (empty($config->characterList) || ! is_array($config->characterList)) {
+ if ($config->characterList === [] || ! is_array($config->characterList)) {
$arrayFrom = [];
$arrayTo = [];
diff --git a/system/Images/Handlers/GDHandler.php b/system/Images/Handlers/GDHandler.php
index 350dbc2306a8..062a69d89212 100644
--- a/system/Images/Handlers/GDHandler.php
+++ b/system/Images/Handlers/GDHandler.php
@@ -471,7 +471,7 @@ protected function textOverlay(string $text, array $options = [], bool $isShadow
// shorthand hex, #f00
if (strlen($color) === 3) {
- $color = implode('', array_map('str_repeat', str_split($color), [2, 2, 2]));
+ $color = implode('', array_map(str_repeat(...), str_split($color), [2, 2, 2]));
}
$color = str_split(substr($color, 0, 6), 2);
diff --git a/system/Router/RouteCollection.php b/system/Router/RouteCollection.php
index 3198d342aa9d..8fba39dc03be 100644
--- a/system/Router/RouteCollection.php
+++ b/system/Router/RouteCollection.php
@@ -853,7 +853,7 @@ public function resource(string $name, ?array $options = null): RouteCollectionI
// In order to allow customization of the route the
// resources are sent to, we need to have a new name
// to store the values in.
- $newName = implode('\\', array_map('ucfirst', explode('/', $name)));
+ $newName = implode('\\', array_map(ucfirst(...), explode('/', $name)));
// If a new controller is specified, then we replace the
// $name value with the name of the new controller.
@@ -947,7 +947,7 @@ public function presenter(string $name, ?array $options = null): RouteCollection
// In order to allow customization of the route the
// resources are sent to, we need to have a new name
// to store the values in.
- $newName = implode('\\', array_map('ucfirst', explode('/', $name)));
+ $newName = implode('\\', array_map(ucfirst(...), explode('/', $name)));
// If a new controller is specified, then we replace the
// $name value with the name of the new controller.
@@ -1456,6 +1456,12 @@ protected function create(string $verb, string $from, $to, ?array $options = nul
$to = $this->processArrayCallableSyntax($from, $to);
}
+ // Merge group filters.
+ if (isset($options['filter'])) {
+ $currentFilter = (array) ($this->currentOptions['filter'] ?? []);
+ $options['filter'] = array_merge($currentFilter, (array) $options['filter']);
+ }
+
$options = array_merge($this->currentOptions ?? [], $options ?? []);
// Route priority detect
diff --git a/system/Security/CheckPhpIni.php b/system/Security/CheckPhpIni.php
index 4cef565ad7d6..1e92cdc403aa 100644
--- a/system/Security/CheckPhpIni.php
+++ b/system/Security/CheckPhpIni.php
@@ -50,17 +50,17 @@ public static function run(bool $isCli = true)
private static function outputForCli(array $output, array $thead, array $tbody): void
{
foreach ($output as $directive => $values) {
- $current = $values['current'];
+ $current = $values['current'] ?? '';
$notRecommended = false;
if ($values['recommended'] !== '') {
- if ($values['recommended'] !== $values['current']) {
+ if ($values['recommended'] !== $current) {
$notRecommended = true;
}
$current = $notRecommended
- ? CLI::color($values['current'] === '' ? 'n/a' : $values['current'], 'red')
- : $values['current'];
+ ? CLI::color($current === '' ? 'n/a' : $current, 'red')
+ : $current;
}
$directive = $notRecommended ? CLI::color($directive, 'red') : $directive;
diff --git a/system/Test/DatabaseTestTrait.php b/system/Test/DatabaseTestTrait.php
index 863ed542319d..c0d4473c7b9e 100644
--- a/system/Test/DatabaseTestTrait.php
+++ b/system/Test/DatabaseTestTrait.php
@@ -19,6 +19,7 @@
use Config\Database;
use Config\Migrations;
use Config\Services;
+use PHPUnit\Framework\Attributes\AfterClass;
/**
* DatabaseTestTrait
@@ -228,14 +229,12 @@ public function seed(string $name)
// --------------------------------------------------------------------
// Utility
// --------------------------------------------------------------------
-
/**
* Reset $doneMigration and $doneSeed
*
- * @afterClass
- *
* @return void
*/
+ #[AfterClass]
public static function resetMigrationSeedCount()
{
self::$doneMigration = false;
diff --git a/system/Throttle/Throttler.php b/system/Throttle/Throttler.php
index 44d12005a80d..c023b5e4d052 100644
--- a/system/Throttle/Throttler.php
+++ b/system/Throttle/Throttler.php
@@ -102,8 +102,11 @@ public function check(string $key, int $capacity, int $seconds, int $cost = 1):
// Number of seconds to get one token
$refresh = 1 / $rate;
+ /** @var float|int|null $tokens */
+ $tokens = $this->cache->get($tokenName);
+
// Check to see if the bucket has even been created yet.
- if (($tokens = $this->cache->get($tokenName)) === null) {
+ if ($tokens === null) {
// If it hasn't been created, then we'll set it to the maximum
// capacity - 1, and save it to the cache.
$tokens = $capacity - $cost;
@@ -124,7 +127,7 @@ public function check(string $key, int $capacity, int $seconds, int $cost = 1):
// should be refilled, then checked against capacity
// to be sure the bucket didn't overflow.
$tokens += $rate * $elapsed;
- $tokens = $tokens > $capacity ? $capacity : $tokens;
+ $tokens = min($tokens, $capacity);
// If $tokens >= 1, then we are safe to perform the action, but
// we need to decrement the number of available tokens.
diff --git a/system/Validation/Rules.php b/system/Validation/Rules.php
index 272b2dcf61fe..04e8a0546e77 100644
--- a/system/Validation/Rules.php
+++ b/system/Validation/Rules.php
@@ -159,7 +159,7 @@ public function in_list($value, string $list): bool
$value = (string) $value;
}
- $list = array_map('trim', explode(',', $list));
+ $list = array_map(trim(...), explode(',', $list));
return in_array($value, $list, true);
}
diff --git a/tests/system/Config/ServicesTest.php b/tests/system/Config/ServicesTest.php
index 7825687841c7..e80e2d394eeb 100644
--- a/tests/system/Config/ServicesTest.php
+++ b/tests/system/Config/ServicesTest.php
@@ -46,6 +46,9 @@
use Config\App;
use Config\Exceptions;
use Config\Security as SecurityConfig;
+use Config\Session as ConfigSession;
+use InvalidArgumentException;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\PreserveGlobalState;
use PHPUnit\Framework\Attributes\RunInSeparateProcess;
@@ -259,6 +262,32 @@ public function testNewSessionWithNullConfig(): void
$this->assertInstanceOf(Session::class, $actual);
}
+ #[DataProvider('provideNewSessionInvalid')]
+ #[PreserveGlobalState(false)]
+ #[RunInSeparateProcess]
+ public function testNewSessionWithInvalidHandler(string $driver): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage(sprintf('Invalid session handler "%s" provided.', $driver));
+
+ $config = new ConfigSession();
+
+ $config->driver = $driver;
+ Services::session($config, false);
+ }
+
+ /**
+ * @return iterable
+ */
+ public static function provideNewSessionInvalid(): iterable
+ {
+ yield 'just a string' => ['file'];
+
+ yield 'inexistent class' => ['Foo'];
+
+ yield 'other class' => [self::class];
+ }
+
#[PreserveGlobalState(false)]
#[RunInSeparateProcess]
public function testCallStatic(): void
diff --git a/tests/system/Database/Builder/SelectTest.php b/tests/system/Database/Builder/SelectTest.php
index cd3f39029f6b..411e26a695bf 100644
--- a/tests/system/Database/Builder/SelectTest.php
+++ b/tests/system/Database/Builder/SelectTest.php
@@ -19,6 +19,7 @@
use CodeIgniter\Database\SQLSRV\Builder as SQLSRVBuilder;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\Mock\MockConnection;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
/**
@@ -67,6 +68,65 @@ public function testSelectAcceptsArray(): void
$this->assertSame($expected, str_replace("\n", ' ', $builder->getCompiledSelect()));
}
+ /**
+ * @param list $select
+ */
+ #[DataProvider('provideSelectAcceptsArrayWithRawSql')]
+ public function testSelectAcceptsArrayWithRawSql(array $select, string $expected): void
+ {
+ $builder = new BaseBuilder('employees', $this->db);
+
+ $builder->select($select);
+
+ $this->assertSame($expected, str_replace("\n", ' ', $builder->getCompiledSelect()));
+ }
+
+ /**
+ * @return list|string>
+ */
+ public static function provideSelectAcceptsArrayWithRawSql(): iterable
+ {
+ yield from [
+ [
+ [
+ new RawSql("IF(salary > 5000, 'High', 'Low') AS salary_level"),
+ 'employee_id',
+ ],
+ <<<'SQL'
+ SELECT IF(salary > 5000, 'High', 'Low') AS salary_level, "employee_id" FROM "employees"
+ SQL,
+ ],
+ [
+ [
+ 'employee_id',
+ new RawSql("IF(salary > 5000, 'High', 'Low') AS salary_level"),
+ ],
+ <<<'SQL'
+ SELECT "employee_id", IF(salary > 5000, 'High', 'Low') AS salary_level FROM "employees"
+ SQL,
+ ],
+ [
+ [
+ new RawSql("CONCAT(first_name, ' ', last_name) AS full_name"),
+ new RawSql("IF(salary > 5000, 'High', 'Low') AS salary_level"),
+ ],
+ <<<'SQL'
+ SELECT CONCAT(first_name, ' ', last_name) AS full_name, IF(salary > 5000, 'High', 'Low') AS salary_level FROM "employees"
+ SQL,
+ ],
+ [
+ [
+ new RawSql("CONCAT(first_name, ' ', last_name) AS full_name"),
+ 'employee_id',
+ new RawSql("IF(salary > 5000, 'High', 'Low') AS salary_level"),
+ ],
+ <<<'SQL'
+ SELECT CONCAT(first_name, ' ', last_name) AS full_name, "employee_id", IF(salary > 5000, 'High', 'Low') AS salary_level FROM "employees"
+ SQL,
+ ],
+ ];
+ }
+
public function testSelectAcceptsMultipleColumns(): void
{
$builder = new BaseBuilder('users', $this->db);
@@ -100,6 +160,28 @@ public function testSelectWorksWithComplexSelects(): void
$this->assertSame($expected, str_replace("\n", ' ', $builder->getCompiledSelect()));
}
+ public function testSelectNullAsInString(): void
+ {
+ $builder = new BaseBuilder('users', $this->db);
+
+ $builder->select('NULL as field_alias, name');
+
+ $expected = 'SELECT NULL as field_alias, "name" FROM "users"';
+
+ $this->assertSame($expected, str_replace("\n", ' ', $builder->getCompiledSelect()));
+ }
+
+ public function testSelectNullAsInArray(): void
+ {
+ $builder = new BaseBuilder('users', $this->db);
+
+ $builder->select(['NULL as field_alias', 'name']);
+
+ $expected = 'SELECT NULL as field_alias, "name" FROM "users"';
+
+ $this->assertSame($expected, str_replace("\n", ' ', $builder->getCompiledSelect()));
+ }
+
/**
* @see https://github.com/codeigniter4/CodeIgniter4/issues/4355
*/
diff --git a/tests/system/Database/Live/OCI8/ConnectionTest.php b/tests/system/Database/Live/OCI8/ConnectionTest.php
new file mode 100644
index 000000000000..caf97ed632fd
--- /dev/null
+++ b/tests/system/Database/Live/OCI8/ConnectionTest.php
@@ -0,0 +1,74 @@
+
+ *
+ * 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\OCI8\Connection;
+use CodeIgniter\Test\CIUnitTestCase;
+use Config\Database as DbConfig;
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\Group;
+
+/**
+ * @internal
+ */
+#[Group('DatabaseLive')]
+final class ConnectionTest extends CIUnitTestCase
+{
+ /**
+ * @var array Database connection settings
+ */
+ private array $settings = [];
+
+ protected function setUp(): void
+ {
+ $dbConfig = config(DbConfig::class);
+ $this->settings = $dbConfig->{$this->DBGroup};
+
+ if ($this->settings['DBDriver'] !== 'OCI8') {
+ $this->markTestSkipped('This test is only for OCI8.');
+ }
+ }
+
+ #[DataProvider('provideIsValidDSN')]
+ public function testIsValidDSN(string $dsn): void
+ {
+ $this->settings['DSN'] = $dsn;
+
+ $db = new Connection($this->settings);
+
+ $isValidDSN = $this->getPrivateMethodInvoker($db, 'isValidDSN');
+
+ $this->assertTrue($isValidDSN());
+ }
+
+ /**
+ * @return array>
+ */
+ public static function provideIsValidDSN(): iterable
+ {
+ yield from [
+ // Easy Connect string
+ // See https://docs.oracle.com/en/database/oracle/oracle-database/23/netag/configuring-naming-methods.html#GUID-36F3A17D-843C-490A-8A23-FB0FE005F8E8
+ 'HostOnly' => ['sales-server'],
+ 'Host:Port' => ['sales-server:3456'],
+ 'Host/ServiceName' => ['sales-server/sales'],
+ 'IPv6Address:Port/ServiceName' => ['[2001:0db8:0:0::200C:417A]:80/sales'],
+ 'Host:Port/ServiceName' => ['sales-server:80/sales'],
+ 'Host/ServiceName:ServerType/InstanceName' => ['sales-server/sales:dedicated/inst1'],
+ 'Host:InstanceName' => ['sales-server//inst1'],
+ 'Host/ServiceNameWithDots' => ['myhost/my.service.name'],
+ 'Host:Port/ServiceNameWithDots' => ['myhost:1521/my.service.name'],
+ ];
+ }
+}
diff --git a/tests/system/HTTP/CURLRequestTest.php b/tests/system/HTTP/CURLRequestTest.php
index dc48eb621d80..f14198be2264 100644
--- a/tests/system/HTTP/CURLRequestTest.php
+++ b/tests/system/HTTP/CURLRequestTest.php
@@ -1227,4 +1227,34 @@ public function testGetHeaderLineContentType(): void
$this->assertSame('text/html; charset=UTF-8', $response->getHeaderLine('Content-Type'));
}
+
+ public function testHTTPversionAsString(): void
+ {
+ $this->request->request('POST', '/post', [
+ 'version' => '1.0',
+ ]);
+
+ $options = $this->request->curl_options;
+
+ $this->assertArrayHasKey(CURLOPT_HTTP_VERSION, $options);
+ $this->assertSame(CURL_HTTP_VERSION_1_0, $options[CURLOPT_HTTP_VERSION]);
+
+ $this->request->request('POST', '/post', [
+ 'version' => '1.1',
+ ]);
+
+ $options = $this->request->curl_options;
+
+ $this->assertArrayHasKey(CURLOPT_HTTP_VERSION, $options);
+ $this->assertSame(CURL_HTTP_VERSION_1_1, $options[CURLOPT_HTTP_VERSION]);
+
+ $this->request->request('POST', '/post', [
+ 'version' => '2.0',
+ ]);
+
+ $options = $this->request->curl_options;
+
+ $this->assertArrayHasKey(CURLOPT_HTTP_VERSION, $options);
+ $this->assertSame(CURL_HTTP_VERSION_2_0, $options[CURLOPT_HTTP_VERSION]);
+ }
}
diff --git a/tests/system/Router/RouteCollectionTest.php b/tests/system/Router/RouteCollectionTest.php
index 89959cd5f33e..5c06154a5d31 100644
--- a/tests/system/Router/RouteCollectionTest.php
+++ b/tests/system/Router/RouteCollectionTest.php
@@ -440,6 +440,22 @@ static function ($routes): void {
$this->assertSame($expected, $routes->getRoutesOptions());
}
+ public function testGroupFilterAndRouteFilter(): void
+ {
+ $routes = $this->getCollector();
+
+ $routes->group('admin', ['filter' => ['csrf']], static function ($routes): void {
+ $routes->get('profile', 'Admin\Profile::index', ['filter' => ['honeypot']]);
+ });
+
+ $expected = [
+ 'admin/profile' => [
+ 'filter' => ['csrf', 'honeypot'],
+ ],
+ ];
+ $this->assertSame($expected, $routes->getRoutesOptions());
+ }
+
public function testGroupingWorksWithEmptyStringPrefix(): void
{
$routes = $this->getCollector();
diff --git a/tools/phpmetrics/composer.json b/tools/phpmetrics/composer.json
deleted file mode 100644
index eef46d0db139..000000000000
--- a/tools/phpmetrics/composer.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "require-dev": {
- "phpmetrics/phpmetrics": "^3.0rc6"
- }
-}
diff --git a/user_guide_src/source/changelogs/index.rst b/user_guide_src/source/changelogs/index.rst
index d97ccaa0b0ac..3dc5cb698f75 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.5.4
v4.5.3
v4.5.2
v4.5.1
diff --git a/user_guide_src/source/changelogs/v4.5.4.rst b/user_guide_src/source/changelogs/v4.5.4.rst
new file mode 100644
index 000000000000..257e42bbfb02
--- /dev/null
+++ b/user_guide_src/source/changelogs/v4.5.4.rst
@@ -0,0 +1,24 @@
+#############
+Version 4.5.4
+#############
+
+Release Date: July 27, 2024
+
+**4.5.4 release of CodeIgniter4**
+
+.. contents::
+ :local:
+ :depth: 3
+
+**********
+Bugs Fixed
+**********
+
+- **Routing:** Fixed a bug that filters passed to ``$routes->group()`` were not
+ merged into filters passed to the inner routes.
+- **CURLRequest:** Fixed a bug preventing the use of strings for ``version`` in the config array
+ when making requests.
+
+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 442a6f49e0d6..431dc6d57bd9 100644
--- a/user_guide_src/source/conf.py
+++ b/user_guide_src/source/conf.py
@@ -26,7 +26,7 @@
version = '4.5'
# The full version, including alpha/beta/rc tags.
-release = '4.5.3'
+release = '4.5.4'
# -- General configuration ---------------------------------------------------
diff --git a/user_guide_src/source/database/query_builder.rst b/user_guide_src/source/database/query_builder.rst
index 171c354d1120..454fc56c6bbf 100644
--- a/user_guide_src/source/database/query_builder.rst
+++ b/user_guide_src/source/database/query_builder.rst
@@ -247,6 +247,8 @@ Use the ``$db->newQuery()`` method to make a subquery the main table:
Join
====
+.. _query-builder-join:
+
$builder->join()
----------------
@@ -270,7 +272,7 @@ RawSql
.. versionadded:: 4.2.0
-Since v4.2.0, ``$builder->join()`` accepts a ``CodeIgniter\Database\RawSql`` instance, which expresses raw SQL strings.
+Since v4.2.0, ``$builder->join()`` accepts a ``CodeIgniter\Database\RawSql`` instance as the JOIN ON condition, which expresses raw SQL strings.
.. literalinclude:: query_builder/102.php
@@ -1498,13 +1500,14 @@ Class Reference
.. php:method:: join($table, $cond[, $type = ''[, $escape = null]])
:param string $table: Table name to join
- :param string $cond: The JOIN ON condition
+ :param string|RawSql $cond: The JOIN ON condition
:param string $type: The JOIN type
:param bool $escape: Whether to escape values and identifiers
:returns: ``BaseBuilder`` instance (method chaining)
:rtype: ``BaseBuilder``
- Adds a ``JOIN`` clause to a query.
+ Adds a ``JOIN`` clause to a query. Since v4.2.0, ``RawSql`` can be used
+ as the JOIN ON condition. See also :ref:`query-builder-join`.
.. php:method:: where($key[, $value = null[, $escape = null]])
diff --git a/user_guide_src/source/general/configuration.rst b/user_guide_src/source/general/configuration.rst
index bfb4b0f1b4ef..cc24b3f3723c 100644
--- a/user_guide_src/source/general/configuration.rst
+++ b/user_guide_src/source/general/configuration.rst
@@ -314,6 +314,10 @@ Registrars provide a means of altering a configuration at runtime across namespa
Registrars work if :ref:`auto-discovery` is enabled in :doc:`Modules `.
It alters configuration properties when the Config object is instantiated.
+.. note:: This feature is implemented in the ``CodeIgniter\Config\BaseConfig``
+ class. So it will not work with a few files in the **app/Config** folder
+ that do not extends the class.
+
There are two ways to implement a Registrar: **implicit** and **explicit**.
.. note:: Values from **.env** always take priority over Registrars.
diff --git a/user_guide_src/source/incoming/filters.rst b/user_guide_src/source/incoming/filters.rst
index 4425ce03d3bf..737fd191392a 100644
--- a/user_guide_src/source/incoming/filters.rst
+++ b/user_guide_src/source/incoming/filters.rst
@@ -82,7 +82,7 @@ Configuring Filters
There are two ways to configure filters when they get run. One is done in
**app/Config/Filters.php**, the other is done in **app/Config/Routes.php**.
-If you want to specify filter to a specific route, use **app/Config/Routes.php**
+If you want to specify filters to defined routes, use **app/Config/Routes.php**
and see :ref:`URI Routing `.
.. Note:: The safest way to apply filters is to :ref:`disable auto-routing `, and :ref:`set filters to routes `.
@@ -95,9 +95,11 @@ configure exactly when the filters run.
.. Warning:: It is recommended that you should always add ``*`` at the end of a URI in the filter settings.
Because a controller method might be accessible by different URLs than you think.
- For example, when :ref:`auto-routing-legacy` is enabled, if you have ``Blog::index``,
+ For example, when :ref:`auto-routing-legacy` is enabled, if you have ``Blog::index()``,
it can be accessible with ``blog``, ``blog/index``, and ``blog/index/1``, etc.
+.. _filters-aliases:
+
$aliases
--------
@@ -106,7 +108,9 @@ filters to run:
.. literalinclude:: filters/003.php
-Aliases are mandatory and if you try to use a full class name later, the system will throw an error. Defining them
+Aliases are mandatory and if you try to use a full class name later, the system will throw an error.
+
+Defining them
in this way makes it simple to switch out the class used. Great for when you decided you need to change to a
different authentication system since you only change the filter's class and you're done.
@@ -170,7 +174,7 @@ an array with the ``except`` key and a URI path (relative to BaseURL) to match a
and the URI paths specified in the filter could be different.
See :ref:`upgrade-447-filter-paths` for details.
-Any place you can use a URI path (relative to BaseURL) in the filter settings, you can use a regular expression or, like in this example, use
+Any place you can use a URI path (relative to BaseURL) in the filter settings, you can use a regular expression or, like in this example above, use
an asterisk (``*``) for a wildcard that will match all characters after that. In this example, any URI path starting with ``api/``
would be exempted from CSRF protection, but the site's forms would all be protected.
@@ -186,17 +190,19 @@ $methods
because :ref:`auto-routing-legacy` permits any HTTP method to access a controller.
Accessing the controller with a method you don't expect could bypass the filter.
-You can apply filters to all requests of a certain HTTP method, like POST, GET, PUT, etc. In this array, you would
-specify the method name in **lowercase**. It's value would be an array of filters to run:
+You can apply filters to all requests of a certain HTTP method, like ``POST``, ``GET``, ``PUT``, etc.
+It's value would be an array of filters to run:
.. literalinclude:: filters/008.php
.. note:: Unlike the ``$globals`` or the
``$filters`` properties, these will only run as before filters.
-In addition to the standard HTTP methods, this also supports one special case: ``cli``. The ``cli`` method would apply to
+In addition to the standard HTTP methods, this also supports one special case: ``CLI``. The ``CLI`` method would apply to
all requests that were run from the command line.
+.. note:: Prior to v4.5.0, due to a bug, you needed to specify the HTTP method names in **lowercase**.
+
$filters
--------
diff --git a/user_guide_src/source/incoming/routing.rst b/user_guide_src/source/incoming/routing.rst
index 867983437acd..90c99ad73b61 100644
--- a/user_guide_src/source/incoming/routing.rst
+++ b/user_guide_src/source/incoming/routing.rst
@@ -388,12 +388,13 @@ Applying Filters
================
You can alter the behavior of specific routes by supplying filters to run before or after the controller. This is especially handy during authentication or api logging.
+
The value for the filter can be a string or an array of strings:
* matching the aliases defined in **app/Config/Filters.php**.
* filter classnames
-See :doc:`Controller Filters ` for more information on setting up filters.
+See :ref:`Controller Filters ` for more information on defining aliases.
.. Warning:: If you set filters to routes in **app/Config/Routes.php**
(not in **app/Config/Filters.php**), it is recommended to disable Auto Routing (Legacy).
@@ -405,7 +406,7 @@ See :doc:`Controller Filters ` for more information on setting up filte
Alias Filter
------------
-You specify an alias defined in **app/Config/Filters.php** for the filter value:
+You specify an alias :ref:`defined in app/Config/Filters.php ` for the filter value:
.. literalinclude:: routing/034.php
@@ -418,7 +419,7 @@ Classname Filter
.. versionadded:: 4.1.5
-You specify a filter classname for the filter value:
+You can specify a filter classname for the filter value:
.. literalinclude:: routing/036.php
@@ -435,7 +436,7 @@ Multiple Filters
:ref:`Upgrading from 4.1.4 to 4.1.5 `
for the details.
-You specify an array for the filter value:
+You can specify an array for the filter value:
.. literalinclude:: routing/037.php
@@ -563,6 +564,9 @@ run the filter before or after the controller. This is especially handy during a
The value for the filter must match one of the aliases defined within **app/Config/Filters.php**.
+.. note:: Prior to v4.5.4, due to a bug, filters passed to the ``group()`` were
+ not merged into the filters passed to the inner routes.
+
Setting Other Options
=====================
diff --git a/user_guide_src/source/installation/upgrade_420.rst b/user_guide_src/source/installation/upgrade_420.rst
index 03aebabb015c..d86800f58dbc 100644
--- a/user_guide_src/source/installation/upgrade_420.rst
+++ b/user_guide_src/source/installation/upgrade_420.rst
@@ -43,6 +43,8 @@ The constants ``EVENT_PRIORITY_LOW``, ``EVENT_PRIORITY_NORMAL`` and ``EVENT_PRIO
composer.json
=============
+.. note:: This procedure is not required in v4.5.0 or later.
+
If you use Composer, when you installed CodeIgniter v4.1.9 or before, and
if there are ``App\\`` and ``Config\\`` namespaces in your ``/composer.json``'s ``autoload.psr-4``
like the following, you need to remove these lines, and run ``composer dump-autoload``.
diff --git a/user_guide_src/source/installation/upgrade_454.rst b/user_guide_src/source/installation/upgrade_454.rst
new file mode 100644
index 000000000000..2e460eed3a16
--- /dev/null
+++ b/user_guide_src/source/installation/upgrade_454.rst
@@ -0,0 +1,33 @@
+#############################
+Upgrading from 4.5.3 to 4.5.4
+#############################
+
+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
+
+*************
+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.
+
+.. note:: 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/Events.php
+- composer.json
diff --git a/user_guide_src/source/installation/upgrading.rst b/user_guide_src/source/installation/upgrading.rst
index 2615c49fe10a..0daa9309cddc 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_454
upgrade_453
upgrade_452
upgrade_451
diff --git a/user_guide_src/source/libraries/email.rst b/user_guide_src/source/libraries/email.rst
index c506a9044757..ec21b79ddcf2 100644
--- a/user_guide_src/source/libraries/email.rst
+++ b/user_guide_src/source/libraries/email.rst
@@ -113,35 +113,37 @@ sending email.
=================== =================== ============================ =======================================================================
Preference Default Value Options Description
=================== =================== ============================ =======================================================================
-**userAgent** CodeIgniter None The "user agent".
+**fromEmail** The email address to be set in the "from" header.
+**fromName** The name to be set in the "from" header.
+**userAgent** CodeIgniter The "user agent".
**protocol** mail ``mail``, ``sendmail``, The mail sending protocol.
or ``smtp``
-**mailPath** /usr/sbin/sendmail None The server path to Sendmail.
-**SMTPHost** No Default None SMTP Server Hostname.
-**SMTPUser** No Default None SMTP Username.
-**SMTPPass** No Default None SMTP Password.
-**SMTPPort** 25 None SMTP Port. (If set to ``465``, TLS will be used for the connection
+**mailPath** /usr/sbin/sendmail The server path to Sendmail.
+**SMTPHost** SMTP Server Hostname.
+**SMTPUser** SMTP Username.
+**SMTPPass** SMTP Password.
+**SMTPPort** 25 SMTP Port. (If set to ``465``, TLS will be used for the connection
regardless of ``SMTPCrypto`` setting.)
-**SMTPTimeout** 5 None SMTP Timeout (in seconds).
-**SMTPKeepAlive** false ``true``/``false`` (boolean) Enable persistent SMTP connections.
+**SMTPTimeout** 5 SMTP Timeout (in seconds).
+**SMTPKeepAlive** false ``true``/``false`` Enable persistent SMTP connections.
**SMTPCrypto** tls ``tls``, ``ssl``, or SMTP Encryption. Setting this to ``ssl`` will create a secure
empty string (``''``) channel to the server using SSL, and ``tls`` will issue a
``STARTTLS`` command to the server. Connection on port ``465`` should
set this to an empty string (``''``). See also
:ref:`email-ssl-tls-for-smtp`.
-**wordWrap** true ``true``/``false`` (boolean) Enable word-wrap.
+**wordWrap** true ``true``/``false`` Enable word-wrap.
**wrapChars** 76 Character count to wrap at.
**mailType** text ``text`` or ``html`` Type of mail. If you send HTML email you must send it as a complete web
page. Make sure you don't have any relative links or relative image
paths otherwise they will not work.
-**charset** utf-8 Character set (``utf-8``, ``iso-8859-1``, etc.).
-**validate** true ``true``/``false`` (boolean) Whether to validate the email address.
+**charset** UTF-8 Character set (``utf-8``, ``iso-8859-1``, etc.).
+**validate** true ``true``/``false`` Whether to validate the email address.
**priority** 3 1, 2, 3, 4, 5 Email Priority. ``1`` = highest. ``5`` = lowest. ``3`` = normal.
-**CRLF** \\n ``\r\n`` or ``\n`` or ``\r`` Newline character. (Use ``\r\n`` to comply with RFC 822).
-**newline** \\n ``\r\n`` or ``\n`` or ``\r`` Newline character. (Use ``\r\n`` to comply with RFC 822).
-**BCCBatchMode** false ``true``/``false`` (boolean) Enable BCC Batch Mode.
-**BCCBatchSize** 200 None Number of emails in each BCC batch.
-**DSN** false ``true``/``false`` (boolean) Enable notify message from server.
+**CRLF** \\r\\n ``\r\n``, ``\n`` or ``\r`` Newline character. (Use ``\r\n`` to comply with RFC 822).
+**newline** \\r\\n ``\r\n``, ``\n`` or ``\r`` Newline character. (Use ``\r\n`` to comply with RFC 822).
+**BCCBatchMode** false ``true``/``false`` Enable BCC Batch Mode.
+**BCCBatchSize** 200 Number of emails in each BCC batch.
+**DSN** false ``true``/``false`` Enable notify message from server.
=================== =================== ============================ =======================================================================
Overriding Word Wrapping
@@ -173,9 +175,9 @@ Class Reference
.. php:method:: setFrom($from[, $name = ''[, $returnPath = null]])
- :param string $from: "From" e-mail address
+ :param string $from: "From" email address
:param string $name: "From" display name
- :param string $returnPath: Optional email address to redirect undelivered e-mail to
+ :param string $returnPath: Optional email address to redirect undelivered email to
:returns: CodeIgniter\\Email\\Email instance (method chaining)
:rtype: CodeIgniter\\Email\\Email
@@ -192,8 +194,8 @@ Class Reference
.. php:method:: setReplyTo($replyto[, $name = ''])
- :param string $replyto: E-mail address for replies
- :param string $name: Display name for the reply-to e-mail address
+ :param string $replyto: Email address for replies
+ :param string $name: Display name for the reply-to email address
:returns: CodeIgniter\\Email\\Email instance (method chaining)
:rtype: CodeIgniter\\Email\\Email
@@ -204,12 +206,12 @@ Class Reference
.. php:method:: setTo($to)
- :param mixed $to: Comma-delimited string or an array of e-mail addresses
+ :param mixed $to: Comma separated string or an array of email addresses
:returns: CodeIgniter\\Email\\Email instance (method chaining)
:rtype: CodeIgniter\\Email\\Email
- Sets the email address(s) of the recipient(s). Can be a single e-mail,
- a comma-delimited list or an array:
+ Sets the email address(es) of the recipient(s). Can be a single email,
+ a comma separated list or an array:
.. literalinclude:: email/006.php
@@ -219,22 +221,22 @@ Class Reference
.. php:method:: setCC($cc)
- :param mixed $cc: Comma-delimited string or an array of e-mail addresses
+ :param mixed $cc: Comma separated string or an array of email addresses
:returns: CodeIgniter\\Email\\Email instance (method chaining)
:rtype: CodeIgniter\\Email\\Email
- Sets the CC email address(s). Just like the "to", can be a single e-mail,
- a comma-delimited list or an array.
+ Sets the CC email address(es). Just like the "to", can be a single email,
+ a comma separated list or an array.
.. php:method:: setBCC($bcc[, $limit = ''])
- :param mixed $bcc: Comma-delimited string or an array of e-mail addresses
- :param int $limit: Maximum number of e-mails to send per batch
+ :param mixed $bcc: Comma separated string or an array of email addresses
+ :param int $limit: Maximum number of emails to send per batch
:returns: CodeIgniter\\Email\\Email instance (method chaining)
:rtype: CodeIgniter\\Email\\Email
- Sets the BCC email address(s). Just like the ``setTo()`` method, can be a single
- e-mail, a comma-delimited list or an array.
+ Sets the BCC email address(es). Just like the ``setTo()`` method, can be a single
+ email, a comma separated list or an array.
If ``$limit`` is set, "batch mode" will be enabled, which will send
the emails to batches, with each batch not exceeding the specified
@@ -242,7 +244,7 @@ Class Reference
.. php:method:: setSubject($subject)
- :param string $subject: E-mail subject line
+ :param string $subject: Email subject line
:returns: CodeIgniter\\Email\\Email instance (method chaining)
:rtype: CodeIgniter\\Email\\Email
@@ -252,21 +254,21 @@ Class Reference
.. php:method:: setMessage($body)
- :param string $body: E-mail message body
+ :param string $body: Email message body
:returns: CodeIgniter\\Email\\Email instance (method chaining)
:rtype: CodeIgniter\\Email\\Email
- Sets the e-mail message body:
+ Sets the email message body:
.. literalinclude:: email/010.php
.. php:method:: setAltMessage($str)
- :param string $str: Alternative e-mail message body
+ :param string $str: Alternative email message body
:returns: CodeIgniter\\Email\\Email instance (method chaining)
:rtype: CodeIgniter\\Email\\Email
- Sets the alternative e-mail message body:
+ Sets the alternative email message body:
.. literalinclude:: email/011.php
@@ -284,7 +286,7 @@ Class Reference
:returns: CodeIgniter\\Email\\Email instance (method chaining)
:rtype: CodeIgniter\\Email\\Email
- Appends additional headers to the e-mail:
+ Appends additional headers to the email:
.. literalinclude:: email/012.php
@@ -311,7 +313,7 @@ Class Reference
:returns: true on success, false on failure
:rtype: bool
- The e-mail sending method. Returns boolean true or false based on
+ The email sending method. Returns boolean true or false based on
success or failure, enabling it to be used conditionally:
.. literalinclude:: email/015.php
@@ -334,7 +336,7 @@ Class Reference
:param string $disposition: 'disposition' of the attachment. Most
email clients make their own decision regardless of the MIME
specification used here. https://www.iana.org/assignments/cont-disp/cont-disp.xhtml
- :param string $newname: Custom file name to use in the e-mail
+ :param string $newname: Custom file name to use in the email
:param string $mime: MIME type to use (useful for buffered data)
:returns: CodeIgniter\\Email\\Email instance (method chaining)
:rtype: CodeIgniter\\Email\\Email
@@ -375,7 +377,7 @@ Class Reference
.. literalinclude:: email/022.php
- .. note:: Content-ID for each e-mail must be re-created for it to be unique.
+ .. note:: Content-ID for each email must be re-created for it to be unique.
.. php:method:: printDebugger($include = ['headers', 'subject', 'body'])
diff --git a/user_guide_src/source/libraries/files.rst b/user_guide_src/source/libraries/files.rst
index 0b55982692c2..1622c3345c51 100644
--- a/user_guide_src/source/libraries/files.rst
+++ b/user_guide_src/source/libraries/files.rst
@@ -14,10 +14,12 @@ Getting a File instance
***********************
You create a new File instance by passing in the path to the file in the constructor.
-By default, the file does not need to exist. However, you can pass an additional argument of "true"
-to check that the file exists and throw ``FileNotFoundException()`` if it does not.
.. literalinclude:: files/001.php
+ :lines: 2-
+
+By default, the file does not need to exist. However, you can pass an additional argument of ``true``
+to check that the file exists and throw ``FileNotFoundException()`` if it does not.
Taking Advantage of Spl
***********************
@@ -25,6 +27,7 @@ Taking Advantage of Spl
Once you have an instance, you have the full power of the SplFileInfo class at the ready, including:
.. literalinclude:: files/002.php
+ :lines: 2-
New Features
************
@@ -38,21 +41,28 @@ You can generate a cryptographically secure random filename, with the current ti
method. This is especially useful to rename files when moving it so that the filename is unguessable:
.. literalinclude:: files/003.php
+ :lines: 2-
getSize()
=========
-Returns the size of the uploaded file in bytes:
+Returns the size of the file in bytes:
.. literalinclude:: files/004.php
+ :lines: 2-
+
+A ``RuntimeException`` will be thrown if the file does not exist or an error occurs.
getSizeByUnit()
===============
-Returns the size of the uploaded file default in bytes. You can pass in either 'kb' or 'mb' as the first parameter to get
+Returns the size of the file default in bytes. You can pass in either ``'kb'`` or ``'mb'`` as the first parameter to get
the results in kilobytes or megabytes, respectively:
.. literalinclude:: files/005.php
+ :lines: 2-
+
+A ``RuntimeException`` will be thrown if the file does not exist or an error occurs.
getMimeType()
=============
@@ -61,6 +71,7 @@ Retrieve the media type (mime type) of the file. Uses methods that are considere
the type of file:
.. literalinclude:: files/006.php
+ :lines: 2-
guessExtension()
================
@@ -70,6 +81,7 @@ will return null. This is often a more trusted source than simply using the exte
the values in **app/Config/Mimes.php** to determine extension:
.. literalinclude:: files/007.php
+ :lines: 2-
Moving Files
============
@@ -78,12 +90,15 @@ Each file can be moved to its new location with the aptly named ``move()`` metho
the file to as the first parameter:
.. literalinclude:: files/008.php
+ :lines: 2-
By default, the original filename was used. You can specify a new filename by passing it as the second parameter:
.. literalinclude:: files/009.php
+ :lines: 2-
-The move() method returns a new File instance that for the relocated file, so you must capture the result if the
+The ``move()`` method returns a new File instance that for the relocated file, so you must capture the result if the
resulting location is needed:
.. literalinclude:: files/010.php
+ :lines: 2-
diff --git a/user_guide_src/source/libraries/user_agent/005.php b/user_guide_src/source/libraries/user_agent/005.php
index 944c70975971..80aad0f42c59 100644
--- a/user_guide_src/source/libraries/user_agent/005.php
+++ b/user_guide_src/source/libraries/user_agent/005.php
@@ -1,5 +1,5 @@
isReferral()) {
- echo $agent->referrer();
+ echo $agent->getReferrer();
}
diff --git a/user_guide_src/source/libraries/validation.rst b/user_guide_src/source/libraries/validation.rst
index 899389d2d004..7b87954653e1 100644
--- a/user_guide_src/source/libraries/validation.rst
+++ b/user_guide_src/source/libraries/validation.rst
@@ -897,15 +897,11 @@ Rule Parameter Description
======================= ========== ============================================= ===================================================
alpha No Fails if field has anything other than
alphabetic characters in ASCII.
-alpha_space No Fails if field contains anything other than
- alphabetic characters or spaces in ASCII.
alpha_dash No Fails if field contains anything other than
alphanumeric characters, underscores or
dashes in ASCII.
alpha_numeric No Fails if field contains anything other than
alphanumeric characters in ASCII.
-alpha_numeric_space No Fails if field contains anything other than
- alphanumeric or space characters in ASCII.
alpha_numeric_punct No Fails if field contains anything other than
alphanumeric, space, or this limited set of
punctuation characters: ``~`` (tilde),
@@ -916,6 +912,10 @@ alpha_numeric_punct No Fails if field contains anything other than
``_`` (underscore), ``+`` (plus),
``=`` (equals), ``|`` (vertical bar),
``:`` (colon), ``.`` (period).
+alpha_numeric_space No Fails if field contains anything other than
+ alphanumeric or space characters in ASCII.
+alpha_space No Fails if field contains anything other than
+ alphabetic characters or spaces in ASCII.
decimal No Fails if field contains anything other than
a decimal number. Also accepts a ``+`` or
``-`` sign for the number.
@@ -940,9 +940,11 @@ in_list Yes Fails if field is not within a predetermined
integer No Fails if field contains anything other than
an integer.
is_natural No Fails if field contains anything other than
- a natural number: 0, 1, 2, 3, etc.
+ a natural number: ``0``, ``1``, ``2``, ``3``
+ , etc.
is_natural_no_zero No Fails if field contains anything other than
- a natural number, except zero: 1, 2, 3, etc.
+ a natural number, except zero: ``1``, ``2``,
+ ``3``, etc.
is_not_unique Yes Checks the database to see if the given value ``is_not_unique[table.field,where_field,where_value]``
exists. Can ignore records by field/value to
filter (currently accept only one filter).
@@ -964,47 +966,22 @@ not_in_list Yes Fails if field is within a predetermined
list.
numeric No Fails if field contains anything other than
numeric characters.
-regex_match Yes Fails if field does not match the regular ``regex_match[/regex/]``
- expression.
permit_empty No Allows the field to receive an empty array,
empty string, null or false.
+regex_match Yes Fails if field does not match the regular ``regex_match[/regex/]``
+ expression.
required No Fails if the field is an empty array, empty
string, null or false.
required_with Yes The field is required when any of the other ``required_with[field1,field2]``
fields is not `empty()`_ in the data.
required_without Yes The field is required when any of the other ``required_without[field1,field2]``
fields is `empty()`_ in the data.
-string No A generic alternative to the alpha* rules
+string No A generic alternative to the **alpha*** rules
that confirms the element is a string
timezone No Fails if field does not match a timezone
per `timezone_identifiers_list()`_
valid_base64 No Fails if field contains anything other than
valid Base64 characters.
-valid_json No Fails if field does not contain a valid JSON
- string.
-valid_email No Fails if field does not contain a valid
- email address.
-valid_emails No Fails if any value provided in a comma
- separated list is not a valid email.
-valid_ip Yes Fails if the supplied IP is not valid. ``valid_ip[ipv6]``
- Accepts an optional parameter of ``ipv4`` or
- ``ipv6`` to specify an IP format.
-valid_url No Fails if field does not contain (loosely) a
- URL. Includes simple strings that could be
- hostnames, like "codeigniter".
- **Normally,** ``valid_url_strict`` **should
- be used.**
-valid_url_strict Yes Fails if field does not contain a valid URL. ``valid_url_strict[https]``
- You can optionally specify a list of valid
- schemas. If not specified, ``http,https``
- are valid. This rule uses PHP's
- ``FILTER_VALIDATE_URL``.
-valid_date Yes Fails if field does not contain a valid date. ``valid_date[d/m/Y]``
- Any string that `strtotime()`_ accepts is
- valid if you don't specify an optional
- parameter that matches a date format.
- **So it is usually necessary to specify
- the parameter.**
valid_cc_number Yes Verifies that the credit card number matches ``valid_cc_number[amex]``
the format used by the specified provider.
Current supported providers are:
@@ -1025,11 +1002,37 @@ valid_cc_number Yes Verifies that the credit card number matches
Scotiabank Scotia Card (``scotia``),
BMO ABM Card (``bmoabm``),
HSBC Canada Card (``hsbc``)
+valid_date Yes Fails if field does not contain a valid date. ``valid_date[d/m/Y]``
+ Any string that `strtotime()`_ accepts is
+ valid if you don't specify an optional
+ parameter that matches a date format.
+ **So it is usually necessary to specify
+ the parameter.**
+valid_email No Fails if field does not contain a valid
+ email address.
+valid_emails No Fails if any value provided in a comma
+ separated list is not a valid email.
+valid_ip Yes Fails if the supplied IP is not valid. ``valid_ip[ipv6]``
+ Accepts an optional parameter of ``ipv4`` or
+ ``ipv6`` to specify an IP format.
+valid_json No Fails if field does not contain a valid JSON
+ string.
+valid_url No Fails if field does not contain (loosely) a
+ URL. Includes simple strings that could be
+ hostnames, like "codeigniter".
+ **Normally,** ``valid_url_strict`` **should
+ be used.**
+valid_url_strict Yes Fails if field does not contain a valid URL. ``valid_url_strict[https]``
+ You can optionally specify a list of valid
+ schemas. If not specified, ``http,https``
+ are valid. This rule uses PHP's
+ ``FILTER_VALIDATE_URL``.
======================= ========== ============================================= ===================================================
.. note:: You can also use any native PHP functions that return boolean and
permit at least one parameter, the field data to validate.
- The Validation library **never alters the data** to validate.
+
+.. important:: The Validation library **never alters the data** to validate.
.. _timezone_identifiers_list(): https://www.php.net/manual/en/function.timezone-identifiers-list.php
.. _strtotime(): https://www.php.net/manual/en/function.strtotime.php
diff --git a/utils/composer.json b/utils/composer.json
new file mode 100644
index 000000000000..65137621e0ba
--- /dev/null
+++ b/utils/composer.json
@@ -0,0 +1,24 @@
+{
+ "require": {
+ "php": "^8.1",
+ "codeigniter/coding-standard": "^1.7",
+ "ergebnis/composer-normalize": "^2.28",
+ "friendsofphp/php-cs-fixer": "^3.47.1",
+ "nexusphp/cs-config": "^3.6",
+ "phpmetrics/phpmetrics": "^2.8 || ^3.0rc6",
+ "vimeo/psalm": "^5.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Utils\\": "src/"
+ }
+ },
+ "config": {
+ "allow-plugins": {
+ "ergebnis/composer-normalize": true
+ },
+ "optimize-autoloader": true,
+ "preferred-install": "dist",
+ "sort-packages": true
+ }
+}
diff --git a/utils/PHPStan/CheckUseStatementsAfterLicenseRule.php b/utils/src/PHPStan/CheckUseStatementsAfterLicenseRule.php
similarity index 79%
rename from utils/PHPStan/CheckUseStatementsAfterLicenseRule.php
rename to utils/src/PHPStan/CheckUseStatementsAfterLicenseRule.php
index a3ccaf215d6e..4b23d3204ca1 100644
--- a/utils/PHPStan/CheckUseStatementsAfterLicenseRule.php
+++ b/utils/src/PHPStan/CheckUseStatementsAfterLicenseRule.php
@@ -18,8 +18,13 @@
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Use_;
use PHPStan\Analyser\Scope;
+use PHPStan\Rules\IdentifierRuleError;
use PHPStan\Rules\Rule;
+use PHPStan\Rules\RuleErrorBuilder;
+/**
+ * @implements Rule
+ */
final class CheckUseStatementsAfterLicenseRule implements Rule
{
private const ERROR_MESSAGE = 'Use statement must be located after license docblock';
@@ -32,6 +37,8 @@ public function getNodeType(): string
/**
* @param Stmt $node
+ *
+ * @return list
*/
public function processNode(Node $node, Scope $scope): array
{
@@ -54,7 +61,11 @@ public function processNode(Node $node, Scope $scope): array
while ($previous) {
if ($previous instanceof Use_) {
- return [self::ERROR_MESSAGE];
+ return [
+ RuleErrorBuilder::message(self::ERROR_MESSAGE)
+ ->identifier('codeigniter.useStmtAfterLicense')
+ ->build(),
+ ];
}
$previous = $previous->getAttribute('previous');
diff --git a/utils/Rector/PassStrictParameterToFunctionParameterRector.php b/utils/src/Rector/PassStrictParameterToFunctionParameterRector.php
similarity index 100%
rename from utils/Rector/PassStrictParameterToFunctionParameterRector.php
rename to utils/src/Rector/PassStrictParameterToFunctionParameterRector.php
diff --git a/utils/Rector/RemoveErrorSuppressInTryCatchStmtsRector.php b/utils/src/Rector/RemoveErrorSuppressInTryCatchStmtsRector.php
similarity index 100%
rename from utils/Rector/RemoveErrorSuppressInTryCatchStmtsRector.php
rename to utils/src/Rector/RemoveErrorSuppressInTryCatchStmtsRector.php
diff --git a/utils/Rector/UnderscoreToCamelCaseVariableNameRector.php b/utils/src/Rector/UnderscoreToCamelCaseVariableNameRector.php
similarity index 100%
rename from utils/Rector/UnderscoreToCamelCaseVariableNameRector.php
rename to utils/src/Rector/UnderscoreToCamelCaseVariableNameRector.php