diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 9bec3b2d34..d399e41dca 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,7 +2,7 @@ blank_issues_enabled: false contact_links: - name: CodeIgniter Forum url: https://forum.codeigniter.com/forum-30.html - about: Please ask your support questions in the forums. Thanks! + about: Please ask your support questions and/or feature requests in the forums. Thanks! - name: CodeIgniter Slack channel url: https://codeigniterchat.slack.com diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 3bd2d0a6c4..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -name: Feature request -about: How to submit a feature request -title: '' -labels: '' -assignees: '' - ---- - -Please submit feature requests to our [forum](https://forum.codeigniter.com/forum-29.html). -We use github issues to track bugs and planned work. diff --git a/.github/ISSUE_TEMPLATE/planned-work.md b/.github/ISSUE_TEMPLATE/planned-work.md index 4faa45ed87..3da11a7dbc 100644 --- a/.github/ISSUE_TEMPLATE/planned-work.md +++ b/.github/ISSUE_TEMPLATE/planned-work.md @@ -1,10 +1,10 @@ --- name: Planned work -about: Approved work planning +about: Maintainers' space. DO NOT use this for your bug reports! title: 'Dev: ' labels: dev assignees: '' --- -Repo maintainers will create "issues" for planned work, so it can be tracked. +Maintainers will create "issues" for planned work, so it can be tracked. diff --git a/.github/mergeable.yml b/.github/mergeable.yml new file mode 100644 index 0000000000..d23053efa3 --- /dev/null +++ b/.github/mergeable.yml @@ -0,0 +1,41 @@ +# https://mergeable.readthedocs.io/en/latest/configuration.html + +version: 2 +mergeable: + - when: issues.opened + validate: + - do: description + and: + - must_include: + regex: '^### PHP Version' + - must_include: + regex: '^### CodeIgniter4 Version' + - do: author + and: + - must_exclude: + regex: 'kenjis' + - must_exclude: + regex: 'lonnieezell' + - must_exclude: + regex: 'MGatner' + - must_exclude: + regex: 'michalsn' + - must_exclude: + regex: 'paulbalandan' + - must_exclude: + regex: 'samsonasik' + fail: + - do: comment + payload: | + Hi there! :wave: + + It looks like you opened an issue without following the bug report template: + + Bug report ([open an issue](https://github.com/codeigniter4/CodeIgniter4/issues/new?assignees=&labels=bug&projects=&template=bug_report.yml&title=Bug%3A+)) + + If you are opening a feature request or support question, please do so in the [forums](https://forum.codeigniter.com/forum-30.html). + + The current issue will be closed. This is a precaution to save maintainers' time, I hope you'll understand. + + Sincerely, the mergeable bot πŸ€– + - do: close diff --git a/CHANGELOG.md b/CHANGELOG.md index e423ad54d9..d31f829aec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [v4.5.7](https://github.com/codeigniter4/CodeIgniter4/tree/v4.5.7) (2024-12-31) +[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.5.6...v4.5.7) + +### Fixed Bugs + +* fix: handle namespaced helper found on Common helper by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/9354 +* fix: `Forge::dropColumn()` always returns `false` on SQLite3 driver by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/9351 + +### Refactoring + +* refactor: enable AddArrowFunctionReturnTypeRector by @samsonasik in https://github.com/codeigniter4/CodeIgniter4/pull/9343 + ## [v4.5.6](https://github.com/codeigniter4/CodeIgniter4/tree/v4.5.6) (2024-12-28) [Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.5.5...v4.5.6) diff --git a/app/Views/errors/cli/error_exception.php b/app/Views/errors/cli/error_exception.php index 2bf1459d40..624f3b5b06 100644 --- a/app/Views/errors/cli/error_exception.php +++ b/app/Views/errors/cli/error_exception.php @@ -50,7 +50,7 @@ if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) { $function .= $padClass . $error['function']; } - $args = implode(', ', array_map(static fn ($value) => match (true) { + $args = implode(', ', array_map(static fn ($value): string => match (true) { is_object($value) => 'Object(' . $value::class . ')', is_array($value) => $value !== [] ? '[...]' : '[]', $value === null => 'null', // return the lowercased version diff --git a/phpdoc.dist.xml b/phpdoc.dist.xml index 686c890109..5a287a06f2 100644 --- a/phpdoc.dist.xml +++ b/phpdoc.dist.xml @@ -10,7 +10,7 @@ api/build/ api/cache/ - + system diff --git a/rector.php b/rector.php index ee86216918..486685959c 100644 --- a/rector.php +++ b/rector.php @@ -41,6 +41,7 @@ use Rector\PHPUnit\CodeQuality\Rector\Class_\YieldDataProviderRector; use Rector\Privatization\Rector\Property\PrivatizeFinalClassPropertyRector; use Rector\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector; use Rector\Strict\Rector\If_\BooleanInIfConditionRuleFixerRector; +use Rector\TypeDeclaration\Rector\ArrowFunction\AddArrowFunctionReturnTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedStrictParamTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\ReturnNeverTypeRector; use Rector\TypeDeclaration\Rector\Closure\AddClosureVoidReturnTypeWhereNoReturnRector; @@ -201,6 +202,7 @@ return RectorConfig::configure() TypedPropertyFromAssignsRector::class, ClosureReturnTypeRector::class, FlipTypeControlToUseExclusiveTypeRector::class, + AddArrowFunctionReturnTypeRector::class, ]) ->withConfiguredRule(StringClassNameToClassConstantRector::class, [ // keep '\\' prefix string on string '\Foo\Bar' diff --git a/system/CLI/CLI.php b/system/CLI/CLI.php index 0b5b01b179..9a7e1459e4 100644 --- a/system/CLI/CLI.php +++ b/system/CLI/CLI.php @@ -339,7 +339,7 @@ class CLI $pattern = preg_match_all('/^\d+(,\d+)*$/', trim($input)); // separate input by comma and convert all to an int[] - $inputToArray = array_map(static fn ($value) => (int) $value, explode(',', $input)); + $inputToArray = array_map(static fn ($value): int => (int) $value, explode(',', $input)); // find max from key of $options $maxOptions = array_key_last($options); // find max from input diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index af1ef4ed1e..e92a6a10ae 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.6'; + public const CI_VERSION = '4.5.7'; /** * App startup time. diff --git a/system/Commands/Utilities/Routes.php b/system/Commands/Utilities/Routes.php index a0f81f6b42..d7a6c0b557 100644 --- a/system/Commands/Utilities/Routes.php +++ b/system/Commands/Utilities/Routes.php @@ -187,7 +187,7 @@ class Routes extends BaseCommand // Sort by Handler. if ($sortByHandler) { - usort($tbody, static fn ($handler1, $handler2) => strcmp($handler1[3], $handler2[3])); + usort($tbody, static fn ($handler1, $handler2): int => strcmp($handler1[3], $handler2[3])); } if ($host !== null) { diff --git a/system/Common.php b/system/Common.php index b647c9d247..d5880acf03 100644 --- a/system/Common.php +++ b/system/Common.php @@ -598,7 +598,7 @@ if (! function_exists('helper')) { if (str_contains($filename, '\\')) { $path = $loader->locateFile($filename, 'Helpers'); - if ($path !== '') { + if ($path === false) { throw FileNotFoundException::forFileNotFound($filename); } diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index e738a331d6..54d9a8980f 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -2018,7 +2018,7 @@ class BaseBuilder $sql = 'INSERT INTO ' . $table . ' (' . implode(', ', $keys) . ")\n{:_table_:}ON DUPLICATE KEY UPDATE\n" . implode( ",\n", array_map( - static fn ($key, $value) => $table . '.' . $key . ($value instanceof RawSql ? + static fn ($key, $value): string => $table . '.' . $key . ($value instanceof RawSql ? ' = ' . $value : ' = VALUES(' . $value . ')'), array_keys($updateFields), @@ -2108,7 +2108,7 @@ class BaseBuilder if (is_string($set)) { $set = explode(',', $set); - $set = array_map(static fn ($key) => trim($key), $set); + $set = array_map(static fn ($key): string => trim($key), $set); } if ($set instanceof RawSql) { @@ -2152,7 +2152,7 @@ class BaseBuilder if (is_string($query)) { if ($columns !== null && is_string($columns)) { $columns = explode(',', $columns); - $columns = array_map(static fn ($key) => trim($key), $columns); + $columns = array_map(static fn ($key): string => trim($key), $columns); } $columns = (array) $columns; @@ -2190,7 +2190,7 @@ class BaseBuilder */ protected function formatValues(array $values): array { - return array_map(static fn ($index) => '(' . implode(',', $index) . ')', $values); + return array_map(static fn ($index): string => '(' . implode(',', $index) . ')', $values); } /** @@ -2649,7 +2649,7 @@ class BaseBuilder $sql .= implode( ",\n", array_map( - static fn ($key, $value) => $key . ($value instanceof RawSql ? + static fn ($key, $value): string => $key . ($value instanceof RawSql ? ' = ' . $value : ' = ' . $alias . '.' . $value), array_keys($updateFields), @@ -2691,8 +2691,8 @@ class BaseBuilder $data = implode( " UNION ALL\n", array_map( - static fn ($value) => 'SELECT ' . implode(', ', array_map( - static fn ($key, $index) => $index . ' ' . $key, + static fn ($value): string => 'SELECT ' . implode(', ', array_map( + static fn ($key, $index): string => $index . ' ' . $key, $keys, $value )), @@ -2946,8 +2946,8 @@ class BaseBuilder $data = implode( " UNION ALL\n", array_map( - static fn ($value) => 'SELECT ' . implode(', ', array_map( - static fn ($key, $index) => $index . ' ' . $key, + static fn ($value): string => 'SELECT ' . implode(', ', array_map( + static fn ($key, $index): string => $index . ' ' . $key, $keys, $value )), diff --git a/system/Database/Forge.php b/system/Database/Forge.php index fc9aa54ec1..974c45848d 100644 --- a/system/Database/Forge.php +++ b/system/Database/Forge.php @@ -778,7 +778,7 @@ class Forge } /** - * @param array|string $columnNames column names to DROP + * @param list|string $columnNames column names to DROP * * @return bool * @@ -861,7 +861,7 @@ class Forge $columnNamesToDrop = explode(',', $columnNamesToDrop); } - $columnNamesToDrop = array_map(fn ($field) => 'DROP COLUMN ' . $this->db->escapeIdentifiers(trim($field)), $columnNamesToDrop); + $columnNamesToDrop = array_map(fn ($field): string => 'DROP COLUMN ' . $this->db->escapeIdentifiers(trim($field)), $columnNamesToDrop); return $sql . implode(', ', $columnNamesToDrop); } diff --git a/system/Database/MySQLi/Builder.php b/system/Database/MySQLi/Builder.php index 81434bc364..8c1de9d9f9 100644 --- a/system/Database/MySQLi/Builder.php +++ b/system/Database/MySQLi/Builder.php @@ -115,7 +115,7 @@ class Builder extends BaseBuilder $sql .= implode( ",\n", array_map( - static fn ($key, $value) => $table . '.' . $key . ($value instanceof RawSql ? + static fn ($key, $value): string => $table . '.' . $key . ($value instanceof RawSql ? ' = ' . $value : ' = ' . $alias . '.' . $value), array_keys($updateFields), @@ -132,8 +132,8 @@ class Builder extends BaseBuilder $data = implode( " UNION ALL\n", array_map( - static fn ($value) => 'SELECT ' . implode(', ', array_map( - static fn ($key, $index) => $index . ' ' . $key, + static fn ($value): string => 'SELECT ' . implode(', ', array_map( + static fn ($key, $index): string => $index . ' ' . $key, $keys, $value )), diff --git a/system/Database/OCI8/Builder.php b/system/Database/OCI8/Builder.php index 24a6bb53b0..5a65ffe9b7 100644 --- a/system/Database/OCI8/Builder.php +++ b/system/Database/OCI8/Builder.php @@ -89,8 +89,8 @@ class Builder extends BaseBuilder $data = implode( " FROM DUAL UNION ALL\n", array_map( - static fn ($value) => 'SELECT ' . implode(', ', array_map( - static fn ($key, $index) => $index . ' ' . $key, + static fn ($value): string => 'SELECT ' . implode(', ', array_map( + static fn ($key, $index): string => $index . ' ' . $key, $keys, $value )), @@ -107,7 +107,7 @@ class Builder extends BaseBuilder */ protected function _replace(string $table, array $keys, array $values): string { - $fieldNames = array_map(static fn ($columnName) => trim($columnName, '"'), $keys); + $fieldNames = array_map(static fn ($columnName): string => trim($columnName, '"'), $keys); $uniqueIndexes = array_filter($this->db->getIndexData($table), static function ($index) use ($fieldNames): bool { $hasAllFields = count(array_intersect($index->fields, $fieldNames)) === count($index->fields); @@ -126,7 +126,7 @@ class Builder extends BaseBuilder $sql = 'MERGE INTO ' . $table . "\n USING (SELECT "; - $sql .= implode(', ', array_map(static fn ($columnName, $value) => $value . ' ' . $columnName, $keys, $values)); + $sql .= implode(', ', array_map(static fn ($columnName, $value): string => $value . ' ' . $columnName, $keys, $values)); $sql .= ' FROM DUAL) "_replace" ON ( '; @@ -134,16 +134,16 @@ class Builder extends BaseBuilder $onList[] = '1 != 1'; foreach ($uniqueIndexes as $index) { - $onList[] = '(' . implode(' AND ', array_map(static fn ($columnName) => $table . '."' . $columnName . '" = "_replace"."' . $columnName . '"', $index->fields)) . ')'; + $onList[] = '(' . implode(' AND ', array_map(static fn ($columnName): string => $table . '."' . $columnName . '" = "_replace"."' . $columnName . '"', $index->fields)) . ')'; } $sql .= implode(' OR ', $onList) . ') WHEN MATCHED THEN UPDATE SET '; - $sql .= implode(', ', array_map(static fn ($columnName) => $columnName . ' = "_replace".' . $columnName, $replaceableFields)); + $sql .= implode(', ', array_map(static fn ($columnName): string => $columnName . ' = "_replace".' . $columnName, $replaceableFields)); $sql .= ' WHEN NOT MATCHED THEN INSERT (' . implode(', ', $replaceableFields) . ') VALUES '; - return $sql . (' (' . implode(', ', array_map(static fn ($columnName) => '"_replace".' . $columnName, $replaceableFields)) . ')'); + return $sql . (' (' . implode(', ', array_map(static fn ($columnName): string => '"_replace".' . $columnName, $replaceableFields)) . ')'); } /** @@ -298,7 +298,7 @@ class Builder extends BaseBuilder $sql .= implode( ",\n", array_map( - static fn ($key, $value) => $table . '.' . $key . ($value instanceof RawSql ? + static fn ($key, $value): string => $table . '.' . $key . ($value instanceof RawSql ? ' = ' . $value : ' = ' . $alias . '.' . $value), array_keys($updateFields), @@ -315,8 +315,8 @@ class Builder extends BaseBuilder $data = implode( " UNION ALL\n", array_map( - static fn ($value) => 'SELECT ' . implode(', ', array_map( - static fn ($key, $index) => $index . ' ' . $key, + static fn ($value): string => 'SELECT ' . implode(', ', array_map( + static fn ($key, $index): string => $index . ' ' . $key, $keys, $value )) . ' FROM DUAL', @@ -342,7 +342,7 @@ class Builder extends BaseBuilder $constraints = $this->QBOptions['constraints'] ?? []; if (empty($constraints)) { - $fieldNames = array_map(static fn ($columnName) => trim($columnName, '"'), $keys); + $fieldNames = array_map(static fn ($columnName): string => trim($columnName, '"'), $keys); $uniqueIndexes = array_filter($this->db->getIndexData($table), static function ($index) use ($fieldNames): bool { $hasAllFields = count(array_intersect($index->fields, $fieldNames)) === count($index->fields); @@ -401,7 +401,7 @@ class Builder extends BaseBuilder $sql .= implode( ",\n", array_map( - static fn ($key, $value) => $key . ($value instanceof RawSql ? + static fn ($key, $value): string => $key . ($value instanceof RawSql ? " = {$value}" : " = {$alias}.{$value}"), array_keys($updateFields), @@ -412,7 +412,7 @@ class Builder extends BaseBuilder $sql .= "\nWHEN NOT MATCHED THEN INSERT (" . implode(', ', $keys) . ")\nVALUES "; $sql .= (' (' - . implode(', ', array_map(static fn ($columnName) => "{$alias}.{$columnName}", $keys)) + . implode(', ', array_map(static fn ($columnName): string => "{$alias}.{$columnName}", $keys)) . ')'); $this->QBOptions['sql'] = $sql; @@ -424,8 +424,8 @@ class Builder extends BaseBuilder $data = implode( " FROM DUAL UNION ALL\n", array_map( - static fn ($value) => 'SELECT ' . implode(', ', array_map( - static fn ($key, $index) => $index . ' ' . $key, + static fn ($value): string => 'SELECT ' . implode(', ', array_map( + static fn ($key, $index): string => $index . ' ' . $key, $keys, $value )), @@ -503,8 +503,8 @@ class Builder extends BaseBuilder $data = implode( " FROM DUAL UNION ALL\n", array_map( - static fn ($value) => 'SELECT ' . implode(', ', array_map( - static fn ($key, $index) => $index . ' ' . $key, + static fn ($value): string => 'SELECT ' . implode(', ', array_map( + static fn ($key, $index): string => $index . ' ' . $key, $keys, $value )), diff --git a/system/Database/OCI8/Result.php b/system/Database/OCI8/Result.php index 0039e90ae9..1049847b7a 100644 --- a/system/Database/OCI8/Result.php +++ b/system/Database/OCI8/Result.php @@ -37,7 +37,7 @@ class Result extends BaseResult */ public function getFieldNames(): array { - return array_map(fn ($fieldIndex) => oci_field_name($this->resultID, $fieldIndex), range(1, $this->getFieldCount())); + return array_map(fn ($fieldIndex): false|string => oci_field_name($this->resultID, $fieldIndex), range(1, $this->getFieldCount())); } /** diff --git a/system/Database/Postgre/Builder.php b/system/Database/Postgre/Builder.php index faae0eda8a..21fa222c75 100644 --- a/system/Database/Postgre/Builder.php +++ b/system/Database/Postgre/Builder.php @@ -353,7 +353,7 @@ class Builder extends BaseBuilder $sql .= implode( ",\n", array_map( - static fn ($key, $value) => $key . ($value instanceof RawSql ? + static fn ($key, $value): string => $key . ($value instanceof RawSql ? ' = ' . $value : ' = ' . $that->cast($alias . '.' . $value, $that->getFieldType($table, $key))), array_keys($updateFields), @@ -394,8 +394,8 @@ class Builder extends BaseBuilder $data = implode( " UNION ALL\n", array_map( - static fn ($value) => 'SELECT ' . implode(', ', array_map( - static fn ($key, $index) => $index . ' ' . $key, + static fn ($value): string => 'SELECT ' . implode(', ', array_map( + static fn ($key, $index): string => $index . ' ' . $key, $keys, $value )), @@ -458,7 +458,7 @@ class Builder extends BaseBuilder // if this is the first iteration of batch then we need to build skeleton sql if ($sql === '') { - $fieldNames = array_map(static fn ($columnName) => trim($columnName, '"'), $keys); + $fieldNames = array_map(static fn ($columnName): string => trim($columnName, '"'), $keys); $constraints = $this->QBOptions['constraints'] ?? []; @@ -524,7 +524,7 @@ class Builder extends BaseBuilder $sql .= implode( ",\n", array_map( - static fn ($key, $value) => $key . ($value instanceof RawSql ? + static fn ($key, $value): string => $key . ($value instanceof RawSql ? " = {$value}" : " = {$alias}.{$value}"), array_keys($updateFields), @@ -617,8 +617,8 @@ class Builder extends BaseBuilder $data = implode( " UNION ALL\n", array_map( - static fn ($value) => 'SELECT ' . implode(', ', array_map( - static fn ($key, $index) => $index . ' ' . $key, + static fn ($value): string => 'SELECT ' . implode(', ', array_map( + static fn ($key, $index): string => $index . ' ' . $key, $keys, $value )), diff --git a/system/Database/Postgre/Connection.php b/system/Database/Postgre/Connection.php index 4ce2b07635..ad278145af 100644 --- a/system/Database/Postgre/Connection.php +++ b/system/Database/Postgre/Connection.php @@ -371,7 +371,7 @@ class Connection extends BaseConnection $obj = new stdClass(); $obj->name = $row->indexname; $_fields = explode(',', preg_replace('/^.*\((.+?)\)$/', '$1', trim($row->indexdef))); - $obj->fields = array_map(static fn ($v) => trim($v), $_fields); + $obj->fields = array_map(static fn ($v): string => trim($v), $_fields); if (str_starts_with($row->indexdef, 'CREATE UNIQUE INDEX pk')) { $obj->type = 'PRIMARY'; diff --git a/system/Database/Query.php b/system/Database/Query.php index ba8e906606..36026817d4 100644 --- a/system/Database/Query.php +++ b/system/Database/Query.php @@ -418,7 +418,7 @@ class Query implements QueryInterface, Stringable */ $search = '/\b(?:' . implode('|', $highlight) . ')\b(?![^(')]*'(?:(?:[^(')]*'){2})*[^(')]*$)/'; - return preg_replace_callback($search, static fn ($matches) => '' . str_replace(' ', ' ', $matches[0]) . '', $sql); + return preg_replace_callback($search, static fn ($matches): string => '' . str_replace(' ', ' ', $matches[0]) . '', $sql); } /** diff --git a/system/Database/SQLSRV/Builder.php b/system/Database/SQLSRV/Builder.php index c1272cdcf3..e320069e80 100644 --- a/system/Database/SQLSRV/Builder.php +++ b/system/Database/SQLSRV/Builder.php @@ -694,7 +694,7 @@ class Builder extends BaseBuilder $identityInFields = in_array($tableIdentity, $keys, true); - $fieldNames = array_map(static fn ($columnName) => trim($columnName, '"'), $keys); + $fieldNames = array_map(static fn ($columnName): string => trim($columnName, '"'), $keys); if (empty($constraints)) { $tableIndexes = $this->db->getIndexData($table); @@ -773,7 +773,7 @@ class Builder extends BaseBuilder $sql .= implode( ",\n", array_map( - static fn ($key, $value) => $key . ($value instanceof RawSql ? + static fn ($key, $value): string => $key . ($value instanceof RawSql ? ' = ' . $value : " = {$alias}.{$value}"), array_keys($updateFields), @@ -787,7 +787,7 @@ class Builder extends BaseBuilder '(' . implode( ', ', array_map( - static fn ($columnName) => $columnName === $tableIdentity + static fn ($columnName): string => $columnName === $tableIdentity ? "CASE WHEN {$alias}.{$columnName} IS NULL THEN (SELECT " . 'isnull(IDENT_CURRENT(\'' . $fullTableName . '\')+IDENT_INCR(\'' . $fullTableName . "'),1)) ELSE {$alias}.{$columnName} END" diff --git a/system/Database/SQLSRV/Connection.php b/system/Database/SQLSRV/Connection.php index 8ded4cd020..6903f42eeb 100644 --- a/system/Database/SQLSRV/Connection.php +++ b/system/Database/SQLSRV/Connection.php @@ -257,7 +257,7 @@ class Connection extends BaseConnection $obj->name = $row->index_name; $_fields = explode(',', trim($row->index_keys)); - $obj->fields = array_map(static fn ($v) => trim($v), $_fields); + $obj->fields = array_map(static fn ($v): string => trim($v), $_fields); if (str_contains($row->index_description, 'primary key located on')) { $obj->type = 'PRIMARY'; diff --git a/system/Database/SQLSRV/Forge.php b/system/Database/SQLSRV/Forge.php index df3d55c779..18d792a6ee 100644 --- a/system/Database/SQLSRV/Forge.php +++ b/system/Database/SQLSRV/Forge.php @@ -225,7 +225,7 @@ class Forge extends BaseForge $sql = 'ALTER TABLE ' . $fullTable . ' DROP '; - $fields = array_map(static fn ($item) => 'COLUMN [' . trim($item) . ']', (array) $columnNamesToDrop); + $fields = array_map(static fn ($item): string => 'COLUMN [' . trim($item) . ']', (array) $columnNamesToDrop); return $sql . implode(',', $fields); } @@ -380,7 +380,7 @@ class Forge extends BaseForge // https://learn.microsoft.com/en-us/sql/t-sql/data-types/char-and-varchar-transact-sql?view=sql-server-ver16#remarks $maxLength = max( array_map( - static fn ($value) => strlen($value), + static fn ($value): int => strlen($value), $attributes['CONSTRAINT'] ) ); diff --git a/system/Database/SQLite3/Builder.php b/system/Database/SQLite3/Builder.php index 15fc2529aa..744b64448f 100644 --- a/system/Database/SQLite3/Builder.php +++ b/system/Database/SQLite3/Builder.php @@ -143,7 +143,7 @@ class Builder extends BaseBuilder $constraints = $this->QBOptions['constraints'] ?? []; if (empty($constraints)) { - $fieldNames = array_map(static fn ($columnName) => trim($columnName, '`'), $keys); + $fieldNames = array_map(static fn ($columnName): string => trim($columnName, '`'), $keys); $allIndexes = array_filter($this->db->getIndexData($table), static function ($index) use ($fieldNames): bool { $hasAllFields = count(array_intersect($index->fields, $fieldNames)) === count($index->fields); @@ -179,7 +179,7 @@ class Builder extends BaseBuilder $sql = 'INSERT INTO ' . $table . ' ('; - $sql .= implode(', ', array_map(static fn ($columnName) => $columnName, $keys)); + $sql .= implode(', ', array_map(static fn ($columnName): string => $columnName, $keys)); $sql .= ")\n"; @@ -192,7 +192,7 @@ class Builder extends BaseBuilder $sql .= implode( ",\n", array_map( - static fn ($key, $value) => $key . ($value instanceof RawSql ? + static fn ($key, $value): string => $key . ($value instanceof RawSql ? " = {$value}" : " = {$alias}.{$value}"), array_keys($updateFields), @@ -265,8 +265,8 @@ class Builder extends BaseBuilder $data = implode( " UNION ALL\n", array_map( - static fn ($value) => 'SELECT ' . implode(', ', array_map( - static fn ($key, $index) => $index . ' ' . $key, + static fn ($value): string => 'SELECT ' . implode(', ', array_map( + static fn ($key, $index): string => $index . ' ' . $key, $keys, $value )), diff --git a/system/Database/SQLite3/Forge.php b/system/Database/SQLite3/Forge.php index 48e9abcc42..03120dd7a0 100644 --- a/system/Database/SQLite3/Forge.php +++ b/system/Database/SQLite3/Forge.php @@ -110,6 +110,31 @@ class Forge extends BaseForge return true; } + /** + * @param list|string $columnNames + * + * @throws DatabaseException + */ + public function dropColumn(string $table, $columnNames): bool + { + $columns = is_array($columnNames) ? $columnNames : array_map(trim(...), explode(',', $columnNames)); + $result = (new Table($this->db, $this)) + ->fromTable($this->db->DBPrefix . $table) + ->dropColumn($columns) + ->run(); + + if (! $result && $this->db->DBDebug) { + throw new DatabaseException(sprintf( + 'Failed to drop column%s "%s" on "%s" table.', + count($columns) > 1 ? 's' : '', + implode('", "', $columns), + $table, + )); + } + + return $result; + } + /** * @param array|string $processedFields Processed column definitions * or column names to DROP @@ -121,17 +146,6 @@ class Forge extends BaseForge protected function _alterTable(string $alterType, string $table, $processedFields) { switch ($alterType) { - case 'DROP': - $columnNamesToDrop = $processedFields; - - $sqlTable = new Table($this->db, $this); - - $sqlTable->fromTable($table) - ->dropColumn($columnNamesToDrop) - ->run(); - - return ''; // Why empty string? - case 'CHANGE': $fieldsToModify = []; diff --git a/system/Database/SQLite3/Table.php b/system/Database/SQLite3/Table.php index 5db002a106..61d4aee753 100644 --- a/system/Database/SQLite3/Table.php +++ b/system/Database/SQLite3/Table.php @@ -113,7 +113,7 @@ class Table $this->keys = array_merge($this->keys, $this->formatKeys($this->db->getIndexData($table))); // if primary key index exists twice then remove psuedo index name 'primary'. - $primaryIndexes = array_filter($this->keys, static fn ($index) => $index['type'] === 'primary'); + $primaryIndexes = array_filter($this->keys, static fn ($index): bool => $index['type'] === 'primary'); if ($primaryIndexes !== [] && count($primaryIndexes) > 1 && array_key_exists('primary', $this->keys)) { unset($this->keys['primary']); @@ -202,7 +202,7 @@ class Table */ public function dropPrimaryKey(): Table { - $primaryIndexes = array_filter($this->keys, static fn ($index) => strtolower($index['type']) === 'primary'); + $primaryIndexes = array_filter($this->keys, static fn ($index): bool => strtolower($index['type']) === 'primary'); foreach (array_keys($primaryIndexes) as $key) { unset($this->keys[$key]); @@ -235,7 +235,7 @@ class Table */ public function addPrimaryKey(array $fields): Table { - $primaryIndexes = array_filter($this->keys, static fn ($index) => strtolower($index['type']) === 'primary'); + $primaryIndexes = array_filter($this->keys, static fn ($index): bool => strtolower($index['type']) === 'primary'); // if primary key already exists we can't add another one if ($primaryIndexes !== []) { @@ -308,7 +308,7 @@ class Table $this->keys = array_filter( $this->keys, - static fn ($index) => count(array_intersect($index['fields'], $fieldNames)) === count($index['fields']) + static fn ($index): bool => count(array_intersect($index['fields'], $fieldNames)) === count($index['fields']) ); // Unique/Index keys diff --git a/system/Debug/Toolbar/Collectors/Database.php b/system/Debug/Toolbar/Collectors/Database.php index 1a923a3551..32f7f07091 100644 --- a/system/Debug/Toolbar/Collectors/Database.php +++ b/system/Debug/Toolbar/Collectors/Database.php @@ -218,7 +218,7 @@ class Database extends BaseCollector $this->getConnections(); $queryCount = count(static::$queries); - $uniqueCount = count(array_filter(static::$queries, static fn ($query) => $query['duplicate'] === false)); + $uniqueCount = count(array_filter(static::$queries, static fn ($query): bool => $query['duplicate'] === false)); $connectionCount = count($this->connections); return sprintf( diff --git a/system/Entity/Entity.php b/system/Entity/Entity.php index 464992af62..ff2fba1f70 100644 --- a/system/Entity/Entity.php +++ b/system/Entity/Entity.php @@ -181,7 +181,7 @@ class Entity implements JsonSerializable { $this->_cast = $cast; - $keys = array_filter(array_keys($this->attributes), static fn ($key) => ! str_starts_with($key, '_')); + $keys = array_filter(array_keys($this->attributes), static fn ($key): bool => ! str_starts_with($key, '_')); if (is_array($this->datamap)) { $keys = array_unique( diff --git a/system/Files/FileCollection.php b/system/Files/FileCollection.php index f131079586..179bbc1d49 100644 --- a/system/Files/FileCollection.php +++ b/system/Files/FileCollection.php @@ -108,7 +108,7 @@ class FileCollection implements Countable, IteratorAggregate $pattern = "#\\A{$pattern}\\z#"; } - return array_filter($files, static fn ($value) => (bool) preg_match($pattern, basename($value))); + return array_filter($files, static fn ($value): bool => (bool) preg_match($pattern, basename($value))); } // -------------------------------------------------------------------- diff --git a/system/HTTP/URI.php b/system/HTTP/URI.php index d0d23a3d26..2b386797fe 100644 --- a/system/HTTP/URI.php +++ b/system/HTTP/URI.php @@ -1021,7 +1021,7 @@ class URI implements Stringable // Encode characters $path = preg_replace_callback( '/(?:[^' . static::CHAR_UNRESERVED . ':@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/', - static fn (array $matches) => rawurlencode($matches[0]), + static fn (array $matches): string => rawurlencode($matches[0]), $path ); @@ -1167,9 +1167,9 @@ class URI implements Stringable $return = []; $query = explode('&', $query); - $params = array_map(static fn (string $chunk) => preg_replace_callback( + $params = array_map(static fn (string $chunk): ?string => preg_replace_callback( '/^(?[^&=]+?)(?:\[[^&=]*\])?=(?[^&=]+)/', - static fn (array $match) => str_replace($match['key'], bin2hex($match['key']), $match[0]), + static fn (array $match): string => str_replace($match['key'], bin2hex($match['key']), $match[0]), urldecode($chunk) ), $query); diff --git a/system/Helpers/Array/ArrayHelper.php b/system/Helpers/Array/ArrayHelper.php index 4bbd34427f..fc5bc46295 100644 --- a/system/Helpers/Array/ArrayHelper.php +++ b/system/Helpers/Array/ArrayHelper.php @@ -58,7 +58,7 @@ final class ArrayHelper ); return array_map( - static fn ($key) => str_replace('\.', '.', $key), + static fn ($key): string => str_replace('\.', '.', $key), $segments ); } @@ -96,7 +96,7 @@ final class ArrayHelper $answer[] = self::arraySearchDot($indexes, $value); } - $answer = array_filter($answer, static fn ($value) => $value !== null); + $answer = array_filter($answer, static fn ($value): bool => $value !== null); if ($answer !== []) { // If array only has one element, we return that element for BC. diff --git a/system/Helpers/form_helper.php b/system/Helpers/form_helper.php index 7e11388841..679008c973 100644 --- a/system/Helpers/form_helper.php +++ b/system/Helpers/form_helper.php @@ -739,10 +739,10 @@ if (! function_exists('validation_show_error')) { $config = config(Validation::class); $view = service('renderer'); - $errors = array_filter(validation_errors(), static fn ($key) => preg_match( + $errors = array_filter(validation_errors(), static fn ($key): bool => preg_match( '/^' . str_replace(['\.\*', '\*\.'], ['\..+', '.+\.'], preg_quote($field, '/')) . '$/', $key - ), ARRAY_FILTER_USE_KEY); + ) === 1, ARRAY_FILTER_USE_KEY); if ($errors === []) { return ''; diff --git a/system/Language/Language.php b/system/Language/Language.php index 1ab2c52691..cbea4a5332 100644 --- a/system/Language/Language.php +++ b/system/Language/Language.php @@ -206,11 +206,11 @@ class Language $argsString = implode( ', ', - array_map(static fn ($element) => '"' . $element . '"', $args) + array_map(static fn ($element): string => '"' . $element . '"', $args) ); $argsUrlEncoded = implode( ', ', - array_map(static fn ($element) => '"' . rawurlencode($element) . '"', $args) + array_map(static fn ($element): string => '"' . rawurlencode($element) . '"', $args) ); log_message( diff --git a/system/Router/AutoRouter.php b/system/Router/AutoRouter.php index ce40f5cdc4..cad9ecbc17 100644 --- a/system/Router/AutoRouter.php +++ b/system/Router/AutoRouter.php @@ -184,7 +184,7 @@ final class AutoRouter implements AutoRouterInterface */ private function scanControllers(array $segments): array { - $segments = array_filter($segments, static fn ($segment) => $segment !== ''); + $segments = array_filter($segments, static fn ($segment): bool => $segment !== ''); // numerically reindex the array, removing gaps $segments = array_values($segments); diff --git a/system/Router/AutoRouterImproved.php b/system/Router/AutoRouterImproved.php index 63cdd77026..6160c0555d 100644 --- a/system/Router/AutoRouterImproved.php +++ b/system/Router/AutoRouterImproved.php @@ -136,7 +136,7 @@ final class AutoRouterImproved implements AutoRouterInterface private function createSegments(string $uri): array { $segments = explode('/', $uri); - $segments = array_filter($segments, static fn ($segment) => $segment !== ''); + $segments = array_filter($segments, static fn ($segment): bool => $segment !== ''); // numerically reindex the array, removing gaps return array_values($segments); @@ -209,7 +209,7 @@ final class AutoRouterImproved implements AutoRouterInterface } $namespaces = array_map( - fn ($segment) => $this->translateURI($segment), + fn ($segment): string => $this->translateURI($segment), $segments ); diff --git a/system/Router/RouteCollection.php b/system/Router/RouteCollection.php index 13cbba4499..faee291a82 100644 --- a/system/Router/RouteCollection.php +++ b/system/Router/RouteCollection.php @@ -1504,7 +1504,7 @@ class RouteCollection implements RouteCollectionInterface for ($i = (int) $options['offset'] + 1; $i < (int) $options['offset'] + 7; $i++) { $to = preg_replace_callback( '/\$X/', - static fn ($m) => '$' . $i, + static fn ($m): string => '$' . $i, $to, 1 ); diff --git a/system/Router/Router.php b/system/Router/Router.php index 0cd1614991..4e8c08852f 100644 --- a/system/Router/Router.php +++ b/system/Router/Router.php @@ -590,7 +590,7 @@ class Router implements RouterInterface */ protected function scanControllers(array $segments): array { - $segments = array_filter($segments, static fn ($segment) => $segment !== ''); + $segments = array_filter($segments, static fn ($segment): bool => $segment !== ''); // numerically reindex the array, removing gaps $segments = array_values($segments); diff --git a/system/Test/ReflectionHelper.php b/system/Test/ReflectionHelper.php index a1b7ee99c6..988fe94900 100644 --- a/system/Test/ReflectionHelper.php +++ b/system/Test/ReflectionHelper.php @@ -41,7 +41,7 @@ trait ReflectionHelper $refMethod = new ReflectionMethod($obj, $method); $obj = (gettype($obj) === 'object') ? $obj : null; - return static fn (...$args) => $refMethod->invokeArgs($obj, $args); + return static fn (...$args): mixed => $refMethod->invokeArgs($obj, $args); } /** diff --git a/system/Validation/DotArrayFilter.php b/system/Validation/DotArrayFilter.php index c7a401e158..62da95cb61 100644 --- a/system/Validation/DotArrayFilter.php +++ b/system/Validation/DotArrayFilter.php @@ -32,7 +32,7 @@ final class DotArrayFilter foreach ($indexes as $index) { $segments = preg_split('/(? str_replace('\.', '.', $key), $segments); + $segments = array_map(static fn ($key): string => str_replace('\.', '.', $key), $segments); $filteredArray = self::filter($segments, $array); diff --git a/system/Validation/Validation.php b/system/Validation/Validation.php index c00329d670..5b51b403fe 100644 --- a/system/Validation/Validation.php +++ b/system/Validation/Validation.php @@ -176,7 +176,7 @@ class Validation implements ValidationInterface $values = array_filter( $flattenedArray, - static fn ($key) => preg_match(self::getRegex($field), $key), + static fn ($key): bool => preg_match(self::getRegex($field), $key) === 1, ARRAY_FILTER_USE_KEY ); @@ -413,7 +413,7 @@ class Validation implements ValidationInterface } // Otherwise remove the if_exist rule and continue the process - $rules = array_filter($rules, static fn ($rule) => $rule instanceof Closure || $rule !== 'if_exist'); + $rules = array_filter($rules, static fn ($rule): bool => $rule instanceof Closure || $rule !== 'if_exist'); } return $rules; @@ -460,7 +460,7 @@ class Validation implements ValidationInterface } } - $rules = array_filter($rules, static fn ($rule) => $rule instanceof Closure || $rule !== 'permit_empty'); + $rules = array_filter($rules, static fn ($rule): bool => $rule instanceof Closure || $rule !== 'permit_empty'); } return $rules; @@ -867,7 +867,7 @@ class Validation implements ValidationInterface $errors = array_filter( $this->getErrors(), - static fn ($key) => preg_match(self::getRegex($field), $key), + static fn ($key): bool => preg_match(self::getRegex($field), $key) === 1, ARRAY_FILTER_USE_KEY ); diff --git a/system/View/Table.php b/system/View/Table.php index 9eb29578a6..a063e71c5a 100644 --- a/system/View/Table.php +++ b/system/View/Table.php @@ -228,13 +228,13 @@ class Table $missingKeys = array_diff_key($keyIndex, $tmpRow); // Remove all keys which don't exist in $keyIndex - $tmpRow = array_filter($tmpRow, static fn ($k) => array_key_exists($k, $keyIndex), ARRAY_FILTER_USE_KEY); + $tmpRow = array_filter($tmpRow, static fn ($k): bool => array_key_exists($k, $keyIndex), ARRAY_FILTER_USE_KEY); // add missing keys to row, but use $this->emptyCells - $tmpRow = array_merge($tmpRow, array_map(fn ($v) => ['data' => $this->emptyCells], $missingKeys)); + $tmpRow = array_merge($tmpRow, array_map(fn ($v): array => ['data' => $this->emptyCells], $missingKeys)); // order keys by $keyIndex values - uksort($tmpRow, static fn ($k1, $k2) => $keyIndex[$k1] <=> $keyIndex[$k2]); + uksort($tmpRow, static fn ($k1, $k2): int => $keyIndex[$k1] <=> $keyIndex[$k2]); } $this->rows[] = $tmpRow; diff --git a/tests/_support/Config/Routes.php b/tests/_support/Config/Routes.php index 9b3ead4fd6..c6286056e0 100644 --- a/tests/_support/Config/Routes.php +++ b/tests/_support/Config/Routes.php @@ -15,7 +15,7 @@ namespace Tests\Support\Config; // This is a simple file to include for testing the RouteCollection class. $routes->add('testing', 'TestController::index', ['as' => 'testing-index']); -$routes->get('closure', static fn () => 'closure test'); +$routes->get('closure', static fn (): string => 'closure test'); $routes->get('/', 'Blog::index', ['hostname' => 'blog.example.com']); $routes->get('/', 'Sub::index', ['subdomain' => 'sub']); $routes->get('/all', 'AllDomain::index', ['subdomain' => '*']); diff --git a/tests/system/AutoReview/FrameworkCodeTest.php b/tests/system/AutoReview/FrameworkCodeTest.php index b8b155bbbf..7473d26e58 100644 --- a/tests/system/AutoReview/FrameworkCodeTest.php +++ b/tests/system/AutoReview/FrameworkCodeTest.php @@ -136,7 +136,7 @@ final class FrameworkCodeTest extends TestCase $testClasses = array_filter( $testClasses, - static fn (string $class) => is_subclass_of($class, TestCase::class) + static fn (string $class): bool => is_subclass_of($class, TestCase::class) ); sort($testClasses); diff --git a/tests/system/Cache/Handlers/DummyHandlerTest.php b/tests/system/Cache/Handlers/DummyHandlerTest.php index 1b6fa3aa77..c9c8a0fa09 100644 --- a/tests/system/Cache/Handlers/DummyHandlerTest.php +++ b/tests/system/Cache/Handlers/DummyHandlerTest.php @@ -42,7 +42,7 @@ final class DummyHandlerTest extends CIUnitTestCase public function testRemember(): void { - $dummyHandler = $this->handler->remember('key', 2, static fn () => 'value'); + $dummyHandler = $this->handler->remember('key', 2, static fn (): string => 'value'); $this->assertNull($dummyHandler); } diff --git a/tests/system/Cache/Handlers/FileHandlerTest.php b/tests/system/Cache/Handlers/FileHandlerTest.php index 9a7213374d..47223198ca 100644 --- a/tests/system/Cache/Handlers/FileHandlerTest.php +++ b/tests/system/Cache/Handlers/FileHandlerTest.php @@ -135,7 +135,7 @@ final class FileHandlerTest extends AbstractHandlerTestCase */ public function testRemember(): void { - $this->handler->remember(self::$key1, 2, static fn () => 'value'); + $this->handler->remember(self::$key1, 2, static fn (): string => 'value'); $this->assertSame('value', $this->handler->get(self::$key1)); $this->assertNull($this->handler->get(self::$dummy)); diff --git a/tests/system/Cache/Handlers/MemcachedHandlerTest.php b/tests/system/Cache/Handlers/MemcachedHandlerTest.php index 45a44b64fe..36c8c220aa 100644 --- a/tests/system/Cache/Handlers/MemcachedHandlerTest.php +++ b/tests/system/Cache/Handlers/MemcachedHandlerTest.php @@ -86,7 +86,7 @@ final class MemcachedHandlerTest extends AbstractHandlerTestCase */ public function testRemember(): void { - $this->handler->remember(self::$key1, 2, static fn () => 'value'); + $this->handler->remember(self::$key1, 2, static fn (): string => 'value'); $this->assertSame('value', $this->handler->get(self::$key1)); $this->assertNull($this->handler->get(self::$dummy)); diff --git a/tests/system/Cache/Handlers/PredisHandlerTest.php b/tests/system/Cache/Handlers/PredisHandlerTest.php index 6d88d6e3f6..9b11c23b06 100644 --- a/tests/system/Cache/Handlers/PredisHandlerTest.php +++ b/tests/system/Cache/Handlers/PredisHandlerTest.php @@ -91,7 +91,7 @@ final class PredisHandlerTest extends AbstractHandlerTestCase */ public function testRemember(): void { - $this->handler->remember(self::$key1, 2, static fn () => 'value'); + $this->handler->remember(self::$key1, 2, static fn (): string => 'value'); $this->assertSame('value', $this->handler->get(self::$key1)); $this->assertNull($this->handler->get(self::$dummy)); diff --git a/tests/system/Cache/Handlers/RedisHandlerTest.php b/tests/system/Cache/Handlers/RedisHandlerTest.php index 8aa2339b06..0eee45f570 100644 --- a/tests/system/Cache/Handlers/RedisHandlerTest.php +++ b/tests/system/Cache/Handlers/RedisHandlerTest.php @@ -97,7 +97,7 @@ final class RedisHandlerTest extends AbstractHandlerTestCase */ public function testRemember(): void { - $this->handler->remember(self::$key1, 2, static fn () => 'value'); + $this->handler->remember(self::$key1, 2, static fn (): string => 'value'); $this->assertSame('value', $this->handler->get(self::$key1)); $this->assertNull($this->handler->get(self::$dummy)); diff --git a/tests/system/CodeIgniterTest.php b/tests/system/CodeIgniterTest.php index 735aed3895..afddc7dbb3 100644 --- a/tests/system/CodeIgniterTest.php +++ b/tests/system/CodeIgniterTest.php @@ -214,7 +214,7 @@ final class CodeIgniterTest extends CIUnitTestCase $routes = service('routes'); $routes->add( 'pages/(:segment)', - static fn ($segment) => 'You want to see "' . esc($segment) . '" page.' + static fn ($segment): string => 'You want to see "' . esc($segment) . '" page.' ); $router = service('router', $routes, service('incomingrequest')); Services::injectMock('router', $router); diff --git a/tests/system/CommonHelperTest.php b/tests/system/CommonHelperTest.php index db304ecfdb..8a41f7972a 100644 --- a/tests/system/CommonHelperTest.php +++ b/tests/system/CommonHelperTest.php @@ -13,7 +13,9 @@ declare(strict_types=1); namespace CodeIgniter; +use CodeIgniter\Autoloader\Autoloader; use CodeIgniter\Autoloader\FileLocator; +use CodeIgniter\Files\Exceptions\FileNotFoundException; use CodeIgniter\Test\CIUnitTestCase; use Config\Services; use PHPUnit\Framework\Attributes\CoversFunction; @@ -148,4 +150,33 @@ final class CommonHelperTest extends CIUnitTestCase $this->assertSame($this->dummyHelpers[0], foo_bar_baz()); } + + public function testNamespacedHelperNotFound(): void + { + $this->expectException(FileNotFoundException::class); + + $locator = $this->getMockLocator(); + Services::injectMock('locator', $locator); + + helper('foo\barbaz'); + } + + public function testNamespacedHelperFound(): void + { + $autoloader = new Autoloader(); + $autoloader->addNamespace('Tests\Support\Helpers', TESTPATH . '_support/Helpers'); + $locator = new FileLocator($autoloader); + + Services::injectMock('locator', $locator); + + $found = true; + + try { + helper('Tests\Support\Helpers\baguette'); + } catch (FileNotFoundException) { + $found = false; + } + + $this->assertTrue($found); + } } diff --git a/tests/system/Database/Builder/UnionTest.php b/tests/system/Database/Builder/UnionTest.php index bd498e2e2c..5711c6c3cb 100644 --- a/tests/system/Database/Builder/UnionTest.php +++ b/tests/system/Database/Builder/UnionTest.php @@ -47,7 +47,7 @@ final class UnionTest extends CIUnitTestCase $builder = $this->db->table('test'); - $builder->union(static fn ($builder) => $builder->from('test')); + $builder->union(static fn ($builder): BaseBuilder => $builder->from('test')); $this->assertSame($expected, $this->buildSelect($builder)); } @@ -85,7 +85,7 @@ final class UnionTest extends CIUnitTestCase $builder = $db->table('users'); - $builder->union(static fn ($builder) => $builder->from('users')); + $builder->union(static fn ($builder): BaseBuilder => $builder->from('users')); $this->assertSame($expected, $this->buildSelect($builder)); } diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php index 16ea6b528c..0ab672e720 100644 --- a/tests/system/Database/Live/ForgeTest.php +++ b/tests/system/Database/Live/ForgeTest.php @@ -1319,8 +1319,7 @@ final class ForgeTest extends CIUnitTestCase $this->forge->createTable('forge_test_two'); $this->assertTrue($this->db->fieldExists('name', 'forge_test_two')); - - $this->forge->dropColumn('forge_test_two', 'name'); + $this->assertTrue($this->forge->dropColumn('forge_test_two', 'name')); $this->db->resetDataCache(); @@ -1707,7 +1706,7 @@ final class ForgeTest extends CIUnitTestCase // check that db_actions_name key exists $indexes = array_filter( $allIndexes, - static fn ($index) => ($index->name === 'db_actions_name') + static fn ($index): bool => ($index->name === 'db_actions_name') && ($index->fields === [0 => 'name']) ); $this->assertCount(1, $indexes); @@ -1715,7 +1714,7 @@ final class ForgeTest extends CIUnitTestCase // check that db_actions_category_name key exists $indexes = array_filter( $allIndexes, - static fn ($index) => ($index->name === 'db_actions_category_name') + static fn ($index): bool => ($index->name === 'db_actions_category_name') && ($index->fields === [0 => 'category', 1 => 'name']) ); $this->assertCount(1, $indexes); @@ -1723,7 +1722,7 @@ final class ForgeTest extends CIUnitTestCase // check that the primary key exists $indexes = array_filter( $allIndexes, - static fn ($index) => $index->type === 'PRIMARY' + static fn ($index): bool => $index->type === 'PRIMARY' ); $this->assertCount(1, $indexes); @@ -1755,7 +1754,7 @@ final class ForgeTest extends CIUnitTestCase // check that db_actions_name key exists $indexes = array_filter( $allIndexes, - static fn ($index) => ($index->name === 'db_actions_name') + static fn ($index): bool => ($index->name === 'db_actions_name') && ($index->fields === [0 => 'name']) ); $this->assertCount(1, $indexes); @@ -1781,7 +1780,7 @@ final class ForgeTest extends CIUnitTestCase // check that the primary key exists $indexes = array_filter( $allIndexes, - static fn ($index) => $index->type === 'PRIMARY' + static fn ($index): bool => $index->type === 'PRIMARY' ); $this->assertCount(1, $indexes); diff --git a/tests/system/Debug/TimerTest.php b/tests/system/Debug/TimerTest.php index 09bf0d535b..c7b02678e4 100644 --- a/tests/system/Debug/TimerTest.php +++ b/tests/system/Debug/TimerTest.php @@ -157,7 +157,7 @@ final class TimerTest extends CIUnitTestCase public function testRecordArrowFunction(): void { $timer = new Timer(); - $returnValue = $timer->record('longjohn', static fn () => strlen('CI4')); + $returnValue = $timer->record('longjohn', static fn (): int => strlen('CI4')); $this->assertLessThan(0.1, $timer->getElapsedTime('longjohn')); $this->assertSame(3, $returnValue); @@ -196,7 +196,7 @@ final class TimerTest extends CIUnitTestCase public function testCommonNoNameCallableExpectTimer(): void { - $returnValue = timer(null, static fn () => strlen('CI4')); + $returnValue = timer(null, static fn (): int => strlen('CI4')); $this->assertInstanceOf(Timer::class, $returnValue); } @@ -212,7 +212,7 @@ final class TimerTest extends CIUnitTestCase public function testCommonCallableExpectWithReturn(): void { - $returnValue = timer('common', static fn () => strlen('CI4')); + $returnValue = timer('common', static fn (): int => strlen('CI4')); $this->assertNotInstanceOf(Timer::class, $returnValue); $this->assertSame(3, $returnValue); diff --git a/tests/system/HTTP/ContentSecurityPolicyTest.php b/tests/system/HTTP/ContentSecurityPolicyTest.php index 596fbfcf0b..c1d90554fc 100644 --- a/tests/system/HTTP/ContentSecurityPolicyTest.php +++ b/tests/system/HTTP/ContentSecurityPolicyTest.php @@ -441,7 +441,7 @@ final class ContentSecurityPolicyTest extends CIUnitTestCase $result = $this->work($body); $nonceStyle = array_filter( $this->getPrivateProperty($this->csp, 'styleSrc'), - static fn ($value) => str_starts_with($value, 'nonce-') + static fn ($value): bool => str_starts_with($value, 'nonce-') ); $this->assertStringContainsString('nonce=', $this->response->getBody()); @@ -516,7 +516,7 @@ final class ContentSecurityPolicyTest extends CIUnitTestCase $result = $this->work($body); $nonceScript = array_filter( $this->getPrivateProperty($this->csp, 'scriptSrc'), - static fn ($value) => str_starts_with($value, 'nonce-') + static fn ($value): bool => str_starts_with($value, 'nonce-') ); $this->assertStringContainsString('nonce=', $this->response->getBody()); diff --git a/tests/system/Router/DefinedRouteCollectorTest.php b/tests/system/Router/DefinedRouteCollectorTest.php index 3c1d29abc0..2ec354c888 100644 --- a/tests/system/Router/DefinedRouteCollectorTest.php +++ b/tests/system/Router/DefinedRouteCollectorTest.php @@ -51,8 +51,8 @@ final class DefinedRouteCollectorTest extends CIUnitTestCase $routes->get('journals', 'Blogs'); $routes->get('100', 'Home::index'); $routes->get('product/(:num)', 'Catalog::productLookupByID/$1'); - $routes->get('feed', static fn () => 'A Closure route.'); - $routes->get('200', static fn () => 'A Closure route.'); + $routes->get('feed', static fn (): string => 'A Closure route.'); + $routes->get('200', static fn (): string => 'A Closure route.'); $routes->view('about', 'pages/about'); $collector = new DefinedRouteCollector($routes); diff --git a/tests/system/Router/RouterTest.php b/tests/system/Router/RouterTest.php index 5ce347e07f..4ad823dda8 100644 --- a/tests/system/Router/RouterTest.php +++ b/tests/system/Router/RouterTest.php @@ -68,8 +68,8 @@ final class RouterTest extends CIUnitTestCase 'posts/(:num)' => 'Blog::show/$1', 'posts/(:num)/edit' => 'Blog::edit/$1', 'books/(:num)/(:alpha)/(:num)' => 'Blog::show/$3/$1', - 'closure/(:num)/(:alpha)' => static fn ($num, $str) => $num . '-' . $str, - 'closure-dash/(:num)/(:alpha)' => static fn ($num, $str) => $num . '-' . $str, + 'closure/(:num)/(:alpha)' => static fn ($num, $str): string => $num . '-' . $str, + 'closure-dash/(:num)/(:alpha)' => static fn ($num, $str): string => $num . '-' . $str, '{locale}/pages' => 'App\Pages::list_all', 'test/(:any)/lang/{locale}' => 'App\Pages::list_all', 'admin/admins' => 'App\Admin\Admins::list_all', diff --git a/tests/system/Test/FeatureTestTraitTest.php b/tests/system/Test/FeatureTestTraitTest.php index 47aaea07d1..349d1af233 100644 --- a/tests/system/Test/FeatureTestTraitTest.php +++ b/tests/system/Test/FeatureTestTraitTest.php @@ -54,7 +54,7 @@ final class FeatureTestTraitTest extends CIUnitTestCase [ 'GET', 'home', - static fn () => 'Hello World', + static fn (): string => 'Hello World', ], ]); $response = $this->get('home'); @@ -74,7 +74,7 @@ final class FeatureTestTraitTest extends CIUnitTestCase [ 'GET', 'foo/bar/1/2/3', - static fn () => 'Hello World', + static fn (): string => 'Hello World', ], ]); $response = $this->get('foo/bar/1/2/3'); @@ -90,7 +90,7 @@ final class FeatureTestTraitTest extends CIUnitTestCase [ 'GET', 'admin', - static fn () => 'Admin Area', + static fn (): string => 'Admin Area', ['filter' => 'test-redirectfilter'], ], ]); @@ -123,7 +123,7 @@ final class FeatureTestTraitTest extends CIUnitTestCase [ 'POST', 'home', - static fn () => 'Hello Mars', + static fn (): string => 'Hello Mars', ], ]); $response = $this->post('home'); @@ -137,7 +137,7 @@ final class FeatureTestTraitTest extends CIUnitTestCase [ 'POST', 'home', - static fn () => 'Hello ' . service('request')->getPost('foo') . '!', + static fn (): string => 'Hello ' . service('request')->getPost('foo') . '!', ], ]); $response = $this->post('home', ['foo' => 'Mars']); @@ -181,7 +181,7 @@ final class FeatureTestTraitTest extends CIUnitTestCase [ 'PUT', 'home', - static fn () => 'Hello Pluto', + static fn (): string => 'Hello Pluto', ], ]); $response = $this->put('home'); @@ -195,7 +195,7 @@ final class FeatureTestTraitTest extends CIUnitTestCase [ 'PATCH', 'home', - static fn () => 'Hello Jupiter', + static fn (): string => 'Hello Jupiter', ], ]); $response = $this->patch('home'); @@ -209,7 +209,7 @@ final class FeatureTestTraitTest extends CIUnitTestCase [ 'OPTIONS', 'home', - static fn () => 'Hello George', + static fn (): string => 'Hello George', ], ]); $response = $this->options('home'); @@ -223,7 +223,7 @@ final class FeatureTestTraitTest extends CIUnitTestCase [ 'DELETE', 'home', - static fn () => 'Hello Wonka', + static fn (): string => 'Hello Wonka', ], ]); $response = $this->delete('home'); @@ -237,7 +237,7 @@ final class FeatureTestTraitTest extends CIUnitTestCase [ 'GET', 'home', - static fn () => 'Home', + static fn (): string => 'Home', ], ])->withSession([ 'fruit' => 'apple', @@ -259,7 +259,7 @@ final class FeatureTestTraitTest extends CIUnitTestCase [ 'GET', 'home', - static fn () => 'Home', + static fn (): string => 'Home', ], ])->withSession()->get('home'); diff --git a/tests/system/Validation/RulesTest.php b/tests/system/Validation/RulesTest.php index a7e3e985de..b0b23c00f6 100644 --- a/tests/system/Validation/RulesTest.php +++ b/tests/system/Validation/RulesTest.php @@ -119,7 +119,7 @@ class RulesTest extends CIUnitTestCase ], // Testing with closure [ - ['foo' => ['if_exist', static fn ($value) => true]], + ['foo' => ['if_exist', static fn ($value): bool => true]], ['foo' => []], true, ], @@ -299,7 +299,7 @@ class RulesTest extends CIUnitTestCase ], [ // Testing with closure - ['foo' => ['permit_empty', static fn ($value) => true]], + ['foo' => ['permit_empty', static fn ($value): bool => true]], ['foo' => ''], true, ], diff --git a/tests/system/Validation/StrictRules/RulesTest.php b/tests/system/Validation/StrictRules/RulesTest.php index 7589360d0e..de06bfe576 100644 --- a/tests/system/Validation/StrictRules/RulesTest.php +++ b/tests/system/Validation/StrictRules/RulesTest.php @@ -91,7 +91,7 @@ final class RulesTest extends TraditionalRulesTest ], // Testing with closure [ - ['foo' => ['permit_empty', static fn ($value) => true]], + ['foo' => ['permit_empty', static fn ($value): bool => true]], ['foo' => ''], true, ], diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php index 5906e2ac54..f6d7252dc1 100644 --- a/tests/system/Validation/ValidationTest.php +++ b/tests/system/Validation/ValidationTest.php @@ -284,7 +284,7 @@ class ValidationTest extends CIUnitTestCase { $this->validation->setRules( [ - 'foo' => ['required', static fn ($value) => $value === 'abc'], + 'foo' => ['required', static fn ($value): bool => $value === 'abc'], ], [ // Errors @@ -339,7 +339,7 @@ class ValidationTest extends CIUnitTestCase $this->validation->setRules([ 'secret' => [ 'label' => 'γ‚·γƒΌγ‚―γƒ¬γƒƒγƒˆ', - 'rules' => ['required', static fn ($value) => $value === 'abc'], + 'rules' => ['required', static fn ($value): bool => $value === 'abc'], 'errors' => [ // Specify the array key for the closure rule. 1 => 'The {field} is invalid', diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php index 33ff7880f8..27a3100ce8 100644 --- a/tests/system/View/ParserTest.php +++ b/tests/system/View/ParserTest.php @@ -797,7 +797,7 @@ final class ParserTest extends CIUnitTestCase public function testParserPluginClosure(): void { $config = $this->config; - $config->plugins['hello'] = static fn (array $params = []) => 'Hello, ' . trim($params[0]); + $config->plugins['hello'] = static fn (array $params = []): string => 'Hello, ' . trim($params[0]); $this->parser = new Parser($config, $this->viewsDir, $this->loader); @@ -828,7 +828,7 @@ final class ParserTest extends CIUnitTestCase public function testParserSingleTag(): void { - $this->parser->addPlugin('hit:it', static fn () => 'Hip to the Hop', false); + $this->parser->addPlugin('hit:it', static fn (): string => 'Hip to the Hop', false); $template = '{+ hit:it +}'; @@ -837,7 +837,7 @@ final class ParserTest extends CIUnitTestCase public function testParserSingleTagWithParams(): void { - $this->parser->addPlugin('hit:it', static fn (array $params = []) => "{$params['first']} to the {$params['last']}", false); + $this->parser->addPlugin('hit:it', static fn (array $params = []): string => "{$params['first']} to the {$params['last']}", false); $template = '{+ hit:it first=foo last=bar +}'; @@ -846,7 +846,7 @@ final class ParserTest extends CIUnitTestCase public function testParserSingleTagWithSingleParams(): void { - $this->parser->addPlugin('hit:it', static fn (array $params = []) => "{$params[0]} to the {$params[1]}", false); + $this->parser->addPlugin('hit:it', static fn (array $params = []): string => "{$params[0]} to the {$params[1]}", false); $template = '{+ hit:it foo bar +}'; diff --git a/user_guide_src/source/changelogs/index.rst b/user_guide_src/source/changelogs/index.rst index a4d102ada2..e4185217c9 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.7 v4.5.6 v4.5.5 v4.5.4 diff --git a/user_guide_src/source/changelogs/v4.5.7.rst b/user_guide_src/source/changelogs/v4.5.7.rst new file mode 100644 index 0000000000..b57dea4abc --- /dev/null +++ b/user_guide_src/source/changelogs/v4.5.7.rst @@ -0,0 +1,22 @@ +############# +Version 4.5.7 +############# + +Release Date: December 31, 2024 + +**4.5.7 release of CodeIgniter4** + +.. contents:: + :local: + :depth: 3 + +********** +Bugs Fixed +********** + +- **Common:** Fixed a bug where the ``helper()`` method may throw `FileNotFoundException` on valid namespaced helper. +- **Forge:** Fixed an issue where `SQLite3`'s Forge always returns `false` when calling ``dropColumn()``. + +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 c5960c23a3..4effd4c7b8 100644 --- a/user_guide_src/source/conf.py +++ b/user_guide_src/source/conf.py @@ -26,7 +26,7 @@ copyright = '2019-' + str(year_now) + ' CodeIgniter Foundation' version = '4.5' # The full version, including alpha/beta/rc tags. -release = '4.5.6' +release = '4.5.7' # -- General configuration --------------------------------------------------- diff --git a/user_guide_src/source/database/queries/016.php b/user_guide_src/source/database/queries/016.php index f674f7c7cc..61c743fdfd 100644 --- a/user_guide_src/source/database/queries/016.php +++ b/user_guide_src/source/database/queries/016.php @@ -1,9 +1,7 @@ prepare(static function ($db) { - return $db->table('user')->insert([ - 'name' => 'x', - 'email' => 'y', - 'country' => 'US', - ]); -}); +$pQuery = $db->prepare(static fn ($db) => $db->table('user')->insert([ + 'name' => 'x', + 'email' => 'y', + 'country' => 'US', +])); diff --git a/user_guide_src/source/database/queries/019.php b/user_guide_src/source/database/queries/019.php index 28729f2e48..8157a5d0f4 100644 --- a/user_guide_src/source/database/queries/019.php +++ b/user_guide_src/source/database/queries/019.php @@ -1,13 +1,11 @@ prepare(static function ($db) { - return $db->table('user')->insert([ - 'name' => 'x', - 'email' => 'y', - 'country' => 'US', - ]); -}); +$pQuery = $db->prepare(static fn ($db) => $db->table('user')->insert([ + 'name' => 'x', + 'email' => 'y', + 'country' => 'US', +])); // Collect the Data $name = 'John Doe'; diff --git a/user_guide_src/source/installation/upgrade_457.rst b/user_guide_src/source/installation/upgrade_457.rst new file mode 100644 index 0000000000..d919c81871 --- /dev/null +++ b/user_guide_src/source/installation/upgrade_457.rst @@ -0,0 +1,32 @@ +############################# +Upgrading from 4.5.6 to 4.5.7 +############################# + +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/Views/errors/cli/error_exception.php diff --git a/user_guide_src/source/installation/upgrading.rst b/user_guide_src/source/installation/upgrading.rst index 0b489abb91..a8dffd3660 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_457 upgrade_456 upgrade_455 upgrade_454 diff --git a/utils/phpstan-baseline/missingType.iterableValue.neon b/utils/phpstan-baseline/missingType.iterableValue.neon index 812228800d..6abf970718 100644 --- a/utils/phpstan-baseline/missingType.iterableValue.neon +++ b/utils/phpstan-baseline/missingType.iterableValue.neon @@ -1,4 +1,4 @@ -# total 1686 errors +# total 1685 errors parameters: ignoreErrors: @@ -1697,11 +1697,6 @@ parameters: count: 1 path: ../../system/Database/Forge.php - - - message: '#^Method CodeIgniter\\Database\\Forge\:\:dropColumn\(\) has parameter \$columnNames with no value type specified in iterable type array\.$#' - count: 1 - path: ../../system/Database/Forge.php - - message: '#^Method CodeIgniter\\Database\\Forge\:\:modifyColumn\(\) has parameter \$fields with no value type specified in iterable type array\.$#' count: 1