Merge pull request #9417 from codeigniter4/4.6

4.6.0 Merge code
This commit is contained in:
John Paul E. Balandan, CPA 2025-01-20 01:28:51 +08:00 committed by GitHub
commit 3be476e91d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
471 changed files with 13664 additions and 8156 deletions

View File

@ -25,11 +25,10 @@ body:
description: Which PHP versions did you run your code?
multiple: true
options:
- '7.4'
- '8.0'
- '8.1'
- '8.2'
- '8.3'
- '8.4'
validations:
required: true

View File

@ -29,7 +29,7 @@ jobs:
matrix:
php-version:
- '8.1'
- '8.3'
- '8.4'
steps:
- name: Checkout base branch for PR
@ -65,3 +65,5 @@ jobs:
- name: Run lint
run: composer cs
env:
PHP_CS_FIXER_IGNORE_ENV: ${{ matrix.php-version == '8.4' }}

View File

@ -59,9 +59,7 @@ jobs:
- '8.1'
- '8.2'
- '8.3'
include:
- php-version: '8.3'
composer-option: '--ignore-platform-req=php'
- '8.4'
uses: ./.github/workflows/reusable-phpunit-test.yml # @TODO Extract to codeigniter4/.github repo
with:
@ -88,6 +86,7 @@ jobs:
- '8.1'
- '8.2'
- '8.3'
- '8.4'
db-platform:
- MySQLi
- OCI8
@ -100,8 +99,6 @@ jobs:
- php-version: '8.1'
db-platform: MySQLi
mysql-version: '5.7'
- php-version: '8.3'
composer-option: '--ignore-platform-req=php'
uses: ./.github/workflows/reusable-phpunit-test.yml # @TODO Extract to codeigniter4/.github repo
with:
@ -129,9 +126,7 @@ jobs:
- '8.1'
- '8.2'
- '8.3'
include:
- php-version: '8.3'
composer-option: '--ignore-platform-req=php'
- '8.4'
uses: ./.github/workflows/reusable-phpunit-test.yml # @TODO Extract to codeigniter4/.github repo
with:
@ -157,9 +152,7 @@ jobs:
- '8.1'
- '8.2'
- '8.3'
include:
- php-version: '8.3'
composer-option: '--ignore-platform-req=php'
- '8.4'
uses: ./.github/workflows/reusable-phpunit-test.yml # @TODO Extract to codeigniter4/.github repo
with:

View File

@ -45,7 +45,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: ['8.1', '8.3']
php-versions: ['8.1', '8.4']
steps:
- name: Checkout base branch for PR
if: github.event_name == 'pull_request'

View File

@ -13,18 +13,18 @@
"php": "^8.1",
"ext-intl": "*",
"ext-mbstring": "*",
"laminas/laminas-escaper": "^2.13",
"laminas/laminas-escaper": "^2.14",
"psr/log": "^3.0"
},
"require-dev": {
"codeigniter/coding-standard": "^1.7",
"fakerphp/faker": "^1.9",
"fakerphp/faker": "^1.24",
"friendsofphp/php-cs-fixer": "^3.47.1",
"kint-php/kint": "^5.0.4",
"mikey179/vfsstream": "^1.6",
"kint-php/kint": "^6.0",
"mikey179/vfsstream": "^1.6.12",
"nexusphp/cs-config": "^3.6",
"phpunit/phpunit": "^10.5.16 || ^11.2",
"predis/predis": "^1.1 || ^2.0"
"predis/predis": "^1.1 || ^2.3"
},
"suggest": {
"ext-curl": "If you use CURLRequest class",

View File

@ -34,18 +34,6 @@ class Cache extends BaseConfig
*/
public string $backupHandler = 'dummy';
/**
* --------------------------------------------------------------------------
* Cache Directory Path
* --------------------------------------------------------------------------
*
* The path to where cache files should be stored, if using a file-based
* system.
*
* @deprecated Use the driver-specific variant under $file
*/
public string $storePath = WRITEPATH . 'cache/';
/**
* --------------------------------------------------------------------------
* Key Prefix
@ -86,6 +74,7 @@ class Cache extends BaseConfig
* --------------------------------------------------------------------------
* File settings
* --------------------------------------------------------------------------
*
* Your file storage preferences can be specified below, if you are using
* the File driver.
*
@ -100,6 +89,7 @@ class Cache extends BaseConfig
* -------------------------------------------------------------------------
* Memcached settings
* -------------------------------------------------------------------------
*
* Your Memcached servers can be specified below, if you are using
* the Memcached drivers.
*

View File

@ -77,18 +77,3 @@ defined('EXIT_USER_INPUT') || define('EXIT_USER_INPUT', 7); // invalid u
defined('EXIT_DATABASE') || define('EXIT_DATABASE', 8); // database error
defined('EXIT__AUTO_MIN') || define('EXIT__AUTO_MIN', 9); // lowest automatically-assigned error code
defined('EXIT__AUTO_MAX') || define('EXIT__AUTO_MAX', 125); // highest automatically-assigned error code
/**
* @deprecated Use \CodeIgniter\Events\Events::PRIORITY_LOW instead.
*/
define('EVENT_PRIORITY_LOW', 200);
/**
* @deprecated Use \CodeIgniter\Events\Events::PRIORITY_NORMAL instead.
*/
define('EVENT_PRIORITY_NORMAL', 100);
/**
* @deprecated Use \CodeIgniter\Events\Events::PRIORITY_HIGH instead.
*/
define('EVENT_PRIORITY_HIGH', 10);

View File

@ -43,6 +43,7 @@ class Database extends Config
'failover' => [],
'port' => 3306,
'numberNative' => false,
'foundRows' => false,
'dateFormat' => [
'date' => 'Y-m-d',
'datetime' => 'Y-m-d H:i:s',
@ -64,6 +65,7 @@ class Database extends Config
// 'failover' => [],
// 'foreignKeys' => true,
// 'busyTimeout' => 1000,
// 'synchronous' => null,
// 'dateFormat' => [
// 'date' => 'Y-m-d',
// 'datetime' => 'Y-m-d H:i:s',

View File

@ -10,9 +10,9 @@ use CodeIgniter\Config\BaseConfig;
class Feature extends BaseConfig
{
/**
* Use improved new auto routing instead of the default legacy version.
* Use improved new auto routing instead of the legacy version.
*/
public bool $autoRoutesImproved = false;
public bool $autoRoutesImproved = true;
/**
* Use filter execution order in 4.4 or before.
@ -26,4 +26,12 @@ class Feature extends BaseConfig
* If false, `limit(0)` returns no records. (the behavior of 3.1.9 or later in version 3.x.)
*/
public bool $limitZeroAsAll = true;
/**
* Use strict location negotiation.
*
* By default, the locale is selected based on a loose comparison of the language code (ISO 639-1)
* Enabling strict comparison will also consider the region code (ISO 3166-1 alpha-2).
*/
public bool $strictLocaleNegotiation = false;
}

View File

@ -3,7 +3,6 @@
namespace Config;
use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Format\FormatterInterface;
use CodeIgniter\Format\JSONFormatter;
use CodeIgniter\Format\XMLFormatter;
@ -62,16 +61,4 @@ class Format extends BaseConfig
'application/xml' => 0,
'text/xml' => 0,
];
/**
* A Factory method to return the appropriate formatter for the given mime type.
*
* @return FormatterInterface
*
* @deprecated This is an alias of `\CodeIgniter\Format\Format::getFormatter`. Use that instead.
*/
public function getFormatter(string $mime)
{
return service('format')->getFormatter($mime);
}
}

View File

@ -3,7 +3,6 @@
namespace Config;
use Kint\Parser\ConstructablePluginInterface;
use Kint\Renderer\AbstractRenderer;
use Kint\Renderer\Rich\TabPluginInterface;
use Kint\Renderer\Rich\ValuePluginInterface;
@ -41,7 +40,6 @@ class Kint
*/
public string $richTheme = 'aante-light.css';
public bool $richFolder = false;
public int $richSort = AbstractRenderer::SORT_FULL;
/**
* @var array<string, class-string<ValuePluginInterface>>|null

View File

@ -136,5 +136,5 @@ class Routing extends BaseRouting
*
* Default: false
*/
public bool $translateUriToCamelCase = false;
public bool $translateUriToCamelCase = true;
}

View File

@ -83,21 +83,4 @@ class Security extends BaseConfig
* @see https://codeigniter4.github.io/userguide/libraries/security.html#redirection-on-failure
*/
public bool $redirect = (ENVIRONMENT === 'production');
/**
* --------------------------------------------------------------------------
* CSRF SameSite
* --------------------------------------------------------------------------
*
* Setting for CSRF SameSite cookie token.
*
* Allowed values are: None - Lax - Strict - ''.
*
* Defaults to `Lax` as recommended in this link:
*
* @see https://portswigger.net/web-security/csrf/samesite-cookies
*
* @deprecated `Config\Cookie` $samesite property is used.
*/
public string $samesite = 'Lax';
}

View File

