mirror of https://gitee.com/topnuomi/framework
第一次提交
This commit is contained in:
parent
a6d5584e33
commit
a137c834f9
|
@ -0,0 +1,7 @@
|
||||||
|
<IfModule mod_rewrite.c>
|
||||||
|
Options +FollowSymlinks -Multiviews
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L]
|
||||||
|
</IfModule>
|
36
README.en.md
36
README.en.md
|
@ -1,36 +0,0 @@
|
||||||
# The new framework
|
|
||||||
|
|
||||||
#### Description
|
|
||||||
{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**}
|
|
||||||
|
|
||||||
#### Software Architecture
|
|
||||||
Software architecture description
|
|
||||||
|
|
||||||
#### Installation
|
|
||||||
|
|
||||||
1. xxxx
|
|
||||||
2. xxxx
|
|
||||||
3. xxxx
|
|
||||||
|
|
||||||
#### Instructions
|
|
||||||
|
|
||||||
1. xxxx
|
|
||||||
2. xxxx
|
|
||||||
3. xxxx
|
|
||||||
|
|
||||||
#### Contribution
|
|
||||||
|
|
||||||
1. Fork the repository
|
|
||||||
2. Create Feat_xxx branch
|
|
||||||
3. Commit your code
|
|
||||||
4. Create Pull Request
|
|
||||||
|
|
||||||
|
|
||||||
#### Gitee Feature
|
|
||||||
|
|
||||||
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
|
|
||||||
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
|
|
||||||
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
|
|
||||||
4. The most valuable open source project [GVP](https://gitee.com/gvp)
|
|
||||||
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
|
|
||||||
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
|
37
README.md
37
README.md
|
@ -1,39 +1,4 @@
|
||||||
# The new framework
|
# The new framework
|
||||||
|
|
||||||
#### 介绍
|
基于PHP 8.0的新框架。
|
||||||
{**以下是 Gitee 平台说明,您可以替换此简介**
|
|
||||||
Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台
|
|
||||||
无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)}
|
|
||||||
|
|
||||||
#### 软件架构
|
|
||||||
软件架构说明
|
|
||||||
|
|
||||||
|
|
||||||
#### 安装教程
|
|
||||||
|
|
||||||
1. xxxx
|
|
||||||
2. xxxx
|
|
||||||
3. xxxx
|
|
||||||
|
|
||||||
#### 使用说明
|
|
||||||
|
|
||||||
1. xxxx
|
|
||||||
2. xxxx
|
|
||||||
3. xxxx
|
|
||||||
|
|
||||||
#### 参与贡献
|
|
||||||
|
|
||||||
1. Fork 本仓库
|
|
||||||
2. 新建 Feat_xxx 分支
|
|
||||||
3. 提交代码
|
|
||||||
4. 新建 Pull Request
|
|
||||||
|
|
||||||
|
|
||||||
#### 特技
|
|
||||||
|
|
||||||
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
|
|
||||||
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
|
|
||||||
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
|
|
||||||
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
|
|
||||||
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
|
|
||||||
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace apps\home\controllers;
|
||||||
|
|
||||||
|
use top\base\Controller;
|
||||||
|
use top\base\Request;
|
||||||
|
use top\base\Response;
|
||||||
|
use top\base\response\Html;
|
||||||
|
use top\base\response\Json;
|
||||||
|
use top\base\response\Redirect;
|
||||||
|
use top\base\response\Text;
|
||||||
|
|
||||||
|
class News extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
public function lists(Request $request, int $id, string $name, int $id1): Response
|
||||||
|
{
|
||||||
|
return $this->response(Json::class, [
|
||||||
|
'id' => $id,
|
||||||
|
'name' => $name,
|
||||||
|
'id1' => $id1,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(int $id = 0): Response
|
||||||
|
{
|
||||||
|
return $this->response(Text::class, '编辑成功' . $id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add(int $id): Response
|
||||||
|
{
|
||||||
|
return $this->response->setContent('测试123');
|
||||||
|
// return $this->response->setType(Redirect::class)->setContent('https://www.baidu.com');
|
||||||
|
// return $this->response->setType(Html::class)->setContent('<h2>测试</h2>');
|
||||||
|
// return $this->response(Redirect::class, 'https://www.baidu.com');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(int $id): Response
|
||||||
|
{
|
||||||
|
echo '执行到应用' . PHP_EOL;
|
||||||
|
return $this->response(Json::class, [
|
||||||
|
'msg' => '删除'
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function response(string $type, $data, int $httpCode = 200): Response
|
||||||
|
{
|
||||||
|
return $this->response->setType($type)->setHttpCode($httpCode)->setContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/2/24 17:29
|
||||||
|
*/
|
||||||
|
|
||||||
|
use top\facade\Route;
|
||||||
|
|
||||||
|
Route::group('index', function () {
|
||||||
|
Route::get('/', 'home/index/index');
|
||||||
|
});
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>@yield(title)</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
@yield(content)
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php $lists = []; $title = ''; ?>
|
||||||
|
|
||||||
|
@extend(layout)
|
||||||
|
|
||||||
|
@block(title)
|
||||||
|
<?=$title?>
|
||||||
|
@endblock(title)
|
||||||
|
|
||||||
|
@block(content)
|
||||||
|
<ul>
|
||||||
|
<?php foreach ($lists as $item): ?>
|
||||||
|
<li><?=$item?></li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
@endblock(content)
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"apps\\": "./applications/",
|
||||||
|
"top\\": "./framework/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"filp/whoops": "^2.12"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
{
|
||||||
|
"_readme": [
|
||||||
|
"This file locks the dependencies of your project to a known state",
|
||||||
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
|
"This file is @generated automatically"
|
||||||
|
],
|
||||||
|
"content-hash": "0c767e3cfcfb811844efc9dbf3eb5ccc",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "filp/whoops",
|
||||||
|
"version": "2.12.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/filp/whoops.git",
|
||||||
|
"reference": "c13c0be93cff50f88bbd70827d993026821914dd"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/filp/whoops/zipball/c13c0be93cff50f88bbd70827d993026821914dd",
|
||||||
|
"reference": "c13c0be93cff50f88bbd70827d993026821914dd",
|
||||||
|
"shasum": "",
|
||||||
|
"mirrors": [
|
||||||
|
{
|
||||||
|
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||||
|
"preferred": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^5.5.9 || ^7.0 || ^8.0",
|
||||||
|
"psr/log": "^1.0.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"mockery/mockery": "^0.9 || ^1.0",
|
||||||
|
"phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3",
|
||||||
|
"symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"symfony/var-dumper": "Pretty print complex values better with var-dumper available",
|
||||||
|
"whoops/soap": "Formats errors as SOAP responses"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.7-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Whoops\\": "src/Whoops/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Filipe Dobreira",
|
||||||
|
"homepage": "https://github.com/filp",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "php error handling for cool kids",
|
||||||
|
"homepage": "https://filp.github.io/whoops/",
|
||||||
|
"keywords": [
|
||||||
|
"error",
|
||||||
|
"exception",
|
||||||
|
"handling",
|
||||||
|
"library",
|
||||||
|
"throwable",
|
||||||
|
"whoops"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/filp/whoops/issues",
|
||||||
|
"source": "https://github.com/filp/whoops/tree/2.12.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/denis-sokolov",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2021-04-25T12:00:00+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/log",
|
||||||
|
"version": "1.1.4",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/log.git",
|
||||||
|
"reference": "d49695b909c3b7628b6289db5479a1c204601f11"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
|
||||||
|
"reference": "d49695b909c3b7628b6289db5479a1c204601f11",
|
||||||
|
"shasum": "",
|
||||||
|
"mirrors": [
|
||||||
|
{
|
||||||
|
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||||
|
"preferred": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.1.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\Log\\": "Psr/Log/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "https://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interface for logging libraries",
|
||||||
|
"homepage": "https://github.com/php-fig/log",
|
||||||
|
"keywords": [
|
||||||
|
"log",
|
||||||
|
"psr",
|
||||||
|
"psr-3"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/php-fig/log/tree/1.1.4"
|
||||||
|
},
|
||||||
|
"time": "2021-05-03T11:20:27+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": [],
|
||||||
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
|
"platform": [],
|
||||||
|
"platform-dev": [],
|
||||||
|
"plugin-api-version": "2.0.0"
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/1/8/0008 14:32
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\base;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Application
|
||||||
|
* @property Attribute attribute
|
||||||
|
* @property Application app
|
||||||
|
* @property Config config
|
||||||
|
* @property Route route
|
||||||
|
* @property Request request
|
||||||
|
* @property Response response
|
||||||
|
* @property Middleware middleware
|
||||||
|
* @package top
|
||||||
|
*/
|
||||||
|
class Application extends Container
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用命名空间
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected string $namespace = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用名称
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected string $name = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用目录
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected string $path = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定标识
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected array $bind = [
|
||||||
|
'attribute' => Attribute::class,
|
||||||
|
'application' => Application::class,
|
||||||
|
'app' => 'application',
|
||||||
|
'config' => Config::class,
|
||||||
|
'route' => Route::class,
|
||||||
|
'request' => Request::class,
|
||||||
|
'response' => Response::class,
|
||||||
|
'middleware' => Middleware::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application constructor.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
// 实例化后保证从容器中取出单例
|
||||||
|
static::setInstance($this);
|
||||||
|
|
||||||
|
// 将当前实例放入容器实例池,以实现全局调用同一个实例
|
||||||
|
$this->instance('app', $this);
|
||||||
|
|
||||||
|
// 初始化应用
|
||||||
|
$this->init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化应用
|
||||||
|
*/
|
||||||
|
private function init(): void
|
||||||
|
{
|
||||||
|
// 加载系统函数库
|
||||||
|
require __DIR__ . '/functions/common.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置/获取命名空间
|
||||||
|
* @param string $namespace
|
||||||
|
* @return string|$this
|
||||||
|
*/
|
||||||
|
public function namespace(string $namespace = ''): string|static
|
||||||
|
{
|
||||||
|
if (!$this->namespace) {
|
||||||
|
$this->namespace = $namespace;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
return $this->namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置应用名称
|
||||||
|
* @param string $name
|
||||||
|
* @return string|Application
|
||||||
|
*/
|
||||||
|
public function name(string $name = ''): string|static
|
||||||
|
{
|
||||||
|
if (!$this->name) {
|
||||||
|
$this->name = $name;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置应用目录
|
||||||
|
* @param string $path
|
||||||
|
* @return string|Application
|
||||||
|
*/
|
||||||
|
public function path(string $path = ''): string|static
|
||||||
|
{
|
||||||
|
if (!$this->path) {
|
||||||
|
$this->path = $path;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动应用
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
// $response = app()->route->dispatch();
|
||||||
|
// $responseInstance = app()->response;
|
||||||
|
// if ($response instanceof $responseInstance) {
|
||||||
|
// echo $responseInstance->dispatch($response);
|
||||||
|
// } else {
|
||||||
|
// echo $response;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/1/8/0008 14:20
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\base;
|
||||||
|
|
||||||
|
use ReflectionClass;
|
||||||
|
use ReflectionException;
|
||||||
|
|
||||||
|
class Attribute
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 反射类池
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private array $refClasses = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取到的类注解
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private array $classAttributes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取到的方法注解
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private array $methodAttributes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取类注解
|
||||||
|
* @param string $className
|
||||||
|
* @param string|null $attributeName
|
||||||
|
* @return mixed
|
||||||
|
* @throws ReflectionException
|
||||||
|
*/
|
||||||
|
public function classAttributes(string $className, ?string $attributeName = null): mixed
|
||||||
|
{
|
||||||
|
if (!isset($this->classAttributes[$className])) {
|
||||||
|
$refClass = self::getClassRef($className);
|
||||||
|
foreach ($refClass->getAttributes() as $classAttribute) {
|
||||||
|
$this->classAttributes[$className][$classAttribute->getName()] = $classAttribute->newInstance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $attributeName) {
|
||||||
|
return $this->classAttributes[$className] ?? null;
|
||||||
|
} elseif (isset($this->classAttributes[$className][$attributeName])) {
|
||||||
|
return $this->classAttributes[$className][$attributeName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取方法注解
|
||||||
|
* @param string $className
|
||||||
|
* @param string $methodName
|
||||||
|
* @param string|null $attributeName
|
||||||
|
* @return mixed
|
||||||
|
* @throws ReflectionException
|
||||||
|
*/
|
||||||
|
public function methodAttributes(string $className, string $methodName, ?string $attributeName = null): mixed
|
||||||
|
{
|
||||||
|
if (!isset($this->methodAttributes[$className][$methodName])) {
|
||||||
|
$refClass = self::getClassRef($className);
|
||||||
|
$refMethod = $refClass->getMethod($methodName);
|
||||||
|
foreach ($refMethod->getAttributes() as $methodAttribute) {
|
||||||
|
$this->methodAttributes[$className][$methodName][$methodAttribute->getName()] = $methodAttribute->newInstance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $attributeName) {
|
||||||
|
return $this->methodAttributes[$className][$methodName] ?? null;
|
||||||
|
} elseif (isset($this->methodAttributes[$className][$methodName][$attributeName])) {
|
||||||
|
return $this->methodAttributes[$className][$methodName][$attributeName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取类反射
|
||||||
|
* @param $className
|
||||||
|
* @return ReflectionClass
|
||||||
|
* @throws ReflectionException
|
||||||
|
*/
|
||||||
|
private function getClassRef($className): ReflectionClass
|
||||||
|
{
|
||||||
|
if (!isset($this->refClasses[$className])) {
|
||||||
|
try {
|
||||||
|
$this->refClasses[$className] = new ReflectionClass($className);
|
||||||
|
} catch (ReflectionException $e) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->refClasses[$className];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/1/8/0008 14:22
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\base;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置
|
||||||
|
* Class Config
|
||||||
|
* @package top\base
|
||||||
|
*/
|
||||||
|
class Config
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
echo app()->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,392 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/1/8/0008 13:37
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\base;
|
||||||
|
|
||||||
|
use ArrayAccess;
|
||||||
|
use Closure;
|
||||||
|
use Countable;
|
||||||
|
use ReflectionClass;
|
||||||
|
use ReflectionException;
|
||||||
|
use ReflectionFunction;
|
||||||
|
use ReflectionMethod;
|
||||||
|
use ReflectionParameter;
|
||||||
|
use top\exceptions\app\CallFailed;
|
||||||
|
use top\exceptions\app\ClassNotFound;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 容器类
|
||||||
|
* Class Container
|
||||||
|
* @package top\base
|
||||||
|
*/
|
||||||
|
class Container implements ArrayAccess, Countable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 当前实例
|
||||||
|
* @var null|Container
|
||||||
|
*/
|
||||||
|
protected static ?Container $instance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实例池
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected array $instances = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定标识
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected array $bind = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前实例
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function getInstance(): static
|
||||||
|
{
|
||||||
|
if (is_null(static::$instance)) {
|
||||||
|
static::$instance = new static();
|
||||||
|
}
|
||||||
|
|
||||||
|
return static::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置当前实例
|
||||||
|
* @param Container $instance
|
||||||
|
*/
|
||||||
|
protected static function setInstance(Container $instance): void
|
||||||
|
{
|
||||||
|
static::$instance = $instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定标识到容器
|
||||||
|
* @param string $name
|
||||||
|
* @param callable|object|string $bind
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function bind(string $name, callable|object|string $bind): static
|
||||||
|
{
|
||||||
|
if (!isset($this->bind[$name])) {
|
||||||
|
$this->bind[$name] = $bind;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定实例到容器
|
||||||
|
* @param string $name
|
||||||
|
* @param object $bind
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function instance(string $name, object $bind): static
|
||||||
|
{
|
||||||
|
$name = $this->getAlias($name);
|
||||||
|
|
||||||
|
$this->instances[$name] = $bind;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取别名
|
||||||
|
* @param string $name
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAlias(string $name): string
|
||||||
|
{
|
||||||
|
if (isset($this->bind[$name])) {
|
||||||
|
$bind = $this->bind[$name];
|
||||||
|
|
||||||
|
if (is_string($bind)) {
|
||||||
|
return $this->getAlias($bind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取类实例
|
||||||
|
* @param string $name
|
||||||
|
* @param array $args
|
||||||
|
* @param bool $newInstance
|
||||||
|
* @return mixed
|
||||||
|
* @throws CallFailed
|
||||||
|
* @throws ClassNotFound
|
||||||
|
*/
|
||||||
|
public function make(string $name, array $args = [], bool $newInstance = false): mixed
|
||||||
|
{
|
||||||
|
// 获取到最终类名
|
||||||
|
$class = $this->getAlias($name);
|
||||||
|
|
||||||
|
// 检查类是否已经实例化,已经实例化则直接返回(需要新的实例则跳过此步)
|
||||||
|
if (isset($this->instances[$class]) && !$newInstance) {
|
||||||
|
return $this->instances[$class];
|
||||||
|
}
|
||||||
|
// 如果标识绑定到了闭包,则直接执行
|
||||||
|
if (isset($this->bind[$class]) && $this->bind[$class] instanceof Closure) {
|
||||||
|
$instance = $this->invokeFunction($this->bind[$class], $args);
|
||||||
|
} else {
|
||||||
|
// 非闭包则表示当前类名从未进行实例化,这里进行实例化
|
||||||
|
$instance = $this->createInstance($class, $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加入到实例池
|
||||||
|
if (!$newInstance && is_object($instance)) {
|
||||||
|
$this->instance($class, $instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建类实例
|
||||||
|
* @param string $className
|
||||||
|
* @param array $args
|
||||||
|
* @return object
|
||||||
|
* @throws CallFailed
|
||||||
|
* @throws ClassNotFound
|
||||||
|
*/
|
||||||
|
private function createInstance(string $className, array $args = []): object
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$reflectionClass = new ReflectionClass($className);
|
||||||
|
} catch (ReflectionException) {
|
||||||
|
throw new ClassNotFound(sprintf("类 '%s' 不存在", $className));
|
||||||
|
}
|
||||||
|
if (!$reflectionClass->isInstantiable()) {
|
||||||
|
throw new CallFailed(sprintf("类 '%s' 不能被实例化", $className));
|
||||||
|
}
|
||||||
|
// 检查是否存在构造方法,没有则直接返回实例
|
||||||
|
$construct = $reflectionClass->getConstructor();
|
||||||
|
if (is_null($construct)) {
|
||||||
|
return new $className;
|
||||||
|
}
|
||||||
|
// 准备调用参数进行注入
|
||||||
|
$parameters = $construct->getParameters();
|
||||||
|
$callParameters = $this->buildParameters($parameters, $args);
|
||||||
|
|
||||||
|
// 返回实例
|
||||||
|
try {
|
||||||
|
return $reflectionClass->newInstanceArgs($callParameters);
|
||||||
|
} catch (ReflectionException) {
|
||||||
|
throw new CallFailed(sprintf("构建类 '%s' 实例失败", $className));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建调用参数
|
||||||
|
* @param array|ReflectionParameter $reflectionParameter
|
||||||
|
* @param array $args
|
||||||
|
* @return array
|
||||||
|
* @throws CallFailed
|
||||||
|
*/
|
||||||
|
private function buildParameters(array|ReflectionParameter $reflectionParameter, array $args = []): array
|
||||||
|
{
|
||||||
|
$index = 0;
|
||||||
|
$callParameters = [];
|
||||||
|
/** @var ReflectionParameter $parameter */
|
||||||
|
foreach ($reflectionParameter as $parameter) {
|
||||||
|
$type = (string)$parameter->getType();
|
||||||
|
$name = $parameter->getName();
|
||||||
|
try {
|
||||||
|
// 一、明确指定参数
|
||||||
|
// 1、如果在当前主动调用参数明确指定了参数,则直接使用
|
||||||
|
// 二、未明确指定参数
|
||||||
|
// 1、如果是类,则判断主动调用参数是否符合约定类型
|
||||||
|
// 是则直接使用,否则实例化一个新的对象作为参数
|
||||||
|
// 2、不是类则按顺序赋值使用
|
||||||
|
// 3、不是类且未主动传递参数则直接取默认值
|
||||||
|
if (isset($args[$name])) {
|
||||||
|
$callParameters[$name] = $args[$name];
|
||||||
|
} else {
|
||||||
|
// 参数是一个类实例
|
||||||
|
if (class_exists($type)) {
|
||||||
|
// 当前主动调用参数是当前约定类型则使用,否则直接构造
|
||||||
|
$instance = $this->make($type);
|
||||||
|
if (isset($args[$index]) && ($args[$index] instanceof $instance)) {
|
||||||
|
$callParameters[$name] = $args[$index];
|
||||||
|
$index++;
|
||||||
|
} else {
|
||||||
|
$callParameters[$name] = $instance;
|
||||||
|
}
|
||||||
|
} elseif (isset($args[$index])) {
|
||||||
|
$callParameters[$name] = $args[$index];
|
||||||
|
$index++;
|
||||||
|
} else {
|
||||||
|
// 如果不是类实例或没有主动调用参数则使用默认值
|
||||||
|
$callParameters[$name] = $parameter->getDefaultValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ReflectionException) {
|
||||||
|
// 没有默认值
|
||||||
|
$class = $parameter->getDeclaringClass()->getName();
|
||||||
|
$method = $parameter->getDeclaringFunction()->getName();
|
||||||
|
throw new CallFailed(sprintf("'%s::%s()' 缺少 '%s' 参数", $class, $method, $name));
|
||||||
|
} catch (ClassNotFound) {
|
||||||
|
throw new CallFailed(sprintf("类 '%s' 不存在", $type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $callParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行函数
|
||||||
|
* @param mixed $function
|
||||||
|
* @param array $args
|
||||||
|
* @return mixed
|
||||||
|
* @throws CallFailed
|
||||||
|
*/
|
||||||
|
public function invokeFunction(mixed $function, array $args = []): mixed
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$funcRef = new ReflectionFunction($function);
|
||||||
|
// 自动注入参数
|
||||||
|
$args = $this->buildParameters($funcRef->getParameters(), $args);
|
||||||
|
return $funcRef->invokeArgs($args);
|
||||||
|
} catch (ReflectionException) {
|
||||||
|
throw new CallFailed("函数调用失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行类方法
|
||||||
|
* @param string|object $className
|
||||||
|
* @param string $methodName
|
||||||
|
* @param array $args
|
||||||
|
* @return mixed
|
||||||
|
* @throws CallFailed
|
||||||
|
* @throws ClassNotFound
|
||||||
|
*/
|
||||||
|
public function invokeMethod(string|object $className, string $methodName, array $args = []): mixed
|
||||||
|
{
|
||||||
|
$className = is_object($className) ? $className::class : $this->getAlias($className);
|
||||||
|
$instance = $this->make($className);
|
||||||
|
if (!method_exists($instance, $methodName)) {
|
||||||
|
throw new CallFailed(sprintf("方法 '%s::%s()' 不存在", $className, $methodName));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$ref = new ReflectionMethod($className, $methodName);
|
||||||
|
// 自动注入参数
|
||||||
|
$args = $this->buildParameters($ref->getParameters(), $args);
|
||||||
|
return $ref->invokeArgs($instance, $args);
|
||||||
|
} catch (ReflectionException) {
|
||||||
|
throw new CallFailed(sprintf("方法 '%s::%s()' 调用失败", $className, $methodName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用成员变量方式获取实例
|
||||||
|
* @param string $name
|
||||||
|
* @return mixed
|
||||||
|
* @throws CallFailed
|
||||||
|
* @throws ClassNotFound
|
||||||
|
*/
|
||||||
|
public function __get(string $name): mixed
|
||||||
|
{
|
||||||
|
if (isset($this->bind[$name])) {
|
||||||
|
return $this->make($name);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定实例
|
||||||
|
* @param string $name
|
||||||
|
* @param $value
|
||||||
|
*/
|
||||||
|
public function __set(string $name, $value): void
|
||||||
|
{
|
||||||
|
$this->bind($name, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置绑定标识
|
||||||
|
* @param string $name
|
||||||
|
* @param mixed $value
|
||||||
|
*/
|
||||||
|
public function set(string $name, mixed $value): void
|
||||||
|
{
|
||||||
|
$this->bind($name, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否存在绑定标识
|
||||||
|
* @param string $name
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function has(string $name = ''): bool
|
||||||
|
{
|
||||||
|
return isset($this->bind[$name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除绑定标识
|
||||||
|
* @param string $name
|
||||||
|
*/
|
||||||
|
public function remove(string $name): void
|
||||||
|
{
|
||||||
|
$alias = $this->getAlias($name);
|
||||||
|
unset($this->instances[$alias]);
|
||||||
|
unset($this->bind[$name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否存在绑定标识
|
||||||
|
* @param mixed $offset
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function offsetExists(mixed $offset): bool
|
||||||
|
{
|
||||||
|
return $this->has($offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取实例
|
||||||
|
* @param mixed $offset
|
||||||
|
* @return mixed
|
||||||
|
* @throws CallFailed
|
||||||
|
* @throws ClassNotFound
|
||||||
|
*/
|
||||||
|
public function offsetGet(mixed $offset): mixed
|
||||||
|
{
|
||||||
|
return $this->make($offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定标识
|
||||||
|
* @param mixed $offset
|
||||||
|
* @param mixed $value
|
||||||
|
*/
|
||||||
|
public function offsetSet(mixed $offset, mixed $value): void
|
||||||
|
{
|
||||||
|
$this->bind($offset, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除标识
|
||||||
|
* @param mixed $offset
|
||||||
|
*/
|
||||||
|
public function offsetUnset(mixed $offset): void
|
||||||
|
{
|
||||||
|
$this->remove($offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回容器中实例数量
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
return count($this->instances);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace top\base;
|
||||||
|
|
||||||
|
use top\base\response\Json;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础控制器
|
||||||
|
* Class Controller
|
||||||
|
* @package top\base
|
||||||
|
*/
|
||||||
|
class Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected Request $request,
|
||||||
|
protected \top\facade\Response $response,
|
||||||
|
protected \top\facade\Application $application,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/3/13 11:34
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\base;
|
||||||
|
|
||||||
|
use ReflectionException;
|
||||||
|
use top\exceptions\app\CallFailed;
|
||||||
|
use top\exceptions\app\ClassNotFound;
|
||||||
|
use top\exceptions\app\ClassNotInstantiable;
|
||||||
|
use top\exceptions\app\DefaultValueMissing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 门面
|
||||||
|
* Class Facade
|
||||||
|
* @package top\base
|
||||||
|
*/
|
||||||
|
class Facade
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 代表类名
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function getRepresentName(): string
|
||||||
|
{
|
||||||
|
return static::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造类实例
|
||||||
|
* @return mixed
|
||||||
|
* @throws CallFailed
|
||||||
|
* @throws ClassNotFound
|
||||||
|
*/
|
||||||
|
private static function makeInstance(): mixed
|
||||||
|
{
|
||||||
|
return Container::getInstance()->make(static::getRepresentName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行方法
|
||||||
|
* @param string $name
|
||||||
|
* @param array $arguments
|
||||||
|
* @return mixed
|
||||||
|
* @throws CallFailed
|
||||||
|
*/
|
||||||
|
private static function invoke(string $name, array $arguments = []): mixed
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$instance = self::makeInstance();
|
||||||
|
return Container::getInstance()->invokeMethod($instance, $name, $arguments);
|
||||||
|
} catch (CallFailed | ClassNotFound $exception) {
|
||||||
|
throw new CallFailed($exception->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 方法静态调用
|
||||||
|
* @param string $name
|
||||||
|
* @param array $arguments
|
||||||
|
* @return mixed
|
||||||
|
* @throws CallFailed
|
||||||
|
*/
|
||||||
|
public static function __callStatic(string $name, array $arguments): mixed
|
||||||
|
{
|
||||||
|
return self::invoke($name, $arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 方法动态调用
|
||||||
|
* @param $name
|
||||||
|
* @param $arguments
|
||||||
|
* @return mixed
|
||||||
|
* @throws CallFailed
|
||||||
|
*/
|
||||||
|
public function __call($name, $arguments)
|
||||||
|
{
|
||||||
|
return self::invoke($name, $arguments);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/2/24 17:11
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\base;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use top\exceptions\app\CallFailed;
|
||||||
|
use top\exceptions\BaseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中间件
|
||||||
|
* Class Middleware
|
||||||
|
* @package top\base
|
||||||
|
*/
|
||||||
|
class Middleware
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware constructor.
|
||||||
|
* @param Application $application
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
private Application $application,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中间件
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private array $middlewares = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加中间件
|
||||||
|
* @param string|array $middlewares
|
||||||
|
*/
|
||||||
|
public function addMiddleware(string|array $middlewares)
|
||||||
|
{
|
||||||
|
if (is_array($middlewares)) {
|
||||||
|
$this->middlewares = array_merge($this->middlewares, $middlewares);
|
||||||
|
} else {
|
||||||
|
$this->middlewares[] = $middlewares;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行中间件
|
||||||
|
* @param Closure $application
|
||||||
|
* @return Response
|
||||||
|
* @throws CallFailed
|
||||||
|
*/
|
||||||
|
public function handler(Closure $application): Response
|
||||||
|
{
|
||||||
|
// 加载全局配置文件中配置的中间件
|
||||||
|
$middlewares = array_reverse($this->middlewares);
|
||||||
|
|
||||||
|
$requestInstance = $this->application->request;
|
||||||
|
$responseInstance = $this->application->response;
|
||||||
|
// 打包应用
|
||||||
|
$next = $application;
|
||||||
|
foreach ($middlewares as $middleware) {
|
||||||
|
$next = function () use ($requestInstance, $responseInstance, $middleware, $next) {
|
||||||
|
$response = (new $middleware)->handler($requestInstance, $next);
|
||||||
|
if ($response instanceof $responseInstance) {
|
||||||
|
return $response;
|
||||||
|
} else {
|
||||||
|
throw new BaseException(sprintf("中间件必须返回 '%s' 实例", $responseInstance::class));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->application->invokeFunction($next);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/1/8/0008 14:47
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\base;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求
|
||||||
|
* Class Request
|
||||||
|
* @package top
|
||||||
|
*/
|
||||||
|
class Request
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* HTTP Header
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private array $header;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SERVER变量
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private array $server;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET变量
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private array $get;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST变量
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private array $post;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FILES变量
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private array $files;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Body内容
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private string $raw;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求模块
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private string $module = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求控制器
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private string $controller = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求方法
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private string $method = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求参数
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private array $parameters = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request constructor.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->header = [];
|
||||||
|
$this->server = $_SERVER;
|
||||||
|
$this->get = $_GET;
|
||||||
|
$this->post = $_POST;
|
||||||
|
$this->files = $_FILES;
|
||||||
|
$this->raw = file_get_contents('php://input');
|
||||||
|
|
||||||
|
$this->setMethod($_SERVER['REQUEST_METHOD'] ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取访问地址
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function url(): string
|
||||||
|
{
|
||||||
|
return $_SERVER['REQUEST_URI'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取模块名称
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getModule(): string
|
||||||
|
{
|
||||||
|
return $this->module;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置模块
|
||||||
|
* @param string $module
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setModule(string $module): static
|
||||||
|
{
|
||||||
|
$this->module = $module;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取控制器名称
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getController(): string
|
||||||
|
{
|
||||||
|
return $this->controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置控制器
|
||||||
|
* @param string $controller
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setController(string $controller): static
|
||||||
|
{
|
||||||
|
$this->controller = $controller;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取方法名称
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getMethod(): string
|
||||||
|
{
|
||||||
|
return $this->method;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置方法
|
||||||
|
* @param string $method
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setMethod(string $method): static
|
||||||
|
{
|
||||||
|
$this->method = strtolower($method);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求参数
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getParameters(): array
|
||||||
|
{
|
||||||
|
return $this->parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置请求参数
|
||||||
|
* @param array $parameters
|
||||||
|
*/
|
||||||
|
public function setParameters(array $parameters): void
|
||||||
|
{
|
||||||
|
$this->parameters = array_merge($this->parameters, $parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,245 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/1/8/0008 14:48
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\base;
|
||||||
|
|
||||||
|
use JetBrains\PhpStorm\Pure;
|
||||||
|
use top\base\response\Html;
|
||||||
|
use top\base\response\Redirect;
|
||||||
|
use top\base\response\Text;
|
||||||
|
use top\traits\Instance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应类
|
||||||
|
* Class Response
|
||||||
|
* @package top\base
|
||||||
|
*/
|
||||||
|
class Response
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 当前响应类型
|
||||||
|
* @var Response
|
||||||
|
*/
|
||||||
|
private Response $type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应header
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected array $headers = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应内容
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected string $content = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Http版本
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected string $httpLevel = 'HTTP/1.1';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Http状态码
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected int $httpCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Http状态码详情
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected array $httpStatus = [
|
||||||
|
100 => 'Continue',
|
||||||
|
101 => 'Switching Protocols',
|
||||||
|
200 => 'OK',
|
||||||
|
201 => 'Created',
|
||||||
|
202 => 'Accepted',
|
||||||
|
203 => 'Non-Authoritative Information',
|
||||||
|
204 => 'No Content',
|
||||||
|
205 => 'Reset Content',
|
||||||
|
206 => 'Partial Content',
|
||||||
|
300 => 'Multiple Choices',
|
||||||
|
301 => 'Moved Permanently',
|
||||||
|
302 => 'Moved Temporarily ',
|
||||||
|
303 => 'See Other',
|
||||||
|
304 => 'Not Modified',
|
||||||
|
305 => 'Use Proxy',
|
||||||
|
307 => 'Temporary Redirect',
|
||||||
|
400 => 'Bad Request',
|
||||||
|
401 => 'Unauthorized',
|
||||||
|
402 => 'Payment Required',
|
||||||
|
403 => 'Forbidden',
|
||||||
|
404 => 'Not Found',
|
||||||
|
405 => 'Method Not Allowed',
|
||||||
|
406 => 'Not Acceptable',
|
||||||
|
407 => 'Proxy Authentication Required',
|
||||||
|
408 => 'Request Timeout',
|
||||||
|
409 => 'Conflict',
|
||||||
|
410 => 'Gone',
|
||||||
|
411 => 'Length Required',
|
||||||
|
412 => 'Precondition Failed',
|
||||||
|
413 => 'Request Entity Too Large',
|
||||||
|
414 => 'Request-URI Too Long',
|
||||||
|
415 => 'Unsupported Media Type',
|
||||||
|
416 => 'Requested Range Not Satisfiable',
|
||||||
|
417 => 'Expectation Failed',
|
||||||
|
500 => 'Internal Server Error',
|
||||||
|
501 => 'Not Implemented',
|
||||||
|
502 => 'Bad Gateway',
|
||||||
|
503 => 'Service Unavailable',
|
||||||
|
504 => 'Gateway Timeout',
|
||||||
|
505 => 'HTTP Version Not Supported',
|
||||||
|
509 => 'Bandwidth Limit Exceeded',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response constructor.
|
||||||
|
* @param int $httpCode
|
||||||
|
* @param array $headers
|
||||||
|
*/
|
||||||
|
public function __construct(int $httpCode = 200, array $headers = [])
|
||||||
|
{
|
||||||
|
$this->type = $this;
|
||||||
|
$this->httpCode = $httpCode;
|
||||||
|
if (!empty($headers)) {
|
||||||
|
$this->setHeaders($headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置Http状态码
|
||||||
|
* @param int $httpCode
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
public function setHttpCode(int $httpCode): Response
|
||||||
|
{
|
||||||
|
$this->type->httpCode = $httpCode;
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Http状态码
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getHttpCode(): int
|
||||||
|
{
|
||||||
|
return $this->type->httpCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置Http协议版本
|
||||||
|
* @param string $httpLevel
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
public function setHttpLevel(string $httpLevel): Response
|
||||||
|
{
|
||||||
|
$this->type->httpLevel = $httpLevel;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Http协议版本
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getHttpLevel(): string
|
||||||
|
{
|
||||||
|
return $this->type->httpLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置响应类型
|
||||||
|
* @param string $type
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setType(string $type): Response|static
|
||||||
|
{
|
||||||
|
$this->type = app()->make($type);
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取响应类型
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
public function getType(): Response
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置当前响应header
|
||||||
|
* @param array $headers
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
public function setHeaders(array $headers = []): Response
|
||||||
|
{
|
||||||
|
$this->type->headers = array_merge($this->headers, $headers);
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取响应header
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getHeaders(): array
|
||||||
|
{
|
||||||
|
return $this->type->headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置响应内容
|
||||||
|
* @param mixed $content
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setContent(mixed $content): static
|
||||||
|
{
|
||||||
|
$this->type->content = $content;
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取响应内容
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getContent(): string
|
||||||
|
{
|
||||||
|
return $this->type->content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应header到客户端
|
||||||
|
*/
|
||||||
|
private function outputHeaders(): void
|
||||||
|
{
|
||||||
|
$initHeader = $this->type->httpCode . ' ' . $this->type->httpStatus[$this->type->httpCode];
|
||||||
|
$this->type->setHeaders([
|
||||||
|
"{$this->type->httpLevel} $initHeader",
|
||||||
|
"Status $initHeader",
|
||||||
|
]);
|
||||||
|
foreach ($this->type->headers as $key => $header) {
|
||||||
|
if (is_numeric($key)) {
|
||||||
|
header($header);
|
||||||
|
} else {
|
||||||
|
header("$key: $header");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行响应
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function send(): string
|
||||||
|
{
|
||||||
|
// 响应Header
|
||||||
|
$this->type->outputHeaders();
|
||||||
|
|
||||||
|
return $this->type->getContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,261 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/1/8/0008 11:05
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\base;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use top\base\response\Redirect;
|
||||||
|
use top\exceptions\app\CallFailed;
|
||||||
|
use top\exceptions\app\ClassNotFound;
|
||||||
|
use top\exceptions\route\RouteNotFound;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 路由
|
||||||
|
* Class Route
|
||||||
|
* @package top\base
|
||||||
|
*/
|
||||||
|
class Route
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 当前组
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private static array $currentGroupName = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 路由列表
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private static array $routeList = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route constructor.
|
||||||
|
* @param Application $application
|
||||||
|
* @param Request $request
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
private Application $application,
|
||||||
|
private Request $request,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET
|
||||||
|
* @param string $rule
|
||||||
|
* @param mixed $route
|
||||||
|
* @return Route
|
||||||
|
*/
|
||||||
|
public static function get(string $rule, mixed $route): Route
|
||||||
|
{
|
||||||
|
return self::addRoute('get', $rule, $route);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST
|
||||||
|
* @param string $rule
|
||||||
|
* @param mixed $route
|
||||||
|
* @return Route
|
||||||
|
*/
|
||||||
|
public static function post(string $rule, mixed $route): Route
|
||||||
|
{
|
||||||
|
return self::addRoute('post', $rule, $route);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PUT
|
||||||
|
* @param string $rule
|
||||||
|
* @param mixed $route
|
||||||
|
* @return Route
|
||||||
|
*/
|
||||||
|
public static function put(string $rule, mixed $route): Route
|
||||||
|
{
|
||||||
|
return self::addRoute('put', $rule, $route);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE
|
||||||
|
* @param string $rule
|
||||||
|
* @param mixed $route
|
||||||
|
* @return Route
|
||||||
|
*/
|
||||||
|
public static function delete(string $rule, mixed $route): Route
|
||||||
|
{
|
||||||
|
return self::addRoute('delete', $rule, $route);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加路由到列表
|
||||||
|
* @param string $type
|
||||||
|
* @param string $rule
|
||||||
|
* @param mixed $route
|
||||||
|
* @return Route
|
||||||
|
*/
|
||||||
|
private static function addRoute(string $type, string $rule, mixed $route): Route
|
||||||
|
{
|
||||||
|
$fullGroupName = implode('/', self::$currentGroupName);
|
||||||
|
$rule = ($rule != '/') ? trim($rule, '/') : $rule;
|
||||||
|
$currentGroup = ($fullGroupName) ? $fullGroupName . '/' . $rule : $rule;
|
||||||
|
|
||||||
|
self::$routeList[$type][$currentGroup] = $route;
|
||||||
|
|
||||||
|
return app()->route;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 路由分组
|
||||||
|
* @param string $name
|
||||||
|
* @param callable $closure
|
||||||
|
*/
|
||||||
|
public static function group(string $name, callable $closure): void
|
||||||
|
{
|
||||||
|
self::$currentGroupName[] = trim($name, '/');
|
||||||
|
call_user_func($closure);
|
||||||
|
array_pop(self::$currentGroupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前访问方式的所有路由规则
|
||||||
|
* @param $method
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getRulesByMethod($method): array
|
||||||
|
{
|
||||||
|
return self::$routeList[$method] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析链接自带参数
|
||||||
|
* @param string $url
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function parseUrlParameters(string $url): array
|
||||||
|
{
|
||||||
|
// 去除两头的/,保证匹配时和路由规则的统一
|
||||||
|
$url = ($url != '/') ? trim($url, '/') : $url;
|
||||||
|
$parameters = [];
|
||||||
|
$urlComponents = parse_url($url);
|
||||||
|
if (isset($urlComponents['query']) && $urlComponents['query']) {
|
||||||
|
parse_str($urlComponents['query'], $parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$urlComponents['path'], $parameters];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 匹配路由规则
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
private function checkRoute(): mixed
|
||||||
|
{
|
||||||
|
list($url, $parameters) = $this->parseUrlParameters($this->request->url());
|
||||||
|
// 获取当前请求方式的所有路由规则
|
||||||
|
$rules = $this->getRulesByMethod($this->request->getMethod());
|
||||||
|
$currentRule = '';
|
||||||
|
foreach ($rules as $rule => $route) {
|
||||||
|
if (str_contains($rule, ':')) { // 规则表达式
|
||||||
|
preg_match_all('#:\w+#', $rule, $keys);
|
||||||
|
$pattern = preg_replace('#:[\d|\w]+#', '(((?!/).)*)', $rule);
|
||||||
|
preg_match("#$pattern#", $url, $match);
|
||||||
|
// 无匹配则直接开始下次匹配
|
||||||
|
if (empty($match)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 重建数组索引
|
||||||
|
unset($match[0]);
|
||||||
|
$match = array_merge($match, []);
|
||||||
|
// 在匹配时第一次解析出参数
|
||||||
|
$idx = 0;
|
||||||
|
foreach ($keys[0] as $parameterName) {
|
||||||
|
$parameters[ltrim($parameterName, ':')] = $match[$idx];
|
||||||
|
$idx += 2;
|
||||||
|
}
|
||||||
|
} else { // 正则表达式
|
||||||
|
$pattern = "#^$rule$#";
|
||||||
|
preg_match($pattern, $url, $match);
|
||||||
|
if (empty($match[0])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$currentRule = $route;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// 设置请求参数
|
||||||
|
$this->request->setParameters($parameters);
|
||||||
|
|
||||||
|
return $currentRule;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行路由
|
||||||
|
* @return Closure
|
||||||
|
* @throws RouteNotFound
|
||||||
|
*/
|
||||||
|
public function dispatch(): Closure
|
||||||
|
{
|
||||||
|
// 检查当前路由,获取到当前配置的路由信息
|
||||||
|
$rule = $this->checkRoute();
|
||||||
|
if ($rule == '') {
|
||||||
|
throw new RouteNotFound(sprintf("请求 '%s' 无法处理", $this->request->url()));
|
||||||
|
}
|
||||||
|
// 打包应用
|
||||||
|
return function () use ($rule) {
|
||||||
|
$response = null;
|
||||||
|
if (is_callable($rule)) {
|
||||||
|
$response = $this->invokeFunction($rule);
|
||||||
|
} elseif (is_array($rule)) { // 路由被定义为执行类方法
|
||||||
|
$controller = $rule[0] ?? 'Index';
|
||||||
|
$method = $rule[1] ?? 'index';
|
||||||
|
$response = $this->invokeClass($controller, $method);
|
||||||
|
} elseif (str_starts_with($rule, '/') || preg_match('/http[s]?:\/\//', $rule, $match)) {
|
||||||
|
// TODO 重定向
|
||||||
|
$response = $this->application->response
|
||||||
|
->setType(Redirect::class)
|
||||||
|
->setHttpCode(301)
|
||||||
|
->setContent($rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果路由处理结果为Response对象则直接返回,否则返回空的Response对象
|
||||||
|
if ($response instanceof $this->application->response) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->application->response;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类方法
|
||||||
|
* @param string $name
|
||||||
|
* @param string $method
|
||||||
|
* @return mixed
|
||||||
|
* @throws CallFailed
|
||||||
|
* @throws ClassNotFound
|
||||||
|
*/
|
||||||
|
private function invokeClass(string $name, string $method): mixed
|
||||||
|
{
|
||||||
|
// 解析路由规则携带的固定参数
|
||||||
|
list($method, $fixedParameters) = $this->parseUrlParameters($method);
|
||||||
|
if (!empty($fixedParameters)) {
|
||||||
|
$this->request->setParameters($fixedParameters);
|
||||||
|
}
|
||||||
|
$requestParameters = $this->request->getParameters();
|
||||||
|
// 调用类方法
|
||||||
|
return $this->application->invokeMethod($name, $method, $requestParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 闭包函数
|
||||||
|
* @param Closure $closure
|
||||||
|
* @return mixed
|
||||||
|
* @throws CallFailed
|
||||||
|
*/
|
||||||
|
private function invokeFunction(Closure $closure): mixed
|
||||||
|
{
|
||||||
|
return $this->application->invokeFunction($closure);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/2/24 14:21
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\base\attributes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Attribute
|
||||||
|
* @package top\base\attributes
|
||||||
|
*/
|
||||||
|
class Attribute
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 传入的参数
|
||||||
|
* @var mixed
|
||||||
|
*/
|
||||||
|
private mixed $data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute constructor.
|
||||||
|
* @param mixed ...$args
|
||||||
|
*/
|
||||||
|
public function __construct(...$args)
|
||||||
|
{
|
||||||
|
$this->data = $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取参数
|
||||||
|
* @param int $index
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getAttributeVar(int $index = 0): mixed
|
||||||
|
{
|
||||||
|
return $this->data[$index] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取注解名
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAttributeName(): string
|
||||||
|
{
|
||||||
|
return substr(static::class, strrpos(static::class, '\\') + 1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/3/13 11:08
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\base\attributes\response;
|
||||||
|
|
||||||
|
#[\Attribute(\Attribute::TARGET_METHOD)]
|
||||||
|
class ResponseType
|
||||||
|
{
|
||||||
|
|
||||||
|
private array $type;
|
||||||
|
private array $list;
|
||||||
|
|
||||||
|
public function __construct(array $type)
|
||||||
|
{
|
||||||
|
$this->type = $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInstance()
|
||||||
|
{
|
||||||
|
$type = $_GET['type'];
|
||||||
|
return app()->make($this->type[$type]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/1/8/0008 16:37
|
||||||
|
*/
|
||||||
|
|
||||||
|
use top\base\Container;
|
||||||
|
use top\base\Response;
|
||||||
|
use top\base\response\Json;
|
||||||
|
use top\exceptions\app\CallFailed;
|
||||||
|
use top\exceptions\app\ClassNotFound;
|
||||||
|
use top\base\Application;
|
||||||
|
|
||||||
|
if (!function_exists('app')) {
|
||||||
|
/**
|
||||||
|
* 获取App对象
|
||||||
|
* @param string $name
|
||||||
|
* @param array $args
|
||||||
|
* @param bool $newInstance
|
||||||
|
* @return Application|null
|
||||||
|
*/
|
||||||
|
function app(string $name = 'application', array $args = [], bool $newInstance = false): ?Application
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return Application::getInstance()->make($name, $args, $newInstance);
|
||||||
|
} catch (CallFailed | ClassNotFound) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('response')) {
|
||||||
|
/**
|
||||||
|
* 响应助手函数
|
||||||
|
* @param mixed $content
|
||||||
|
* @param string $type
|
||||||
|
* @param int $code
|
||||||
|
* @param array $headers
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
function response(mixed $content, string $type = Json::class, int $code = 200, array $headers = []): Response
|
||||||
|
{
|
||||||
|
return Application::getInstance()->response
|
||||||
|
->setHeaders($headers)
|
||||||
|
->setHttpCode($code)
|
||||||
|
->setType($type)
|
||||||
|
->setContent($content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('param')) {
|
||||||
|
/**
|
||||||
|
* 获取请求中的参数
|
||||||
|
* @param string $name
|
||||||
|
* @param array $except
|
||||||
|
* @param mixed|null $default
|
||||||
|
* @param mixed|null $filter
|
||||||
|
* @return bool|array|null
|
||||||
|
*/
|
||||||
|
function param(string $name = '*', array $except = [], mixed $default = null, mixed $filter = null): bool|array|null
|
||||||
|
{
|
||||||
|
$request = Application::getInstance()->request;
|
||||||
|
$parameters = $request->getParameters();
|
||||||
|
if (str_starts_with($name, '?')) {
|
||||||
|
$name = substr($name, 1);
|
||||||
|
return isset($parameters[$name]);
|
||||||
|
}
|
||||||
|
if ($name == '*') {
|
||||||
|
foreach ($parameters as $key => $parameter) {
|
||||||
|
if (in_array($key, $except)) {
|
||||||
|
unset($parameters[$key]);
|
||||||
|
} else {
|
||||||
|
// TODO 留下的参数在此处进行过滤
|
||||||
|
$parameters[$key] = filter($parameter, $filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $parameters;
|
||||||
|
} else {
|
||||||
|
if (isset($parameters[$name])) {
|
||||||
|
// TODO 参数在此进行过滤
|
||||||
|
return filter($parameters[$name], $filter);
|
||||||
|
} else {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('filter')) {
|
||||||
|
/**
|
||||||
|
* 过滤数据
|
||||||
|
* @param mixed $data
|
||||||
|
* @param mixed|null $filter
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
function filter(mixed $data, mixed $filter = null): mixed
|
||||||
|
{
|
||||||
|
$app = Application::getInstance();
|
||||||
|
if (is_callable($filter) || (is_string($filter) && function_exists($filter))) {
|
||||||
|
return $app->invokeFunction($filter, [$data]);
|
||||||
|
} elseif (is_array($filter)) {
|
||||||
|
$class = $filter[0] ?? '';
|
||||||
|
$method = $filter[1] ?? '';
|
||||||
|
return $app->invokeMethod($class, $method, [$data]);
|
||||||
|
} else {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('get_header')) {
|
||||||
|
/**
|
||||||
|
* 获取请求所有header
|
||||||
|
* @return bool|array
|
||||||
|
*/
|
||||||
|
function get_header(): bool|array
|
||||||
|
{
|
||||||
|
if (PHP_SAPI === 'apache2handler') {
|
||||||
|
$headers = getallheaders();
|
||||||
|
$data = [];
|
||||||
|
foreach ($headers as $key => $value) {
|
||||||
|
$data[strtolower($key)] = $value;
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
} else {
|
||||||
|
$headers = [];
|
||||||
|
foreach ($_SERVER as $key => $value) {
|
||||||
|
if ('http_' == strtolower(substr($key, 0, 5))) {
|
||||||
|
$headers[strtolower(substr($key, 5))] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/1/9/0009 10:01
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\base\response;
|
||||||
|
|
||||||
|
use top\base\Response;
|
||||||
|
|
||||||
|
class Html extends Response
|
||||||
|
{
|
||||||
|
protected array $headers = [
|
||||||
|
'Content-Type' => 'text/html; charset=utf-8'
|
||||||
|
];
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/1/9/0009 10:01
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\base\response;
|
||||||
|
|
||||||
|
use top\base\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Json
|
||||||
|
* @package top\base\response
|
||||||
|
*/
|
||||||
|
class Json extends Response
|
||||||
|
{
|
||||||
|
protected array $headers = [
|
||||||
|
'Content-Type' => 'application/json; charset=utf-8'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function setContent($content): static
|
||||||
|
{
|
||||||
|
return parent::setContent(json_encode($content, 256));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/1/9/0009 10:01
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\base\response;
|
||||||
|
|
||||||
|
use top\base\Response;
|
||||||
|
|
||||||
|
class Raw extends Response
|
||||||
|
{
|
||||||
|
protected array $headers = [
|
||||||
|
'Content-Type' => 'text/explain; charset=utf-8'
|
||||||
|
];
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace top\base\response;
|
||||||
|
|
||||||
|
use top\base\Response;
|
||||||
|
|
||||||
|
class Redirect extends Response
|
||||||
|
{
|
||||||
|
public function setContent($content): static
|
||||||
|
{
|
||||||
|
$this->setHttpCode(301);
|
||||||
|
$this->setHeaders([
|
||||||
|
'Location' => $content,
|
||||||
|
]);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/3/13 15:57
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\base\response;
|
||||||
|
|
||||||
|
use top\base\Response;
|
||||||
|
|
||||||
|
class Text extends Response
|
||||||
|
{
|
||||||
|
protected array $headers = [
|
||||||
|
'Content-Type' => 'text/explain; charset=utf-8'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function setContent($content): static
|
||||||
|
{
|
||||||
|
return parent::setContent($content);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/1/9/0009 10:01
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\base\response;
|
||||||
|
|
||||||
|
use top\base\Response;
|
||||||
|
use top\utils\Xml as XmlUtil;
|
||||||
|
|
||||||
|
class Xml extends Response
|
||||||
|
{
|
||||||
|
protected array $headers = [
|
||||||
|
'Content-Type' => 'text/xml; charset=utf-8'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function setContent($content): static
|
||||||
|
{
|
||||||
|
return parent::setContent(XmlUtil::encode($content));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace top\exceptions;
|
||||||
|
|
||||||
|
class BaseException extends \Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/1/8/0008 16:37
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\exceptions\app;
|
||||||
|
|
||||||
|
use top\exceptions\BaseException;
|
||||||
|
|
||||||
|
class CallFailed extends BaseException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/1/8/0008 16:37
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\exceptions\app;
|
||||||
|
|
||||||
|
use top\exceptions\BaseException;
|
||||||
|
|
||||||
|
class ClassNotFound extends BaseException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/1/8/0008 16:37
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\exceptions\app;
|
||||||
|
|
||||||
|
use top\exceptions\BaseException;
|
||||||
|
|
||||||
|
class ClassNotInstantiable extends BaseException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/1/8/0008 16:37
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\exceptions\app;
|
||||||
|
|
||||||
|
use top\exceptions\BaseException;
|
||||||
|
|
||||||
|
class DefaultValueMissing extends BaseException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace top\exceptions\app;
|
||||||
|
|
||||||
|
use top\exceptions\BaseException;
|
||||||
|
|
||||||
|
class Failed extends BaseException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/1/8/0008 16:38
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\exceptions\app;
|
||||||
|
|
||||||
|
use top\exceptions\BaseException;
|
||||||
|
|
||||||
|
class MethodNotFound extends BaseException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace top\exceptions\route;
|
||||||
|
|
||||||
|
use top\exceptions\BaseException;
|
||||||
|
|
||||||
|
class RouteNotFound extends BaseException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/3/13 13:32
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\facade;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use top\base\Config;
|
||||||
|
use top\base\Container;
|
||||||
|
use top\base\Facade;
|
||||||
|
use top\base\Middleware;
|
||||||
|
use top\base\Request;
|
||||||
|
use top\base\Response;
|
||||||
|
use top\base\Route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Application
|
||||||
|
* @property Attribute attribute
|
||||||
|
* @property Application app
|
||||||
|
* @property Config config
|
||||||
|
* @property Route route
|
||||||
|
* @property Request request
|
||||||
|
* @property Response response
|
||||||
|
* @property Middleware middleware
|
||||||
|
* @method Application path(string $path = '')
|
||||||
|
* @method Application name(string $name = '')
|
||||||
|
* @method string boot()
|
||||||
|
* @method static Container getInstance()
|
||||||
|
* @method static static bind(string $name, callable|object|string $bind)
|
||||||
|
* @method static static instance(string $name, object $bind)
|
||||||
|
* @method string getAlias(string $name)
|
||||||
|
* @method mixed make(string $name, array $args = [], bool $newInstance = false)
|
||||||
|
* @method mixed invokeFunction(Closure $function, array $args = [])
|
||||||
|
* @method mixed invokeMethod(string|object $className, string $methodName, array $args = [])
|
||||||
|
* @package top\facade
|
||||||
|
*/
|
||||||
|
class Application extends Facade
|
||||||
|
{
|
||||||
|
public static function getRepresentName(): string
|
||||||
|
{
|
||||||
|
return 'Application';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace top\facade;
|
||||||
|
|
||||||
|
use top\base\Facade;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Attribute
|
||||||
|
* @method mixed classAttributes(string $className, ?string $attributeName = null)
|
||||||
|
* @method mixed methodAttributes(string $className, string $methodName, ?string $attributeName = null)
|
||||||
|
* @package top\facade
|
||||||
|
*/
|
||||||
|
class Attribute extends Facade
|
||||||
|
{
|
||||||
|
public static function getRepresentName(): string
|
||||||
|
{
|
||||||
|
return 'attribute';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace top\facade;
|
||||||
|
|
||||||
|
use top\base\Facade;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Response
|
||||||
|
* @package top\facade
|
||||||
|
* @method \top\base\Response setHttpCode(int $httpCode)
|
||||||
|
* @method int getHttpCode()
|
||||||
|
* @method \top\base\Response setHttpLevel(string $httpLevel)
|
||||||
|
* @method string getHttpLevel()
|
||||||
|
* @method \top\base\Response setType(string $type)
|
||||||
|
* @method string getType()
|
||||||
|
* @method \top\base\Response setHeaders(array $headers)
|
||||||
|
* @method array getHeaders()
|
||||||
|
* @method \top\base\Response setContent(mixed $content)
|
||||||
|
* @method mixed getContent()
|
||||||
|
* @method string send()
|
||||||
|
*/
|
||||||
|
class Response extends Facade
|
||||||
|
{
|
||||||
|
public static function getRepresentName(): string
|
||||||
|
{
|
||||||
|
return 'response';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/3/13 11:39
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\facade;
|
||||||
|
|
||||||
|
use top\base\Facade;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method static group(string $groupName, $closure)
|
||||||
|
* @method static get(string $rule, mixed $route)
|
||||||
|
* @method static post(string $rule, mixed $route)
|
||||||
|
* @method static put(string $rule, mixed $route)
|
||||||
|
* @method static delete(string $rule, mixed $route)
|
||||||
|
*/
|
||||||
|
class Route extends Facade
|
||||||
|
{
|
||||||
|
public static function getRepresentName(): string
|
||||||
|
{
|
||||||
|
return 'route';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Author: topnuomi
|
||||||
|
* Date: 2021/1/8/0008 15:09
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace top\traits;
|
||||||
|
|
||||||
|
use top\base\Container;
|
||||||
|
use top\exceptions\app\CallFailed;
|
||||||
|
use top\exceptions\app\ClassNotFound;
|
||||||
|
use top\exceptions\app\ClassNotInstantiable;
|
||||||
|
use top\exceptions\app\DefaultValueMissing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单例
|
||||||
|
* Trait Instance
|
||||||
|
* @package top\traits
|
||||||
|
*/
|
||||||
|
trait Instance
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前实例
|
||||||
|
* @var object|null
|
||||||
|
*/
|
||||||
|
private static ?object $instance = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed ...$args
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public static function instance(...$args): static
|
||||||
|
{
|
||||||
|
if (null === self::$instance) {
|
||||||
|
try {
|
||||||
|
self::$instance = Container::getInstance()->make(static::class, $args);
|
||||||
|
} catch (CallFailed | ClassNotFound | ClassNotInstantiable | DefaultValueMissing) {
|
||||||
|
self::$instance = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace top\utils;
|
||||||
|
|
||||||
|
class Xml
|
||||||
|
{
|
||||||
|
public static function encode($params): string
|
||||||
|
{
|
||||||
|
$xml = '<xml>';
|
||||||
|
ksort($params);
|
||||||
|
foreach ($params as $key => $param) {
|
||||||
|
if (is_numeric($param)) {
|
||||||
|
$xml .= '<' . $key . '>' . $param . '</' . $key . '>';
|
||||||
|
} else {
|
||||||
|
$xml .= '<' . $key . '><![CDATA[' . $param . ']]></' . $key . '>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$xml .= '</xml>';
|
||||||
|
return $xml;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
<?php
|
||||||
|
use top\base\Application;
|
||||||
|
use top\base\Request;
|
||||||
|
use top\base\Response;
|
||||||
|
use top\base\response\Redirect;
|
||||||
|
use top\base\Route;
|
||||||
|
use apps\home\controllers\News;
|
||||||
|
use top\base\response\Json;
|
||||||
|
use Whoops\Handler\PrettyPageHandler;
|
||||||
|
use Whoops\Run;
|
||||||
|
|
||||||
|
require './vendor/autoload.php';
|
||||||
|
|
||||||
|
// 美化异常输出
|
||||||
|
// $whoops = new Run();
|
||||||
|
// $whoops->pushHandler(new PrettyPageHandler);
|
||||||
|
// $whoops->register();
|
||||||
|
|
||||||
|
|
||||||
|
$app = new Application();
|
||||||
|
// 执行应用
|
||||||
|
$app->namespace('apps')
|
||||||
|
->path('./applications/')
|
||||||
|
->name('home')
|
||||||
|
->boot();
|
||||||
|
|
||||||
|
Route::get('/', function () {
|
||||||
|
return \response('测试');
|
||||||
|
});
|
||||||
|
// 测试注册路由
|
||||||
|
Route::get('news', [News::class, 'add?id=122']);
|
||||||
|
Route::post('news/:id', function () use ($app): Response {
|
||||||
|
$parameters = $app->request->getParameters();
|
||||||
|
return $app->response->setType(Json::class)->setContent([
|
||||||
|
'msg' => $parameters['id'] . '获取成功',
|
||||||
|
'code' => 10000,
|
||||||
|
'data' => new stdClass(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
Route::put('news/:id', [News::class, 'edit']);
|
||||||
|
Route::delete('news/:id', [News::class, 'delete']);
|
||||||
|
|
||||||
|
Route::group('admin', function () {
|
||||||
|
Route::get('member/edit/:category/:id', '/news');
|
||||||
|
});
|
||||||
|
function aaa($data)
|
||||||
|
{
|
||||||
|
return $data . '-top-nuomi';
|
||||||
|
}
|
||||||
|
// 中间件
|
||||||
|
class TestMiddleware
|
||||||
|
{
|
||||||
|
public function handler(Request $request, $next)
|
||||||
|
{
|
||||||
|
// print_r(param(name: 'name', filter: 'aaa'));
|
||||||
|
// return \response('https://topnuomi.com', \top\base\response\Text::class);
|
||||||
|
// echo 'TestMiddleware' . PHP_EOL;
|
||||||
|
// echo 'TestMiddleware' . PHP_EOL;
|
||||||
|
// print_r($next);
|
||||||
|
return $next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestMiddleware1
|
||||||
|
{
|
||||||
|
public function handler(Request $request, $next)
|
||||||
|
{
|
||||||
|
// echo 'TestMiddleware1' . PHP_EOL;
|
||||||
|
// echo 'TestMiddleware1' . PHP_EOL;
|
||||||
|
return $next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 路由调度,获得打包后的应用
|
||||||
|
$closure = $app->route->dispatch();
|
||||||
|
|
||||||
|
// 测试添加中间件
|
||||||
|
$app->middleware->addMiddleware([
|
||||||
|
TestMiddleware::class,
|
||||||
|
TestMiddleware1::class
|
||||||
|
]);
|
||||||
|
$response = $app->middleware->handler($closure);
|
||||||
|
|
||||||
|
// print_r($response);
|
||||||
|
|
||||||
|
// 响应输出
|
||||||
|
echo $app->response->send();
|
||||||
|
|
||||||
|
// class NewsModel extends Request
|
||||||
|
// {
|
||||||
|
// }
|
||||||
|
// class Request
|
||||||
|
// {
|
||||||
|
// }
|
||||||
|
// class News
|
||||||
|
// {
|
||||||
|
// public function lists(int $a, Request $request, int $page, News $news, int $t = 12345)
|
||||||
|
// {
|
||||||
|
// var_dump($a);
|
||||||
|
// var_dump($page);
|
||||||
|
// var_dump($request);
|
||||||
|
// var_dump($news);
|
||||||
|
// var_dump($t);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// $container = Container::getInstance();
|
||||||
|
// $container->invokeMethod('News', 'lists', [
|
||||||
|
// 100, 1, 321
|
||||||
|
// ]);
|
||||||
|
// $container->bind('test', function ($name = '') {
|
||||||
|
// return new A($name);
|
||||||
|
// });
|
||||||
|
// var_dump($container->make('A', ['王麻子1']));
|
||||||
|
// $a = $container->make('A', ['王麻子'], true);
|
||||||
|
// var_dump($container->make('A', ['王麻子'], true));
|
||||||
|
// var_dump($container->make('A', ['王麻子2']));
|
||||||
|
// var_dump($container->invokeMethod('B', 'method', [
|
||||||
|
// 'i' => 1,
|
||||||
|
// 'a' => new B,
|
||||||
|
// 'j' => 2,
|
||||||
|
// ]));
|
||||||
|
// var_dump($container->make('test'));
|
||||||
|
// var_dump($container->make('test'));
|
||||||
|
// var_dump($container->make('test', ['王麻子3'], true));
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload.php @generated by Composer
|
||||||
|
|
||||||
|
require_once __DIR__ . '/composer/autoload_real.php';
|
||||||
|
|
||||||
|
return ComposerAutoloaderInitdd5637fbb8560d2a3e276d04d58781d5::getLoader();
|
|
@ -0,0 +1,479 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer\Autoload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||||
|
*
|
||||||
|
* $loader = new \Composer\Autoload\ClassLoader();
|
||||||
|
*
|
||||||
|
* // register classes with namespaces
|
||||||
|
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||||
|
* $loader->add('Symfony', __DIR__.'/framework');
|
||||||
|
*
|
||||||
|
* // activate the autoloader
|
||||||
|
* $loader->register();
|
||||||
|
*
|
||||||
|
* // to enable searching the include path (eg. for PEAR packages)
|
||||||
|
* $loader->setUseIncludePath(true);
|
||||||
|
*
|
||||||
|
* In this example, if you try to use a class in the Symfony\Component
|
||||||
|
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||||
|
* the autoloader will first look for the class under the component/
|
||||||
|
* directory, and it will then fallback to the framework/ directory if not
|
||||||
|
* found before giving up.
|
||||||
|
*
|
||||||
|
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien@symfony.com>
|
||||||
|
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
* @see https://www.php-fig.org/psr/psr-0/
|
||||||
|
* @see https://www.php-fig.org/psr/psr-4/
|
||||||
|
*/
|
||||||
|
class ClassLoader
|
||||||
|
{
|
||||||
|
private $vendorDir;
|
||||||
|
|
||||||
|
// PSR-4
|
||||||
|
private $prefixLengthsPsr4 = array();
|
||||||
|
private $prefixDirsPsr4 = array();
|
||||||
|
private $fallbackDirsPsr4 = array();
|
||||||
|
|
||||||
|
// PSR-0
|
||||||
|
private $prefixesPsr0 = array();
|
||||||
|
private $fallbackDirsPsr0 = array();
|
||||||
|
|
||||||
|
private $useIncludePath = false;
|
||||||
|
private $classMap = array();
|
||||||
|
private $classMapAuthoritative = false;
|
||||||
|
private $missingClasses = array();
|
||||||
|
private $apcuPrefix;
|
||||||
|
|
||||||
|
private static $registeredLoaders = array();
|
||||||
|
|
||||||
|
public function __construct($vendorDir = null)
|
||||||
|
{
|
||||||
|
$this->vendorDir = $vendorDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPrefixes()
|
||||||
|
{
|
||||||
|
if (!empty($this->prefixesPsr0)) {
|
||||||
|
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPrefixesPsr4()
|
||||||
|
{
|
||||||
|
return $this->prefixDirsPsr4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFallbackDirs()
|
||||||
|
{
|
||||||
|
return $this->fallbackDirsPsr0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFallbackDirsPsr4()
|
||||||
|
{
|
||||||
|
return $this->fallbackDirsPsr4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClassMap()
|
||||||
|
{
|
||||||
|
return $this->classMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $classMap Class to filename map
|
||||||
|
*/
|
||||||
|
public function addClassMap(array $classMap)
|
||||||
|
{
|
||||||
|
if ($this->classMap) {
|
||||||
|
$this->classMap = array_merge($this->classMap, $classMap);
|
||||||
|
} else {
|
||||||
|
$this->classMap = $classMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a set of PSR-0 directories for a given prefix, either
|
||||||
|
* appending or prepending to the ones previously set for this prefix.
|
||||||
|
*
|
||||||
|
* @param string $prefix The prefix
|
||||||
|
* @param array|string $paths The PSR-0 root directories
|
||||||
|
* @param bool $prepend Whether to prepend the directories
|
||||||
|
*/
|
||||||
|
public function add($prefix, $paths, $prepend = false)
|
||||||
|
{
|
||||||
|
if (!$prefix) {
|
||||||
|
if ($prepend) {
|
||||||
|
$this->fallbackDirsPsr0 = array_merge(
|
||||||
|
(array) $paths,
|
||||||
|
$this->fallbackDirsPsr0
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->fallbackDirsPsr0 = array_merge(
|
||||||
|
$this->fallbackDirsPsr0,
|
||||||
|
(array) $paths
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$first = $prefix[0];
|
||||||
|
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||||
|
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($prepend) {
|
||||||
|
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||||
|
(array) $paths,
|
||||||
|
$this->prefixesPsr0[$first][$prefix]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||||
|
$this->prefixesPsr0[$first][$prefix],
|
||||||
|
(array) $paths
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a set of PSR-4 directories for a given namespace, either
|
||||||
|
* appending or prepending to the ones previously set for this namespace.
|
||||||
|
*
|
||||||
|
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||||
|
* @param array|string $paths The PSR-4 base directories
|
||||||
|
* @param bool $prepend Whether to prepend the directories
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function addPsr4($prefix, $paths, $prepend = false)
|
||||||
|
{
|
||||||
|
if (!$prefix) {
|
||||||
|
// Register directories for the root namespace.
|
||||||
|
if ($prepend) {
|
||||||
|
$this->fallbackDirsPsr4 = array_merge(
|
||||||
|
(array) $paths,
|
||||||
|
$this->fallbackDirsPsr4
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$this->fallbackDirsPsr4 = array_merge(
|
||||||
|
$this->fallbackDirsPsr4,
|
||||||
|
(array) $paths
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||||
|
// Register directories for a new namespace.
|
||||||
|
$length = strlen($prefix);
|
||||||
|
if ('\\' !== $prefix[$length - 1]) {
|
||||||
|
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||||
|
}
|
||||||
|
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||||
|
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||||
|
} elseif ($prepend) {
|
||||||
|
// Prepend directories for an already registered namespace.
|
||||||
|
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||||
|
(array) $paths,
|
||||||
|
$this->prefixDirsPsr4[$prefix]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Append directories for an already registered namespace.
|
||||||
|
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||||
|
$this->prefixDirsPsr4[$prefix],
|
||||||
|
(array) $paths
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a set of PSR-0 directories for a given prefix,
|
||||||
|
* replacing any others previously set for this prefix.
|
||||||
|
*
|
||||||
|
* @param string $prefix The prefix
|
||||||
|
* @param array|string $paths The PSR-0 base directories
|
||||||
|
*/
|
||||||
|
public function set($prefix, $paths)
|
||||||
|
{
|
||||||
|
if (!$prefix) {
|
||||||
|
$this->fallbackDirsPsr0 = (array) $paths;
|
||||||
|
} else {
|
||||||
|
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a set of PSR-4 directories for a given namespace,
|
||||||
|
* replacing any others previously set for this namespace.
|
||||||
|
*
|
||||||
|
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||||
|
* @param array|string $paths The PSR-4 base directories
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function setPsr4($prefix, $paths)
|
||||||
|
{
|
||||||
|
if (!$prefix) {
|
||||||
|
$this->fallbackDirsPsr4 = (array) $paths;
|
||||||
|
} else {
|
||||||
|
$length = strlen($prefix);
|
||||||
|
if ('\\' !== $prefix[$length - 1]) {
|
||||||
|
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||||
|
}
|
||||||
|
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||||
|
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns on searching the include path for class files.
|
||||||
|
*
|
||||||
|
* @param bool $useIncludePath
|
||||||
|
*/
|
||||||
|
public function setUseIncludePath($useIncludePath)
|
||||||
|
{
|
||||||
|
$this->useIncludePath = $useIncludePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be used to check if the autoloader uses the include path to check
|
||||||
|
* for classes.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getUseIncludePath()
|
||||||
|
{
|
||||||
|
return $this->useIncludePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns off searching the prefix and fallback directories for classes
|
||||||
|
* that have not been registered with the class map.
|
||||||
|
*
|
||||||
|
* @param bool $classMapAuthoritative
|
||||||
|
*/
|
||||||
|
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||||
|
{
|
||||||
|
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should class lookup fail if not found in the current class map?
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isClassMapAuthoritative()
|
||||||
|
{
|
||||||
|
return $this->classMapAuthoritative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||||
|
*
|
||||||
|
* @param string|null $apcuPrefix
|
||||||
|
*/
|
||||||
|
public function setApcuPrefix($apcuPrefix)
|
||||||
|
{
|
||||||
|
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getApcuPrefix()
|
||||||
|
{
|
||||||
|
return $this->apcuPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers this instance as an autoloader.
|
||||||
|
*
|
||||||
|
* @param bool $prepend Whether to prepend the autoloader or not
|
||||||
|
*/
|
||||||
|
public function register($prepend = false)
|
||||||
|
{
|
||||||
|
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||||
|
|
||||||
|
if (null === $this->vendorDir) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($prepend) {
|
||||||
|
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
|
||||||
|
} else {
|
||||||
|
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||||
|
self::$registeredLoaders[$this->vendorDir] = $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters this instance as an autoloader.
|
||||||
|
*/
|
||||||
|
public function unregister()
|
||||||
|
{
|
||||||
|
spl_autoload_unregister(array($this, 'loadClass'));
|
||||||
|
|
||||||
|
if (null !== $this->vendorDir) {
|
||||||
|
unset(self::$registeredLoaders[$this->vendorDir]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the given class or interface.
|
||||||
|
*
|
||||||
|
* @param string $class The name of the class
|
||||||
|
* @return bool|null True if loaded, null otherwise
|
||||||
|
*/
|
||||||
|
public function loadClass($class)
|
||||||
|
{
|
||||||
|
if ($file = $this->findFile($class)) {
|
||||||
|
includeFile($file);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the path to the file where the class is defined.
|
||||||
|
*
|
||||||
|
* @param string $class The name of the class
|
||||||
|
*
|
||||||
|
* @return string|false The path if found, false otherwise
|
||||||
|
*/
|
||||||
|
public function findFile($class)
|
||||||
|
{
|
||||||
|
// class map lookup
|
||||||
|
if (isset($this->classMap[$class])) {
|
||||||
|
return $this->classMap[$class];
|
||||||
|
}
|
||||||
|
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (null !== $this->apcuPrefix) {
|
||||||
|
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||||
|
if ($hit) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $this->findFileWithExtension($class, '.php');
|
||||||
|
|
||||||
|
// Search for Hack files if we are running on HHVM
|
||||||
|
if (false === $file && defined('HHVM_VERSION')) {
|
||||||
|
$file = $this->findFileWithExtension($class, '.hh');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $this->apcuPrefix) {
|
||||||
|
apcu_add($this->apcuPrefix.$class, $file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === $file) {
|
||||||
|
// Remember that this class does not exist.
|
||||||
|
$this->missingClasses[$class] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the currently registered loaders indexed by their corresponding vendor directories.
|
||||||
|
*
|
||||||
|
* @return self[]
|
||||||
|
*/
|
||||||
|
public static function getRegisteredLoaders()
|
||||||
|
{
|
||||||
|
return self::$registeredLoaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function findFileWithExtension($class, $ext)
|
||||||
|
{
|
||||||
|
// PSR-4 lookup
|
||||||
|
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||||
|
|
||||||
|
$first = $class[0];
|
||||||
|
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||||
|
$subPath = $class;
|
||||||
|
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||||
|
$subPath = substr($subPath, 0, $lastPos);
|
||||||
|
$search = $subPath . '\\';
|
||||||
|
if (isset($this->prefixDirsPsr4[$search])) {
|
||||||
|
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
||||||
|
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||||
|
if (file_exists($file = $dir . $pathEnd)) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSR-4 fallback dirs
|
||||||
|
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||||
|
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSR-0 lookup
|
||||||
|
if (false !== $pos = strrpos($class, '\\')) {
|
||||||
|
// namespaced class name
|
||||||
|
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||||
|
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||||
|
} else {
|
||||||
|
// PEAR-like class name
|
||||||
|
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->prefixesPsr0[$first])) {
|
||||||
|
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||||
|
if (0 === strpos($class, $prefix)) {
|
||||||
|
foreach ($dirs as $dir) {
|
||||||
|
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSR-0 fallback dirs
|
||||||
|
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||||
|
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSR-0 include paths.
|
||||||
|
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scope isolated include.
|
||||||
|
*
|
||||||
|
* Prevents access to $this/self from included files.
|
||||||
|
*/
|
||||||
|
function includeFile($file)
|
||||||
|
{
|
||||||
|
include $file;
|
||||||
|
}
|
|
@ -0,0 +1,301 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace Composer;
|
||||||
|
|
||||||
|
use Composer\Autoload\ClassLoader;
|
||||||
|
use Composer\Semver\VersionParser;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class InstalledVersions
|
||||||
|
{
|
||||||
|
private static $installed = array (
|
||||||
|
'root' =>
|
||||||
|
array (
|
||||||
|
'pretty_version' => '1.0.0+no-version-set',
|
||||||
|
'version' => '1.0.0.0',
|
||||||
|
'aliases' =>
|
||||||
|
array (
|
||||||
|
),
|
||||||
|
'reference' => NULL,
|
||||||
|
'name' => '__root__',
|
||||||
|
),
|
||||||
|
'versions' =>
|
||||||
|
array (
|
||||||
|
'__root__' =>
|
||||||
|
array (
|
||||||
|
'pretty_version' => '1.0.0+no-version-set',
|
||||||
|
'version' => '1.0.0.0',
|
||||||
|
'aliases' =>
|
||||||
|
array (
|
||||||
|
),
|
||||||
|
'reference' => NULL,
|
||||||
|
),
|
||||||
|
'filp/whoops' =>
|
||||||
|
array (
|
||||||
|
'pretty_version' => '2.12.1',
|
||||||
|
'version' => '2.12.1.0',
|
||||||
|
'aliases' =>
|
||||||
|
array (
|
||||||
|
),
|
||||||
|
'reference' => 'c13c0be93cff50f88bbd70827d993026821914dd',
|
||||||
|
),
|
||||||
|
'psr/log' =>
|
||||||
|
array (
|
||||||
|
'pretty_version' => '1.1.4',
|
||||||
|
'version' => '1.1.4.0',
|
||||||
|
'aliases' =>
|
||||||
|
array (
|
||||||
|
),
|
||||||
|
'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
private static $canGetVendors;
|
||||||
|
private static $installedByVendor = array();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function getInstalledPackages()
|
||||||
|
{
|
||||||
|
$packages = array();
|
||||||
|
foreach (self::getInstalled() as $installed) {
|
||||||
|
$packages[] = array_keys($installed['versions']);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (1 === \count($packages)) {
|
||||||
|
return $packages[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function isInstalled($packageName)
|
||||||
|
{
|
||||||
|
foreach (self::getInstalled() as $installed) {
|
||||||
|
if (isset($installed['versions'][$packageName])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function satisfies(VersionParser $parser, $packageName, $constraint)
|
||||||
|
{
|
||||||
|
$constraint = $parser->parseConstraints($constraint);
|
||||||
|
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
|
||||||
|
|
||||||
|
return $provided->matches($constraint);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function getVersionRanges($packageName)
|
||||||
|
{
|
||||||
|
foreach (self::getInstalled() as $installed) {
|
||||||
|
if (!isset($installed['versions'][$packageName])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ranges = array();
|
||||||
|
if (isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||||
|
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
|
||||||
|
}
|
||||||
|
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
|
||||||
|
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
|
||||||
|
}
|
||||||
|
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
|
||||||
|
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
|
||||||
|
}
|
||||||
|
if (array_key_exists('provided', $installed['versions'][$packageName])) {
|
||||||
|
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(' || ', $ranges);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function getVersion($packageName)
|
||||||
|
{
|
||||||
|
foreach (self::getInstalled() as $installed) {
|
||||||
|
if (!isset($installed['versions'][$packageName])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($installed['versions'][$packageName]['version'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $installed['versions'][$packageName]['version'];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function getPrettyVersion($packageName)
|
||||||
|
{
|
||||||
|
foreach (self::getInstalled() as $installed) {
|
||||||
|
if (!isset($installed['versions'][$packageName])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $installed['versions'][$packageName]['pretty_version'];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function getReference($packageName)
|
||||||
|
{
|
||||||
|
foreach (self::getInstalled() as $installed) {
|
||||||
|
if (!isset($installed['versions'][$packageName])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($installed['versions'][$packageName]['reference'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $installed['versions'][$packageName]['reference'];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function getRootPackage()
|
||||||
|
{
|
||||||
|
$installed = self::getInstalled();
|
||||||
|
|
||||||
|
return $installed[0]['root'];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function getRawData()
|
||||||
|
{
|
||||||
|
return self::$installed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function reload($data)
|
||||||
|
{
|
||||||
|
self::$installed = $data;
|
||||||
|
self::$installedByVendor = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static function getInstalled()
|
||||||
|
{
|
||||||
|
if (null === self::$canGetVendors) {
|
||||||
|
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
|
||||||
|
}
|
||||||
|
|
||||||
|
$installed = array();
|
||||||
|
|
||||||
|
if (self::$canGetVendors) {
|
||||||
|
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
||||||
|
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||||
|
$installed[] = self::$installedByVendor[$vendorDir];
|
||||||
|
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||||
|
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$installed[] = self::$installed;
|
||||||
|
|
||||||
|
return $installed;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_classmap.php @generated by Composer
|
||||||
|
|
||||||
|
$vendorDir = dirname(dirname(__FILE__));
|
||||||
|
$baseDir = dirname($vendorDir);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||||
|
);
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_namespaces.php @generated by Composer
|
||||||
|
|
||||||
|
$vendorDir = dirname(dirname(__FILE__));
|
||||||
|
$baseDir = dirname($vendorDir);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
);
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_psr4.php @generated by Composer
|
||||||
|
|
||||||
|
$vendorDir = dirname(dirname(__FILE__));
|
||||||
|
$baseDir = dirname($vendorDir);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'top\\' => array($baseDir . '/framework'),
|
||||||
|
'apps\\' => array($baseDir . '/applications'),
|
||||||
|
'Whoops\\' => array($vendorDir . '/filp/whoops/src/Whoops'),
|
||||||
|
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
|
||||||
|
);
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_real.php @generated by Composer
|
||||||
|
|
||||||
|
class ComposerAutoloaderInitdd5637fbb8560d2a3e276d04d58781d5
|
||||||
|
{
|
||||||
|
private static $loader;
|
||||||
|
|
||||||
|
public static function loadClassLoader($class)
|
||||||
|
{
|
||||||
|
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||||
|
require __DIR__ . '/ClassLoader.php';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Composer\Autoload\ClassLoader
|
||||||
|
*/
|
||||||
|
public static function getLoader()
|
||||||
|
{
|
||||||
|
if (null !== self::$loader) {
|
||||||
|
return self::$loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
require __DIR__ . '/platform_check.php';
|
||||||
|
|
||||||
|
spl_autoload_register(array('ComposerAutoloaderInitdd5637fbb8560d2a3e276d04d58781d5', 'loadClassLoader'), true, true);
|
||||||
|
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
|
||||||
|
spl_autoload_unregister(array('ComposerAutoloaderInitdd5637fbb8560d2a3e276d04d58781d5', 'loadClassLoader'));
|
||||||
|
|
||||||
|
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
||||||
|
if ($useStaticLoader) {
|
||||||
|
require __DIR__ . '/autoload_static.php';
|
||||||
|
|
||||||
|
call_user_func(\Composer\Autoload\ComposerStaticInitdd5637fbb8560d2a3e276d04d58781d5::getInitializer($loader));
|
||||||
|
} else {
|
||||||
|
$map = require __DIR__ . '/autoload_namespaces.php';
|
||||||
|
foreach ($map as $namespace => $path) {
|
||||||
|
$loader->set($namespace, $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$map = require __DIR__ . '/autoload_psr4.php';
|
||||||
|
foreach ($map as $namespace => $path) {
|
||||||
|
$loader->setPsr4($namespace, $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$classMap = require __DIR__ . '/autoload_classmap.php';
|
||||||
|
if ($classMap) {
|
||||||
|
$loader->addClassMap($classMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$loader->register(true);
|
||||||
|
|
||||||
|
return $loader;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload_static.php @generated by Composer
|
||||||
|
|
||||||
|
namespace Composer\Autoload;
|
||||||
|
|
||||||
|
class ComposerStaticInitdd5637fbb8560d2a3e276d04d58781d5
|
||||||
|
{
|
||||||
|
public static $prefixLengthsPsr4 = array (
|
||||||
|
't' =>
|
||||||
|
array (
|
||||||
|
'top\\' => 4,
|
||||||
|
),
|
||||||
|
'a' =>
|
||||||
|
array (
|
||||||
|
'apps\\' => 5,
|
||||||
|
),
|
||||||
|
'W' =>
|
||||||
|
array (
|
||||||
|
'Whoops\\' => 7,
|
||||||
|
),
|
||||||
|
'P' =>
|
||||||
|
array (
|
||||||
|
'Psr\\Log\\' => 8,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
public static $prefixDirsPsr4 = array (
|
||||||
|
'top\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/../..' . '/framework',
|
||||||
|
),
|
||||||
|
'apps\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/../..' . '/applications',
|
||||||
|
),
|
||||||
|
'Whoops\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/filp/whoops/src/Whoops',
|
||||||
|
),
|
||||||
|
'Psr\\Log\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
public static $classMap = array (
|
||||||
|
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||||
|
);
|
||||||
|
|
||||||
|
public static function getInitializer(ClassLoader $loader)
|
||||||
|
{
|
||||||
|
return \Closure::bind(function () use ($loader) {
|
||||||
|
$loader->prefixLengthsPsr4 = ComposerStaticInitdd5637fbb8560d2a3e276d04d58781d5::$prefixLengthsPsr4;
|
||||||
|
$loader->prefixDirsPsr4 = ComposerStaticInitdd5637fbb8560d2a3e276d04d58781d5::$prefixDirsPsr4;
|
||||||
|
$loader->classMap = ComposerStaticInitdd5637fbb8560d2a3e276d04d58781d5::$classMap;
|
||||||
|
|
||||||
|
}, null, ClassLoader::class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
{
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "filp/whoops",
|
||||||
|
"version": "2.12.1",
|
||||||
|
"version_normalized": "2.12.1.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/filp/whoops.git",
|
||||||
|
"reference": "c13c0be93cff50f88bbd70827d993026821914dd"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/filp/whoops/zipball/c13c0be93cff50f88bbd70827d993026821914dd",
|
||||||
|
"reference": "c13c0be93cff50f88bbd70827d993026821914dd",
|
||||||
|
"shasum": "",
|
||||||
|
"mirrors": [
|
||||||
|
{
|
||||||
|
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||||
|
"preferred": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^5.5.9 || ^7.0 || ^8.0",
|
||||||
|
"psr/log": "^1.0.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"mockery/mockery": "^0.9 || ^1.0",
|
||||||
|
"phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3",
|
||||||
|
"symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"symfony/var-dumper": "Pretty print complex values better with var-dumper available",
|
||||||
|
"whoops/soap": "Formats errors as SOAP responses"
|
||||||
|
},
|
||||||
|
"time": "2021-04-25T12:00:00+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.7-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Whoops\\": "src/Whoops/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Filipe Dobreira",
|
||||||
|
"homepage": "https://github.com/filp",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "php error handling for cool kids",
|
||||||
|
"homepage": "https://filp.github.io/whoops/",
|
||||||
|
"keywords": [
|
||||||
|
"error",
|
||||||
|
"exception",
|
||||||
|
"handling",
|
||||||
|
"library",
|
||||||
|
"throwable",
|
||||||
|
"whoops"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/filp/whoops/issues",
|
||||||
|
"source": "https://github.com/filp/whoops/tree/2.12.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/denis-sokolov",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"install-path": "../filp/whoops"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/log",
|
||||||
|
"version": "1.1.4",
|
||||||
|
"version_normalized": "1.1.4.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/log.git",
|
||||||
|
"reference": "d49695b909c3b7628b6289db5479a1c204601f11"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
|
||||||
|
"reference": "d49695b909c3b7628b6289db5479a1c204601f11",
|
||||||
|
"shasum": "",
|
||||||
|
"mirrors": [
|
||||||
|
{
|
||||||
|
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||||
|
"preferred": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.0"
|
||||||
|
},
|
||||||
|
"time": "2021-05-03T11:20:27+00:00",
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.1.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"installation-source": "dist",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\Log\\": "Psr/Log/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "https://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interface for logging libraries",
|
||||||
|
"homepage": "https://github.com/php-fig/log",
|
||||||
|
"keywords": [
|
||||||
|
"log",
|
||||||
|
"psr",
|
||||||
|
"psr-3"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/php-fig/log/tree/1.1.4"
|
||||||
|
},
|
||||||
|
"install-path": "../psr/log"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"dev-package-names": []
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php return array (
|
||||||
|
'root' =>
|
||||||
|
array (
|
||||||
|
'pretty_version' => '1.0.0+no-version-set',
|
||||||
|
'version' => '1.0.0.0',
|
||||||
|
'aliases' =>
|
||||||
|
array (
|
||||||
|
),
|
||||||
|
'reference' => NULL,
|
||||||
|
'name' => '__root__',
|
||||||
|
),
|
||||||
|
'versions' =>
|
||||||
|
array (
|
||||||
|
'__root__' =>
|
||||||
|
array (
|
||||||
|
'pretty_version' => '1.0.0+no-version-set',
|
||||||
|
'version' => '1.0.0.0',
|
||||||
|
'aliases' =>
|
||||||
|
array (
|
||||||
|
),
|
||||||
|
'reference' => NULL,
|
||||||
|
),
|
||||||
|
'filp/whoops' =>
|
||||||
|
array (
|
||||||
|
'pretty_version' => '2.12.1',
|
||||||
|
'version' => '2.12.1.0',
|
||||||
|
'aliases' =>
|
||||||
|
array (
|
||||||
|
),
|
||||||
|
'reference' => 'c13c0be93cff50f88bbd70827d993026821914dd',
|
||||||
|
),
|
||||||
|
'psr/log' =>
|
||||||
|
array (
|
||||||
|
'pretty_version' => '1.1.4',
|
||||||
|
'version' => '1.1.4.0',
|
||||||
|
'aliases' =>
|
||||||
|
array (
|
||||||
|
),
|
||||||
|
'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// platform_check.php @generated by Composer
|
||||||
|
|
||||||
|
$issues = array();
|
||||||
|
|
||||||
|
if (!(PHP_VERSION_ID >= 50509)) {
|
||||||
|
$issues[] = 'Your Composer dependencies require a PHP version ">= 5.5.9". You are running ' . PHP_VERSION . '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($issues) {
|
||||||
|
if (!headers_sent()) {
|
||||||
|
header('HTTP/1.1 500 Internal Server Error');
|
||||||
|
}
|
||||||
|
if (!ini_get('display_errors')) {
|
||||||
|
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||||
|
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
|
||||||
|
} elseif (!headers_sent()) {
|
||||||
|
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trigger_error(
|
||||||
|
'Composer detected issues in your platform: ' . implode(' ', $issues),
|
||||||
|
E_USER_ERROR
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
github: denis-sokolov
|
|
@ -0,0 +1,56 @@
|
||||||
|
name: Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
name: PHP ${{ matrix.php }}
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php: ['7.0', '7.1', '7.2', '7.3', '7.4', '8.0']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php }}
|
||||||
|
tools: composer:v2
|
||||||
|
coverage: none
|
||||||
|
env:
|
||||||
|
update: true
|
||||||
|
|
||||||
|
- name: Setup Problem Matchers
|
||||||
|
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
|
||||||
|
|
||||||
|
- name: Fix PHPUnit Version PHP < 7.4
|
||||||
|
uses: nick-invision/retry@v1
|
||||||
|
with:
|
||||||
|
timeout_minutes: 5
|
||||||
|
max_attempts: 5
|
||||||
|
command: composer require "phpunit/phpunit:^6.5.14 || ^7.5.20 || ^8.5.8" --dev --no-update --no-interaction
|
||||||
|
if: "matrix.php < 7.4"
|
||||||
|
|
||||||
|
- name: Fix PHPUnit Version PHP >= 7.4
|
||||||
|
uses: nick-invision/retry@v1
|
||||||
|
with:
|
||||||
|
timeout_minutes: 5
|
||||||
|
max_attempts: 5
|
||||||
|
command: composer require "phpunit/phpunit:^9.3.3" --dev --no-update --no-interaction
|
||||||
|
if: "matrix.php >= 7.4"
|
||||||
|
|
||||||
|
- name: Install PHP Dependencies
|
||||||
|
uses: nick-invision/retry@v1
|
||||||
|
with:
|
||||||
|
timeout_minutes: 5
|
||||||
|
max_attempts: 5
|
||||||
|
command: composer update --no-interaction --no-progress
|
||||||
|
|
||||||
|
- name: Execute PHPUnit
|
||||||
|
run: vendor/bin/phpunit
|
|
@ -0,0 +1,2 @@
|
||||||
|
Denis Sokolov <denis@sokolov.cc>
|
||||||
|
Filipe Dobreira <dobreira@gmail.com>
|
|
@ -0,0 +1,98 @@
|
||||||
|
# 2.12.1
|
||||||
|
|
||||||
|
* Avoid redirecting away from an error.
|
||||||
|
|
||||||
|
# 2.12.0
|
||||||
|
|
||||||
|
* Hide non-string values in super globals when requested.
|
||||||
|
|
||||||
|
# 2.11.0
|
||||||
|
|
||||||
|
* Customize exit code
|
||||||
|
|
||||||
|
# 2.10.0
|
||||||
|
|
||||||
|
* Better chaining on handler classes
|
||||||
|
|
||||||
|
# 2.9.2
|
||||||
|
|
||||||
|
* Fix copy button styles
|
||||||
|
|
||||||
|
# 2.9.1
|
||||||
|
|
||||||
|
* Fix xdebug function crash on PHP 8
|
||||||
|
|
||||||
|
# 2.9.0
|
||||||
|
|
||||||
|
* JsonResponseHandler includes the exception code
|
||||||
|
|
||||||
|
# 2.8.0
|
||||||
|
|
||||||
|
* Support PHP 8
|
||||||
|
|
||||||
|
# 2.7.3
|
||||||
|
|
||||||
|
* PrettyPageHandler functionality to hide superglobal keys has a clearer name hideSuperglobalKey
|
||||||
|
|
||||||
|
# 2.7.2
|
||||||
|
|
||||||
|
* PrettyPageHandler now accepts custom js files
|
||||||
|
* PrettyPageHandler templateHelper is now accessible through inheritance
|
||||||
|
|
||||||
|
# 2.7.1
|
||||||
|
|
||||||
|
* Fix a PHP warning in some cases with anonymous classes.
|
||||||
|
|
||||||
|
# 2.7.0
|
||||||
|
|
||||||
|
* removeFirstHandler and removeLastHandler.
|
||||||
|
|
||||||
|
# 2.6.0
|
||||||
|
|
||||||
|
* Fix 2.4.0 pushHandler changing the order of handlers.
|
||||||
|
|
||||||
|
# 2.5.1
|
||||||
|
|
||||||
|
* Fix error messaging in a rare case.
|
||||||
|
|
||||||
|
# 2.5.0
|
||||||
|
|
||||||
|
* Automatically configure xdebug if available.
|
||||||
|
|
||||||
|
# 2.4.1
|
||||||
|
|
||||||
|
* Try harder to close all output buffers
|
||||||
|
|
||||||
|
# 2.4.0
|
||||||
|
|
||||||
|
* Allow to prepend and append handlers.
|
||||||
|
|
||||||
|
# 2.3.2
|
||||||
|
|
||||||
|
* Various fixes from the community.
|
||||||
|
|
||||||
|
# 2.3.1
|
||||||
|
|
||||||
|
* Prevent exception in Whoops when caught exception frame is not related to real file
|
||||||
|
|
||||||
|
# 2.3.0
|
||||||
|
|
||||||
|
* Show previous exception messages.
|
||||||
|
|
||||||
|
# 2.2.0
|
||||||
|
|
||||||
|
* Support PHP 7.2
|
||||||
|
|
||||||
|
# 2.1.0
|
||||||
|
|
||||||
|
* Add a `SystemFacade` to allow clients to override Whoops behavior.
|
||||||
|
* Show frame arguments in `PrettyPageHandler`.
|
||||||
|
* Highlight the line with the error.
|
||||||
|
* Add icons to search on Google and Stack Overflow.
|
||||||
|
|
||||||
|
# 2.0.0
|
||||||
|
|
||||||
|
Backwards compatibility breaking changes:
|
||||||
|
|
||||||
|
* `Run` class is now `final`. If you inherited from `Run`, please now instead use a custom `SystemFacade` injected into the `Run` constructor, or contribute your changes to our core.
|
||||||
|
* PHP < 5.5 support dropped.
|
|
@ -0,0 +1,19 @@
|
||||||
|
# The MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"name": "filp/whoops",
|
||||||
|
"license": "MIT",
|
||||||
|
"description": "php error handling for cool kids",
|
||||||
|
"keywords": ["library", "error", "handling", "exception", "whoops", "throwable"],
|
||||||
|
"homepage": "https://filp.github.io/whoops/",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Filipe Dobreira",
|
||||||
|
"homepage": "https://github.com/filp",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"test": "phpunit --testdox tests"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^5.5.9 || ^7.0 || ^8.0",
|
||||||
|
"psr/log": "^1.0.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3",
|
||||||
|
"mockery/mockery": "^0.9 || ^1.0",
|
||||||
|
"symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"symfony/var-dumper": "Pretty print complex values better with var-dumper available",
|
||||||
|
"whoops/soap": "Formats errors as SOAP responses"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Whoops\\": "src/Whoops/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Whoops\\": "tests/Whoops/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.7-dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops\Exception;
|
||||||
|
|
||||||
|
use ErrorException as BaseErrorException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps ErrorException; mostly used for typing (at least now)
|
||||||
|
* to easily cleanup the stack trace of redundant info.
|
||||||
|
*/
|
||||||
|
class ErrorException extends BaseErrorException
|
||||||
|
{
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops\Exception;
|
||||||
|
|
||||||
|
class Formatter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns all basic information about the exception in a simple array
|
||||||
|
* for further convertion to other languages
|
||||||
|
* @param Inspector $inspector
|
||||||
|
* @param bool $shouldAddTrace
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function formatExceptionAsDataArray(Inspector $inspector, $shouldAddTrace)
|
||||||
|
{
|
||||||
|
$exception = $inspector->getException();
|
||||||
|
$response = [
|
||||||
|
'type' => get_class($exception),
|
||||||
|
'message' => $exception->getMessage(),
|
||||||
|
'code' => $exception->getCode(),
|
||||||
|
'file' => $exception->getFile(),
|
||||||
|
'line' => $exception->getLine(),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($shouldAddTrace) {
|
||||||
|
$frames = $inspector->getFrames();
|
||||||
|
$frameData = [];
|
||||||
|
|
||||||
|
foreach ($frames as $frame) {
|
||||||
|
/** @var Frame $frame */
|
||||||
|
$frameData[] = [
|
||||||
|
'file' => $frame->getFile(),
|
||||||
|
'line' => $frame->getLine(),
|
||||||
|
'function' => $frame->getFunction(),
|
||||||
|
'class' => $frame->getClass(),
|
||||||
|
'args' => $frame->getArgs(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$response['trace'] = $frameData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function formatExceptionPlain(Inspector $inspector)
|
||||||
|
{
|
||||||
|
$message = $inspector->getException()->getMessage();
|
||||||
|
$frames = $inspector->getFrames();
|
||||||
|
|
||||||
|
$plain = $inspector->getExceptionName();
|
||||||
|
$plain .= ' thrown with message "';
|
||||||
|
$plain .= $message;
|
||||||
|
$plain .= '"'."\n\n";
|
||||||
|
|
||||||
|
$plain .= "Stacktrace:\n";
|
||||||
|
foreach ($frames as $i => $frame) {
|
||||||
|
$plain .= "#". (count($frames) - $i - 1). " ";
|
||||||
|
$plain .= $frame->getClass() ?: '';
|
||||||
|
$plain .= $frame->getClass() && $frame->getFunction() ? ":" : "";
|
||||||
|
$plain .= $frame->getFunction() ?: '';
|
||||||
|
$plain .= ' in ';
|
||||||
|
$plain .= ($frame->getFile() ?: '<#unknown>');
|
||||||
|
$plain .= ':';
|
||||||
|
$plain .= (int) $frame->getLine(). "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $plain;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,295 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops\Exception;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Serializable;
|
||||||
|
|
||||||
|
class Frame implements Serializable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $frame;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $fileContentsCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array[]
|
||||||
|
*/
|
||||||
|
protected $comments = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $application;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array[]
|
||||||
|
*/
|
||||||
|
public function __construct(array $frame)
|
||||||
|
{
|
||||||
|
$this->frame = $frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool $shortened
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getFile($shortened = false)
|
||||||
|
{
|
||||||
|
if (empty($this->frame['file'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $this->frame['file'];
|
||||||
|
|
||||||
|
// Check if this frame occurred within an eval().
|
||||||
|
// @todo: This can be made more reliable by checking if we've entered
|
||||||
|
// eval() in a previous trace, but will need some more work on the upper
|
||||||
|
// trace collector(s).
|
||||||
|
if (preg_match('/^(.*)\((\d+)\) : (?:eval\(\)\'d|assert) code$/', $file, $matches)) {
|
||||||
|
$file = $this->frame['file'] = $matches[1];
|
||||||
|
$this->frame['line'] = (int) $matches[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($shortened && is_string($file)) {
|
||||||
|
// Replace the part of the path that all frames have in common, and add 'soft hyphens' for smoother line-breaks.
|
||||||
|
$dirname = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__))))));
|
||||||
|
if ($dirname !== '/') {
|
||||||
|
$file = str_replace($dirname, "…", $file);
|
||||||
|
}
|
||||||
|
$file = str_replace("/", "/­", $file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int|null
|
||||||
|
*/
|
||||||
|
public function getLine()
|
||||||
|
{
|
||||||
|
return isset($this->frame['line']) ? $this->frame['line'] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getClass()
|
||||||
|
{
|
||||||
|
return isset($this->frame['class']) ? $this->frame['class'] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getFunction()
|
||||||
|
{
|
||||||
|
return isset($this->frame['function']) ? $this->frame['function'] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getArgs()
|
||||||
|
{
|
||||||
|
return isset($this->frame['args']) ? (array) $this->frame['args'] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the full contents of the file for this frame,
|
||||||
|
* if it's known.
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getFileContents()
|
||||||
|
{
|
||||||
|
if ($this->fileContentsCache === null && $filePath = $this->getFile()) {
|
||||||
|
// Leave the stage early when 'Unknown' or '[internal]' is passed
|
||||||
|
// this would otherwise raise an exception when
|
||||||
|
// open_basedir is enabled.
|
||||||
|
if ($filePath === "Unknown" || $filePath === '[internal]') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->fileContentsCache = file_get_contents($filePath);
|
||||||
|
} catch (ErrorException $exception) {
|
||||||
|
// Internal file paths of PHP extensions cannot be opened
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->fileContentsCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a comment to this frame, that can be received and
|
||||||
|
* used by other handlers. For example, the PrettyPage handler
|
||||||
|
* can attach these comments under the code for each frame.
|
||||||
|
*
|
||||||
|
* An interesting use for this would be, for example, code analysis
|
||||||
|
* & annotations.
|
||||||
|
*
|
||||||
|
* @param string $comment
|
||||||
|
* @param string $context Optional string identifying the origin of the comment
|
||||||
|
*/
|
||||||
|
public function addComment($comment, $context = 'global')
|
||||||
|
{
|
||||||
|
$this->comments[] = [
|
||||||
|
'comment' => $comment,
|
||||||
|
'context' => $context,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all comments for this frame. Optionally allows
|
||||||
|
* a filter to only retrieve comments from a specific
|
||||||
|
* context.
|
||||||
|
*
|
||||||
|
* @param string $filter
|
||||||
|
* @return array[]
|
||||||
|
*/
|
||||||
|
public function getComments($filter = null)
|
||||||
|
{
|
||||||
|
$comments = $this->comments;
|
||||||
|
|
||||||
|
if ($filter !== null) {
|
||||||
|
$comments = array_filter($comments, function ($c) use ($filter) {
|
||||||
|
return $c['context'] == $filter;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the array containing the raw frame data from which
|
||||||
|
* this Frame object was built
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getRawFrame()
|
||||||
|
{
|
||||||
|
return $this->frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the contents of the file for this frame as an
|
||||||
|
* array of lines, and optionally as a clamped range of lines.
|
||||||
|
*
|
||||||
|
* NOTE: lines are 0-indexed
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* Get all lines for this file
|
||||||
|
* $frame->getFileLines(); // => array( 0 => '<?php', 1 => '...', ...)
|
||||||
|
* @example
|
||||||
|
* Get one line for this file, starting at line 10 (zero-indexed, remember!)
|
||||||
|
* $frame->getFileLines(9, 1); // array( 9 => '...' )
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException if $length is less than or equal to 0
|
||||||
|
* @param int $start
|
||||||
|
* @param int $length
|
||||||
|
* @return string[]|null
|
||||||
|
*/
|
||||||
|
public function getFileLines($start = 0, $length = null)
|
||||||
|
{
|
||||||
|
if (null !== ($contents = $this->getFileContents())) {
|
||||||
|
$lines = explode("\n", $contents);
|
||||||
|
|
||||||
|
// Get a subset of lines from $start to $end
|
||||||
|
if ($length !== null) {
|
||||||
|
$start = (int) $start;
|
||||||
|
$length = (int) $length;
|
||||||
|
if ($start < 0) {
|
||||||
|
$start = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($length <= 0) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
"\$length($length) cannot be lower or equal to 0"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = array_slice($lines, $start, $length, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $lines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the Serializable interface, with special
|
||||||
|
* steps to also save the existing comments.
|
||||||
|
*
|
||||||
|
* @see Serializable::serialize
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function serialize()
|
||||||
|
{
|
||||||
|
$frame = $this->frame;
|
||||||
|
if (!empty($this->comments)) {
|
||||||
|
$frame['_comments'] = $this->comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
return serialize($frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unserializes the frame data, while also preserving
|
||||||
|
* any existing comment data.
|
||||||
|
*
|
||||||
|
* @see Serializable::unserialize
|
||||||
|
* @param string $serializedFrame
|
||||||
|
*/
|
||||||
|
public function unserialize($serializedFrame)
|
||||||
|
{
|
||||||
|
$frame = unserialize($serializedFrame);
|
||||||
|
|
||||||
|
if (!empty($frame['_comments'])) {
|
||||||
|
$this->comments = $frame['_comments'];
|
||||||
|
unset($frame['_comments']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->frame = $frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares Frame against one another
|
||||||
|
* @param Frame $frame
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function equals(Frame $frame)
|
||||||
|
{
|
||||||
|
if (!$this->getFile() || $this->getFile() === 'Unknown' || !$this->getLine()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $frame->getFile() === $this->getFile() && $frame->getLine() === $this->getLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this frame belongs to the application or not.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function isApplication()
|
||||||
|
{
|
||||||
|
return $this->application;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark as an frame belonging to the application.
|
||||||
|
*
|
||||||
|
* @param boolean $application
|
||||||
|
*/
|
||||||
|
public function setApplication($application)
|
||||||
|
{
|
||||||
|
$this->application = $application;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,203 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops\Exception;
|
||||||
|
|
||||||
|
use ArrayAccess;
|
||||||
|
use ArrayIterator;
|
||||||
|
use Countable;
|
||||||
|
use IteratorAggregate;
|
||||||
|
use Serializable;
|
||||||
|
use UnexpectedValueException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposes a fluent interface for dealing with an ordered list
|
||||||
|
* of stack-trace frames.
|
||||||
|
*/
|
||||||
|
class FrameCollection implements ArrayAccess, IteratorAggregate, Serializable, Countable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array[]
|
||||||
|
*/
|
||||||
|
private $frames;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $frames
|
||||||
|
*/
|
||||||
|
public function __construct(array $frames)
|
||||||
|
{
|
||||||
|
$this->frames = array_map(function ($frame) {
|
||||||
|
return new Frame($frame);
|
||||||
|
}, $frames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters frames using a callable, returns the same FrameCollection
|
||||||
|
*
|
||||||
|
* @param callable $callable
|
||||||
|
* @return FrameCollection
|
||||||
|
*/
|
||||||
|
public function filter($callable)
|
||||||
|
{
|
||||||
|
$this->frames = array_values(array_filter($this->frames, $callable));
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map the collection of frames
|
||||||
|
*
|
||||||
|
* @param callable $callable
|
||||||
|
* @return FrameCollection
|
||||||
|
*/
|
||||||
|
public function map($callable)
|
||||||
|
{
|
||||||
|
// Contain the map within a higher-order callable
|
||||||
|
// that enforces type-correctness for the $callable
|
||||||
|
$this->frames = array_map(function ($frame) use ($callable) {
|
||||||
|
$frame = call_user_func($callable, $frame);
|
||||||
|
|
||||||
|
if (!$frame instanceof Frame) {
|
||||||
|
throw new UnexpectedValueException(
|
||||||
|
"Callable to " . __CLASS__ . "::map must return a Frame object"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $frame;
|
||||||
|
}, $this->frames);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array with all frames, does not affect
|
||||||
|
* the internal array.
|
||||||
|
*
|
||||||
|
* @todo If this gets any more complex than this,
|
||||||
|
* have getIterator use this method.
|
||||||
|
* @see FrameCollection::getIterator
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getArray()
|
||||||
|
{
|
||||||
|
return $this->frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see IteratorAggregate::getIterator
|
||||||
|
* @return ArrayIterator
|
||||||
|
*/
|
||||||
|
public function getIterator()
|
||||||
|
{
|
||||||
|
return new ArrayIterator($this->frames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see ArrayAccess::offsetExists
|
||||||
|
* @param int $offset
|
||||||
|
*/
|
||||||
|
public function offsetExists($offset)
|
||||||
|
{
|
||||||
|
return isset($this->frames[$offset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see ArrayAccess::offsetGet
|
||||||
|
* @param int $offset
|
||||||
|
*/
|
||||||
|
public function offsetGet($offset)
|
||||||
|
{
|
||||||
|
return $this->frames[$offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see ArrayAccess::offsetSet
|
||||||
|
* @param int $offset
|
||||||
|
*/
|
||||||
|
public function offsetSet($offset, $value)
|
||||||
|
{
|
||||||
|
throw new \Exception(__CLASS__ . ' is read only');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see ArrayAccess::offsetUnset
|
||||||
|
* @param int $offset
|
||||||
|
*/
|
||||||
|
public function offsetUnset($offset)
|
||||||
|
{
|
||||||
|
throw new \Exception(__CLASS__ . ' is read only');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Countable::count
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function count()
|
||||||
|
{
|
||||||
|
return count($this->frames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count the frames that belongs to the application.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function countIsApplication()
|
||||||
|
{
|
||||||
|
return count(array_filter($this->frames, function (Frame $f) {
|
||||||
|
return $f->isApplication();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Serializable::serialize
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function serialize()
|
||||||
|
{
|
||||||
|
return serialize($this->frames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Serializable::unserialize
|
||||||
|
* @param string $serializedFrames
|
||||||
|
*/
|
||||||
|
public function unserialize($serializedFrames)
|
||||||
|
{
|
||||||
|
$this->frames = unserialize($serializedFrames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Frame[] $frames Array of Frame instances, usually from $e->getPrevious()
|
||||||
|
*/
|
||||||
|
public function prependFrames(array $frames)
|
||||||
|
{
|
||||||
|
$this->frames = array_merge($frames, $this->frames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the innermost part of stack trace that is not the same as that of outer exception
|
||||||
|
*
|
||||||
|
* @param FrameCollection $parentFrames Outer exception frames to compare tail against
|
||||||
|
* @return Frame[]
|
||||||
|
*/
|
||||||
|
public function topDiff(FrameCollection $parentFrames)
|
||||||
|
{
|
||||||
|
$diff = $this->frames;
|
||||||
|
|
||||||
|
$parentFrames = $parentFrames->getArray();
|
||||||
|
$p = count($parentFrames)-1;
|
||||||
|
|
||||||
|
for ($i = count($diff)-1; $i >= 0 && $p >= 0; $i--) {
|
||||||
|
/** @var Frame $tailFrame */
|
||||||
|
$tailFrame = $diff[$i];
|
||||||
|
if ($tailFrame->equals($parentFrames[$p])) {
|
||||||
|
unset($diff[$i]);
|
||||||
|
}
|
||||||
|
$p--;
|
||||||
|
}
|
||||||
|
return $diff;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,323 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops\Exception;
|
||||||
|
|
||||||
|
use Whoops\Util\Misc;
|
||||||
|
|
||||||
|
class Inspector
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Throwable
|
||||||
|
*/
|
||||||
|
private $exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Whoops\Exception\FrameCollection
|
||||||
|
*/
|
||||||
|
private $frames;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Whoops\Exception\Inspector
|
||||||
|
*/
|
||||||
|
private $previousExceptionInspector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Throwable[]
|
||||||
|
*/
|
||||||
|
private $previousExceptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Throwable $exception The exception to inspect
|
||||||
|
*/
|
||||||
|
public function __construct($exception)
|
||||||
|
{
|
||||||
|
$this->exception = $exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Throwable
|
||||||
|
*/
|
||||||
|
public function getException()
|
||||||
|
{
|
||||||
|
return $this->exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getExceptionName()
|
||||||
|
{
|
||||||
|
return get_class($this->exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getExceptionMessage()
|
||||||
|
{
|
||||||
|
return $this->extractDocrefUrl($this->exception->getMessage())['message'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getPreviousExceptionMessages()
|
||||||
|
{
|
||||||
|
return array_map(function ($prev) {
|
||||||
|
/** @var \Throwable $prev */
|
||||||
|
return $this->extractDocrefUrl($prev->getMessage())['message'];
|
||||||
|
}, $this->getPreviousExceptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int[]
|
||||||
|
*/
|
||||||
|
public function getPreviousExceptionCodes()
|
||||||
|
{
|
||||||
|
return array_map(function ($prev) {
|
||||||
|
/** @var \Throwable $prev */
|
||||||
|
return $prev->getCode();
|
||||||
|
}, $this->getPreviousExceptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a url to the php-manual related to the underlying error - when available.
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getExceptionDocrefUrl()
|
||||||
|
{
|
||||||
|
return $this->extractDocrefUrl($this->exception->getMessage())['url'];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractDocrefUrl($message)
|
||||||
|
{
|
||||||
|
$docref = [
|
||||||
|
'message' => $message,
|
||||||
|
'url' => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
// php embbeds urls to the manual into the Exception message with the following ini-settings defined
|
||||||
|
// http://php.net/manual/en/errorfunc.configuration.php#ini.docref-root
|
||||||
|
if (!ini_get('html_errors') || !ini_get('docref_root')) {
|
||||||
|
return $docref;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern = "/\[<a href='([^']+)'>(?:[^<]+)<\/a>\]/";
|
||||||
|
if (preg_match($pattern, $message, $matches)) {
|
||||||
|
// -> strip those automatically generated links from the exception message
|
||||||
|
$docref['message'] = preg_replace($pattern, '', $message, 1);
|
||||||
|
$docref['url'] = $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $docref;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the wrapped Exception has a previous Exception?
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasPreviousException()
|
||||||
|
{
|
||||||
|
return $this->previousExceptionInspector || $this->exception->getPrevious();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an Inspector for a previous Exception, if any.
|
||||||
|
* @todo Clean this up a bit, cache stuff a bit better.
|
||||||
|
* @return Inspector
|
||||||
|
*/
|
||||||
|
public function getPreviousExceptionInspector()
|
||||||
|
{
|
||||||
|
if ($this->previousExceptionInspector === null) {
|
||||||
|
$previousException = $this->exception->getPrevious();
|
||||||
|
|
||||||
|
if ($previousException) {
|
||||||
|
$this->previousExceptionInspector = new Inspector($previousException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->previousExceptionInspector;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of all previous exceptions for this inspector's exception
|
||||||
|
* @return \Throwable[]
|
||||||
|
*/
|
||||||
|
public function getPreviousExceptions()
|
||||||
|
{
|
||||||
|
if ($this->previousExceptions === null) {
|
||||||
|
$this->previousExceptions = [];
|
||||||
|
|
||||||
|
$prev = $this->exception->getPrevious();
|
||||||
|
while ($prev !== null) {
|
||||||
|
$this->previousExceptions[] = $prev;
|
||||||
|
$prev = $prev->getPrevious();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->previousExceptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an iterator for the inspected exception's
|
||||||
|
* frames.
|
||||||
|
* @return \Whoops\Exception\FrameCollection
|
||||||
|
*/
|
||||||
|
public function getFrames()
|
||||||
|
{
|
||||||
|
if ($this->frames === null) {
|
||||||
|
$frames = $this->getTrace($this->exception);
|
||||||
|
|
||||||
|
// Fill empty line/file info for call_user_func_array usages (PHP Bug #44428)
|
||||||
|
foreach ($frames as $k => $frame) {
|
||||||
|
if (empty($frame['file'])) {
|
||||||
|
// Default values when file and line are missing
|
||||||
|
$file = '[internal]';
|
||||||
|
$line = 0;
|
||||||
|
|
||||||
|
$next_frame = !empty($frames[$k + 1]) ? $frames[$k + 1] : [];
|
||||||
|
|
||||||
|
if ($this->isValidNextFrame($next_frame)) {
|
||||||
|
$file = $next_frame['file'];
|
||||||
|
$line = $next_frame['line'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$frames[$k]['file'] = $file;
|
||||||
|
$frames[$k]['line'] = $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find latest non-error handling frame index ($i) used to remove error handling frames
|
||||||
|
$i = 0;
|
||||||
|
foreach ($frames as $k => $frame) {
|
||||||
|
if ($frame['file'] == $this->exception->getFile() && $frame['line'] == $this->exception->getLine()) {
|
||||||
|
$i = $k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove error handling frames
|
||||||
|
if ($i > 0) {
|
||||||
|
array_splice($frames, 0, $i);
|
||||||
|
}
|
||||||
|
|
||||||
|
$firstFrame = $this->getFrameFromException($this->exception);
|
||||||
|
array_unshift($frames, $firstFrame);
|
||||||
|
|
||||||
|
$this->frames = new FrameCollection($frames);
|
||||||
|
|
||||||
|
if ($previousInspector = $this->getPreviousExceptionInspector()) {
|
||||||
|
// Keep outer frame on top of the inner one
|
||||||
|
$outerFrames = $this->frames;
|
||||||
|
$newFrames = clone $previousInspector->getFrames();
|
||||||
|
// I assume it will always be set, but let's be safe
|
||||||
|
if (isset($newFrames[0])) {
|
||||||
|
$newFrames[0]->addComment(
|
||||||
|
$previousInspector->getExceptionMessage(),
|
||||||
|
'Exception message:'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$newFrames->prependFrames($outerFrames->topDiff($newFrames));
|
||||||
|
$this->frames = $newFrames;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the backtrace from an exception.
|
||||||
|
*
|
||||||
|
* If xdebug is installed
|
||||||
|
*
|
||||||
|
* @param \Throwable $e
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getTrace($e)
|
||||||
|
{
|
||||||
|
$traces = $e->getTrace();
|
||||||
|
|
||||||
|
// Get trace from xdebug if enabled, failure exceptions only trace to the shutdown handler by default
|
||||||
|
if (!$e instanceof \ErrorException) {
|
||||||
|
return $traces;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Misc::isLevelFatal($e->getSeverity())) {
|
||||||
|
return $traces;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!extension_loaded('xdebug') || !function_exists('xdebug_is_enabled') || !xdebug_is_enabled()) {
|
||||||
|
return $traces;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use xdebug to get the full stack trace and remove the shutdown handler stack trace
|
||||||
|
$stack = array_reverse(xdebug_get_function_stack());
|
||||||
|
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||||
|
$traces = array_diff_key($stack, $trace);
|
||||||
|
|
||||||
|
return $traces;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an exception, generates an array in the format
|
||||||
|
* generated by Exception::getTrace()
|
||||||
|
* @param \Throwable $exception
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getFrameFromException($exception)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'file' => $exception->getFile(),
|
||||||
|
'line' => $exception->getLine(),
|
||||||
|
'class' => get_class($exception),
|
||||||
|
'args' => [
|
||||||
|
$exception->getMessage(),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an error, generates an array in the format
|
||||||
|
* generated by ErrorException
|
||||||
|
* @param ErrorException $exception
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getFrameFromError(ErrorException $exception)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'file' => $exception->getFile(),
|
||||||
|
'line' => $exception->getLine(),
|
||||||
|
'class' => null,
|
||||||
|
'args' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the frame can be used to fill in previous frame's missing info
|
||||||
|
* happens for call_user_func and call_user_func_array usages (PHP Bug #44428)
|
||||||
|
*
|
||||||
|
* @param array $frame
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function isValidNextFrame(array $frame)
|
||||||
|
{
|
||||||
|
if (empty($frame['file'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($frame['line'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($frame['function']) || !stristr($frame['function'], 'call_user_func')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops\Handler;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for Closures passed as handlers. Can be used
|
||||||
|
* directly, or will be instantiated automagically by Whoops\Run
|
||||||
|
* if passed to Run::pushHandler
|
||||||
|
*/
|
||||||
|
class CallbackHandler extends Handler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var callable
|
||||||
|
*/
|
||||||
|
protected $callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidArgumentException If argument is not callable
|
||||||
|
* @param callable $callable
|
||||||
|
*/
|
||||||
|
public function __construct($callable)
|
||||||
|
{
|
||||||
|
if (!is_callable($callable)) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
'Argument to ' . __METHOD__ . ' must be valid callable'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->callable = $callable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int|null
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$exception = $this->getException();
|
||||||
|
$inspector = $this->getInspector();
|
||||||
|
$run = $this->getRun();
|
||||||
|
$callable = $this->callable;
|
||||||
|
|
||||||
|
// invoke the callable directly, to get simpler stacktraces (in comparison to call_user_func).
|
||||||
|
// this assumes that $callable is a properly typed php-callable, which we check in __construct().
|
||||||
|
return $callable($exception, $inspector, $run);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops\Handler;
|
||||||
|
|
||||||
|
use Whoops\Exception\Inspector;
|
||||||
|
use Whoops\RunInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract implementation of a Handler.
|
||||||
|
*/
|
||||||
|
abstract class Handler implements HandlerInterface
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Return constants that can be returned from Handler::handle
|
||||||
|
to message the handler walker.
|
||||||
|
*/
|
||||||
|
const DONE = 0x10; // returning this is optional, only exists for
|
||||||
|
// semantic purposes
|
||||||
|
/**
|
||||||
|
* The Handler has handled the Throwable in some way, and wishes to skip any other Handler.
|
||||||
|
* Execution will continue.
|
||||||
|
*/
|
||||||
|
const LAST_HANDLER = 0x20;
|
||||||
|
/**
|
||||||
|
* The Handler has handled the Throwable in some way, and wishes to quit/stop execution
|
||||||
|
*/
|
||||||
|
const QUIT = 0x30;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var RunInterface
|
||||||
|
*/
|
||||||
|
private $run;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Inspector $inspector
|
||||||
|
*/
|
||||||
|
private $inspector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Throwable $exception
|
||||||
|
*/
|
||||||
|
private $exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param RunInterface $run
|
||||||
|
*/
|
||||||
|
public function setRun(RunInterface $run)
|
||||||
|
{
|
||||||
|
$this->run = $run;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return RunInterface
|
||||||
|
*/
|
||||||
|
protected function getRun()
|
||||||
|
{
|
||||||
|
return $this->run;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Inspector $inspector
|
||||||
|
*/
|
||||||
|
public function setInspector(Inspector $inspector)
|
||||||
|
{
|
||||||
|
$this->inspector = $inspector;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Inspector
|
||||||
|
*/
|
||||||
|
protected function getInspector()
|
||||||
|
{
|
||||||
|
return $this->inspector;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Throwable $exception
|
||||||
|
*/
|
||||||
|
public function setException($exception)
|
||||||
|
{
|
||||||
|
$this->exception = $exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Throwable
|
||||||
|
*/
|
||||||
|
protected function getException()
|
||||||
|
{
|
||||||
|
return $this->exception;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops\Handler;
|
||||||
|
|
||||||
|
use Whoops\Exception\Inspector;
|
||||||
|
use Whoops\RunInterface;
|
||||||
|
|
||||||
|
interface HandlerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return int|null A handler may return nothing, or a Handler::HANDLE_* constant
|
||||||
|
*/
|
||||||
|
public function handle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param RunInterface $run
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setRun(RunInterface $run);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Throwable $exception
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setException($exception);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Inspector $inspector
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setInspector(Inspector $inspector);
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops\Handler;
|
||||||
|
|
||||||
|
use Whoops\Exception\Formatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catches an exception and converts it to a JSON
|
||||||
|
* response. Additionally can also return exception
|
||||||
|
* frames for consumption by an API.
|
||||||
|
*/
|
||||||
|
class JsonResponseHandler extends Handler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $returnFrames = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $jsonApi = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns errors[[]] instead of error[] to be in compliance with the json:api spec
|
||||||
|
* @param bool $jsonApi Default is false
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function setJsonApi($jsonApi = false)
|
||||||
|
{
|
||||||
|
$this->jsonApi = (bool) $jsonApi;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool|null $returnFrames
|
||||||
|
* @return bool|static
|
||||||
|
*/
|
||||||
|
public function addTraceToOutput($returnFrames = null)
|
||||||
|
{
|
||||||
|
if (func_num_args() == 0) {
|
||||||
|
return $this->returnFrames;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->returnFrames = (bool) $returnFrames;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
if ($this->jsonApi === true) {
|
||||||
|
$response = [
|
||||||
|
'errors' => [
|
||||||
|
Formatter::formatExceptionAsDataArray(
|
||||||
|
$this->getInspector(),
|
||||||
|
$this->addTraceToOutput()
|
||||||
|
),
|
||||||
|
]
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$response = [
|
||||||
|
'error' => Formatter::formatExceptionAsDataArray(
|
||||||
|
$this->getInspector(),
|
||||||
|
$this->addTraceToOutput()
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($response, defined('JSON_PARTIAL_OUTPUT_ON_ERROR') ? JSON_PARTIAL_OUTPUT_ON_ERROR : 0);
|
||||||
|
|
||||||
|
return Handler::QUIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function contentType()
|
||||||
|
{
|
||||||
|
return 'application/json';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,359 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
* Plaintext handler for command line and logs.
|
||||||
|
* @author Pierre-Yves Landuré <https://howto.biapy.com/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops\Handler;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Whoops\Exception\Frame;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler outputing plaintext error messages. Can be used
|
||||||
|
* directly, or will be instantiated automagically by Whoops\Run
|
||||||
|
* if passed to Run::pushHandler
|
||||||
|
*/
|
||||||
|
class PlainTextHandler extends Handler
|
||||||
|
{
|
||||||
|
const VAR_DUMP_PREFIX = ' | ';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Psr\Log\LoggerInterface
|
||||||
|
*/
|
||||||
|
protected $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var callable
|
||||||
|
*/
|
||||||
|
protected $dumper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $addTraceToOutput = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool|integer
|
||||||
|
*/
|
||||||
|
private $addTraceFunctionArgsToOutput = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
private $traceFunctionArgsOutputLimit = 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $addPreviousToOutput = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $loggerOnly = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
* @throws InvalidArgumentException If argument is not null or a LoggerInterface
|
||||||
|
* @param \Psr\Log\LoggerInterface|null $logger
|
||||||
|
*/
|
||||||
|
public function __construct($logger = null)
|
||||||
|
{
|
||||||
|
$this->setLogger($logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the output logger interface.
|
||||||
|
* @throws InvalidArgumentException If argument is not null or a LoggerInterface
|
||||||
|
* @param \Psr\Log\LoggerInterface|null $logger
|
||||||
|
*/
|
||||||
|
public function setLogger($logger = null)
|
||||||
|
{
|
||||||
|
if (! (is_null($logger)
|
||||||
|
|| $logger instanceof LoggerInterface)) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
'Argument to ' . __METHOD__ .
|
||||||
|
" must be a valid Logger Interface (aka. Monolog), " .
|
||||||
|
get_class($logger) . ' given.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger = $logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Psr\Log\LoggerInterface|null
|
||||||
|
*/
|
||||||
|
public function getLogger()
|
||||||
|
{
|
||||||
|
return $this->logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set var dumper callback function.
|
||||||
|
*
|
||||||
|
* @param callable $dumper
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function setDumper(callable $dumper)
|
||||||
|
{
|
||||||
|
$this->dumper = $dumper;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add error trace to output.
|
||||||
|
* @param bool|null $addTraceToOutput
|
||||||
|
* @return bool|static
|
||||||
|
*/
|
||||||
|
public function addTraceToOutput($addTraceToOutput = null)
|
||||||
|
{
|
||||||
|
if (func_num_args() == 0) {
|
||||||
|
return $this->addTraceToOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addTraceToOutput = (bool) $addTraceToOutput;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add previous exceptions to output.
|
||||||
|
* @param bool|null $addPreviousToOutput
|
||||||
|
* @return bool|static
|
||||||
|
*/
|
||||||
|
public function addPreviousToOutput($addPreviousToOutput = null)
|
||||||
|
{
|
||||||
|
if (func_num_args() == 0) {
|
||||||
|
return $this->addPreviousToOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addPreviousToOutput = (bool) $addPreviousToOutput;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add error trace function arguments to output.
|
||||||
|
* Set to True for all frame args, or integer for the n first frame args.
|
||||||
|
* @param bool|integer|null $addTraceFunctionArgsToOutput
|
||||||
|
* @return static|bool|integer
|
||||||
|
*/
|
||||||
|
public function addTraceFunctionArgsToOutput($addTraceFunctionArgsToOutput = null)
|
||||||
|
{
|
||||||
|
if (func_num_args() == 0) {
|
||||||
|
return $this->addTraceFunctionArgsToOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! is_integer($addTraceFunctionArgsToOutput)) {
|
||||||
|
$this->addTraceFunctionArgsToOutput = (bool) $addTraceFunctionArgsToOutput;
|
||||||
|
} else {
|
||||||
|
$this->addTraceFunctionArgsToOutput = $addTraceFunctionArgsToOutput;
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the size limit in bytes of frame arguments var_dump output.
|
||||||
|
* If the limit is reached, the var_dump output is discarded.
|
||||||
|
* Prevent memory limit errors.
|
||||||
|
* @var integer
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function setTraceFunctionArgsOutputLimit($traceFunctionArgsOutputLimit)
|
||||||
|
{
|
||||||
|
$this->traceFunctionArgsOutputLimit = (integer) $traceFunctionArgsOutputLimit;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create plain text response and return it as a string
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generateResponse()
|
||||||
|
{
|
||||||
|
$exception = $this->getException();
|
||||||
|
$message = $this->getExceptionOutput($exception);
|
||||||
|
|
||||||
|
if ($this->addPreviousToOutput) {
|
||||||
|
$previous = $exception->getPrevious();
|
||||||
|
while ($previous) {
|
||||||
|
$message .= "\n\nCaused by\n" . $this->getExceptionOutput($previous);
|
||||||
|
$previous = $previous->getPrevious();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return $message . $this->getTraceOutput() . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the size limit in bytes of frame arguments var_dump output.
|
||||||
|
* If the limit is reached, the var_dump output is discarded.
|
||||||
|
* Prevent memory limit errors.
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
public function getTraceFunctionArgsOutputLimit()
|
||||||
|
{
|
||||||
|
return $this->traceFunctionArgsOutputLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only output to logger.
|
||||||
|
* @param bool|null $loggerOnly
|
||||||
|
* @return static|bool
|
||||||
|
*/
|
||||||
|
public function loggerOnly($loggerOnly = null)
|
||||||
|
{
|
||||||
|
if (func_num_args() == 0) {
|
||||||
|
return $this->loggerOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->loggerOnly = (bool) $loggerOnly;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if handler can output to stdout.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function canOutput()
|
||||||
|
{
|
||||||
|
return !$this->loggerOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the frame args var_dump.
|
||||||
|
* @param \Whoops\Exception\Frame $frame [description]
|
||||||
|
* @param integer $line [description]
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getFrameArgsOutput(Frame $frame, $line)
|
||||||
|
{
|
||||||
|
if ($this->addTraceFunctionArgsToOutput() === false
|
||||||
|
|| $this->addTraceFunctionArgsToOutput() < $line) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump the arguments:
|
||||||
|
ob_start();
|
||||||
|
$this->dump($frame->getArgs());
|
||||||
|
if (ob_get_length() > $this->getTraceFunctionArgsOutputLimit()) {
|
||||||
|
// The argument var_dump is to big.
|
||||||
|
// Discarded to limit memory usage.
|
||||||
|
ob_clean();
|
||||||
|
return sprintf(
|
||||||
|
"\n%sArguments dump length greater than %d Bytes. Discarded.",
|
||||||
|
self::VAR_DUMP_PREFIX,
|
||||||
|
$this->getTraceFunctionArgsOutputLimit()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
"\n%s",
|
||||||
|
preg_replace('/^/m', self::VAR_DUMP_PREFIX, ob_get_clean())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dump variable.
|
||||||
|
*
|
||||||
|
* @param mixed $var
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function dump($var)
|
||||||
|
{
|
||||||
|
if ($this->dumper) {
|
||||||
|
call_user_func($this->dumper, $var);
|
||||||
|
} else {
|
||||||
|
var_dump($var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the exception trace as plain text.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getTraceOutput()
|
||||||
|
{
|
||||||
|
if (! $this->addTraceToOutput()) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$inspector = $this->getInspector();
|
||||||
|
$frames = $inspector->getFrames();
|
||||||
|
|
||||||
|
$response = "\nStack trace:";
|
||||||
|
|
||||||
|
$line = 1;
|
||||||
|
foreach ($frames as $frame) {
|
||||||
|
/** @var Frame $frame */
|
||||||
|
$class = $frame->getClass();
|
||||||
|
|
||||||
|
$template = "\n%3d. %s->%s() %s:%d%s";
|
||||||
|
if (! $class) {
|
||||||
|
// Remove method arrow (->) from output.
|
||||||
|
$template = "\n%3d. %s%s() %s:%d%s";
|
||||||
|
}
|
||||||
|
|
||||||
|
$response .= sprintf(
|
||||||
|
$template,
|
||||||
|
$line,
|
||||||
|
$class,
|
||||||
|
$frame->getFunction(),
|
||||||
|
$frame->getFile(),
|
||||||
|
$frame->getLine(),
|
||||||
|
$this->getFrameArgsOutput($frame, $line)
|
||||||
|
);
|
||||||
|
|
||||||
|
$line++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the exception as plain text.
|
||||||
|
* @param \Throwable $exception
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getExceptionOutput($exception)
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
"%s: %s in file %s on line %d",
|
||||||
|
get_class($exception),
|
||||||
|
$exception->getMessage(),
|
||||||
|
$exception->getFile(),
|
||||||
|
$exception->getLine()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$response = $this->generateResponse();
|
||||||
|
|
||||||
|
if ($this->getLogger()) {
|
||||||
|
$this->getLogger()->error($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $this->canOutput()) {
|
||||||
|
return Handler::DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $response;
|
||||||
|
|
||||||
|
return Handler::QUIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function contentType()
|
||||||
|
{
|
||||||
|
return 'text/plain';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,830 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops\Handler;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use RuntimeException;
|
||||||
|
use Symfony\Component\VarDumper\Cloner\AbstractCloner;
|
||||||
|
use Symfony\Component\VarDumper\Cloner\VarCloner;
|
||||||
|
use UnexpectedValueException;
|
||||||
|
use Whoops\Exception\Formatter;
|
||||||
|
use Whoops\Util\Misc;
|
||||||
|
use Whoops\Util\TemplateHelper;
|
||||||
|
|
||||||
|
class PrettyPageHandler extends Handler
|
||||||
|
{
|
||||||
|
const EDITOR_SUBLIME = "sublime";
|
||||||
|
const EDITOR_TEXTMATE = "textmate";
|
||||||
|
const EDITOR_EMACS = "emacs";
|
||||||
|
const EDITOR_MACVIM = "macvim";
|
||||||
|
const EDITOR_PHPSTORM = "phpstorm";
|
||||||
|
const EDITOR_IDEA = "idea";
|
||||||
|
const EDITOR_VSCODE = "vscode";
|
||||||
|
const EDITOR_ATOM = "atom";
|
||||||
|
const EDITOR_ESPRESSO = "espresso";
|
||||||
|
const EDITOR_XDEBUG = "xdebug";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search paths to be scanned for resources.
|
||||||
|
*
|
||||||
|
* Stored in the reverse order they're declared.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $searchPaths = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fast lookup cache for known resource locations.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $resourceCache = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the custom css file.
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
private $customCss = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the custom js file.
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
private $customJs = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array[]
|
||||||
|
*/
|
||||||
|
private $extraTables = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $handleUnconditionally = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $pageTitle = "Whoops! There was an error.";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array[]
|
||||||
|
*/
|
||||||
|
private $applicationPaths;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array[]
|
||||||
|
*/
|
||||||
|
private $blacklist = [
|
||||||
|
'_GET' => [],
|
||||||
|
'_POST' => [],
|
||||||
|
'_FILES' => [],
|
||||||
|
'_COOKIE' => [],
|
||||||
|
'_SESSION' => [],
|
||||||
|
'_SERVER' => [],
|
||||||
|
'_ENV' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An identifier for a known IDE/text editor.
|
||||||
|
*
|
||||||
|
* Either a string, or a calalble that resolves a string, that can be used
|
||||||
|
* to open a given file in an editor. If the string contains the special
|
||||||
|
* substrings %file or %line, they will be replaced with the correct data.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* "txmt://open?url=%file&line=%line"
|
||||||
|
*
|
||||||
|
* @var callable|string $editor
|
||||||
|
*/
|
||||||
|
protected $editor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of known editor strings.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $editors = [
|
||||||
|
"sublime" => "subl://open?url=file://%file&line=%line",
|
||||||
|
"textmate" => "txmt://open?url=file://%file&line=%line",
|
||||||
|
"emacs" => "emacs://open?url=file://%file&line=%line",
|
||||||
|
"macvim" => "mvim://open/?url=file://%file&line=%line",
|
||||||
|
"phpstorm" => "phpstorm://open?file=%file&line=%line",
|
||||||
|
"idea" => "idea://open?file=%file&line=%line",
|
||||||
|
"vscode" => "vscode://file/%file:%line",
|
||||||
|
"atom" => "atom://core/open/file?filename=%file&line=%line",
|
||||||
|
"espresso" => "x-espresso://open?filepath=%file&lines=%line",
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var TemplateHelper
|
||||||
|
*/
|
||||||
|
protected $templateHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
if (ini_get('xdebug.file_link_format') || extension_loaded('xdebug')) {
|
||||||
|
// Register editor using xdebug's file_link_format option.
|
||||||
|
$this->editors['xdebug'] = function ($file, $line) {
|
||||||
|
return str_replace(['%f', '%l'], [$file, $line], ini_get('xdebug.file_link_format'));
|
||||||
|
};
|
||||||
|
|
||||||
|
// If xdebug is available, use it as default editor.
|
||||||
|
$this->setEditor('xdebug');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the default, local resource search path:
|
||||||
|
$this->searchPaths[] = __DIR__ . "/../Resources";
|
||||||
|
|
||||||
|
// blacklist php provided auth based values
|
||||||
|
$this->blacklist('_SERVER', 'PHP_AUTH_PW');
|
||||||
|
|
||||||
|
$this->templateHelper = new TemplateHelper();
|
||||||
|
|
||||||
|
if (class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) {
|
||||||
|
$cloner = new VarCloner();
|
||||||
|
// Only dump object internals if a custom caster exists for performance reasons
|
||||||
|
// https://github.com/filp/whoops/pull/404
|
||||||
|
$cloner->addCasters(['*' => function ($obj, $a, $stub, $isNested, $filter = 0) {
|
||||||
|
$class = $stub->class;
|
||||||
|
$classes = [$class => $class] + class_parents($obj) + class_implements($obj);
|
||||||
|
|
||||||
|
foreach ($classes as $class) {
|
||||||
|
if (isset(AbstractCloner::$defaultCasters[$class])) {
|
||||||
|
return $a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all internals
|
||||||
|
return [];
|
||||||
|
}]);
|
||||||
|
$this->templateHelper->setCloner($cloner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int|null
|
||||||
|
*
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
if (!$this->handleUnconditionally()) {
|
||||||
|
// Check conditions for outputting HTML:
|
||||||
|
// @todo: Make this more robust
|
||||||
|
if (PHP_SAPI === 'cli') {
|
||||||
|
// Help users who have been relying on an internal test value
|
||||||
|
// fix their code to the proper method
|
||||||
|
if (isset($_ENV['whoops-test'])) {
|
||||||
|
throw new \Exception(
|
||||||
|
'Use handleUnconditionally instead of whoops-test'
|
||||||
|
.' environment variable'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Handler::DONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$templateFile = $this->getResource("views/layout.html.php");
|
||||||
|
$cssFile = $this->getResource("css/whoops.base.css");
|
||||||
|
$zeptoFile = $this->getResource("js/zepto.min.js");
|
||||||
|
$prettifyFile = $this->getResource("js/prettify.min.js");
|
||||||
|
$clipboard = $this->getResource("js/clipboard.min.js");
|
||||||
|
$jsFile = $this->getResource("js/whoops.base.js");
|
||||||
|
|
||||||
|
if ($this->customCss) {
|
||||||
|
$customCssFile = $this->getResource($this->customCss);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->customJs) {
|
||||||
|
$customJsFile = $this->getResource($this->customJs);
|
||||||
|
}
|
||||||
|
|
||||||
|
$inspector = $this->getInspector();
|
||||||
|
$frames = $this->getExceptionFrames();
|
||||||
|
$code = $this->getExceptionCode();
|
||||||
|
|
||||||
|
// List of variables that will be passed to the layout template.
|
||||||
|
$vars = [
|
||||||
|
"page_title" => $this->getPageTitle(),
|
||||||
|
|
||||||
|
// @todo: Asset compiler
|
||||||
|
"stylesheet" => file_get_contents($cssFile),
|
||||||
|
"zepto" => file_get_contents($zeptoFile),
|
||||||
|
"prettify" => file_get_contents($prettifyFile),
|
||||||
|
"clipboard" => file_get_contents($clipboard),
|
||||||
|
"javascript" => file_get_contents($jsFile),
|
||||||
|
|
||||||
|
// Template paths:
|
||||||
|
"header" => $this->getResource("views/header.html.php"),
|
||||||
|
"header_outer" => $this->getResource("views/header_outer.html.php"),
|
||||||
|
"frame_list" => $this->getResource("views/frame_list.html.php"),
|
||||||
|
"frames_description" => $this->getResource("views/frames_description.html.php"),
|
||||||
|
"frames_container" => $this->getResource("views/frames_container.html.php"),
|
||||||
|
"panel_details" => $this->getResource("views/panel_details.html.php"),
|
||||||
|
"panel_details_outer" => $this->getResource("views/panel_details_outer.html.php"),
|
||||||
|
"panel_left" => $this->getResource("views/panel_left.html.php"),
|
||||||
|
"panel_left_outer" => $this->getResource("views/panel_left_outer.html.php"),
|
||||||
|
"frame_code" => $this->getResource("views/frame_code.html.php"),
|
||||||
|
"env_details" => $this->getResource("views/env_details.html.php"),
|
||||||
|
|
||||||
|
"title" => $this->getPageTitle(),
|
||||||
|
"name" => explode("\\", $inspector->getExceptionName()),
|
||||||
|
"message" => $inspector->getExceptionMessage(),
|
||||||
|
"previousMessages" => $inspector->getPreviousExceptionMessages(),
|
||||||
|
"docref_url" => $inspector->getExceptionDocrefUrl(),
|
||||||
|
"code" => $code,
|
||||||
|
"previousCodes" => $inspector->getPreviousExceptionCodes(),
|
||||||
|
"plain_exception" => Formatter::formatExceptionPlain($inspector),
|
||||||
|
"frames" => $frames,
|
||||||
|
"has_frames" => !!count($frames),
|
||||||
|
"handler" => $this,
|
||||||
|
"handlers" => $this->getRun()->getHandlers(),
|
||||||
|
|
||||||
|
"active_frames_tab" => count($frames) && $frames->offsetGet(0)->isApplication() ? 'application' : 'all',
|
||||||
|
"has_frames_tabs" => $this->getApplicationPaths(),
|
||||||
|
|
||||||
|
"tables" => [
|
||||||
|
"GET Data" => $this->masked($_GET, '_GET'),
|
||||||
|
"POST Data" => $this->masked($_POST, '_POST'),
|
||||||
|
"Files" => isset($_FILES) ? $this->masked($_FILES, '_FILES') : [],
|
||||||
|
"Cookies" => $this->masked($_COOKIE, '_COOKIE'),
|
||||||
|
"Session" => isset($_SESSION) ? $this->masked($_SESSION, '_SESSION') : [],
|
||||||
|
"Server/Request Data" => $this->masked($_SERVER, '_SERVER'),
|
||||||
|
"Environment Variables" => $this->masked($_ENV, '_ENV'),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isset($customCssFile)) {
|
||||||
|
$vars["stylesheet"] .= file_get_contents($customCssFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($customJsFile)) {
|
||||||
|
$vars["javascript"] .= file_get_contents($customJsFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add extra entries list of data tables:
|
||||||
|
// @todo: Consolidate addDataTable and addDataTableCallback
|
||||||
|
$extraTables = array_map(function ($table) use ($inspector) {
|
||||||
|
return $table instanceof \Closure ? $table($inspector) : $table;
|
||||||
|
}, $this->getDataTables());
|
||||||
|
$vars["tables"] = array_merge($extraTables, $vars["tables"]);
|
||||||
|
|
||||||
|
$plainTextHandler = new PlainTextHandler();
|
||||||
|
$plainTextHandler->setException($this->getException());
|
||||||
|
$plainTextHandler->setInspector($this->getInspector());
|
||||||
|
$vars["preface"] = "<!--\n\n\n" . $this->templateHelper->escape($plainTextHandler->generateResponse()) . "\n\n\n\n\n\n\n\n\n\n\n-->";
|
||||||
|
|
||||||
|
$this->templateHelper->setVariables($vars);
|
||||||
|
$this->templateHelper->render($templateFile);
|
||||||
|
|
||||||
|
return Handler::QUIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the stack trace frames of the exception currently being handled.
|
||||||
|
*
|
||||||
|
* @return \Whoops\Exception\FrameCollection
|
||||||
|
*/
|
||||||
|
protected function getExceptionFrames()
|
||||||
|
{
|
||||||
|
$frames = $this->getInspector()->getFrames();
|
||||||
|
|
||||||
|
if ($this->getApplicationPaths()) {
|
||||||
|
foreach ($frames as $frame) {
|
||||||
|
foreach ($this->getApplicationPaths() as $path) {
|
||||||
|
if (strpos($frame->getFile(), $path) === 0) {
|
||||||
|
$frame->setApplication(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the code of the exception currently being handled.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getExceptionCode()
|
||||||
|
{
|
||||||
|
$exception = $this->getException();
|
||||||
|
|
||||||
|
$code = $exception->getCode();
|
||||||
|
if ($exception instanceof \ErrorException) {
|
||||||
|
// ErrorExceptions wrap the php-error types within the 'severity' property
|
||||||
|
$code = Misc::translateErrorCode($exception->getSeverity());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function contentType()
|
||||||
|
{
|
||||||
|
return 'text/html';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an entry to the list of tables displayed in the template.
|
||||||
|
*
|
||||||
|
* The expected data is a simple associative array. Any nested arrays
|
||||||
|
* will be flattened with `print_r`.
|
||||||
|
*
|
||||||
|
* @param string $label
|
||||||
|
* @param array $data
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function addDataTable($label, array $data)
|
||||||
|
{
|
||||||
|
$this->extraTables[$label] = $data;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily adds an entry to the list of tables displayed in the table.
|
||||||
|
*
|
||||||
|
* The supplied callback argument will be called when the error is
|
||||||
|
* rendered, it should produce a simple associative array. Any nested
|
||||||
|
* arrays will be flattened with `print_r`.
|
||||||
|
*
|
||||||
|
* @param string $label
|
||||||
|
* @param callable $callback Callable returning an associative array
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If $callback is not callable
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function addDataTableCallback($label, /* callable */ $callback)
|
||||||
|
{
|
||||||
|
if (!is_callable($callback)) {
|
||||||
|
throw new InvalidArgumentException('Expecting callback argument to be callable');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->extraTables[$label] = function (\Whoops\Exception\Inspector $inspector = null) use ($callback) {
|
||||||
|
try {
|
||||||
|
$result = call_user_func($callback, $inspector);
|
||||||
|
|
||||||
|
// Only return the result if it can be iterated over by foreach().
|
||||||
|
return is_array($result) || $result instanceof \Traversable ? $result : [];
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Don't allow failure to break the rendering of the original exception.
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all the extra data tables registered with this handler.
|
||||||
|
*
|
||||||
|
* Optionally accepts a 'label' parameter, to only return the data table
|
||||||
|
* under that label.
|
||||||
|
*
|
||||||
|
* @param string|null $label
|
||||||
|
*
|
||||||
|
* @return array[]|callable
|
||||||
|
*/
|
||||||
|
public function getDataTables($label = null)
|
||||||
|
{
|
||||||
|
if ($label !== null) {
|
||||||
|
return isset($this->extraTables[$label]) ?
|
||||||
|
$this->extraTables[$label] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->extraTables;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether to handle unconditionally.
|
||||||
|
*
|
||||||
|
* Allows to disable all attempts to dynamically decide whether to handle
|
||||||
|
* or return prematurely. Set this to ensure that the handler will perform,
|
||||||
|
* no matter what.
|
||||||
|
*
|
||||||
|
* @param bool|null $value
|
||||||
|
*
|
||||||
|
* @return bool|static
|
||||||
|
*/
|
||||||
|
public function handleUnconditionally($value = null)
|
||||||
|
{
|
||||||
|
if (func_num_args() == 0) {
|
||||||
|
return $this->handleUnconditionally;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->handleUnconditionally = (bool) $value;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an editor resolver.
|
||||||
|
*
|
||||||
|
* Either a string, or a closure that resolves a string, that can be used
|
||||||
|
* to open a given file in an editor. If the string contains the special
|
||||||
|
* substrings %file or %line, they will be replaced with the correct data.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* $run->addEditor('macvim', "mvim://open?url=file://%file&line=%line")
|
||||||
|
* @example
|
||||||
|
* $run->addEditor('remove-it', function($file, $line) {
|
||||||
|
* unlink($file);
|
||||||
|
* return "http://stackoverflow.com";
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @param string $identifier
|
||||||
|
* @param string|callable $resolver
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function addEditor($identifier, $resolver)
|
||||||
|
{
|
||||||
|
$this->editors[$identifier] = $resolver;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the editor to use to open referenced files.
|
||||||
|
*
|
||||||
|
* Pass either the name of a configured editor, or a closure that directly
|
||||||
|
* resolves an editor string.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* $run->setEditor(function($file, $line) { return "file:///{$file}"; });
|
||||||
|
* @example
|
||||||
|
* $run->setEditor('sublime');
|
||||||
|
*
|
||||||
|
* @param string|callable $editor
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If invalid argument identifier provided
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function setEditor($editor)
|
||||||
|
{
|
||||||
|
if (!is_callable($editor) && !isset($this->editors[$editor])) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
"Unknown editor identifier: $editor. Known editors:" .
|
||||||
|
implode(",", array_keys($this->editors))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->editor = $editor;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the editor href for a given file and line, if available.
|
||||||
|
*
|
||||||
|
* @param string $filePath
|
||||||
|
* @param int $line
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If editor resolver does not return a string
|
||||||
|
*
|
||||||
|
* @return string|bool
|
||||||
|
*/
|
||||||
|
public function getEditorHref($filePath, $line)
|
||||||
|
{
|
||||||
|
$editor = $this->getEditor($filePath, $line);
|
||||||
|
|
||||||
|
if (empty($editor)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the editor is a string, and replace the
|
||||||
|
// %line and %file placeholders:
|
||||||
|
if (!isset($editor['url']) || !is_string($editor['url'])) {
|
||||||
|
throw new UnexpectedValueException(
|
||||||
|
__METHOD__ . " should always resolve to a string or a valid editor array; got something else instead."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$editor['url'] = str_replace("%line", rawurlencode($line), $editor['url']);
|
||||||
|
$editor['url'] = str_replace("%file", rawurlencode($filePath), $editor['url']);
|
||||||
|
|
||||||
|
return $editor['url'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the editor link should act as an Ajax request.
|
||||||
|
*
|
||||||
|
* @param string $filePath
|
||||||
|
* @param int $line
|
||||||
|
*
|
||||||
|
* @throws UnexpectedValueException If editor resolver does not return a boolean
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function getEditorAjax($filePath, $line)
|
||||||
|
{
|
||||||
|
$editor = $this->getEditor($filePath, $line);
|
||||||
|
|
||||||
|
// Check that the ajax is a bool
|
||||||
|
if (!isset($editor['ajax']) || !is_bool($editor['ajax'])) {
|
||||||
|
throw new UnexpectedValueException(
|
||||||
|
__METHOD__ . " should always resolve to a bool; got something else instead."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $editor['ajax'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines both the editor and if ajax should be used.
|
||||||
|
*
|
||||||
|
* @param string $filePath
|
||||||
|
* @param int $line
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getEditor($filePath, $line)
|
||||||
|
{
|
||||||
|
if (!$this->editor || (!is_string($this->editor) && !is_callable($this->editor))) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($this->editor) && isset($this->editors[$this->editor]) && !is_callable($this->editors[$this->editor])) {
|
||||||
|
return [
|
||||||
|
'ajax' => false,
|
||||||
|
'url' => $this->editors[$this->editor],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_callable($this->editor) || (isset($this->editors[$this->editor]) && is_callable($this->editors[$this->editor]))) {
|
||||||
|
if (is_callable($this->editor)) {
|
||||||
|
$callback = call_user_func($this->editor, $filePath, $line);
|
||||||
|
} else {
|
||||||
|
$callback = call_user_func($this->editors[$this->editor], $filePath, $line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($callback)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($callback)) {
|
||||||
|
return [
|
||||||
|
'ajax' => false,
|
||||||
|
'url' => $callback,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'ajax' => isset($callback['ajax']) ? $callback['ajax'] : false,
|
||||||
|
'url' => isset($callback['url']) ? $callback['url'] : $callback,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the page title.
|
||||||
|
*
|
||||||
|
* @param string $title
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function setPageTitle($title)
|
||||||
|
{
|
||||||
|
$this->pageTitle = (string) $title;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the page title.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPageTitle()
|
||||||
|
{
|
||||||
|
return $this->pageTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a path to the list of paths to be searched for resources.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If $path is not a valid directory
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function addResourcePath($path)
|
||||||
|
{
|
||||||
|
if (!is_dir($path)) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
"'$path' is not a valid directory"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
array_unshift($this->searchPaths, $path);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a custom css file to be loaded.
|
||||||
|
*
|
||||||
|
* @param string|null $name
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function addCustomCss($name)
|
||||||
|
{
|
||||||
|
$this->customCss = $name;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a custom js file to be loaded.
|
||||||
|
*
|
||||||
|
* @param string|null $name
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function addCustomJs($name)
|
||||||
|
{
|
||||||
|
$this->customJs = $name;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getResourcePaths()
|
||||||
|
{
|
||||||
|
return $this->searchPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a resource, by its relative path, in all available search paths.
|
||||||
|
*
|
||||||
|
* The search is performed starting at the last search path, and all the
|
||||||
|
* way back to the first, enabling a cascading-type system of overrides for
|
||||||
|
* all resources.
|
||||||
|
*
|
||||||
|
* @param string $resource
|
||||||
|
*
|
||||||
|
* @throws RuntimeException If resource cannot be found in any of the available paths
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getResource($resource)
|
||||||
|
{
|
||||||
|
// If the resource was found before, we can speed things up
|
||||||
|
// by caching its absolute, resolved path:
|
||||||
|
if (isset($this->resourceCache[$resource])) {
|
||||||
|
return $this->resourceCache[$resource];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search through available search paths, until we find the
|
||||||
|
// resource we're after:
|
||||||
|
foreach ($this->searchPaths as $path) {
|
||||||
|
$fullPath = $path . "/$resource";
|
||||||
|
|
||||||
|
if (is_file($fullPath)) {
|
||||||
|
// Cache the result:
|
||||||
|
$this->resourceCache[$resource] = $fullPath;
|
||||||
|
return $fullPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got this far, nothing was found.
|
||||||
|
throw new RuntimeException(
|
||||||
|
"Could not find resource '$resource' in any resource paths."
|
||||||
|
. "(searched: " . join(", ", $this->searchPaths). ")"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getResourcesPath()
|
||||||
|
{
|
||||||
|
$allPaths = $this->getResourcePaths();
|
||||||
|
|
||||||
|
// Compat: return only the first path added
|
||||||
|
return end($allPaths) ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*
|
||||||
|
* @param string $resourcesPath
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function setResourcesPath($resourcesPath)
|
||||||
|
{
|
||||||
|
$this->addResourcePath($resourcesPath);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the application paths.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getApplicationPaths()
|
||||||
|
{
|
||||||
|
return $this->applicationPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the application paths.
|
||||||
|
*
|
||||||
|
* @param array $applicationPaths
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setApplicationPaths($applicationPaths)
|
||||||
|
{
|
||||||
|
$this->applicationPaths = $applicationPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the application root path.
|
||||||
|
*
|
||||||
|
* @param string $applicationRootPath
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setApplicationRootPath($applicationRootPath)
|
||||||
|
{
|
||||||
|
$this->templateHelper->setApplicationRootPath($applicationRootPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* blacklist a sensitive value within one of the superglobal arrays.
|
||||||
|
* Alias for the hideSuperglobalKey method.
|
||||||
|
*
|
||||||
|
* @param string $superGlobalName The name of the superglobal array, e.g. '_GET'
|
||||||
|
* @param string $key The key within the superglobal
|
||||||
|
* @see hideSuperglobalKey
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function blacklist($superGlobalName, $key)
|
||||||
|
{
|
||||||
|
$this->blacklist[$superGlobalName][] = $key;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide a sensitive value within one of the superglobal arrays.
|
||||||
|
*
|
||||||
|
* @param string $superGlobalName The name of the superglobal array, e.g. '_GET'
|
||||||
|
* @param string $key The key within the superglobal
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function hideSuperglobalKey($superGlobalName, $key)
|
||||||
|
{
|
||||||
|
return $this->blacklist($superGlobalName, $key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks all values within the given superGlobal array.
|
||||||
|
*
|
||||||
|
* Blacklisted values will be replaced by a equal length string containing
|
||||||
|
* only '*' characters for string values.
|
||||||
|
* Non-string values will be replaced with a fixed asterisk count.
|
||||||
|
* We intentionally dont rely on $GLOBALS as it depends on the 'auto_globals_jit' php.ini setting.
|
||||||
|
*
|
||||||
|
* @param array $superGlobal One of the superglobal arrays
|
||||||
|
* @param string $superGlobalName The name of the superglobal array, e.g. '_GET'
|
||||||
|
*
|
||||||
|
* @return array $values without sensitive data
|
||||||
|
*/
|
||||||
|
private function masked(array $superGlobal, $superGlobalName)
|
||||||
|
{
|
||||||
|
$blacklisted = $this->blacklist[$superGlobalName];
|
||||||
|
|
||||||
|
$values = $superGlobal;
|
||||||
|
|
||||||
|
foreach ($blacklisted as $key) {
|
||||||
|
if (isset($superGlobal[$key])) {
|
||||||
|
$values[$key] = str_repeat('*', is_string($superGlobal[$key]) ? strlen($superGlobal[$key]) : 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $values;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops\Handler;
|
||||||
|
|
||||||
|
use SimpleXMLElement;
|
||||||
|
use Whoops\Exception\Formatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catches an exception and converts it to an XML
|
||||||
|
* response. Additionally can also return exception
|
||||||
|
* frames for consumption by an API.
|
||||||
|
*/
|
||||||
|
class XmlResponseHandler extends Handler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $returnFrames = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool|null $returnFrames
|
||||||
|
* @return bool|static
|
||||||
|
*/
|
||||||
|
public function addTraceToOutput($returnFrames = null)
|
||||||
|
{
|
||||||
|
if (func_num_args() == 0) {
|
||||||
|
return $this->returnFrames;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->returnFrames = (bool) $returnFrames;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$response = [
|
||||||
|
'error' => Formatter::formatExceptionAsDataArray(
|
||||||
|
$this->getInspector(),
|
||||||
|
$this->addTraceToOutput()
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
echo self::toXml($response);
|
||||||
|
|
||||||
|
return Handler::QUIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function contentType()
|
||||||
|
{
|
||||||
|
return 'application/xml';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SimpleXMLElement $node Node to append data to, will be modified in place
|
||||||
|
* @param array|\Traversable $data
|
||||||
|
* @return SimpleXMLElement The modified node, for chaining
|
||||||
|
*/
|
||||||
|
private static function addDataToNode(\SimpleXMLElement $node, $data)
|
||||||
|
{
|
||||||
|
assert(is_array($data) || $data instanceof Traversable);
|
||||||
|
|
||||||
|
foreach ($data as $key => $value) {
|
||||||
|
if (is_numeric($key)) {
|
||||||
|
// Convert the key to a valid string
|
||||||
|
$key = "unknownNode_". (string) $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete any char not allowed in XML element names
|
||||||
|
$key = preg_replace('/[^a-z0-9\-\_\.\:]/i', '', $key);
|
||||||
|
|
||||||
|
if (is_array($value)) {
|
||||||
|
$child = $node->addChild($key);
|
||||||
|
self::addDataToNode($child, $value);
|
||||||
|
} else {
|
||||||
|
$value = str_replace('&', '&', print_r($value, true));
|
||||||
|
$node->addChild($key, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main function for converting to an XML document.
|
||||||
|
*
|
||||||
|
* @param array|\Traversable $data
|
||||||
|
* @return string XML
|
||||||
|
*/
|
||||||
|
private static function toXml($data)
|
||||||
|
{
|
||||||
|
assert(is_array($data) || $data instanceof Traversable);
|
||||||
|
|
||||||
|
$node = simplexml_load_string("<?xml version='1.0' encoding='utf-8'?><root />");
|
||||||
|
|
||||||
|
return self::addDataToNode($node, $data)->asXML();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,604 @@
|
||||||
|
body {
|
||||||
|
font: 12px "Helvetica Neue", helvetica, arial, sans-serif;
|
||||||
|
color: #131313;
|
||||||
|
background: #eeeeee;
|
||||||
|
padding:0;
|
||||||
|
margin: 0;
|
||||||
|
max-height: 100%;
|
||||||
|
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Whoops.container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 9999999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
overflow-y: scroll;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
margin: 0;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.branding {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 20px;
|
||||||
|
color: #777777;
|
||||||
|
font-size: 10px;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
.branding a {
|
||||||
|
color: #e95353;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
color: white;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
padding: 35px 40px;
|
||||||
|
max-height: 180px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
header.header-expand {
|
||||||
|
max-height: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exc-title {
|
||||||
|
margin: 0;
|
||||||
|
color: #bebebe;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.exc-title-primary, .exc-title-secondary {
|
||||||
|
color: #e95353;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exc-message {
|
||||||
|
font-size: 20px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
margin: 4px 0 0 0;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.exc-message span {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.exc-message-empty-notice {
|
||||||
|
color: #a29d9d;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prev-exc-title {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prev-exc-title + ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 0 0 20px;
|
||||||
|
line-height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prev-exc-title + ul li {
|
||||||
|
font: 12px "Helvetica Neue", helvetica, arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prev-exc-title + ul li .prev-exc-code {
|
||||||
|
display: inline-block;
|
||||||
|
color: #bebebe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-container {
|
||||||
|
left: 30%;
|
||||||
|
width: 70%;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
.details {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-heading {
|
||||||
|
color: #4288CE;
|
||||||
|
font-weight: 300;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, .1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.details pre.sf-dump {
|
||||||
|
white-space: pre;
|
||||||
|
word-wrap: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details pre.sf-dump,
|
||||||
|
.details pre.sf-dump .sf-dump-num,
|
||||||
|
.details pre.sf-dump .sf-dump-const,
|
||||||
|
.details pre.sf-dump .sf-dump-str,
|
||||||
|
.details pre.sf-dump .sf-dump-note,
|
||||||
|
.details pre.sf-dump .sf-dump-ref,
|
||||||
|
.details pre.sf-dump .sf-dump-public,
|
||||||
|
.details pre.sf-dump .sf-dump-protected,
|
||||||
|
.details pre.sf-dump .sf-dump-private,
|
||||||
|
.details pre.sf-dump .sf-dump-meta,
|
||||||
|
.details pre.sf-dump .sf-dump-key,
|
||||||
|
.details pre.sf-dump .sf-dump-index {
|
||||||
|
color: #463C54;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel {
|
||||||
|
width: 30%;
|
||||||
|
background: #ded8d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frames-description {
|
||||||
|
background: rgba(0, 0, 0, .05);
|
||||||
|
padding: 8px 15px;
|
||||||
|
color: #a29d9d;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frames-description.frames-description-application {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.frames-container.frames-container-application .frame:not(.frame-application) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frames-tab {
|
||||||
|
color: #a29d9d;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 8px;
|
||||||
|
margin: 0 2px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frames-tab.frames-tab-active {
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
color: #bebebe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame {
|
||||||
|
padding: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.1s ease;
|
||||||
|
background: #eeeeee;
|
||||||
|
}
|
||||||
|
.frame:not(:last-child) {
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, .05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame.active {
|
||||||
|
box-shadow: inset -5px 0 0 0 #4288CE;
|
||||||
|
color: #4288CE;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame:not(.active):hover {
|
||||||
|
background: #BEE9EA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-method-info {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-class, .frame-function, .frame-index {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-index {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-method-info {
|
||||||
|
margin-left: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-index {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #a29d9d;
|
||||||
|
background-color: rgba(0, 0, 0, .05);
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
line-height: 18px;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0 1px 0 1px;
|
||||||
|
text-align: center;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-application .frame-index {
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
color: #bebebe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-file {
|
||||||
|
font-family: "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace;
|
||||||
|
color: #a29d9d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-file .editor-link {
|
||||||
|
color: #a29d9d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-line {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-line:before {
|
||||||
|
content: ":";
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-code {
|
||||||
|
padding: 5px;
|
||||||
|
background: #303030;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-code.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-code .frame-file {
|
||||||
|
color: #a29d9d;
|
||||||
|
padding: 12px 6px;
|
||||||
|
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block {
|
||||||
|
padding: 10px;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 3px 0 rgba(0, 0, 0, .05),
|
||||||
|
0 10px 30px rgba(0, 0, 0, .05),
|
||||||
|
inset 0 0 1px 0 rgba(255, 255, 255, .07);
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
-o-tab-size: 4;
|
||||||
|
tab-size: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linenums {
|
||||||
|
margin: 0;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-comments {
|
||||||
|
border-top: none;
|
||||||
|
margin-top: 15px;
|
||||||
|
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-comments.empty {
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-comments.empty:before {
|
||||||
|
content: "No comments for this stack frame.";
|
||||||
|
font-weight: 300;
|
||||||
|
color: #a29d9d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-comment {
|
||||||
|
padding: 10px;
|
||||||
|
color: #e3e3e3;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: rgba(255, 255, 255, .05);
|
||||||
|
}
|
||||||
|
.frame-comment a {
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.frame-comment a:hover {
|
||||||
|
color: #4bb1b1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-comment:not(:last-child) {
|
||||||
|
border-bottom: 1px dotted rgba(0, 0, 0, .3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.frame-comment-context {
|
||||||
|
font-size: 10px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delimiter {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table-container label {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #303030;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 10px 0;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
margin-bottom: 5px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
.data-table {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table tbody {
|
||||||
|
font: 13px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table thead {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table tr {
|
||||||
|
padding: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table td:first-child {
|
||||||
|
width: 20%;
|
||||||
|
min-width: 130px;
|
||||||
|
overflow: hidden;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #463C54;
|
||||||
|
padding-right: 5px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table td:last-child {
|
||||||
|
width: 80%;
|
||||||
|
-ms-word-break: break-all;
|
||||||
|
word-break: break-all;
|
||||||
|
word-break: break-word;
|
||||||
|
-webkit-hyphens: auto;
|
||||||
|
-moz-hyphens: auto;
|
||||||
|
hyphens: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table span.empty {
|
||||||
|
color: rgba(0, 0, 0, .3);
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
.data-table label.empty {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handler {
|
||||||
|
padding: 4px 0;
|
||||||
|
font: 14px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* prettify code style
|
||||||
|
Uses the Doxy theme as a base */
|
||||||
|
pre .str, code .str { color: #BCD42A; } /* string */
|
||||||
|
pre .kwd, code .kwd { color: #4bb1b1; font-weight: bold; } /* keyword*/
|
||||||
|
pre .com, code .com { color: #888; font-weight: bold; } /* comment */
|
||||||
|
pre .typ, code .typ { color: #ef7c61; } /* type */
|
||||||
|
pre .lit, code .lit { color: #BCD42A; } /* literal */
|
||||||
|
pre .pun, code .pun { color: #fff; font-weight: bold; } /* punctuation */
|
||||||
|
pre .pln, code .pln { color: #e9e4e5; } /* plaintext */
|
||||||
|
pre .tag, code .tag { color: #4bb1b1; } /* html/xml tag */
|
||||||
|
pre .htm, code .htm { color: #dda0dd; } /* html tag */
|
||||||
|
pre .xsl, code .xsl { color: #d0a0d0; } /* xslt tag */
|
||||||
|
pre .atn, code .atn { color: #ef7c61; font-weight: normal;} /* html/xml attribute name */
|
||||||
|
pre .atv, code .atv { color: #bcd42a; } /* html/xml attribute value */
|
||||||
|
pre .dec, code .dec { color: #606; } /* decimal */
|
||||||
|
pre.code-block, code.code-block, .frame-args.code-block, .frame-args.code-block samp {
|
||||||
|
font-family: "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace;
|
||||||
|
background: #333;
|
||||||
|
color: #e9e4e5;
|
||||||
|
}
|
||||||
|
pre.code-block {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre.code-block a, code.code-block a {
|
||||||
|
text-decoration:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linenums li {
|
||||||
|
color: #A5A5A5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linenums li.current{
|
||||||
|
background: rgba(255, 100, 100, .07);
|
||||||
|
}
|
||||||
|
.linenums li.current.active {
|
||||||
|
background: rgba(255, 100, 100, .17);
|
||||||
|
}
|
||||||
|
|
||||||
|
pre:not(.prettyprinted) {
|
||||||
|
padding-left: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#plain-exception {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rightButton {
|
||||||
|
cursor: pointer;
|
||||||
|
border: 0;
|
||||||
|
opacity: .8;
|
||||||
|
background: none;
|
||||||
|
|
||||||
|
color: rgba(255, 255, 255, 0.1);
|
||||||
|
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.1);
|
||||||
|
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rightButton:hover {
|
||||||
|
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.3);
|
||||||
|
color: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* inspired by githubs kbd styles */
|
||||||
|
kbd {
|
||||||
|
-moz-border-bottom-colors: none;
|
||||||
|
-moz-border-left-colors: none;
|
||||||
|
-moz-border-right-colors: none;
|
||||||
|
-moz-border-top-colors: none;
|
||||||
|
background-color: #fcfcfc;
|
||||||
|
border-color: #ccc #ccc #bbb;
|
||||||
|
border-image: none;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
color: #555;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 10px;
|
||||||
|
padding: 3px 5px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* == Media queries */
|
||||||
|
|
||||||
|
/* Expand the spacing in the details section */
|
||||||
|
@media (min-width: 1000px) {
|
||||||
|
.details, .frame-code {
|
||||||
|
padding: 20px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-container {
|
||||||
|
left: 32%;
|
||||||
|
width: 68%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frames-container {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel {
|
||||||
|
width: 32%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stack panels */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.panel {
|
||||||
|
position: static;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stack details tables */
|
||||||
|
@media (max-width: 400px) {
|
||||||
|
.data-table,
|
||||||
|
.data-table tbody,
|
||||||
|
.data-table tbody tr,
|
||||||
|
.data-table tbody td {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table tbody tr:first-child {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table tbody td:first-child,
|
||||||
|
.data-table tbody td:last-child {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table tbody td:last-child {
|
||||||
|
padding-top: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltipped {
|
||||||
|
position: relative
|
||||||
|
}
|
||||||
|
.tooltipped:after {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000000;
|
||||||
|
display: none;
|
||||||
|
padding: 5px 8px;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
text-shadow: none;
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: normal;
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: pre;
|
||||||
|
pointer-events: none;
|
||||||
|
content: attr(aria-label);
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
border-radius: 3px;
|
||||||
|
-webkit-font-smoothing: subpixel-antialiased
|
||||||
|
}
|
||||||
|
.tooltipped:before {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000001;
|
||||||
|
display: none;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
pointer-events: none;
|
||||||
|
content: "";
|
||||||
|
border: 5px solid transparent
|
||||||
|
}
|
||||||
|
.tooltipped:hover:before,
|
||||||
|
.tooltipped:hover:after,
|
||||||
|
.tooltipped:active:before,
|
||||||
|
.tooltipped:active:after,
|
||||||
|
.tooltipped:focus:before,
|
||||||
|
.tooltipped:focus:after {
|
||||||
|
display: inline-block;
|
||||||
|
text-decoration: none
|
||||||
|
}
|
||||||
|
.tooltipped-s:after {
|
||||||
|
top: 100%;
|
||||||
|
right: 50%;
|
||||||
|
margin-top: 5px
|
||||||
|
}
|
||||||
|
.tooltipped-s:before {
|
||||||
|
top: auto;
|
||||||
|
right: 50%;
|
||||||
|
bottom: -5px;
|
||||||
|
margin-right: -5px;
|
||||||
|
border-bottom-color: rgba(0, 0, 0, 0.8)
|
||||||
|
}
|
||||||
|
|
||||||
|
pre.sf-dump {
|
||||||
|
padding: 0px !important;
|
||||||
|
margin: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-for-help {
|
||||||
|
width: 85%;
|
||||||
|
padding: 0;
|
||||||
|
margin: 10px 0;
|
||||||
|
list-style-type: none;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.search-for-help li {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
.search-for-help li:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
.search-for-help li a {
|
||||||
|
|
||||||
|
}
|
||||||
|
.search-for-help li a i {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.search-for-help li a svg {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
.search-for-help li a svg path {
|
||||||
|
background-size: contain;
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,28 @@
|
||||||
|
var r=null;window.PR_SHOULD_USE_CONTINUATION=!0;
|
||||||
|
(function(){function O(a){function i(d){var a=d.charCodeAt(0);if(a!==92)return a;var f=d.charAt(1);return(a=s[f])?a:"0"<=f&&f<="7"?parseInt(d.substring(1),8):f==="u"||f==="x"?parseInt(d.substring(2),16):d.charCodeAt(1)}function g(d){if(d<32)return(d<16?"\\x0":"\\x")+d.toString(16);d=String.fromCharCode(d);return d==="\\"||d==="-"||d==="]"||d==="^"?"\\"+d:d}function j(d){var a=d.substring(1,d.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),d=[],f=
|
||||||
|
a[0]==="^",b=["["];f&&b.push("^");for(var f=f?1:0,c=a.length;f<c;++f){var h=a[f];if(/\\[bdsw]/i.test(h))b.push(h);else{var h=i(h),e;f+2<c&&"-"===a[f+1]?(e=i(a[f+2]),f+=2):e=h;d.push([h,e]);e<65||h>122||(e<65||h>90||d.push([Math.max(65,h)|32,Math.min(e,90)|32]),e<97||h>122||d.push([Math.max(97,h)&-33,Math.min(e,122)&-33]))}}d.sort(function(d,a){return d[0]-a[0]||a[1]-d[1]});a=[];c=[];for(f=0;f<d.length;++f)h=d[f],h[0]<=c[1]+1?c[1]=Math.max(c[1],h[1]):a.push(c=h);for(f=0;f<a.length;++f)h=a[f],b.push(g(h[0])),
|
||||||
|
h[1]>h[0]&&(h[1]+1>h[0]&&b.push("-"),b.push(g(h[1])));b.push("]");return b.join("")}function t(d){for(var a=d.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=a.length,i=[],c=0,h=0;c<b;++c){var e=a[c];e==="("?++h:"\\"===e.charAt(0)&&(e=+e.substring(1))&&(e<=h?i[e]=-1:a[c]=g(e))}for(c=1;c<i.length;++c)-1===i[c]&&(i[c]=++z);for(h=c=0;c<b;++c)e=a[c],e==="("?(++h,i[h]||(a[c]="(?:")):"\\"===e.charAt(0)&&(e=+e.substring(1))&&e<=h&&
|
||||||
|
(a[c]="\\"+i[e]);for(c=0;c<b;++c)"^"===a[c]&&"^"!==a[c+1]&&(a[c]="");if(d.ignoreCase&&w)for(c=0;c<b;++c)e=a[c],d=e.charAt(0),e.length>=2&&d==="["?a[c]=j(e):d!=="\\"&&(a[c]=e.replace(/[A-Za-z]/g,function(d){d=d.charCodeAt(0);return"["+String.fromCharCode(d&-33,d|32)+"]"}));return a.join("")}for(var z=0,w=!1,k=!1,m=0,b=a.length;m<b;++m){var o=a[m];if(o.ignoreCase)k=!0;else if(/[a-z]/i.test(o.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){w=!0;k=!1;break}}for(var s={b:8,t:9,n:10,v:11,
|
||||||
|
f:12,r:13},q=[],m=0,b=a.length;m<b;++m){o=a[m];if(o.global||o.multiline)throw Error(""+o);q.push("(?:"+t(o)+")")}return RegExp(q.join("|"),k?"gi":"g")}function P(a,i){function g(a){switch(a.nodeType){case 1:if(j.test(a.className))break;for(var b=a.firstChild;b;b=b.nextSibling)g(b);b=a.nodeName.toLowerCase();if("br"===b||"li"===b)t[k]="\n",w[k<<1]=z++,w[k++<<1|1]=a;break;case 3:case 4:b=a.nodeValue,b.length&&(b=i?b.replace(/\r\n?/g,"\n"):b.replace(/[\t\n\r ]+/g," "),t[k]=b,w[k<<1]=z,z+=b.length,w[k++<<
|
||||||
|
1|1]=a)}}var j=/(?:^|\s)nocode(?:\s|$)/,t=[],z=0,w=[],k=0;g(a);return{a:t.join("").replace(/\n$/,""),d:w}}function E(a,i,g,j){i&&(a={a:i,e:a},g(a),j.push.apply(j,a.g))}function x(a,i){function g(a){for(var k=a.e,m=[k,"pln"],b=0,o=a.a.match(t)||[],s={},q=0,d=o.length;q<d;++q){var v=o[q],f=s[v],u=void 0,c;if(typeof f==="string")c=!1;else{var h=j[v.charAt(0)];if(h)u=v.match(h[1]),f=h[0];else{for(c=0;c<z;++c)if(h=i[c],u=v.match(h[1])){f=h[0];break}u||(f="pln")}if((c=f.length>=5&&"lang-"===f.substring(0,
|
||||||
|
5))&&!(u&&typeof u[1]==="string"))c=!1,f="src";c||(s[v]=f)}h=b;b+=v.length;if(c){c=u[1];var e=v.indexOf(c),p=e+c.length;u[2]&&(p=v.length-u[2].length,e=p-c.length);f=f.substring(5);E(k+h,v.substring(0,e),g,m);E(k+h+e,c,F(f,c),m);E(k+h+p,v.substring(p),g,m)}else m.push(k+h,f)}a.g=m}var j={},t;(function(){for(var g=a.concat(i),k=[],m={},b=0,o=g.length;b<o;++b){var s=g[b],q=s[3];if(q)for(var d=q.length;--d>=0;)j[q.charAt(d)]=s;s=s[1];q=""+s;m.hasOwnProperty(q)||(k.push(s),m[q]=r)}k.push(/[\S\s]/);t=
|
||||||
|
O(k)})();var z=i.length;return g}function l(a){var i=[],g=[];a.tripleQuotedStrings?i.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,r,"'\""]):a.multiLineStrings?i.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,r,"'\"`"]):i.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,r,"\"'"]);a.verbatimStrings&&
|
||||||
|
g.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,r]);var j=a.hashComments;j&&(a.cStyleComments?(j>1?i.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,r,"#"]):i.push(["com",/^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\n\r]*)/,r,"#"]),g.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,r])):i.push(["com",/^#[^\n\r]*/,r,"#"]));a.cStyleComments&&(g.push(["com",/^\/\/[^\n\r]*/,r]),g.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,
|
||||||
|
r]));a.regexLiterals&&g.push(["lang-regex",/^(?:^^\.?|[+-]|[!=]={0,2}|#|%=?|&&?=?|\(|\*=?|[+-]=|->|\/=?|::?|<<?=?|>{1,3}=?|[,;?@[{~]|\^\^?=?|\|\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(j=a.types)&&g.push(["typ",j]);a=(""+a.keywords).replace(/^ | $/g,"");a.length&&g.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),r]);i.push(["pln",/^\s+/,r," \r\n\t\u00a0"]);g.push(["lit",
|
||||||
|
/^@[$_a-z][\w$@]*/i,r],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,r],["pln",/^[$_a-z][\w$@]*/i,r],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,r,"0123456789"],["pln",/^\\[\S\s]?/,r],["pun",/^.[^\s\w"$'./@\\`]*/,r]);return x(i,g)}function G(a,i,g){function j(a){switch(a.nodeType){case 1:if(z.test(a.className))break;if("br"===a.nodeName)t(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)j(a);break;case 3:case 4:if(g){var b=
|
||||||
|
a.nodeValue,f=b.match(n);if(f){var i=b.substring(0,f.index);a.nodeValue=i;(b=b.substring(f.index+f[0].length))&&a.parentNode.insertBefore(k.createTextNode(b),a.nextSibling);t(a);i||a.parentNode.removeChild(a)}}}}function t(a){function i(a,b){var d=b?a.cloneNode(!1):a,e=a.parentNode;if(e){var e=i(e,1),f=a.nextSibling;e.appendChild(d);for(var g=f;g;g=f)f=g.nextSibling,e.appendChild(g)}return d}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=i(a.nextSibling,0),f;(f=a.parentNode)&&f.nodeType===
|
||||||
|
1;)a=f;b.push(a)}for(var z=/(?:^|\s)nocode(?:\s|$)/,n=/\r\n?|\n/,k=a.ownerDocument,m=k.createElement("li");a.firstChild;)m.appendChild(a.firstChild);for(var b=[m],o=0;o<b.length;++o)j(b[o]);i===(i|0)&&b[0].setAttribute("value",i);var s=k.createElement("ol");s.className="linenums";for(var i=Math.max(0,i-1|0)||0,o=0,q=b.length;o<q;++o)m=b[o],m.className="L"+(o+i)%10,m.firstChild||m.appendChild(k.createTextNode("\u00a0")),s.appendChild(m);a.appendChild(s)}function n(a,i){for(var g=i.length;--g>=0;){var j=
|
||||||
|
i[g];A.hasOwnProperty(j)?C.console&&console.warn("cannot override language handler %s",j):A[j]=a}}function F(a,i){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(i)?"default-markup":"default-code";return A[a]}function H(a){var i=a.h;try{var g=P(a.c,a.i),j=g.a;a.a=j;a.d=g.d;a.e=0;F(i,j)(a);var t=/\bMSIE\s(\d+)/.exec(navigator.userAgent),t=t&&+t[1]<=8,i=/\n/g,n=a.a,w=n.length,g=0,k=a.d,m=k.length,j=0,b=a.g,o=b.length,s=0;b[o]=w;var q,d;for(d=q=0;d<o;)b[d]!==b[d+2]?(b[q++]=b[d++],b[q++]=b[d++]):d+=2;o=q;
|
||||||
|
for(d=q=0;d<o;){for(var v=b[d],f=b[d+1],u=d+2;u+2<=o&&b[u+1]===f;)u+=2;b[q++]=v;b[q++]=f;d=u}b.length=q;var c=a.c,h;if(c)h=c.style.display,c.style.display="none";try{for(;j<m;){var e=k[j+2]||w,p=b[s+2]||w,u=Math.min(e,p),l=k[j+1],D;if(l.nodeType!==1&&(D=n.substring(g,u))){t&&(D=D.replace(i,"\r"));l.nodeValue=D;var y=l.ownerDocument,x=y.createElement("span");x.className=b[s+1];var B=l.parentNode;B.replaceChild(x,l);x.appendChild(l);g<e&&(k[j+1]=l=y.createTextNode(n.substring(u,e)),B.insertBefore(l,
|
||||||
|
x.nextSibling))}g=u;g>=e&&(j+=2);g>=p&&(s+=2)}}finally{if(c)c.style.display=h}}catch(A){C.console&&console.log(A&&A.stack?A.stack:A)}}var C=window,y=["break,continue,do,else,for,if,return,while"],B=[[y,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],I=[B,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],
|
||||||
|
J=[B,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],K=[J,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,let,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var,virtual,where"],B=[B,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],
|
||||||
|
L=[y,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],M=[y,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],y=[y,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],N=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/,
|
||||||
|
Q=/\S/,R=l({keywords:[I,K,B,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+L,M,y],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};n(R,["default-code"]);n(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",
|
||||||
|
/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);n(x([["pln",/^\s+/,r," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,r,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],
|
||||||
|
["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);n(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);n(l({keywords:I,hashComments:!0,cStyleComments:!0,types:N}),["c","cc","cpp","cxx","cyc","m"]);n(l({keywords:"null,true,false"}),["json"]);n(l({keywords:K,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:N}),
|
||||||
|
["cs"]);n(l({keywords:J,cStyleComments:!0}),["java"]);n(l({keywords:y,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);n(l({keywords:L,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py"]);n(l({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);n(l({keywords:M,hashComments:!0,
|
||||||
|
multiLineStrings:!0,regexLiterals:!0}),["rb"]);n(l({keywords:B,cStyleComments:!0,regexLiterals:!0}),["js"]);n(l({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);n(x([],[["str",/^[\S\s]+/]]),["regex"]);var S=C.PR={createSimpleLexer:x,registerLangHandler:n,sourceDecorator:l,
|
||||||
|
PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ",prettyPrintOne:C.prettyPrintOne=function(a,i,g){var j=document.createElement("pre");j.innerHTML=a;g&&G(j,g,!0);H({h:i,j:g,c:j,i:1});return j.innerHTML},prettyPrint:C.prettyPrint=function(a){function i(){var u;for(var g=C.PR_SHOULD_USE_CONTINUATION?k.now()+250:Infinity;m<j.length&&
|
||||||
|
k.now()<g;m++){var c=j[m],h=c.className;if(s.test(h)&&!q.test(h)){for(var e=!1,p=c.parentNode;p;p=p.parentNode)if(f.test(p.tagName)&&p.className&&s.test(p.className)){e=!0;break}if(!e){c.className+=" prettyprinted";var h=h.match(o),n;if(e=!h){for(var e=c,p=void 0,l=e.firstChild;l;l=l.nextSibling)var t=l.nodeType,p=t===1?p?e:l:t===3?Q.test(l.nodeValue)?e:p:p;e=(n=p===e?void 0:p)&&v.test(n.tagName)}e&&(h=n.className.match(o));h&&(h=h[1]);u=d.test(c.tagName)?1:(e=(e=c.currentStyle)?e.whiteSpace:document.defaultView&&
|
||||||
|
document.defaultView.getComputedStyle?document.defaultView.getComputedStyle(c,r).getPropertyValue("white-space"):0)&&"pre"===e.substring(0,3),e=u;(p=(p=c.className.match(/\blinenums\b(?::(\d+))?/))?p[1]&&p[1].length?+p[1]:!0:!1)&&G(c,p,e);b={h:h,c:c,j:p,i:e};H(b)}}}m<j.length?setTimeout(i,250):a&&a()}for(var g=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],j=[],n=0;n<g.length;++n)for(var l=0,w=g[n].length;l<w;++l)j.push(g[n][l]);var g=
|
||||||
|
r,k=Date;k.now||(k={now:function(){return+new Date}});var m=0,b,o=/\blang(?:uage)?-([\w.]+)(?!\S)/,s=/\bprettyprint\b/,q=/\bprettyprinted\b/,d=/pre|xmp/i,v=/^code$/i,f=/^(?:pre|code|xmp)$/i;i()}};typeof define==="function"&&define.amd&&define("google-code-prettify",[],function(){return S})})();
|
|
@ -0,0 +1,210 @@
|
||||||
|
Zepto(function($) {
|
||||||
|
var $leftPanel = $('.left-panel');
|
||||||
|
var $frameContainer = $('.frames-container');
|
||||||
|
var $appFramesTab = $('#application-frames-tab');
|
||||||
|
var $allFramesTab = $('#all-frames-tab');
|
||||||
|
var $container = $('.details-container');
|
||||||
|
var $activeLine = $frameContainer.find('.frame.active');
|
||||||
|
var $activeFrame = $container.find('.frame-code.active');
|
||||||
|
var $ajaxEditors = $('.editor-link[data-ajax]');
|
||||||
|
var $header = $('header');
|
||||||
|
|
||||||
|
$header.on('mouseenter', function () {
|
||||||
|
if ($header.find('.exception').height() >= 145) {
|
||||||
|
$header.addClass('header-expand');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$header.on('mouseleave', function () {
|
||||||
|
$header.removeClass('header-expand');
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* add prettyprint classes to our current active codeblock
|
||||||
|
* run prettyPrint() to highlight the active code
|
||||||
|
* scroll to the line when prettyprint is done
|
||||||
|
* highlight the current line
|
||||||
|
*/
|
||||||
|
var renderCurrentCodeblock = function(id) {
|
||||||
|
|
||||||
|
// remove previous codeblocks so we only render the active one
|
||||||
|
$('.code-block').removeClass('prettyprint');
|
||||||
|
|
||||||
|
// pass the id in when we can for speed
|
||||||
|
if (typeof(id) === 'undefined' || typeof(id) === 'object') {
|
||||||
|
var id = /frame\-line\-([\d]*)/.exec($activeLine.attr('id'))[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#frame-code-linenums-' + id).addClass('prettyprint');
|
||||||
|
$('#frame-code-args-' + id).addClass('prettyprint');
|
||||||
|
|
||||||
|
prettyPrint(highlightCurrentLine);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Highlight the active and neighboring lines for the current frame
|
||||||
|
* Adjust the offset to make sure that line is veritcally centered
|
||||||
|
*/
|
||||||
|
|
||||||
|
var highlightCurrentLine = function() {
|
||||||
|
var activeLineNumber = +($activeLine.find('.frame-line').text());
|
||||||
|
var $lines = $activeFrame.find('.linenums li');
|
||||||
|
var firstLine = +($lines.first().val());
|
||||||
|
|
||||||
|
// We show more code than needed, purely for proper syntax highlighting
|
||||||
|
// Let’s hide a big chunk of that code and then scroll the remaining block
|
||||||
|
$activeFrame.find('.code-block').first().css({
|
||||||
|
maxHeight: 345,
|
||||||
|
overflow: 'hidden',
|
||||||
|
});
|
||||||
|
|
||||||
|
var $offset = $($lines[activeLineNumber - firstLine - 10]);
|
||||||
|
if ($offset.length > 0) {
|
||||||
|
$offset[0].scrollIntoView();
|
||||||
|
}
|
||||||
|
|
||||||
|
$($lines[activeLineNumber - firstLine - 1]).addClass('current');
|
||||||
|
$($lines[activeLineNumber - firstLine]).addClass('current active');
|
||||||
|
$($lines[activeLineNumber - firstLine + 1]).addClass('current');
|
||||||
|
|
||||||
|
$container.scrollTop(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* click handler for loading codeblocks
|
||||||
|
*/
|
||||||
|
|
||||||
|
$frameContainer.on('click', '.frame', function() {
|
||||||
|
|
||||||
|
var $this = $(this);
|
||||||
|
var id = /frame\-line\-([\d]*)/.exec($this.attr('id'))[1];
|
||||||
|
var $codeFrame = $('#frame-code-' + id);
|
||||||
|
|
||||||
|
if ($codeFrame) {
|
||||||
|
|
||||||
|
$activeLine.removeClass('active');
|
||||||
|
$activeFrame.removeClass('active');
|
||||||
|
|
||||||
|
$this.addClass('active');
|
||||||
|
$codeFrame.addClass('active');
|
||||||
|
|
||||||
|
$activeLine = $this;
|
||||||
|
$activeFrame = $codeFrame;
|
||||||
|
|
||||||
|
renderCurrentCodeblock(id);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var clipboard = new Clipboard('.clipboard');
|
||||||
|
var showTooltip = function(elem, msg) {
|
||||||
|
elem.classList.add('tooltipped', 'tooltipped-s');
|
||||||
|
elem.setAttribute('aria-label', msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
clipboard.on('success', function(e) {
|
||||||
|
e.clearSelection();
|
||||||
|
|
||||||
|
showTooltip(e.trigger, 'Copied!');
|
||||||
|
});
|
||||||
|
|
||||||
|
clipboard.on('error', function(e) {
|
||||||
|
showTooltip(e.trigger, fallbackMessage(e.action));
|
||||||
|
});
|
||||||
|
|
||||||
|
var btn = document.querySelector('.clipboard');
|
||||||
|
|
||||||
|
btn.addEventListener('mouseleave', function(e) {
|
||||||
|
e.currentTarget.classList.remove('tooltipped', 'tooltipped-s');
|
||||||
|
e.currentTarget.removeAttribute('aria-label');
|
||||||
|
});
|
||||||
|
|
||||||
|
function fallbackMessage(action) {
|
||||||
|
var actionMsg = '';
|
||||||
|
var actionKey = (action === 'cut' ? 'X' : 'C');
|
||||||
|
|
||||||
|
if (/Mac/i.test(navigator.userAgent)) {
|
||||||
|
actionMsg = 'Press ⌘-' + actionKey + ' to ' + action;
|
||||||
|
} else {
|
||||||
|
actionMsg = 'Press Ctrl-' + actionKey + ' to ' + action;
|
||||||
|
}
|
||||||
|
|
||||||
|
return actionMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollIntoView($node, $parent) {
|
||||||
|
var nodeOffset = $node.offset();
|
||||||
|
var nodeTop = nodeOffset.top;
|
||||||
|
var nodeBottom = nodeTop + nodeOffset.height;
|
||||||
|
var parentScrollTop = $parent.scrollTop();
|
||||||
|
var parentHeight = $parent.height();
|
||||||
|
|
||||||
|
if (nodeTop < 0) {
|
||||||
|
$parent.scrollTop(parentScrollTop + nodeTop);
|
||||||
|
} else if (nodeBottom > parentHeight) {
|
||||||
|
$parent.scrollTop(parentScrollTop + nodeBottom - parentHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on('keydown', function(e) {
|
||||||
|
var applicationFrames = $frameContainer.hasClass('frames-container-application'),
|
||||||
|
frameClass = applicationFrames ? '.frame.frame-application' : '.frame';
|
||||||
|
|
||||||
|
if(e.ctrlKey || e.which === 74 || e.which === 75) {
|
||||||
|
// CTRL+Arrow-UP/k and Arrow-Down/j support:
|
||||||
|
// 1) select the next/prev element
|
||||||
|
// 2) make sure the newly selected element is within the view-scope
|
||||||
|
// 3) focus the (right) container, so arrow-up/down (without ctrl) scroll the details
|
||||||
|
if (e.which === 38 /* arrow up */ || e.which === 75 /* k */) {
|
||||||
|
$activeLine.prev(frameClass).click();
|
||||||
|
scrollIntoView($activeLine, $leftPanel);
|
||||||
|
$container.focus();
|
||||||
|
e.preventDefault();
|
||||||
|
} else if (e.which === 40 /* arrow down */ || e.which === 74 /* j */) {
|
||||||
|
$activeLine.next(frameClass).click();
|
||||||
|
scrollIntoView($activeLine, $leftPanel);
|
||||||
|
$container.focus();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
} else if (e.which == 78 /* n */) {
|
||||||
|
if ($appFramesTab.length) {
|
||||||
|
setActiveFramesTab($('.frames-tab:not(.frames-tab-active)'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render late enough for highlightCurrentLine to be ready
|
||||||
|
renderCurrentCodeblock();
|
||||||
|
|
||||||
|
// Avoid to quit the page with some protocol (e.g. IntelliJ Platform REST API)
|
||||||
|
$ajaxEditors.on('click', function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
$.get(this.href);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Symfony VarDumper: Close the by default expanded objects
|
||||||
|
$('.sf-dump-expanded')
|
||||||
|
.removeClass('sf-dump-expanded')
|
||||||
|
.addClass('sf-dump-compact');
|
||||||
|
$('.sf-dump-toggle span').html('▶');
|
||||||
|
|
||||||
|
// Make the given frames-tab active
|
||||||
|
function setActiveFramesTab($tab) {
|
||||||
|
$tab.addClass('frames-tab-active');
|
||||||
|
|
||||||
|
if ($tab.attr('id') == 'application-frames-tab') {
|
||||||
|
$frameContainer.addClass('frames-container-application');
|
||||||
|
$allFramesTab.removeClass('frames-tab-active');
|
||||||
|
} else {
|
||||||
|
$frameContainer.removeClass('frames-container-application');
|
||||||
|
$appFramesTab.removeClass('frames-tab-active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('a.frames-tab').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
setActiveFramesTab($(this));
|
||||||
|
});
|
||||||
|
});
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,42 @@
|
||||||
|
<?php /* List data-table values, i.e: $_SERVER, $_GET, .... */ ?>
|
||||||
|
<div class="details">
|
||||||
|
<h2 class="details-heading">Environment & details:</h2>
|
||||||
|
|
||||||
|
<div class="data-table-container" id="data-tables">
|
||||||
|
<?php foreach ($tables as $label => $data): ?>
|
||||||
|
<div class="data-table" id="sg-<?php echo $tpl->escape($tpl->slug($label)) ?>">
|
||||||
|
<?php if (!empty($data)): ?>
|
||||||
|
<label><?php echo $tpl->escape($label) ?></label>
|
||||||
|
<table class="data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td class="data-table-k">Key</td>
|
||||||
|
<td class="data-table-v">Value</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<?php foreach ($data as $k => $value): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo $tpl->escape($k) ?></td>
|
||||||
|
<td><?php echo $tpl->dump($value) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</table>
|
||||||
|
<?php else: ?>
|
||||||
|
<label class="empty"><?php echo $tpl->escape($label) ?></label>
|
||||||
|
<span class="empty">empty</span>
|
||||||
|
<?php endif ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php /* List registered handlers, in order of first to last registered */ ?>
|
||||||
|
<div class="data-table-container" id="handlers">
|
||||||
|
<label>Registered Handlers</label>
|
||||||
|
<?php foreach ($handlers as $i => $h): ?>
|
||||||
|
<div class="handler <?php echo ($h === $handler) ? 'active' : ''?>">
|
||||||
|
<?php echo $i ?>. <?php echo $tpl->escape(get_class($h)) ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
|
@ -0,0 +1,63 @@
|
||||||
|
<?php /* Display a code block for all frames in the stack.
|
||||||
|
* @todo: This should PROBABLY be done on-demand, lest
|
||||||
|
* we get 200 frames to process. */ ?>
|
||||||
|
<div class="frame-code-container <?php echo (!$has_frames ? 'empty' : '') ?>">
|
||||||
|
<?php foreach ($frames as $i => $frame): ?>
|
||||||
|
<?php $line = $frame->getLine(); ?>
|
||||||
|
<div class="frame-code <?php echo ($i == 0 ) ? 'active' : '' ?>" id="frame-code-<?php echo $i ?>">
|
||||||
|
<div class="frame-file">
|
||||||
|
<?php $filePath = $frame->getFile(); ?>
|
||||||
|
<?php if ($filePath && $editorHref = $handler->getEditorHref($filePath, (int) $line)): ?>
|
||||||
|
<a href="<?php echo $editorHref ?>" class="editor-link"<?php echo ($handler->getEditorAjax($filePath, (int) $line) ? ' data-ajax' : '') ?>>
|
||||||
|
Open:
|
||||||
|
<strong><?php echo $tpl->breakOnDelimiter('/', $tpl->escape($filePath ?: '<#unknown>')) ?></strong>
|
||||||
|
</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<strong><?php echo $tpl->breakOnDelimiter('/', $tpl->escape($filePath ?: '<#unknown>')) ?></strong>
|
||||||
|
<?php endif ?>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
// Do nothing if there's no line to work off
|
||||||
|
if ($line !== null):
|
||||||
|
|
||||||
|
// the $line is 1-indexed, we nab -1 where needed to account for this
|
||||||
|
$range = $frame->getFileLines($line - 20, 40);
|
||||||
|
|
||||||
|
// getFileLines can return null if there is no source code
|
||||||
|
if ($range):
|
||||||
|
$range = array_map(function ($line) { return empty($line) ? ' ' : $line;}, $range);
|
||||||
|
$start = key($range) + 1;
|
||||||
|
$code = join("\n", $range);
|
||||||
|
?>
|
||||||
|
<pre id="frame-code-linenums-<?=$i?>" class="code-block linenums:<?php echo $start ?>"><?php echo $tpl->escape($code) ?></pre>
|
||||||
|
|
||||||
|
<?php endif ?>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php $frameArgs = $tpl->dumpArgs($frame); ?>
|
||||||
|
<?php if ($frameArgs): ?>
|
||||||
|
<div class="frame-file">
|
||||||
|
Arguments
|
||||||
|
</div>
|
||||||
|
<div id="frame-code-args-<?=$i?>" class="code-block frame-args">
|
||||||
|
<?php echo $frameArgs; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Append comments for this frame
|
||||||
|
$comments = $frame->getComments();
|
||||||
|
?>
|
||||||
|
<div class="frame-comments <?php echo empty($comments) ? 'empty' : '' ?>">
|
||||||
|
<?php foreach ($comments as $commentNo => $comment): ?>
|
||||||
|
<?php extract($comment) ?>
|
||||||
|
<div class="frame-comment" id="comment-<?php echo $i . '-' . $commentNo ?>">
|
||||||
|
<span class="frame-comment-context"><?php echo $tpl->escape($context) ?></span>
|
||||||
|
<?php echo $tpl->escapeButPreserveUris($comment) ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</div>
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php /* List file names & line numbers for all stack frames;
|
||||||
|
clicking these links/buttons will display the code view
|
||||||
|
for that particular frame */ ?>
|
||||||
|
<?php foreach ($frames as $i => $frame): ?>
|
||||||
|
<div class="frame <?php echo ($i == 0 ? 'active' : '') ?> <?php echo ($frame->isApplication() ? 'frame-application' : '') ?>" id="frame-line-<?php echo $i ?>">
|
||||||
|
<span class="frame-index"><?php echo (count($frames) - $i - 1) ?></span>
|
||||||
|
<div class="frame-method-info">
|
||||||
|
<span class="frame-class"><?php echo $tpl->breakOnDelimiter('\\', $tpl->escape($frame->getClass() ?: '')) ?></span>
|
||||||
|
<span class="frame-function"><?php echo $tpl->breakOnDelimiter('\\', $tpl->escape($frame->getFunction() ?: '')) ?></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="frame-file">
|
||||||
|
<?php echo $frame->getFile() ? $tpl->breakOnDelimiter('/', $tpl->shorten($tpl->escape($frame->getFile()))) : '<#unknown>' ?><!--
|
||||||
|
--><span class="frame-line"><?php echo (int) $frame->getLine() ?></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach;
|
|
@ -0,0 +1,3 @@
|
||||||
|
<div class="frames-container <?php echo $active_frames_tab == 'application' ? 'frames-container-application' : '' ?>">
|
||||||
|
<?php $tpl->render($frame_list) ?>
|
||||||
|
</div>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<div class="frames-description <?php echo $has_frames_tabs ? 'frames-description-application' : '' ?>">
|
||||||
|
<?php if ($has_frames_tabs): ?>
|
||||||
|
<a href="#" id="application-frames-tab" class="frames-tab <?php echo $active_frames_tab == 'application' ? 'frames-tab-active' : '' ?>">
|
||||||
|
Application frames (<?php echo $frames->countIsApplication() ?>)
|
||||||
|
</a>
|
||||||
|
<a href="#" id="all-frames-tab" class="frames-tab <?php echo $active_frames_tab == 'all' ? 'frames-tab-active' : '' ?>">
|
||||||
|
All frames (<?php echo count($frames) ?>)
|
||||||
|
</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<span>
|
||||||
|
Stack frames (<?php echo count($frames) ?>)
|
||||||
|
</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
|
@ -0,0 +1,96 @@
|
||||||
|
<div class="exception">
|
||||||
|
<div class="exc-title">
|
||||||
|
<?php foreach ($name as $i => $nameSection): ?>
|
||||||
|
<?php if ($i == count($name) - 1): ?>
|
||||||
|
<span class="exc-title-primary"><?php echo $tpl->escape($nameSection) ?></span>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php echo $tpl->escape($nameSection) . ' \\' ?>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php endforeach ?>
|
||||||
|
<?php if ($code): ?>
|
||||||
|
<span title="Exception Code">(<?php echo $tpl->escape($code) ?>)</span>
|
||||||
|
<?php endif ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="exc-message">
|
||||||
|
<?php if (!empty($message)): ?>
|
||||||
|
<span><?php echo $tpl->escape($message) ?></span>
|
||||||
|
|
||||||
|
|
||||||
|
<?php if (count($previousMessages)): ?>
|
||||||
|
<div class="exc-title prev-exc-title">
|
||||||
|
<span class="exc-title-secondary">Previous exceptions</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<?php foreach ($previousMessages as $i => $previousMessage): ?>
|
||||||
|
<li>
|
||||||
|
<?php echo $tpl->escape($previousMessage) ?>
|
||||||
|
<span class="prev-exc-code">(<?php echo $previousCodes[$i] ?>)</span>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="exc-message-empty-notice">No message</span>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<ul class="search-for-help">
|
||||||
|
<?php if (!empty($docref_url)): ?>
|
||||||
|
<li>
|
||||||
|
<a rel="noopener noreferrer" target="_blank" href="<?php echo $docref_url; ?>" title="Search for help in the PHP manual.">
|
||||||
|
<!-- PHP icon by Icons Solid -->
|
||||||
|
<!-- https://www.iconfinder.com/icons/322421/book_icon -->
|
||||||
|
<!-- Free for commercial use -->
|
||||||
|
<svg height="16px" id="Layer_1" style="enable-background:new 0 0 32 32;" version="1.1" viewBox="0 0 32 32" width="16px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g transform="translate(240 0)"><path d="M-211,4v26h-24c-1.104,0-2-0.895-2-2s0.896-2,2-2h22V0h-22c-2.209,0-4,1.791-4,4v24c0,2.209,1.791,4,4,4h26V4H-211z M-235,8V2h20v22h-20V8z M-219,6h-12V4h12V6z M-223,10h-8V8h8V10z M-227,14h-4v-2h4V14z"/></g></svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif ?>
|
||||||
|
<li>
|
||||||
|
<a rel="noopener noreferrer" target="_blank" href="https://google.com/search?q=<?php echo urlencode(implode('\\', $name).' '.$message) ?>" title="Search for help on Google.">
|
||||||
|
<!-- Google icon by Alfredo H, from https://www.iconfinder.com/alfredoh -->
|
||||||
|
<!-- Creative Commons (Attribution 3.0 Unported) -->
|
||||||
|
<!-- http://creativecommons.org/licenses/by/3.0/ -->
|
||||||
|
<svg class="google" height="16" viewBox="0 0 512 512" width="16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M457.732 216.625c2.628 14.04 4.063 28.743 4.063 44.098C461.795 380.688 381.48 466 260.205 466c-116.024 0-210-93.977-210-210s93.976-210 210-210c56.703 0 104.076 20.867 140.44 54.73l-59.205 59.197v-.135c-22.046-21.002-50-31.762-81.236-31.762-69.297 0-125.604 58.537-125.604 127.84 0 69.29 56.306 127.97 125.604 127.97 62.87 0 105.653-35.966 114.46-85.313h-114.46v-81.902h197.528z"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a rel="noopener noreferrer" target="_blank" href="https://duckduckgo.com/?q=<?php echo urlencode(implode('\\', $name).' '.$message) ?>" title="Search for help on DuckDuckGo.">
|
||||||
|
<!-- DuckDuckGo icon by IconBaandar Team, from https://www.iconfinder.com/iconbaandar -->
|
||||||
|
<!-- Creative Commons (Attribution 3.0 Unported) -->
|
||||||
|
<!-- http://creativecommons.org/licenses/by/3.0/ -->
|
||||||
|
<svg class="duckduckgo" height="16" viewBox="150 150 1675 1675" width="16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M1792 1024c0 204.364-80.472 398.56-224.955 543.04-144.483 144.48-338.68 224.95-543.044 224.95-204.36 0-398.56-80.47-543.04-224.95-144.48-144.482-224.95-338.676-224.95-543.04 0-204.365 80.47-398.562 224.96-543.045C625.44 336.47 819.64 256 1024 256c204.367 0 398.565 80.47 543.05 224.954C1711.532 625.437 1792 819.634 1792 1024zm-270.206 497.787C1654.256 1389.327 1728 1211.36 1728 1024c0-187.363-73.74-365.332-206.203-497.796C1389.332 393.74 1211.363 320 1024 320s-365.33 73.742-497.795 206.205C393.742 658.67 320 836.637 320 1024c0 187.36 73.744 365.326 206.206 497.787C658.67 1654.25 836.638 1727.99 1024 1727.99c187.362 0 365.33-73.74 497.794-206.203z"/>
|
||||||
|
<path d="M1438.64 1177.41c0-.03-.005-.017-.01.004l.01-.004z"/>
|
||||||
|
<path d="M1499.8 976.878c.03-.156-.024-.048-.11.107l.11-.107z"/>
|
||||||
|
<path d="M1105.19 991.642zm-68.013-376.128c-8.087-10.14-18.028-19.965-29.89-29.408-13.29-10.582-29-20.76-47.223-30.443-35.07-18.624-74.482-31.61-115.265-38.046-39.78-6.28-80.84-6.256-120.39.917l1.37 31.562c1.8.164 7.7 3.9 14.36 8.32-20.68 5.94-39.77 14.447-39.48 39.683l.2 17.48 17.3-1.73c29.38-2.95 60.17-2.06 90.32 2.61 9.21 1.42 18.36 3.2 27.38 5.32l-4.33 1.15c-20.45 5.58-38.93 12.52-54.25 20.61-46.28 24.32-75.51 60.85-90.14 108.37-14.14 45.95-14.27 101.81-2.72 166.51l.06.06c15.14 84.57 64.16 316.39 104.11 505.39 19.78 93.59 37.38 176.83 47.14 224.4 3.26 15.84 5.03 31.02 5.52 45.52.3 9.08.09 17.96-.58 26.62-.45 5.8-1.11 11.51-1.96 17.112l31.62 4.75c.71-4.705 1.3-9.494 1.76-14.373 48.964 10.517 99.78 16.05 151.88 16.05 60.68 0 119.61-7.505 175.91-21.64 3.04 6.08 6.08 12.19 9.11 18.32l28.62-14.128c-2.11-4.27-4.235-8.55-6.37-12.84-23.005-46.124-47.498-93.01-68.67-133.534-15.39-29.466-29.01-55.53-39.046-75.58-26.826-53.618-53.637-119.47-68.28-182.368-8.78-37.705-13.128-74.098-10.308-105.627-15.31-6.28-26.69-11.8-31.968-15.59l-.01.015c-14.22-10.2-31.11-28.12-41.82-49.717-8.618-17.376-13.4-37.246-10.147-57.84 3.17-19.84 27.334-46.714 57.843-67.46v-.063c26.554-18.05 58.75-32.506 86.32-34.31 7.835-.51 16.31-1.008 23.99-1.45 33.45-1.95 50.243-2.93 84.475-11.42 10.88-2.697 26.19-6.56 43.53-11.09 2.364-40.7-5.947-87.596-21.04-133.234-22.004-66.53-58.68-131.25-97.627-170.21-12.543-12.55-28.17-22.79-45.9-30.933-16.88-7.753-35.64-13.615-55.436-17.782zm-10.658 178.553s6.77-42.485 58.39-33.977c27.96 4.654 37.89 29.833 37.89 29.833s-25.31-14.46-44.95-14.198c-40.33.53-51.35 18.342-51.35 18.342zm-240.45-18.802c48.49-19.853 72.11 11.298 72.11 11.298s-35.21-15.928-69.46 5.59c-34.19 21.477-32.92 43.452-32.92 43.452s-18.17-40.5 30.26-60.34zm296.5 95.4c0-6.677 2.68-12.694 7.01-17.02 4.37-4.37 10.42-7.074 17.1-7.074 6.73 0 12.79 2.7 17.15 7.05 4.33 4.33 7.01 10.36 7.01 17.05 0 6.74-2.7 12.81-7.07 17.18-4.33 4.33-10.37 7.01-17.1 7.01-6.68 0-12.72-2.69-17.05-7.03-4.36-4.37-7.07-10.43-7.07-17.16zm-268.42 51.27c0-8.535 3.41-16.22 8.93-21.738 5.55-5.55 13.25-8.982 21.81-8.982 8.51 0 16.18 3.415 21.7 8.934 5.55 5.55 8.98 13.25 8.98 21.78 0 8.53-3.44 16.23-8.98 21.79-5.52 5.52-13.19 8.93-21.71 8.93-8.55 0-16.26-3.43-21.82-8.99-5.52-5.52-8.93-13.2-8.93-21.74z"/>
|
||||||
|
<path d="M1102.48 986.34zm390.074-64.347c-28.917-11.34-74.89-12.68-93.32-3.778-11.5 5.567-35.743 13.483-63.565 21.707-25.75 7.606-53.9 15.296-78.15 21.702-17.69 4.67-33.3 8.66-44.4 11.435-34.92 8.76-52.05 9.77-86.17 11.78-7.84.46-16.48.97-24.48 1.5-28.12 1.86-60.97 16.77-88.05 35.4v.06c-31.12 21.4-55.77 49.12-59.01 69.59-3.32 21.24 1.56 41.74 10.35 59.67 10.92 22.28 28.15 40.77 42.66 51.29l.01-.02c5.38 3.9 16.98 9.6 32.6 16.08 26.03 10.79 63.2 23.76 101.25 34.23 43.6 11.99 89.11 21.05 121.69 20.41 34.26-.69 77.73-10.52 114.54-24.67 22.15-8.52 42.21-18.71 56.88-29.58 17.85-13.22 28.7-28.42 28.4-44.74-.07-3.89-.72-7.63-1.97-11.21l-.02.01c-11.6-33.06-50.37-23.59-105.53-10.12-46.86 11.445-107.94 26.365-169.01 20.434-32.56-3.167-54.45-10.61-67.88-20.133-5.96-4.224-9.93-8.67-12.18-13.11-1.96-3.865-2.68-7.84-2.33-11.714.39-4.42 2.17-9.048 5.1-13.57l-.05-.03c7.86-12.118 23.082-9.72 43.93-6.43 25.91 4.08 58.2 9.172 99.013-3.61 39.63-12.378 87.76-29.9 131.184-47.39 42.405-17.08 80.08-34.078 100.74-46.18 25.46-14.87 37.57-29.428 40.59-42.866 2.725-12.152-.89-22.48-8.903-31.07-5.87-6.29-14.254-11.31-23.956-15.115z"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a rel="noopener noreferrer" target="_blank" href="https://stackoverflow.com/search?q=<?php echo urlencode(implode('\\', $name).' '.$message) ?>" title="Search for help on Stack Overflow.">
|
||||||
|
<!-- Stack Overflow icon by Picons.me, from https://www.iconfinder.com/Picons -->
|
||||||
|
<!-- Free for commercial use -->
|
||||||
|
<svg class="stackoverflow" height="16" viewBox="-1163 1657.697 56.693 56.693" width="16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M-1126.04 1689.533l-16.577-9.778 2.088-3.54 16.578 9.778zM-1127.386 1694.635l-18.586-4.996 1.068-3.97 18.586 4.995zM-1127.824 1700.137l-19.165-1.767.378-4.093 19.165 1.767zM-1147.263 1701.293h19.247v4.11h-19.247z"/>
|
||||||
|
<path d="M-1121.458 1710.947s0 .96-.032.96v.016h-30.796s-.96 0-.96-.016h-.032v-20.03h3.288v16.805h25.244v-16.804h3.288v19.07zM-1130.667 1667.04l10.844 15.903-3.396 2.316-10.843-15.903zM-1118.313 1663.044l3.29 18.963-4.05.703-3.29-18.963z"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<span id="plain-exception"><?php echo $tpl->escape($plain_exception) ?></span>
|
||||||
|
<button id="copy-button" class="rightButton clipboard" data-clipboard-text="<?php echo $tpl->escape($plain_exception) ?>" title="Copy exception details to clipboard">
|
||||||
|
COPY
|
||||||
|
</button>
|
||||||
|
<button id="hide-error" class="rightButton" title="Hide error message" onclick="document.getElementsByClassName('Whoops')[0].style.display = 'none';">
|
||||||
|
HIDE
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<header>
|
||||||
|
<?php $tpl->render($header) ?>
|
||||||
|
</header>
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Layout template file for Whoops's pretty error output.
|
||||||
|
*/
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html><?php echo $preface; ?>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="robots" content="noindex,nofollow"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
|
||||||
|
<title><?php echo $tpl->escape($page_title) ?></title>
|
||||||
|
|
||||||
|
<style><?php echo $stylesheet ?></style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="Whoops container">
|
||||||
|
<div class="stack-container">
|
||||||
|
|
||||||
|
<?php $tpl->render($panel_left_outer) ?>
|
||||||
|
|
||||||
|
<?php $tpl->render($panel_details_outer) ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script><?php echo $prettify ?></script>
|
||||||
|
<script><?php echo $zepto ?></script>
|
||||||
|
<script><?php echo $clipboard ?></script>
|
||||||
|
<script><?php echo $javascript ?></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,2 @@
|
||||||
|
<?php $tpl->render($frame_code) ?>
|
||||||
|
<?php $tpl->render($env_details) ?>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<div class="panel details-container cf">
|
||||||
|
<?php $tpl->render($panel_details) ?>
|
||||||
|
</div>
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?php
|
||||||
|
$tpl->render($header_outer);
|
||||||
|
$tpl->render($frames_description);
|
||||||
|
$tpl->render($frames_container);
|
|
@ -0,0 +1,3 @@
|
||||||
|
<div class="panel left-panel cf <?php echo (!$has_frames ? 'empty' : '') ?>">
|
||||||
|
<?php $tpl->render($panel_left) ?>
|
||||||
|
</div>
|
|
@ -0,0 +1,545 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Throwable;
|
||||||
|
use Whoops\Exception\ErrorException;
|
||||||
|
use Whoops\Exception\Inspector;
|
||||||
|
use Whoops\Handler\CallbackHandler;
|
||||||
|
use Whoops\Handler\Handler;
|
||||||
|
use Whoops\Handler\HandlerInterface;
|
||||||
|
use Whoops\Util\Misc;
|
||||||
|
use Whoops\Util\SystemFacade;
|
||||||
|
|
||||||
|
final class Run implements RunInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $isRegistered;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $allowQuit = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $sendOutput = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var integer|false
|
||||||
|
*/
|
||||||
|
private $sendHttpCode = 500;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var integer|false
|
||||||
|
*/
|
||||||
|
private $sendExitCode = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var HandlerInterface[]
|
||||||
|
*/
|
||||||
|
private $handlerStack = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
* @psalm-var list<array{patterns: string, levels: int}>
|
||||||
|
*/
|
||||||
|
private $silencedPatterns = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var SystemFacade
|
||||||
|
*/
|
||||||
|
private $system;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In certain scenarios, like in shutdown handler, we can not throw exceptions.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $canThrowExceptions = true;
|
||||||
|
|
||||||
|
public function __construct(SystemFacade $system = null)
|
||||||
|
{
|
||||||
|
$this->system = $system ?: new SystemFacade;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Explicitly request your handler runs as the last of all currently registered handlers.
|
||||||
|
*
|
||||||
|
* @param HandlerInterface $handler
|
||||||
|
*
|
||||||
|
* @return Run
|
||||||
|
*/
|
||||||
|
public function appendHandler($handler)
|
||||||
|
{
|
||||||
|
array_unshift($this->handlerStack, $this->resolveHandler($handler));
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Explicitly request your handler runs as the first of all currently registered handlers.
|
||||||
|
*
|
||||||
|
* @param HandlerInterface $handler
|
||||||
|
*
|
||||||
|
* @return Run
|
||||||
|
*/
|
||||||
|
public function prependHandler($handler)
|
||||||
|
{
|
||||||
|
return $this->pushHandler($handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register your handler as the last of all currently registered handlers (to be executed first).
|
||||||
|
* Prefer using appendHandler and prependHandler for clarity.
|
||||||
|
*
|
||||||
|
* @param Callable|HandlerInterface $handler
|
||||||
|
*
|
||||||
|
* @return Run
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If argument is not callable or instance of HandlerInterface.
|
||||||
|
*/
|
||||||
|
public function pushHandler($handler)
|
||||||
|
{
|
||||||
|
$this->handlerStack[] = $this->resolveHandler($handler);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes and returns the last handler pushed to the handler stack.
|
||||||
|
*
|
||||||
|
* @see Run::removeFirstHandler(), Run::removeLastHandler()
|
||||||
|
*
|
||||||
|
* @return HandlerInterface|null
|
||||||
|
*/
|
||||||
|
public function popHandler()
|
||||||
|
{
|
||||||
|
return array_pop($this->handlerStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the first handler.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function removeFirstHandler()
|
||||||
|
{
|
||||||
|
array_pop($this->handlerStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the last handler.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function removeLastHandler()
|
||||||
|
{
|
||||||
|
array_shift($this->handlerStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array with all handlers, in the order they were added to the stack.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getHandlers()
|
||||||
|
{
|
||||||
|
return $this->handlerStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all handlers in the handlerStack, including the default PrettyPage handler.
|
||||||
|
*
|
||||||
|
* @return Run
|
||||||
|
*/
|
||||||
|
public function clearHandlers()
|
||||||
|
{
|
||||||
|
$this->handlerStack = [];
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers this instance as an error handler.
|
||||||
|
*
|
||||||
|
* @return Run
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
if (!$this->isRegistered) {
|
||||||
|
// Workaround PHP bug 42098
|
||||||
|
// https://bugs.php.net/bug.php?id=42098
|
||||||
|
class_exists("\\Whoops\\Exception\\ErrorException");
|
||||||
|
class_exists("\\Whoops\\Exception\\FrameCollection");
|
||||||
|
class_exists("\\Whoops\\Exception\\Frame");
|
||||||
|
class_exists("\\Whoops\\Exception\\Inspector");
|
||||||
|
|
||||||
|
$this->system->setErrorHandler([$this, self::ERROR_HANDLER]);
|
||||||
|
$this->system->setExceptionHandler([$this, self::EXCEPTION_HANDLER]);
|
||||||
|
$this->system->registerShutdownFunction([$this, self::SHUTDOWN_HANDLER]);
|
||||||
|
|
||||||
|
$this->isRegistered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters all handlers registered by this Whoops\Run instance.
|
||||||
|
*
|
||||||
|
* @return Run
|
||||||
|
*/
|
||||||
|
public function unregister()
|
||||||
|
{
|
||||||
|
if ($this->isRegistered) {
|
||||||
|
$this->system->restoreExceptionHandler();
|
||||||
|
$this->system->restoreErrorHandler();
|
||||||
|
|
||||||
|
$this->isRegistered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should Whoops allow Handlers to force the script to quit?
|
||||||
|
*
|
||||||
|
* @param bool|int $exit
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function allowQuit($exit = null)
|
||||||
|
{
|
||||||
|
if (func_num_args() == 0) {
|
||||||
|
return $this->allowQuit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->allowQuit = (bool) $exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Silence particular errors in particular files.
|
||||||
|
*
|
||||||
|
* @param array|string $patterns List or a single regex pattern to match.
|
||||||
|
* @param int $levels Defaults to E_STRICT | E_DEPRECATED.
|
||||||
|
*
|
||||||
|
* @return Run
|
||||||
|
*/
|
||||||
|
public function silenceErrorsInPaths($patterns, $levels = 10240)
|
||||||
|
{
|
||||||
|
$this->silencedPatterns = array_merge(
|
||||||
|
$this->silencedPatterns,
|
||||||
|
array_map(
|
||||||
|
function ($pattern) use ($levels) {
|
||||||
|
return [
|
||||||
|
"pattern" => $pattern,
|
||||||
|
"levels" => $levels,
|
||||||
|
];
|
||||||
|
},
|
||||||
|
(array) $patterns
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array with silent errors in path configuration.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getSilenceErrorsInPaths()
|
||||||
|
{
|
||||||
|
return $this->silencedPatterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should Whoops send HTTP error code to the browser if possible?
|
||||||
|
* Whoops will by default send HTTP code 500, but you may wish to
|
||||||
|
* use 502, 503, or another 5xx family code.
|
||||||
|
*
|
||||||
|
* @param bool|int $code
|
||||||
|
*
|
||||||
|
* @return int|false
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function sendHttpCode($code = null)
|
||||||
|
{
|
||||||
|
if (func_num_args() == 0) {
|
||||||
|
return $this->sendHttpCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$code) {
|
||||||
|
return $this->sendHttpCode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($code === true) {
|
||||||
|
$code = 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($code < 400 || 600 <= $code) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
"Invalid status code '$code', must be 4xx or 5xx"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->sendHttpCode = $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should Whoops exit with a specific code on the CLI if possible?
|
||||||
|
* Whoops will exit with 1 by default, but you can specify something else.
|
||||||
|
*
|
||||||
|
* @param int $code
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function sendExitCode($code = null)
|
||||||
|
{
|
||||||
|
if (func_num_args() == 0) {
|
||||||
|
return $this->sendExitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($code < 0 || 255 <= $code) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
"Invalid status code '$code', must be between 0 and 254"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->sendExitCode = (int) $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should Whoops push output directly to the client?
|
||||||
|
* If this is false, output will be returned by handleException.
|
||||||
|
*
|
||||||
|
* @param bool|int $send
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function writeToOutput($send = null)
|
||||||
|
{
|
||||||
|
if (func_num_args() == 0) {
|
||||||
|
return $this->sendOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->sendOutput = (bool) $send;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles an exception, ultimately generating a Whoops error page.
|
||||||
|
*
|
||||||
|
* @param Throwable $exception
|
||||||
|
*
|
||||||
|
* @return string Output generated by handlers.
|
||||||
|
*/
|
||||||
|
public function handleException($exception)
|
||||||
|
{
|
||||||
|
// Walk the registered handlers in the reverse order
|
||||||
|
// they were registered, and pass off the exception
|
||||||
|
$inspector = $this->getInspector($exception);
|
||||||
|
|
||||||
|
// Capture output produced while handling the exception,
|
||||||
|
// we might want to send it straight away to the client,
|
||||||
|
// or return it silently.
|
||||||
|
$this->system->startOutputBuffering();
|
||||||
|
|
||||||
|
// Just in case there are no handlers:
|
||||||
|
$handlerResponse = null;
|
||||||
|
$handlerContentType = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
foreach (array_reverse($this->handlerStack) as $handler) {
|
||||||
|
$handler->setRun($this);
|
||||||
|
$handler->setInspector($inspector);
|
||||||
|
$handler->setException($exception);
|
||||||
|
|
||||||
|
// The HandlerInterface does not require an Exception passed to handle()
|
||||||
|
// and neither of our bundled handlers use it.
|
||||||
|
// However, 3rd party handlers may have already relied on this parameter,
|
||||||
|
// and removing it would be possibly breaking for users.
|
||||||
|
$handlerResponse = $handler->handle($exception);
|
||||||
|
|
||||||
|
// Collect the content type for possible sending in the headers.
|
||||||
|
$handlerContentType = method_exists($handler, 'contentType') ? $handler->contentType() : null;
|
||||||
|
|
||||||
|
if (in_array($handlerResponse, [Handler::LAST_HANDLER, Handler::QUIT])) {
|
||||||
|
// The Handler has handled the exception in some way, and
|
||||||
|
// wishes to quit execution (Handler::QUIT), or skip any
|
||||||
|
// other handlers (Handler::LAST_HANDLER). If $this->allowQuit
|
||||||
|
// is false, Handler::QUIT behaves like Handler::LAST_HANDLER
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$willQuit = $handlerResponse == Handler::QUIT && $this->allowQuit();
|
||||||
|
} finally {
|
||||||
|
$output = $this->system->cleanOutputBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're allowed to, send output generated by handlers directly
|
||||||
|
// to the output, otherwise, and if the script doesn't quit, return
|
||||||
|
// it so that it may be used by the caller
|
||||||
|
if ($this->writeToOutput()) {
|
||||||
|
// @todo Might be able to clean this up a bit better
|
||||||
|
if ($willQuit) {
|
||||||
|
// Cleanup all other output buffers before sending our output:
|
||||||
|
while ($this->system->getOutputBufferLevel() > 0) {
|
||||||
|
$this->system->endOutputBuffering();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send any headers if needed:
|
||||||
|
if (Misc::canSendHeaders() && $handlerContentType) {
|
||||||
|
header("Content-Type: {$handlerContentType}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->writeToOutputNow($output);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($willQuit) {
|
||||||
|
// HHVM fix for https://github.com/facebook/hhvm/issues/4055
|
||||||
|
$this->system->flushOutputBuffer();
|
||||||
|
|
||||||
|
$this->system->stopExecution(
|
||||||
|
$this->sendExitCode()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts generic PHP errors to \ErrorException instances, before passing them off to be handled.
|
||||||
|
*
|
||||||
|
* This method MUST be compatible with set_error_handler.
|
||||||
|
*
|
||||||
|
* @param int $level
|
||||||
|
* @param string $message
|
||||||
|
* @param string|null $file
|
||||||
|
* @param int|null $line
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @throws ErrorException
|
||||||
|
*/
|
||||||
|
public function handleError($level, $message, $file = null, $line = null)
|
||||||
|
{
|
||||||
|
if ($level & $this->system->getErrorReportingLevel()) {
|
||||||
|
foreach ($this->silencedPatterns as $entry) {
|
||||||
|
$pathMatches = (bool) preg_match($entry["pattern"], $file);
|
||||||
|
$levelMatches = $level & $entry["levels"];
|
||||||
|
if ($pathMatches && $levelMatches) {
|
||||||
|
// Ignore the error, abort handling
|
||||||
|
// See https://github.com/filp/whoops/issues/418
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX we pass $level for the "code" param only for BC reasons.
|
||||||
|
// see https://github.com/filp/whoops/issues/267
|
||||||
|
$exception = new ErrorException($message, /*code*/ $level, /*severity*/ $level, $file, $line);
|
||||||
|
if ($this->canThrowExceptions) {
|
||||||
|
throw $exception;
|
||||||
|
} else {
|
||||||
|
$this->handleException($exception);
|
||||||
|
}
|
||||||
|
// Do not propagate errors which were already handled by Whoops.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Propagate error to the next handler, allows error_get_last() to
|
||||||
|
// work on silenced errors.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special case to deal with Fatal errors and the like.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handleShutdown()
|
||||||
|
{
|
||||||
|
// If we reached this step, we are in shutdown handler.
|
||||||
|
// An exception thrown in a shutdown handler will not be propagated
|
||||||
|
// to the exception handler. Pass that information along.
|
||||||
|
$this->canThrowExceptions = false;
|
||||||
|
|
||||||
|
$error = $this->system->getLastError();
|
||||||
|
if ($error && Misc::isLevelFatal($error['type'])) {
|
||||||
|
// If there was a fatal error,
|
||||||
|
// it was not handled in handleError yet.
|
||||||
|
$this->allowQuit = false;
|
||||||
|
$this->handleError(
|
||||||
|
$error['type'],
|
||||||
|
$error['message'],
|
||||||
|
$error['file'],
|
||||||
|
$error['line']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Throwable $exception
|
||||||
|
*
|
||||||
|
* @return Inspector
|
||||||
|
*/
|
||||||
|
private function getInspector($exception)
|
||||||
|
{
|
||||||
|
return new Inspector($exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the giving handler.
|
||||||
|
*
|
||||||
|
* @param HandlerInterface $handler
|
||||||
|
*
|
||||||
|
* @return HandlerInterface
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
*/
|
||||||
|
private function resolveHandler($handler)
|
||||||
|
{
|
||||||
|
if (is_callable($handler)) {
|
||||||
|
$handler = new CallbackHandler($handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$handler instanceof HandlerInterface) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
"Handler must be a callable, or instance of "
|
||||||
|
. "Whoops\\Handler\\HandlerInterface"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Echo something to the browser.
|
||||||
|
*
|
||||||
|
* @param string $output
|
||||||
|
*
|
||||||
|
* @return Run
|
||||||
|
*/
|
||||||
|
private function writeToOutputNow($output)
|
||||||
|
{
|
||||||
|
if ($this->sendHttpCode() && Misc::canSendHeaders()) {
|
||||||
|
$this->system->setHttpResponseCode(
|
||||||
|
$this->sendHttpCode()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $output;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Whoops\Exception\ErrorException;
|
||||||
|
use Whoops\Handler\HandlerInterface;
|
||||||
|
|
||||||
|
interface RunInterface
|
||||||
|
{
|
||||||
|
const EXCEPTION_HANDLER = "handleException";
|
||||||
|
const ERROR_HANDLER = "handleError";
|
||||||
|
const SHUTDOWN_HANDLER = "handleShutdown";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes a handler to the end of the stack
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If argument is not callable or instance of HandlerInterface
|
||||||
|
* @param Callable|HandlerInterface $handler
|
||||||
|
* @return Run
|
||||||
|
*/
|
||||||
|
public function pushHandler($handler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the last handler in the stack and returns it.
|
||||||
|
* Returns null if there"s nothing else to pop.
|
||||||
|
*
|
||||||
|
* @return null|HandlerInterface
|
||||||
|
*/
|
||||||
|
public function popHandler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array with all handlers, in the
|
||||||
|
* order they were added to the stack.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getHandlers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all handlers in the handlerStack, including
|
||||||
|
* the default PrettyPage handler.
|
||||||
|
*
|
||||||
|
* @return Run
|
||||||
|
*/
|
||||||
|
public function clearHandlers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers this instance as an error handler.
|
||||||
|
*
|
||||||
|
* @return Run
|
||||||
|
*/
|
||||||
|
public function register();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters all handlers registered by this Whoops\Run instance
|
||||||
|
*
|
||||||
|
* @return Run
|
||||||
|
*/
|
||||||
|
public function unregister();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should Whoops allow Handlers to force the script to quit?
|
||||||
|
*
|
||||||
|
* @param bool|int $exit
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function allowQuit($exit = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Silence particular errors in particular files
|
||||||
|
*
|
||||||
|
* @param array|string $patterns List or a single regex pattern to match
|
||||||
|
* @param int $levels Defaults to E_STRICT | E_DEPRECATED
|
||||||
|
* @return \Whoops\Run
|
||||||
|
*/
|
||||||
|
public function silenceErrorsInPaths($patterns, $levels = 10240);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should Whoops send HTTP error code to the browser if possible?
|
||||||
|
* Whoops will by default send HTTP code 500, but you may wish to
|
||||||
|
* use 502, 503, or another 5xx family code.
|
||||||
|
*
|
||||||
|
* @param bool|int $code
|
||||||
|
* @return int|false
|
||||||
|
*/
|
||||||
|
public function sendHttpCode($code = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should Whoops exit with a specific code on the CLI if possible?
|
||||||
|
* Whoops will exit with 1 by default, but you can specify something else.
|
||||||
|
*
|
||||||
|
* @param int $code
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function sendExitCode($code = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should Whoops push output directly to the client?
|
||||||
|
* If this is false, output will be returned by handleException
|
||||||
|
*
|
||||||
|
* @param bool|int $send
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function writeToOutput($send = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles an exception, ultimately generating a Whoops error
|
||||||
|
* page.
|
||||||
|
*
|
||||||
|
* @param \Throwable $exception
|
||||||
|
* @return string Output generated by handlers
|
||||||
|
*/
|
||||||
|
public function handleException($exception);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts generic PHP errors to \ErrorException
|
||||||
|
* instances, before passing them off to be handled.
|
||||||
|
*
|
||||||
|
* This method MUST be compatible with set_error_handler.
|
||||||
|
*
|
||||||
|
* @param int $level
|
||||||
|
* @param string $message
|
||||||
|
* @param string $file
|
||||||
|
* @param int $line
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* @throws ErrorException
|
||||||
|
*/
|
||||||
|
public function handleError($level, $message, $file = null, $line = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special case to deal with Fatal errors and the like.
|
||||||
|
*/
|
||||||
|
public function handleShutdown();
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops\Util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used as output callable for Symfony\Component\VarDumper\Dumper\HtmlDumper::dump()
|
||||||
|
*
|
||||||
|
* @see TemplateHelper::dump()
|
||||||
|
*/
|
||||||
|
class HtmlDumperOutput
|
||||||
|
{
|
||||||
|
private $output;
|
||||||
|
|
||||||
|
public function __invoke($line, $depth)
|
||||||
|
{
|
||||||
|
// A negative depth means "end of dump"
|
||||||
|
if ($depth >= 0) {
|
||||||
|
// Adds a two spaces indentation to the line
|
||||||
|
$this->output .= str_repeat(' ', $depth) . $line . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOutput()
|
||||||
|
{
|
||||||
|
return $this->output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clear()
|
||||||
|
{
|
||||||
|
$this->output = null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops\Util;
|
||||||
|
|
||||||
|
class Misc
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Can we at this point in time send HTTP headers?
|
||||||
|
*
|
||||||
|
* Currently this checks if we are even serving an HTTP request,
|
||||||
|
* as opposed to running from a command line.
|
||||||
|
*
|
||||||
|
* If we are serving an HTTP request, we check if it's not too late.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function canSendHeaders()
|
||||||
|
{
|
||||||
|
return isset($_SERVER["REQUEST_URI"]) && !headers_sent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isAjaxRequest()
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
|
||||||
|
&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check, if possible, that this execution was triggered by a command line.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isCommandLine()
|
||||||
|
{
|
||||||
|
return PHP_SAPI == 'cli';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate ErrorException code into the represented constant.
|
||||||
|
*
|
||||||
|
* @param int $error_code
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function translateErrorCode($error_code)
|
||||||
|
{
|
||||||
|
$constants = get_defined_constants(true);
|
||||||
|
if (array_key_exists('Core', $constants)) {
|
||||||
|
foreach ($constants['Core'] as $constant => $value) {
|
||||||
|
if (substr($constant, 0, 2) == 'E_' && $value == $error_code) {
|
||||||
|
return $constant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "E_UNKNOWN";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if an error level is fatal (halts execution)
|
||||||
|
*
|
||||||
|
* @param int $level
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isLevelFatal($level)
|
||||||
|
{
|
||||||
|
$errors = E_ERROR;
|
||||||
|
$errors |= E_PARSE;
|
||||||
|
$errors |= E_CORE_ERROR;
|
||||||
|
$errors |= E_CORE_WARNING;
|
||||||
|
$errors |= E_COMPILE_ERROR;
|
||||||
|
$errors |= E_COMPILE_WARNING;
|
||||||
|
return ($level & $errors) > 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops\Util;
|
||||||
|
|
||||||
|
class SystemFacade
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Turns on output buffering.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function startOutputBuffering()
|
||||||
|
{
|
||||||
|
return ob_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable $handler
|
||||||
|
* @param int $types
|
||||||
|
*
|
||||||
|
* @return callable|null
|
||||||
|
*/
|
||||||
|
public function setErrorHandler(callable $handler, $types = 'use-php-defaults')
|
||||||
|
{
|
||||||
|
// Since PHP 5.4 the constant E_ALL contains all errors (even E_STRICT)
|
||||||
|
if ($types === 'use-php-defaults') {
|
||||||
|
$types = E_ALL;
|
||||||
|
}
|
||||||
|
return set_error_handler($handler, $types);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable $handler
|
||||||
|
*
|
||||||
|
* @return callable|null
|
||||||
|
*/
|
||||||
|
public function setExceptionHandler(callable $handler)
|
||||||
|
{
|
||||||
|
return set_exception_handler($handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function restoreExceptionHandler()
|
||||||
|
{
|
||||||
|
restore_exception_handler();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function restoreErrorHandler()
|
||||||
|
{
|
||||||
|
restore_error_handler();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable $function
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function registerShutdownFunction(callable $function)
|
||||||
|
{
|
||||||
|
register_shutdown_function($function);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string|false
|
||||||
|
*/
|
||||||
|
public function cleanOutputBuffer()
|
||||||
|
{
|
||||||
|
return ob_get_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getOutputBufferLevel()
|
||||||
|
{
|
||||||
|
return ob_get_level();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function endOutputBuffering()
|
||||||
|
{
|
||||||
|
return ob_end_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function flushOutputBuffer()
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getErrorReportingLevel()
|
||||||
|
{
|
||||||
|
return error_reporting();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function getLastError()
|
||||||
|
{
|
||||||
|
return error_get_last();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $httpCode
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function setHttpResponseCode($httpCode)
|
||||||
|
{
|
||||||
|
if (!headers_sent()) {
|
||||||
|
// Ensure that no 'location' header is present as otherwise this
|
||||||
|
// will override the HTTP code being set here, and mask the
|
||||||
|
// expected error page.
|
||||||
|
header_remove('location');
|
||||||
|
}
|
||||||
|
|
||||||
|
return http_response_code($httpCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $exitStatus
|
||||||
|
*/
|
||||||
|
public function stopExecution($exitStatus)
|
||||||
|
{
|
||||||
|
exit($exitStatus);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,352 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Whoops - php errors for cool kids
|
||||||
|
* @author Filipe Dobreira <http://github.com/filp>
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Whoops\Util;
|
||||||
|
|
||||||
|
use Symfony\Component\VarDumper\Caster\Caster;
|
||||||
|
use Symfony\Component\VarDumper\Cloner\AbstractCloner;
|
||||||
|
use Symfony\Component\VarDumper\Cloner\VarCloner;
|
||||||
|
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
|
||||||
|
use Whoops\Exception\Frame;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposes useful tools for working with/in templates
|
||||||
|
*/
|
||||||
|
class TemplateHelper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* An array of variables to be passed to all templates
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $variables = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var HtmlDumper
|
||||||
|
*/
|
||||||
|
private $htmlDumper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var HtmlDumperOutput
|
||||||
|
*/
|
||||||
|
private $htmlDumperOutput;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var AbstractCloner
|
||||||
|
*/
|
||||||
|
private $cloner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $applicationRootPath;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
// root path for ordinary composer projects
|
||||||
|
$this->applicationRootPath = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__))))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes a string for output in an HTML document
|
||||||
|
*
|
||||||
|
* @param string $raw
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function escape($raw)
|
||||||
|
{
|
||||||
|
$flags = ENT_QUOTES;
|
||||||
|
|
||||||
|
// HHVM has all constants defined, but only ENT_IGNORE
|
||||||
|
// works at the moment
|
||||||
|
if (defined("ENT_SUBSTITUTE") && !defined("HHVM_VERSION")) {
|
||||||
|
$flags |= ENT_SUBSTITUTE;
|
||||||
|
} else {
|
||||||
|
// This is for 5.3.
|
||||||
|
// The documentation warns of a potential security issue,
|
||||||
|
// but it seems it does not apply in our case, because
|
||||||
|
// we do not blacklist anything anywhere.
|
||||||
|
$flags |= ENT_IGNORE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$raw = str_replace(chr(9), ' ', $raw);
|
||||||
|
|
||||||
|
return htmlspecialchars($raw, $flags, "UTF-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes a string for output in an HTML document, but preserves
|
||||||
|
* URIs within it, and converts them to clickable anchor elements.
|
||||||
|
*
|
||||||
|
* @param string $raw
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function escapeButPreserveUris($raw)
|
||||||
|
{
|
||||||
|
$escaped = $this->escape($raw);
|
||||||
|
return preg_replace(
|
||||||
|
"@([A-z]+?://([-\w\.]+[-\w])+(:\d+)?(/([\w/_\.#-]*(\?\S+)?[^\.\s])?)?)@",
|
||||||
|
"<a href=\"$1\" target=\"_blank\" rel=\"noreferrer noopener\">$1</a>",
|
||||||
|
$escaped
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure that the given string breaks on the delimiter.
|
||||||
|
*
|
||||||
|
* @param string $delimiter
|
||||||
|
* @param string $s
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function breakOnDelimiter($delimiter, $s)
|
||||||
|
{
|
||||||
|
$parts = explode($delimiter, $s);
|
||||||
|
foreach ($parts as &$part) {
|
||||||
|
$part = '<span class="delimiter">' . $part . '</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode($delimiter, $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the part of the path that all files have in common.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function shorten($path)
|
||||||
|
{
|
||||||
|
if ($this->applicationRootPath != "/") {
|
||||||
|
$path = str_replace($this->applicationRootPath, '…', $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getDumper()
|
||||||
|
{
|
||||||
|
if (!$this->htmlDumper && class_exists('Symfony\Component\VarDumper\Cloner\VarCloner')) {
|
||||||
|
$this->htmlDumperOutput = new HtmlDumperOutput();
|
||||||
|
// re-use the same var-dumper instance, so it won't re-render the global styles/scripts on each dump.
|
||||||
|
$this->htmlDumper = new HtmlDumper($this->htmlDumperOutput);
|
||||||
|
|
||||||
|
$styles = [
|
||||||
|
'default' => 'color:#FFFFFF; line-height:normal; font:12px "Inconsolata", "Fira Mono", "Source Code Pro", Monaco, Consolas, "Lucida Console", monospace !important; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: normal',
|
||||||
|
'num' => 'color:#BCD42A',
|
||||||
|
'const' => 'color: #4bb1b1;',
|
||||||
|
'str' => 'color:#BCD42A',
|
||||||
|
'note' => 'color:#ef7c61',
|
||||||
|
'ref' => 'color:#A0A0A0',
|
||||||
|
'public' => 'color:#FFFFFF',
|
||||||
|
'protected' => 'color:#FFFFFF',
|
||||||
|
'private' => 'color:#FFFFFF',
|
||||||
|
'meta' => 'color:#FFFFFF',
|
||||||
|
'key' => 'color:#BCD42A',
|
||||||
|
'index' => 'color:#ef7c61',
|
||||||
|
];
|
||||||
|
$this->htmlDumper->setStyles($styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->htmlDumper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the given value into a human readable string.
|
||||||
|
*
|
||||||
|
* @param mixed $value
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function dump($value)
|
||||||
|
{
|
||||||
|
$dumper = $this->getDumper();
|
||||||
|
|
||||||
|
if ($dumper) {
|
||||||
|
// re-use the same DumpOutput instance, so it won't re-render the global styles/scripts on each dump.
|
||||||
|
// exclude verbose information (e.g. exception stack traces)
|
||||||
|
if (class_exists('Symfony\Component\VarDumper\Caster\Caster')) {
|
||||||
|
$cloneVar = $this->getCloner()->cloneVar($value, Caster::EXCLUDE_VERBOSE);
|
||||||
|
// Symfony VarDumper 2.6 Caster class dont exist.
|
||||||
|
} else {
|
||||||
|
$cloneVar = $this->getCloner()->cloneVar($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$dumper->dump(
|
||||||
|
$cloneVar,
|
||||||
|
$this->htmlDumperOutput
|
||||||
|
);
|
||||||
|
|
||||||
|
$output = $this->htmlDumperOutput->getOutput();
|
||||||
|
$this->htmlDumperOutput->clear();
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
return htmlspecialchars(print_r($value, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the args of the given Frame as a human readable html string
|
||||||
|
*
|
||||||
|
* @param Frame $frame
|
||||||
|
* @return string the rendered html
|
||||||
|
*/
|
||||||
|
public function dumpArgs(Frame $frame)
|
||||||
|
{
|
||||||
|
// we support frame args only when the optional dumper is available
|
||||||
|
if (!$this->getDumper()) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$html = '';
|
||||||
|
$numFrames = count($frame->getArgs());
|
||||||
|
|
||||||
|
if ($numFrames > 0) {
|
||||||
|
$html = '<ol class="linenums">';
|
||||||
|
foreach ($frame->getArgs() as $j => $frameArg) {
|
||||||
|
$html .= '<li>'. $this->dump($frameArg) .'</li>';
|
||||||
|
}
|
||||||
|
$html .= '</ol>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a string to a slug version of itself
|
||||||
|
*
|
||||||
|
* @param string $original
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function slug($original)
|
||||||
|
{
|
||||||
|
$slug = str_replace(" ", "-", $original);
|
||||||
|
$slug = preg_replace('/[^\w\d\-\_]/i', '', $slug);
|
||||||
|
return strtolower($slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a template path, render it within its own scope. This
|
||||||
|
* method also accepts an array of additional variables to be
|
||||||
|
* passed to the template.
|
||||||
|
*
|
||||||
|
* @param string $template
|
||||||
|
* @param array $additionalVariables
|
||||||
|
*/
|
||||||
|
public function render($template, array $additionalVariables = null)
|
||||||
|
{
|
||||||
|
$variables = $this->getVariables();
|
||||||
|
|
||||||
|
// Pass the helper to the template:
|
||||||
|
$variables["tpl"] = $this;
|
||||||
|
|
||||||
|
if ($additionalVariables !== null) {
|
||||||
|
$variables = array_replace($variables, $additionalVariables);
|
||||||
|
}
|
||||||
|
|
||||||
|
call_user_func(function () {
|
||||||
|
extract(func_get_arg(1));
|
||||||
|
require func_get_arg(0);
|
||||||
|
}, $template, $variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the variables to be passed to all templates rendered
|
||||||
|
* by this template helper.
|
||||||
|
*
|
||||||
|
* @param array $variables
|
||||||
|
*/
|
||||||
|
public function setVariables(array $variables)
|
||||||
|
{
|
||||||
|
$this->variables = $variables;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a single template variable, by its name:
|
||||||
|
*
|
||||||
|
* @param string $variableName
|
||||||
|
* @param mixed $variableValue
|
||||||
|
*/
|
||||||
|
public function setVariable($variableName, $variableValue)
|
||||||
|
{
|
||||||
|
$this->variables[$variableName] = $variableValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a single template variable, by its name, or
|
||||||
|
* $defaultValue if the variable does not exist
|
||||||
|
*
|
||||||
|
* @param string $variableName
|
||||||
|
* @param mixed $defaultValue
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getVariable($variableName, $defaultValue = null)
|
||||||
|
{
|
||||||
|
return isset($this->variables[$variableName]) ?
|
||||||
|
$this->variables[$variableName] : $defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsets a single template variable, by its name
|
||||||
|
*
|
||||||
|
* @param string $variableName
|
||||||
|
*/
|
||||||
|
public function delVariable($variableName)
|
||||||
|
{
|
||||||
|
unset($this->variables[$variableName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all variables for this helper
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getVariables()
|
||||||
|
{
|
||||||
|
return $this->variables;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the cloner used for dumping variables.
|
||||||
|
*
|
||||||
|
* @param AbstractCloner $cloner
|
||||||
|
*/
|
||||||
|
public function setCloner($cloner)
|
||||||
|
{
|
||||||
|
$this->cloner = $cloner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cloner used for dumping variables.
|
||||||
|
*
|
||||||
|
* @return AbstractCloner
|
||||||
|
*/
|
||||||
|
public function getCloner()
|
||||||
|
{
|
||||||
|
if (!$this->cloner) {
|
||||||
|
$this->cloner = new VarCloner();
|
||||||
|
}
|
||||||
|
return $this->cloner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the application root path.
|
||||||
|
*
|
||||||
|
* @param string $applicationRootPath
|
||||||
|
*/
|
||||||
|
public function setApplicationRootPath($applicationRootPath)
|
||||||
|
{
|
||||||
|
$this->applicationRootPath = $applicationRootPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the application root path.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getApplicationRootPath()
|
||||||
|
{
|
||||||
|
return $this->applicationRootPath;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2012 PHP Framework Interoperability Group
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -0,0 +1,128 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Psr\Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a simple Logger implementation that other Loggers can inherit from.
|
||||||
|
*
|
||||||
|
* It simply delegates all log-level-specific methods to the `log` method to
|
||||||
|
* reduce boilerplate code that a simple Logger that does the same thing with
|
||||||
|
* messages regardless of the error level has to implement.
|
||||||
|
*/
|
||||||
|
abstract class AbstractLogger implements LoggerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* System is unusable.
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
* @param mixed[] $context
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function emergency($message, array $context = array())
|
||||||
|
{
|
||||||
|
$this->log(LogLevel::EMERGENCY, $message, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action must be taken immediately.
|
||||||
|
*
|
||||||
|
* Example: Entire website down, database unavailable, etc. This should
|
||||||
|
* trigger the SMS alerts and wake you up.
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
* @param mixed[] $context
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function alert($message, array $context = array())
|
||||||
|
{
|
||||||
|
$this->log(LogLevel::ALERT, $message, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Critical conditions.
|
||||||
|
*
|
||||||
|
* Example: Application component unavailable, unexpected exception.
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
* @param mixed[] $context
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function critical($message, array $context = array())
|
||||||
|
{
|
||||||
|
$this->log(LogLevel::CRITICAL, $message, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runtime errors that do not require immediate action but should typically
|
||||||
|
* be logged and monitored.
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
* @param mixed[] $context
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function error($message, array $context = array())
|
||||||
|
{
|
||||||
|
$this->log(LogLevel::ERROR, $message, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exceptional occurrences that are not errors.
|
||||||
|
*
|
||||||
|
* Example: Use of deprecated APIs, poor use of an API, undesirable things
|
||||||
|
* that are not necessarily wrong.
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
* @param mixed[] $context
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function warning($message, array $context = array())
|
||||||
|
{
|
||||||
|
$this->log(LogLevel::WARNING, $message, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normal but significant events.
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
* @param mixed[] $context
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function notice($message, array $context = array())
|
||||||
|
{
|
||||||
|
$this->log(LogLevel::NOTICE, $message, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interesting events.
|
||||||
|
*
|
||||||
|
* Example: User logs in, SQL logs.
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
* @param mixed[] $context
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function info($message, array $context = array())
|
||||||
|
{
|
||||||
|
$this->log(LogLevel::INFO, $message, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detailed debug information.
|
||||||
|
*
|
||||||
|
* @param string $message
|
||||||
|
* @param mixed[] $context
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function debug($message, array $context = array())
|
||||||
|
{
|
||||||
|
$this->log(LogLevel::DEBUG, $message, $context);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Psr\Log;
|
||||||
|
|
||||||
|
class InvalidArgumentException extends \InvalidArgumentException
|
||||||
|
{
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue