hyperf / hyperf

🚀 A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.

Home Page:https://www.hyperf.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[BUG] composer update更新symfony相关包后,访问任意路由出现Not Found,#[Inject]的部分类在启动服务时会出现must not be accessed before initialization

lujihong opened this issue · comments

commented

运行环境信息如下:

Linux 1dba10174efb 6.6.16-linuxkit #1 SMP PREEMPT_DYNAMIC Fri Feb 16 11:55:08 UTC 2024 x86_64 Linux
PHP 8.3.4 (cli) (built: Mar 16 2024 00:49:34) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.4, Copyright (c) Zend Technologies
    with Zend OPcache v8.3.4, Copyright (c), by Zend Technologies
swow/swow                            v1.4.1             Coroutine-based multi-platform support engine with a focus on concurrent I/O
hyperf/amqp                          v3.1.16            A amqplib for hyperf.
hyperf/async-queue                   v3.1.15            A async queue component for hyperf.
hyperf/cache                         v3.1.15            A cache component for hyperf.
hyperf/code-parser                   v3.1.15            A code parser component for Hyperf.
hyperf/codec                         v3.1.15            A codec component for Hyperf.
hyperf/collection                    v3.1.15            Hyperf Collection package which come from illuminate/collections
hyperf/command                       v3.1.15            Command for hyperf
hyperf/conditionable                 v3.1.15            Hyperf Macroable package which come from illuminate/conditionable
hyperf/config                        v3.1.15            An independent component that provides configuration container.
hyperf/constants                     v3.1.16            A constants component for hyperf.
hyperf/context                       v3.1.15            A coroutine/application context library.
hyperf/contract                      v3.1.15            The contracts of Hyperf.
hyperf/coordinator                   v3.1.15            Hyperf Coordinator
hyperf/coroutine                     v3.1.15            Hyperf Coroutine
hyperf/crontab                       v3.1.16            A crontab component for Hyperf.
hyperf/database                      v3.1.15            A flexible database library.
hyperf/database-pgsql                v3.1.15            A pgsql handler for hyperf/database.
hyperf/db-connection                 v3.1.15            A hyperf db connection handler for hyperf/database.
hyperf/devtool                       v3.1.15            A Devtool for Hyperf.
hyperf/di                            v3.1.15            A DI for Hyperf.
hyperf/dispatcher                    v3.1.15            A HTTP Server for Hyperf.
hyperf/engine-contract               v1.9.1             Contract for Coroutine Engine
hyperf/engine-swow                   v2.10.3            Coroutine engine provided by swow.
hyperf/event                         v3.1.15            an event manager that implements PSR-14.
hyperf/exception-handler             v3.1.15            Exception handler for hyperf
hyperf/filesystem                    v3.1.15            flysystem integration for hyperf
hyperf/flysystem-oss                 v1.3.0            
hyperf/framework                     v3.1.15            A coroutine framework that focuses on hyperspeed and flexible, specifically use ...
hyperf/guzzle                        v3.1.15            Swoole coroutine handler for guzzle
hyperf/http-message                  v3.1.15            microservice framework base on swoole
hyperf/http-server                   v3.1.15            A HTTP Server for Hyperf.
hyperf/ide-helper                    v3.1.15            IDE help files for Hyperf.
hyperf/logger                        v3.1.15            A logger component for hyperf.
hyperf/macroable                     v3.1.15            Hyperf Macroable package which come from illuminate/macroable
hyperf/memory                        v3.1.15            An independent component that use to operate and manage memory.
hyperf/migration-generator           v3.1.15            Migration generator for Hyperf.
hyperf/model-cache                   v3.1.15            A model cache component for hyperf.
hyperf/model-listener                v3.1.15            A model listener for Hyperf.
hyperf/paginator                     v3.1.15            A paginator component for hyperf.
hyperf/phar                          v3.1.15            A component that supports pack Hyperf project as a Phar stand-alone package.
hyperf/pipeline                      v3.1.15            Hyperf Macroable package which come from illuminate/pipeline
hyperf/pool                          v3.1.15            An independent universal connection pool component.
hyperf/process                       v3.1.15            A process component for hyperf.
hyperf/redis                         v3.1.15            A redis component for hyperf.
hyperf/serializer                    v3.1.15            A serializer component for Hyperf.
hyperf/server                        v3.1.15            A base server library for Hyperf.
hyperf/signal                        v3.1.15            A signal library for Hyperf.
hyperf/snowflake                     v3.1.15            A snowflake library
hyperf/stdlib                        v3.1.15            A stdlib component for Hyperf.
hyperf/stringable                    v3.1.15            Hyperf Stringable package which come from illuminate/support
hyperf/support                       v3.1.15            A support component for Hyperf.
hyperf/tappable                      v3.1.15            Hyperf Macroable package which come from illuminate/tappable
hyperf/testing                       v3.1.15            Testing for hyperf
hyperf/translation                   v3.1.15            An independent translation component, forked by illuminate/translation.
hyperf/utils                         v3.1.0             A tools package that could help developer solved the problem quickly.
hyperf/validation                    v3.1.15            hyperf validation
hyperf/watcher                       v3.1.15            Hot reload watcher for Hyperf
hyperf/websocket-server              v3.1.16            A websocket server library for Hyperf.

