create-prisma-php-app 3.1.5 → 3.1.6

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.
@@ -1,12 +1,8 @@
1
1
  <?php
2
2
 
3
- namespace Lib\PHPX;
3
+ declare(strict_types=1);
4
4
 
5
- /**
6
- * Interface IPHPX
7
- *
8
- * The interface for the PHPX component classes.
9
- */
5
+ namespace Lib\PHPX;
10
6
 
11
7
  interface IPHPX
12
8
  {
@@ -1,5 +1,7 @@
1
1
  <?php
2
2
 
3
+ declare(strict_types=1);
4
+
3
5
  namespace Lib\PHPX;
4
6
 
5
7
  use Lib\PHPX\IPHPX;
@@ -18,6 +18,7 @@ use ReflectionClass;
18
18
  use ReflectionProperty;
19
19
  use ReflectionType;
20
20
  use ReflectionNamedType;
21
+ use Lib\PHPX\TypeCoercer;
21
22
  use Lib\PHPX\Exceptions\ComponentValidationException;
22
23
 
23
24
  class TemplateCompiler
@@ -524,48 +525,7 @@ class TemplateCompiler
524
525
 
525
526
  private static function coerce(mixed $value, ?ReflectionType $type): mixed
526
527
  {
527
- if (!$type instanceof ReflectionNamedType || $type->isBuiltin() === false) {
528
- return $value;
529
- }
530
-
531
- $name = $type->getName();
532
- if ($value === '' && $name === 'bool') return true;
533
-
534
- return match ($name) {
535
- 'bool' => filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? false,
536
- 'int' => (int) $value,
537
- 'float' => (float) $value,
538
- 'array' => self::toArray($value),
539
-
540
- default => $value,
541
- };
542
- }
543
-
544
- private static function toArray(mixed $v): array
545
- {
546
- if (is_array($v)) {
547
- return $v;
548
- }
549
- if (is_string($v)) {
550
- $decoded = json_decode($v, true);
551
- if (is_array($decoded)) {
552
- return $decoded;
553
- }
554
- if (str_contains($v, ',')) {
555
- return array_map('trim', explode(',', $v));
556
- }
557
- return [$decoded ?? self::coerceScalarString($v)];
558
- }
559
- return [$v];
560
- }
561
-
562
- private static function coerceScalarString(string $s): mixed
563
- {
564
- return match (strtolower($s)) {
565
- 'true' => true,
566
- 'false' => false,
567
- default => $s,
568
- };
528
+ return TypeCoercer::coerce($value, $type);
569
529
  }
570
530
 
571
531
  protected static function initializeClassMappings(): void
@@ -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) ||
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)) {
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-prisma-php-app",
3
- "version": "3.1.5",
3
+ "version": "3.1.6",
4
4
  "description": "Prisma-PHP: A Revolutionary Library Bridging PHP with Prisma ORM",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",