create-prisma-php-app 3.0.4 → 3.1.1

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.
@@ -14,11 +14,11 @@ use DOMText;
14
14
  use RuntimeException;
15
15
  use Bootstrap;
16
16
  use LibXMLError;
17
- use DOMXPath;
18
17
  use ReflectionClass;
19
18
  use ReflectionProperty;
20
19
  use ReflectionType;
21
20
  use ReflectionNamedType;
21
+ use Lib\PHPX\Exceptions\ComponentValidationException;
22
22
 
23
23
  class TemplateCompiler
24
24
  {
@@ -30,6 +30,11 @@ class TemplateCompiler
30
30
  'kbd' => true,
31
31
  'var' => true,
32
32
  ];
33
+ private const SYSTEM_PROPS = [
34
+ 'children' => true,
35
+ 'key' => true,
36
+ 'ref' => true,
37
+ ];
33
38
 
34
39
  protected static array $classMappings = [];
35
40
  protected static array $selfClosingTags = [
@@ -56,6 +61,7 @@ class TemplateCompiler
56
61
  private static array $reflections = [];
57
62
  private static array $constructors = [];
58
63
  private static array $publicProperties = [];
64
+ private static array $allowedProps = [];
59
65
 
60
66
  public static function compile(string $templateContent): string
61
67
  {
@@ -276,8 +282,8 @@ class TemplateCompiler
276
282
  $node->setAttribute('type', 'text/php');
277
283
  }
278
284
 
279
- if ($node->hasAttribute('pp-section-id')) {
280
- self::$sectionStack[] = $node->getAttribute('pp-section-id');
285
+ if ($node->hasAttribute('pp-component')) {
286
+ self::$sectionStack[] = $node->getAttribute('pp-component');
281
287
  $pushed = true;
282
288
  }
283
289
 
@@ -361,7 +367,6 @@ class TemplateCompiler
361
367
  string $componentName,
362
368
  array $incomingProps
363
369
  ): string {
364
- $incomingProps = self::sanitizeIncomingProps($incomingProps);
365
370
  $mapping = self::selectComponentMapping($componentName);
366
371
  $instance = self::initializeComponentInstance($mapping, $incomingProps);
367
372
 
@@ -370,7 +375,7 @@ class TemplateCompiler
370
375
  $childHtml .= self::processNode($c);
371
376
  }
372
377
 
373
- $instance->children = self::sanitizeEventAttributes($childHtml);
378
+ $instance->children = $childHtml;
374
379
 
375
380
  $baseId = 's' . base_convert(sprintf('%u', crc32($mapping['className'])), 10, 36);
376
381
  $idx = self::$componentInstanceCounts[$baseId] ?? 0;
@@ -379,55 +384,10 @@ class TemplateCompiler
379
384
 
380
385
  $html = $instance->render();
381
386
  $fragDom = self::convertToXml($html);
382
- $xpath = new DOMXPath($fragDom);
383
-
384
- /** @var DOMElement $el */
385
- foreach ($xpath->query('//*') as $el) {
386
-
387
- $tag = $el->tagName;
388
- if (ctype_upper($tag[0]) || isset(self::$classMappings[$tag])) {
389
- continue;
390
- }
391
-
392
- $originalEvents = [];
393
- $componentEvents = [];
394
-
395
- foreach (iterator_to_array($el->attributes) as $attr) {
396
- $name = $attr->name;
397
- $value = $attr->value;
398
-
399
- if (str_starts_with($name, 'pp-original-')) {
400
- $origName = substr($name, strlen('pp-original-'));
401
- $originalEvents[$origName] = $value;
402
- } elseif (str_starts_with($name, 'on')) {
403
- $event = substr($name, 2);
404
- if ($value !== '' && in_array($event, PrismaPHPSettings::$htmlEvents, true)) {
405
- $componentEvents[$name] = $value;
406
- }
407
- }
408
- }
409
-
410
- foreach (array_keys($originalEvents) as $k) $el->removeAttribute("pp-original-{$k}");
411
- foreach (array_keys($componentEvents) as $k) $el->removeAttribute($k);
412
-
413
- foreach ($componentEvents as $evAttr => $compValue) {
414
- $el->setAttribute("data-pp-child-{$evAttr}", $compValue);
415
-
416
- if (isset($originalEvents[$evAttr])) {
417
- $el->setAttribute("data-pp-parent-{$evAttr}", $originalEvents[$evAttr]);
418
- unset($originalEvents[$evAttr]);
419
- }
420
- }
421
-
422
- foreach ($originalEvents as $name => $value) {
423
- $el->setAttribute($name, $value);
424
- }
425
- }
426
-
427
387
  $root = $fragDom->documentElement;
428
388
  foreach ($root->childNodes as $c) {
429
389
  if ($c instanceof DOMElement) {
430
- $c->setAttribute('pp-phpx-id', $sectionId);
390
+ $c->setAttribute('pp-component', $sectionId);
431
391
  break;
432
392
  }
433
393
  }
@@ -444,53 +404,6 @@ class TemplateCompiler
444
404
  return $htmlOut;
445
405
  }
446
406
 
447
- protected static function sanitizeIncomingProps(array $props): array
448
- {
449
- foreach ($props as $key => $val) {
450
- if (str_starts_with($key, 'on')) {
451
- $event = substr($key, 2);
452
- if (in_array($event, PrismaPHPSettings::$htmlEvents, true) && trim((string)$val) !== '') {
453
- $props["pp-original-on{$event}"] = (string)$val;
454
- unset($props[$key]);
455
- }
456
- }
457
- }
458
-
459
- return $props;
460
- }
461
-
462
- protected static function sanitizeEventAttributes(string $html): string
463
- {
464
- $fragDom = self::convertToXml($html, false);
465
- $xpath = new DOMXPath($fragDom);
466
-
467
- /** @var DOMElement $el */
468
- foreach ($xpath->query('//*') as $el) {
469
- foreach (iterator_to_array($el->attributes) as $attr) {
470
- $name = strtolower($attr->name);
471
-
472
- if (!str_starts_with($name, 'on')) {
473
- continue;
474
- }
475
-
476
- $event = substr($name, 2);
477
- $value = trim($attr->value);
478
-
479
- if ($value !== '' && in_array($event, PrismaPHPSettings::$htmlEvents, true)) {
480
- $el->setAttribute("pp-original-on{$event}", $value);
481
- }
482
-
483
- $el->removeAttribute($name);
484
- }
485
- }
486
-
487
- $body = $fragDom->getElementsByTagName('body')[0] ?? null;
488
-
489
- return $body instanceof DOMElement
490
- ? self::innerXml($body)
491
- : self::innerXml($fragDom);
492
- }
493
-
494
407
  private static function selectComponentMapping(string $componentName): array
495
408
  {
496
409
  if (!isset(self::$classMappings[$componentName])) {
@@ -529,16 +442,20 @@ class TemplateCompiler
529
442
  throw new RuntimeException("Class {$className} not found");
530
443
  }
531
444
 
445
+ self::cacheClassReflection($className);
446
+
532
447
  if (!isset(self::$reflections[$className])) {
533
448
  $rc = new ReflectionClass($className);
534
449
  self::$reflections[$className] = $rc;
535
450
  self::$constructors[$className] = $rc->getConstructor();
536
451
  self::$publicProperties[$className] = array_filter(
537
452
  $rc->getProperties(ReflectionProperty::IS_PUBLIC),
538
- fn(ReflectionProperty $p) => ! $p->isStatic()
453
+ fn(ReflectionProperty $p) => !$p->isStatic()
539
454
  );
540
455
  }
541
456
 
457
+ self::validateComponentProps($className, $attributes);
458
+
542
459
  $ref = self::$reflections[$className];
543
460
  $ctor = self::$constructors[$className];
544
461
  $inst = $ref->newInstanceWithoutConstructor();
@@ -560,6 +477,51 @@ class TemplateCompiler
560
477
  return $inst;
561
478
  }