问题描述:

问题一:
我在访问任意之前可以正常访问的路由后出现Not Found;

问题二:
在#[Inject]的部分类在启动服务时,出现了错误:

[WARNING] Error: Typed property App\Socket\ClientManager::$redis must not be accessed before initialization in /opt/www/app/Socket/ClientManager.php:55

问题代码片段:

<?php
declare(strict_types=1);

namespace App\Wechat\Handler\Socket;

use Hyperf\Codec\Json;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Redis\Redis;
use Hyperf\WebSocketServer\Sender;
use Swow\Psr7\Psr7;
use Swow\Psr7\Server\ServerConnection;
use Ramsey\Uuid\Uuid;

class ClientManager
{
    private static string $serverId;

    #[Inject]
    public Redis $redis;

出现问题的原因:

我执行composer update后,看到更新了symfony相关的包,具体是哪几个没注意,然后就出现了路由找不到和Inject出问题,symfony包信息如下:

symfony/cache                        v6.4.6             Provides extended PSR-6, PSR-16 (and tags) implementations
symfony/cache-contracts              v3.4.2             Generic abstractions related to caching
symfony/console                      v6.4.6             Eases the creation of beautiful and testable command line interfaces
symfony/deprecation-contracts        v3.4.0             A generic function and convention to trigger deprecation notices
symfony/event-dispatcher             v7.0.3             Provides tools that allow your application components to communicate with each oth...
symfony/event-dispatcher-contracts   v3.4.2             Generic abstractions related to dispatching event
symfony/filesystem                   v6.4.6             Provides basic utilities for the filesystem
symfony/finder                       v7.0.0             Finds files and directories via an intuitive fluent interface
symfony/http-client                  v7.0.6             Provides powerful methods to fetch HTTP resources synchronously or asynchronously
symfony/http-client-contracts        v3.4.2             Generic abstractions related to HTTP clients
symfony/http-foundation              v6.4.4             Defines an object-oriented layer for the HTTP specification
symfony/mime                         v7.0.6             Allows manipulating MIME messages
symfony/options-resolver             v7.0.0             Provides an improved replacement for the array_replace PHP function
symfony/polyfill-ctype               v1.29.0            Symfony polyfill for ctype functions
symfony/polyfill-intl-grapheme       v1.29.0            Symfony polyfill for intl's grapheme_* functions
symfony/polyfill-intl-idn            v1.29.0            Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions
symfony/polyfill-intl-normalizer     v1.29.0            Symfony polyfill for intl's Normalizer class and related functions
symfony/polyfill-mbstring            v1.29.0            Symfony polyfill for the Mbstring extension
symfony/polyfill-php72               v1.29.0            Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions
symfony/polyfill-php80               v1.29.0            Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions
symfony/polyfill-php81               v1.29.0            Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions
symfony/polyfill-php83               v1.29.0            Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions
symfony/process                      v6.4.4             Executes commands in sub-processes
symfony/property-access              v6.4.6             Provides functions to read and write from/to an object or array using a simple str...
symfony/property-info                v7.0.6             Extracts information about PHP class' properties using metadata of popular sources
symfony/psr-http-message-bridge      v6.4.6             PSR HTTP message bridge
symfony/serializer                   v6.4.6             Handles serializing and deserializing data structures, including object graphs, in...
symfony/service-contracts            v3.4.2             Generic abstractions related to writing services
symfony/stopwatch                    v7.0.3             Provides a way to profile code
symfony/string                       v7.0.4             Provides an object-oriented API to strings and deals with bytes, UTF-8 code points...
symfony/translation                  v6.4.4             Provides tools to internationalize your application
symfony/translation-contracts        v3.4.2             Generic abstractions related to translation
symfony/var-exporter                 v7.0.6             Allows exporting any serializable PHP data structure to plain PHP code
commented

composer dump-autoload -o 也试了没用

commented

我发现是 \Hyperf\Di\Annotation\Inject 注入的都有问题,同时路由注解也有问题,因为我通过/config/routes.php配置的路由可以正常访问

commented
<?php
declare(strict_types=1);

namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Contract\RequestInterface;

#[AutoController]
class IndexController
{
    // Hyperf 会自动为此方法生成一个 /index/index 的路由,允许通过 GET 或 POST 方式请求
    public function index(RequestInterface $request)
    {
        // 从请求中获得 id 参数
        $id = $request->input('id', 1);
        return (string)$id;
    }
}

这个测试代码通过路由注解都没法正常访问


commented

php bin/hyperf.php describe:routes
这个命令都执行不了,报错:
There are no commands defined in the "describe" namespace.

commented

runtime/container/proxy目录下的代理类都没生成为啥服务还是启动成功了?

commented

上传个最小复现示例。盲猜不好猜

commented

已发现问题,原因是symfony/finder包更新导致,hyperf/di组件依赖这个包

commented

@limingxinleo 请及时修复一下

你没发现问题。

启动服务,截图看看,看看终端输出日志

commented
image
commented

终端没有输出任何报错信息,runtime/container下的代理类没生成

这样还有问题?

commented

代理类都没生成,终端这里没报错,但是请求路由提示找不到

commented

vendor/symfony/finder/Finder.php 修改注释掉以下代码中部分逻辑即可正常生成代理类:

public function filter(\Closure $closure, bool $prune = false): static
{
    $this->filters[] = $closure;

    //if ($prune) {
    //    $this->pruneFilters[] = $closure;
    //}

    return $this;
}


private function searchInDirectory(string $dir): \Iterator
{
    $exclude = $this->exclude;
    $notPaths = $this->notPaths;

    //if ($this->pruneFilters) {
    //   $exclude = array_merge($exclude, $this->pruneFilters);
    //}

    if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) {
        $exclude = array_merge($exclude, self::$vcsPatterns);
    }

