# 从 4.5.8 升级到 4.6.0

请根据你的安装方式参考对应的升级说明。

- Composer 安装：App Starter 方式的升级说明

- Composer 安装：将 CodeIgniter4 添加到现有项目的升级说明

- 手动安装：升级说明

## 破坏性变更

### 异常类变更

部分类抛出的异常类已经变更。某些异常类的父类也已变更。 详情请参见 变更记录。

如果你的代码会捕获这些异常，请修改对应的异常类。

### Time::createFromTimestamp() 时区变更

现在，如果你没有显式传入时区， Time::createFromTimestamp() 会返回一个使用 UTC 的 Time 实例。在 v4.4.6 到 v4.6.0 之前的版本中，返回的是使用当前默认时区的 Time 实例。

此行为变更是为了与 PHP 8.4 的变更保持一致。PHP 8.4 新增了 `DateTimeInterface::createFromTimestamp()` 方法。

如果你想继续使用默认时区，则需要将时区作为第二个参数传入:

    use CodeIgniter\I18n\Time;

    $time = Time::createFromTimestamp(1501821586, date_default_timezone_get());

### Time 保留微秒

在之前的版本中，Time 在某些情况下会丢失微秒。 现在这些 bug 已被修复。

由于这些修复，`Time` 比较的结果可能会不同：

    use CodeIgniter\I18n\Time;

    $time1 = new Time('2024-01-01 12:00:00.654321');
    $time2 = new Time('2024-01-01 12:00:00');

    $time1->equals($time2);
    // Before: true
    //  After: false

在这种情况下，你需要移除微秒：

    use CodeIgniter\I18n\Time;

    $time1 = new Time('2024-01-01 12:00:00.654321');
    $time2 = new Time('2024-01-01 12:00:00');

    // Removes the microseconds.
    $time1 = Time::createFromFormat(
        'Y-m-d H:i:s',
        $time1->format('Y-m-d H:i:s'),
        $time1->getTimezone(),
    );

    $time1->equals($time2);
    // Before: true
    //  After: true

以下情况现在也会保留微秒：

    use CodeIgniter\I18n\Time;

    $time = Time::createFromFormat('Y-m-d H:i:s.u', '2024-07-09 09:13:34.654321');
    echo $time->format('Y-m-d H:i:s.u');
    // Before: 2024-07-09 09:13:34.000000
    //  After: 2024-07-09 09:13:34.654321

    use CodeIgniter\I18n\Time;

    $time = new Time('1 hour ago');
    echo $time->format('Y-m-d H:i:s.u');
    // Before: 2024-07-26 21:05:57.000000
    //  After: 2024-07-26 21:05:57.857235

请注意，表示当前时间的 `Time` 在此前版本中已保留微秒。

    use CodeIgniter\I18n\Time;

    $time = Time::now();
    echo $time->format('Y-m-d H:i:s.u');
    // Before: 2024-07-26 21:39:32.249072
    //  After: 2024-07-26 21:39:32.249072

另外，返回 `int` 的方法仍然会丢失微秒。

    use CodeIgniter\I18n\Time;

    $time1 = new Time('2024-01-01 12:00:00');
    echo $time1->getTimestamp(); // 1704110400

    $time2 = new Time('2024-01-01 12:00:00.654321');
    echo $time2->getTimestamp(); // 1704110400

### Time::setTimestamp() 行为修复

在之前的版本中，如果对一个使用非默认时区的 Time 实例调用 `Time::setTimestamp()`， 可能会返回日期/时间错误的 Time 实例。

该 bug 已修复，现在它的行为与 `DateTimeImmutable` 相同：

    use CodeIgniter\I18n\Time;

    // The Application Timezone is "UTC".

    // Set $time1 timezone to "America/Chicago".
    $time1 = Time::parse('2024-08-20', 'America/Chicago');

    // The timestamp is "2024-08-20 00:00" in "UTC".
    $stamp = strtotime('2024-08-20'); // 1724112000

    // But $time2 timezone is "America/Chicago".
    $time2 = $time1->setTimestamp($stamp);

    echo $time2->format('Y-m-d H:i:s P');
    // Before: 2024-08-20 00:00:00 -05:00
    //  After: 2024-08-19 19:00:00 -05:00

