[BUG] composer update更新symfony相关包后,访问任意路由出现Not Found,#[Inject]的部分类在启动服务时会出现must not be accessed before initialization
lujihong opened this issue · comments
运行环境信息如下:
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
composer dump-autoload -o 也试了没用
我发现是 \Hyperf\Di\Annotation\Inject 注入的都有问题,同时路由注解也有问题,因为我通过/config/routes.php配置的路由可以正常访问
<?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;
}
}
这个测试代码通过路由注解都没法正常访问
php bin/hyperf.php describe:routes
这个命令都执行不了,报错:
There are no commands defined in the "describe" namespace.
runtime/container/proxy目录下的代理类都没生成为啥服务还是启动成功了?
上传个最小复现示例。盲猜不好猜
已发现问题,原因是symfony/finder包更新导致,hyperf/di组件依赖这个包
@limingxinleo 请及时修复一下
你没发现问题。
启动服务,截图看看,看看终端输出日志
终端没有输出任何报错信息,runtime/container下的代理类没生成
这样还有问题?
代理类都没生成,终端这里没报错,但是请求路由提示找不到
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;
}
- 先确认一下目录权限
- 把完整的错误,包括详细的 trace 贴出来
我发现了一个非常诡异的事情,第一种解决办法:我修改上面的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);
/**
- This file is part of Hyperf.
- @link https://www.hyperf.io
- @document https://hyperf.wiki
- @contact group@hyperf.io
- @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
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;
}
}
`
已经生成代理类,再更改,如果没有重新生成,容易出“诡异的问题”,删除代理类重新生成就好了。
我每次都是删除后再运行的,启动命令:rm -rf runtime/* && php bin/hyperf.php start
你也可以尽量不用#[Inject]
你也可以尽量不用#[Inject]
现在的问题不是用不用#[Inject]的问题,是启动服务后代理类都没有生成,这样肯定功能不正常吧,难道是写了#[Inject]的类才会生成代理类吗?
你也可以尽量不用#[Inject]
现在的问题不是用不用#[Inject]的问题,是启动服务后代理类都没有生成,这样肯定功能不正常吧,难道是写了#[Inject]的类才会生成代理类吗?
不用也可能会生成,因为有其他注解或切眉功能。我只是分享我的个人习惯,尽量避免生成代理类,因为我也遇到过几次类似的问题。
问题排查解决:
由于我使用的是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?
问题排查解决:
由于我使用的是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