    if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) {
        $notPaths[] = '#(^|/)\..+(/|$)#';
    }

    $minDepth = 0;
    $maxDepth = \PHP_INT_MAX;

    foreach ($this->depths as $comparator) {
        switch ($comparator->getOperator()) {
            case '>':
                $minDepth = $comparator->getTarget() + 1;
                break;
            case '>=':
                $minDepth = $comparator->getTarget();
                break;
            case '<':
                $maxDepth = $comparator->getTarget() - 1;
                break;
            case '<=':
                $maxDepth = $comparator->getTarget();
                break;
            default:
                $minDepth = $maxDepth = $comparator->getTarget();
        }
    }

    $flags = \RecursiveDirectoryIterator::SKIP_DOTS;

    if ($this->followLinks) {
        $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
    }

    $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs);

    if ($exclude) {
        $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $exclude);
    }

    $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);

    if ($minDepth > 0 || $maxDepth < \PHP_INT_MAX) {
        $iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth);
    }

    if ($this->mode) {
        $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode);
    }

    if ($this->names || $this->notNames) {
        $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames);
    }

    if ($this->contains || $this->notContains) {
        $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
    }

    if ($this->sizes) {
        $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes);
    }

    if ($this->dates) {
        $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates);
    }

    if ($this->filters) {
        $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
    }

    if ($this->paths || $notPaths) {
        $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths);
    }

    if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) {
        $iterator = new Iterator\VcsIgnoredFilterIterator($iterator, $dir);
    }

    return $iterator;
}
  1. 先确认一下目录权限
  2. 把完整的错误,包括详细的 trace 贴出来