@ -41,6 +41,7 @@ p.lead {
.header {
background: var(--light-bg-color);
color: var(--dark-text-color);
margin-top: 2.17rem;
}
.header .container {
padding: 1rem;
@ -65,10 +66,13 @@ p.lead {
}
.environment {
background: var(--dark-bg-color);
color: var(--light-text-color);
background: var(--brand-primary-color);
color: var(--main-bg-color);
text-align: center;
padding: 0.2rem;
padding: calc(4px + 0.2083vw);
width: 100%;
margin-top: -2.14rem;
position: fixed;
}
.source {

View File

@ -0,0 +1,84 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?= lang('Errors.badRequest') ?></title>
<style>
div.logo {
height: 200px;
width: 155px;
display: inline-block;
opacity: 0.08;
position: absolute;
top: 2rem;
left: 50%;
margin-left: -73px;
}
body {
height: 100%;
background: #fafafa;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #777;
font-weight: 300;
}
h1 {
font-weight: lighter;
letter-spacing: normal;
font-size: 3rem;
margin-top: 0;
margin-bottom: 0;
color: #222;
}
.wrap {
max-width: 1024px;
margin: 5rem auto;
padding: 2rem;
background: #fff;
text-align: center;
border: 1px solid #efefef;
border-radius: 0.5rem;
position: relative;
}
pre {
white-space: normal;
margin-top: 1.5rem;
}
code {
background: #fafafa;
border: 1px solid #efefef;
padding: 0.5rem 1rem;
border-radius: 5px;
display: block;
}
p {
margin-top: 1.5rem;
}
.footer {
margin-top: 2rem;
border-top: 1px solid #efefef;
padding: 1em 2em 0 2em;
font-size: 85%;
color: #999;
}
a:active,
a:link,
a:visited {
color: #dd4814;
}
</style>
</head>
<body>
<div class="wrap">
<h1>400</h1>
<p>
<?php if (ENVIRONMENT !== 'production') : ?>
<?= nl2br(esc($message)) ?>
<?php else : ?>
<?= lang('Errors.sorryBadRequest') ?>
<?php endif; ?>
</p>
</div>
</body>
</html>

View File

@ -13,21 +13,21 @@
"php": "^8.1",
"ext-intl": "*",
"ext-mbstring": "*",
"laminas/laminas-escaper": "^2.13",
"laminas/laminas-escaper": "^2.14",
"psr/log": "^3.0"
},
"require-dev": {
"codeigniter/phpstan-codeigniter": "^1.5.1",
"fakerphp/faker": "^1.9",
"kint-php/kint": "^5.0.4",
"mikey179/vfsstream": "^1.6",
"fakerphp/faker": "^1.24",
"kint-php/kint": "^6.0",
"mikey179/vfsstream": "^1.6.12",
"nexusphp/tachycardia": "^2.0",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "^2.0",
"phpstan/phpstan-strict-rules": "^2.0",
"phpunit/phpcov": "^9.0.2 || ^10.0",
"phpunit/phpunit": "^10.5.16 || ^11.2",
"predis/predis": "^1.1 || ^2.0",
"predis/predis": "^1.1 || ^2.3",
"rector/rector": "2.0.6",
"shipmonk/phpstan-baseline-per-identifier": "^2.0"
},
@ -90,7 +90,7 @@
"CodeIgniter\\ComposerScripts::postUpdate"
],
"post-autoload-dump": [
"@composer update --working-dir=utils"
"@composer update --working-dir=utils --ignore-platform-req=php"
],
"analyze": [
"Composer\\Config::disableProcessTimeout",

View File

@ -38,5 +38,8 @@ parameters:
disallowedImplicitArrayCreation: true
disallowedShortTernary: true
matchingInheritedMethodNames: true
codeigniter:
additionalServices:
- AfterAutoloadModule\Config\Services
shipmonkBaselinePerIdentifier:
directory: %currentWorkingDirectory%

View File

@ -19,7 +19,6 @@ use Rector\CodeQuality\Rector\FuncCall\ChangeArrayPushToArrayAssignRector;
use Rector\CodeQuality\Rector\FuncCall\CompactToVariablesRector;
use Rector\CodeQuality\Rector\FunctionLike\SimplifyUselessVariableRector;
use Rector\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector;
use Rector\CodeQuality\Rector\If_\ShortenElseIfRector;
use Rector\CodeQuality\Rector\Ternary\TernaryEmptyArrayArrayDimFetchToCoalesceRector;
use Rector\CodingStyle\Rector\ClassMethod\FuncGetArgsToVariadicParamRector;
use Rector\CodingStyle\Rector\ClassMethod\MakeInheritedMethodVisibilitySameAsParentRector;
@ -183,7 +182,6 @@ return RectorConfig::configure()
ChangeIfElseValueAssignToEarlyReturnRector::class,
InlineIfToExplicitIfRector::class,
PreparedValueToEarlyReturnRector::class,
ShortenElseIfRector::class,
UnusedForeachValueToArrayKeysRector::class,
ChangeArrayPushToArrayAssignRector::class,
RemoveErrorSuppressInTryCatchStmtsRector::class,
@ -208,4 +206,4 @@ return RectorConfig::configure()
// keep '\\' prefix string on string '\Foo\Bar'
StringClassNameToClassConstantRector::SHOULD_KEEP_PRE_SLASH => true,
])
->withCodeQualityLevel(31);
->withCodeQualityLevel(34);

View File

