create-prisma-php-app 4.0.0-alpha.2 → 4.0.0-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.htaccess +54 -41
- package/dist/bootstrap.php +143 -98
- package/dist/index.js +264 -99
- package/dist/settings/auto-swagger-docs.ts +196 -95
- package/dist/settings/bs-config.ts +56 -58
- package/dist/settings/files-list.json +1 -1
- package/dist/settings/restart-mcp.ts +58 -0
- package/dist/settings/restart-websocket.ts +51 -45
- package/dist/settings/utils.ts +240 -0
- package/dist/src/Lib/AI/ChatGPTClient.php +147 -0
- package/dist/src/Lib/Auth/Auth.php +544 -0
- package/dist/src/Lib/Auth/AuthConfig.php +89 -0
- package/dist/src/Lib/CacheHandler.php +121 -0
- package/dist/src/Lib/ErrorHandler.php +322 -0
- package/dist/src/Lib/FileManager/UploadFile.php +383 -0
- package/dist/src/Lib/Headers/Boom.php +192 -0
- package/dist/src/Lib/IncludeTracker.php +59 -0
- package/dist/src/Lib/MCP/WeatherTools.php +104 -0
- package/dist/src/Lib/MCP/mcp-server.php +80 -0
- package/dist/src/Lib/MainLayout.php +230 -0
- package/dist/src/Lib/Middleware/AuthMiddleware.php +154 -0
- package/dist/src/Lib/Middleware/CorsMiddleware.php +145 -0
- package/dist/src/Lib/PHPMailer/Mailer.php +169 -0
- package/dist/src/Lib/PHPX/Exceptions/ComponentValidationException.php +49 -0
- package/dist/src/Lib/PHPX/Fragment.php +32 -0
- package/dist/src/Lib/PHPX/IPHPX.php +22 -0
- package/dist/src/Lib/PHPX/PHPX.php +287 -0
- package/dist/src/Lib/PHPX/TemplateCompiler.php +641 -0
- package/dist/src/Lib/PHPX/TwMerge.php +346 -0
- package/dist/src/Lib/PHPX/TypeCoercer.php +490 -0
- package/dist/src/Lib/PartialRenderer.php +40 -0
- package/dist/src/Lib/PrismaPHPSettings.php +181 -0
- package/dist/src/Lib/Request.php +479 -0
- package/dist/src/Lib/Security/RateLimiter.php +33 -0
- package/dist/src/Lib/Set.php +102 -0
- package/dist/src/Lib/StateManager.php +127 -0
- package/dist/src/Lib/Validator.php +752 -0
- package/dist/src/{Websocket → Lib/Websocket}/ConnectionManager.php +1 -1
- package/dist/src/Lib/Websocket/websocket-server.php +118 -0
- package/dist/src/app/error.php +1 -1
- package/dist/src/app/index.php +24 -5
- package/dist/src/app/js/index.js +1 -1
- package/dist/src/app/layout.php +2 -2
- package/package.json +1 -1
- package/dist/settings/restart-websocket.bat +0 -28
- package/dist/src/app/assets/images/prisma-php-black.svg +0 -6
- package/dist/websocket-server.php +0 -22
- package/vendor/autoload.php +0 -25
- package/vendor/composer/ClassLoader.php +0 -579
- package/vendor/composer/InstalledVersions.php +0 -359
- package/vendor/composer/LICENSE +0 -21
- package/vendor/composer/autoload_classmap.php +0 -10
- package/vendor/composer/autoload_namespaces.php +0 -9
- package/vendor/composer/autoload_psr4.php +0 -10
- package/vendor/composer/autoload_real.php +0 -38
- package/vendor/composer/autoload_static.php +0 -25
- package/vendor/composer/installed.json +0 -825
- package/vendor/composer/installed.php +0 -132
- package/vendor/composer/platform_check.php +0 -26
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Lib\PHPX;
|
|
6
|
+
|
|
7
|
+
use Lib\Validator;
|
|
8
|
+
use ReflectionType;
|
|
9
|
+
use ReflectionNamedType;
|
|
10
|
+
use ReflectionUnionType;
|
|
11
|
+
use ReflectionIntersectionType;
|
|
12
|
+
use DateTime;
|
|
13
|
+
use DateTimeImmutable;
|
|
14
|
+
use DateTimeInterface;
|
|
15
|
+
use Brick\Math\BigDecimal;
|
|
16
|
+
use Brick\Math\BigInteger;
|
|
17
|
+
|
|
18
|
+
class TypeCoercer
|
|
19
|
+
{
|
|
20
|
+
private static array $typeCache = [];
|
|
21
|
+
private static array $phpTypeMap = [
|
|
22
|
+
'boolean' => 'bool',
|
|
23
|
+
'integer' => 'int',
|
|
24
|
+
'double' => 'float',
|
|
25
|
+
'string' => 'string',
|
|
26
|
+
'array' => 'array',
|
|
27
|
+
'object' => 'object',
|
|
28
|
+
'resource' => 'resource',
|
|
29
|
+
'NULL' => 'null',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
public static function coerce(mixed $value, ?ReflectionType $type, array $validationRules = []): mixed
|
|
33
|
+
{
|
|
34
|
+
if ($type === null) {
|
|
35
|
+
return $value;
|
|
36
|
+
}
|
|
37
|
+
$typeKey = self::getTypeKey($type);
|
|
38
|
+
if (!isset(self::$typeCache[$typeKey])) {
|
|
39
|
+
self::$typeCache[$typeKey] = self::analyzeType($type);
|
|
40
|
+
}
|
|
41
|
+
$typeInfo = self::$typeCache[$typeKey];
|
|
42
|
+
return self::coerceWithTypeInfo($value, $typeInfo, $validationRules);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private static function coerceWithTypeInfo(mixed $value, array $typeInfo, array $validationRules = []): mixed
|
|
46
|
+
{
|
|
47
|
+
if ($value === null && $typeInfo['allowsNull']) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
if ($typeInfo['isUnion']) {
|
|
51
|
+
return self::coerceUnionTypeSmart($value, $typeInfo['types'], $validationRules);
|
|
52
|
+
}
|
|
53
|
+
if (count($typeInfo['types']) === 1) {
|
|
54
|
+
$currentType = self::getNormalizedType($value);
|
|
55
|
+
$targetType = $typeInfo['types'][0]['name'];
|
|
56
|
+
if ($currentType === $targetType) {
|
|
57
|
+
return $value;
|
|
58
|
+
}
|
|
59
|
+
return self::coerceSingleType($value, $typeInfo['types'][0], $validationRules);
|
|
60
|
+
}
|
|
61
|
+
return $value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private static function analyzeType(ReflectionType $type): array
|
|
65
|
+
{
|
|
66
|
+
$info = [
|
|
67
|
+
'isUnion' => false,
|
|
68
|
+
'isIntersection' => false,
|
|
69
|
+
'allowsNull' => false,
|
|
70
|
+
'types' => [],
|
|
71
|
+
];
|
|
72
|
+
if ($type instanceof ReflectionUnionType) {
|
|
73
|
+
$info['isUnion'] = true;
|
|
74
|
+
foreach ($type->getTypes() as $unionType) {
|
|
75
|
+
if ($unionType->getName() === 'null') {
|
|
76
|
+
$info['allowsNull'] = true;
|
|
77
|
+
} else {
|
|
78
|
+
$info['types'][] = [
|
|
79
|
+
'name' => $unionType->getName(),
|
|
80
|
+
'isBuiltin' => $unionType->isBuiltin(),
|
|
81
|
+
'allowsNull' => $unionType->allowsNull(),
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} elseif ($type instanceof ReflectionNamedType) {
|
|
86
|
+
$info['allowsNull'] = $type->allowsNull();
|
|
87
|
+
if ($type->getName() !== 'null') {
|
|
88
|
+
$info['types'][] = [
|
|
89
|
+
'name' => $type->getName(),
|
|
90
|
+
'isBuiltin' => $type->isBuiltin(),
|
|
91
|
+
'allowsNull' => $type->allowsNull(),
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
} elseif ($type instanceof ReflectionIntersectionType) {
|
|
95
|
+
$info['isIntersection'] = true;
|
|
96
|
+
foreach ($type->getTypes() as $intersectionType) {
|
|
97
|
+
if ($intersectionType instanceof ReflectionNamedType) {
|
|
98
|
+
$info['types'][] = [
|
|
99
|
+
'name' => $intersectionType->getName(),
|
|
100
|
+
'isBuiltin' => $intersectionType->isBuiltin(),
|
|
101
|
+
'allowsNull' => $intersectionType->allowsNull(),
|
|
102
|
+
];
|
|
103
|
+
} else {
|
|
104
|
+
$info['types'][] = [
|
|
105
|
+
'name' => (string) $intersectionType,
|
|
106
|
+
'isBuiltin' => false,
|
|
107
|
+
'allowsNull' => false,
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return $info;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private static function coerceUnionTypeSmart(mixed $value, array $types, array $validationRules = []): mixed
|
|
116
|
+
{
|
|
117
|
+
$typeNames = array_column($types, 'name');
|
|
118
|
+
return match (true) {
|
|
119
|
+
self::hasTypes($typeNames, ['string', 'bool']) =>
|
|
120
|
+
self::coerceStringBoolUnion($value, $types, $validationRules),
|
|
121
|
+
self::hasTypes($typeNames, ['int', 'string']) =>
|
|
122
|
+
self::coerceIntStringUnion($value, $types, $validationRules),
|
|
123
|
+
self::hasTypes($typeNames, ['float', 'string']) =>
|
|
124
|
+
self::coerceFloatStringUnion($value, $types, $validationRules),
|
|
125
|
+
self::hasTypes($typeNames, ['array', 'string']) =>
|
|
126
|
+
self::coerceArrayStringUnion($value, $types, $validationRules),
|
|
127
|
+
self::hasDateTimeTypes($typeNames) =>
|
|
128
|
+
self::coerceDateTimeUnion($value, $types, $validationRules),
|
|
129
|
+
default => self::coerceUnionTypePhpCompliant($value, $types, $validationRules)
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private static function coerceStringBoolUnion(mixed $value, array $types, array $validationRules = []): mixed
|
|
134
|
+
{
|
|
135
|
+
if (is_bool($value)) {
|
|
136
|
+
return $value;
|
|
137
|
+
}
|
|
138
|
+
if (is_string($value) && self::isBooleanLike($value)) {
|
|
139
|
+
return self::coerceBool($value, $validationRules);
|
|
140
|
+
}
|
|
141
|
+
return self::coerceString($value, $validationRules);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private static function coerceIntStringUnion(mixed $value, array $types, array $validationRules = []): mixed
|
|
145
|
+
{
|
|
146
|
+
if (is_int($value)) {
|
|
147
|
+
return $value;
|
|
148
|
+
}
|
|
149
|
+
if (self::isIntegerLike($value)) {
|
|
150
|
+
return self::coerceInt($value, $validationRules);
|
|
151
|
+
}
|
|
152
|
+
return self::coerceString($value, $validationRules);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private static function coerceFloatStringUnion(mixed $value, array $types, array $validationRules = []): mixed
|
|
156
|
+
{
|
|
157
|
+
if (is_float($value)) {
|
|
158
|
+
return $value;
|
|
159
|
+
}
|
|
160
|
+
if (is_string($value) && is_numeric($value)) {
|
|
161
|
+
return self::coerceFloat($value, $validationRules);
|
|
162
|
+
}
|
|
163
|
+
return self::coerceString($value, $validationRules);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private static function coerceArrayStringUnion(mixed $value, array $types, array $validationRules = []): mixed
|
|
167
|
+
{
|
|
168
|
+
if (is_array($value)) {
|
|
169
|
+
return $value;
|
|
170
|
+
}
|
|
171
|
+
if (is_string($value) && self::isArrayLike($value)) {
|
|
172
|
+
return self::coerceArray($value, $validationRules);
|
|
173
|
+
}
|
|
174
|
+
return self::coerceString($value, $validationRules);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private static function coerceDateTimeUnion(mixed $value, array $types, array $validationRules = []): mixed
|
|
178
|
+
{
|
|
179
|
+
if ($value instanceof DateTimeInterface) {
|
|
180
|
+
return $value;
|
|
181
|
+
}
|
|
182
|
+
if (is_string($value) || is_numeric($value)) {
|
|
183
|
+
foreach ($types as $typeInfo) {
|
|
184
|
+
if (in_array($typeInfo['name'], ['DateTime', 'DateTimeImmutable', 'DateTimeInterface'])) {
|
|
185
|
+
$coerced = self::coerceCustomType($value, $typeInfo['name'], $validationRules);
|
|
186
|
+
if ($coerced !== $value) {
|
|
187
|
+
return $coerced;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return self::coerceString($value, $validationRules);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private static function coerceUnionTypePhpCompliant(mixed $value, array $types, array $validationRules = []): mixed
|
|
196
|
+
{
|
|
197
|
+
foreach ($types as $typeInfo) {
|
|
198
|
+
$coerced = self::coerceSingleType($value, $typeInfo, $validationRules);
|
|
199
|
+
if (self::isValidCoercion($value, $coerced, $typeInfo['name'])) {
|
|
200
|
+
return $coerced;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return $value;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private static function hasTypes(array $typeNames, array $requiredTypes): bool
|
|
207
|
+
{
|
|
208
|
+
foreach ($requiredTypes as $required) {
|
|
209
|
+
if (!in_array($required, $typeNames, true)) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private static function hasDateTimeTypes(array $typeNames): bool
|
|
217
|
+
{
|
|
218
|
+
$dateTimeTypes = ['DateTime', 'DateTimeImmutable', 'DateTimeInterface'];
|
|
219
|
+
return !empty(array_intersect($typeNames, $dateTimeTypes));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private static function isBooleanLike(mixed $value): bool
|
|
223
|
+
{
|
|
224
|
+
if (!is_string($value)) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
return in_array(strtolower(trim($value)), [
|
|
228
|
+
'true',
|
|
229
|
+
'false',
|
|
230
|
+
'1',
|
|
231
|
+
'0',
|
|
232
|
+
'yes',
|
|
233
|
+
'no',
|
|
234
|
+
'on',
|
|
235
|
+
'off',
|
|
236
|
+
'checked',
|
|
237
|
+
''
|
|
238
|
+
], true);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private static function isIntegerLike(mixed $value): bool
|
|
242
|
+
{
|
|
243
|
+
return is_string($value) && is_numeric($value) && (string)(int)$value === trim($value);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private static function isArrayLike(string $value): bool
|
|
247
|
+
{
|
|
248
|
+
return Validator::json($value) === true
|
|
249
|
+
|| str_contains($value, ',')
|
|
250
|
+
|| str_contains($value, '[')
|
|
251
|
+
|| str_contains($value, '{');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private static function getNormalizedType(mixed $value): string
|
|
255
|
+
{
|
|
256
|
+
$type = gettype($value);
|
|
257
|
+
return self::$phpTypeMap[$type] ?? $type;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private static function coerceSingleType(mixed $value, array $typeInfo, array $validationRules = []): mixed
|
|
261
|
+
{
|
|
262
|
+
if (!$typeInfo['isBuiltin']) {
|
|
263
|
+
return self::coerceCustomType($value, $typeInfo['name'], $validationRules);
|
|
264
|
+
}
|
|
265
|
+
return match ($typeInfo['name']) {
|
|
266
|
+
'bool' => self::coerceBool($value, $validationRules),
|
|
267
|
+
'int' => self::coerceInt($value, $validationRules),
|
|
268
|
+
'float' => self::coerceFloat($value, $validationRules),
|
|
269
|
+
'string' => self::coerceString($value, $validationRules),
|
|
270
|
+
'array' => self::coerceArray($value, $validationRules),
|
|
271
|
+
'object' => self::coerceObject($value, $validationRules),
|
|
272
|
+
'mixed' => $value,
|
|
273
|
+
default => $value,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private static function coerceBool(mixed $value, array $rules = []): mixed
|
|
278
|
+
{
|
|
279
|
+
$validated = Validator::boolean($value);
|
|
280
|
+
if ($validated !== null) {
|
|
281
|
+
return $validated;
|
|
282
|
+
}
|
|
283
|
+
if (is_string($value)) {
|
|
284
|
+
return match (strtolower(trim($value))) {
|
|
285
|
+
'true', '1', 'yes', 'on', 'checked' => true,
|
|
286
|
+
'false', '0', 'no', 'off', '' => false,
|
|
287
|
+
default => filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? false,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
return (bool) $value;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private static function coerceInt(mixed $value, array $rules = []): mixed
|
|
294
|
+
{
|
|
295
|
+
$validated = Validator::int($value);
|
|
296
|
+
return $validated ?? (int) $value;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private static function coerceFloat(mixed $value, array $rules = []): mixed
|
|
300
|
+
{
|
|
301
|
+
$validated = Validator::float($value);
|
|
302
|
+
return $validated ?? (float) $value;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private static function coerceString(mixed $value, array $rules = []): mixed
|
|
306
|
+
{
|
|
307
|
+
$escapeHtml = $rules['escapeHtml'] ?? true;
|
|
308
|
+
if (isset($rules['type'])) {
|
|
309
|
+
return match ($rules['type']) {
|
|
310
|
+
'email' => Validator::email($value) ?? Validator::string($value, $escapeHtml),
|
|
311
|
+
'url' => Validator::url($value) ?? Validator::string($value, $escapeHtml),
|
|
312
|
+
'uuid' => Validator::uuid($value) ?? Validator::string($value, $escapeHtml),
|
|
313
|
+
'ulid' => Validator::ulid($value) ?? Validator::string($value, $escapeHtml),
|
|
314
|
+
'cuid' => Validator::cuid($value) ?? Validator::string($value, $escapeHtml),
|
|
315
|
+
'cuid2' => Validator::cuid2($value) ?? Validator::string($value, $escapeHtml),
|
|
316
|
+
'ip' => Validator::ip($value) ?? Validator::string($value, $escapeHtml),
|
|
317
|
+
'html' => Validator::html(Validator::string($value, false)),
|
|
318
|
+
'emojis' => Validator::emojis(Validator::string($value, $escapeHtml)),
|
|
319
|
+
default => Validator::string($value, $escapeHtml),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
return Validator::string($value, $escapeHtml);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private static function coerceArray(mixed $value, array $rules = []): array
|
|
326
|
+
{
|
|
327
|
+
if (is_array($value)) {
|
|
328
|
+
return $value;
|
|
329
|
+
}
|
|
330
|
+
if (is_string($value)) {
|
|
331
|
+
if (Validator::json($value) === true) {
|
|
332
|
+
$decoded = json_decode($value, true);
|
|
333
|
+
if (is_array($decoded)) {
|
|
334
|
+
return $decoded;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (str_contains($value, ',')) {
|
|
338
|
+
return array_map('trim', explode(',', $value));
|
|
339
|
+
}
|
|
340
|
+
return [$value];
|
|
341
|
+
}
|
|
342
|
+
return (array) $value;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private static function coerceObject(mixed $value, array $rules = []): object
|
|
346
|
+
{
|
|
347
|
+
if (is_object($value)) {
|
|
348
|
+
return $value;
|
|
349
|
+
}
|
|
350
|
+
if (is_array($value)) {
|
|
351
|
+
return (object) $value;
|
|
352
|
+
}
|
|
353
|
+
if (is_string($value) && Validator::json($value)) {
|
|
354
|
+
$decoded = json_decode($value);
|
|
355
|
+
if (is_object($decoded)) {
|
|
356
|
+
return $decoded;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return (object) $value;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private static function coerceCustomType(mixed $value, string $typeName, array $rules = []): mixed
|
|
363
|
+
{
|
|
364
|
+
return match ($typeName) {
|
|
365
|
+
'DateTime' => self::coerceDateTime($value, $rules),
|
|
366
|
+
'DateTimeImmutable' => self::coerceDateTimeImmutable($value, $rules),
|
|
367
|
+
'DateTimeInterface' => self::coerceDateTimeInterface($value, $rules),
|
|
368
|
+
'BigDecimal' => self::coerceBigDecimal($value, $rules),
|
|
369
|
+
'BigInteger' => self::coerceBigInteger($value, $rules),
|
|
370
|
+
default => $value,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private static function coerceDateTime(mixed $value, array $rules = []): mixed
|
|
375
|
+
{
|
|
376
|
+
if ($value instanceof DateTime) {
|
|
377
|
+
return $value;
|
|
378
|
+
}
|
|
379
|
+
if ($value instanceof DateTimeImmutable) {
|
|
380
|
+
return DateTime::createFromImmutable($value);
|
|
381
|
+
}
|
|
382
|
+
$format = $rules['format'] ?? null;
|
|
383
|
+
try {
|
|
384
|
+
if ($format) {
|
|
385
|
+
return DateTime::createFromFormat($format, (string)$value) ?: $value;
|
|
386
|
+
} else {
|
|
387
|
+
return new DateTime((string)$value);
|
|
388
|
+
}
|
|
389
|
+
} catch (\Exception) {
|
|
390
|
+
return $value;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private static function coerceDateTimeImmutable(mixed $value, array $rules = []): mixed
|
|
395
|
+
{
|
|
396
|
+
if ($value instanceof DateTimeImmutable) {
|
|
397
|
+
return $value;
|
|
398
|
+
}
|
|
399
|
+
if ($value instanceof DateTime) {
|
|
400
|
+
return DateTimeImmutable::createFromMutable($value);
|
|
401
|
+
}
|
|
402
|
+
$format = $rules['format'] ?? null;
|
|
403
|
+
try {
|
|
404
|
+
if ($format) {
|
|
405
|
+
return DateTimeImmutable::createFromFormat($format, (string)$value) ?: $value;
|
|
406
|
+
} else {
|
|
407
|
+
return new DateTimeImmutable((string)$value);
|
|
408
|
+
}
|
|
409
|
+
} catch (\Exception) {
|
|
410
|
+
return $value;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private static function coerceDateTimeInterface(mixed $value, array $rules = []): mixed
|
|
415
|
+
{
|
|
416
|
+
if ($value instanceof DateTimeInterface) {
|
|
417
|
+
return $value;
|
|
418
|
+
}
|
|
419
|
+
return self::coerceDateTimeImmutable($value, $rules);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private static function coerceBigDecimal(mixed $value, array $rules = []): mixed
|
|
423
|
+
{
|
|
424
|
+
$scale = $rules['scale'] ?? 30;
|
|
425
|
+
return Validator::decimal($value, $scale) ?? $value;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
private static function coerceBigInteger(mixed $value, array $rules = []): mixed
|
|
429
|
+
{
|
|
430
|
+
return Validator::bigInt($value) ?? $value;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private static function isValidCoercion(mixed $original, mixed $coerced, string $typeName): bool
|
|
434
|
+
{
|
|
435
|
+
if (gettype($original) === gettype($coerced)) {
|
|
436
|
+
return match ($typeName) {
|
|
437
|
+
'string' => true,
|
|
438
|
+
'array' => $original !== $coerced,
|
|
439
|
+
default => $original === $coerced,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
return match ($typeName) {
|
|
443
|
+
'bool' => is_bool($coerced),
|
|
444
|
+
'int' => is_int($coerced),
|
|
445
|
+
'float' => is_float($coerced),
|
|
446
|
+
'string' => is_string($coerced),
|
|
447
|
+
'array' => is_array($coerced),
|
|
448
|
+
'object' => is_object($coerced),
|
|
449
|
+
'DateTime' => $coerced instanceof DateTime,
|
|
450
|
+
'DateTimeImmutable' => $coerced instanceof DateTimeImmutable,
|
|
451
|
+
'DateTimeInterface' => $coerced instanceof DateTimeInterface,
|
|
452
|
+
'BigDecimal' => $coerced instanceof BigDecimal,
|
|
453
|
+
'BigInteger' => $coerced instanceof BigInteger,
|
|
454
|
+
default => true,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
private static function getTypeKey(ReflectionType $type): string
|
|
459
|
+
{
|
|
460
|
+
if ($type instanceof ReflectionUnionType) {
|
|
461
|
+
$types = array_map(fn($t) => $t->getName(), $type->getTypes());
|
|
462
|
+
sort($types);
|
|
463
|
+
return 'union:' . implode('|', $types);
|
|
464
|
+
}
|
|
465
|
+
if ($type instanceof ReflectionNamedType) {
|
|
466
|
+
return 'named:' . $type->getName() . ($type->allowsNull() ? '|null' : '');
|
|
467
|
+
}
|
|
468
|
+
if ($type instanceof ReflectionIntersectionType) {
|
|
469
|
+
$types = array_map(function ($t) {
|
|
470
|
+
return $t instanceof ReflectionNamedType ? $t->getName() : (string) $t;
|
|
471
|
+
}, $type->getTypes());
|
|
472
|
+
sort($types);
|
|
473
|
+
return 'intersection:' . implode('&', $types);
|
|
474
|
+
}
|
|
475
|
+
return 'unknown:' . get_class($type);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
public static function clearCache(): void
|
|
479
|
+
{
|
|
480
|
+
self::$typeCache = [];
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
public static function getCacheStats(): array
|
|
484
|
+
{
|
|
485
|
+
return [
|
|
486
|
+
'type_cache_size' => count(self::$typeCache),
|
|
487
|
+
'cached_types' => array_keys(self::$typeCache),
|
|
488
|
+
];
|
|
489
|
+
}
|
|
490
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Lib;
|
|
6
|
+
|
|
7
|
+
use DOMDocument;
|
|
8
|
+
use DOMXPath;
|
|
9
|
+
use DOMElement;
|
|
10
|
+
|
|
11
|
+
final class PartialRenderer
|
|
12
|
+
{
|
|
13
|
+
/** @return array<string,string> selector => outerHTML */
|
|
14
|
+
public static function extract(string $html, array $selectors): array
|
|
15
|
+
{
|
|
16
|
+
$doc = new DOMDocument('1.0', 'UTF-8');
|
|
17
|
+
libxml_use_internal_errors(true);
|
|
18
|
+
$doc->loadHTML($html, LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_HTML_NOIMPLIED);
|
|
19
|
+
libxml_clear_errors();
|
|
20
|
+
|
|
21
|
+
$xpath = new DOMXPath($doc);
|
|
22
|
+
$payload = [];
|
|
23
|
+
|
|
24
|
+
foreach ($selectors as $rawSel) {
|
|
25
|
+
$sel = preg_replace('/[^-_\w]/', '', (string)$rawSel);
|
|
26
|
+
|
|
27
|
+
/** @var DOMElement|null $node */
|
|
28
|
+
$node = $xpath->query("//*[@pp-sync='{$sel}']")->item(0);
|
|
29
|
+
|
|
30
|
+
if (!$node && $sel !== '') {
|
|
31
|
+
$node = $xpath->query("//*[@id='{$sel}' or contains(concat(' ',normalize-space(@class),' '),' {$sel} ')]")->item(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if ($node) {
|
|
35
|
+
$payload[$sel] = $doc->saveHTML($node);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return $payload;
|
|
39
|
+
}
|
|
40
|
+
}
|