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.
Files changed (59) hide show
  1. package/dist/.htaccess +54 -41
  2. package/dist/bootstrap.php +143 -98
  3. package/dist/index.js +264 -99
  4. package/dist/settings/auto-swagger-docs.ts +196 -95
  5. package/dist/settings/bs-config.ts +56 -58
  6. package/dist/settings/files-list.json +1 -1
  7. package/dist/settings/restart-mcp.ts +58 -0
  8. package/dist/settings/restart-websocket.ts +51 -45
  9. package/dist/settings/utils.ts +240 -0
  10. package/dist/src/Lib/AI/ChatGPTClient.php +147 -0
  11. package/dist/src/Lib/Auth/Auth.php +544 -0
  12. package/dist/src/Lib/Auth/AuthConfig.php +89 -0
  13. package/dist/src/Lib/CacheHandler.php +121 -0
  14. package/dist/src/Lib/ErrorHandler.php +322 -0
  15. package/dist/src/Lib/FileManager/UploadFile.php +383 -0
  16. package/dist/src/Lib/Headers/Boom.php +192 -0
  17. package/dist/src/Lib/IncludeTracker.php +59 -0
  18. package/dist/src/Lib/MCP/WeatherTools.php +104 -0
  19. package/dist/src/Lib/MCP/mcp-server.php +80 -0
  20. package/dist/src/Lib/MainLayout.php +230 -0
  21. package/dist/src/Lib/Middleware/AuthMiddleware.php +154 -0
  22. package/dist/src/Lib/Middleware/CorsMiddleware.php +145 -0
  23. package/dist/src/Lib/PHPMailer/Mailer.php +169 -0
  24. package/dist/src/Lib/PHPX/Exceptions/ComponentValidationException.php +49 -0
  25. package/dist/src/Lib/PHPX/Fragment.php +32 -0
  26. package/dist/src/Lib/PHPX/IPHPX.php +22 -0
  27. package/dist/src/Lib/PHPX/PHPX.php +287 -0
  28. package/dist/src/Lib/PHPX/TemplateCompiler.php +641 -0
  29. package/dist/src/Lib/PHPX/TwMerge.php +346 -0
  30. package/dist/src/Lib/PHPX/TypeCoercer.php +490 -0
  31. package/dist/src/Lib/PartialRenderer.php +40 -0
  32. package/dist/src/Lib/PrismaPHPSettings.php +181 -0
  33. package/dist/src/Lib/Request.php +479 -0
  34. package/dist/src/Lib/Security/RateLimiter.php +33 -0
  35. package/dist/src/Lib/Set.php +102 -0
  36. package/dist/src/Lib/StateManager.php +127 -0
  37. package/dist/src/Lib/Validator.php +752 -0
  38. package/dist/src/{Websocket → Lib/Websocket}/ConnectionManager.php +1 -1
  39. package/dist/src/Lib/Websocket/websocket-server.php +118 -0
  40. package/dist/src/app/error.php +1 -1
  41. package/dist/src/app/index.php +24 -5
  42. package/dist/src/app/js/index.js +1 -1
  43. package/dist/src/app/layout.php +2 -2
  44. package/package.json +1 -1
  45. package/dist/settings/restart-websocket.bat +0 -28
  46. package/dist/src/app/assets/images/prisma-php-black.svg +0 -6
  47. package/dist/websocket-server.php +0 -22
  48. package/vendor/autoload.php +0 -25
  49. package/vendor/composer/ClassLoader.php +0 -579
  50. package/vendor/composer/InstalledVersions.php +0 -359
  51. package/vendor/composer/LICENSE +0 -21
  52. package/vendor/composer/autoload_classmap.php +0 -10
  53. package/vendor/composer/autoload_namespaces.php +0 -9
  54. package/vendor/composer/autoload_psr4.php +0 -10
  55. package/vendor/composer/autoload_real.php +0 -38
  56. package/vendor/composer/autoload_static.php +0 -25
  57. package/vendor/composer/installed.json +0 -825
  58. package/vendor/composer/installed.php +0 -132
  59. package/vendor/composer/platform_check.php +0 -26
@@ -0,0 +1,641 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Lib\PHPX;
6
+
7
+ use Lib\PrismaPHPSettings;
8
+ use Lib\MainLayout;
9
+ use DOMDocument;
10
+ use DOMElement;
11
+ use DOMComment;
12
+ use DOMNode;
13
+ use DOMText;
14
+ use RuntimeException;
15
+ use Bootstrap;
16
+ use LibXMLError;
17
+ use ReflectionClass;
18
+ use ReflectionProperty;
19
+ use ReflectionType;
20
+ use ReflectionNamedType;
21
+ use Lib\PHPX\TypeCoercer;
22
+ use Lib\PHPX\Exceptions\ComponentValidationException;
23
+
24
+ class TemplateCompiler
25
+ {
26
+ protected const BINDING_REGEX = '/\{\{\s*((?:(?!\{\{|\}\})[\s\S])*?)\s*\}\}/uS';
27
+ private const LITERAL_TEXT_TAGS = [
28
+ 'code' => true,
29
+ 'pre' => true,
30
+ 'samp' => true,
31
+ 'kbd' => true,
32
+ 'var' => true,
33
+ ];
34
+ private const SYSTEM_PROPS = [
35
+ 'children' => true,
36
+ 'key' => true,
37
+ 'ref' => true,
38
+ ];
39
+
40
+ protected static array $classMappings = [];
41
+ protected static array $selfClosingTags = [
42
+ 'area',
43
+ 'base',
44
+ 'br',
45
+ 'col',
46
+ 'command',
47
+ 'embed',
48
+ 'hr',
49
+ 'img',
50
+ 'input',
51
+ 'keygen',
52
+ 'link',
53
+ 'meta',
54
+ 'param',
55
+ 'source',
56
+ 'track',
57
+ 'wbr'
58
+ ];
59
+ private static array $sectionStack = [];
60
+ private static int $compileDepth = 0;
61
+ private static array $componentInstanceCounts = [];
62
+ private static array $reflections = [];
63
+ private static array $constructors = [];
64
+ private static array $publicProperties = [];
65
+ private static array $allowedProps = [];
66
+
67
+ public static function compile(string $templateContent): string
68
+ {
69
+ if (self::$compileDepth === 0) {
70
+ self::$componentInstanceCounts = [];
71
+ }
72
+ self::$compileDepth++;
73
+
74
+ if (empty(self::$classMappings)) {
75
+ self::initializeClassMappings();
76
+ }
77
+
78
+ $dom = self::convertToXml($templateContent);
79
+ $root = $dom->documentElement;
80
+
81
+ $output = [];
82
+ foreach ($root->childNodes as $child) {
83
+ $output[] = self::processNode($child);
84
+ }
85
+
86
+ self::$compileDepth--;
87
+ return implode('', $output);
88
+ }
89
+
90
+ public static function injectDynamicContent(string $htmlContent): string
91
+ {
92
+ $headOpenPattern = '/(<head\b[^>]*>)/i';
93
+
94
+ $htmlContent = preg_replace(
95
+ $headOpenPattern,
96
+ '$1' . MainLayout::outputMetadata(),
97
+ $htmlContent,
98
+ 1
99
+ );
100
+
101
+ $headClosePattern = '/(<\/head\s*>)/i';
102
+ $headScripts = MainLayout::outputHeadScripts();
103
+ $htmlContent = preg_replace(
104
+ $headClosePattern,
105
+ $headScripts . '$1',
106
+ $htmlContent,
107
+ 1
108
+ );
109
+
110
+ if (!isset($_SERVER['HTTP_X_PPHP_NAVIGATION'])) {
111
+ if (!PrismaPHPSettings::$option->backendOnly) {
112
+ $htmlContent = preg_replace(
113
+ '/<body([^>]*)>/i',
114
+ '<body$1 hidden>',
115
+ $htmlContent,
116
+ 1
117
+ );
118
+ }
119
+ }
120
+
121
+ $bodyClosePattern = '/(<\/body\s*>)/i';
122
+
123
+ $htmlContent = preg_replace(
124
+ $bodyClosePattern,
125
+ MainLayout::outputFooterScripts() . '$1',
126
+ $htmlContent,
127
+ 1
128
+ );
129
+
130
+ return $htmlContent;
131
+ }
132
+
133
+ private static function escapeAmpersands(string $content): string
134
+ {
135
+ return preg_replace(
136
+ '/&(?![a-zA-Z][A-Za-z0-9]*;|#[0-9]+;|#x[0-9A-Fa-f]+;)/',
137
+ '&amp;',
138
+ $content
139
+ );
140
+ }
141
+
142
+ private static function escapeAttributeAngles(string $html): string
143
+ {
144
+ return preg_replace_callback(
145
+ '/(\s[\w:-]+=)([\'"])(.*?)\2/s',
146
+ fn($m) => $m[1] . $m[2] . str_replace(['<', '>'], ['&lt;', '&gt;'], $m[3]) . $m[2],
147
+ $html
148
+ );
149
+ }
150
+
151
+ private static function escapeMustacheAngles(string $content): string
152
+ {
153
+ return preg_replace_callback(
154
+ '/\{\{[\s\S]*?\}\}/u',
155
+ fn($m) => str_replace(['<', '>'], ['&lt;', '&gt;'], $m[0]),
156
+ $content
157
+ );
158
+ }
159
+
160
+ public static function convertToXml(string $templateContent): DOMDocument
161
+ {
162
+ $content = self::protectInlineScripts($templateContent);
163
+ $content = self::normalizeNamedEntities($content);
164
+
165
+ $content = self::escapeAmpersands($content);
166
+ $content = self::escapeAttributeAngles($content);
167
+ $content = self::escapeMustacheAngles($content);
168
+
169
+ $xml = "<root>{$content}</root>";
170
+
171
+ $dom = new DOMDocument('1.0', 'UTF-8');
172
+ libxml_use_internal_errors(true);
173
+ if (!$dom->loadXML($xml, LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_NONET)) {
174
+ throw new RuntimeException(
175
+ 'XML Parsing Failed: ' . implode('; ', self::getXmlErrors())
176
+ );
177
+ }
178
+ libxml_clear_errors();
179
+ libxml_use_internal_errors(false);
180
+ return $dom;
181
+ }
182
+
183
+ private static function normalizeNamedEntities(string $html): string
184
+ {
185
+ return preg_replace_callback(
186
+ '/&([a-zA-Z][a-zA-Z0-9]+);/',
187
+ static function (array $m): string {
188
+ $decoded = html_entity_decode($m[0], ENT_HTML5, 'UTF-8');
189
+
190
+ if ($decoded === $m[0]) {
191
+ return $m[0];
192
+ }
193
+
194
+ if (function_exists('mb_ord')) {
195
+ return '&#' . mb_ord($decoded, 'UTF-8') . ';';
196
+ }
197
+
198
+ $code = unpack('N', mb_convert_encoding($decoded, 'UCS-4BE', 'UTF-8'))[1];
199
+ return '&#' . $code . ';';
200
+ },
201
+ $html
202
+ );
203
+ }
204
+
205
+ private static function protectInlineScripts(string $html): string
206
+ {
207
+ if (stripos($html, '<script') === false) {
208
+ return $html;
209
+ }
210
+
211
+ $processScripts = static function (string $content): string {
212
+ $callback = static function (array $m): string {
213
+ if (preg_match('/\bsrc\s*=/i', $m[1])) {
214
+ return $m[0];
215
+ }
216
+
217
+ if (str_contains($m[2], '<![CDATA[')) {
218
+ return $m[0];
219
+ }
220
+
221
+ $type = '';
222
+ if (preg_match('/\btype\s*=\s*([\'"]?)([^\'"\s>]+)/i', $m[1], $t)) {
223
+ $type = strtolower($t[2]);
224
+ }
225
+
226
+ $codeTypes = [
227
+ '',
228
+ 'text/javascript',
229
+ 'application/javascript',
230
+ 'module',
231
+ 'text/php',
232
+ ];
233
+
234
+ if (!in_array($type, $codeTypes, true)) {
235
+ return $m[0];
236
+ }
237
+
238
+ $code = str_replace(']]>', ']]]]><![CDATA[>', $m[2]);
239
+
240
+ return "<script{$m[1]}><![CDATA[\n{$code}\n]]></script>";
241
+ };
242
+
243
+ $result = preg_replace_callback(
244
+ '#<script\b([^>]*?)>(.*?)</script>#is',
245
+ $callback,
246
+ $content
247
+ );
248
+
249
+ if ($result === null) {
250
+ $result = preg_replace_callback(
251
+ '#<script\b([^>]*?)>(.*?)</script>#is',
252
+ $callback,
253
+ $content
254
+ );
255
+
256
+ return $result ?? $content;
257
+ }
258
+
259
+ return $result;
260
+ };
261
+
262
+ if (preg_match('/^(.*?<body\b[^>]*>)(.*?)(<\/body>.*)$/is', $html, $parts)) {
263
+ [$all, $beforeBody, $body, $afterBody] = $parts;
264
+ return $beforeBody . $processScripts($body) . $afterBody;
265
+ }
266
+
267
+ return $processScripts($html);
268
+ }
269
+
270
+ public static function innerXml(DOMNode $node): string
271
+ {
272
+ if ($node instanceof DOMDocument) {
273
+ $node = $node->documentElement;
274
+ }
275
+
276
+ /** @var DOMDocument $doc */
277
+ $doc = $node->ownerDocument;
278
+
279
+ $html = '';
280
+ foreach ($node->childNodes as $child) {
281
+ $html .= $doc->saveXML($child);
282
+ }
283
+ return $html;
284
+ }
285
+
286
+ protected static function getXmlErrors(): array
287
+ {
288
+ $errors = libxml_get_errors();
289
+ libxml_clear_errors();
290
+ return array_map(fn($e) => self::formatLibxmlError($e), $errors);
291
+ }
292
+
293
+ protected static function formatLibxmlError(LibXMLError $error): string
294
+ {
295
+ $type = match ($error->level) {
296
+ LIBXML_ERR_WARNING => 'Warning',
297
+ LIBXML_ERR_ERROR => 'Error',
298
+ LIBXML_ERR_FATAL => 'Fatal',
299
+ default => 'Unknown',
300
+ };
301
+ return sprintf(
302
+ "[%s] Line %d, Col %d: %s",
303
+ $type,
304
+ $error->line,
305
+ $error->column,
306
+ trim($error->message)
307
+ );
308
+ }
309
+
310
+ protected static function processNode(DOMNode $node): string
311
+ {
312
+ if ($node instanceof DOMText) {
313
+ return self::processTextNode($node);
314
+ }
315
+
316
+ if ($node instanceof DOMElement) {
317
+ $pushed = false;
318
+ $tag = strtolower($node->nodeName);
319
+
320
+ if (
321
+ $tag === 'script' &&
322
+ !$node->hasAttribute('src') &&
323
+ !$node->hasAttribute('type')
324
+ ) {
325
+ $node->setAttribute('type', 'text/php');
326
+ }
327
+
328
+ if ($node->hasAttribute('pp-component')) {
329
+ self::$sectionStack[] = $node->getAttribute('pp-component');
330
+ $pushed = true;
331
+ }
332
+
333
+ self::processAttributes($node);
334
+
335
+ if (isset(self::$classMappings[$node->nodeName])) {
336
+ $html = self::renderComponent(
337
+ $node,
338
+ $node->nodeName,
339
+ self::getNodeAttributes($node)
340
+ );
341
+ if ($pushed) {
342
+ array_pop(self::$sectionStack);
343
+ }
344
+ return $html;
345
+ }
346
+
347
+ $children = '';
348
+ foreach ($node->childNodes as $c) {
349
+ $children .= self::processNode($c);
350
+ }
351
+ $attrs = self::getNodeAttributes($node) + ['children' => $children];
352
+ $out = self::renderAsHtml($node->nodeName, $attrs);
353
+
354
+ if ($pushed) {
355
+ array_pop(self::$sectionStack);
356
+ }
357
+ return $out;
358
+ }
359
+
360
+ if ($node instanceof DOMComment) {
361
+ return "<!--{$node->textContent}-->";
362
+ }
363
+
364
+ return $node->textContent;
365
+ }
366
+
367
+ private static function processTextNode(DOMText $node): string
368
+ {
369
+ $parent = strtolower($node->parentNode?->nodeName ?? '');
370
+ if (isset(self::LITERAL_TEXT_TAGS[$parent])) {
371
+ return htmlspecialchars(
372
+ $node->textContent,
373
+ ENT_NOQUOTES | ENT_SUBSTITUTE,
374
+ 'UTF-8'
375
+ );
376
+ }
377
+
378
+ return preg_replace_callback(
379
+ self::BINDING_REGEX,
380
+ fn($m) => self::processBindingExpression(trim($m[1])),
381
+ $node->textContent
382
+ );
383
+ }
384
+
385
+ private static function processAttributes(DOMElement $node): void
386
+ {
387
+ foreach ($node->attributes as $a) {
388
+ if (!preg_match(self::BINDING_REGEX, $a->value, $m)) {
389
+ continue;
390
+ }
391
+
392
+ $rawExpr = trim($m[1]);
393
+ $node->setAttribute("pp-bind-{$a->name}", $rawExpr);
394
+ }
395
+ }
396
+
397
+ private static function processBindingExpression(string $expr): string
398
+ {
399
+ $escaped = htmlspecialchars($expr, ENT_QUOTES, 'UTF-8');
400
+
401
+ if (preg_match('/^[\w.]+$/u', $expr)) {
402
+ return "<span pp-bind=\"{$escaped}\"></span>";
403
+ }
404
+
405
+ return "<span pp-bind-expr=\"{$escaped}\"></span>";
406
+ }
407
+
408
+ protected static function renderComponent(
409
+ DOMElement $node,
410
+ string $componentName,
411
+ array $incomingProps
412
+ ): string {
413
+ $mapping = self::selectComponentMapping($componentName);
414
+
415
+ $baseId = 's' . base_convert(sprintf('%u', crc32($mapping['className'])), 10, 36);
416
+ $idx = self::$componentInstanceCounts[$baseId] ?? 0;
417
+ self::$componentInstanceCounts[$baseId] = $idx + 1;
418
+ $sectionId = $idx === 0 ? $baseId : "{$baseId}{$idx}";
419
+
420
+ $originalStack = self::$sectionStack;
421
+ self::$sectionStack[] = $sectionId;
422
+
423
+ PHPX::setRenderingContext($originalStack, $sectionId);
424
+
425
+ $instance = self::initializeComponentInstance($mapping, $incomingProps);
426
+
427
+ $childHtml = '';
428
+ foreach ($node->childNodes as $c) {
429
+ $childHtml .= self::processNode($c);
430
+ }
431
+
432
+ self::$sectionStack = $originalStack;
433
+
434
+ $instance->children = trim($childHtml);
435
+
436
+ PHPX::setRenderingContext($originalStack, $sectionId);
437
+
438
+ $html = $instance->render();
439
+ $html = self::preprocessFragmentSyntax($html);
440
+
441
+ $fragDom = self::convertToXml($html);
442
+ $root = $fragDom->documentElement;
443
+ foreach ($root->childNodes as $c) {
444
+ if ($c instanceof DOMElement) {
445
+ $c->setAttribute('pp-component', $sectionId);
446
+ break;
447
+ }
448
+ }
449
+
450
+ $htmlOut = self::innerXml($fragDom);
451
+ $htmlOut = preg_replace_callback(
452
+ '/<([a-z0-9-]+)([^>]*)\/>/i',
453
+ fn($m) => in_array(strtolower($m[1]), self::$selfClosingTags, true)
454
+ ? $m[0]
455
+ : "<{$m[1]}{$m[2]}></{$m[1]}>",
456
+ $htmlOut
457
+ );
458
+
459
+ if (
460
+ str_contains($htmlOut, '{{') ||
461
+ self::hasComponentTag($htmlOut) ||
462
+ stripos($htmlOut, '<script') !== false
463
+ ) {
464
+ $htmlOut = self::compile($htmlOut);
465
+ }
466
+
467
+ return $htmlOut;
468
+ }
469
+
470
+ private static function preprocessFragmentSyntax(string $content): string
471
+ {
472
+ $content = preg_replace('/<>/', '<Fragment>', $content);
473
+ $content = preg_replace('/<\/>/', '</Fragment>', $content);
474
+
475
+ return $content;
476
+ }
477
+
478
+ private static function selectComponentMapping(string $componentName): array
479
+ {
480
+ if (!isset(self::$classMappings[$componentName])) {
481
+ throw new RuntimeException("Component {$componentName} not registered");
482
+ }
483
+ $mappings = self::$classMappings[$componentName];
484
+
485
+ $srcNorm = str_replace('\\', '/', SRC_PATH) . '/';
486
+ $relImp = str_replace($srcNorm, '', str_replace('\\', '/', Bootstrap::$contentToInclude));
487
+
488
+ if (isset($mappings[0]) && is_array($mappings[0])) {
489
+ foreach ($mappings as $entry) {
490
+ $imp = isset($entry['importer'])
491
+ ? str_replace('\\', '/', $entry['importer'])
492
+ : '';
493
+ if (str_replace($srcNorm, '', $imp) === $relImp) {
494
+ return $entry;
495
+ }
496
+ }
497
+ return $mappings[0];
498
+ }
499
+ return $mappings;
500
+ }
501
+
502
+ protected static function initializeComponentInstance(array $mapping, array $attributes)
503
+ {
504
+ if (!isset($mapping['className'], $mapping['filePath'])) {
505
+ throw new RuntimeException("Invalid mapping");
506
+ }
507
+
508
+ $className = $mapping['className'];
509
+ $filePath = $mapping['filePath'];
510
+
511
+ require_once str_replace('\\', '/', SRC_PATH . '/' . $filePath);
512
+ if (!class_exists($className)) {
513
+ throw new RuntimeException("Class {$className} not found");
514
+ }
515
+
516
+ self::cacheClassReflection($className);
517
+
518
+ if (!isset(self::$reflections[$className])) {
519
+ $rc = new ReflectionClass($className);
520
+ self::$reflections[$className] = $rc;
521
+ self::$constructors[$className] = $rc->getConstructor();
522
+ self::$publicProperties[$className] = array_filter(
523
+ $rc->getProperties(ReflectionProperty::IS_PUBLIC),
524
+ fn(ReflectionProperty $p) => !$p->isStatic()
525
+ );
526
+ }
527
+
528
+ self::validateComponentProps($className, $attributes);
529
+
530
+ $ref = self::$reflections[$className];
531
+ $ctor = self::$constructors[$className];
532
+ $inst = $ref->newInstanceWithoutConstructor();
533
+
534
+ foreach (self::$publicProperties[$className] as $prop) {
535
+ $name = $prop->getName();
536
+
537
+ if (!array_key_exists($name, $attributes)) {
538
+ continue;
539
+ }
540
+ $value = self::coerce($attributes[$name], $prop->getType());
541
+ $prop->setValue($inst, $value);
542
+ }
543
+
544
+ if ($ctor) {
545
+ $ctor->invoke($inst, $attributes);
546
+ }
547
+
548
+ return $inst;
549
+ }
550
+
551
+ private static function cacheClassReflection(string $className): void
552
+ {
553
+ if (isset(self::$reflections[$className])) {
554
+ return;
555
+ }
556
+
557
+ $rc = new ReflectionClass($className);
558
+ self::$reflections[$className] = $rc;
559
+ self::$constructors[$className] = $rc->getConstructor();
560
+
561
+ $publicProps = array_filter(
562
+ $rc->getProperties(ReflectionProperty::IS_PUBLIC),
563
+ fn(ReflectionProperty $p) => !$p->isStatic()
564
+ );
565
+ self::$publicProperties[$className] = $publicProps;
566
+
567
+ $allowed = self::SYSTEM_PROPS;
568
+ foreach ($publicProps as $prop) {
569
+ $allowed[$prop->getName()] = true;
570
+ }
571
+ self::$allowedProps[$className] = $allowed;
572
+ }
573
+
574
+ private static function validateComponentProps(string $className, array $attributes): void
575
+ {
576
+ foreach (self::$publicProperties[$className] as $prop) {
577
+ $name = $prop->getName();
578
+ $type = $prop->getType();
579
+
580
+ if (
581
+ $type instanceof ReflectionNamedType && $type->isBuiltin()
582
+ && ! $type->allowsNull()
583
+ && ! array_key_exists($name, $attributes)
584
+ ) {
585
+ throw new ComponentValidationException(
586
+ $name,
587
+ $className,
588
+ array_map(fn($p) => $p->getName(), self::$publicProperties[$className])
589
+ );
590
+ }
591
+ }
592
+
593
+ return;
594
+ }
595
+
596
+ private static function coerce(mixed $value, ?ReflectionType $type): mixed
597
+ {
598
+ return TypeCoercer::coerce($value, $type);
599
+ }
600
+
601
+ protected static function initializeClassMappings(): void
602
+ {
603
+ foreach (PrismaPHPSettings::$classLogFiles as $tag => $cls) {
604
+ self::$classMappings[$tag] = $cls;
605
+ }
606
+ }
607
+
608
+ protected static function hasComponentTag(string $html): bool
609
+ {
610
+ return preg_match('/<\/*[A-Z][\w-]*/u', $html) === 1;
611
+ }
612
+
613
+ private static function getNodeAttributes(DOMElement $node): array
614
+ {
615
+ $out = [];
616
+ foreach ($node->attributes as $a) {
617
+ $out[$a->name] = $a->value;
618
+ }
619
+ return $out;
620
+ }
621
+
622
+ private static function renderAsHtml(string $tag, array $attrs): string
623
+ {
624
+ $pairs = [];
625
+ foreach ($attrs as $k => $v) {
626
+ if ($k === 'children') {
627
+ continue;
628
+ }
629
+ $pairs[] = sprintf(
630
+ '%s="%s"',
631
+ $k,
632
+ htmlspecialchars($v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
633
+ );
634
+ }
635
+ $attrStr = $pairs ? ' ' . implode(' ', $pairs) : '';
636
+
637
+ return in_array(strtolower($tag), self::$selfClosingTags, true)
638
+ ? "<{$tag}{$attrStr} />"
639
+ : "<{$tag}{$attrStr}>{$attrs['children']}</{$tag}>";
640
+ }
641
+ }