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.
@@ -22,7 +22,7 @@ use Lib\ErrorHandler;
22
22
  use Firebase\JWT\JWT;
23
23
  use Firebase\JWT\Key;
24
24
 
25
- final class Bootstrap
25
+ final class Bootstrap extends RuntimeException
26
26
  {
27
27
  public static string $contentToInclude = '';
28
28
  public static array $layoutsToInclude = [];
@@ -35,12 +35,22 @@ final class Bootstrap
35
35
  public static bool $secondRequestC69CD = false;
36
36
  public static array $requestFilesData = [];
37
37
 
38
+ private string $context;
39
+
38
40
  private static array $fileExistCache = [];
39
41
  private static array $regexCache = [];
40
42
 
41
- /**
42
- * Main entry point to run the entire routing and rendering logic.
43
- */
43
+ public function __construct(string $message, string $context = '', int $code = 0, ?Throwable $previous = null)
44
+ {
45
+ $this->context = $context;
46
+ parent::__construct($message, $code, $previous);
47
+ }
48
+
49
+ public function getContext(): string
50
+ {
51
+ return $this->context;
52
+ }
53
+
44
54
  public static function run(): void
45
55
  {
46
56
  // Load environment variables
@@ -807,6 +817,10 @@ final class Bootstrap
807
817
 
808
818
  public static function createUpdateRequestData(): void
809
819
  {
820
+ if (Bootstrap::$contentToInclude === '') {
821
+ return;
822
+ }
823
+
810
824
  $requestJsonData = SETTINGS_PATH . '/request-data.json';
811
825
 
812
826
  if (file_exists($requestJsonData)) {
@@ -951,6 +965,9 @@ try {
951
965
  ob_start();
952
966
  require_once APP_PATH . '/not-found.php';
953
967
  MainLayout::$childLayoutChildren = ob_get_clean();
968
+
969
+ http_response_code(404);
970
+ CacheHandler::$isCacheable = false;
954
971
  }
955
972
 
956
973
  // If the top-level layout is in use
@@ -1001,7 +1018,9 @@ try {
1001
1018
  MainLayout::$html = TemplateCompiler::injectDynamicContent(MainLayout::$html);
1002
1019
  MainLayout::$html = "<!DOCTYPE html>\n" . MainLayout::$html;
1003
1020
 
1004
- if (isset(Bootstrap::$requestFilesData[Request::$decodedUri]['fileName']) && $_ENV['CACHE_ENABLED'] === 'true') {
1021
+ if (
1022
+ http_response_code() === 200 && isset(Bootstrap::$requestFilesData[Request::$decodedUri]['fileName']) && $_ENV['CACHE_ENABLED'] === 'true'
1023
+ ) {
1005
1024
  CacheHandler::saveCache(Request::$decodedUri, MainLayout::$html);
1006
1025
  }
1007
1026
 
@@ -1025,15 +1044,18 @@ try {
1025
1044
  }
1026
1045
  } catch (Throwable $e) {
1027
1046
  if (Bootstrap::isAjaxOrXFileRequestOrRouteFile()) {
1028
- $errorDetails = "Unhandled Exception: " . $e->getMessage() .
1029
- " in " . $e->getFile() .
1030
- " on line " . $e->getLine();
1047
+ $errorDetails = json_encode([
1048
+ 'success' => false,
1049
+ 'error' => [
1050
+ 'type' => get_class($e),
1051
+ 'message' => $e->getMessage(),
1052
+ 'file' => $e->getFile(),
1053
+ 'line' => $e->getLine(),
1054
+ 'trace' => $e->getTraceAsString()
1055
+ ]
1056
+ ]);
1031
1057
  } else {
1032
- $errorDetails = "Unhandled Exception: " . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
1033
- $errorDetails .= "<br>File: " . htmlspecialchars($e->getFile(), ENT_QUOTES, 'UTF-8');
1034
- $errorDetails .= "<br>Line: " . htmlspecialchars((string)$e->getLine(), ENT_QUOTES, 'UTF-8');
1035
- $errorDetails .= "<br/>TraceAsString: " . htmlspecialchars($e->getTraceAsString(), ENT_QUOTES, 'UTF-8');
1036
- $errorDetails = "<div class='error'>{$errorDetails}</div>";
1058
+ $errorDetails = ErrorHandler::formatExceptionForDisplay($e);
1037
1059
  }
1038
1060
  ErrorHandler::modifyOutputLayoutForError($errorDetails);
1039
1061
  }
@@ -4,6 +4,8 @@ namespace Lib;
4
4
 
5
5
  use Bootstrap;
6
6
  use Lib\MainLayout;
7
+ use Throwable;
8
+ use Lib\PHPX\Exceptions\ComponentValidationException;
7
9
 
8
10
  class ErrorHandler
9
11
  {
@@ -123,4 +125,196 @@ class ErrorHandler
123
125
  }
124
126
  exit;
125
127
  }
128
+
129
+ public static function formatExceptionForDisplay(Throwable $exception): string
130
+ {
131
+ // Handle specific exception types
132
+ if ($exception instanceof ComponentValidationException) {
133
+ return self::formatComponentValidationError($exception);
134
+ }
135
+
136
+ // Handle template compilation errors specifically
137
+ if (strpos($exception->getMessage(), 'Invalid prop') !== false) {
138
+ return self::formatTemplateCompilerError($exception);
139
+ }
140
+
141
+ // Generic exception formatting
142
+ return self::formatGenericException($exception);
143
+ }
144
+
145
+ private static function formatComponentValidationError(ComponentValidationException $exception): string
146
+ {
147
+ $message = htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8');
148
+ $file = htmlspecialchars($exception->getFile(), ENT_QUOTES, 'UTF-8');
149
+ $line = $exception->getLine();
150
+
151
+ // Get the details from the ComponentValidationException
152
+ $propName = method_exists($exception, 'getPropName') ? $exception->getPropName() : 'unknown';
153
+ $componentName = method_exists($exception, 'getComponentName') ? $exception->getComponentName() : 'unknown';
154
+ $availableProps = method_exists($exception, 'getAvailableProps') ? $exception->getAvailableProps() : [];
155
+
156
+ $availablePropsString = !empty($availableProps) ? implode(', ', $availableProps) : 'none defined';
157
+
158
+ return <<<HTML
159
+ <div class="error-container max-w-4xl mx-auto mt-8 bg-red-50 border border-red-200 rounded-lg shadow-lg">
160
+ <div class="bg-red-100 px-6 py-4 border-b border-red-200">
161
+ <h2 class="text-xl font-bold text-red-800 flex items-center">
162
+ <svg class="w-6 h-6 mr-2" fill="currentColor" viewBox="0 0 20 20">
163
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
164
+ </svg>
165
+ Component Validation Error
166
+ </h2>
167
+ </div>
168
+
169
+ <div class="p-6">
170
+ <div class="bg-white border border-red-200 rounded-lg p-4 mb-4">
171
+ <div class="mb-3">
172
+ <span class="inline-block bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm font-medium">
173
+ Component: {$componentName}
174
+ </span>
175
+ <span class="inline-block bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm font-medium ml-2">
176
+ Invalid Prop: {$propName}
177
+ </span>
178
+ </div>
179
+ <pre class="text-sm text-red-800 whitespace-pre-wrap font-mono">{$message}</pre>
180
+ </div>
181
+
182
+ <div class="text-sm text-gray-600 mb-4">
183
+ <strong>File:</strong> <code class="bg-gray-100 px-2 py-1 rounded text-xs">{$file}</code><br />
184
+ <strong>Line:</strong> <span class="bg-gray-100 px-2 py-1 rounded text-xs">{$line}</span>
185
+ </div>
186
+
187
+ <div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4">
188
+ <h4 class="font-medium text-blue-800 mb-2">💡 Available Props:</h4>
189
+ <p class="text-blue-700 text-sm">
190
+ <code class="bg-blue-100 px-2 py-1 rounded text-xs">{$availablePropsString}</code>
191
+ </p>
192
+ </div>
193
+
194
+ <div class="bg-green-50 border border-green-200 rounded-lg p-4 mb-4">
195
+ <h4 class="font-medium text-green-800 mb-2">🔧 Quick Fixes:</h4>
196
+ <ul class="text-green-700 text-sm space-y-1">
197
+ <li>• Remove the '<code>{$propName}</code>' prop from your template</li>
198
+ <li>• Add '<code>public \${$propName};</code>' to your <code>{$componentName}</code> component class</li>
199
+ <li>• Use data attributes: '<code>data-{$propName}</code>' instead</li>
200
+ </ul>
201
+ </div>
202
+
203
+ <details class="mt-4">
204
+ <summary class="cursor-pointer text-red-600 font-medium hover:text-red-800 select-none">
205
+ Show Stack Trace
206
+ </summary>
207
+ <div class="mt-3 bg-gray-50 border border-gray-200 rounded p-4">
208
+ <pre class="text-xs text-gray-700 overflow-auto whitespace-pre-wrap max-h-96">{$exception->getTraceAsString()}</pre>
209
+ </div>
210
+ </details>
211
+ </div>
212
+ </div>
213
+ HTML;
214
+ }
215
+
216
+ private static function formatTemplateCompilerError(Throwable $exception): string
217
+ {
218
+ $message = htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8');
219
+ $file = htmlspecialchars($exception->getFile(), ENT_QUOTES, 'UTF-8');
220
+ $line = $exception->getLine();
221
+
222
+ // Extract the component validation error details
223
+ if (preg_match("/Invalid prop '([^']+)' passed to component '([^']+)'/", $exception->getMessage(), $matches)) {
224
+ $invalidProp = $matches[1];
225
+ $componentName = $matches[2];
226
+
227
+ return <<<HTML
228
+ <div class="error-container max-w-4xl mx-auto mt-8 bg-red-50 border border-red-200 rounded-lg shadow-lg">
229
+ <div class="bg-red-100 px-6 py-4 border-b border-red-200">
230
+ <h2 class="text-xl font-bold text-red-800 flex items-center">
231
+ <svg class="w-6 h-6 mr-2" fill="currentColor" viewBox="0 0 20 20">
232
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
233
+ </svg>
234
+ Template Compilation Error
235
+ </h2>
236
+ </div>
237
+
238
+ <div class="p-6">
239
+ <div class="bg-white border border-red-200 rounded-lg p-4 mb-4">
240
+ <div class="mb-3">
241
+ <span class="inline-block bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm font-medium">
242
+ Component: {$componentName}
243
+ </span>
244
+ <span class="inline-block bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm font-medium ml-2">
245
+ Invalid Prop: {$invalidProp}
246
+ </span>
247
+ </div>
248
+ <p class="text-red-800 font-medium">{$message}</p>
249
+ </div>
250
+
251
+ <div class="text-sm text-gray-600 mb-4">
252
+ <strong>File:</strong> {$file}<br />
253
+ <strong>Line:</strong> {$line}
254
+ </div>
255
+
256
+ <div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4">
257
+ <h4 class="font-medium text-blue-800 mb-2">💡 Quick Fix:</h4>
258
+ <p class="text-blue-700 text-sm">
259
+ Either remove the '<code>{$invalidProp}</code>' prop from your template, or add it as a public property to your <code>{$componentName}</code> component class.
260
+ </p>
261
+ </div>
262
+
263
+ <details class="mt-4">
264
+ <summary class="cursor-pointer text-red-600 font-medium hover:text-red-800 select-none">
265
+ Show Stack Trace
266
+ </summary>
267
+ <div class="mt-3 bg-gray-50 border border-gray-200 rounded p-4">
268
+ <pre class="text-xs text-gray-700 overflow-auto whitespace-pre-wrap">{$exception->getTraceAsString()}</pre>
269
+ </div>
270
+ </details>
271
+ </div>
272
+ </div>
273
+ HTML;
274
+ }
275
+
276
+ // Fallback to generic formatting
277
+ return self::formatGenericException($exception);
278
+ }
279
+
280
+ private static function formatGenericException(Throwable $exception): string
281
+ {
282
+ $type = htmlspecialchars(get_class($exception), ENT_QUOTES, 'UTF-8');
283
+ $message = htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8');
284
+ $file = htmlspecialchars($exception->getFile(), ENT_QUOTES, 'UTF-8');
285
+ $line = $exception->getLine();
286
+
287
+ return <<<HTML
288
+ <div class="error-container max-w-4xl mx-auto mt-8 bg-red-50 border border-red-200 rounded-lg shadow-lg">
289
+ <div class="bg-red-100 px-6 py-4 border-b border-red-200">
290
+ <h2 class="text-xl font-bold text-red-800 flex items-center">
291
+ <svg class="w-6 h-6 mr-2" fill="currentColor" viewBox="0 0 20 20">
292
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
293
+ </svg>
294
+ {$type}
295
+ </h2>
296
+ </div>
297
+
298
+ <div class="p-6">
299
+ <div class="bg-white border border-red-200 rounded-lg p-4 mb-4">
300
+ <p class="text-red-800 font-medium break-words">{$message}</p>
301
+ </div>
302
+
303
+ <div class="text-sm text-gray-600 mb-4">
304
+ <strong>File:</strong> <code class="bg-gray-100 px-2 py-1 rounded text-xs">{$file}</code><br />
305
+ <strong>Line:</strong> <span class="bg-gray-100 px-2 py-1 rounded text-xs">{$line}</span>
306
+ </div>
307
+
308
+ <details class="mt-4">
309
+ <summary class="cursor-pointer text-red-600 font-medium hover:text-red-800 select-none">
310
+ Show Stack Trace
311
+ </summary>
312
+ <div class="mt-3 bg-gray-50 border border-gray-200 rounded p-4">
313
+ <pre class="text-xs text-gray-700 overflow-auto whitespace-pre-wrap max-h-96">{$exception->getTraceAsString()}</pre>
314
+ </div>
315
+ </details>
316
+ </div>
317
+ </div>
318
+ HTML;
319
+ }
126
320
  }
@@ -4,10 +4,6 @@ namespace Lib;
4
4
 
5
5
  use RuntimeException;
6
6
  use InvalidArgumentException;
7
- use Lib\PrismaPHPSettings;
8
- use DOMDocument;
9
- use DOMElement;
10
- use DOMXPath;
11
7
  use Lib\PHPX\TemplateCompiler;
12
8
 
13
9
  class IncludeTracker
@@ -15,7 +11,7 @@ class IncludeTracker
15
11
  public static array $sections = [];
16
12
 
17
13
  /**
18
- * Includes and echoes a file wrapped in a unique pp-section-id container.
14
+ * Includes and echoes a file wrapped in a unique pp-component container.
19
15
  * Supported $mode values: 'include', 'include_once', 'require', 'require_once'
20
16
  *
21
17
  * @param string $filePath The path to the file to be included.
@@ -43,8 +39,6 @@ class IncludeTracker
43
39
  $wrapped = self::wrapWithId($filePath, $html);
44
40
  $fragDom = TemplateCompiler::convertToXml($wrapped);
45
41
 
46
- self::prefixInlineHandlers($fragDom);
47
-
48
42
  $newHtml = TemplateCompiler::innerXml($fragDom);
49
43
 
50
44
  self::$sections[$filePath] = [
@@ -58,39 +52,6 @@ class IncludeTracker
58
52
  private static function wrapWithId(string $filePath, string $html): string
59
53
  {
60
54
  $id = 's' . base_convert(sprintf('%u', crc32($filePath)), 10, 36);
61
- return "<div pp-section-id=\"$id\">\n$html\n</div>";
62
- }
63
-
64
- private static function prefixInlineHandlers(DOMDocument $doc): void
65
- {
66
- $xp = new DOMXPath($doc);
67
-
68
- /** @var DOMElement $el */
69
- foreach ($xp->query('//*') as $el) {
70
- $handlers = [];
71
-
72
- foreach (iterator_to_array($el->attributes) as $attr) {
73
- $name = $attr->name;
74
-
75
- if (!str_starts_with($name, 'on')) {
76
- continue;
77
- }
78
-
79
- $event = substr($name, 2);
80
- if (
81
- !in_array($event, PrismaPHPSettings::$htmlEvents, true) ||
82
- trim($attr->value) === ''
83
- ) {
84
- continue;
85
- }
86
-
87
- $handlers[$name] = $attr->value;
88
- $el->removeAttribute($name);
89
- }
90
-
91
- foreach ($handlers as $origName => $value) {
92
- $el->setAttribute("pp-inc-{$origName}", $value);
93
- }
94
- }
55
+ return "<div pp-component=\"$id\">\n$html\n</div>";
95
56
  }
96
57
  }
@@ -0,0 +1,49 @@
1
+ <?php
2
+
3
+ namespace Lib\PHPX\Exceptions;
4
+
5
+ use RuntimeException;
6
+
7
+ class ComponentValidationException extends RuntimeException
8
+ {
9
+ private string $propName;
10
+ private string $componentName;
11
+ private array $availableProps;
12
+
13
+ public function __construct(
14
+ string $propName,
15
+ string $componentName,
16
+ array $availableProps,
17
+ string $context = ''
18
+ ) {
19
+ $this->propName = $propName;
20
+ $this->componentName = $componentName;
21
+ $this->availableProps = $availableProps;
22
+
23
+ $availableList = implode(', ', $availableProps);
24
+
25
+ $message = "Invalid prop '{$propName}' for component '{$componentName}'.\n";
26
+ $message .= "Available props: {$availableList}";
27
+
28
+ if ($context) {
29
+ $message .= "\n{$context}";
30
+ }
31
+
32
+ parent::__construct($message);
33
+ }
34
+
35
+ public function getPropName(): string
36
+ {
37
+ return $this->propName;
38
+ }
39
+
40
+ public function getComponentName(): string
41
+ {
42
+ return $this->componentName;
43
+ }
44
+
45
+ public function getAvailableProps(): array
46
+ {
47
+ return $this->availableProps;
48
+ }
49
+ }
@@ -19,11 +19,6 @@ class PHPX implements IPHPX
19
19
  */
20
20
  public mixed $children;
21
21
 
22
- /**
23
- * @var string The CSS class for custom styling.
24
- */
25
- protected string $class;
26
-
27
22
  /**
28
23
  * @var array<string, mixed> The array representation of the HTML attributes.
29
24
  */
@@ -38,27 +33,27 @@ class PHPX implements IPHPX
38
33
  {
39
34
  $this->props = $props;
40
35
  $this->children = $props['children'] ?? '';
41
- $this->class = $props['class'] ?? '';
42
36
  }
43
37
 
44
38
  /**
45
39
  * Combines and returns the CSS classes for the component.
46
40
  *
47
41
  * This method merges the provided classes, which can be either strings or arrays of strings,
48
- * with the component's `$class` property. It uses the `Utils::mergeClasses` method to ensure
49
- * that the resulting CSS class string is optimized, with duplicate or conflicting classes removed.
42
+ * without automatically including the component's `$class` property. It uses the `Utils::mergeClasses`
43
+ * method to ensure that the resulting CSS class string is optimized, with duplicate or conflicting
44
+ * classes removed.
50
45
  *
51
46
  * ### Features:
52
47
  * - Accepts multiple arguments as strings or arrays of strings.
53
- * - Automatically merges the provided classes with `$this->class`.
48
+ * - Only merges the classes provided as arguments (does not include `$this->class` automatically).
54
49
  * - Ensures the final CSS class string is well-formatted and free of conflicts.
55
50
  *
56
51
  * @param string|array ...$classes The CSS classes to be merged. Each argument can be a string or an array of strings.
57
- * @return string A single CSS class string with the merged and optimized classes, including `$this->class`.
52
+ * @return string A single CSS class string with the merged and optimized classes.
58
53
  */
59
54
  protected function getMergeClasses(string|array ...$classes): string
60
55
  {
61
- $all = array_merge($classes, [$this->class]);
56
+ $all = array_merge($classes);
62
57
 
63
58
  $expr = [];
64
59
  foreach ($all as &$chunk) {