commented

我发现了一个非常诡异的事情,第一种解决办法:我修改上面的vendor/symfony/finder/Finder.php的两处地方注释掉后,rm -rf runtime/* && php bin/hyperf.php start启动服务,在runtime/container/proxy目录下可以正常生成代理类,启动的终端没有任何报错信息。此时路由访问等一切正常。

第二种诡异的解决办法,还原vendor/symfony/finder/Finder.php文件的修改,我尝试寻找问题所在点,修改vendor/hyperf/di/src/ReflectionManager.php文件(注意我注释的地方):
`<?php

declare(strict_types=1);
/**

namespace Hyperf\Di;

use Hyperf\Di\Aop\Ast;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use Symfony\Component\Finder\Finder;
use Throwable;

use function Hyperf\Support\value;

class ReflectionManager extends MetadataCollector
{
protected static array $container = [];

public static function reflectClass(string $className): ReflectionClass
{
    if (! isset(static::$container['class'][$className])) {
        if (! class_exists($className) && ! interface_exists($className) && ! trait_exists($className)) {
            throw new InvalidArgumentException("Class {$className} not exist");
        }
        static::$container['class'][$className] = new ReflectionClass($className);
    }
    return static::$container['class'][$className];
}

public static function reflectMethod(string $className, string $method): ReflectionMethod
{
    $key = $className . '::' . $method;
    if (! isset(static::$container['method'][$key])) {
        // TODO check interface_exist
        if (! class_exists($className) && ! trait_exists($className)) {
            throw new InvalidArgumentException("Class {$className} not exist");
        }
        static::$container['method'][$key] = static::reflectClass($className)->getMethod($method);
    }
    return static::$container['method'][$key];
}

public static function reflectProperty(string $className, string $property): ReflectionProperty
{
    $key = $className . '::' . $property;
    if (! isset(static::$container['property'][$key])) {
        if (! class_exists($className)) {
            throw new InvalidArgumentException("Class {$className} not exist");
        }
        static::$container['property'][$key] = static::reflectClass($className)->getProperty($property);
    }
    return static::$container['property'][$key];
}

public static function reflectPropertyNames(string $className)
{
    $key = $className;
    if (! isset(static::$container['property_names'][$key])) {
        if (! class_exists($className) && ! interface_exists($className) && ! trait_exists($className)) {
            throw new InvalidArgumentException("Class {$className} not exist");
        }
        static::$container['property_names'][$key] = value(function () use ($className) {
            $properties = static::reflectClass($className)->getProperties();
            $result = [];
            foreach ($properties as $property) {
                $result[] = $property->getName();
            }
            return $result;
        });
    }
    return static::$container['property_names'][$key];
}

public static function clear(?string $key = null): void
{
    if ($key === null) {
        static::$container = [];
    }
}

public static function getPropertyDefaultValue(ReflectionProperty $property)
{
    return method_exists($property, 'getDefaultValue')
        ? $property->getDefaultValue()
        : $property->getDeclaringClass()->getDefaultProperties()[$property->getName()] ?? null;
}

public static function getAllClasses(array $paths): array
{
    $finder = new Finder();
    $finder->files()->in($paths)->name('*.php');

    return static::getAllClassesByFinder($finder);
}

public static function getAllClassesByFinder(Finder $finder): array
{
    $parser = new Ast();

    $reflectionClasses = [];
    foreach ($finder as $file) {
        try {

// var_dump(1); 注意这个位置我如果加上var_dump(1) 然后rm -rf runtime/* && php bin/hyperf.php start启动服务,在runtime/container/proxy目录下可以正常生成代理类,此时终端显示启动服务成功,也没有任何报错信息,同时也没有任何打印结果。此时路由访问等一切正常。

            $stmts = $parser->parse($file->getContents());
            if (! $className = $parser->parseClassByStmts($stmts)) {
                continue;
            }
            $reflectionClasses[$className] = static::reflectClass($className);
        } catch (Throwable $e) {
            echo sprintf(
                "\033[31m%s\033[0m",
                '[ERROR] DI Reflection Manager collecting class reflections failed. ' . PHP_EOL .
                    "File: {$file->getRealPath()}." . PHP_EOL .
                    'Exception: ' . $e->getMessage()
            ) . PHP_EOL;
        }
    }
    return $reflectionClasses;
}

public static function getContainer(): array
{
    return self::$container;
}

}
`

已经生成代理类,再更改,如果没有重新生成,容易出“诡异的问题”,删除代理类重新生成就好了。

commented

我每次都是删除后再运行的,启动命令:rm -rf runtime/* && php bin/hyperf.php start

你也可以尽量不用#[Inject]

commented

你也可以尽量不用#[Inject]

现在的问题不是用不用#[Inject]的问题,是启动服务后代理类都没有生成,这样肯定功能不正常吧,难道是写了#[Inject]的类才会生成代理类吗?

你也可以尽量不用#[Inject]

现在的问题不是用不用#[Inject]的问题,是启动服务后代理类都没有生成,这样肯定功能不正常吧,难道是写了#[Inject]的类才会生成代理类吗?

不用也可能会生成,因为有其他注解或切眉功能。我只是分享我的个人习惯,尽量避免生成代理类,因为我也遇到过几次类似的问题。

commented

问题排查解决:

由于我使用的是swow驱动,bin/hyperf.php文件中引用了swow-skeleton框架代码: Hyperf\Di\ClassLoader::init(handler: new Hyperf\Di\ScanHandler\ProcScanHandler());

解决方案一:
在swow-skeleton框架中不知道为啥使用了ProcScanHandler作为扫描处理器,这个使用proc_open创建子进程扫描似乎有问题,因为我在ProcScanHandler文件的scan方法中任意位置加入sleep、file_put_contents、var_dump(其他未测试)的情况下,代理类可以正常生成,没有任何问题。

解决方案二(推荐):
使用默认的PcntlScanHandler, 这里依赖于 PHP 的 pcntl 扩展,因为使用了pcntl_fork创建子进程,性能和稳定性各方面都要更好。

环境是windows下运行的docker?

commented

问题排查解决:

由于我使用的是swow驱动,bin/hyperf.php文件中引用了swow-skeleton框架代码: Hyperf\Di\ClassLoader::init(handler: new Hyperf\Di\ScanHandler\ProcScanHandler());

解决方案一:

在swow-skeleton框架中不知道为啥使用了ProcScanHandler作为扫描处理器,这个使用proc_open创建子进程扫描似乎有问题,因为我在ProcScanHandler文件的scan方法中任意位置加入sleep、file_put_contents、var_dump(其他未测试)的情况下,代理类可以正常生成,没有任何问题。

解决方案二(推荐):

使用默认的PcntlScanHandler, 这里依赖于 PHP 的 pcntl 扩展,因为使用了pcntl_fork创建子进程,性能和稳定性各方面都要更好。

可以给 swow-skeleton 提个 PR