# 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，说明您遇到的问题，我们会尽快帮助您解决。
