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,121 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Lib;
|
|
6
|
+
|
|
7
|
+
use Lib\PrismaPHPSettings;
|
|
8
|
+
|
|
9
|
+
class CacheHandler
|
|
10
|
+
{
|
|
11
|
+
public static bool $isCacheable = true; // Enable or disable caching by route
|
|
12
|
+
public static int $ttl = 0; // Time to live in seconds (0 = no action taken)
|
|
13
|
+
|
|
14
|
+
private static string $cacheDir = DOCUMENT_PATH . '/caches';
|
|
15
|
+
private static bool $cacheDirChecked = false;
|
|
16
|
+
|
|
17
|
+
private static function ensureCacheDirectoryExists(): void
|
|
18
|
+
{
|
|
19
|
+
if (self::$cacheDirChecked) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!is_dir(self::$cacheDir) && !mkdir(self::$cacheDir, 0777, true) && !is_dir(self::$cacheDir)) {
|
|
24
|
+
die("Error: Unable to create cache directory at: " . self::$cacheDir);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
self::$cacheDirChecked = true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public static function getCacheFilePath(string $uri): string
|
|
31
|
+
{
|
|
32
|
+
$requestFilesData = PrismaPHPSettings::$includeFiles;
|
|
33
|
+
$fileName = $requestFilesData[$uri]['fileName'] ?? '';
|
|
34
|
+
$isCacheable = $requestFilesData[$uri]['isCacheable'] ?? self::$isCacheable;
|
|
35
|
+
|
|
36
|
+
if (!$isCacheable || $fileName === '') {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return self::$cacheDir . '/' . $fileName . '.html';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private static function isExpired(string $cacheFile, int $ttlSeconds = 600): bool
|
|
44
|
+
{
|
|
45
|
+
if (!file_exists($cacheFile)) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
$fileAge = time() - filemtime($cacheFile);
|
|
49
|
+
return $fileAge > $ttlSeconds;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Serve cache if available, not expired, and route is marked cacheable.
|
|
54
|
+
* We look up a route-specific TTL if defined, otherwise use a fallback.
|
|
55
|
+
*/
|
|
56
|
+
public static function serveCache(string $uri, int $defaultTtl = 600): void
|
|
57
|
+
{
|
|
58
|
+
if ($uri === '') {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
$requestFilesData = PrismaPHPSettings::$includeFiles;
|
|
63
|
+
|
|
64
|
+
// Get the route-specific TTL if set, or default to 0
|
|
65
|
+
$routeTtl = $requestFilesData[$uri]['cacheTtl'] ?? 0;
|
|
66
|
+
|
|
67
|
+
// If the route has a TTL greater than 0, use that.
|
|
68
|
+
// Otherwise (0 or not defined), use the default.
|
|
69
|
+
$ttlSeconds = ($routeTtl > 0) ? $routeTtl : $defaultTtl;
|
|
70
|
+
|
|
71
|
+
$cacheFile = self::getCacheFilePath($uri);
|
|
72
|
+
|
|
73
|
+
if ($cacheFile === '' || !file_exists($cacheFile) || self::isExpired($cacheFile, $ttlSeconds)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
echo "<!-- Cached copy generated at: " . date('Y-m-d H:i:s', filemtime($cacheFile)) . " -->\n";
|
|
78
|
+
readfile($cacheFile);
|
|
79
|
+
exit;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public static function saveCache(string $uri, string $content, bool $useLock = true): void
|
|
83
|
+
{
|
|
84
|
+
if ($uri === '') {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
self::ensureCacheDirectoryExists();
|
|
88
|
+
|
|
89
|
+
$cacheFile = self::getCacheFilePath($uri);
|
|
90
|
+
if ($cacheFile === '') {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
$flags = $useLock ? LOCK_EX : 0;
|
|
95
|
+
$written = @file_put_contents($cacheFile, $content, $flags);
|
|
96
|
+
|
|
97
|
+
if ($written === false) {
|
|
98
|
+
die("Error: Failed to write cache file: $cacheFile");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public static function resetCache(?string $uri = null): void
|
|
103
|
+
{
|
|
104
|
+
self::ensureCacheDirectoryExists();
|
|
105
|
+
|
|
106
|
+
if ($uri !== null) {
|
|
107
|
+
$cacheFile = self::getCacheFilePath($uri);
|
|
108
|
+
if ($cacheFile !== '' && file_exists($cacheFile)) {
|
|
109
|
+
unlink($cacheFile);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
$files = glob(self::$cacheDir . '/*.html') ?: [];
|
|
115
|
+
foreach ($files as $file) {
|
|
116
|
+
if (is_file($file)) {
|
|
117
|
+
unlink($file);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Lib;
|
|
6
|
+
|
|
7
|
+
use Bootstrap;
|
|
8
|
+
use Lib\MainLayout;
|
|
9
|
+
use Throwable;
|
|
10
|
+
use Lib\PHPX\Exceptions\ComponentValidationException;
|
|
11
|
+
|
|
12
|
+
class ErrorHandler
|
|
13
|
+
{
|
|
14
|
+
public static string $content = '';
|
|
15
|
+
|
|
16
|
+
public static function registerHandlers(): void
|
|
17
|
+
{
|
|
18
|
+
self::registerExceptionHandler();
|
|
19
|
+
self::registerShutdownFunction();
|
|
20
|
+
self::registerErrorHandler();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private static function registerExceptionHandler(): void
|
|
24
|
+
{
|
|
25
|
+
set_exception_handler(function ($exception) {
|
|
26
|
+
$errorContent = Bootstrap::isAjaxOrXFileRequestOrRouteFile()
|
|
27
|
+
? "Exception: " . $exception->getMessage()
|
|
28
|
+
: "<div class='error'>Exception: " . htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8') . "</div>";
|
|
29
|
+
|
|
30
|
+
self::modifyOutputLayoutForError($errorContent);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private static function registerShutdownFunction(): void
|
|
35
|
+
{
|
|
36
|
+
register_shutdown_function(function () {
|
|
37
|
+
$error = error_get_last();
|
|
38
|
+
if (
|
|
39
|
+
$error !== null &&
|
|
40
|
+
in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_RECOVERABLE_ERROR], true)
|
|
41
|
+
) {
|
|
42
|
+
$errorContent = Bootstrap::isAjaxOrXFileRequestOrRouteFile()
|
|
43
|
+
? "Fatal Error: " . $error['message'] . " in " . $error['file'] . " on line " . $error['line']
|
|
44
|
+
: "<div class='error'>Fatal Error: " . htmlspecialchars($error['message'], ENT_QUOTES, 'UTF-8') .
|
|
45
|
+
" in " . htmlspecialchars($error['file'], ENT_QUOTES, 'UTF-8') .
|
|
46
|
+
" on line " . $error['line'] . "</div>";
|
|
47
|
+
|
|
48
|
+
self::modifyOutputLayoutForError($errorContent);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private static function registerErrorHandler(): void
|
|
54
|
+
{
|
|
55
|
+
set_error_handler(function ($severity, $message, $file, $line) {
|
|
56
|
+
if (!(error_reporting() & $severity)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
$errorContent = Bootstrap::isAjaxOrXFileRequestOrRouteFile()
|
|
60
|
+
? "Error: {$severity} - {$message} in {$file} on line {$line}"
|
|
61
|
+
: "<div class='error'>Error: {$message} in {$file} on line {$line}</div>";
|
|
62
|
+
|
|
63
|
+
if ($severity === E_WARNING || $severity === E_NOTICE) {
|
|
64
|
+
self::modifyOutputLayoutForError($errorContent);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public static function checkFatalError(): void
|
|
70
|
+
{
|
|
71
|
+
$error = error_get_last();
|
|
72
|
+
if (
|
|
73
|
+
$error !== null &&
|
|
74
|
+
in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_RECOVERABLE_ERROR], true)
|
|
75
|
+
) {
|
|
76
|
+
$errorContent = Bootstrap::isAjaxOrXFileRequestOrRouteFile()
|
|
77
|
+
? "Fatal Error: " . $error['message'] . " in " . $error['file'] . " on line " . $error['line']
|
|
78
|
+
: "<div class='error'>Fatal Error: " . htmlspecialchars($error['message'], ENT_QUOTES, 'UTF-8') .
|
|
79
|
+
" in " . htmlspecialchars($error['file'], ENT_QUOTES, 'UTF-8') .
|
|
80
|
+
" on line " . $error['line'] . "</div>";
|
|
81
|
+
|
|
82
|
+
self::modifyOutputLayoutForError($errorContent);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public static function modifyOutputLayoutForError($contentToAdd): void
|
|
87
|
+
{
|
|
88
|
+
$errorFile = APP_PATH . '/error.php';
|
|
89
|
+
$errorFileExists = file_exists($errorFile);
|
|
90
|
+
|
|
91
|
+
if ($_ENV['SHOW_ERRORS'] === "false") {
|
|
92
|
+
if ($errorFileExists) {
|
|
93
|
+
$contentToAdd = Bootstrap::isAjaxOrXFileRequestOrRouteFile() ? "An error occurred" : "<div class='error'>An error occurred</div>";
|
|
94
|
+
} else {
|
|
95
|
+
exit;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if ($errorFileExists) {
|
|
100
|
+
if (ob_get_level()) {
|
|
101
|
+
ob_end_clean();
|
|
102
|
+
}
|
|
103
|
+
self::$content = $contentToAdd;
|
|
104
|
+
if (Bootstrap::isAjaxOrXFileRequestOrRouteFile()) {
|
|
105
|
+
header('Content-Type: application/json');
|
|
106
|
+
echo json_encode(['success' => false, 'error' => self::$content]);
|
|
107
|
+
http_response_code(403);
|
|
108
|
+
} else {
|
|
109
|
+
$layoutFile = APP_PATH . '/layout.php';
|
|
110
|
+
if (file_exists($layoutFile)) {
|
|
111
|
+
ob_start();
|
|
112
|
+
require_once $errorFile;
|
|
113
|
+
MainLayout::$children = ob_get_clean();
|
|
114
|
+
require $layoutFile;
|
|
115
|
+
} else {
|
|
116
|
+
echo self::$content;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
if (Bootstrap::isAjaxOrXFileRequestOrRouteFile()) {
|
|
121
|
+
header('Content-Type: application/json');
|
|
122
|
+
echo json_encode(['success' => false, 'error' => $contentToAdd]);
|
|
123
|
+
http_response_code(403);
|
|
124
|
+
} else {
|
|
125
|
+
echo $contentToAdd;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
exit;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public static function formatExceptionForDisplay(Throwable $exception): string
|
|
132
|
+
{
|
|
133
|
+
// Handle specific exception types
|
|
134
|
+
if ($exception instanceof ComponentValidationException) {
|
|
135
|
+
return self::formatComponentValidationError($exception);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Handle template compilation errors specifically
|
|
139
|
+
if (strpos($exception->getMessage(), 'Invalid prop') !== false) {
|
|
140
|
+
return self::formatTemplateCompilerError($exception);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Generic exception formatting
|
|
144
|
+
return self::formatGenericException($exception);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private static function formatComponentValidationError(ComponentValidationException $exception): string
|
|
148
|
+
{
|
|
149
|
+
$message = htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8');
|
|
150
|
+
$file = htmlspecialchars($exception->getFile(), ENT_QUOTES, 'UTF-8');
|
|
151
|
+
$line = $exception->getLine();
|
|
152
|
+
|
|
153
|
+
// Get the details from the ComponentValidationException
|
|
154
|
+
$propName = method_exists($exception, 'getPropName') ? $exception->getPropName() : 'unknown';
|
|
155
|
+
$componentName = method_exists($exception, 'getComponentName') ? $exception->getComponentName() : 'unknown';
|
|
156
|
+
$availableProps = method_exists($exception, 'getAvailableProps') ? $exception->getAvailableProps() : [];
|
|
157
|
+
|
|
158
|
+
$availablePropsString = !empty($availableProps) ? implode(', ', $availableProps) : 'none defined';
|
|
159
|
+
|
|
160
|
+
return <<<HTML
|
|
161
|
+
<div class="error-container max-w-4xl mx-auto mt-8 bg-red-50 border border-red-200 rounded-lg shadow-lg">
|
|
162
|
+
<div class="bg-red-100 px-6 py-4 border-b border-red-200">
|
|
163
|
+
<h2 class="text-xl font-bold text-red-800 flex items-center">
|
|
164
|
+
<svg class="w-6 h-6 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
165
|
+
<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>
|
|
166
|
+
</svg>
|
|
167
|
+
Component Validation Error
|
|
168
|
+
</h2>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<div class="p-6">
|
|
172
|
+
<div class="bg-white border border-red-200 rounded-lg p-4 mb-4">
|
|
173
|
+
<div class="mb-3">
|
|
174
|
+
<span class="inline-block bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm font-medium">
|
|
175
|
+
Component: {$componentName}
|
|
176
|
+
</span>
|
|
177
|
+
<span class="inline-block bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm font-medium ml-2">
|
|
178
|
+
Invalid Prop: {$propName}
|
|
179
|
+
</span>
|
|
180
|
+
</div>
|
|
181
|
+
<pre class="text-sm text-red-800 whitespace-pre-wrap font-mono">{$message}</pre>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<div class="text-sm text-gray-600 mb-4">
|
|
185
|
+
<strong>File:</strong> <code class="bg-gray-100 px-2 py-1 rounded text-xs">{$file}</code><br />
|
|
186
|
+
<strong>Line:</strong> <span class="bg-gray-100 px-2 py-1 rounded text-xs">{$line}</span>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4">
|
|
190
|
+
<h4 class="font-medium text-blue-800 mb-2">💡 Available Props:</h4>
|
|
191
|
+
<p class="text-blue-700 text-sm">
|
|
192
|
+
<code class="bg-blue-100 px-2 py-1 rounded text-xs">{$availablePropsString}</code>
|
|
193
|
+
</p>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<div class="bg-green-50 border border-green-200 rounded-lg p-4 mb-4">
|
|
197
|
+
<h4 class="font-medium text-green-800 mb-2">🔧 Quick Fixes:</h4>
|
|
198
|
+
<ul class="text-green-700 text-sm space-y-1">
|
|
199
|
+
<li>• Remove the '<code>{$propName}</code>' prop from your template</li>
|
|
200
|
+
<li>• Add '<code>public \${$propName};</code>' to your <code>{$componentName}</code> component class</li>
|
|
201
|
+
<li>• Use data attributes: '<code>data-{$propName}</code>' instead</li>
|
|
202
|
+
</ul>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<details class="mt-4">
|
|
206
|
+
<summary class="cursor-pointer text-red-600 font-medium hover:text-red-800 select-none">
|
|
207
|
+
Show Stack Trace
|
|
208
|
+
</summary>
|
|
209
|
+
<div class="mt-3 bg-gray-50 border border-gray-200 rounded p-4">
|
|
210
|
+
<pre class="text-xs text-gray-700 overflow-auto whitespace-pre-wrap max-h-96">{$exception->getTraceAsString()}</pre>
|
|
211
|
+
</div>
|
|
212
|
+
</details>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
HTML;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private static function formatTemplateCompilerError(Throwable $exception): string
|
|
219
|
+
{
|
|
220
|
+
$message = htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8');
|
|
221
|
+
$file = htmlspecialchars($exception->getFile(), ENT_QUOTES, 'UTF-8');
|
|
222
|
+
$line = $exception->getLine();
|
|
223
|
+
|
|
224
|
+
// Extract the component validation error details
|
|
225
|
+
if (preg_match("/Invalid prop '([^']+)' passed to component '([^']+)'/", $exception->getMessage(), $matches)) {
|
|
226
|
+
$invalidProp = $matches[1];
|
|
227
|
+
$componentName = $matches[2];
|
|
228
|
+
|
|
229
|
+
return <<<HTML
|
|
230
|
+
<div class="error-container max-w-4xl mx-auto mt-8 bg-red-50 border border-red-200 rounded-lg shadow-lg">
|
|
231
|
+
<div class="bg-red-100 px-6 py-4 border-b border-red-200">
|
|
232
|
+
<h2 class="text-xl font-bold text-red-800 flex items-center">
|
|
233
|
+
<svg class="w-6 h-6 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
234
|
+
<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>
|
|
235
|
+
</svg>
|
|
236
|
+
Template Compilation Error
|
|
237
|
+
</h2>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<div class="p-6">
|
|
241
|
+
<div class="bg-white border border-red-200 rounded-lg p-4 mb-4">
|
|
242
|
+
<div class="mb-3">
|
|
243
|
+
<span class="inline-block bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm font-medium">
|
|
244
|
+
Component: {$componentName}
|
|
245
|
+
</span>
|
|
246
|
+
<span class="inline-block bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm font-medium ml-2">
|
|
247
|
+
Invalid Prop: {$invalidProp}
|
|
248
|
+
</span>
|
|
249
|
+
</div>
|
|
250
|
+
<p class="text-red-800 font-medium">{$message}</p>
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
<div class="text-sm text-gray-600 mb-4">
|
|
254
|
+
<strong>File:</strong> {$file}<br />
|
|
255
|
+
<strong>Line:</strong> {$line}
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4">
|
|
259
|
+
<h4 class="font-medium text-blue-800 mb-2">💡 Quick Fix:</h4>
|
|
260
|
+
<p class="text-blue-700 text-sm">
|
|
261
|
+
Either remove the '<code>{$invalidProp}</code>' prop from your template, or add it as a public property to your <code>{$componentName}</code> component class.
|
|
262
|
+
</p>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
<details class="mt-4">
|
|
266
|
+
<summary class="cursor-pointer text-red-600 font-medium hover:text-red-800 select-none">
|
|
267
|
+
Show Stack Trace
|
|
268
|
+
</summary>
|
|
269
|
+
<div class="mt-3 bg-gray-50 border border-gray-200 rounded p-4">
|
|
270
|
+
<pre class="text-xs text-gray-700 overflow-auto whitespace-pre-wrap">{$exception->getTraceAsString()}</pre>
|
|
271
|
+
</div>
|
|
272
|
+
</details>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
HTML;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Fallback to generic formatting
|
|
279
|
+
return self::formatGenericException($exception);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private static function formatGenericException(Throwable $exception): string
|
|
283
|
+
{
|
|
284
|
+
$type = htmlspecialchars(get_class($exception), ENT_QUOTES, 'UTF-8');
|
|
285
|
+
$message = htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8');
|
|
286
|
+
$file = htmlspecialchars($exception->getFile(), ENT_QUOTES, 'UTF-8');
|
|
287
|
+
$line = $exception->getLine();
|
|
288
|
+
|
|
289
|
+
return <<<HTML
|
|
290
|
+
<div class="error-container max-w-4xl mx-auto mt-8 bg-red-50 border border-red-200 rounded-lg shadow-lg">
|
|
291
|
+
<div class="bg-red-100 px-6 py-4 border-b border-red-200">
|
|
292
|
+
<h2 class="text-xl font-bold text-red-800 flex items-center">
|
|
293
|
+
<svg class="w-6 h-6 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
|
294
|
+
<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>
|
|
295
|
+
</svg>
|
|
296
|
+
{$type}
|
|
297
|
+
</h2>
|
|
298
|
+
</div>
|
|
299
|
+
|
|
300
|
+
<div class="p-6">
|
|
301
|
+
<div class="bg-white border border-red-200 rounded-lg p-4 mb-4">
|
|
302
|
+
<p class="text-red-800 font-medium break-words">{$message}</p>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
<div class="text-sm text-gray-600 mb-4">
|
|
306
|
+
<strong>File:</strong> <code class="bg-gray-100 px-2 py-1 rounded text-xs">{$file}</code><br />
|
|
307
|
+
<strong>Line:</strong> <span class="bg-gray-100 px-2 py-1 rounded text-xs">{$line}</span>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
<details class="mt-4">
|
|
311
|
+
<summary class="cursor-pointer text-red-600 font-medium hover:text-red-800 select-none">
|
|
312
|
+
Show Stack Trace
|
|
313
|
+
</summary>
|
|
314
|
+
<div class="mt-3 bg-gray-50 border border-gray-200 rounded p-4">
|
|
315
|
+
<pre class="text-xs text-gray-700 overflow-auto whitespace-pre-wrap max-h-96">{$exception->getTraceAsString()}</pre>
|
|
316
|
+
</div>
|
|
317
|
+
</details>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
HTML;
|
|
321
|
+
}
|
|
322
|
+
}
|