请注意，如果你使用的是默认时区，则行为没有变化：

    use CodeIgniter\I18n\Time;

    // The Application Timezone is "America/Chicago".

    // $time1 timezone is "America/Chicago".
    $time1 = Time::parse('2024-08-20');

    // The timestamp is "2024-08-20 00:00" in "America/Chicago".
    $stamp = strtotime('2024-08-20'); // 1724130000

    // $time2 timezone is also "America/Chicago".
    $time2 = $time1->setTimestamp($stamp);

    echo $time2->format('Y-m-d H:i:s P');
    // Before: 2024-08-20 00:00:00 -05:00
    //  After: 2024-08-20 00:00:00 -05:00

### 注册器的脏数据修复

为了防止 注册器 的自动发现执行两次，现在当 Registrar 类被加载或实例化时， 如果它实例化了一个 Config 类（该类继承自 `CodeIgniter\Config\BaseConfig`）， 就会抛出 `ConfigException`。

这是因为如果注册器的自动发现执行两次，可能会向 Config 类的属性中添加重复的值。

所有 Registrar 类（所有命名空间中的 Config/Registrar.php）都必须修改， 确保它们在被加载或实例化时不会实例化任何 Config 类。

如果你使用的包/模块提供了这样的 Registrar 类，则这些包/模块中的 Registrar 类也需要修复。

下面是一个不再有效的代码示例：

    <?php

    namespace CodeIgniter\Shield\Config;

    use Config\App;

    class Registrar
    {
        public function __construct()
        {
            $config = new App(); // Bad. When this class is instantiated, Config\App will be instantiated.

            // Does something.
        }

        public static function Pager(): array
        {
            return [
                'templates' => [
                    'module_pager' => 'MyModule\Views\Pager',
                ],
            ];
        }

        public static function hack(): void
        {
            $config = config('Cache');

            // Does something.
        }
    }

    Registrar::hack(); // Bad. When this class is loaded, Config\Cache will be instantiated.

### Session ID（SID）变更

现在，Session 类 会强制使用 PHP 默认的 32 个字符 SID， 并且每个字符有 4 位熵。此变更是为了与 PHP 9 的行为保持一致。

换句话说，现在始终使用以下设置：

    session.sid_bits_per_character = 4
    session.sid_length = 32

在之前的版本中，会遵循 PHP ini 设置。因此，此变更可能会改变你的 SID 长度。

如果你不能接受此变更，请自定义 Session 类。

### 接口变更

部分接口已发生变更。实现这些接口的类应更新其 API 以反映这些变更。 详情请参见 变更记录。

### 方法签名变更

部分方法签名已发生变更。扩展这些类的类应更新其 API 以反映这些变更。 详情请参见 变更记录。

### 移除已弃用项

部分已弃用项已被移除。如果你仍在使用这些项，或者正在扩展这些类，请升级你的代码。 详情请参见 变更记录。

## 破坏性增强

### 过滤器变更

`Filters` 类已变更，现在允许同一个过滤器在 before 或 after 中以不同参数执行多次。

如果你扩展了 `Filters`，则需要对其进行修改，以符合以下变更：

- 数组属性 `$filters` 和 `$filtersClasses` 的结构已变更。

- 属性 `$arguments` 和 `$argumentsClass` 不再使用。

- `Filters` 已调整为同一个过滤器类不会被实例化多次。如果某个过滤器类同时用于 before 和 after，则会使用同一个实例。

## 项目文件

项目空间 （根目录、app、public、writable）中的部分文件已更新。 由于这些文件位于 system 范围之外，框架不会在没有你介入的情况下自动修改它们。

目前有一些第三方 CodeIgniter 模块可用于协助合并项目空间中的变更： 在 Packagist 上浏览。

### 内容变更

以下文件有较大改动（包括弃用项或界面调整），建议将更新后的版本与你的应用程序进行合并：

#### 配置

- app/Config/Feature.php

    - `Config\Feature::$autoRoutesImproved` 已变更为 `true`。

    - 新增了 `Config\Feature::$strictLocaleNegotiation`。

- app/Config/Routing.php

    - `Config\Routing::$translateUriToCamelCase` 已变更为 `true`。

- app/Config/Kint.php

    - 已移除 `Config\Kint::$richSort`。v6 中的 Kint 不再使用 `AbstractRenderer::SORT_FULL`。如果你的代码中保留此属性，会因为常量未定义而导致运行时错误。

### 所有变更

以下列出了 项目空间 中所有已变更的文件； 其中很多只是注释或格式调整，不会影响运行时行为：

- app/Config/Cache.php

- app/Config/Constants.php

- app/Config/Database.php

- app/Config/Feature.php

- app/Config/Format.php

- app/Config/Kint.php

- app/Config/Routing.php

- app/Config/Security.php

- app/Views/errors/html/debug.css

- app/Views/errors/html/error_400.php

- preload.php

- public/index.php

- spark