@ -224,18 +224,6 @@ trait ResponseTrait
return $this->fail($description, $this->codes['resource_not_found'], $code, $message);
}
/**
* Used when the data provided by the client cannot be validated.
*
* @return ResponseInterface
*
* @deprecated Use failValidationErrors instead
*/
protected function failValidationError(string $description = 'Bad Request', ?string $code = null, string $message = '')
{
return $this->fail($description, $this->codes['invalid_data'], $code, $message);
}
/**
* Used when the data provided by the client cannot be validated on one or more fields.
*

View File

@ -14,16 +14,16 @@ declare(strict_types=1);
namespace CodeIgniter\Autoloader;
use CodeIgniter\Exceptions\ConfigException;
use CodeIgniter\Exceptions\InvalidArgumentException;
use CodeIgniter\Exceptions\RuntimeException;
use Composer\Autoload\ClassLoader;
use Composer\InstalledVersions;
use Config\Autoload;
use Config\Kint as KintConfig;
use Config\Modules;
use InvalidArgumentException;
use Kint;
use Kint\Renderer\CliRenderer;
use Kint\Renderer\RichRenderer;
use RuntimeException;
/**
* An autoloader that uses both PSR4 autoloading, and traditional classmaps.
@ -547,7 +547,7 @@ class Autoloader
RichRenderer::$theme = $config->richTheme;
RichRenderer::$folder = $config->richFolder;
RichRenderer::$sort = $config->richSort;
if (isset($config->richObjectPlugins) && is_array($config->richObjectPlugins)) {
RichRenderer::$value_plugins = $config->richObjectPlugins;
}

View File

@ -21,12 +21,12 @@ use CodeIgniter\Database\Exceptions\DataException;
use CodeIgniter\Database\Query;
use CodeIgniter\DataConverter\DataConverter;
use CodeIgniter\Entity\Entity;
use CodeIgniter\Exceptions\InvalidArgumentException;
use CodeIgniter\Exceptions\ModelException;
use CodeIgniter\I18n\Time;
use CodeIgniter\Pager\Pager;
use CodeIgniter\Validation\ValidationInterface;
use Config\Feature;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionException;
use ReflectionProperty;

View File

@ -14,7 +14,7 @@ declare(strict_types=1);
namespace CodeIgniter\CLI;
use CodeIgniter\CLI\Exceptions\CLIException;
use InvalidArgumentException;
use CodeIgniter\Exceptions\InvalidArgumentException;
use Throwable;
/**

View File

@ -52,12 +52,12 @@ class Commands
/**
* Runs a command given
*
* @return int|void Exit code
* @return int Exit code
*/
public function run(string $command, array $params)
{
if (! $this->verifyCommand($command, $this->commands)) {
return;
return EXIT_ERROR;
}
// The file would have already been loaded during the

View File

@ -14,7 +14,7 @@ declare(strict_types=1);
namespace CodeIgniter\CLI\Exceptions;
use CodeIgniter\Exceptions\DebugTraceableTrait;
use RuntimeException;
use CodeIgniter\Exceptions\RuntimeException;
/**
* CLIException

View File

@ -14,13 +14,12 @@ declare(strict_types=1);
namespace CodeIgniter\Cache\Exceptions;
use CodeIgniter\Exceptions\DebugTraceableTrait;
use CodeIgniter\Exceptions\ExceptionInterface;
use RuntimeException;
use CodeIgniter\Exceptions\RuntimeException;
/**
* CacheException
*/
class CacheException extends RuntimeException implements ExceptionInterface
class CacheException extends RuntimeException
{
use DebugTraceableTrait;

View File

@ -15,9 +15,10 @@ namespace CodeIgniter\Cache\Handlers;
use Closure;
use CodeIgniter\Cache\CacheInterface;
use CodeIgniter\Exceptions\BadMethodCallException;
use CodeIgniter\Exceptions\InvalidArgumentException;
use Config\Cache;
use Exception;
use InvalidArgumentException;
/**
* Base class for cache handling
@ -108,6 +109,6 @@ abstract class BaseHandler implements CacheInterface
*/
public function deleteMatching(string $pattern)
{
throw new Exception('The deleteMatching method is not implemented.');
throw new BadMethodCallException('The deleteMatching method is not implemented.');
}
}

View File

@ -54,13 +54,6 @@ class FileHandler extends BaseHandler
*/
public function __construct(Cache $config)
{
if (! property_exists($config, 'file')) {
$config->file = [
'storePath' => $config->storePath ?? WRITEPATH . 'cache',
'mode' => 0640,
];
}
$this->path = ! empty($config->file['storePath']) ? $config->file['storePath'] : WRITEPATH . 'cache';
$this->path = rtrim($this->path, '/') . '/';

View File

@ -13,6 +13,7 @@ declare(strict_types=1);
namespace CodeIgniter\Cache\Handlers;
use CodeIgniter\Exceptions\BadMethodCallException;
use CodeIgniter\Exceptions\CriticalError;
use CodeIgniter\I18n\Time;
use Config\Cache;
@ -197,7 +198,7 @@ class MemcachedHandler extends BaseHandler
*/
public function deleteMatching(string $pattern)
{
throw new Exception('The deleteMatching method is not implemented for Memcached. You must select File, Redis or Predis handlers to use it.');
throw new BadMethodCallException('The deleteMatching method is not implemented for Memcached. You must select File, Redis or Predis handlers to use it.');
}
/**

View File

@ -13,9 +13,9 @@ declare(strict_types=1);
namespace CodeIgniter\Cache\Handlers;
use CodeIgniter\Exceptions\BadMethodCallException;
use CodeIgniter\I18n\Time;
use Config\Cache;
use Exception;
/**
* Cache handler for WinCache from Microsoft & IIS.
@ -80,7 +80,7 @@ class WincacheHandler extends BaseHandler
*/
public function deleteMatching(string $pattern)
{
throw new Exception('The deleteMatching method is not implemented for Wincache. You must select File, Redis or Predis handlers to use it.');
throw new BadMethodCallException('The deleteMatching method is not implemented for Wincache. You must select File, Redis or Predis handlers to use it.');
}
/**

View File

@ -13,12 +13,12 @@ declare(strict_types=1);
namespace CodeIgniter\Cache;
use CodeIgniter\Exceptions\RuntimeException;
use CodeIgniter\HTTP\CLIRequest;
use CodeIgniter\HTTP\Header;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Cache as CacheConfig;
use Exception;
/**
* Web Page Caching
@ -131,7 +131,7 @@ final class ResponseCache
|| ! isset($cachedResponse['output'])
|| ! isset($cachedResponse['headers'])
) {
throw new Exception('Error unserializing page cache');
throw new RuntimeException('Error unserializing page cache');
}
$headers = $cachedResponse['headers'];

View File

@ -16,6 +16,7 @@ use CodeIgniter\Cache\ResponseCache;
use CodeIgniter\Debug\Timer;
use CodeIgniter\Events\Events;
use CodeIgniter\Exceptions\FrameworkException;
use CodeIgniter\Exceptions\LogicException;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\Filters\Filters;
use CodeIgniter\HTTP\CLIRequest;
@ -28,7 +29,6 @@ use CodeIgniter\HTTP\Request;
use CodeIgniter\HTTP\ResponsableInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\HTTP\URI;
use CodeIgniter\Router\Exceptions\RedirectException as DeprecatedRedirectException;
use CodeIgniter\Router\RouteCollectionInterface;
use CodeIgniter\Router\Router;
use Config\App;
@ -41,7 +41,6 @@ use Kint;
use Kint\Renderer\CliRenderer;
use Kint\Renderer\RichRenderer;
use Locale;
use LogicException;
use Throwable;
/**
@ -294,7 +293,7 @@ class CodeIgniter
RichRenderer::$theme = $config->richTheme;
RichRenderer::$folder = $config->richFolder;
RichRenderer::$sort = $config->richSort;
if (isset($config->richObjectPlugins) && is_array($config->richObjectPlugins)) {
RichRenderer::$value_plugins = $config->richObjectPlugins;
}
@ -318,7 +317,7 @@ class CodeIgniter
*
* @param bool $returnResponse Used for testing purposes only.
*
* @return ResponseInterface|void
* @return ResponseInterface|null
*/
public function run(?RouteCollectionInterface $routes = null, bool $returnResponse = false)
{
@ -353,11 +352,8 @@ class CodeIgniter
} else {
try {
$this->response = $this->handleRequest($routes, config(Cache::class), $returnResponse);
} catch (DeprecatedRedirectException|ResponsableInterface $e) {
} catch (ResponsableInterface $e) {
$this->outputBufferingEnd();
if ($e instanceof DeprecatedRedirectException) {
$e = new RedirectException($e->getMessage(), $e->getCode(), $e);
}
$this->response = $e->getResponse();
} catch (PageNotFoundException $e) {
@ -379,6 +375,8 @@ class CodeIgniter
}
$this->sendResponse();
return null;
}
/**
@ -863,7 +861,7 @@ class CodeIgniter
* controller method and make the script go. If it's not able to, will
* show the appropriate Page Not Found error.
*
* @return ResponseInterface|string|void
* @return ResponseInterface|string|null
*/
protected function startController()
{
@ -889,6 +887,8 @@ class CodeIgniter
) {
throw PageNotFoundException::forControllerNotFound($this->controller, $this->method);
}
return null;
}
/**

View File

@ -74,7 +74,7 @@ class MigrateRollback extends BaseCommand
$force = array_key_exists('f', $params) || CLI::getOption('f');
if (! $force && CLI::prompt(lang('Migrations.rollBackConfirm'), ['y', 'n']) === 'n') {
return;
return null;
}
// @codeCoverageIgnoreEnd
}
@ -115,5 +115,7 @@ class MigrateRollback extends BaseCommand
$this->showError($e);
// @codeCoverageIgnoreEnd
}
return null;
}
}

View File

@ -16,8 +16,9 @@ namespace CodeIgniter\Commands\Database;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Database\TableName;
use CodeIgniter\Exceptions\InvalidArgumentException;
use Config\Database;
use InvalidArgumentException;
/**
* Get table data if it exists in the database.
@ -199,7 +200,7 @@ class ShowTableInfo extends BaseCommand
CLI::newLine();
$this->removeDBPrefix();
$thead = $this->db->getFieldNames($tableName);
$thead = $this->db->getFieldNames(TableName::fromActualName($this->db->DBPrefix, $tableName));
$this->restoreDBPrefix();
// If there is a field named `id`, sort by it.
@ -277,7 +278,7 @@ class ShowTableInfo extends BaseCommand
$this->tbody = [];
$this->removeDBPrefix();
$builder = $this->db->table($tableName);
$builder = $this->db->table(TableName::fromActualName($this->db->DBPrefix, $tableName));
$builder->limit($limitRows);
if ($sortField !== null) {
$builder->orderBy($sortField, $this->sortDesc ? 'DESC' : 'ASC');

View File

@ -0,0 +1,202 @@
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Commands\Translation;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use CodeIgniter\Exceptions\LogicException;
use Config\App;
use ErrorException;
use FilesystemIterator;
use Locale;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;
/**
* @see \CodeIgniter\Commands\Translation\LocalizationSyncTest
*/
class LocalizationSync extends BaseCommand
{
protected $group = 'Translation';
protected $name = 'lang:sync';
protected $description = 'Synchronize translation files from one language to another.';
protected $usage = 'lang:sync [options]';
protected $arguments = [];
protected $options = [
'--locale' => 'The original locale (en, ru, etc.).',
'--target' => 'Target locale (en, ru, etc.).',
];
private string $languagePath;
public function run(array $params)
{
$optionTargetLocale = '';
$optionLocale = $params['locale'] ?? Locale::getDefault();
$this->languagePath = APPPATH . 'Language';
if (isset($params['target']) && $params['target'] !== '') {
$optionTargetLocale = $params['target'];
}
if (! in_array($optionLocale, config(App::class)->supportedLocales, true)) {
CLI::error(
'Error: "' . $optionLocale . '" is not supported. Supported locales: '
. implode(', ', config(App::class)->supportedLocales),
);
return EXIT_USER_INPUT;
}
if ($optionTargetLocale === '') {
CLI::error(
'Error: "--target" is not configured. Supported locales: '
. implode(', ', config(App::class)->supportedLocales),
);
return EXIT_USER_INPUT;
}
if (! in_array($optionTargetLocale, config(App::class)->supportedLocales, true)) {
CLI::error(
'Error: "' . $optionTargetLocale . '" is not supported. Supported locales: '
. implode(', ', config(App::class)->supportedLocales),
);
return EXIT_USER_INPUT;
}
if ($optionTargetLocale === $optionLocale) {
CLI::error(
'Error: You cannot have the same values for "--target" and "--locale".',
);
return EXIT_USER_INPUT;
}
if (ENVIRONMENT === 'testing') {
$this->languagePath = SUPPORTPATH . 'Language';
}
if ($this->process($optionLocale, $optionTargetLocale) === EXIT_ERROR) {
return EXIT_ERROR;
}
CLI::write('All operations done!');
return EXIT_SUCCESS;
}
private function process(string $originalLocale, string $targetLocale): int
{
$originalLocaleDir = $this->languagePath . DIRECTORY_SEPARATOR . $originalLocale;
$targetLocaleDir = $this->languagePath . DIRECTORY_SEPARATOR . $targetLocale;
if (! is_dir($originalLocaleDir)) {
CLI::error(
'Error: The "' . clean_path($originalLocaleDir) . '" directory was not found.',
);
return EXIT_ERROR;
}
// Unifying the error - mkdir() may cause an exception.
try {
if (! is_dir($targetLocaleDir) && ! mkdir($targetLocaleDir, 0775)) {
throw new ErrorException();
}
} catch (ErrorException $e) {
CLI::error(
'Error: The target directory "' . clean_path($targetLocaleDir) . '" cannot be accessed.',
);
return EXIT_ERROR;
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
$originalLocaleDir,
FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS,
),
);
/**
* @var array<non-empty-string, SplFileInfo> $files
*/
$files = iterator_to_array($iterator, true);
ksort($files);
foreach ($files as $originalLanguageFile) {
if ($originalLanguageFile->getExtension() !== 'php') {
continue;
}
$targetLanguageFile = $targetLocaleDir . DIRECTORY_SEPARATOR . $originalLanguageFile->getFilename();
$targetLanguageKeys = [];
$originalLanguageKeys = include $originalLanguageFile;
if (is_file($targetLanguageFile)) {
$targetLanguageKeys = include $targetLanguageFile;
}
$targetLanguageKeys = $this->mergeLanguageKeys($originalLanguageKeys, $targetLanguageKeys, $originalLanguageFile->getBasename('.php'));
$content = "<?php\n\nreturn " . var_export($targetLanguageKeys, true) . ";\n";
file_put_contents($targetLanguageFile, $content);
}
return EXIT_SUCCESS;
}
/**
* @param array<string, array<string,mixed>|string|null> $originalLanguageKeys
* @param array<string, array<string,mixed>|string|null> $targetLanguageKeys
*
* @return array<string, array<string,mixed>|string|null>
*/
private function mergeLanguageKeys(array $originalLanguageKeys, array $targetLanguageKeys, string $prefix = ''): array
{
$mergedLanguageKeys = [];
foreach ($originalLanguageKeys as $key => $value) {
$placeholderValue = $prefix !== '' ? $prefix . '.' . $key : $key;
if (is_string($value)) {
// Keep the old value
// TODO: The value type may not match the original one
if (array_key_exists($key, $targetLanguageKeys)) {
$mergedLanguageKeys[$key] = $targetLanguageKeys[$key];
continue;
}
// Set new key with placeholder
$mergedLanguageKeys[$key] = $placeholderValue;
} elseif (is_array($value)) {
if (! array_key_exists($key, $targetLanguageKeys)) {
$mergedLanguageKeys[$key] = $this->mergeLanguageKeys($value, [], $placeholderValue);
continue;
}
$mergedLanguageKeys[$key] = $this->mergeLanguageKeys($value, $targetLanguageKeys[$key], $placeholderValue);
} else {
throw new LogicException('Value for the key "' . $placeholderValue . '" is of the wrong type. Only "array" or "string" is allowed.');
}
}
return $mergedLanguageKeys;
}
}

View File