562
479
 
480
+ private static function cacheClassReflection(string $className): void
481
+ {
482
+ if (isset(self::$reflections[$className])) {
483
+ return;
484
+ }
485
+
486
+ $rc = new ReflectionClass($className);
487
+ self::$reflections[$className] = $rc;
488
+ self::$constructors[$className] = $rc->getConstructor();
489
+
490
+ $publicProps = array_filter(
491
+ $rc->getProperties(ReflectionProperty::IS_PUBLIC),
492
+ fn(ReflectionProperty $p) => !$p->isStatic()
493
+ );
494
+ self::$publicProperties[$className] = $publicProps;
495
+
496
+ $allowed = self::SYSTEM_PROPS;
497
+ foreach ($publicProps as $prop) {
498
+ $allowed[$prop->getName()] = true;
499
+ }
500
+ self::$allowedProps[$className] = $allowed;
501
+ }
502
+
503
+ private static function validateComponentProps(string $className, array $attributes): void
504
+ {
505
+ foreach (self::$publicProperties[$className] as $prop) {
506
+ $name = $prop->getName();
507
+ $type = $prop->getType();
508
+
509
+ if (
510
+ $type instanceof ReflectionNamedType && $type->isBuiltin()
511
+ && ! $type->allowsNull()
512
+ && ! array_key_exists($name, $attributes)
513
+ ) {
514
+ throw new ComponentValidationException(
515
+ $name,
516
+ $className,
517
+ array_map(fn($p) => $p->getName(), self::$publicProperties[$className])
518
+ );
519
+ }
520
+ }
521
+
522
+ return;
523
+ }
524
+
563
525
  private static function coerce(mixed $value, ?ReflectionType $type): mixed
564
526
  {
565
527
  if (!$type instanceof ReflectionNamedType || $type->isBuiltin() === false) {
@@ -92,13 +92,6 @@ class PrismaPHPSettings
92
92
  */
93
93
  public static string $localStoreKey;
94
94
 
95
- /**
96
- * The HTML events loaded from the html-events.json file.
97
- *
98
- * @var array
99
- */
100
- public static array $htmlEvents = [];
101
-
102
95
  public static function init(): void
103
96
  {
104
97
  self::$option = self::getPrismaSettings();
@@ -106,7 +99,6 @@ class PrismaPHPSettings
106
99
  self::$classLogFiles = self::getClassesLogFiles();
107
100
  self::$includeFiles = self::getIncludeFiles();
108
101
  self::$localStoreKey = self::getLocalStorageKey();
109
- self::$htmlEvents = self::getHtmlEvents();
110
102
  }
111
103
 
112
104
  /**
@@ -186,15 +178,4 @@ class PrismaPHPSettings
186
178
  $localStorageKey = $_ENV['LOCALSTORE_KEY'] ?? 'pphp_local_store_59e13';
187
179
  return strtolower(preg_replace('/\s+/', '_', trim($localStorageKey)));
188
180
  }
189
-
190
- private static function getHtmlEvents(): array
191
- {
192
- $path = SETTINGS_PATH . '/html-events.json';
193
- if (!file_exists($path)) {
194
- return [];
195
- }
196
-
197
- $events = json_decode(file_get_contents($path), true);
198
- return is_array($events) ? $events : [];
199
- }
200
181
  }
@@ -1,6 +1,6 @@
1
1
  <?php use Lib\ErrorHandler; ?>
2
2
 
3
- <div class="flex items-center justify-center h-screen">
3
+ <div class="flex items-center justify-center">
4
4
  <div class="text-center max-w-md">
5
5
  <h1 class="text-6xl font-bold text-red-500">Oops!</h1>
6
6
  <p class="text-xl mt-4"><?= ErrorHandler::$content ?></p>