# 2.0 升級指南

2.0 版本新增了不少強大的功能，如果您已經投入了業務使用的項目且是基於官方提供的 Skeleton 項目創建的 1.1 版本的應用項目，那麼可以根據下面的內容點來調整您的 Skeleton 項目，如果您是一個新的項目，按照文檔通過 `composer create-project hyperf/hyperf-skeleton` 命令創建新的項目即可使用新的 2.0 版本的 skeleton 代碼，如果您當前使用的版本是低於 1.1 的版本，那麼需要您先升級到 1.1 後再根據此升級指南升級到 2.0 版本。

## 升級 Swoole 到 4.5+

2.0 版本將最低的 Swoole 版本要求從 4.4+ 提升到了 4.5+，這兩個版本之間有一些使用上的細節差異，Hyperf 在較早的版本便已適配了這裏版本差異，您可無需理會這裏的差異細節，提升 Swoole 版本到 4.5+ 主要是減少歷史包袱對 Hyperf 造成的長期影響。您可通過執行 `php --ri swoole` 來查看當前環境中的 Swoole 版本，您可根據 [Swoole 文檔](https://wiki.swoole.com/#/environment) 的指引來完成對 Swoole 的升級。

## 入口文件添加 ClassLoader 初始化

2.0 改變了 AOP 的底層邏輯，所以需要您在框架入口文件 `bin/hyperf.php` 中添加一行初始化的代碼，您需要在入口匿名函數內的第一行添加代碼 `Hyperf\Di\ClassLoader::init();`，如下所示：

```php
<?php

ini_set('display_errors', 'on');
ini_set('display_startup_errors', 'on');

error_reporting(E_ALL);
date_default_timezone_set('Asia/Shanghai');

! defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));
! defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', SWOOLE_HOOK_ALL);

require BASE_PATH . '/vendor/autoload.php';

// Self-called anonymous function that creates its own scope and keep the global namespace clean.
(function () {
    Hyperf\Di\ClassLoader::init();

    /** @var \Psr\Container\ContainerInterface $container */
    $container = require BASE_PATH . '/config/container.php';

    $application = $container->get(\Hyperf\Contract\ApplicationInterface::class);
    $application->run();
})();

```

與此同時，PHPUnit 的入口文件也許做同樣的處理，文件位於 `tests/bootstrap.php`，如下所示：

```php
<?php

declare(strict_types=1);

error_reporting(E_ALL);
date_default_timezone_set('Asia/Shanghai');

! defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));
! defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', SWOOLE_HOOK_ALL);

Swoole\Runtime::enableCoroutine(true);

require BASE_PATH . '/vendor/autoload.php';

Hyperf\Di\ClassLoader::init();

$container = require BASE_PATH . '/config/container.php';

$container->get(Hyperf\Contract\ApplicationInterface::class);

```

## 調整 `composer.json`

因為 2.0 版本 AOP 底層邏輯的調整，故移除了 `init-proxy.sh` 腳本，所以需要您從 `composer.json` 中去掉 `scripts.post-autoload-dump` 內的 `"init-proxy.sh"` 執行語句，並修改 `post-autoload-dump` 內的命令為 `rm -rf runtime/container` 語句。

```json
{
    "scripts": {
        "post-root-package-install": [
            "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
        ],
        "post-autoload-dump": [
            "rm -rf runtime/container"
        ],
        "analyse": "phpstan analyse --memory-limit 300M -l 0 -c phpstan.neon ./app ./config",
        "cs-fix": "php-cs-fixer fix $1",
        "start": "php ./bin/hyperf.php start",
        "test": "co-phpunit -c phpunit.xml --colors=always"
    }
}

```

## 調整 composer.json 的依賴版本

由於要升級到 2.0 版本的組件，而原來 skeleton 項目默認情況下是依賴 1.1.x 版本的組件的，所以我們需要對依賴的約束條件進行一些調整，將原來所有 Hyperf 組件的依賴 `~1.1.0` 修改為 `~2.0.0`，同時您還需要將 `phpstan/phpstan` 版本依賴修改為 `^0.12`，修改完後需運行 `composer update` 來將依賴項升級到 2.0 版本。

## 調整 Dockerfile

在 Docker 鏡像的打包過程中，主動執行 `php bin/hyperf.php` 命令會幫助提前創建所有需要生成的代理類和註解掃描緩存，這樣在生產環境運行啟動時便無需再次的掃描，這樣可以極大的優化生產環境啟動的時間和內存使用量。以下示例不包含未修改的 Dockerfile 代碼。

```dockerfile
ENV TIMEZONE=${timezone:-"Asia/Shanghai"} \
    APP_ENV=prod \
    SCAN_CACHEABLE=(true)

COPY . /opt/www
RUN composer install --no-dev -o && php bin/hyperf.php

EXPOSE 9501

ENTRYPOINT ["php", "/opt/www/bin/hyperf.php", "start"]
```

非 `Docker` 部署的用户，需要注意的是，在重新啟動服務之前，最好先執行一次 `php bin/hyperf.php` 後再重新啟動服務，以減少重新啟動時的耗時。

## 調整 config/config.php 配置文件

您需要在 `config/config.php` 配置中添加 `app_env` 和 `scan_cacheable` 兩個配置項，下面的代碼示例不包含其它無關的配置內容，如下所示：

```php
<?php

return [
    // 生產環境使用 prod 值
    'app_env' => env('APP_ENV', 'dev'),
    // 是否使用註解掃描緩存
    'scan_cacheable' => env('SCAN_CACHEABLE', false),
];
```

`scan_cacheable` 配置用於控制應用啟動時是否使用註解掃描緩存，以上 `Dockerfile` 和 `config/config.php` 中都有相關的修改。當這個配置的值為 `true` 時，項目啟動時則會認為所有類都已經完成了掃描並正確生成了對應的緩存和代理，則會跳過掃描階段以便優化啟動時間和減少內存開銷。

## 修改 `config/autoload/logger.php`

因為 2.0 版本提高了對 Monolog 依賴的版本，在高版本的 Monolog 中，默認的日誌格式發生了變化，如果對於日誌的格式有要求，比如需要根據日誌格式與日誌系統對接等，可修改 `config/autoload/logger.php` 配置文件的 `dateFormat` 配置項，以保持與之前版本的一致。

```php
<?php

declare(strict_types=1);

return [
    'default' => [
        'handler' => [
            'class' => Monolog\Handler\StreamHandler::class,
            'constructor' => [
                'stream' => BASE_PATH . '/runtime/logs/hyperf.log',
                'level' => Monolog\Logger::DEBUG,
            ],
        ],
        'formatter' => [
            'class' => Monolog\Formatter\LineFormatter::class,
            'constructor' => [
                'format' => null,
                'dateFormat' => 'Y-m-d H:i:s',
                'allowInlineLineBreaks' => true,
            ],
        ],
        'processors' => [
        ],
    ],
];

```

## 修改 `config/autoload/exceptions.php`

2.0 版本對 路由找不到(404)、請求方法不匹配(405) 等 HTTP 路由異常行為的處理邏輯進行了調整，統一改為拋出 `Hyperf\HttpMessage\Exception\HttpException` 的子異常類，然後通過 ExceptionHandler 來統一管理這些異常並做對應的響應處理，這樣用户也可通過拋出對應的異常以獲得一致的響應返回體驗，但鑑於 ExceptionHandler 是一個由用户管理的機制，而在 1.1 版本下默認的 Skeleton 配置了一個 `App\Exception\Handler\AppExceptionHandler` 類來對異常進行託底處理，並統一以 500 狀態碼返回給客户端，故您需要將 2.0 版本提供的用來處理 HttpException 的 `Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler` 配置到 `config/autoload/exceptions.php` 配置文件中，並確保位於 `App\Exception\Handler\AppExceptionHandler` 配置的前面，以下配置示例省略了無關的配置，如下所示：

```php
<?php
return [
    'handler' => [
        'http' => [
            Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler::class,
        ],
    ],
];
```

當您完成了 ExceptionHandler 的配置後，可以通過直接訪問一個不存在的路由，如果響應的狀態碼為 `404` 即可理解為完成了此項配置的修改。

## 修改 gRPC 客户端

為了避免和 gRPC 實際業務的命名衝突，2.0 版本對 gRPC 客户端的基類函數命名進行了調整。

* `simpleRequest` -> `_simpleRequest`
* `clientStreamRequest` -> `_clientStreamRequest`
* `getGrpcClient` -> `_getGrpcClient`

除此之外，一些不應該暴露的方法變成了私有方法，如您的 gRPC 客户端涉及到以上方法的調用，請進行命名上的調整。

## 移除 DI 懶加載監聽器

如果您的項目中有使用到 DI 組件的懶加載功能，此前您需要註冊一個 `Hyperf\Di\Listener\LazyLoaderBootApplicationListener` 監聽器，而在 2.0 版本，這一監聽器被移除了，您可以直接使用該功能，故如果您此前有使用到該功能，您需要在 `config/autoload/listeners.php` 中移除該監聽器的註冊；

## 綁定 NormalizerInterface 關係

當您使用了 JSONRPC 功能並使用了 `symfony/serializer` 庫來提供序列化功能時，由於 2.0 版本不再自動映射 `Hyperf\Contract\NormalizerInterface` 的實現類，所以您需要手動添加該映射關係，如下：

```php
use Hyperf\Utils\Serializer\SerializerFactory;
use Hyperf\Utils\Serializer\Serializer;

return [
    Hyperf\Contract\NormalizerInterface::class => new SerializerFactory(Serializer::class),
];
```

## 調整 Hyperf\Contract\ProcessInterface 的實現類

`Hyperf\Contract\ProcessInterface` 中的 `isEnable` 方法增加了一個 `$server` 參數，即 `isEnable($server): bool`，所有 ProcessInterface 的實現類都需要您對該方法進行一些調整。

## 檢查 config/autoload/aspects.php 文件

如果您此前對 Skeleton 進行過一些精簡操作，需要檢查 `config/autoload/aspects.php` 文件是否存在，如不存在需要增加一個文件並返回一個空數組.

> 這個問題在 2.0.1 會被修正，後續可無需做此檢查。

## 檢查自定義註解的收集器

如果您使用了自定義註解且使用了自定義收集器 `Collector` 來收集註解元數據，則需要將對應的 `Collector` 配置到 `annotations.scan.collectors` 中，因為開發模式下，會根據文件的修改時間判斷文件是否修改，然後決定是否重新收集對應的註解元數據。所以，當沒有配置 `annotations.scan.collectors` 時，就會導致註解只在首次啟動 `server` 時可以生效。

如在應用層，該配置位於 `config/autoload/annotations.php` 文件，如下：

```php
<?php

return [
    'scan' => [
        'collectors' => [
            CustomCollector::class,
        ],
    ],
];
```

如在組件，該配置則由 ConfigProvider 提供，如下：

```php
<?php

return [
    'annotations' => [
        'scan' => [
            'collectors' => [
                CustomCollector::class,
            ],
        ],
    ]
];
```

## 啟動服務並測試訪問接口

使用 Swoole 4.5 版本和 view 組件如果出現接口 404 的問題，可以嘗試刪除 `config/autoload/server.php` 文件中的 `static_handler_locations` 配置項。

此配置下的路徑都會被認為是靜態文件路由，所以如果配置了`/`，就會導致所有接口都會被認為是文件路徑，導致接口 404。

## 完成升級

至此，2.0 版本升級即已完成，但由於 Hyperf 的各個底層文件都是可以通過 DI 來實現重寫的，如您重寫了某些本次升級調整到了的框架內部文件，您仍需再根據您的實際情況進行一定的調整。   

如您在升級上或升級後遇到任何的問題，請前往 [Github Issue](https://github.com/hyperf/hyperf/issues) 提交您的 issue，説明您遇到的問題，我們會盡快幫助您解決。