@ -73,13 +73,7 @@ class FilterCheck extends BaseCommand
*/
public function run(array $params)
{
$tbody = [];
if (! isset($params[0], $params[1])) {
CLI::error('You must specify a HTTP verb and a route.');
CLI::write(' Usage: ' . $this->usage);
CLI::write('Example: filter:check GET /');
CLI::write(' filter:check PUT products/1');
if (! $this->checkParams($params)) {
return EXIT_ERROR;
}
@ -107,15 +101,38 @@ class FilterCheck extends BaseCommand
return EXIT_ERROR;
}
$filters = $this->addRequiredFilters($filterCollector, $filters);
$this->showTable($filterCollector, $filters, $method, $route);
$this->showFilterClasses($filterCollector, $method, $route);
$tbody[] = [
strtoupper($method),
$route,
implode(' ', $filters['before']),
implode(' ', $filters['after']),
];
return EXIT_SUCCESS;
}
/**
* @param array<int|string, string|null> $params
*/
private function checkParams(array $params): bool
{
if (! isset($params[0], $params[1])) {
CLI::error('You must specify a HTTP verb and a route.');
CLI::write(' Usage: ' . $this->usage);
CLI::write('Example: filter:check GET /');
CLI::write(' filter:check PUT products/1');
return false;
}
return true;
}
/**
* @param array{before: list<string>, after: list<string>} $filters
*/
private function showTable(
FilterCollector $filterCollector,
array $filters,
string $method,
string $route,
): void {
$thead = [
'Method',
'Route',
@ -123,33 +140,60 @@ class FilterCheck extends BaseCommand
'After Filters',
];
CLI::table($tbody, $thead);
return EXIT_SUCCESS;
}
private function addRequiredFilters(FilterCollector $filterCollector, array $filters): array
{
$output = [];
$required = $filterCollector->getRequiredFilters();
$colored = [];
$coloredRequired = $this->colorItems($required);
foreach ($required['before'] as $filter) {
$filter = CLI::color($filter, 'yellow');
$colored[] = $filter;
$before = array_merge($coloredRequired['before'], $filters['before']);
$after = array_merge($filters['after'], $coloredRequired['after']);
$tbody = [];
$tbody[] = [
strtoupper($method),
$route,
implode(' ', $before),
implode(' ', $after),
];
CLI::table($tbody, $thead);
}
$output['before'] = array_merge($colored, $filters['before']);
$colored = [];
foreach ($required['after'] as $filter) {
$filter = CLI::color($filter, 'yellow');
$colored[] = $filter;
/**
* Color all elements of the array.
*
* @param array<array-key, mixed> $array
*
* @return array<array-key, mixed>
*/
private function colorItems(array $array): array
{
return array_map(function ($item): array|string {
if (is_array($item)) {
return $this->colorItems($item);
}
$output['after'] = array_merge($filters['after'], $colored);
return $output;
return CLI::color($item, 'yellow');
}, $array);
}
private function showFilterClasses(
FilterCollector $filterCollector,
string $method,
string $route,
): void {
$requiredFilterClasses = $filterCollector->getRequiredFilterClasses();
$filterClasses = $filterCollector->getClasses($method, $route);
$coloredRequiredFilterClasses = $this->colorItems($requiredFilterClasses);
$classList = [
'before' => array_merge($coloredRequiredFilterClasses['before'], $filterClasses['before']),
'after' => array_merge($filterClasses['after'], $coloredRequiredFilterClasses['after']),
];
foreach ($classList as $position => $classes) {
CLI::write(ucfirst($position) . ' Filter Classes:', 'cyan');
CLI::write(implode(' → ', $classes));
}
}
}

View File

@ -120,10 +120,10 @@ class Namespaces extends BaseCommand
private function truncate(string $string, int $max): string
{
$length = strlen($string);
$length = mb_strlen($string);
if ($length > $max) {
return substr($string, 0, $max - 3) . '...';
return mb_substr($string, 0, $max - 3) . '...';
}
return $string;

View File

@ -17,8 +17,8 @@ use CodeIgniter\Autoloader\FileLocator;
use CodeIgniter\Autoloader\FileLocatorCached;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use CodeIgniter\Exceptions\RuntimeException;
use CodeIgniter\Publisher\Publisher;
use RuntimeException;
/**
* Optimize for production.

View File

@ -14,6 +14,7 @@ declare(strict_types=1);
namespace CodeIgniter\Commands\Utilities;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use CodeIgniter\Security\CheckPhpIni;
/**
@ -41,7 +42,7 @@ final class PhpIniCheck extends BaseCommand
*
* @var string
*/
protected $description = 'Check your php.ini values.';
protected $description = 'Check your php.ini values in production environment.';
/**
* The Command's usage
@ -56,6 +57,7 @@ final class PhpIniCheck extends BaseCommand
* @var array<string, string>
*/
protected $arguments = [
'opcache' => 'Check detail opcache values in production environment.',
];
/**
@ -70,7 +72,24 @@ final class PhpIniCheck extends BaseCommand
*/
public function run(array $params)
{
CheckPhpIni::run();
if (isset($params[0]) && ! in_array($params[0], array_keys($this->arguments), true)) {
CLI::error('You must specify a correct argument.');
CLI::write(' Usage: ' . $this->usage);
CLI::write(' Example: phpini:check opcache');
CLI::write('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);
}
return EXIT_ERROR;
}
$argument = $params[0] ?? null;
CheckPhpIni::run(argument: $argument);
return EXIT_SUCCESS;
}

View File

@ -67,17 +67,24 @@ class Publish extends BaseCommand
*
* @var array<string, string>
*/
protected $options = [];
protected $options = [
'--namespace' => 'The namespace from which to search for files to publish. By default, all namespaces are analysed.',
];
/**
* Displays the help for the spark cli script itself.
*/
public function run(array $params)
{
$directory = array_shift($params) ?? 'Publishers';
$directory = $params[0] ?? 'Publishers';
$namespace = $params['namespace'] ?? '';
if ([] === $publishers = Publisher::discover($directory)) {
if ([] === $publishers = Publisher::discover($directory, $namespace)) {
if ($namespace === '') {
CLI::write(lang('Publisher.publishMissing', [$directory]));
} else {
CLI::write(lang('Publisher.publishMissingNamespace', [$directory, $namespace]));
}
return;
}

View File

@ -42,7 +42,7 @@ final class FilterCollector
* @param string $method HTTP verb like `GET`,`POST` or `CLI`.
* @param string $uri URI path to find filters for
*
* @return array{before: list<string>, after: list<string>} array of filter alias or classname
* @return array{before: list<string>, after: list<string>} array of alias/classname:args
*/
public function get(string $method, string $uri): array
{
@ -78,10 +78,52 @@ final class FilterCollector
return $finder->find($uri);
}
/**
* Returns filter classes for the URI
*
* @param string $method HTTP verb like `GET`,`POST` or `CLI`.
* @param string $uri URI path to find filters for
*
* @return array{before: list<string>, after: list<string>} array of classname:args
*/
public function getClasses(string $method, string $uri): array
{
if ($method === strtolower($method)) {
@trigger_error(
'Passing lowercase HTTP method "' . $method . '" is deprecated.'
. ' Use uppercase HTTP method like "' . strtoupper($method) . '".',
E_USER_DEPRECATED,
);
}
/**
* @deprecated 4.5.0
* @TODO Remove this in the future.
*/
$method = strtoupper($method);
if ($method === 'CLI') {
return [
'before' => [],
'after' => [],
];
}
$request = service('incomingrequest', null, false);
$request->setMethod($method);
$router = $this->createRouter($request);
$filters = $this->createFilters($request);
$finder = new FilterFinder($router, $filters);
return $finder->findClasses($uri);
}
/**
* Returns Required Filters
*
* @return array{before: list<string>, after: list<string>} array of filter alias or classname
* @return array{before: list<string>, after: list<string>} array of aliases
*/
public function getRequiredFilters(): array
{
@ -96,6 +138,24 @@ final class FilterCollector
return $finder->getRequiredFilters();
}
/**
* Returns Required Filter class list
*
* @return array{before: list<string>, after: list<string>} array of classnames
*/
public function getRequiredFilterClasses(): array
{
$request = service('incomingrequest', null, false);
$request->setMethod(Method::GET);
$router = $this->createRouter($request);
$filters = $this->createFilters($request);
$finder = new FilterFinder($router, $filters);
return $finder->getRequiredFilterClasses();
}
private function createRouter(Request $request): Router
{
$routes = service('routes');

View File

@ -46,23 +46,20 @@ final class FilterFinder
/**
* @param string $uri URI path to find filters for
*
* @return array{before: list<string>, after: list<string>} array of filter alias or classname
* @return array{before: list<string>, after: list<string>} array of alias/classname:args
*/
public function find(string $uri): array
{
$this->filters->reset();
// Add route filters
try {
// Add route filters
$routeFilters = $this->getRouteFilters($uri);
$this->filters->enableFilters($routeFilters, 'before');
$oldFilterOrder = config(Feature::class)->oldFilterOrder ?? false;
if (! $oldFilterOrder) {
$routeFilters = array_reverse($routeFilters);
}
$this->filters->enableFilters($routeFilters, 'after');
$this->filters->initialize($uri);
@ -81,10 +78,66 @@ final class FilterFinder
}
}
/**
* @param string $uri URI path to find filters for
*
* @return array{before: list<string>, after: list<string>} array of classname:args
*/
public function findClasses(string $uri): array
{
$this->filters->reset();
try {
// Add route filters
$routeFilters = $this->getRouteFilters($uri);
$this->filters->enableFilters($routeFilters, 'before');
$oldFilterOrder = config(Feature::class)->oldFilterOrder ?? false;
if (! $oldFilterOrder) {
$routeFilters = array_reverse($routeFilters);
}
$this->filters->enableFilters($routeFilters, 'after');
$this->filters->initialize($uri);
$filterClassList = $this->filters->getFiltersClass();
$filterClasses = [
'before' => [],
'after' => [],
];
foreach ($filterClassList['before'] as $classInfo) {
$classWithArguments = ($classInfo[1] === []) ? $classInfo[0]
: $classInfo[0] . ':' . implode(',', $classInfo[1]);
$filterClasses['before'][] = $classWithArguments;
}
foreach ($filterClassList['after'] as $classInfo) {
$classWithArguments = ($classInfo[1] === []) ? $classInfo[0]
: $classInfo[0] . ':' . implode(',', $classInfo[1]);
$filterClasses['after'][] = $classWithArguments;
}
return $filterClasses;
} catch (RedirectException) {
return [
'before' => [],
'after' => [],
];
} catch (BadRequestException|PageNotFoundException) {
return [
'before' => ['<unknown>'],
'after' => ['<unknown>'],
];
}
}
/**
* Returns Required Filters
*
* @return array{before: list<string>, after:list<string>}
* @return array{before: list<string>, after:list<string>} array of aliases
*/
public function getRequiredFilters(): array
{
@ -96,4 +149,31 @@ final class FilterFinder
'after' => $requiredAfter,
];
}
/**
* Returns Required Filter classes
*
* @return array{before: list<string>, after:list<string>}
*/
public function getRequiredFilterClasses(): array
{
$before = $this->filters->getRequiredClasses('before');
$after = $this->filters->getRequiredClasses('after');
$requiredBefore = [];
$requiredAfter = [];
foreach ($before as $classInfo) {
$requiredBefore[] = $classInfo[0];
}
foreach ($after as $classInfo) {
$requiredAfter[] = $classInfo[0];
}
return [
'before' => $requiredBefore,
'after' => $requiredAfter,
];
}
}

View File

@ -20,6 +20,8 @@ use CodeIgniter\Cookie\Exceptions\CookieException;
use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Database\ConnectionInterface;
use CodeIgniter\Debug\Timer;
use CodeIgniter\Exceptions\InvalidArgumentException;
use CodeIgniter\Exceptions\RuntimeException;
use CodeIgniter\Files\Exceptions\FileNotFoundException;
use CodeIgniter\HTTP\CLIRequest;
use CodeIgniter\HTTP\Exceptions\HTTPException;

View File

@ -11,11 +11,12 @@
namespace CodeIgniter\Config;
use CodeIgniter\Exceptions\ConfigException;
use CodeIgniter\Exceptions\RuntimeException;
use Config\Encryption;
use Config\Modules;
use ReflectionClass;
use ReflectionException;
use RuntimeException;
/**
* Class BaseConfig
@ -45,12 +46,22 @@ class BaseConfig
public static bool $override = true;
/**
* Has module discovery happened yet?
* Has module discovery completed?
*
* @var bool
*/
protected static $didDiscovery = false;
/**
* Is module discovery running or not?
*/
protected static bool $discovering = false;
/**
* The processing Registrar file for error message.
*/
protected static string $registrarFile = '';
/**
* The modules configuration.
*
@ -230,10 +241,24 @@ class BaseConfig
}
if (! static::$didDiscovery) {
// Discovery must be completed before the first instantiation of any Config class.
if (static::$discovering) {
throw new ConfigException(
'During Auto-Discovery of Registrars,'
. ' "' . static::class . '" executes Auto-Discovery again.'
. ' "' . clean_path(static::$registrarFile) . '" seems to have bad code.',
);
}
static::$discovering = true;
$locator = service('locator');
$registrarsFiles = $locator->search('Config/Registrar.php');
foreach ($registrarsFiles as $file) {
// Saves the file for error message.
static::$registrarFile = $file;
$className = $locator->findQualifiedNameFromPath($file);
if ($className === false) {
@ -244,6 +269,7 @@ class BaseConfig
}
static::$didDiscovery = true;
static::$discovering = false;
}
$shortName = (new ReflectionClass($this))->getShortName();

View File

@ -29,6 +29,7 @@ use CodeIgniter\Debug\Timer;
use CodeIgniter\Debug\Toolbar;
use CodeIgniter\Email\Email;
use CodeIgniter\Encryption\EncrypterInterface;
use CodeIgniter\Exceptions\InvalidArgumentException;
use CodeIgniter\Filters\Filters;
use CodeIgniter\Format\Format;
use CodeIgniter\Honeypot\Honeypot;
@ -79,7 +80,6 @@ use Config\Session as ConfigSession;
use Config\Toolbar as ConfigToolbar;
use Config\Validation as ConfigValidation;
use Config\View as ConfigView;
use InvalidArgumentException;
/**
* Services Configuration file.
@ -389,6 +389,15 @@ class BaseService
static::$mocks[strtolower($name)] = $mock;
}
/**
* Resets the service cache.
*/
public static function resetServicesCache(): void
{
self::$serviceNames = [];
static::$discovered = false;
}
protected static function buildServicesCache(): void
{
if (! static::$discovered) {

View File

@ -13,7 +13,7 @@ declare(strict_types=1);
namespace CodeIgniter\Config;
use InvalidArgumentException;
use CodeIgniter\Exceptions\InvalidArgumentException;
/**
* Environment-specific configuration

View File

@ -14,8 +14,8 @@ declare(strict_types=1);
namespace CodeIgniter\Config;
use CodeIgniter\Database\ConnectionInterface;
use CodeIgniter\Exceptions\InvalidArgumentException;
use CodeIgniter\Model;
use InvalidArgumentException;
/**
* Factories for creating instances.

View File

@ -58,15 +58,6 @@ interface CloneableCookieInterface extends CookieInterface
*/
public function withExpired();
/**
* Creates a new Cookie that will virtually never expire from the browser.
*
* @return static
*
* @deprecated See https://github.com/codeigniter4/CodeIgniter4/pull/6413
*/
public function withNeverExpiring();
/**
* Creates a new Cookie with a new path on the server the cookie is available.
*

View File

@ -15,11 +15,11 @@ namespace CodeIgniter\Cookie;
use ArrayAccess;
use CodeIgniter\Cookie\Exceptions\CookieException;
use CodeIgniter\Exceptions\InvalidArgumentException;
use CodeIgniter\Exceptions\LogicException;
use CodeIgniter\I18n\Time;
use Config\Cookie as CookieConfig;
use DateTimeInterface;
use InvalidArgumentException;
use LogicException;
use ReturnTypeWillChange;
/**
@ -465,18 +465,6 @@ class Cookie implements ArrayAccess, CloneableCookieInterface
return $cookie;
}
/**
* @deprecated See https://github.com/codeigniter4/CodeIgniter4/pull/6413
*/
public function withNeverExpiring()
{
$cookie = clone $this;
$cookie->expires = Time::now()->getTimestamp() + 5 * YEAR;
return $cookie;
}
/**
* {@inheritDoc}
*/

View File

@ -159,28 +159,6 @@ class CookieStore implements Countable, IteratorAggregate
return $store;
}
/**
* Dispatches all cookies in store.
*
* @deprecated Response should dispatch cookies.
*/
public function dispatch(): void
{
foreach ($this->cookies as $cookie) {
$name = $cookie->getPrefixedName();
$value = $cookie->getValue();
$options = $cookie->getOptions();
if ($cookie->isRaw()) {
$this->setRawCookie($name, $value, $options);
} else {
$this->setCookie($name, $value, $options);
}
}
$this->clear();
}
/**
* Returns all cookie instances in store.
*
@ -232,28 +210,4 @@ class CookieStore implements Countable, IteratorAggregate
}
}
}
/**
* Extracted call to `setrawcookie()` in order to run unit tests on it.
*
* @codeCoverageIgnore
*
* @deprecated
*/
protected function setRawCookie(string $name, string $value, array $options): void
{
setrawcookie($name, $value, $options);
}
/**
* Extracted call to `setcookie()` in order to run unit tests on it.
*
* @codeCoverageIgnore
*
* @deprecated
*/
protected function setCookie(string $name, string $value, array $options): void
{
setcookie($name, $value, $options);
}
}

View File

@ -13,7 +13,7 @@ declare(strict_types=1);
namespace CodeIgniter\DataCaster\Cast;
use InvalidArgumentException;
use CodeIgniter\Exceptions\InvalidArgumentException;
abstract class BaseCast implements CastInterface
{

View File

@ -14,8 +14,8 @@ declare(strict_types=1);
namespace CodeIgniter\DataCaster\Cast;
use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Exceptions\InvalidArgumentException;
use CodeIgniter\I18n\Time;
use InvalidArgumentException;
/**
* Class DatetimeCast
@ -43,12 +43,7 @@ class DatetimeCast extends BaseCast
/**
* @see https://www.php.net/manual/en/datetimeimmutable.createfromformat.php#datetimeimmutable.createfromformat.parameters
*/
$format = match ($params[0] ?? '') {
'' => $helper->dateFormat['datetime'],
'ms' => $helper->dateFormat['datetime-ms'],
'us' => $helper->dateFormat['datetime-us'],
default => throw new InvalidArgumentException('Invalid parameter: ' . $params[0]),
};
$format = self::getDateTimeFormat($params, $helper);
return Time::createFromFormat($format, $value);
}
@ -62,6 +57,29 @@ class DatetimeCast extends BaseCast
self::invalidTypeValueError($value);
}
return (string) $value;
if (! $helper instanceof BaseConnection) {
$message = 'The parameter $helper must be BaseConnection.';
throw new InvalidArgumentException($message);
}
$format = self::getDateTimeFormat($params, $helper);
return $value->format($format);
}
/**
* Gets DateTime format from the DB connection.
*
* @param list<string> $params Additional param
*/
protected static function getDateTimeFormat(array $params, BaseConnection $db): string
{
return match ($params[0] ?? '') {
'' => $db->dateFormat['datetime'],
'ms' => $db->dateFormat['datetime-ms'],
'us' => $db->dateFormat['datetime-us'],
default => throw new InvalidArgumentException('Invalid parameter: ' . $params[0]),
};
}
}

