eee
This commit is contained in:
@@ -24,7 +24,7 @@ $data = [
|
|||||||
'name' => '张三',
|
'name' => '张三',
|
||||||
'email' => 'zhangsan@example.com',
|
'email' => 'zhangsan@example.com',
|
||||||
'age' => 28,
|
'age' => 28,
|
||||||
'skills' => ['PHP', 'JavaScript', 'MySQL', 'Redis'],
|
'skills' => ['PHP', 'JavaScript', 'MySQL', 'NoSql'],
|
||||||
'posts' => [
|
'posts' => [
|
||||||
[
|
[
|
||||||
'title' => 'Blade 模板引擎介绍',
|
'title' => 'Blade 模板引擎介绍',
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class ExceptionHandlerDispatcher implements ExceptionHandlerInterface
|
|||||||
*/
|
*/
|
||||||
public function emit(Throwable $exception, object $response): ResponseInterface
|
public function emit(Throwable $exception, object $response): ResponseInterface
|
||||||
{
|
{
|
||||||
error($exception);
|
\Kiri::getLogger()->json_log($exception);
|
||||||
$response->withContentType(ContentType::HTML)->withBody(new Stream(throwable($exception)));
|
$response->withContentType(ContentType::HTML)->withBody(new Stream(throwable($exception)));
|
||||||
if ($exception->getCode() == 404) {
|
if ($exception->getCode() == 404) {
|
||||||
return $response->withStatus(404);
|
return $response->withStatus(404);
|
||||||
|
|||||||
@@ -16,30 +16,24 @@ use Psr\Http\Server\RequestHandlerInterface;
|
|||||||
class SessionMiddleware implements MiddlewareInterface
|
class SessionMiddleware implements MiddlewareInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ServerRequestInterface $request
|
* @param ServerRequestInterface $request
|
||||||
* @param RequestHandlerInterface $handler
|
* @param RequestHandlerInterface $handler
|
||||||
* @return ResponseInterface
|
* @return ResponseInterface
|
||||||
* @throws
|
* @throws
|
||||||
*/
|
*/
|
||||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||||
{
|
{
|
||||||
// 启动 Session
|
// 启动 Session
|
||||||
Session::start($request);
|
Session::start($request);
|
||||||
|
|
||||||
try {
|
// 处理请求
|
||||||
// 处理请求
|
$response = $handler->handle($request);
|
||||||
$response = $handler->handle($request);
|
|
||||||
|
|
||||||
// 保存 Session
|
// 保存 Session
|
||||||
Session::save();
|
Session::save();
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
} catch (\Throwable $e) {
|
}
|
||||||
// 即使出错也保存 Session
|
|
||||||
Session::save();
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -299,9 +299,7 @@ class BladeCompiler
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 替换剩余的 @yield 为空
|
// 替换剩余的 @yield 为空
|
||||||
$layoutContent = preg_replace('/@yield\s*\([\'"](.+?)[\'"]\)/', '', $layoutContent);
|
return preg_replace('/@yield\s*\([\'"](.+?)[\'"]\)/', '', $layoutContent);
|
||||||
|
|
||||||
return $layoutContent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理 @section ... @endsection (非继承模式,用于组件等)
|
// 处理 @section ... @endsection (非继承模式,用于组件等)
|
||||||
@@ -324,9 +322,7 @@ class BladeCompiler
|
|||||||
}, $content);
|
}, $content);
|
||||||
|
|
||||||
// 处理 @parent(在 section 中使用)
|
// 处理 @parent(在 section 中使用)
|
||||||
$content = preg_replace('/@parent/', '', $content);
|
return preg_replace('/@parent/', '', $content);
|
||||||
|
|
||||||
return $content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -348,6 +344,8 @@ class BladeCompiler
|
|||||||
$data = eval("return [{$dataStr}];");
|
$data = eval("return [{$dataStr}];");
|
||||||
$dataCode = var_export($data, true);
|
$dataCode = var_export($data, true);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
\Kiri::getLogger()->json_log($e);
|
||||||
|
|
||||||
$dataCode = '[]';
|
$dataCode = '[]';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ class BladeView
|
|||||||
require $compiledPath;
|
require $compiledPath;
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ob_end_clean();
|
ob_end_clean();
|
||||||
|
|
||||||
|
\Kiri::getLogger()->json_log($throwable);
|
||||||
|
|
||||||
throw new \RuntimeException("视图渲染失败: {$this->view}", 0, $e);
|
throw new \RuntimeException("视图渲染失败: {$this->view}", 0, $e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ class OnRequest implements OnRequestInterface
|
|||||||
|
|
||||||
$PsrResponse = $this->router->query($request->server['path_info'], $request->getMethod())->run($PsrRequest);
|
$PsrResponse = $this->router->query($request->server['path_info'], $request->getMethod())->run($PsrRequest);
|
||||||
} catch (Throwable $throwable) {
|
} catch (Throwable $throwable) {
|
||||||
|
\Kiri::getLogger()->json_log($throwable);
|
||||||
$PsrResponse = $this->exception->emit($throwable, $this->constrictResponse);
|
$PsrResponse = $this->exception->emit($throwable, $this->constrictResponse);
|
||||||
} finally {
|
} finally {
|
||||||
$this->responseEmitter->response($PsrResponse, $response, $PsrRequest);
|
$this->responseEmitter->response($PsrResponse, $response, $PsrRequest);
|
||||||
|
|||||||
+29
-22
@@ -14,7 +14,7 @@ use Psr\Http\Message\StreamInterface;
|
|||||||
* 渲染 Blade 视图
|
* 渲染 Blade 视图
|
||||||
*
|
*
|
||||||
* @param string $path 视图路径(支持 . 分隔,如 'user.profile')
|
* @param string $path 视图路径(支持 . 分隔,如 'user.profile')
|
||||||
* @param array $data 视图数据
|
* @param array $data 视图数据
|
||||||
*
|
*
|
||||||
* @return ResponseInterface
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
@@ -25,8 +25,8 @@ function View(string $path, array $data = []): ResponseInterface
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 获取视图路径和缓存路径
|
// 获取视图路径和缓存路径
|
||||||
$viewPath = APP_PATH . 'resources/view';
|
$viewPath = APP_PATH . 'resources/view';
|
||||||
$cachePath = storage(null, 'view/cache');
|
$cachePath = storage(null, 'view/cache');
|
||||||
|
|
||||||
// 创建或获取 BladeFactory 实例
|
// 创建或获取 BladeFactory 实例
|
||||||
$factory = BladeHelper::getFactory();
|
$factory = BladeHelper::getFactory();
|
||||||
@@ -36,12 +36,19 @@ function View(string $path, array $data = []): ResponseInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 渲染视图
|
// 渲染视图
|
||||||
$content = $factory->render($path, $data);
|
return $response->html($factory->render($path, $data));
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $throwable) {
|
||||||
$content = function_exists('throwable') ? throwable($e) : $e->getMessage();
|
\Kiri::getLogger()->json_log($throwable);
|
||||||
}
|
|
||||||
|
|
||||||
return $response->html($content);
|
ob_start();
|
||||||
|
|
||||||
|
extract(['errorData' => $throwable], EXTR_SKIP);
|
||||||
|
include __DIR__.'/template/error.php';
|
||||||
|
|
||||||
|
$message = ob_get_clean();
|
||||||
|
|
||||||
|
return $response->html($message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,7 +111,7 @@ class Response implements ResponseInterface
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $content
|
* @param array $content
|
||||||
* @param int $statusCode
|
* @param int $statusCode
|
||||||
*
|
*
|
||||||
* @return ResponseInterface
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
@@ -115,8 +122,8 @@ class Response implements ResponseInterface
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $url
|
* @param string $url
|
||||||
* @param array $params
|
* @param array $params
|
||||||
* @param int $statusCode
|
* @param int $statusCode
|
||||||
*
|
*
|
||||||
* @return ResponseInterface
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
@@ -127,7 +134,7 @@ class Response implements ResponseInterface
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $content
|
* @param array $content
|
||||||
* @param int $statusCode
|
* @param int $statusCode
|
||||||
*
|
*
|
||||||
* @return ResponseInterface
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
@@ -139,7 +146,7 @@ class Response implements ResponseInterface
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $content
|
* @param string $content
|
||||||
* @param int $statusCode
|
* @param int $statusCode
|
||||||
*
|
*
|
||||||
* @return ResponseInterface
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
@@ -151,7 +158,7 @@ class Response implements ResponseInterface
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $content
|
* @param string $content
|
||||||
* @param int $statusCode
|
* @param int $statusCode
|
||||||
*
|
*
|
||||||
* @return ResponseInterface
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
@@ -162,8 +169,8 @@ class Response implements ResponseInterface
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mixed $data
|
* @param mixed $data
|
||||||
* @param int $statusCode
|
* @param int $statusCode
|
||||||
* @param ContentType $type
|
* @param ContentType $type
|
||||||
*
|
*
|
||||||
* @return Response
|
* @return Response
|
||||||
@@ -176,7 +183,7 @@ class Response implements ResponseInterface
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $method
|
* @param string $method
|
||||||
* @param mixed ...$params
|
* @param mixed ...$params
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
@@ -342,7 +349,7 @@ class Response implements ResponseInterface
|
|||||||
* immutability of the message, and MUST return an instance that has the
|
* immutability of the message, and MUST return an instance that has the
|
||||||
* new and/or updated header and value.
|
* new and/or updated header and value.
|
||||||
*
|
*
|
||||||
* @param string $name Case-insensitive header field name.
|
* @param string $name Case-insensitive header field name.
|
||||||
* @param string|string[] $value Header value(s).
|
* @param string|string[] $value Header value(s).
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
@@ -376,7 +383,7 @@ class Response implements ResponseInterface
|
|||||||
* immutability of the message, and MUST return an instance that has the
|
* immutability of the message, and MUST return an instance that has the
|
||||||
* new header and/or value.
|
* new header and/or value.
|
||||||
*
|
*
|
||||||
* @param string $name Case-insensitive header field name to add.
|
* @param string $name Case-insensitive header field name to add.
|
||||||
* @param string|string[] $value Header value(s).
|
* @param string|string[] $value Header value(s).
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
@@ -454,8 +461,8 @@ class Response implements ResponseInterface
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $content
|
* @param string $content
|
||||||
* @param int $statusCode
|
* @param int $statusCode
|
||||||
* @param ContentType $contentType
|
* @param ContentType $contentType
|
||||||
*
|
*
|
||||||
* @return ResponseInterface
|
* @return ResponseInterface
|
||||||
@@ -479,7 +486,7 @@ class Response implements ResponseInterface
|
|||||||
* @link http://tools.ietf.org/html/rfc7231#section-6
|
* @link http://tools.ietf.org/html/rfc7231#section-6
|
||||||
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
||||||
*
|
*
|
||||||
* @param int $code The 3-digit integer result code to set.
|
* @param int $code The 3-digit integer result code to set.
|
||||||
* @param string $reasonPhrase The reason phrase to use with the
|
* @param string $reasonPhrase The reason phrase to use with the
|
||||||
* provided status code; if none is provided, implementations MAY
|
* provided status code; if none is provided, implementations MAY
|
||||||
* use the defaults as suggested in the HTTP specification.
|
* use the defaults as suggested in the HTTP specification.
|
||||||
|
|||||||
+2
-2
@@ -192,7 +192,7 @@ class Router
|
|||||||
|
|
||||||
$container = Kiri::getDi();
|
$container = Kiri::getDi();
|
||||||
$scanner = $container->get(Kiri\Di\Scanner::class);
|
$scanner = $container->get(Kiri\Di\Scanner::class);
|
||||||
$scanner->load_directory(APP_PATH . 'app/Controller');
|
$scanner->scan(APP_PATH . 'app/');
|
||||||
$this->reset($container);
|
$this->reset($container);
|
||||||
|
|
||||||
$coordinator->done();
|
$coordinator->done();
|
||||||
@@ -255,7 +255,7 @@ class Router
|
|||||||
try {
|
try {
|
||||||
include "$files";
|
include "$files";
|
||||||
} catch (\Throwable $throwable) {
|
} catch (\Throwable $throwable) {
|
||||||
error($throwable);
|
\Kiri::getLogger()->json_log($throwable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
|||||||
$this->register($route, $value, $handler);
|
$this->register($route, $value, $handler);
|
||||||
}
|
}
|
||||||
} catch (Throwable $throwable) {
|
} catch (Throwable $throwable) {
|
||||||
error($throwable);
|
\Kiri::getLogger()->json_log($throwable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class MixedProxy extends TypesProxy
|
|||||||
try {
|
try {
|
||||||
return $value == ($form->{$field} = $value);
|
return $value == ($form->{$field} = $value);
|
||||||
} catch (\Throwable $throwable) {
|
} catch (\Throwable $throwable) {
|
||||||
return false;
|
return $this->getLogger()->json_log($throwable, [], false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
namespace Kiri\Router\Validator\Types;
|
namespace Kiri\Router\Validator\Types;
|
||||||
|
|
||||||
abstract class TypesProxy
|
use Kiri\Abstracts\Component;
|
||||||
|
|
||||||
|
abstract class TypesProxy extends Component
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,498 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang='en'>
|
||||||
|
<head>
|
||||||
|
<meta charset='UTF-8'>
|
||||||
|
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
||||||
|
<title>Error: <?= htmlspecialchars($errorData['type']) ?></title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.6;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-header {
|
||||||
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 20px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-title {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-title i {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-content {
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-section {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header:hover {
|
||||||
|
background: #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-toggle {
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content {
|
||||||
|
padding: 20px;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block {
|
||||||
|
background: #282c34;
|
||||||
|
color: #abb2bf;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-path {
|
||||||
|
color: #61afef;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-number {
|
||||||
|
color: #98c379;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: #e06c75;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding: 10px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background: #f5f5f5;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:hover {
|
||||||
|
background: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trace-item {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 10px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
border-radius: 0 4px 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trace-header {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trace-location {
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-error {
|
||||||
|
background: #f44336;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-warning {
|
||||||
|
background: #ff9800;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-info {
|
||||||
|
background: #2196f3;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-success {
|
||||||
|
background: #4caf50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.environment-info {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: all 0.3s;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #5a6fd8;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: #6c757d;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: #5a6268;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.error-container {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.environment-info {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="error-container">
|
||||||
|
<div class="error-header">
|
||||||
|
<h1 class="error-title">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
|
<?= htmlspecialchars($errorData['type']) ?>
|
||||||
|
</h1>
|
||||||
|
<div class="error-subtitle">
|
||||||
|
<?= htmlspecialchars($errorData['message']) ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="error-content">
|
||||||
|
<!-- Error Details -->
|
||||||
|
<div class="error-section">
|
||||||
|
<div class="section-header" onclick="toggleSection('details')">
|
||||||
|
<span class="section-title">Error Details</span>
|
||||||
|
<span class="section-toggle" id="toggle-details">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="section-content" id="section-details">
|
||||||
|
<div class="code-block">
|
||||||
|
<span class="file-path"><?= htmlspecialchars($errorData['file']) ?></span>
|
||||||
|
<span class="line-number">:<?= $errorData['line'] ?></span>
|
||||||
|
<br><br>
|
||||||
|
<span class="error-message"><?= htmlspecialchars($errorData['message']) ?></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Type</th>
|
||||||
|
<td><span class="badge badge-error"><?= htmlspecialchars($errorData['type']) ?></span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Code</th>
|
||||||
|
<td><?= $errorData['code'] ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Timestamp</th>
|
||||||
|
<td><?= $errorData['timestamp'] ?></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stack Trace -->
|
||||||
|
<div class="error-section">
|
||||||
|
<div class="section-header" onclick="toggleSection('trace')">
|
||||||
|
<span class="section-title">Stack Trace</span>
|
||||||
|
<span class="section-toggle" id="toggle-trace">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="section-content" id="section-trace">
|
||||||
|
<?php
|
||||||
|
foreach ($errorData['trace'] as $index => $trace): ?>
|
||||||
|
<div class="trace-item">
|
||||||
|
<div class="trace-header">
|
||||||
|
#<?= $index ?>:
|
||||||
|
<?= $trace['class'] ?? '' ?><?= $trace['type'] ?? '' ?><?= $trace['function'] ?>()
|
||||||
|
</div>
|
||||||
|
<div class="trace-location">
|
||||||
|
<?= $trace['file'] ?? 'internal' ?><?= isset($trace['line']) ? ':' . $trace['line'] : '' ?>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
if (!empty($trace['args'])): ?>
|
||||||
|
<div style="margin-top: 5px; font-size: 12px; color: #666;">
|
||||||
|
Arguments: <?= implode(', ', $trace['args']) ?>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Server Information -->
|
||||||
|
<div class="error-section">
|
||||||
|
<div class="section-header" onclick="toggleSection('server')">
|
||||||
|
<span class="section-title">Server Information</span>
|
||||||
|
<span class="section-toggle" id="toggle-server">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="section-content hidden" id="section-server">
|
||||||
|
<table>
|
||||||
|
<?php
|
||||||
|
foreach ($errorData['server'] as $key => $value): ?>
|
||||||
|
<tr>
|
||||||
|
<th><?= htmlspecialchars($key) ?></th>
|
||||||
|
<td><?= htmlspecialchars($value) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php
|
||||||
|
endforeach; ?>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Request Information -->
|
||||||
|
<div class="error-section">
|
||||||
|
<div class="section-header" onclick="toggleSection('request')">
|
||||||
|
<span class="section-title">Request Information</span>
|
||||||
|
<span class="section-toggle" id="toggle-request">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="section-content hidden" id="section-request">
|
||||||
|
<h4>GET Parameters</h4>
|
||||||
|
<?php
|
||||||
|
if (!empty($errorData['request']['GET'])): ?>
|
||||||
|
<table>
|
||||||
|
<?php
|
||||||
|
foreach ($errorData['request']['GET'] as $key => $value): ?>
|
||||||
|
<tr>
|
||||||
|
<th><?= htmlspecialchars($key) ?></th>
|
||||||
|
<td><?= htmlspecialchars(is_array($value) ? json_encode($value) : $value) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php
|
||||||
|
endforeach; ?>
|
||||||
|
</table>
|
||||||
|
<?php
|
||||||
|
else: ?>
|
||||||
|
<p>No GET parameters</p>
|
||||||
|
<?php
|
||||||
|
endif; ?>
|
||||||
|
|
||||||
|
<h4>POST Parameters</h4>
|
||||||
|
<?php
|
||||||
|
if (!empty($errorData['request']['POST'])): ?>
|
||||||
|
<table>
|
||||||
|
<?php
|
||||||
|
foreach ($errorData['request']['POST'] as $key => $value): ?>
|
||||||
|
<tr>
|
||||||
|
<th><?= htmlspecialchars($key) ?></th>
|
||||||
|
<td><?= htmlspecialchars(is_array($value) ? json_encode($value) : $value) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php
|
||||||
|
endforeach; ?>
|
||||||
|
</table>
|
||||||
|
<?php
|
||||||
|
else: ?>
|
||||||
|
<p>No POST parameters</p>
|
||||||
|
<?php
|
||||||
|
endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Environment Info -->
|
||||||
|
<div class="environment-info">
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">Memory Usage</div>
|
||||||
|
<div class="info-value"><?= $errorData['memory_usage'] ?></div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">Peak Memory</div>
|
||||||
|
<div class="info-value"><?= $errorData['peak_memory'] ?></div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">Execution Time</div>
|
||||||
|
<div class="info-value"><?= round($errorData['execution_time'], 4) ?> seconds</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">PHP Version</div>
|
||||||
|
<div class="info-value"><?= PHP_VERSION ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="actions">
|
||||||
|
<button class="btn btn-primary" onclick="window.location.reload()">
|
||||||
|
<i class="fas fa-redo"></i> Reload Page
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary" onclick="window.history.back()">
|
||||||
|
<i class="fas fa-arrow-left"></i> Go Back
|
||||||
|
</button>
|
||||||
|
<a href="/" class="btn btn-secondary">
|
||||||
|
<i class="fas fa-home"></i> Go Home
|
||||||
|
</a>
|
||||||
|
<button class="btn btn-secondary" onclick="copyErrorDetails()">
|
||||||
|
<i class="fas fa-copy"></i> Copy Error Details
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleSection(sectionId) {
|
||||||
|
const section = document.getElementById(`section-${sectionId}`);
|
||||||
|
const toggle = document.getElementById(`toggle-${sectionId}`);
|
||||||
|
|
||||||
|
if (section.classList.contains('hidden')) {
|
||||||
|
section.classList.remove('hidden');
|
||||||
|
toggle.textContent = '▲';
|
||||||
|
} else {
|
||||||
|
section.classList.add('hidden');
|
||||||
|
toggle.textContent = '▼';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyErrorDetails() {
|
||||||
|
const errorDetails = {
|
||||||
|
type: '<?= addslashes($errorData['type']) ?>',
|
||||||
|
message: '<?= addslashes($errorData['message']) ?>',
|
||||||
|
file: '<?= addslashes($errorData['file']) ?>',
|
||||||
|
line: <?= $errorData['line'] ?>,
|
||||||
|
timestamp: '<?= $errorData['timestamp'] ?>',
|
||||||
|
trace: <?= json_encode($errorData['trace']) ?>
|
||||||
|
};
|
||||||
|
|
||||||
|
const text = `Error Details:
|
||||||
|
Type: ${errorDetails.type}
|
||||||
|
Message: ${errorDetails.message}
|
||||||
|
File: ${errorDetails.file}
|
||||||
|
Line: ${errorDetails.line}
|
||||||
|
Timestamp: ${errorDetails.timestamp}
|
||||||
|
|
||||||
|
Stack Trace:
|
||||||
|
${JSON.stringify(errorDetails.trace, null, 2)}`;
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
alert('Error details copied to clipboard!');
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('Failed to copy: ', err);
|
||||||
|
alert('Failed to copy error details');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-expand first section
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
toggleSection('details');
|
||||||
|
toggleSection('trace');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user