Skip to content

Commit 3e0d541

Browse files
committed
ref v2
1 parent 578e3be commit 3e0d541

File tree

6 files changed

+117
-93
lines changed

6 files changed

+117
-93
lines changed

src/Enums/TypeKindEnum.php

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,4 @@ public static function getNameTo(string $type, ?string $className = null): self
6969
default => throw new RuntimeException("not found type $type"),
7070
};
7171
}
72-
73-
public function getOpenApiName(): string
74-
{
75-
return match ($this) {
76-
self::INT => 'integer',
77-
self::FLOAT => 'number',
78-
self::BOOLEAN => 'boolean',
79-
self::OBJECT, self::CLASS_OBJECT => 'object',
80-
self::ARRAY, self::COLLECT_SINGLE_OBJECT , self::COLLECT_UNION_OBJECT => 'array',
81-
default => 'string',
82-
};
83-
}
8472
}

src/OpenApi/Collections/OpenApiCollection.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Astral\Serialize\OpenApi\Annotations\Summary;
1111
use Astral\Serialize\OpenApi\Annotations\Tag;
1212
use Astral\Serialize\OpenApi\Enum\ContentTypeEnum;
13+
use Astral\Serialize\OpenApi\Enum\ParameterTypeEnum;
1314
use Astral\Serialize\OpenApi\Storage\OpenAPI\Method\Method;
1415
use Astral\Serialize\OpenApi\Storage\OpenAPI\RequestBodyStorage;
1516
use Astral\Serialize\OpenApi\Storage\OpenAPI\ResponseStorage;
@@ -109,7 +110,7 @@ public function buildRequestBodyParameterCollections(string $className, array $g
109110
$vol = new ParameterCollection(
110111
name: current($property->getInputNamesByGroups($groups,$className)),
111112
descriptions: '',
112-
type: current($property->getTypes())->kind ?: TypeKindEnum::STRING,
113+
type: ParameterTypeEnum::getByTypes($property->getTypes()),
113114
required: !$property->isNullable(),
114115
ignore: false,
115116
);
@@ -153,7 +154,7 @@ public function buildResponseParameterCollections(): array
153154
$vol = new ParameterCollection(
154155
name:current($property->getOutNamesByGroups($groups,$responseClass)),
155156
descriptions: '',
156-
type: current($property->getTypes())->kind ?: TypeKindEnum::STRING,
157+
type: ParameterTypeEnum::getByTypes($property->getTypes()),
157158
required: !$property->isNullable(),
158159
ignore: false,
159160
);

src/OpenApi/Collections/ParameterCollection.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Astral\Serialize\OpenApi\Collections;
66

77
use Astral\Serialize\Enums\TypeKindEnum;
8+
use Astral\Serialize\OpenApi\Enum\ParameterTypeEnum;
89
use Attribute;
910

1011
class ParameterCollection
@@ -14,7 +15,7 @@ public function __construct(
1415
public string $name,
1516
/** @var string descriptions */
1617
public string $descriptions = '',
17-
public TypeKindEnum $type = TypeKindEnum::STRING,
18+
public ParameterTypeEnum $type = ParameterTypeEnum::STRING,
1819
/** @var mixed 示例值 */
1920
public mixed $example = '',
2021
/** @var bool 是否必填 */

src/OpenApi/Enum/ParameterTypeEnum.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,50 @@
22

33
namespace Astral\Serialize\OpenApi\Enum;
44

5+
use Astral\Serialize\Enums\TypeKindEnum;
6+
use Astral\Serialize\Support\Collections\TypeCollection;
7+
58
enum ParameterTypeEnum: string
69
{
710
case ARRAY = 'array';
811
case STRING = 'string';
912
case OBJECT = 'object';
13+
case BOOLEAN = 'boolean';
14+
case INTEGER = 'integer';
15+
case NUMBER = 'number';
16+
case ONE_OF = 'oneOf';
17+
case ANY_OF = 'anyOf';
18+
case ALL_OF = 'allOf';
19+
20+
/**
21+
* @param TypeCollection[] $types
22+
*/
23+
public static function getByTypes(array $types): ParameterTypeEnum
24+
{
25+
26+
$count = count($types);
27+
28+
if($count === 1){
29+
$type = current($types)->kind;
30+
return match (true){
31+
$type === TypeKindEnum::INT => self::INTEGER,
32+
$type === TypeKindEnum::FLOAT => self::NUMBER,
33+
$type === TypeKindEnum::BOOLEAN => self::BOOLEAN,
34+
$type === TypeKindEnum::OBJECT, $type === TypeKindEnum::CLASS_OBJECT => self::OBJECT,
35+
$type === TypeKindEnum::ARRAY, $type === TypeKindEnum::COLLECT_SINGLE_OBJECT , $type === TypeKindEnum::COLLECT_UNION_OBJECT => self::ARRAY,
36+
default => self::STRING,
37+
};
38+
}
39+
40+
$hasUnion = false;
41+
foreach ($types as $type){
42+
if($type->kind === TypeKindEnum::COLLECT_UNION_OBJECT){
43+
$hasUnion = true;
44+
}
45+
}
46+
47+
return $hasUnion ? self::ANY_OF : self::ONE_OF;
48+
49+
50+
}
1051
}

src/OpenApi/Handler/Handler.php

Lines changed: 68 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616
abstract class Handler implements HandleInterface
1717
{
1818
/** @var OpenAPI */
19-
protected static OpenAPI $OpenAPI;
20-
private string $controllerPrefix = '';
21-
private string $controllerSuffix = '';
19+
public static OpenAPI $OpenAPI;
2220

2321
public function __construct(
2422
protected readonly ParameterStorage $headerParameterStorages = new ParameterStorage()
@@ -50,100 +48,98 @@ public function addGlobalHeader(string $name, string $example = '', string $desc
5048
}
5149

5250
/**
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
5659
{
57-
return self::$OpenAPI;
58-
}
60+
// 1. 取项目根目录
61+
$projectRoot = getcwd();
5962

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+
}
7066

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 = '';
8170

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);
9273

93-
foreach ($appClassMap as $className) {
94-
$this->buildByClass($className);
95-
}
74+
return $this;
9675
}
9776

9877
/**
99-
* 解析Controller文件
78+
* 递归扫描指定目录下的所有子目录和文件。
79+
* @param string $folder 要扫描的文件夹路径
80+
* @param string $namespace 该文件夹对应的 PHP 命名空间前缀
10081
*
101-
* @param array<string,string> $folders 文件路径 => 命名空间
102-
* @return $this
10382
* @throws ReflectionException
10483
*/
105-
public function handleByFolders(array $folders): self
84+
protected function scanFolderRecursively(string $folder, string $namespace): void
10685
{
86+
// 如果不是目录,跳过
87+
if (! is_dir($folder)) {
88+
return;
89+
}
10790

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, '.')) {
11195
continue;
11296
}
11397

114-
foreach (scandir($folder) as $file) {
98+
$path = $folder . DIRECTORY_SEPARATOR . $file;
11599

116-
$path = $folder . '/' . $file;
117-
if ($file === '.' || $file === '..' || str_starts_with($file, '.')) {
118-
continue;
119-
}
100+
// 如果是子目录,则递归,并拼接命名空间
101+
if (is_dir($path)) {
120102

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+
}
123109

124-
continue;
125-
}
110+
// 只处理 .php 文件
111+
if (pathinfo($file, PATHINFO_EXTENSION) !== 'php') {
112+
continue;
113+
}
126114

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+
}
130125

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;
133129

130+
// 如果类尚未加载,则尝试 include
131+
if (! class_exists($className)) {
132+
include_once $path;
133+
@ob_clean();
134134
if (! class_exists($className)) {
135-
include_once $folder . '/' . $file;
136-
ob_clean(); // 清除一些引入进来的莫名其妙输出文件
137-
if (! class_exists($className)) {
138-
continue;
139-
}
135+
// 如果 include 后仍然不存在该类,跳过
136+
continue;
140137
}
141-
142-
$this->buildByClass($className);
143138
}
144-
}
145139

146-
return $this;
140+
// 调用子类实现的 buildByClass
141+
$this->buildByClass($className);
142+
}
147143
}
148144

149145

@@ -158,6 +154,6 @@ public function output(string $path): bool
158154
*/
159155
public function toString(): string
160156
{
161-
return json_encode(self::$OpenAPI, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
157+
return json_encode(self::$OpenAPI, JSON_THROW_ON_ERROR);
162158
}
163159
}

tests/Openapi/ArrayOpenApi.php

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,7 @@ public function one(TestOpenApiRequest $request): TestOpenApiResponse
5757

5858
//
5959
it('test openapi build by class', function () {
60-
61-
$api = new \Astral\Serialize\OpenApi\OpenApi();
62-
$api->buildByClass(TestOpenApiController::class);
63-
$res = $api->toString();
64-
print_r($res);
65-
60+
$api = new \Astral\Serialize\OpenApi\OpenApi();
61+
$api->buildByClass(TestOpenApiController::class);
62+
$res = $api->toString();
6663
});

0 commit comments

Comments
 (0)