View File

@ -32,7 +32,7 @@ class TimestampCast extends BaseCast
self::invalidTypeValueError($value);
}
return Time::createFromTimestamp((int) $value);
return Time::createFromTimestamp((int) $value, date_default_timezone_get());
}
public static function set(

View File

@ -26,7 +26,7 @@ use CodeIgniter\DataCaster\Cast\TimestampCast;
use CodeIgniter\DataCaster\Cast\URICast;
use CodeIgniter\Entity\Cast\CastInterface as EntityCastInterface;
use CodeIgniter\Entity\Exceptions\CastException;
use InvalidArgumentException;
use CodeIgniter\Exceptions\InvalidArgumentException;
final class DataCaster
{

View File

@ -16,9 +16,9 @@ namespace CodeIgniter\Database;
use Closure;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\Exceptions\DataException;
use CodeIgniter\Exceptions\InvalidArgumentException;
use CodeIgniter\Traits\ConditionalTrait;
use Config\Feature;
use InvalidArgumentException;
/**
* Class BaseBuilder
@ -298,7 +298,7 @@ class BaseBuilder
/**
* Constructor
*
* @param array|string $tableName tablename or tablenames with or without aliases
* @param array|string|TableName $tableName tablename or tablenames with or without aliases
*
* Examples of $tableName: `mytable`, `jobs j`, `jobs j, users u`, `['jobs j','users u']`
*
@ -315,14 +315,19 @@ class BaseBuilder
*/
$this->db = $db;
if ($tableName instanceof TableName) {
$this->tableName = $tableName->getTableName();
$this->QBFrom[] = $this->db->escapeIdentifier($tableName);
$this->db->addTableAlias($tableName->getAlias());
}
// If it contains `,`, it has multiple tables
if (is_string($tableName) && ! str_contains($tableName, ',')) {
elseif (is_string($tableName) && ! str_contains($tableName, ',')) {
$this->tableName = $tableName; // @TODO remove alias if exists
$this->from($tableName);
} else {
$this->tableName = '';
}
$this->from($tableName);
}
if ($options !== null && $options !== []) {
foreach ($options as $key => $value) {
@ -3014,7 +3019,7 @@ class BaseBuilder
*
* @param array|string $table The table to inspect
*
* @return string|void
* @return string|null
*/
protected function trackAliases($table)
{
@ -3023,7 +3028,7 @@ class BaseBuilder
$this->trackAliases($t);
}
return;
return null;
}
// Does the string contain a comma? If so, we need to separate
@ -3038,11 +3043,13 @@ class BaseBuilder
$table = preg_replace('/\s+AS\s+/i', ' ', $table);
// Grab the alias
$table = trim(strrchr($table, ' '));
$alias = trim(strrchr($table, ' '));
// Store the alias, if it doesn't already exist
$this->db->addTableAlias($table);
$this->db->addTableAlias($alias);
}
return null;
}
/**

View File

@ -340,7 +340,7 @@ abstract class BaseConnection implements ConnectionInterface
/**
* Array of table aliases.
*
* @var array
* @var list<string>
*/
protected $aliasedTables = [];
@ -576,10 +576,14 @@ abstract class BaseConnection implements ConnectionInterface
*
* @return $this
*/
public function addTableAlias(string $table)
public function addTableAlias(string $alias)
{
if (! in_array($table, $this->aliasedTables, true)) {
$this->aliasedTables[] = $table;
if ($alias === '') {
return $this;
}
if (! in_array($alias, $this->aliasedTables, true)) {
$this->aliasedTables[] = $alias;
}
return $this;
@ -897,6 +901,16 @@ abstract class BaseConnection implements ConnectionInterface
return false;
}
/**
* Reset transaction status - to restart transactions after strict mode failure
*/
public function resetTransStatus(): static
{
$this->transStatus = true;
return $this;
}
/**
* Begin Transaction
*/
@ -915,7 +929,7 @@ abstract class BaseConnection implements ConnectionInterface
/**
* Returns a non-shared new instance of the query builder for this connection.
*
* @param array|string $tableName
* @param array|string|TableName $tableName
*
* @return BaseBuilder
*
@ -1046,7 +1060,7 @@ abstract class BaseConnection implements ConnectionInterface
* insert the table prefix (if it exists) in the proper position, and escape only
* the correct identifiers.
*
* @param array|int|string $item
* @param array|int|string|TableName $item
* @param bool $prefixSingle Prefix a table name with no segments?
* @param bool $protectIdentifiers Protect table or column names?
* @param bool $fieldExists Supplied $item contains a column name?
@ -1070,6 +1084,11 @@ abstract class BaseConnection implements ConnectionInterface
return $escapedArray;
}
if ($item instanceof TableName) {
/** @psalm-suppress NoValue I don't know why ERROR. */
return $this->escapeTableName($item);
}
// If you pass `['column1', 'column2']`, `$item` will be int because the array keys are int.
$item = (string) $item;
@ -1212,10 +1231,18 @@ abstract class BaseConnection implements ConnectionInterface
*
* This function escapes single identifier.
*
* @param non-empty-string $item
* @param non-empty-string|TableName $item
*/
public function escapeIdentifier(string $item): string
public function escapeIdentifier($item): string
{
if ($item === '') {
return '';
}
if ($item instanceof TableName) {
return $this->escapeTableName($item);
}
return $this->escapeChar
. str_replace(
$this->escapeChar,
@ -1225,6 +1252,17 @@ abstract class BaseConnection implements ConnectionInterface
. $this->escapeChar;
}
/**
* Returns escaped table name with alias.
*/
private function escapeTableName(TableName $tableName): string
{
$alias = $tableName->getAlias();
return $this->escapeIdentifier($tableName->getActualTableName())
. (($alias !== '') ? ' ' . $this->escapeIdentifier($alias) : '');
}
/**
* Escape the SQL Identifiers
*
@ -1538,12 +1576,16 @@ abstract class BaseConnection implements ConnectionInterface
/**
* Fetch Field Names
*
* @param string|TableName $tableName
*
* @return false|list<string>
*
* @throws DatabaseException
*/
public function getFieldNames(string $table)
public function getFieldNames($tableName)
{
$table = ($tableName instanceof TableName) ? $tableName->getTableName() : $tableName;
// Is there a cached result?
if (isset($this->dataCache['field_names'][$table])) {
return $this->dataCache['field_names'][$table];
@ -1553,7 +1595,7 @@ abstract class BaseConnection implements ConnectionInterface
$this->initialize();
}
if (false === ($sql = $this->_listColumns($table))) {
if (false === ($sql = $this->_listColumns($tableName))) {
if ($this->DBDebug) {
throw new DatabaseException('This feature is not available for the database you are using.');
}
@ -1769,9 +1811,11 @@ abstract class BaseConnection implements ConnectionInterface
/**
* Generates a platform-specific query string so that the column names can be fetched.
*
* @param string|TableName $table
*
* @return false|string
*/
abstract protected function _listColumns(string $table = '');
abstract protected function _listColumns($table = '');
/**
* Platform-specific field data information.

View File

@ -14,9 +14,9 @@ declare(strict_types=1);
namespace CodeIgniter\Database;
use ArgumentCountError;
use BadMethodCallException;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Events\Events;
use CodeIgniter\Exceptions\BadMethodCallException;
use ErrorException;
/**

View File

@ -14,8 +14,8 @@ declare(strict_types=1);
namespace CodeIgniter\Database;
use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Exceptions\InvalidArgumentException;
use Config\Database as DbConfig;
use InvalidArgumentException;
/**
* Class Config

View File

@ -15,7 +15,7 @@ namespace CodeIgniter\Database;
use CodeIgniter\Exceptions\ConfigException;
use CodeIgniter\Exceptions\CriticalError;
use InvalidArgumentException;
use CodeIgniter\Exceptions\InvalidArgumentException;
/**
* Database Connection Factory

View File

@ -14,7 +14,7 @@ declare(strict_types=1);
namespace CodeIgniter\Database\Exceptions;
use CodeIgniter\Exceptions\DebugTraceableTrait;
use RuntimeException;
use CodeIgniter\Exceptions\RuntimeException;
class DataException extends RuntimeException implements ExceptionInterface
{

View File

@ -14,9 +14,9 @@ declare(strict_types=1);
namespace CodeIgniter\Database\Exceptions;
use CodeIgniter\Exceptions\HasExitCodeInterface;
use Error;
use CodeIgniter\Exceptions\RuntimeException;
class DatabaseException extends Error implements ExceptionInterface, HasExitCodeInterface
class DatabaseException extends RuntimeException implements ExceptionInterface, HasExitCodeInterface
{
public function getExitCode(): int
{

View File

@ -14,8 +14,8 @@ declare(strict_types=1);
namespace CodeIgniter\Database;
use CodeIgniter\Database\Exceptions\DatabaseException;
use InvalidArgumentException;
use RuntimeException;
use CodeIgniter\Exceptions\InvalidArgumentException;
use CodeIgniter\Exceptions\RuntimeException;
use Throwable;
/**

View File

@ -16,10 +16,10 @@ namespace CodeIgniter\Database;
use CodeIgniter\CLI\CLI;
use CodeIgniter\Events\Events;
use CodeIgniter\Exceptions\ConfigException;
use CodeIgniter\Exceptions\RuntimeException;
use CodeIgniter\I18n\Time;
use Config\Database;
use Config\Migrations as MigrationsConfig;
use RuntimeException;
use stdClass;
/**

View File

@ -15,7 +15,8 @@ namespace CodeIgniter\Database\MySQLi;
use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Database\Exceptions\DatabaseException;
use LogicException;
use CodeIgniter\Database\TableName;
use CodeIgniter\Exceptions\LogicException;
use mysqli;
use mysqli_result;
use mysqli_sql_exception;
@ -81,6 +82,16 @@ class Connection extends BaseConnection
*/
public $numberNative = false;
/**
* Use MYSQLI_CLIENT_FOUND_ROWS
*
* Whether affectedRows() should return number of rows found,
* or number of rows changed, after an UPDATE query.
*
* @var bool
*/
public $foundRows = false;
/**
* Connect to the database.
*
@ -182,6 +193,10 @@ class Connection extends BaseConnection
$clientFlags += MYSQLI_CLIENT_SSL;
}
if ($this->foundRows) {
$clientFlags += MYSQLI_CLIENT_FOUND_ROWS;
}
try {
if ($this->mysqli->real_connect(
$hostname,
@ -408,10 +423,19 @@ class Connection extends BaseConnection
/**
* Generates a platform-specific query string so that the column names can be fetched.
*
* @param string|TableName $table
*/
protected function _listColumns(string $table = ''): string
protected function _listColumns($table = ''): string
{
return 'SHOW COLUMNS FROM ' . $this->protectIdentifiers($table, true, null, false);
$tableName = $this->protectIdentifiers(
$table,
true,
null,
false,
);
return 'SHOW COLUMNS FROM ' . $tableName;
}
/**

View File

@ -13,9 +13,9 @@ declare(strict_types=1);
namespace CodeIgniter\Database\MySQLi;
use BadMethodCallException;
use CodeIgniter\Database\BasePreparedQuery;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Exceptions\BadMethodCallException;
use mysqli;
use mysqli_result;
use mysqli_sql_exception;

View File

@ -16,6 +16,7 @@ namespace CodeIgniter\Database\OCI8;
use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\Query;
use CodeIgniter\Database\TableName;
use ErrorException;
use stdClass;
@ -284,18 +285,25 @@ class Connection extends BaseConnection
/**
* Generates a platform-specific query string so that the column names can be fetched.
*
* @param string|TableName $table
*/
protected function _listColumns(string $table = ''): string
protected function _listColumns($table = ''): string
{
if (str_contains($table, '.')) {
sscanf($table, '%[^.].%s', $owner, $table);
if ($table instanceof TableName) {
$tableName = $this->escape(strtoupper($table->getActualTableName()));
$owner = $this->username;
} elseif (str_contains($table, '.')) {
sscanf($table, '%[^.].%s', $owner, $tableName);
$tableName = $this->escape(strtoupper($this->DBPrefix . $tableName));
} else {
$owner = $this->username;
$tableName = $this->escape(strtoupper($this->DBPrefix . $table));
}
return 'SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS
WHERE UPPER(OWNER) = ' . $this->escape(strtoupper($owner)) . '
AND UPPER(TABLE_NAME) = ' . $this->escape(strtoupper($this->DBPrefix . $table));
AND UPPER(TABLE_NAME) = ' . $tableName;
}
/**

View File

@ -13,9 +13,9 @@ declare(strict_types=1);
namespace CodeIgniter\Database\OCI8;
use BadMethodCallException;
use CodeIgniter\Database\BasePreparedQuery;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Exceptions\BadMethodCallException;
use OCILob;
/**

View File

@ -16,7 +16,7 @@ namespace CodeIgniter\Database\Postgre;
use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\RawSql;
use InvalidArgumentException;
use CodeIgniter\Exceptions\InvalidArgumentException;
/**
* Builder for Postgre

View File

@ -16,6 +16,7 @@ namespace CodeIgniter\Database\Postgre;
use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\RawSql;
use CodeIgniter\Database\TableName;
use ErrorException;
use PgSql\Connection as PgSqlConnection;
use PgSql\Result as PgSqlResult;
@ -305,13 +306,20 @@ class Connection extends BaseConnection
/**
* Generates a platform-specific query string so that the column names can be fetched.
*
* @param string|TableName $table
*/
protected function _listColumns(string $table = ''): string
protected function _listColumns($table = ''): string
{
if ($table instanceof TableName) {
$tableName = $this->escape($table->getActualTableName());
} else {
$tableName = $this->escape($this->DBPrefix . strtolower($table));
}
return 'SELECT "column_name"
FROM "information_schema"."columns"
WHERE LOWER("table_name") = '
. $this->escape($this->DBPrefix . strtolower($table))
WHERE LOWER("table_name") = ' . $tableName
. ' ORDER BY "ordinal_position"';
}

View File

@ -13,9 +13,9 @@ declare(strict_types=1);
namespace CodeIgniter\Database\Postgre;
use BadMethodCallException;
use CodeIgniter\Database\BasePreparedQuery;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Exceptions\BadMethodCallException;
use Exception;
use PgSql\Connection as PgSqlConnection;
use PgSql\Result as PgSqlResult;

View File

@ -13,7 +13,7 @@ declare(strict_types=1);
namespace CodeIgniter\Database;
use BadMethodCallException;
use CodeIgniter\Exceptions\BadMethodCallException;
/**
* @template TConnection

View File

@ -15,6 +15,7 @@ namespace CodeIgniter\Database\SQLSRV;
use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\TableName;
use stdClass;
/**
@ -225,12 +226,20 @@ class Connection extends BaseConnection
/**
* Generates a platform-specific query string so that the column names can be fetched.
*
* @param string|TableName $table
*/
protected function _listColumns(string $table = ''): string
protected function _listColumns($table = ''): string
{
if ($table instanceof TableName) {
$tableName = $this->escape(strtolower($table->getActualTableName()));
} else {
$tableName = $this->escape($this->DBPrefix . strtolower($table));
}
return 'SELECT [COLUMN_NAME] '
. ' FROM [INFORMATION_SCHEMA].[COLUMNS]'
. ' WHERE [TABLE_NAME] = ' . $this->escape($this->DBPrefix . $table)
. ' WHERE [TABLE_NAME] = ' . $tableName
. ' AND [TABLE_SCHEMA] = ' . $this->escape($this->schema);
}

View File

@ -13,9 +13,9 @@ declare(strict_types=1);
namespace CodeIgniter\Database\SQLSRV;
use BadMethodCallException;
use CodeIgniter\Database\BasePreparedQuery;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Exceptions\BadMethodCallException;
/**
* Prepared query for Postgre

View File

@ -16,7 +16,7 @@ namespace CodeIgniter\Database\SQLite3;
use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\RawSql;
use InvalidArgumentException;
use CodeIgniter\Exceptions\InvalidArgumentException;
/**
* Builder for SQLite3

View File

@ -15,6 +15,8 @@ namespace CodeIgniter\Database\SQLite3;
use CodeIgniter\Database\BaseConnection;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\TableName;
use CodeIgniter\Exceptions\InvalidArgumentException;
use Exception;
use SQLite3;
use SQLite3Result;
@ -55,6 +57,15 @@ class Connection extends BaseConnection
*/
protected $busyTimeout;
/**
* The setting of the "synchronous" flag
*
* @var int<0, 3>|null flag
*
* @see https://www.sqlite.org/pragma.html#pragma_synchronous
*/
protected ?int $synchronous = null;
/**
* @return void
*/
@ -69,6 +80,13 @@ class Connection extends BaseConnection
if (is_int($this->busyTimeout)) {
$this->connID->busyTimeout($this->busyTimeout);
}
if (is_int($this->synchronous)) {
if (! in_array($this->synchronous, [0, 1, 2, 3], true)) {
throw new InvalidArgumentException('Invalid synchronous value.');
}
$this->connID->exec('PRAGMA synchronous = ' . $this->synchronous);
}
}
/**
@ -209,19 +227,31 @@ class Connection extends BaseConnection
/**
* Generates a platform-specific query string so that the column names can be fetched.
*
* @param string|TableName $table
*/
protected function _listColumns(string $table = ''): string
protected function _listColumns($table = ''): string
{
return 'PRAGMA TABLE_INFO(' . $this->protectIdentifiers($table, true, null, false) . ')';
if ($table instanceof TableName) {
$tableName = $this->escapeIdentifier($table);
} else {
$tableName = $this->protectIdentifiers($table, true, null, false);
}
return 'PRAGMA TABLE_INFO(' . $tableName . ')';
}
/**
* @param string|TableName $tableName
*
* @return false|list<string>
*
* @throws DatabaseException
*/
public function getFieldNames(string $table)
public function getFieldNames($tableName)
{
$table = ($tableName instanceof TableName) ? $tableName->getTableName() : $tableName;
// Is there a cached result?
if (isset($this->dataCache['field_names'][$table])) {
return $this->dataCache['field_names'][$table];
@ -231,7 +261,7 @@ class Connection extends BaseConnection
$this->initialize();
}
$sql = $this->_listColumns($table);
$sql = $this->_listColumns($tableName);
$query = $this->query($sql);
$this->dataCache['field_names'][$table] = [];

View File

@ -13,9 +13,9 @@ declare(strict_types=1);
namespace CodeIgniter\Database\SQLite3;
use BadMethodCallException;
use CodeIgniter\Database\BasePreparedQuery;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Exceptions\BadMethodCallException;
use Exception;
use SQLite3;
use SQLite3Result;

View File

@ -14,10 +14,10 @@ declare(strict_types=1);
namespace CodeIgniter\Database;
use CodeIgniter\CLI\CLI;
use CodeIgniter\Exceptions\InvalidArgumentException;
use Config\Database;
use Faker\Factory;
use Faker\Generator;
use InvalidArgumentException;
/**
* Class Seeder

View File

@ -0,0 +1,135 @@
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Database;
/**
* Represents a table name in SQL.
*
* @interal
*
* @see \CodeIgniter\Database\TableNameTest
*/
class TableName
{
/**
* @param string $actualTable Actual table name
* @param string $logicalTable Logical table name (w/o DB prefix)
* @param string $schema Schema name
* @param string $database Database name
* @param string $alias Alias name
*/
protected function __construct(
private readonly string $actualTable,
private readonly string $logicalTable = '',
private readonly string $schema = '',
private readonly string $database = '',
private readonly string $alias = '',
) {
}
/**
* Creates a new instance.
*
* @param string $table Table name (w/o DB prefix)
* @param string $alias Alias name
*/
public static function create(string $dbPrefix, string $table, string $alias = ''): self
{
return new self(
$dbPrefix . $table,
$table,
'',
'',
$alias,
);
}
/**
* Creates a new instance from an actual table name.
*
* @param string $actualTable Actual table name with DB prefix
* @param string $alias Alias name
*/
public static function fromActualName(string $dbPrefix, string $actualTable, string $alias = ''): self
{
$prefix = $dbPrefix;
$logicalTable = '';
if (str_starts_with($actualTable, $prefix)) {
$logicalTable = substr($actualTable, strlen($prefix));
}
return new self(
$actualTable,
$logicalTable,
'',
$alias,
);
}
/**
* Creates a new instance from full name.
*
* @param string $table Table name (w/o DB prefix)
* @param string $schema Schema name
* @param string $database Database name
* @param string $alias Alias name
*/
public static function fromFullName(
string $dbPrefix,
string $table,
string $schema = '',
string $database = '',
string $alias = '',
): self {
return new self(
$dbPrefix . $table,
$table,
$schema,
$database,
$alias,
);
}
/**
* Returns the single segment table name w/o DB prefix.
*/
public function getTableName(): string
{
return $this->logicalTable;
}
/**
* Returns the actual single segment table name w/z DB prefix.
*/
public function getActualTableName(): string
{
return $this->actualTable;
}
public function getAlias(): string
{
return $this->alias;
}
public function getSchema(): string
{
return $this->schema;
}
public function getDatabase(): string
{
return $this->database;
}
}

View File

@ -75,8 +75,10 @@ final class ExceptionHandler extends BaseExceptionHandler implements ExceptionHa
);
}
// Handles non-HTML requests.
if (! str_contains($request->getHeaderLine('accept'), 'text/html')) {
$data = (ENVIRONMENT === 'development' || ENVIRONMENT === 'testing')
// If display_errors is enabled, shows the error details.
$data = $this->isDisplayErrorsEnabled()
? $this->collectVars($exception, $statusCode)
: '';
@ -134,13 +136,8 @@ final class ExceptionHandler extends BaseExceptionHandler implements ExceptionHa
// Production environments should have a custom exception file.
$view = 'production.php';
if (
in_array(
strtolower(ini_get('display_errors')),
['1', 'true', 'on', 'yes'],
true,
)
) {
if ($this->isDisplayErrorsEnabled()) {
// If display_errors is enabled, shows the error details.
$view = 'error_exception.php';
}
@ -158,4 +155,13 @@ final class ExceptionHandler extends BaseExceptionHandler implements ExceptionHa
return $view;
}
private function isDisplayErrorsEnabled(): bool
{
return in_array(
strtolower(ini_get('display_errors')),
['1', 'true', 'on', 'yes'],
true,
);
}
}

View File

@ -208,6 +208,14 @@ class Exceptions
public function errorHandler(int $severity, string $message, ?string $file = null, ?int $line = null)
{
if ($this->isDeprecationError($severity)) {
if ($this->isSessionSidDeprecationError($message, $file, $line)) {
return true;
}
if ($this->isImplicitNullableDeprecationError($message, $file, $line)) {
return true;
}
if (! $this->config->logDeprecations || (bool) env('CODEIGNITER_SCREAM_DEPRECATIONS')) {
throw new ErrorException($message, 0, $severity, $file, $line);
}
@ -222,6 +230,64 @@ class Exceptions
return false; // return false to propagate the error to PHP standard error handler
}
/**
* Handles session.sid_length and session.sid_bits_per_character deprecations
* in PHP 8.4.
*/
private function isSessionSidDeprecationError(string $message, ?string $file = null, ?int $line = null): bool
{
if (
PHP_VERSION_ID >= 80400
&& str_contains($message, 'session.sid_')
) {
log_message(
LogLevel::WARNING,
'[DEPRECATED] {message} in {errFile} on line {errLine}.',
[
'message' => $message,
'errFile' => clean_path($file ?? ''),
'errLine' => $line ?? 0,
],
);
return true;
}
return false;
}
/**
* Workaround to implicit nullable deprecation errors in PHP 8.4.
*
* "Implicitly marking parameter $xxx as nullable is deprecated,
* the explicit nullable type must be used instead"
*
* @TODO remove this before v4.6.0 release
*/
private function isImplicitNullableDeprecationError(string $message, ?string $file = null, ?int $line = null): bool
{
if (
PHP_VERSION_ID >= 80400
&& str_contains($message, 'the explicit nullable type must be used instead')
// Only Kint and Faker, which cause this error, are logged.
&& (str_starts_with($message, 'Kint\\') || str_starts_with($message, 'Faker\\'))
) {
log_message(
LogLevel::WARNING,
'[DEPRECATED] {message} in {errFile} on line {errLine}.',
[
'message' => $message,
'errFile' => clean_path($file ?? ''),
'errLine' => $line ?? 0,
],
);
return true;
}
return false;
}
/**
* Checks to see if any errors have happened during shutdown that
* need to be caught and handle them.

View File

@ -13,7 +13,7 @@ declare(strict_types=1);
namespace CodeIgniter\Debug;
use RuntimeException;
use CodeIgniter\Exceptions\RuntimeException;
/**
* Class Timer

View File

@ -14,13 +14,12 @@ declare(strict_types=1);
namespace CodeIgniter\Encryption\Exceptions;
use CodeIgniter\Exceptions\DebugTraceableTrait;
use CodeIgniter\Exceptions\ExceptionInterface;
use RuntimeException;
use CodeIgniter\Exceptions\RuntimeException;
/**
* Encryption exception
*/
class EncryptionException extends RuntimeException implements ExceptionInterface
class EncryptionException extends RuntimeException
{
use DebugTraceableTrait;

View File

@ -40,7 +40,7 @@ class DatetimeCast extends BaseCast
}
if (is_numeric($value)) {
return Time::createFromTimestamp((int) $value);
return Time::createFromTimestamp((int) $value, date_default_timezone_get());
}
if (is_string($value)) {

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Exceptions;
/**
* Exception thrown if a function is called in the wrong way, or the function
* does not exist.
*/
class BadFunctionCallException extends \BadFunctionCallException implements ExceptionInterface
{
}

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Exceptions;
/**
* Exception thrown if a method is called in the wrong way, or the method
* does not exist.
*/
class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface
{
}

View File

@ -14,9 +14,10 @@ declare(strict_types=1);
namespace CodeIgniter\Exceptions;
/**
* Exception for automatic logging.
* Exception thrown if the value of the Config class is invalid or the type is
* incorrect.
*/
class ConfigException extends CriticalError implements HasExitCodeInterface
class ConfigException extends RuntimeException implements HasExitCodeInterface
{
use DebugTraceableTrait;

View File

@ -13,11 +13,9 @@ declare(strict_types=1);
namespace CodeIgniter\Exceptions;
use Error;
/**
* Error: Critical conditions, like component unavailable, etc.
*/
class CriticalError extends Error
class CriticalError extends RuntimeException
{
}

View File

@ -13,12 +13,10 @@ declare(strict_types=1);
namespace CodeIgniter\Exceptions;
use RuntimeException;
/**
* Class DownloadException
*/
class DownloadException extends RuntimeException implements ExceptionInterface
class DownloadException extends RuntimeException
{
use DebugTraceableTrait;

View File

@ -13,15 +13,13 @@ declare(strict_types=1);
namespace CodeIgniter\Exceptions;
use RuntimeException;
/**
* Class FrameworkException
*
* A collection of exceptions thrown by the framework
* that can only be determined at run time.
*/
class FrameworkException extends RuntimeException implements ExceptionInterface
class FrameworkException extends RuntimeException
{
use DebugTraceableTrait;

View File

@ -16,6 +16,6 @@ namespace CodeIgniter\Exceptions;
/**
* Interface for Exceptions that has exception code as HTTP status code.
*/
interface HTTPExceptionInterface
interface HTTPExceptionInterface extends ExceptionInterface
{
}

View File

@ -16,7 +16,7 @@ namespace CodeIgniter\Exceptions;
/**
* Interface for Exceptions that has exception code as exit code.
*/
interface HasExitCodeInterface
interface HasExitCodeInterface extends ExceptionInterface
{
/**
* Returns exit status code.

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Exceptions;
/**
* Exception thrown if an argument is not of the expected type.
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Exceptions;
/**
* Exception that represents error in the program logic.
*/
class LogicException extends \LogicException implements ExceptionInterface
{
}

View File

@ -13,9 +13,7 @@ declare(strict_types=1);
namespace CodeIgniter\Exceptions;
use OutOfBoundsException;
class PageNotFoundException extends OutOfBoundsException implements ExceptionInterface, HTTPExceptionInterface
class PageNotFoundException extends RuntimeException implements HTTPExceptionInterface
{
use DebugTraceableTrait;

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Exceptions;
/**
* Exception thrown if an error which can only be found on runtime occurs.
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}

View File

@ -14,9 +14,9 @@ declare(strict_types=1);
namespace CodeIgniter\Exceptions;
/**
* Exception for automatic logging.
* Exception thrown when there is an error with the test code.
*/
class TestException extends CriticalError
class TestException extends LogicException
{
use DebugTraceableTrait;

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace CodeIgniter\Files\Exceptions;
/**
* Provides a domain-level interface for broad capture
* of all Files-related exceptions.
*
* catch (\CodeIgniter\Files\Exceptions\ExceptionInterface) { ... }
*/
interface ExceptionInterface extends \CodeIgniter\Exceptions\ExceptionInterface
{
}

View File

@ -14,8 +14,7 @@ declare(strict_types=1);
namespace CodeIgniter\Files\Exceptions;
use CodeIgniter\Exceptions\DebugTraceableTrait;
use CodeIgniter\Exceptions\ExceptionInterface;
use RuntimeException;
use CodeIgniter\Exceptions\RuntimeException;
class FileException extends RuntimeException implements ExceptionInterface
{

View File

@ -14,8 +14,7 @@ declare(strict_types=1);
namespace CodeIgniter\Files\Exceptions;
use CodeIgniter\Exceptions\DebugTraceableTrait;
use CodeIgniter\Exceptions\ExceptionInterface;
use RuntimeException;
use CodeIgniter\Exceptions\RuntimeException;
class FileNotFoundException extends RuntimeException implements ExceptionInterface
{

View File

@ -70,16 +70,38 @@ class File extends SplFileInfo
return $this->size ?? ($this->size = parent::getSize());
}
/**
* Retrieve the file size by unit, calculated in IEC standards with 1024 as base value.
*
* @phpstan-param positive-int $precision
*/
public function getSizeByBinaryUnit(FileSizeUnit $unit = FileSizeUnit::B, int $precision = 3): int|string
{
return $this->getSizeByUnitInternal(1024, $unit, $precision);
}
/**
* Retrieve the file size by unit, calculated in metric standards with 1000 as base value.
*
* @phpstan-param positive-int $precision
*/
public function getSizeByMetricUnit(FileSizeUnit $unit = FileSizeUnit::B, int $precision = 3): int|string
{
return $this->getSizeByUnitInternal(1000, $unit, $precision);
}
/**
* Retrieve the file size by unit.
*
* @deprecated 4.6.0 Use getSizeByBinaryUnit() or getSizeByMetricUnit() instead
*
* @return false|int|string
*/
public function getSizeByUnit(string $unit = 'b')
{
return match (strtolower($unit)) {
'kb' => number_format($this->getSize() / 1024, 3),
'mb' => number_format(($this->getSize() / 1024) / 1024, 3),
'kb' => $this->getSizeByBinaryUnit(FileSizeUnit::KB),
'mb' => $this->getSizeByBinaryUnit(FileSizeUnit::MB),
default => $this->getSize(),
};
}
@ -189,4 +211,17 @@ class File extends SplFileInfo
return $destination;
}
private function getSizeByUnitInternal(int $fileSizeBase, FileSizeUnit $unit, int $precision): int|string
{
$exponent = $unit->value;
$divider = $fileSizeBase ** $exponent;
$size = $this->getSize() / $divider;
if ($unit !== FileSizeUnit::B) {
$size = number_format($size, $precision);
}
return $size;
}
}

View File

@ -13,11 +13,11 @@ declare(strict_types=1);
namespace CodeIgniter\Files;
use CodeIgniter\Exceptions\InvalidArgumentException;
use CodeIgniter\Files\Exceptions\FileException;
use CodeIgniter\Files\Exceptions\FileNotFoundException;
use Countable;
use Generator;
use InvalidArgumentException;
use IteratorAggregate;
/**
@ -340,6 +340,44 @@ class FileCollection implements Countable, IteratorAggregate
return $this->removeFiles(array_diff($files, self::matchFiles($files, $pattern)));
}
/**
* Keeps only the files from the list that match multiple patterns
* (within the optional scope).
*
* @param list<string> $patterns Array of regex or pseudo-regex strings
* @param string|null $scope A directory to limit the scope
*
* @return $this
*/
public function retainMultiplePatterns(array $patterns, ?string $scope = null)
{
if ($patterns === []) {
return $this;
}
if (count($patterns) === 1 && $patterns[0] === '') {
return $this;
}
// Start with all files or those in scope
$files = $scope === null ? $this->files : self::filterFiles($this->files, $scope);
// Add files to retain to array
$filesToRetain = [];
foreach ($patterns as $pattern) {
if ($pattern === '') {
continue;
}
// Matches the pattern within the scoped files
$filesToRetain = array_merge($filesToRetain, self::matchFiles($files, $pattern));
}
// Remove the inverse of files to retain
return $this->removeFiles(array_diff($files, $filesToRetain));
}
// --------------------------------------------------------------------
// Interface Methods
// --------------------------------------------------------------------

Some files were not shown because too many files have changed in this diff Show More