16
16
abstract class Handler implements HandleInterface
17
17
{
18
18
/** @var OpenAPI */
19
- protected static OpenAPI $ OpenAPI ;
20
- private string $ controllerPrefix = '' ;
21
- private string $ controllerSuffix = '' ;
19
+ public static OpenAPI $ OpenAPI ;
22
20
23
21
public function __construct (
24
22
protected readonly ParameterStorage $ headerParameterStorages = new ParameterStorage ()
@@ -50,100 +48,98 @@ public function addGlobalHeader(string $name, string $example = '', string $desc
50
48
}
51
49
52
50
/**
53
- * Undocumented function
54
- */
55
- public function getOpenAPI (): OpenAPI
51
+ * 遍历整个项目根目录,自动扫描所有 PHP 文件,
52
+ * 如果文件内容中包含 "Astral\Serialize\OpenApi\Annotations",
53
+ * 则认为它是需要处理的 Controller,进而调用 buildByClass。
54
+ *
55
+ * @return $this
56
+ * @throws ReflectionException
57
+ */
58
+ public function handleByFolders (): self
56
59
{
57
- return self :: $ OpenAPI ;
58
- }
60
+ // 1. 取项目根目录
61
+ $ projectRoot = getcwd ();
59
62
60
- /**
61
- * 增加类前缀标识
62
- *
63
- * @return $this
64
- */
65
- public function withControllerPrefix (string $ value ): self
66
- {
67
- $ this ->controllerPrefix = $ value ;
68
- return $ this ;
69
- }
63
+ if ($ projectRoot === false ) {
64
+ return $ this ;
65
+ }
70
66
71
- /**
72
- * 增加类后缀标识
73
- *
74
- * @return $this
75
- */
76
- public function withControllerSuffix (string $ value ): self
77
- {
78
- $ this ->controllerSuffix = $ value ;
79
- return $ this ;
80
- }
67
+ // 2. 默认根命名空间留空(根据项目实际情况可改为 "App"、"App\\Http\\Controllers" 等)
68
+ // 如果你希望扫描时拼接命名空间前缀,可以在这里做修改。
69
+ $ rootNamespace = '' ;
81
70
82
- /**
83
- * @throws ReflectionException
84
- */
85
- public function handleByAutoLoad (): void
86
- {
87
- $ classMap = require dir ('vendor/composer/autoload_classmap.php ' );
88
- $ appClassMap = array_keys (array_filter ($ classMap , static function ($ key ) {
89
- return (str_starts_with ($ key , 'App \\' ) && str_ends_with ($ key , 'Controller ' ))
90
- || (str_starts_with ($ key , 'April \\' ) && str_ends_with ($ key , 'Controller ' ));
91
- }, ARRAY_FILTER_USE_KEY ));
71
+ // 3. 调用内部递归方法开始扫描
72
+ $ this ->scanFolderRecursively ($ projectRoot , $ rootNamespace );
92
73
93
- foreach ($ appClassMap as $ className ) {
94
- $ this ->buildByClass ($ className );
95
- }
74
+ return $ this ;
96
75
}
97
76
98
77
/**
99
- * 解析Controller文件
78
+ * 递归扫描指定目录下的所有子目录和文件。
79
+ * @param string $folder 要扫描的文件夹路径
80
+ * @param string $namespace 该文件夹对应的 PHP 命名空间前缀
100
81
*
101
- * @param array<string,string> $folders 文件路径 => 命名空间
102
- * @return $this
103
82
* @throws ReflectionException
104
83
*/
105
- public function handleByFolders ( array $ folders ): self
84
+ protected function scanFolderRecursively ( string $ folder , string $ namespace ): void
106
85
{
86
+ // 如果不是目录,跳过
87
+ if (! is_dir ($ folder )) {
88
+ return ;
89
+ }
107
90
108
- foreach ($ folders as $ folder => $ namespace ) {
109
-
110
- if (! is_dir ($ folder )) {
91
+ // 遍历当前文件夹下的所有内容
92
+ foreach (scandir ($ folder ) as $ file ) {
93
+ // 跳过 . 和 .. ,以及隐藏文件夹/文件
94
+ if ($ file === '. ' || $ file === '.. ' || str_starts_with ($ file , '. ' )) {
111
95
continue ;
112
96
}
113
97
114
- foreach ( scandir ( $ folder) as $ file) {
98
+ $ path = $ folder . DIRECTORY_SEPARATOR . $ file;
115
99
116
- $ path = $ folder . '/ ' . $ file ;
117
- if ($ file === '. ' || $ file === '.. ' || str_starts_with ($ file , '. ' )) {
118
- continue ;
119
- }
100
+ // 如果是子目录,则递归,并拼接命名空间
101
+ if (is_dir ($ path )) {
120
102
121
- if (is_dir ($ path )) {
122
- $ this ->handleByFolders ([$ path => $ namespace . '\\' . $ file ]);
103
+ // 例如,如果当前命名空间是 "App":
104
+ // 子目录 "Http" 则新的命名空间为 "App\Http"
105
+ $ newNamespace = $ namespace !== '' ? ($ namespace . '\\' . $ file ) : $ file ;
106
+ $ this ->scanFolderRecursively ($ path , $ newNamespace );
107
+ continue ;
108
+ }
123
109
124
- continue ;
125
- }
110
+ // 只处理 .php 文件
111
+ if (pathinfo ($ file , PATHINFO_EXTENSION ) !== 'php ' ) {
112
+ continue ;
113
+ }
126
114
127
- if (pathinfo ($ file , PATHINFO_EXTENSION ) !== 'php ' ) {
128
- continue ;
129
- }
115
+ // 读取文件内容,检查是否包含 Annotations 关键字
116
+ $ fileContent = @file_get_contents ($ path );
117
+ if ($ fileContent === false ) {
118
+ continue ;
119
+ }
120
+
121
+ // 如果文件中没有引入 Astral\Serialize\OpenApi\Annotations,就跳过
122
+ if (!str_contains ($ fileContent , 'Astral \\Serialize \\OpenApi \\Annotations ' )) {
123
+ continue ;
124
+ }
130
125
131
- $ fileName = $ this ->controllerPrefix . trim (substr ($ file , 0 , strpos ($ file , '. ' ))) . $ this ->controllerSuffix ;
132
- $ className = $ namespace ? $ namespace . '\\' . $ fileName : $ fileName ;
126
+ // 计算类名:去掉 .php 之后,将命名空间前缀 + 文件名 组成完整类名
127
+ $ baseName = substr ($ file , 0 , -4 ); // 去掉 ".php"
128
+ $ className = $ namespace !== '' ? ($ namespace . '\\' . $ baseName ) : $ baseName ;
133
129
130
+ // 如果类尚未加载,则尝试 include
131
+ if (! class_exists ($ className )) {
132
+ include_once $ path ;
133
+ @ob_clean ();
134
134
if (! class_exists ($ className )) {
135
- include_once $ folder . '/ ' . $ file ;
136
- ob_clean (); // 清除一些引入进来的莫名其妙输出文件
137
- if (! class_exists ($ className )) {
138
- continue ;
139
- }
135
+ // 如果 include 后仍然不存在该类,跳过
136
+ continue ;
140
137
}
141
-
142
- $ this ->buildByClass ($ className );
143
138
}
144
- }
145
139
146
- return $ this ;
140
+ // 调用子类实现的 buildByClass
141
+ $ this ->buildByClass ($ className );
142
+ }
147
143
}
148
144
149
145
@@ -158,6 +154,6 @@ public function output(string $path): bool
158
154
*/
159
155
public function toString (): string
160
156
{
161
- return json_encode (self ::$ OpenAPI , JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE );
157
+ return json_encode (self ::$ OpenAPI , JSON_THROW_ON_ERROR );
162
158
}
163
159
}
0 commit comments