框架更新

This commit is contained in:
技术老胡
2024-10-19 09:46:48 +08:00
parent eb649b402f
commit 502f2f4dc1
496 changed files with 83120 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
name: Bug 反馈
description: 反馈您所遇到的问题,以帮助我们更好的改进
labels: ["bug"]
body:
- type: dropdown
id: addon
attributes:
label: 所属功能组件
options:
- 路由(Route)
- 控制器(Controller)
- 中间件(Middleware)
- 容器(Container)
- 服务(Service)
- 门面(Facade)
- 事件(Event)
- 请求(Request)
- 缓存(Cache)
- 响应(Response)
- 视图(View)
- 异常(Exception)
- 日志(Log)
- 验证器(Validate)
- 多语言(Lang)
- Session/Cookie
- 文件系统(Filesystem)
- 命令行(Command)
- 其它
description: |
* 模型(Model)和数据库(Db)功能请前往 https://github.com/top-think/think-orm/issues 反馈
* 多应用(MultiApp)功能请前往 https://github.com/top-think/think-multi-app/issues 反馈
validations:
required: true
- type: input
id: version
attributes:
label: ThinkPHP 版本
description: 您所使用的 ThinkPHP 版本是?
placeholder: 8.0.3
validations:
required: true
- type: input
id: system
attributes:
label: 操作系统
description: 您代码在什么系统上运行?
placeholder: Windows、Centos、Ubuntu、Debian
validations:
required: true
- type: textarea
attributes:
label: 错误信息
description: 如果有报错信息,请附上相关错误信息或截图。如:错误提示语、出错文件路径、出错行号和 Traces 等信息
placeholder: |
信息:控制器不存在
路径vendor/topthink/framework/src/think/App.php
行号313
Traces
......
validations:
required: false
- type: textarea
attributes:
label: 其它说明
description: 如您还有其它需要补充,可在此输入。
validations:
required: false

View File

@@ -0,0 +1,20 @@
---
name: 功能请求
about: 您对 ThinkPHP 有什么改进的想法或建议,可在此提出。
title: ''
labels: 'enhancement'
assignees: ''
---
**您的功能请求与问题相关吗?**
[是的话请在此描述]
**描述您想要的解决方案**
[有的话请在此描述]
**描述您考虑过的替代方案**
[有的话请在此描述]
**其它信息**
[其它相关信息或截图]

View File

@@ -0,0 +1,10 @@
---
name: 其它问题
about: 其它问题可在此反馈
title: ''
labels: 'help wanted'
assignees: ''
---

View File

@@ -0,0 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: ThinkPHP 完全开发手册
url: https://doc.thinkphp.cn/
about: 在创建 Issue 之前,请仔细查阅开发手册,以便对其有更深入的了解,和更好地分析问题。
- name: ThinkPHP 轻社区
url: https://q.thinkphp.cn/
about: 欢迎来到ThinkPHP轻社区和大家一起交流。

View File

@@ -0,0 +1,48 @@
name: build
on:
push:
branches:
- "8.0"
pull_request:
schedule:
- cron: "0 0 * * *"
jobs:
tests:
runs-on: ubuntu-22.04
strategy:
fail-fast: true
matrix:
php: [8.0, 8.1, 8.2]
name: PHP ${{ matrix.php }}
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 10
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: xdebug
- name: Install dependencies
uses: nick-fields/retry@v2
with:
timeout_minutes: 5
max_attempts: 5
command: composer update --prefer-dist --no-interaction --prefer-stable --no-suggest
- name: Execute tests
run: vendor/bin/phpunit --coverage-clover build/logs/clover.xml
- name: Upload Scrutinizer coverage
uses: sudo-bot/action-scrutinizer@latest
with:
cli-args: "--format=php-clover build/logs/clover.xml --revision=${{ github.event.pull_request.head.sha || github.sha }}"

View File

@@ -0,0 +1,119 @@
如何贡献我的源代码
===
此文档介绍了 ThinkPHP 团队的组成以及运转机制,您提交的代码将给 ThinkPHP 项目带来什么好处,以及如何才能加入我们的行列。
## 通过 Github 贡献代码
ThinkPHP 目前使用 Git 来控制程序版本,如果你想为 ThinkPHP 贡献源代码,请先大致了解 Git 的使用方法。我们目前把项目托管在 GitHub 上,任何 GitHub 用户都可以向我们贡献代码。
参与的方式很简单,`fork`一份 ThinkPHP 的代码到你的仓库中,修改后提交,并向我们发起`pull request`申请,我们会及时对代码进行审查并处理你的申请并。审查通过后,你的代码将被`merge`进我们的仓库中,这样你就会自动出现在贡献者名单里了,非常方便。
我们希望你贡献的代码符合:
* ThinkPHP 的编码规范
* 适当的注释,能让其他人读懂
* 遵循 Apache2 开源协议
**如果想要了解更多细节或有任何疑问,请继续阅读下面的内容**
### 注意事项
* 本项目代码格式化标准选用 [**PSR-2**](http://www.kancloud.cn/thinkphp/php-fig-psr/3141)
* 类名和类文件名遵循 [**PSR-4**](http://www.kancloud.cn/thinkphp/php-fig-psr/3144)
* 对于 Issues 的处理,请使用诸如 `fix #xxx(Issue ID)` 的 commit title 直接关闭 issue。
* 系统会自动在 PHP 7.1 ~ 7.3 上测试修改,请确保你的修改符合 PHP 7.1 ~ 7.3 的语法规范;
* 管理员不会合并造成 CI faild 的修改,若出现 CI faild 请检查自己的源代码或修改相应的[单元测试文件](tests)
## GitHub Issue
GitHub 提供了 Issue 功能,该功能可以用于:
* 提出 bug
* 提出功能改进
* 反馈使用体验
该功能不应该用于:
* 提出修改意见(涉及代码署名和修订追溯问题)
* 不友善的言论
## 快速修改
**GitHub 提供了快速编辑文件的功能**
1. 登录 GitHub 帐号;
2. 浏览项目文件,找到要进行修改的文件;
3. 点击右上角铅笔图标进行修改;
4. 填写 `Commit changes` 相关内容Title 必填);
5. 提交修改,等待 CI 验证和管理员合并。
**若您需要一次提交大量修改,请继续阅读下面的内容**
## 完整流程
1. `fork`本项目;
2. 克隆(`clone`)你 `fork` 的项目到本地;
3. 新建分支(`branch`)并检出(`checkout`)新分支;
4. 添加本项目到你的本地 git 仓库作为上游(`upstream`)
5. 进行修改,若你的修改包含方法或函数的增减,请记得修改[单元测试文件](tests)
6. 变基(衍合 `rebase`)你的分支到上游 master 分支;
7. `push` 你的本地仓库到 GitHub
8. 提交 `pull request`
9. 等待 CI 验证(若不通过则重复 5~7GitHub 会自动更新你的 `pull request`
10. 等待管理员处理,并及时 `rebase` 你的分支到上游 master 分支(若上游 master 分支有修改)。
*若有必要,可以 `git push -f` 强行推送 rebase 后的分支到自己的 `fork`*
*绝对不可以使用 `git push -f` 强行推送修改到上游*
### 注意事项
* 若对上述流程有任何不清楚的地方,请查阅 GIT 教程,如 [这个](http://backlogtool.com/git-guide/cn/)
* 对于代码**不同方面**的修改,请在自己 `fork` 的项目中**创建不同的分支**(原因参见`完整流程`第9条备注部分
* 变基及交互式变基操作参见 [Git 交互式变基](http://pakchoi.me/2015/03/17/git-interactive-rebase/)
## 推荐资源
### 开发环境
* XAMPP for Windows 5.5.x
* WampServer (for Windows)
* upupw Apache PHP5.4 ( for Windows)
或自行安装
- Apache / Nginx
- PHP 7.1 ~ 7.3
- MySQL / MariaDB
*Windows 用户推荐添加 PHP bin 目录到 PATH方便使用 composer*
*Linux 用户自行配置环境, Mac 用户推荐使用内置 Apache 配合 Homebrew 安装 PHP 和 MariaDB*
### 编辑器
Sublime Text 3 + phpfmt 插件
phpfmt 插件参数
```json
{
"autocomplete": true,
"enable_auto_align": true,
"format_on_save": true,
"indent_with_space": true,
"psr1_naming": false,
"psr2": true,
"version": 4
}
```
或其他 编辑器 / IDE 配合 PSR2 自动格式化工具
### Git GUI
* SourceTree
* GitHub Desktop
或其他 Git 图形界面客户端

32
vendor/topthink/framework/LICENSE.txt vendored Normal file
View File

@@ -0,0 +1,32 @@
ThinkPHP遵循Apache2开源协议发布并提供免费使用。
版权所有Copyright © 2006-2023 by ThinkPHP (http://thinkphp.cn)
All rights reserved。
ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
Apache Licence是著名的非盈利开源组织Apache采用的协议。
该协议和BSD类似鼓励代码共享和尊重原作者的著作权
允许代码修改,再作为开源或商业软件发布。需要满足
的条件:
1 需要给代码的用户一份Apache Licence
2 如果你修改了代码,需要在被修改的文件中说明;
3 在延伸的代码中(修改和有源代码衍生的代码中)需要
带有原来代码中的协议,商标,专利声明和其他原来作者规
定需要包含的说明;
4 如果再发布的产品中包含一个Notice文件则在Notice文
件中需要带有本协议内容。你可以在Notice中增加自己的
许可但不可以表现为对Apache Licence构成更改。
具体的协议参考http://www.apache.org/licenses/LICENSE-2.0
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

83
vendor/topthink/framework/README.md vendored Normal file
View File

@@ -0,0 +1,83 @@
![](https://www.thinkphp.cn/uploads/images/20230630/300c856765af4d8ae758c503185f8739.png)
# ThinkPHP 8.0
[![build](https://github.com/top-think/framework/actions/workflows/build.yml/badge.svg?branch=8.0)](https://github.com/top-think/framework/actions)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/top-think/framework/badges/quality-score.png?b=8.0)](https://scrutinizer-ci.com/g/top-think/framework/?branch=8.0)
[![Code Coverage](https://scrutinizer-ci.com/g/top-think/framework/badges/coverage.png?b=8.0)](https://scrutinizer-ci.com/g/top-think/framework/?branch=8.0)
[![Total Downloads](https://poser.pugx.org/topthink/framework/downloads)](https://packagist.org/packages/topthink/framework)
[![Latest Stable Version](https://poser.pugx.org/topthink/framework/v/stable)](https://packagist.org/packages/topthink/framework)
[![PHP Version](https://img.shields.io/badge/php-%3E%3D8.0-8892BF.svg)](http://www.php.net/)
[![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework)
## 主要特性
- 基于 PHP`8.0+`重构
- 升级`PSR`依赖
- 依赖`think-orm`3.0 版本
- `6.0`/`6.1`无缝升级
> ThinkPHP8.0 的运行环境要求 PHP8.0+
现在开始,你可以使用官方提供的[ThinkChat](https://chat.topthink.com/),让你在学习 ThinkPHP 的旅途中享受私人 AI 助理服务!
[![](https://www.topthink.com/uploads/assistant/20230630/4d1a3f0ad2958b49bb8189b7ef824cb0.png)](https://chat.topthink.com/)
ThinkPHP 生态服务由[顶想云](https://www.topthink.com)TOPThink Cloud提供为生态提供专业的开发者服务和价值之选。
## 文档
[完全开发手册](https://doc.thinkphp.cn)
## 赞助商
全新的[赞助计划](https://www.thinkphp.cn/sponsor)可以让你通过我们的网站、手册、欢迎页及 GIT 仓库获得巨大曝光,同时提升企业的品牌声誉,也更好保障 ThinkPHP 的可持续发展。
[![](https://www.thinkphp.cn/sponsor/special.svg)](https://www.thinkphp.cn/sponsor/special)
[![](https://www.thinkphp.cn/sponsor.svg)](https://www.thinkphp.cn/sponsor)
## 安装
```
composer create-project topthink/think tp
```
启动服务
```
cd tp
php think run
```
然后就可以在浏览器中访问
```
http://localhost:8000
```
如果需要更新框架使用
```
composer update topthink/framework
```
## 命名规范
`ThinkPHP`遵循 PSR-2 命名规范和 PSR-4 自动加载规范。
## 参与开发
直接提交 PR 或者 Issue 即可
## 版权信息
ThinkPHP 遵循 Apache2 开源协议发布,并提供免费使用。
本项目包含的第三方源码和二进制文件之版权信息另行标注。
版权所有 Copyright © 2006-2023 by ThinkPHP (http://thinkphp.cn) All rights reserved。
ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
更多细节参阅 [LICENSE.txt](LICENSE.txt)

54
vendor/topthink/framework/composer.json vendored Normal file
View File

@@ -0,0 +1,54 @@
{
"name": "topthink/framework",
"description": "The ThinkPHP Framework.",
"keywords": [
"framework",
"thinkphp",
"ORM"
],
"homepage": "http://thinkphp.cn/",
"license": "Apache-2.0",
"authors": [
{
"name": "liu21st",
"email": "liu21st@gmail.com"
},
{
"name": "yunwuxin",
"email": "448901948@qq.com"
}
],
"require": {
"php": ">=8.0.0",
"ext-json": "*",
"ext-mbstring": "*",
"psr/log": "^1.0|^2.0|^3.0",
"psr/container": "^2.0",
"psr/simple-cache": "^1.0|^2.0|^3.0",
"psr/http-message": "^1.0",
"topthink/think-orm": "^3.0",
"topthink/think-helper": "^3.1"
},
"require-dev": {
"mikey179/vfsstream": "^1.6",
"mockery/mockery": "^1.2",
"phpunit/phpunit": "^9.5",
"guzzlehttp/psr7": "^2.1.0"
},
"autoload": {
"files": [],
"psr-4": {
"think\\": "src/think/"
}
},
"autoload-dev": {
"psr-4": {
"think\\tests\\": "tests/"
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"sort-packages": true
}
}

BIN
vendor/topthink/framework/logo.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
backupGlobals="false"
backupStaticAttributes="false"
beStrictAboutTestsThatDoNotTestAnything="false"
bootstrap="tests/bootstrap.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
verbose="true"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src/think</directory>
</include>
</coverage>
<testsuites>
<testsuite name="ThinkPHP Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
</phpunit>

664
vendor/topthink/framework/src/helper.php vendored Normal file
View File

@@ -0,0 +1,664 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
//------------------------
// ThinkPHP 助手函数
//-------------------------
use think\App;
use think\Container;
use think\exception\HttpException;
use think\exception\HttpResponseException;
use think\facade\Cache;
use think\facade\Config;
use think\facade\Cookie;
use think\facade\Env;
use think\facade\Event;
use think\facade\Lang;
use think\facade\Log;
use think\facade\Request;
use think\facade\Route;
use think\facade\Session;
use think\Response;
use think\response\File;
use think\response\Json;
use think\response\Jsonp;
use think\response\Redirect;
use think\response\View;
use think\response\Xml;
use think\route\Url as UrlBuild;
use think\Validate;
if (!function_exists('abort')) {
/**
* 抛出HTTP异常
* @param integer|Response $code 状态码 或者 Response对象实例
* @param string $message 错误信息
* @param array $header 参数
*/
function abort($code, string $message = '', array $header = [])
{
if ($code instanceof Response) {
throw new HttpResponseException($code);
} else {
throw new HttpException($code, $message, null, $header);
}
}
}
if (!function_exists('app')) {
/**
* 快速获取容器中的实例 支持依赖注入
* @template T
* @param string|class-string<T> $name 类名或标识 默认获取当前应用实例
* @param array $args 参数
* @param bool $newInstance 是否每次创建新的实例
* @return T|object|App
*/
function app(string $name = '', array $args = [], bool $newInstance = false)
{
return Container::getInstance()->make($name ?: App::class, $args, $newInstance);
}
}
if (!function_exists('bind')) {
/**
* 绑定一个类到容器
* @param string|array $abstract 类标识、接口(支持批量绑定)
* @param mixed $concrete 要绑定的类、闭包或者实例
* @return Container
*/
function bind($abstract, $concrete = null)
{
return Container::getInstance()->bind($abstract, $concrete);
}
}
if (!function_exists('cache')) {
/**
* 缓存管理
* @param string $name 缓存名称
* @param mixed $value 缓存值
* @param mixed $options 缓存参数
* @param string $tag 缓存标签
* @return mixed
*/
function cache(string $name = null, $value = '', $options = null, $tag = null)
{
if (is_null($name)) {
return app('cache');
}
if ('' === $value) {
// 获取缓存
return str_starts_with($name, '?') ? Cache::has(substr($name, 1)) : Cache::get($name);
} elseif (is_null($value)) {
// 删除缓存
return Cache::delete($name);
}
// 缓存数据
if (is_array($options)) {
$expire = $options['expire'] ?? null; //修复查询缓存无法设置过期时间
} else {
$expire = $options;
}
if (is_null($tag)) {
return Cache::set($name, $value, $expire);
} else {
return Cache::tag($tag)->set($name, $value, $expire);
}
}
}
if (!function_exists('config')) {
/**
* 获取和设置配置参数
* @param string|array $name 参数名
* @param mixed $value 参数值
* @return mixed
*/
function config($name = '', $value = null)
{
if (is_array($name)) {
return Config::set($name, $value);
}
return str_starts_with($name, '?') ? Config::has(substr($name, 1)) : Config::get($name, $value);
}
}
if (!function_exists('cookie')) {
/**
* Cookie管理
* @param string $name cookie名称
* @param mixed $value cookie值
* @param mixed $option 参数
* @return mixed
*/
function cookie(string $name, $value = '', $option = null)
{
if (is_null($value)) {
// 删除
Cookie::delete($name, $option ?: []);
} elseif ('' === $value) {
// 获取
return str_starts_with($name, '?') ? Cookie::has(substr($name, 1)) : Cookie::get($name);
} else {
// 设置
return Cookie::set($name, $value, $option);
}
}
}
if (!function_exists('download')) {
/**
* 获取\think\response\Download对象实例
* @param string $filename 要下载的文件
* @param string $name 显示文件名
* @param bool $content 是否为内容
* @param int $expire 有效期(秒)
* @return \think\response\File
*/
function download(string $filename, string $name = '', bool $content = false, int $expire = 180): File
{
return Response::create($filename, 'file')->name($name)->isContent($content)->expire($expire);
}
}
if (!function_exists('dump')) {
/**
* 浏览器友好的变量输出
* @param mixed $vars 要输出的变量
* @return void
*/
function dump(...$vars)
{
ob_start();
var_dump(...$vars);
$output = ob_get_clean();
$output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output);
if (PHP_SAPI == 'cli') {
$output = PHP_EOL . $output . PHP_EOL;
} else {
if (!extension_loaded('xdebug')) {
$output = htmlspecialchars($output, ENT_SUBSTITUTE);
}
$output = '<pre>' . $output . '</pre>';
}
echo $output;
}
}
if (!function_exists('env')) {
/**
* 获取环境变量值
* @access public
* @param string $name 环境变量名(支持二级 .号分割)
* @param string $default 默认值
* @return mixed
*/
function env(string $name = null, $default = null)
{
return Env::get($name, $default);
}
}
if (!function_exists('event')) {
/**
* 触发事件
* @param mixed $event 事件名(或者类名)
* @param mixed $args 参数
* @return mixed
*/
function event($event, $args = null)
{
return Event::trigger($event, $args);
}
}
if (!function_exists('halt')) {
/**
* 调试变量并且中断输出
* @param mixed $vars 调试变量或者信息
*/
function halt(...$vars)
{
dump(...$vars);
throw new HttpResponseException(Response::create());
}
}
if (!function_exists('input')) {
/**
* 获取输入数据 支持默认值和过滤
* @param string $key 获取的变量名
* @param mixed $default 默认值
* @param string|array|null $filter 过滤方法
* @return mixed
*/
function input(string $key = '', $default = null, $filter = '')
{
if (str_starts_with($key, '?')) {
$key = substr($key, 1);
$has = true;
}
if ($pos = strpos($key, '.')) {
// 指定参数来源
$method = substr($key, 0, $pos);
if (in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) {
$key = substr($key, $pos + 1);
if ('server' == $method && is_null($default)) {
$default = '';
}
} else {
$method = 'param';
}
} else {
// 默认为自动判断
$method = 'param';
}
return isset($has) ?
request()->has($key, $method) :
request()->$method($key, $default, $filter);
}
}
if (!function_exists('invoke')) {
/**
* 调用反射实例化对象或者执行方法 支持依赖注入
* @param mixed $call 类名或者callable
* @param array $args 参数
* @return mixed
*/
function invoke($call, array $args = [])
{
if (is_callable($call)) {
return Container::getInstance()->invoke($call, $args);
}
return Container::getInstance()->invokeClass($call, $args);
}
}
if (!function_exists('json')) {
/**
* 获取\think\response\Json对象实例
* @param mixed $data 返回的数据
* @param int $code 状态码
* @param array $header 头部
* @param array $options 参数
* @return \think\response\Json
*/
function json($data = [], $code = 200, $header = [], $options = []): Json
{
return Response::create($data, 'json', $code)->header($header)->options($options);
}
}
if (!function_exists('jsonp')) {
/**
* 获取\think\response\Jsonp对象实例
* @param mixed $data 返回的数据
* @param int $code 状态码
* @param array $header 头部
* @param array $options 参数
* @return \think\response\Jsonp
*/
function jsonp($data = [], $code = 200, $header = [], $options = []): Jsonp
{
return Response::create($data, 'jsonp', $code)->header($header)->options($options);
}
}
if (!function_exists('lang')) {
/**
* 获取语言变量值
* @param string $name 语言变量名
* @param array $vars 动态变量值
* @param string $lang 语言
* @return mixed
*/
function lang(string $name, array $vars = [], string $lang = '')
{
return Lang::get($name, $vars, $lang);
}
}
if (!function_exists('parse_name')) {
/**
* 字符串命名风格转换
* type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
* @param string $name 字符串
* @param int $type 转换类型
* @param bool $ucfirst 首字母是否大写(驼峰规则)
* @return string
*/
function parse_name(string $name, int $type = 0, bool $ucfirst = true): string
{
if ($type) {
$name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {
return strtoupper($match[1]);
}, $name);
return $ucfirst ? ucfirst($name) : lcfirst($name);
}
return strtolower(trim(preg_replace('/[A-Z]/', '_\\0', $name), '_'));
}
}
if (!function_exists('redirect')) {
/**
* 获取\think\response\Redirect对象实例
* @param string $url 重定向地址
* @param int $code 状态码
* @return \think\response\Redirect
*/
function redirect(string $url = '', int $code = 302): Redirect
{
return Response::create($url, 'redirect', $code);
}
}
if (!function_exists('request')) {
/**
* 获取当前Request对象实例
* @return Request
*/
function request(): \think\Request
{
return app('request');
}
}
if (!function_exists('response')) {
/**
* 创建普通 Response 对象实例
* @param mixed $data 输出数据
* @param int|string $code 状态码
* @param array $header 头信息
* @param string $type
* @return Response
*/
function response($data = '', $code = 200, $header = [], $type = 'html'): Response
{
return Response::create($data, $type, $code)->header($header);
}
}
if (!function_exists('session')) {
/**
* Session管理
* @param string $name session名称
* @param mixed $value session值
* @return mixed
*/
function session($name = '', $value = '')
{
if (is_null($name)) {
// 清除
Session::clear();
} elseif ('' === $name) {
return Session::all();
} elseif (is_null($value)) {
// 删除
Session::delete($name);
} elseif ('' === $value) {
// 判断或获取
return str_starts_with($name, '?') ? Session::has(substr($name, 1)) : Session::get($name);
} else {
// 设置
Session::set($name, $value);
}
}
}
if (!function_exists('token')) {
/**
* 获取Token令牌
* @param string $name 令牌名称
* @param mixed $type 令牌生成方法
* @return string
*/
function token(string $name = '__token__', string $type = 'md5'): string
{
return Request::buildToken($name, $type);
}
}
if (!function_exists('token_field')) {
/**
* 生成令牌隐藏表单
* @param string $name 令牌名称
* @param mixed $type 令牌生成方法
* @return string
*/
function token_field(string $name = '__token__', string $type = 'md5'): string
{
$token = Request::buildToken($name, $type);
return '<input type="hidden" name="' . $name . '" value="' . $token . '" />';
}
}
if (!function_exists('token_meta')) {
/**
* 生成令牌meta
* @param string $name 令牌名称
* @param mixed $type 令牌生成方法
* @return string
*/
function token_meta(string $name = '__token__', string $type = 'md5'): string
{
$token = Request::buildToken($name, $type);
return '<meta name="csrf-token" content="' . $token . '">';
}
}
if (!function_exists('trace')) {
/**
* 记录日志信息
* @param mixed $log log信息 支持字符串和数组
* @param string $level 日志级别
* @return array|void
*/
function trace($log = '[think]', string $level = 'log')
{
if ('[think]' === $log) {
return Log::getLog();
}
Log::record($log, $level);
}
}
if (!function_exists('url')) {
/**
* Url生成
* @param string $url 路由地址
* @param array $vars 变量
* @param bool|string $suffix 生成的URL后缀
* @param bool|string $domain 域名
* @return UrlBuild
*/
function url(string $url = '', array $vars = [], $suffix = true, $domain = false): UrlBuild
{
return Route::buildUrl($url, $vars)->suffix($suffix)->domain($domain);
}
}
if (!function_exists('validate')) {
/**
* 生成验证对象
* @param string|array $validate 验证器类名或者验证规则数组
* @param array $message 错误提示信息
* @param bool $batch 是否批量验证
* @param bool $failException 是否抛出异常
* @return Validate
*/
function validate($validate = '', array $message = [], bool $batch = false, bool $failException = true): Validate
{
if (is_array($validate) || '' === $validate) {
$v = new Validate();
if (is_array($validate)) {
$v->rule($validate);
}
} else {
if (str_contains($validate, '.')) {
// 支持场景
[$validate, $scene] = explode('.', $validate);
}
$class = str_contains($validate, '\\') ? $validate : app()->parseClass('validate', $validate);
$v = new $class();
if (!empty($scene)) {
$v->scene($scene);
}
}
return $v->message($message)->batch($batch)->failException($failException);
}
}
if (!function_exists('view')) {
/**
* 渲染模板输出
* @param string $template 模板文件
* @param array $vars 模板变量
* @param int $code 状态码
* @param callable $filter 内容过滤
* @return \think\response\View
*/
function view(string $template = '', $vars = [], $code = 200, $filter = null): View
{
return Response::create($template, 'view', $code)->assign($vars)->filter($filter);
}
}
if (!function_exists('display')) {
/**
* 渲染模板输出
* @param string $content 渲染内容
* @param array $vars 模板变量
* @param int $code 状态码
* @param callable $filter 内容过滤
* @return \think\response\View
*/
function display(string $content, $vars = [], $code = 200, $filter = null): View
{
return Response::create($content, 'view', $code)->isContent(true)->assign($vars)->filter($filter);
}
}
if (!function_exists('xml')) {
/**
* 获取\think\response\Xml对象实例
* @param mixed $data 返回的数据
* @param int $code 状态码
* @param array $header 头部
* @param array $options 参数
* @return \think\response\Xml
*/
function xml($data = [], $code = 200, $header = [], $options = []): Xml
{
return Response::create($data, 'xml', $code)->header($header)->options($options);
}
}
if (!function_exists('app_path')) {
/**
* 获取当前应用目录
*
* @param string $path
* @return string
*/
function app_path($path = '')
{
return app()->getAppPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
}
}
if (!function_exists('base_path')) {
/**
* 获取应用基础目录
*
* @param string $path
* @return string
*/
function base_path($path = '')
{
return app()->getBasePath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
}
}
if (!function_exists('config_path')) {
/**
* 获取应用配置目录
*
* @param string $path
* @return string
*/
function config_path($path = '')
{
return app()->getConfigPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
}
}
if (!function_exists('public_path')) {
/**
* 获取web根目录
*
* @param string $path
* @return string
*/
function public_path($path = '')
{
return app()->getRootPath() . 'public' . DIRECTORY_SEPARATOR . ($path ? ltrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $path);
}
}
if (!function_exists('runtime_path')) {
/**
* 获取应用运行时目录
*
* @param string $path
* @return string
*/
function runtime_path($path = '')
{
return app()->getRuntimePath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
}
}
if (!function_exists('root_path')) {
/**
* 获取项目根目录
*
* @param string $path
* @return string
*/
function root_path($path = '')
{
return app()->getRootPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
}
}

View File

@@ -0,0 +1,152 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
// 核心中文语言包
return [
// 系统错误提示
'Undefined variable' => '未定义变量',
'Undefined index' => '未定义数组索引',
'Undefined offset' => '未定义数组下标',
'Parse error' => '语法解析错误',
'Type error' => '类型错误',
'Fatal error' => '致命错误',
'syntax error' => '语法错误',
// 框架核心错误提示
'dispatch type not support' => '不支持的调度类型',
'method param miss' => '方法参数错误',
'method not exists' => '方法不存在',
'function not exists' => '函数不存在',
'app not exists' => '应用不存在',
'controller not exists' => '控制器不存在',
'class not exists' => '类不存在',
'property not exists' => '类的属性不存在',
'template not exists' => '模板文件不存在',
'illegal controller name' => '非法的控制器名称',
'illegal action name' => '非法的操作名称',
'url suffix deny' => '禁止的URL后缀访问',
'Undefined cache config' => '缓存配置未定义',
'Route Not Found' => '当前访问路由未定义或不匹配',
'Undefined db config' => '数据库配置未定义',
'Undefined log config' => '日志配置未定义',
'Undefined db type' => '未定义数据库类型',
'variable type error' => '变量类型错误',
'PSR-4 error' => 'PSR-4 规范错误',
'not support type' => '不支持的分页索引字段类型',
'not support total' => '简洁模式下不能获取数据总数',
'not support last' => '简洁模式下不能获取最后一页',
'error session handler' => '错误的SESSION处理器类',
'not allow php tag' => '模板不允许使用PHP语法',
'not support' => '不支持',
'database config error' => '数据库配置信息错误',
'redisd master' => 'Redisd 主服务器错误',
'redisd slave' => 'Redisd 从服务器错误',
'must run at sae' => '必须在SAE运行',
'memcache init error' => '未开通Memcache服务请在SAE管理平台初始化Memcache服务',
'KVDB init error' => '没有初始化KVDB请在SAE管理平台初始化KVDB服务',
'fields not exists' => '数据表字段不存在',
'where express error' => '查询表达式错误',
'no data to update' => '没有任何数据需要更新',
'miss data to insert' => '缺少需要写入的数据',
'miss complex primary data' => '缺少复合主键数据',
'miss update condition' => '缺少更新条件',
'model data Not Found' => '模型数据不存在',
'table data not Found' => '表数据不存在',
'delete without condition' => '没有条件不会执行删除操作',
'miss relation data' => '缺少关联表数据',
'tag attr must' => '模板标签属性必须',
'tag error' => '模板标签错误',
'cache write error' => '缓存写入失败',
'sae mc write error' => 'SAE mc 写入错误',
'route name not exists' => '路由标识不存在(或参数不够)',
'invalid request' => '非法请求',
'bind attr has exists' => '模型的属性已经存在',
'relation data not exists' => '关联数据不存在',
'relation not support' => '关联不支持',
'chunk not support order' => 'Chunk不支持调用order方法',
'route pattern error' => '路由变量规则定义错误',
'route behavior will not support' => '路由行为废弃(使用中间件替代)',
'closure not support cache(true)' => '使用闭包查询不支持cache(true)请指定缓存Key',
// 上传错误信息
'unknown upload error' => '未知上传错误!',
'file write error' => '文件写入失败!',
'upload temp dir not found' => '找不到临时文件夹!',
'no file to uploaded' => '没有文件被上传!',
'only the portion of file is uploaded' => '文件只有部分被上传!',
'upload File size exceeds the maximum value' => '上传文件大小超过了最大值!',
'upload write error' => '文件上传保存错误!',
'has the same filename: {:filename}' => '存在同名文件:{:filename}',
'upload illegal files' => '非法上传文件',
'illegal image files' => '非法图片文件',
'extensions to upload is not allowed' => '上传文件后缀不允许',
'mimetype to upload is not allowed' => '上传文件MIME类型不允许',
'filesize not match' => '上传文件大小不符!',
'directory {:path} creation failed' => '目录 {:path} 创建失败!',
'The middleware must return Response instance' => '中间件方法必须返回Response对象实例',
'The queue was exhausted, with no response returned' => '中间件队列为空',
// Validate Error Message
':attribute require' => ':attribute不能为空',
':attribute must' => ':attribute必须',
':attribute must be numeric' => ':attribute必须是数字',
':attribute must be integer' => ':attribute必须是整数',
':attribute must be float' => ':attribute必须是浮点数',
':attribute must be string' => ':attribute必须是字符串',
':attribute must start with :rule' => ':attribute必须以 :rule 开头',
':attribute must end with :rule' => ':attribute必须以 :rule 结尾',
':attribute must contain :rule' => ':attribute必须包含 :rule',
':attribute must be bool' => ':attribute必须是布尔值',
':attribute not a valid email address' => ':attribute格式不符',
':attribute not a valid mobile' => ':attribute格式不符',
':attribute must be a array' => ':attribute必须是数组',
':attribute must be yes,on or 1' => ':attribute必须是yes、on或者1',
':attribute not a valid datetime' => ':attribute不是一个有效的日期或时间格式',
':attribute not a valid file' => ':attribute不是有效的上传文件',
':attribute not a valid image' => ':attribute不是有效的图像文件',
':attribute must be alpha' => ':attribute只能是字母',
':attribute must be alpha-numeric' => ':attribute只能是字母和数字',
':attribute must be alpha-numeric, dash, underscore' => ':attribute只能是字母、数字和下划线_及破折号-',
':attribute not a valid domain or ip' => ':attribute不是有效的域名或者IP',
':attribute must be chinese' => ':attribute只能是汉字',
':attribute must be chinese or alpha' => ':attribute只能是汉字、字母',
':attribute must be chinese,alpha-numeric' => ':attribute只能是汉字、字母和数字',
':attribute must be chinese,alpha-numeric,underscore, dash' => ':attribute只能是汉字、字母、数字和下划线_及破折号-',
':attribute not a valid url' => ':attribute不是有效的URL地址',
':attribute not a valid ip' => ':attribute不是有效的IP地址',
':attribute must be dateFormat of :rule' => ':attribute必须使用日期格式 :rule',
':attribute must be in :rule' => ':attribute必须在 :rule 范围内',
':attribute be notin :rule' => ':attribute不能在 :rule 范围内',
':attribute must between :1 - :2' => ':attribute只能在 :1 - :2 之间',
':attribute not between :1 - :2' => ':attribute不能在 :1 - :2 之间',
'size of :attribute must be :rule' => ':attribute长度不符合要求 :rule',
'max size of :attribute must be :rule' => ':attribute长度不能超过 :rule',
'min size of :attribute must be :rule' => ':attribute长度不能小于 :rule',
':attribute cannot be less than :rule' => ':attribute日期不能小于 :rule',
':attribute cannot exceed :rule' => ':attribute日期不能超过 :rule',
':attribute not within :rule' => '不在有效期内 :rule',
'access IP is not allowed' => '不允许的IP访问',
'access IP denied' => '禁止的IP访问',
':attribute out of accord with :2' => ':attribute和确认字段:2不一致',
':attribute cannot be same with :2' => ':attribute和比较字段:2不能相同',
':attribute must greater than or equal :rule' => ':attribute必须大于等于 :rule',
':attribute must greater than :rule' => ':attribute必须大于 :rule',
':attribute must less than or equal :rule' => ':attribute必须小于等于 :rule',
':attribute must less than :rule' => ':attribute必须小于 :rule',
':attribute must equal :rule' => ':attribute必须等于 :rule',
':attribute has exists' => ':attribute已存在',
':attribute not conform to the rules' => ':attribute不符合指定规则',
'invalid Request method' => '无效的请求类型',
'invalid token' => '令牌数据无效',
'not conform to the rules' => '规则错误',
'record has update' => '记录已经被更新了',
];

View File

@@ -0,0 +1,620 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think;
use Composer\InstalledVersions;
use think\event\AppInit;
use think\helper\Str;
use think\initializer\BootService;
use think\initializer\Error;
use think\initializer\RegisterService;
/**
* App 基础类
* @property Route $route
* @property Config $config
* @property Cache $cache
* @property Request $request
* @property Http $http
* @property Console $console
* @property Env $env
* @property Event $event
* @property Middleware $middleware
* @property Log $log
* @property Lang $lang
* @property Db $db
* @property Cookie $cookie
* @property Session $session
* @property Validate $validate
*/
class App extends Container
{
const VERSION = '8.0.0';
/**
* 应用调试模式
* @var bool
*/
protected $appDebug = false;
/**
* 环境变量标识
* @var string
*/
protected $envName = '';
/**
* 应用开始时间
* @var float
*/
protected $beginTime;
/**
* 应用内存初始占用
* @var integer
*/
protected $beginMem;
/**
* 当前应用类库命名空间
* @var string
*/
protected $namespace = 'app';
/**
* 应用根目录
* @var string
*/
protected $rootPath = '';
/**
* 框架目录
* @var string
*/
protected $thinkPath = '';
/**
* 应用目录
* @var string
*/
protected $appPath = '';
/**
* Runtime目录
* @var string
*/
protected $runtimePath = '';
/**
* 路由定义目录
* @var string
*/
protected $routePath = '';
/**
* 配置后缀
* @var string
*/
protected $configExt = '.php';
/**
* 应用初始化器
* @var array
*/
protected $initializers = [
Error::class,
RegisterService::class,
BootService::class,
];
/**
* 注册的系统服务
* @var array
*/
protected $services = [];
/**
* 初始化
* @var bool
*/
protected $initialized = false;
/**
* 容器绑定标识
* @var array
*/
protected $bind = [
'app' => App::class,
'cache' => Cache::class,
'config' => Config::class,
'console' => Console::class,
'cookie' => Cookie::class,
'db' => Db::class,
'env' => Env::class,
'event' => Event::class,
'http' => Http::class,
'lang' => Lang::class,
'log' => Log::class,
'middleware' => Middleware::class,
'request' => Request::class,
'response' => Response::class,
'route' => Route::class,
'session' => Session::class,
'validate' => Validate::class,
'view' => View::class,
'think\DbManager' => Db::class,
'think\LogManager' => Log::class,
'think\CacheManager' => Cache::class,
// 接口依赖注入
'Psr\Log\LoggerInterface' => Log::class,
];
/**
* 架构方法
* @access public
* @param string $rootPath 应用根目录
*/
public function __construct(string $rootPath = '')
{
$this->thinkPath = realpath(dirname(__DIR__)) . DIRECTORY_SEPARATOR;
$this->rootPath = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath();
$this->appPath = $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
$this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
if (is_file($this->appPath . 'provider.php')) {
$this->bind(include $this->appPath . 'provider.php');
}
static::setInstance($this);
$this->instance('app', $this);
$this->instance('think\Container', $this);
}
/**
* 注册服务
* @access public
* @param Service|string $service 服务
* @param bool $force 强制重新注册
* @return Service|null
*/
public function register(Service|string $service, bool $force = false)
{
$registered = $this->getService($service);
if ($registered && !$force) {
return $registered;
}
if (is_string($service)) {
$service = new $service($this);
}
if (method_exists($service, 'register')) {
$service->register();
}
if (property_exists($service, 'bind')) {
$this->bind($service->bind);
}
$this->services[] = $service;
}
/**
* 执行服务
* @access public
* @param Service $service 服务
* @return mixed
*/
public function bootService(Service $service)
{
if (method_exists($service, 'boot')) {
return $this->invoke([$service, 'boot']);
}
}
/**
* 获取服务
* @param string|Service $service
* @return Service|null
*/
public function getService(Service|string $service): ?Service
{
$name = is_string($service) ? $service : $service::class;
return array_values(array_filter($this->services, function ($value) use ($name) {
return $value instanceof $name;
}, ARRAY_FILTER_USE_BOTH))[0] ?? null;
}
/**
* 开启应用调试模式
* @access public
* @param bool $debug 开启应用调试模式
* @return $this
*/
public function debug(bool $debug = true)
{
$this->appDebug = $debug;
return $this;
}
/**
* 是否为调试模式
* @access public
* @return bool
*/
public function isDebug(): bool
{
return $this->appDebug;
}
/**
* 设置应用命名空间
* @access public
* @param string $namespace 应用命名空间
* @return $this
*/
public function setNamespace(string $namespace)
{
$this->namespace = $namespace;
return $this;
}
/**
* 获取应用类库命名空间
* @access public
* @return string
*/
public function getNamespace(): string
{
return $this->namespace;
}
/**
* 设置环境变量标识
* @access public
* @param string $name 环境标识
* @return $this
*/
public function setEnvName(string $name)
{
$this->envName = $name;
return $this;
}
/**
* 获取框架版本
* @access public
* @return string
*/
public function version(): string
{
return ltrim(InstalledVersions::getPrettyVersion('topthink/framework'), 'v');
}
/**
* 获取应用根目录
* @access public
* @return string
*/
public function getRootPath(): string
{
return $this->rootPath;
}
/**
* 获取应用基础目录
* @access public
* @return string
*/
public function getBasePath(): string
{
return $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
}
/**
* 获取当前应用目录
* @access public
* @return string
*/
public function getAppPath(): string
{
return $this->appPath;
}
/**
* 设置应用目录
* @param string $path 应用目录
*/
public function setAppPath(string $path)
{
$this->appPath = $path;
}
/**
* 获取应用运行时目录
* @access public
* @return string
*/
public function getRuntimePath(): string
{
return $this->runtimePath;
}
/**
* 设置runtime目录
* @param string $path 定义目录
*/
public function setRuntimePath(string $path): void
{
$this->runtimePath = $path;
}
/**
* 获取核心框架目录
* @access public
* @return string
*/
public function getThinkPath(): string
{
return $this->thinkPath;
}
/**
* 获取应用配置目录
* @access public
* @return string
*/
public function getConfigPath(): string
{
return $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
}
/**
* 获取配置后缀
* @access public
* @return string
*/
public function getConfigExt(): string
{
return $this->configExt;
}
/**
* 获取应用开启时间
* @access public
* @return float
*/
public function getBeginTime(): float
{
return $this->beginTime;
}
/**
* 获取应用初始内存占用
* @access public
* @return integer
*/
public function getBeginMem(): int
{
return $this->beginMem;
}
/**
* 加载环境变量定义
* @access public
* @param string $envName 环境标识
* @return void
*/
public function loadEnv(string $envName = ''): void
{
// 加载环境变量
$envFile = $envName ? $this->rootPath . '.env.' . $envName : $this->rootPath . '.env';
if (is_file($envFile)) {
$this->env->load($envFile);
}
}
/**
* 初始化应用
* @access public
* @return $this
*/
public function initialize()
{
$this->initialized = true;
$this->beginTime = microtime(true);
$this->beginMem = memory_get_usage();
$this->loadEnv($this->envName);
$this->configExt = $this->env->get('config_ext', '.php');
$this->debugModeInit();
// 加载全局初始化文件
$this->load();
// 加载应用默认语言包
$this->loadLangPack();
// 监听AppInit
$this->event->trigger(AppInit::class);
date_default_timezone_set($this->config->get('app.default_timezone', 'Asia/Shanghai'));
// 初始化
foreach ($this->initializers as $initializer) {
$this->make($initializer)->init($this);
}
return $this;
}
/**
* 是否初始化过
* @return bool
*/
public function initialized()
{
return $this->initialized;
}
/**
* 加载语言包
* @return void
*/
public function loadLangPack(): void
{
// 加载默认语言包
$langSet = $this->lang->defaultLangSet();
$this->lang->switchLangSet($langSet);
}
/**
* 引导应用
* @access public
* @return void
*/
public function boot(): void
{
array_walk($this->services, function ($service) {
$this->bootService($service);
});
}
/**
* 加载应用文件和配置
* @access protected
* @return void
*/
protected function load(): void
{
$appPath = $this->getAppPath();
if (is_file($appPath . 'common.php')) {
include_once $appPath . 'common.php';
}
include_once $this->thinkPath . 'helper.php';
$configPath = $this->getConfigPath();
$files = [];
if (is_dir($configPath)) {
$files = glob($configPath . '*' . $this->configExt);
}
foreach ($files as $file) {
$this->config->load($file, pathinfo($file, PATHINFO_FILENAME));
}
if (is_file($appPath . 'event.php')) {
$this->loadEvent(include $appPath . 'event.php');
}
if (is_file($appPath . 'service.php')) {
$services = include $appPath . 'service.php';
foreach ($services as $service) {
$this->register($service);
}
}
}
/**
* 调试模式设置
* @access protected
* @return void
*/
protected function debugModeInit(): void
{
// 应用调试模式
if (!$this->appDebug) {
$this->appDebug = $this->env->get('app_debug') ? true : false;
ini_set('display_errors', 'Off');
}
if (!$this->runningInConsole()) {
//重新申请一块比较大的buffer
if (ob_get_level() > 0) {
$output = ob_get_clean();
}
ob_start();
if (!empty($output)) {
echo $output;
}
}
}
/**
* 注册应用事件
* @access protected
* @param array $event 事件数据
* @return void
*/
public function loadEvent(array $event): void
{
if (isset($event['bind'])) {
$this->event->bind($event['bind']);
}
if (isset($event['listen'])) {
$this->event->listenEvents($event['listen']);
}
if (isset($event['subscribe'])) {
$this->event->subscribe($event['subscribe']);
}
}
/**
* 解析应用类的类名
* @access public
* @param string $layer 层名 controller model ...
* @param string $name 类名
* @return string
*/
public function parseClass(string $layer, string $name): string
{
$name = str_replace(['/', '.'], '\\', $name);
$array = explode('\\', $name);
$class = Str::studly(array_pop($array));
$path = $array ? implode('\\', $array) . '\\' : '';
return $this->namespace . '\\' . $layer . '\\' . $path . $class;
}
/**
* 是否运行在命令行下
* @return bool
*/
public function runningInConsole(): bool
{
return php_sapi_name() === 'cli' || php_sapi_name() === 'phpdbg';
}
/**
* 获取应用根目录
* @access protected
* @return string
*/
protected function getDefaultRootPath(): string
{
return dirname($this->thinkPath, 4) . DIRECTORY_SEPARATOR;
}
}

View File

@@ -0,0 +1,200 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types = 1);
namespace think;
use DateInterval;
use DateTimeInterface;
use Psr\SimpleCache\CacheInterface;
use think\cache\Driver;
use think\cache\TagSet;
use think\exception\InvalidArgumentException;
use think\helper\Arr;
/**
* 缓存管理类
* @mixin Driver
* @mixin \think\cache\driver\File
*/
class Cache extends Manager implements CacheInterface
{
protected $namespace = '\\think\\cache\\driver\\';
/**
* 默认驱动
* @return string|null
*/
public function getDefaultDriver(): ?string
{
return $this->getConfig('default');
}
/**
* 获取缓存配置
* @access public
* @param null|string $name 名称
* @param mixed $default 默认值
* @return mixed
*/
public function getConfig(string $name = null, $default = null)
{
if (!is_null($name)) {
return $this->app->config->get('cache.' . $name, $default);
}
return $this->app->config->get('cache');
}
/**
* 获取驱动配置
* @param string $store
* @param string $name
* @param mixed $default
* @return array
*/
public function getStoreConfig(string $store, string $name = null, $default = null)
{
if ($config = $this->getConfig("stores.{$store}")) {
return Arr::get($config, $name, $default);
}
throw new \InvalidArgumentException("Store [$store] not found.");
}
protected function resolveType(string $name)
{
return $this->getStoreConfig($name, 'type', 'file');
}
protected function resolveConfig(string $name)
{
return $this->getStoreConfig($name);
}
/**
* 连接或者切换缓存
* @access public
* @param string|null $name 连接配置名
* @return Driver
*/
public function store(string $name = null)
{
return $this->driver($name);
}
/**
* 清空缓冲池
* @access public
* @return bool
*/
public function clear(): bool
{
return $this->store()->clear();
}
/**
* 读取缓存
* @access public
* @param string $key 缓存变量名
* @param mixed $default 默认值
* @return mixed
*/
public function get($key, mixed $default = null): mixed
{
return $this->store()->get($key, $default);
}
/**
* 写入缓存
* @access public
* @param string $key 缓存变量名
* @param mixed $value 存储数据
* @param int|DateTimeInterface|DateInterval $ttl 有效时间 0为永久
* @return bool
*/
public function set($key, $value, $ttl = null): bool
{
return $this->store()->set($key, $value, $ttl);
}
/**
* 删除缓存
* @access public
* @param string $key 缓存变量名
* @return bool
*/
public function delete($key): bool
{
return $this->store()->delete($key);
}
/**
* 读取缓存
* @access public
* @param iterable $keys 缓存变量名
* @param mixed $default 默认值
* @return iterable
* @throws InvalidArgumentException
*/
public function getMultiple($keys, $default = null): iterable
{
return $this->store()->getMultiple($keys, $default);
}
/**
* 写入缓存
* @access public
* @param iterable $values 缓存数据
* @param null|int|\DateInterval $ttl 有效时间 0为永久
* @return bool
*/
public function setMultiple($values, $ttl = null): bool
{
return $this->store()->setMultiple($values, $ttl);
}
/**
* 删除缓存
* @access public
* @param iterable $keys 缓存变量名
* @return bool
* @throws InvalidArgumentException
*/
public function deleteMultiple($keys): bool
{
return $this->store()->deleteMultiple($keys);
}
/**
* 判断缓存是否存在
* @access public
* @param string $key 缓存变量名
* @return bool
*/
public function has($key): bool
{
return $this->store()->has($key);
}
/**
* 缓存标签
* @access public
* @param string|array $name 标签名
* @return TagSet
*/
public function tag($name)
{
return $this->store()->tag($name);
}
}

View File

@@ -0,0 +1,173 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think;
/**
* 配置管理类
* @package think
*/
class Config
{
/**
* 配置参数
* @var array
*/
protected $config = [];
/**
* 构造方法
* @access public
*/
public function __construct(protected string $path = '', protected string $ext = '.php')
{
}
public static function __make(App $app)
{
$path = $app->getConfigPath();
$ext = $app->getConfigExt();
return new static($path, $ext);
}
/**
* 加载配置文件(多种格式)
* @access public
* @param string $file 配置文件名
* @param string $name 一级配置名
* @return array
*/
public function load(string $file, string $name = ''): array
{
if (is_file($file)) {
$filename = $file;
} elseif (is_file($this->path . $file . $this->ext)) {
$filename = $this->path . $file . $this->ext;
}
if (isset($filename)) {
return $this->parse($filename, $name);
}
return $this->config;
}
/**
* 解析配置文件
* @access public
* @param string $file 配置文件名
* @param string $name 一级配置名
* @return array
*/
protected function parse(string $file, string $name): array
{
$type = pathinfo($file, PATHINFO_EXTENSION);
$config = [];
$config = match ($type) {
'php' => include $file,
'yml','yaml' => function_exists('yaml_parse_file') ? yaml_parse_file($file) : [],
'ini' => parse_ini_file($file, true, INI_SCANNER_TYPED) ?: [],
'json' => json_decode(file_get_contents($file), true),
default => [],
};
return is_array($config) ? $this->set($config, strtolower($name)) : [];
}
/**
* 检测配置是否存在
* @access public
* @param string $name 配置参数名(支持多级配置 .号分割)
* @return bool
*/
public function has(string $name): bool
{
if (!str_contains($name, '.') && !isset($this->config[strtolower($name)])) {
return false;
}
return !is_null($this->get($name));
}
/**
* 获取一级配置
* @access protected
* @param string $name 一级配置名
* @return array
*/
protected function pull(string $name): array
{
$name = strtolower($name);
return $this->config[$name] ?? [];
}
/**
* 获取配置参数 为空则获取所有配置
* @access public
* @param string $name 配置参数名(支持多级配置 .号分割)
* @param mixed $default 默认值
* @return mixed
*/
public function get(string $name = null, $default = null)
{
// 无参数时获取所有
if (empty($name)) {
return $this->config;
}
if (!str_contains($name, '.')) {
return $this->pull($name);
}
$name = explode('.', $name);
$name[0] = strtolower($name[0]);
$config = $this->config;
// 按.拆分成多维数组进行判断
foreach ($name as $val) {
if (isset($config[$val])) {
$config = $config[$val];
} else {
return $default;
}
}
return $config;
}
/**
* 设置配置参数 name为数组则为批量设置
* @access public
* @param array $config 配置参数
* @param string $name 配置名
* @return array
*/
public function set(array $config, string $name = null): array
{
if (empty($name)) {
$this->config = array_merge($this->config, array_change_key_case($config));
return $this->config;
}
if (isset($this->config[$name])) {
$result = array_merge($this->config[$name], $config);
} else {
$result = $config;
}
$this->config[$name] = $result;
return $result;
}
}

View File

@@ -0,0 +1,787 @@
<?php
// +----------------------------------------------------------------------
// | TopThink [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2015 http://www.topthink.com All rights reserved.
// +----------------------------------------------------------------------
// | Author: zhangyajun <448901948@qq.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think;
use Closure;
use InvalidArgumentException;
use LogicException;
use think\console\Command;
use think\console\command\Clear;
use think\console\command\Help;
use think\console\command\Help as HelpCommand;
use think\console\command\Lists;
use think\console\command\make\Command as MakeCommand;
use think\console\command\make\Controller;
use think\console\command\make\Event;
use think\console\command\make\Listener;
use think\console\command\make\Middleware;
use think\console\command\make\Model;
use think\console\command\make\Service;
use think\console\command\make\Subscribe;
use think\console\command\make\Validate;
use think\console\command\optimize\Route;
use think\console\command\optimize\Schema;
use think\console\command\RouteList;
use think\console\command\RunServer;
use think\console\command\ServiceDiscover;
use think\console\command\VendorPublish;
use think\console\command\Version;
use think\console\Input;
use think\console\input\Argument as InputArgument;
use think\console\input\Definition as InputDefinition;
use think\console\input\Option as InputOption;
use think\console\Output;
use think\console\output\driver\Buffer;
/**
* 控制台应用管理类
*/
class Console
{
/** @var Command[] */
protected $commands = [];
protected $wantHelps = false;
protected $catchExceptions = true;
protected $autoExit = true;
protected $definition;
protected $defaultCommand = 'list';
protected $defaultCommands = [
'help' => Help::class,
'list' => Lists::class,
'clear' => Clear::class,
'make:command' => MakeCommand::class,
'make:controller' => Controller::class,
'make:model' => Model::class,
'make:middleware' => Middleware::class,
'make:validate' => Validate::class,
'make:event' => Event::class,
'make:listener' => Listener::class,
'make:service' => Service::class,
'make:subscribe' => Subscribe::class,
'optimize:route' => Route::class,
'optimize:schema' => Schema::class,
'run' => RunServer::class,
'version' => Version::class,
'route:list' => RouteList::class,
'service:discover' => ServiceDiscover::class,
'vendor:publish' => VendorPublish::class,
];
/**
* 启动器
* @var array
*/
protected static $startCallbacks = [];
public function __construct(protected App $app)
{
$this->initialize();
$this->definition = $this->getDefaultInputDefinition();
//加载指令
$this->loadCommands();
// 设置执行用户
$user = $this->app->config->get('console.user');
if (!empty($user)) {
$this->setUser($user);
}
$this->start();
}
/**
* 初始化
*/
protected function initialize():void
{
if (!$this->app->initialized()) {
$this->app->initialize();
}
$this->makeRequest();
}
/**
* 构造request
*/
protected function makeRequest():void
{
$url = $this->app->config->get('app.url', 'http://localhost');
$components = parse_url($url);
$server = $_SERVER;
if (isset($components['path'])) {
$server = array_merge($server, [
'SCRIPT_FILENAME' => $components['path'],
'SCRIPT_NAME' => $components['path'],
'REQUEST_URI' => $components['path'],
]);
}
if (isset($components['host'])) {
$server['SERVER_NAME'] = $components['host'];
$server['HTTP_HOST'] = $components['host'];
}
if (isset($components['scheme'])) {
if ('https' === $components['scheme']) {
$server['HTTPS'] = 'on';
$server['SERVER_PORT'] = 443;
} else {
unset($server['HTTPS']);
$server['SERVER_PORT'] = 80;
}
}
if (isset($components['port'])) {
$server['SERVER_PORT'] = $components['port'];
$server['HTTP_HOST'] .= ':' . $components['port'];
}
/** @var Request $request */
$request = $this->app->make('request');
$request->withServer($server);
}
/**
* 添加初始化器
* @param Closure $callback
*/
public static function starting(Closure $callback): void
{
static::$startCallbacks[] = $callback;
}
/**
* 清空启动器
*/
public static function flushStartCallbacks(): void
{
static::$startCallbacks = [];
}
/**
* 设置执行用户
* @param $user
*/
public static function setUser(string $user): void
{
if (extension_loaded('posix')) {
$user = posix_getpwnam($user);
if (!empty($user)) {
posix_setgid($user['gid']);
posix_setuid($user['uid']);
}
}
}
/**
* 启动
*/
protected function start(): void
{
foreach (static::$startCallbacks as $callback) {
$callback($this);
}
}
/**
* 加载指令
* @access protected
*/
protected function loadCommands(): void
{
$commands = $this->app->config->get('console.commands', []);
$commands = array_merge($this->defaultCommands, $commands);
$this->addCommands($commands);
}
/**
* @access public
* @param string $command
* @param array $parameters
* @param string $driver
* @return Output|Buffer
*/
public function call(string $command, array $parameters = [], string $driver = 'buffer')
{
array_unshift($parameters, $command);
$input = new Input($parameters);
$output = new Output($driver);
$this->setCatchExceptions(false);
$this->find($command)->run($input, $output);
return $output;
}
/**
* 执行当前的指令
* @access public
* @return int
* @throws \Exception
* @api
*/
public function run()
{
$input = new Input();
$output = new Output();
$this->configureIO($input, $output);
try {
$exitCode = $this->doRun($input, $output);
} catch (\Exception $e) {
if (!$this->catchExceptions) {
throw $e;
}
$output->renderException($e);
$exitCode = $e->getCode();
if (is_numeric($exitCode)) {
$exitCode = (int) $exitCode;
if (0 === $exitCode) {
$exitCode = 1;
}
} else {
$exitCode = 1;
}
}
if ($this->autoExit) {
if ($exitCode > 255) {
$exitCode = 255;
}
exit($exitCode);
}
return $exitCode;
}
/**
* 执行指令
* @access public
* @param Input $input
* @param Output $output
* @return int
*/
public function doRun(Input $input, Output $output)
{
if (true === $input->hasParameterOption(['--version', '-V'])) {
$output->writeln($this->getLongVersion());
return 0;
}
$name = $this->getCommandName($input);
if (true === $input->hasParameterOption(['--help', '-h'])) {
if (!$name) {
$name = 'help';
$input = new Input(['help']);
} else {
$this->wantHelps = true;
}
}
if (!$name) {
$name = $this->defaultCommand;
$input = new Input([$this->defaultCommand]);
}
$command = $this->find($name);
return $this->doRunCommand($command, $input, $output);
}
/**
* 设置输入参数定义
* @access public
* @param InputDefinition $definition
*/
public function setDefinition(InputDefinition $definition): void
{
$this->definition = $definition;
}
/**
* 获取输入参数定义
* @access public
* @return InputDefinition The InputDefinition instance
*/
public function getDefinition(): InputDefinition
{
return $this->definition;
}
/**
* Gets the help message.
* @access public
* @return string A help message.
*/
public function getHelp(): string
{
return $this->getLongVersion();
}
/**
* 是否捕获异常
* @access public
* @param bool $boolean
* @api
*/
public function setCatchExceptions(bool $boolean): void
{
$this->catchExceptions = $boolean;
}
/**
* 是否自动退出
* @access public
* @param bool $boolean
* @api
*/
public function setAutoExit(bool $boolean): void
{
$this->autoExit = $boolean;
}
/**
* 获取完整的版本号
* @access public
* @return string
*/
public function getLongVersion(): string
{
if ($this->app->version()) {
return sprintf('version <comment>%s</comment>', $this->app->version());
}
return '<info>Console Tool</info>';
}
/**
* 添加指令集
* @access public
* @param array $commands
*/
public function addCommands(array $commands): void
{
foreach ($commands as $key => $command) {
if (is_subclass_of($command, Command::class)) {
// 注册指令
$this->addCommand($command, is_numeric($key) ? '' : $key);
}
}
}
/**
* 添加一个指令
* @access public
* @param string|Command $command 指令对象或者指令类名
* @param string $name 指令名 留空则自动获取
* @return Command|void
*/
public function addCommand(string|Command $command, string $name = '')
{
if ($name) {
$this->commands[$name] = $command;
return;
}
if (is_string($command)) {
$command = $this->app->invokeClass($command);
}
$command->setConsole($this);
if (!$command->isEnabled()) {
$command->setConsole(null);
return;
}
$command->setApp($this->app);
if (null === $command->getDefinition()) {
throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', $command::class));
}
$this->commands[$command->getName()] = $command;
foreach ($command->getAliases() as $alias) {
$this->commands[$alias] = $command;
}
return $command;
}
/**
* 获取指令
* @access public
* @param string $name 指令名称
* @return Command
* @throws InvalidArgumentException
*/
public function getCommand(string $name): Command
{
if (!isset($this->commands[$name])) {
throw new InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
}
$command = $this->commands[$name];
if (is_string($command)) {
$command = $this->app->invokeClass($command);
/** @var Command $command */
$command->setConsole($this);
$command->setApp($this->app);
}
if ($this->wantHelps) {
$this->wantHelps = false;
/** @var HelpCommand $helpCommand */
$helpCommand = $this->getCommand('help');
$helpCommand->setCommand($command);
return $helpCommand;
}
return $command;
}
/**
* 某个指令是否存在
* @access public
* @param string $name 指令名称
* @return bool
*/
public function hasCommand(string $name): bool
{
return isset($this->commands[$name]);
}
/**
* 获取所有的命名空间
* @access public
* @return array
*/
public function getNamespaces(): array
{
$namespaces = [];
foreach ($this->commands as $key => $command) {
if (is_string($command)) {
$namespaces = array_merge($namespaces, $this->extractAllNamespaces($key));
} else {
$namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
foreach ($command->getAliases() as $alias) {
$namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
}
}
}
return array_values(array_unique(array_filter($namespaces)));
}
/**
* 查找注册命名空间中的名称或缩写。
* @access public
* @param string $namespace
* @return string
* @throws InvalidArgumentException
*/
public function findNamespace(string $namespace): string
{
$allNamespaces = $this->getNamespaces();
$expr = preg_replace_callback('{([^:]+|)}', function ($matches) {
return preg_quote($matches[1]) . '[^:]*';
}, $namespace);
$namespaces = preg_grep('{^' . $expr . '}', $allNamespaces);
if (empty($namespaces)) {
$message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
if (1 == count($alternatives)) {
$message .= "\n\nDid you mean this?\n ";
} else {
$message .= "\n\nDid you mean one of these?\n ";
}
$message .= implode("\n ", $alternatives);
}
throw new InvalidArgumentException($message);
}
$exact = in_array($namespace, $namespaces, true);
if (count($namespaces) > 1 && !$exact) {
throw new InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))));
}
return $exact ? $namespace : reset($namespaces);
}
/**
* 查找指令
* @access public
* @param string $name 名称或者别名
* @return Command
* @throws InvalidArgumentException
*/
public function find(string $name): Command
{
$allCommands = array_keys($this->commands);
$expr = preg_replace_callback('{([^:]+|)}', function ($matches) {
return preg_quote($matches[1]) . '[^:]*';
}, $name);
$commands = preg_grep('{^' . $expr . '}', $allCommands);
if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) {
if (false !== $pos = strrpos($name, ':')) {
$this->findNamespace(substr($name, 0, $pos));
}
$message = sprintf('Command "%s" is not defined.', $name);
if ($alternatives = $this->findAlternatives($name, $allCommands)) {
if (1 == count($alternatives)) {
$message .= "\n\nDid you mean this?\n ";
} else {
$message .= "\n\nDid you mean one of these?\n ";
}
$message .= implode("\n ", $alternatives);
}
throw new InvalidArgumentException($message);
}
$exact = in_array($name, $commands, true);
if (count($commands) > 1 && !$exact) {
$suggestions = $this->getAbbreviationSuggestions(array_values($commands));
throw new InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions));
}
return $this->getCommand($exact ? $name : reset($commands));
}
/**
* 获取所有的指令
* @access public
* @param string $namespace 命名空间
* @return Command[]
* @api
*/
public function all(string $namespace = null): array
{
if (null === $namespace) {
return $this->commands;
}
$commands = [];
foreach ($this->commands as $name => $command) {
if ($this->extractNamespace($name, substr_count($namespace, ':') + 1) === $namespace) {
$commands[$name] = $command;
}
}
return $commands;
}
/**
* 配置基于用户的参数和选项的输入和输出实例。
* @access protected
* @param Input $input 输入实例
* @param Output $output 输出实例
*/
protected function configureIO(Input $input, Output $output): void
{
if (true === $input->hasParameterOption(['--ansi'])) {
$output->setDecorated(true);
} elseif (true === $input->hasParameterOption(['--no-ansi'])) {
$output->setDecorated(false);
}
if (true === $input->hasParameterOption(['--no-interaction', '-n'])) {
$input->setInteractive(false);
}
if (true === $input->hasParameterOption(['--quiet', '-q'])) {
$output->setVerbosity(Output::VERBOSITY_QUIET);
} elseif ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) {
$output->setVerbosity(Output::VERBOSITY_DEBUG);
} elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) {
$output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE);
} elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) {
$output->setVerbosity(Output::VERBOSITY_VERBOSE);
}
}
/**
* 执行指令
* @access protected
* @param Command $command 指令实例
* @param Input $input 输入实例
* @param Output $output 输出实例
* @return int
* @throws \Exception
*/
protected function doRunCommand(Command $command, Input $input, Output $output)
{
return $command->run($input, $output);
}
/**
* 获取指令的基础名称
* @access protected
* @param Input $input
* @return string
*/
protected function getCommandName(Input $input): string
{
return $input->getFirstArgument() ?: '';
}
/**
* 获取默认输入定义
* @access protected
* @return InputDefinition
*/
protected function getDefaultInputDefinition(): InputDefinition
{
return new InputDefinition([
new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this console version'),
new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
]);
}
/**
* 获取可能的建议
* @access private
* @param array $abbrevs
* @return string
*/
private function getAbbreviationSuggestions(array $abbrevs): string
{
return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
}
/**
* 返回命名空间部分
* @access public
* @param string $name 指令
* @param int $limit 部分的命名空间的最大数量
* @return string
*/
public function extractNamespace(string $name, int $limit = 0): string
{
$parts = explode(':', $name);
array_pop($parts);
return implode(':', 0 === $limit ? $parts : array_slice($parts, 0, $limit));
}
/**
* 查找可替代的建议
* @access private
* @param string $name
* @param array|\Traversable $collection
* @return array
*/
private function findAlternatives(string $name, array|\Traversable $collection): array
{
$threshold = 1e3;
$alternatives = [];
$collectionParts = [];
foreach ($collection as $item) {
$collectionParts[$item] = explode(':', $item);
}
foreach (explode(':', $name) as $i => $subname) {
foreach ($collectionParts as $collectionName => $parts) {
$exists = isset($alternatives[$collectionName]);
if (!isset($parts[$i]) && $exists) {
$alternatives[$collectionName] += $threshold;
continue;
} elseif (!isset($parts[$i])) {
continue;
}
$lev = levenshtein($subname, $parts[$i]);
if ($lev <= strlen($subname) / 3 || '' !== $subname && str_contains($parts[$i], $subname)) {
$alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
} elseif ($exists) {
$alternatives[$collectionName] += $threshold;
}
}
}
foreach ($collection as $item) {
$lev = levenshtein($name, $item);
if ($lev <= strlen($name) / 3 || str_contains($item, $name)) {
$alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
}
}
$alternatives = array_filter($alternatives, function ($lev) use ($threshold) {
return $lev < 2 * $threshold;
});
asort($alternatives);
return array_keys($alternatives);
}
/**
* 返回所有的命名空间
* @access private
* @param string $name
* @return array
*/
private function extractAllNamespaces(string $name): array
{
$parts = explode(':', $name, -1);
$namespaces = [];
foreach ($parts as $part) {
if (count($namespaces)) {
$namespaces[] = end($namespaces) . ':' . $part;
} else {
$namespaces[] = $part;
}
}
return $namespaces;
}
}

View File

@@ -0,0 +1,558 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think;
use ArrayAccess;
use ArrayIterator;
use Closure;
use Countable;
use InvalidArgumentException;
use IteratorAggregate;
use Psr\Container\ContainerInterface;
use ReflectionClass;
use ReflectionException;
use ReflectionNamedType;
use ReflectionFunction;
use ReflectionFunctionAbstract;
use ReflectionMethod;
use think\exception\ClassNotFoundException;
use think\exception\FuncNotFoundException;
use think\helper\Str;
use Traversable;
/**
* 容器管理类 支持PSR-11
*/
class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, Countable
{
/**
* 容器对象实例
* @var Container|Closure
*/
protected static $instance;
/**
* 容器中的对象实例
* @var array
*/
protected $instances = [];
/**
* 容器绑定标识
* @var array
*/
protected $bind = [];
/**
* 容器回调
* @var array
*/
protected $invokeCallback = [];
/**
* 获取当前容器的实例(单例)
* @access public
* @return static
*/
public static function getInstance()
{
if (is_null(static::$instance)) {
static::$instance = new static;
}
if (static::$instance instanceof Closure) {
return (static::$instance)();
}
return static::$instance;
}
/**
* 设置当前容器的实例
* @access public
* @param object|Closure $instance
* @return void
*/
public static function setInstance($instance): void
{
static::$instance = $instance;
}
/**
* 注册一个容器对象回调
*
* @param string|Closure $abstract
* @param Closure|null $callback
* @return void
*/
public function resolving(string|Closure $abstract, Closure $callback = null): void
{
if ($abstract instanceof Closure) {
$this->invokeCallback['*'][] = $abstract;
return;
}
$abstract = $this->getAlias($abstract);
$this->invokeCallback[$abstract][] = $callback;
}
/**
* 获取容器中的对象实例 不存在则创建
* @template T
* @param string|class-string<T> $abstract 类名或者标识
* @param array $vars 变量
* @param bool $newInstance 是否每次创建新的实例
* @return T|object
*/
public static function pull(string $abstract, array $vars = [], bool $newInstance = false)
{
return static::getInstance()->make($abstract, $vars, $newInstance);
}
/**
* 获取容器中的对象实例
* @template T
* @param string|class-string<T> $abstract 类名或者标识
* @return T|object
*/
public function get(string $abstract)
{
if ($this->has($abstract)) {
return $this->make($abstract);
}
throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract);
}
/**
* 绑定一个类、闭包、实例、接口实现到容器
* @access public
* @param string|array $abstract 类标识、接口
* @param mixed $concrete 要绑定的类、闭包或者实例
* @return $this
*/
public function bind(string|array $abstract, $concrete = null)
{
if (is_array($abstract)) {
foreach ($abstract as $key => $val) {
$this->bind($key, $val);
}
} elseif ($concrete instanceof Closure) {
$this->bind[$abstract] = $concrete;
} elseif (is_object($concrete)) {
$this->instance($abstract, $concrete);
} else {
$abstract = $this->getAlias($abstract);
if ($abstract != $concrete) {
$this->bind[$abstract] = $concrete;
}
}
return $this;
}
/**
* 根据别名获取真实类名
* @param string $abstract
* @return string
*/
public function getAlias(string $abstract): string
{
if (isset($this->bind[$abstract])) {
$bind = $this->bind[$abstract];
if (is_string($bind)) {
return $this->getAlias($bind);
}
}
return $abstract;
}
/**
* 绑定一个类实例到容器
* @access public
* @param string $abstract 类名或者标识
* @param object $instance 类的实例
* @return $this
*/
public function instance(string $abstract, $instance)
{
$abstract = $this->getAlias($abstract);
$this->instances[$abstract] = $instance;
return $this;
}
/**
* 判断容器中是否存在类及标识
* @access public
* @param string $abstract 类名或者标识
* @return bool
*/
public function bound(string $abstract): bool
{
return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
}
/**
* 判断容器中是否存在类及标识
* @access public
* @param string $name 类名或者标识
* @return bool
*/
public function has(string $name): bool
{
return $this->bound($name);
}
/**
* 判断容器中是否存在对象实例
* @access public
* @param string $abstract 类名或者标识
* @return bool
*/
public function exists(string $abstract): bool
{
$abstract = $this->getAlias($abstract);
return isset($this->instances[$abstract]);
}
/**
* 创建类的实例 已经存在则直接获取
* @template T
* @param string|class-string<T> $abstract 类名或者标识
* @param array $vars 变量
* @param bool $newInstance 是否每次创建新的实例
* @return T|object
*/
public function make(string $abstract, array $vars = [], bool $newInstance = false)
{
$abstract = $this->getAlias($abstract);
if (isset($this->instances[$abstract]) && !$newInstance) {
return $this->instances[$abstract];
}
if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
$object = $this->invokeFunction($this->bind[$abstract], $vars);
} else {
$object = $this->invokeClass($abstract, $vars);
}
if (!$newInstance) {
$this->instances[$abstract] = $object;
}
return $object;
}
/**
* 删除容器中的对象实例
* @access public
* @param string $name 类名或者标识
* @return void
*/
public function delete(string $name)
{
$name = $this->getAlias($name);
if (isset($this->instances[$name])) {
unset($this->instances[$name]);
}
}
/**
* 执行函数或者闭包方法 支持参数调用
* @access public
* @param string|Closure $function 函数或者闭包
* @param array $vars 参数
* @return mixed
*/
public function invokeFunction(string|Closure $function, array $vars = [])
{
try {
$reflect = new ReflectionFunction($function);
} catch (ReflectionException $e) {
throw new FuncNotFoundException("function not exists: {$function}()", $function, $e);
}
$args = $this->bindParams($reflect, $vars);
return $function(...$args);
}
/**
* 调用反射执行类的方法 支持参数绑定
* @access public
* @param mixed $method 方法
* @param array $vars 参数
* @param bool $accessible 设置是否可访问
* @return mixed
*/
public function invokeMethod($method, array $vars = [], bool $accessible = false)
{
if (is_array($method)) {
[$class, $method] = $method;
$class = is_object($class) ? $class : $this->invokeClass($class);
} else {
// 静态方法
[$class, $method] = explode('::', $method);
}
try {
$reflect = new ReflectionMethod($class, $method);
} catch (ReflectionException $e) {
$class = is_object($class) ? $class::class : $class;
throw new FuncNotFoundException('method not exists: ' . $class . '::' . $method . '()', "{$class}::{$method}", $e);
}
$args = $this->bindParams($reflect, $vars);
if ($accessible) {
$reflect->setAccessible($accessible);
}
return $reflect->invokeArgs(is_object($class) ? $class : null, $args);
}
/**
* 调用反射执行类的方法 支持参数绑定
* @access public
* @param object $instance 对象实例
* @param mixed $reflect 反射类
* @param array $vars 参数
* @return mixed
*/
public function invokeReflectMethod($instance, $reflect, array $vars = [])
{
$args = $this->bindParams($reflect, $vars);
return $reflect->invokeArgs($instance, $args);
}
/**
* 调用反射执行callable 支持参数绑定
* @access public
* @param mixed $callable
* @param array $vars 参数
* @param bool $accessible 设置是否可访问
* @return mixed
*/
public function invoke($callable, array $vars = [], bool $accessible = false)
{
if ($callable instanceof Closure) {
return $this->invokeFunction($callable, $vars);
} elseif (is_string($callable) && !str_contains($callable, '::')) {
return $this->invokeFunction($callable, $vars);
} else {
return $this->invokeMethod($callable, $vars, $accessible);
}
}
/**
* 调用反射执行类的实例化 支持依赖注入
* @access public
* @param string $class 类名
* @param array $vars 参数
* @return mixed
*/
public function invokeClass(string $class, array $vars = [])
{
try {
$reflect = new ReflectionClass($class);
} catch (ReflectionException $e) {
throw new ClassNotFoundException('class not exists: ' . $class, $class, $e);
}
if ($reflect->hasMethod('__make')) {
$method = $reflect->getMethod('__make');
if ($method->isPublic() && $method->isStatic()) {
$args = $this->bindParams($method, $vars);
$object = $method->invokeArgs(null, $args);
$this->invokeAfter($class, $object);
return $object;
}
}
$constructor = $reflect->getConstructor();
$args = $constructor ? $this->bindParams($constructor, $vars) : [];
$object = $reflect->newInstanceArgs($args);
$this->invokeAfter($class, $object);
return $object;
}
/**
* 执行invokeClass回调
* @access protected
* @param string $class 对象类名
* @param object $object 容器对象实例
* @return void
*/
protected function invokeAfter(string $class, $object): void
{
if (isset($this->invokeCallback['*'])) {
foreach ($this->invokeCallback['*'] as $callback) {
$callback($object, $this);
}
}
if (isset($this->invokeCallback[$class])) {
foreach ($this->invokeCallback[$class] as $callback) {
$callback($object, $this);
}
}
}
/**
* 绑定参数
* @access protected
* @param ReflectionFunctionAbstract $reflect 反射类
* @param array $vars 参数
* @return array
*/
protected function bindParams(ReflectionFunctionAbstract $reflect, array $vars = []): array
{
if ($reflect->getNumberOfParameters() == 0) {
return [];
}
// 判断数组类型 数字数组时按顺序绑定参数
reset($vars);
$type = key($vars) === 0 ? 1 : 0;
$params = $reflect->getParameters();
$args = [];
foreach ($params as $param) {
$name = $param->getName();
$lowerName = Str::snake($name);
$reflectionType = $param->getType();
if ($param->isVariadic()) {
return array_merge($args, array_values($vars));
} elseif ($reflectionType && $reflectionType instanceof ReflectionNamedType && $reflectionType->isBuiltin() === false) {
$args[] = $this->getObjectParam($reflectionType->getName(), $vars);
} elseif (1 == $type && !empty($vars)) {
$args[] = array_shift($vars);
} elseif (0 == $type && array_key_exists($name, $vars)) {
$args[] = $vars[$name];
} elseif (0 == $type && array_key_exists($lowerName, $vars)) {
$args[] = $vars[$lowerName];
} elseif ($param->isDefaultValueAvailable()) {
$args[] = $param->getDefaultValue();
} else {
throw new InvalidArgumentException('method param miss:' . $name);
}
}
return $args;
}
/**
* 创建工厂对象实例
* @param string $name 工厂类名
* @param string $namespace 默认命名空间
* @param array $args
* @return mixed
* @deprecated
* @access public
*/
public static function factory(string $name, string $namespace = '', ...$args)
{
$class = str_contains($name, '\\') ? $name : $namespace . ucwords($name);
return Container::getInstance()->invokeClass($class, $args);
}
/**
* 获取对象类型的参数值
* @access protected
* @param string $className 类名
* @param array $vars 参数
* @return mixed
*/
protected function getObjectParam(string $className, array &$vars)
{
$array = $vars;
$value = array_shift($array);
if ($value instanceof $className) {
$result = $value;
array_shift($vars);
} else {
$result = $this->make($className);
}
return $result;
}
public function __set($name, $value)
{
$this->bind($name, $value);
}
public function __get($name)
{
return $this->get($name);
}
public function __isset($name): bool
{
return $this->exists($name);
}
public function __unset($name)
{
$this->delete($name);
}
public function offsetExists(mixed $key): bool
{
return $this->exists($key);
}
public function offsetGet(mixed $key): mixed
{
return $this->make($key);
}
public function offsetSet(mixed $key, mixed $value): void
{
$this->bind($key, $value);
}
public function offsetUnset(mixed $key): void
{
$this->delete($key);
}
//Countable
public function count(): int
{
return count($this->instances);
}
//IteratorAggregate
public function getIterator(): Traversable
{
return new ArrayIterator($this->instances);
}
}

View File

@@ -0,0 +1,220 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think;
use DateTimeInterface;
/**
* Cookie管理类
* @package think
*/
class Cookie
{
/**
* 配置参数
* @var array
*/
protected $config = [
// cookie 保存时间
'expire' => 0,
// cookie 保存路径
'path' => '/',
// cookie 有效域名
'domain' => '',
// cookie 启用安全传输
'secure' => false,
// httponly设置
'httponly' => false,
// samesite 设置,支持 'strict' 'lax'
'samesite' => '',
];
/**
* Cookie写入数据
* @var array
*/
protected $cookie = [];
/**
* 构造方法
* @access public
*/
public function __construct(protected Request $request, array $config = [])
{
$this->config = array_merge($this->config, array_change_key_case($config));
}
public static function __make(Request $request, Config $config)
{
return new static($request, $config->get('cookie'));
}
/**
* 获取cookie
* @access public
* @param mixed $name 数据名称
* @param string $default 默认值
* @return mixed
*/
public function get(string $name = '', $default = null)
{
return $this->request->cookie($name, $default);
}
/**
* 是否存在Cookie参数
* @access public
* @param string $name 变量名
* @return bool
*/
public function has(string $name): bool
{
return $this->request->has($name, 'cookie');
}
/**
* Cookie 设置
*
* @access public
* @param string $name cookie名称
* @param string $value cookie值
* @param mixed $option 可选参数
* @return void
*/
public function set(string $name, string $value, $option = null): void
{
// 参数设置(会覆盖黙认设置)
if (!is_null($option)) {
if (is_numeric($option) || $option instanceof DateTimeInterface) {
$option = ['expire' => $option];
}
$config = array_merge($this->config, array_change_key_case($option));
} else {
$config = $this->config;
}
if ($config['expire'] instanceof DateTimeInterface) {
$expire = $config['expire']->getTimestamp();
} else {
$expire = !empty($config['expire']) ? time() + intval($config['expire']) : 0;
}
$this->setCookie($name, $value, $expire, $config);
}
/**
* Cookie 保存
*
* @access public
* @param string $name cookie名称
* @param string $value cookie值
* @param int $expire 有效期
* @param array $option 可选参数
* @return void
*/
protected function setCookie(string $name, string $value, int $expire, array $option = []): void
{
$this->cookie[$name] = [$value, $expire, $option];
}
/**
* 永久保存Cookie数据
* @access public
* @param string $name cookie名称
* @param string $value cookie值
* @param mixed $option 可选参数 可能会是 null|integer|string
* @return void
*/
public function forever(string $name, string $value = '', $option = null): void
{
if (is_null($option) || is_numeric($option)) {
$option = [];
}
$option['expire'] = 315360000;
$this->set($name, $value, $option);
}
/**
* Cookie删除
* @access public
* @param string $name cookie名称
* @param array $options cookie参数
* @return void
*/
public function delete(string $name, array $options = []): void
{
$config = array_merge($this->config, array_change_key_case($options));
$this->setCookie($name, '', time() - 3600, $config);
}
/**
* 获取cookie保存数据
* @access public
* @return array
*/
public function getCookie(): array
{
return $this->cookie;
}
/**
* 保存Cookie
* @access public
* @return void
*/
public function save(): void
{
foreach ($this->cookie as $name => $val) {
[$value, $expire, $option] = $val;
$this->saveCookie(
$name,
$value,
$expire,
$option['path'],
$option['domain'],
$option['secure'] ? true : false,
$option['httponly'] ? true : false,
$option['samesite']
);
}
}
/**
* 保存Cookie
* @access public
* @param string $name cookie名称
* @param string $value cookie值
* @param int $expire cookie过期时间
* @param string $path 有效的服务器路径
* @param string $domain 有效域名/子域名
* @param bool $secure 是否仅仅通过HTTPS
* @param bool $httponly 仅可通过HTTP访问
* @param string $samesite 防止CSRF攻击和用户追踪
* @return void
*/
protected function saveCookie(string $name, string $value, int $expire, string $path, string $domain, bool $secure, bool $httponly, string $samesite): void
{
setcookie($name, $value, [
'expires' => $expire,
'path' => $path,
'domain' => $domain,
'secure' => $secure,
'httponly' => $httponly,
'samesite' => $samesite,
]);
}
}

View File

@@ -0,0 +1,117 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think;
/**
* 数据库管理类
* @package think
* @property Config $config
*/
class Db extends DbManager
{
/**
* @param Event $event
* @param Config $config
* @param Log $log
* @param Cache $cache
* @return Db
* @codeCoverageIgnore
*/
public static function __make(Event $event, Config $config, Log $log, Cache $cache)
{
$db = new static();
$db->setConfig($config);
$db->setEvent($event);
$db->setLog($log);
$store = $db->getConfig('cache_store');
$db->setCache($cache->store($store));
$db->triggerSql();
return $db;
}
/**
* 注入模型对象
* @access public
* @return void
*/
protected function modelMaker(): void
{
}
/**
* 设置配置对象
* @access public
* @param Config $config 配置对象
* @return void
*/
public function setConfig($config): void
{
$this->config = $config;
}
/**
* 获取配置参数
* @access public
* @param string $name 配置参数
* @param mixed $default 默认值
* @return mixed
*/
public function getConfig(string $name = '', $default = null)
{
if ('' !== $name) {
return $this->config->get('database.' . $name, $default);
}
return $this->config->get('database', []);
}
/**
* 设置Event对象
* @param Event $event
*/
public function setEvent(Event $event): void
{
$this->event = $event;
}
/**
* 注册回调方法
* @access public
* @param string $event 事件名
* @param callable $callback 回调方法
* @return void
*/
public function event(string $event, callable $callback): void
{
if ($this->event) {
$this->event->listen('db.' . $event, $callback);
}
}
/**
* 触发事件
* @access public
* @param string $event 事件名
* @param mixed $params 传入参数
* @param bool $once
* @return mixed
*/
public function trigger(string $event, $params = null, bool $once = false)
{
if ($this->event) {
return $this->event->trigger('db.' . $event, $params, $once);
}
}
}

View File

@@ -0,0 +1,199 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think;
use ArrayAccess;
/**
* Env管理类
* @package think
*/
class Env implements ArrayAccess
{
/**
* 环境变量数据
* @var array
*/
protected $data = [];
/**
* 数据转换映射
* @var array
*/
protected $convert = [
'true' => true,
'false' => false,
'off' => false,
'on' => true,
];
public function __construct()
{
$this->data = $_ENV;
}
/**
* 读取环境变量定义文件
* @access public
* @param string $file 环境变量定义文件
* @return void
*/
public function load(string $file): void
{
$env = parse_ini_file($file, true, INI_SCANNER_RAW) ?: [];
$this->set($env);
}
/**
* 获取环境变量值
* @access public
* @param string $name 环境变量名
* @param mixed $default 默认值
* @return mixed
*/
public function get(string $name = null, $default = null)
{
if (is_null($name)) {
return $this->data;
}
$name = strtoupper(str_replace('.', '_', $name));
if (isset($this->data[$name])) {
$result = $this->data[$name];
if (is_string($result) && isset($this->convert[$result])) {
return $this->convert[$result];
}
return $result;
}
return $this->getEnv($name, $default);
}
protected function getEnv(string $name, $default = null)
{
$result = getenv('PHP_' . $name);
if (false === $result) {
return $default;
}
if (isset($this->convert[$result])) {
$result = $this->convert[$result];
}
if (!isset($this->data[$name])) {
$this->data[$name] = $result;
}
return $result;
}
/**
* 设置环境变量值
* @access public
* @param string|array $env 环境变量
* @param mixed $value 值
* @return void
*/
public function set($env, $value = null): void
{
if (is_array($env)) {
$env = array_change_key_case($env, CASE_UPPER);
foreach ($env as $key => $val) {
if (is_array($val)) {
foreach ($val as $k => $v) {
if (is_string($k)) {
$this->data[$key . '_' . strtoupper($k)] = $v;
} else {
$this->data[$key][$k] = $v;
}
}
} else {
$this->data[$key] = $val;
}
}
} else {
$name = strtoupper(str_replace('.', '_', $env));
$this->data[$name] = $value;
}
}
/**
* 检测是否存在环境变量
* @access public
* @param string $name 参数名
* @return bool
*/
public function has(string $name): bool
{
return !is_null($this->get($name));
}
/**
* 设置环境变量
* @access public
* @param string $name 参数名
* @param mixed $value 值
*/
public function __set(string $name, $value): void
{
$this->set($name, $value);
}
/**
* 获取环境变量
* @access public
* @param string $name 参数名
* @return mixed
*/
public function __get(string $name)
{
return $this->get($name);
}
/**
* 检测是否存在环境变量
* @access public
* @param string $name 参数名
* @return bool
*/
public function __isset(string $name): bool
{
return $this->has($name);
}
// ArrayAccess
public function offsetSet(mixed $name, mixed $value): void
{
$this->set($name, $value);
}
public function offsetExists(mixed $name): bool
{
return $this->__isset($name);
}
public function offsetUnset(mixed $name): void
{
throw new Exception('not support: unset');
}
public function offsetGet(mixed $name): mixed
{
return $this->get($name);
}
}

View File

@@ -0,0 +1,271 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think;
use ReflectionClass;
use ReflectionMethod;
/**
* 事件管理类
* @package think
*/
class Event
{
/**
* 监听者
* @var array
*/
protected $listener = [];
/**
* 事件别名
* @var array
*/
protected $bind = [
'AppInit' => event\AppInit::class,
'HttpRun' => event\HttpRun::class,
'HttpEnd' => event\HttpEnd::class,
'RouteLoaded' => event\RouteLoaded::class,
'LogWrite' => event\LogWrite::class,
'LogRecord' => event\LogRecord::class,
];
/**
* 应用对象
* @var App
*/
protected $app;
public function __construct(App $app)
{
$this->app = $app;
}
/**
* 批量注册事件监听
* @access public
* @param array $events 事件定义
* @return $this
*/
public function listenEvents(array $events)
{
foreach ($events as $event => $listeners) {
if (isset($this->bind[$event])) {
$event = $this->bind[$event];
}
$this->listener[$event] = array_merge($this->listener[$event] ?? [], $listeners);
}
return $this;
}
/**
* 注册事件监听
* @access public
* @param string $event 事件名称
* @param mixed $listener 监听操作(或者类名)
* @param bool $first 是否优先执行
* @return $this
*/
public function listen(string $event, $listener, bool $first = false)
{
if (isset($this->bind[$event])) {
$event = $this->bind[$event];
}
if ($first && isset($this->listener[$event])) {
array_unshift($this->listener[$event], $listener);
} else {
$this->listener[$event][] = $listener;
}
return $this;
}
/**
* 是否存在事件监听
* @access public
* @param string $event 事件名称
* @return bool
*/
public function hasListener(string $event): bool
{
if (isset($this->bind[$event])) {
$event = $this->bind[$event];
}
return isset($this->listener[$event]);
}
/**
* 移除事件监听
* @access public
* @param string $event 事件名称
* @return void
*/
public function remove(string $event): void
{
if (isset($this->bind[$event])) {
$event = $this->bind[$event];
}
unset($this->listener[$event]);
}
/**
* 指定事件别名标识 便于调用
* @access public
* @param array $events 事件别名
* @return $this
*/
public function bind(array $events)
{
$this->bind = array_merge($this->bind, $events);
return $this;
}
/**
* 注册事件订阅者
* @access public
* @param mixed $subscriber 订阅者
* @return $this
*/
public function subscribe($subscriber)
{
$subscribers = (array) $subscriber;
foreach ($subscribers as $subscriber) {
if (is_string($subscriber)) {
$subscriber = $this->app->make($subscriber);
}
if (method_exists($subscriber, 'subscribe')) {
// 手动订阅
$subscriber->subscribe($this);
} else {
// 智能订阅
$this->observe($subscriber);
}
}
return $this;
}
/**
* 自动注册事件观察者
* @access public
* @param string|object $observer 观察者
* @param null|string $prefix 事件名前缀
* @return $this
*/
public function observe($observer, string $prefix = '')
{
if (is_string($observer)) {
$observer = $this->app->make($observer);
}
$reflect = new ReflectionClass($observer);
$methods = $reflect->getMethods(ReflectionMethod::IS_PUBLIC);
if (empty($prefix) && $reflect->hasProperty('eventPrefix')) {
$reflectProperty = $reflect->getProperty('eventPrefix');
$reflectProperty->setAccessible(true);
$prefix = $reflectProperty->getValue($observer);
}
foreach ($methods as $method) {
$name = $method->getName();
if (str_starts_with($name, 'on')) {
$this->listen($prefix . substr($name, 2), [$observer, $name]);
}
}
return $this;
}
/**
* 触发事件
* @access public
* @param string|object $event 事件名称
* @param mixed $params 传入参数
* @param bool $once 只获取一个有效返回值
* @return mixed
*/
public function trigger($event, $params = null, bool $once = false)
{
if (is_object($event)) {
$params = $event;
$event = $event::class;
}
if (isset($this->bind[$event])) {
$event = $this->bind[$event];
}
$result = [];
$listeners = $this->listener[$event] ?? [];
if (str_contains($event, '.')) {
[$prefix, $event] = explode('.', $event, 2);
if (isset($this->listener[$prefix . '.*'])) {
$listeners = array_merge($listeners, $this->listener[$prefix . '.*']);
}
}
$listeners = array_unique($listeners, SORT_REGULAR);
foreach ($listeners as $key => $listener) {
$result[$key] = $this->dispatch($listener, $params);
if (false === $result[$key] || (!is_null($result[$key]) && $once)) {
break;
}
}
return $once ? end($result) : $result;
}
/**
* 触发事件(只获取一个有效返回值)
* @param $event
* @param null $params
* @return mixed
*/
public function until($event, $params = null)
{
return $this->trigger($event, $params, true);
}
/**
* 执行事件调度
* @access protected
* @param mixed $event 事件方法
* @param mixed $params 参数
* @return mixed
*/
protected function dispatch($event, $params = null)
{
if (!is_string($event)) {
$call = $event;
} elseif (str_contains($event, '::')) {
$call = $event;
} else {
$obj = $this->app->make($event);
$call = [$obj, 'handle'];
}
return $this->app->invoke($call, [$params]);
}
}

View File

@@ -0,0 +1,59 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think;
/**
* 异常基础类
* @package think
*/
class Exception extends \Exception
{
/**
* 保存异常页面显示的额外Debug数据
* @var array
*/
protected $data = [];
/**
* 设置异常额外的Debug数据
* 数据将会显示为下面的格式
*
* Exception Data
* --------------------------------------------------
* Label 1
* key1 value1
* key2 value2
* Label 2
* key1 value1
* key2 value2
*
* @access protected
* @param string $label 数据分类,用于异常页面显示
* @param array $data 需要显示的数据,必须为关联数组
*/
final protected function setData(string $label, array $data)
{
$this->data[$label] = $data;
}
/**
* 获取异常额外Debug数据
* 主要用于输出到异常页面便于调试
* @access public
* @return array 由setData设置的Debug数据
*/
final public function getData(): array
{
return $this->data;
}
}

View File

@@ -0,0 +1,99 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think;
/**
* Facade管理类
*/
class Facade
{
/**
* 始终创建新的对象实例
* @var bool
*/
protected static $alwaysNewInstance;
/**
* 创建Facade实例
* @static
* @access protected
* @param string $class 类名或标识
* @param array $args 变量
* @param bool $newInstance 是否每次创建新的实例
* @return object
*/
protected static function createFacade(string $class = '', array $args = [], bool $newInstance = false)
{
$class = $class ?: static::class;
$facadeClass = static::getFacadeClass();
if ($facadeClass) {
$class = $facadeClass;
}
if (static::$alwaysNewInstance) {
$newInstance = true;
}
return Container::getInstance()->make($class, $args, $newInstance);
}
/**
* 获取当前Facade对应类名
* @access protected
* @return string
*/
protected static function getFacadeClass()
{
}
/**
* 带参数实例化当前Facade类
* @access public
* @return object
*/
public static function instance(...$args)
{
if (__CLASS__ != static::class) {
return self::createFacade('', $args);
}
}
/**
* 调用类的实例
* @access public
* @param string $class 类名或者标识
* @param array|true $args 变量
* @param bool $newInstance 是否每次创建新的实例
* @return object
*/
public static function make(string $class, $args = [], $newInstance = false)
{
if (__CLASS__ != static::class) {
return self::__callStatic('make', func_get_args());
}
if (true === $args) {
// 总是创建新的实例化对象
$newInstance = true;
$args = [];
}
return self::createFacade($class, $args, $newInstance);
}
// 调用实际类的方法
public static function __callStatic($method, $params)
{
return call_user_func_array([static::createFacade(), $method], $params);
}
}

View File

@@ -0,0 +1,198 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think;
use SplFileInfo;
use Closure;
use think\exception\FileException;
/**
* 文件上传类
* @package think
*/
class File extends SplFileInfo
{
/**
* 文件hash规则
* @var array
*/
protected $hash = [];
protected $hashName;
/**
* 保存的文件后缀
* @var string
*/
protected $extension;
public function __construct(string $path, bool $checkPath = true)
{
if ($checkPath && !is_file($path)) {
throw new FileException(sprintf('The file "%s" does not exist', $path));
}
parent::__construct($path);
}
/**
* 获取文件的哈希散列值
* @access public
* @param string $type
* @return string
*/
public function hash(string $type = 'sha1'): string
{
if (!isset($this->hash[$type])) {
$this->hash[$type] = hash_file($type, $this->getPathname());
}
return $this->hash[$type];
}
/**
* 获取文件的MD5值
* @access public
* @return string
*/
public function md5(): string
{
return $this->hash('md5');
}
/**
* 获取文件的SHA1值
* @access public
* @return string
*/
public function sha1(): string
{
return $this->hash('sha1');
}
/**
* 获取文件类型信息
* @access public
* @return string
*/
public function getMime(): string
{
$finfo = finfo_open(FILEINFO_MIME_TYPE);
return finfo_file($finfo, $this->getPathname());
}
/**
* 移动文件
* @access public
* @param string $directory 保存路径
* @param string|null $name 保存的文件名
* @return File
*/
public function move(string $directory, string $name = null): File
{
$target = $this->getTargetFile($directory, $name);
set_error_handler(function ($type, $msg) use (&$error) {
$error = $msg;
});
$renamed = rename($this->getPathname(), (string) $target);
restore_error_handler();
if (!$renamed) {
throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error)));
}
@chmod((string) $target, 0666 & ~umask());
return $target;
}
/**
* 实例化一个新文件
* @param string $directory
* @param null|string $name
* @return File
*/
protected function getTargetFile(string $directory, string $name = null): File
{
if (!is_dir($directory)) {
if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) {
throw new FileException(sprintf('Unable to create the "%s" directory', $directory));
}
} elseif (!is_writable($directory)) {
throw new FileException(sprintf('Unable to write in the "%s" directory', $directory));
}
$target = rtrim($directory, '/\\') . \DIRECTORY_SEPARATOR . (null === $name ? $this->getBasename() : $this->getName($name));
return new self($target, false);
}
/**
* 获取文件名
* @param string $name
* @return string
*/
protected function getName(string $name): string
{
$originalName = str_replace('\\', '/', $name);
$pos = strrpos($originalName, '/');
$originalName = false === $pos ? $originalName : substr($originalName, $pos + 1);
return $originalName;
}
/**
* 文件扩展名
* @return string
*/
public function extension(): string
{
return $this->getExtension();
}
/**
* 指定保存文件的扩展名
* @param string $extension
* @return void
*/
public function setExtension(string $extension): void
{
$this->extension = $extension;
}
/**
* 自动生成文件名
* @access public
* @param string|Closure|null $rule
* @return string
*/
public function hashName(string|Closure|null $rule = null): string
{
if (!$this->hashName) {
if ($rule instanceof Closure) {
$this->hashName = call_user_func_array($rule, [$this]);
} else {
$this->hashName = match (true) {
in_array($rule, hash_algos()) && $hash = $this->hash($rule) => substr($hash, 0, 2) . DIRECTORY_SEPARATOR . substr($hash, 2),
is_callable($rule) => call_user_func($rule),
default => date('Ymd') . DIRECTORY_SEPARATOR . md5(microtime(true) . $this->getPathname()),
};
}
}
$extension = $this->extension ?? $this->extension();
return $this->hashName . ($extension ? '.' . $extension : '');
}
}

View File

@@ -0,0 +1,279 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think;
use think\event\HttpEnd;
use think\event\HttpRun;
use think\event\RouteLoaded;
use think\exception\Handle;
use Throwable;
/**
* Web应用管理类
* @package think
*/
class Http
{
/**
* 应用名称
* @var string
*/
protected $name;
/**
* 应用路径
* @var string
*/
protected $path;
/**
* 路由路径
* @var string
*/
protected $routePath;
/**
* 是否绑定应用
* @var bool
*/
protected $isBind = false;
public function __construct(protected App $app)
{
$this->routePath = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR;
}
/**
* 设置应用名称
* @access public
* @param string $name 应用名称
* @return $this
*/
public function name(string $name)
{
$this->name = $name;
return $this;
}
/**
* 获取应用名称
* @access public
* @return string
*/
public function getName(): string
{
return $this->name ?: '';
}
/**
* 设置应用目录
* @access public
* @param string $path 应用目录
* @return $this
*/
public function path(string $path)
{
if (str_ends_with($path, DIRECTORY_SEPARATOR)) {
$path .= DIRECTORY_SEPARATOR;
}
$this->path = $path;
return $this;
}
/**
* 获取应用路径
* @access public
* @return string
*/
public function getPath(): string
{
return $this->path ?: '';
}
/**
* 获取路由目录
* @access public
* @return string
*/
public function getRoutePath(): string
{
return $this->routePath;
}
/**
* 设置路由目录
* @access public
* @param string $path 路由定义目录
*/
public function setRoutePath(string $path): void
{
$this->routePath = $path;
}
/**
* 设置应用绑定
* @access public
* @param bool $bind 是否绑定
* @return $this
*/
public function setBind(bool $bind = true)
{
$this->isBind = $bind;
return $this;
}
/**
* 是否绑定应用
* @access public
* @return bool
*/
public function isBind(): bool
{
return $this->isBind;
}
/**
* 执行应用程序
* @access public
* @param Request|null $request
* @return Response
*/
public function run(Request $request = null): Response
{
//初始化
$this->initialize();
//自动创建request对象
$request = $request ?? $this->app->make('request', [], true);
$this->app->instance('request', $request);
try {
$response = $this->runWithRequest($request);
} catch (Throwable $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
}
return $response;
}
/**
* 初始化
*/
protected function initialize()
{
if (!$this->app->initialized()) {
$this->app->initialize();
}
}
/**
* 执行应用程序
* @param Request $request
* @return mixed
*/
protected function runWithRequest(Request $request)
{
// 加载全局中间件
$this->loadMiddleware();
// 监听HttpRun
$this->app->event->trigger(HttpRun::class);
return $this->app->middleware->pipeline()
->send($request)
->then(function ($request) {
return $this->dispatchToRoute($request);
});
}
protected function dispatchToRoute($request)
{
$withRoute = $this->app->config->get('app.with_route', true) ? function () {
$this->loadRoutes();
} : false;
return $this->app->route->dispatch($request, $withRoute);
}
/**
* 加载全局中间件
*/
protected function loadMiddleware(): void
{
if (is_file($this->app->getBasePath() . 'middleware.php')) {
$this->app->middleware->import(include $this->app->getBasePath() . 'middleware.php');
}
}
/**
* 加载路由
* @access protected
* @return void
*/
protected function loadRoutes(): void
{
// 加载路由定义
$routePath = $this->getRoutePath();
if (is_dir($routePath)) {
$files = glob($routePath . '*.php');
foreach ($files as $file) {
include $file;
}
}
$this->app->event->trigger(RouteLoaded::class);
}
/**
* Report the exception to the exception handler.
*
* @param Throwable $e
* @return void
*/
protected function reportException(Throwable $e)
{
$this->app->make(Handle::class)->report($e);
}
/**
* Render the exception to a response.
*
* @param Request $request
* @param Throwable $e
* @return Response
*/
protected function renderException($request, Throwable $e)
{
return $this->app->make(Handle::class)->render($request, $e);
}
/**
* HttpEnd
* @param Response $response
* @return void
*/
public function end(Response $response): void
{
$this->app->event->trigger(HttpEnd::class, $response);
//执行中间件
$this->app->middleware->end($response);
// 写入日志
$this->app->log->save();
}
}

View File

@@ -0,0 +1,271 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think;
/**
* 多语言管理类
* @package think
*/
class Lang
{
protected $app;
/**
* 配置参数
* @var array
*/
protected $config = [
// 默认语言
'default_lang' => 'zh-cn',
// 允许的语言列表
'allow_lang_list' => [],
// 是否使用Cookie记录
'use_cookie' => true,
// 扩展语言包
'extend_list' => [],
// 多语言cookie变量
'cookie_var' => 'think_lang',
// 多语言header变量
'header_var' => 'think-lang',
// 多语言自动侦测变量名
'detect_var' => 'lang',
// Accept-Language转义为对应语言包名称
'accept_language' => [
'zh-hans-cn' => 'zh-cn',
],
// 是否支持语言分组
'allow_group' => false,
];
/**
* 多语言信息
* @var array
*/
private $lang = [];
/**
* 当前语言
* @var string
*/
private $range = 'zh-cn';
/**
* 构造方法
* @access public
* @param array $config
*/
public function __construct(App $app, array $config = [])
{
$this->config = array_merge($this->config, array_change_key_case($config));
$this->range = $this->config['default_lang'];
$this->app = $app;
}
public static function __make(App $app, Config $config)
{
return new static($app, $config->get('lang'));
}
/**
* 获取当前语言配置
* @access public
* @return array
*/
public function getConfig(): array
{
return $this->config;
}
/**
* 设置当前语言
* @access public
* @param string $lang 语言
* @return void
*/
public function setLangSet(string $lang): void
{
$this->range = $lang;
}
/**
* 获取当前语言
* @access public
* @return string
*/
public function getLangSet(): string
{
return $this->range;
}
/**
* 获取默认语言
* @access public
* @return string
*/
public function defaultLangSet()
{
return $this->config['default_lang'];
}
/**
* 切换语言
* @access public
* @param string $langset 语言
* @return void
*/
public function switchLangSet(string $langset)
{
if (empty($langset)) {
return;
}
$this->setLangSet($langset);
// 加载系统语言包
$this->load([
$this->app->getThinkPath() . 'lang' . DIRECTORY_SEPARATOR . $langset . '.php',
]);
// 加载系统语言包
$files = glob($this->app->getAppPath() . 'lang' . DIRECTORY_SEPARATOR . $langset . '.*');
$this->load($files);
// 加载扩展(自定义)语言包
$list = $this->app->config->get('lang.extend_list', []);
if (isset($list[$langset])) {
$this->load($list[$langset]);
}
}
/**
* 加载语言定义(不区分大小写)
* @access public
* @param string|array $file 语言文件
* @param string $range 语言作用域
* @return array
*/
public function load($file, $range = ''): array
{
$range = $range ?: $this->range;
if (!isset($this->lang[$range])) {
$this->lang[$range] = [];
}
$lang = [];
foreach ((array) $file as $name) {
if (is_file($name)) {
$result = $this->parse($name);
$lang = array_change_key_case($result) + $lang;
}
}
if (!empty($lang)) {
$this->lang[$range] = $lang + $this->lang[$range];
}
return $this->lang[$range];
}
/**
* 解析语言文件
* @access protected
* @param string $file 语言文件名
* @return array
*/
protected function parse(string $file): array
{
$type = pathinfo($file, PATHINFO_EXTENSION);
$result = match ($type) {
'php' => include $file,
'yml','yaml'=> function_exists('yaml_parse_file') ? yaml_parse_file($file) : [],
'json' => json_decode(file_get_contents($file), true),
default => [],
};
return is_array($result) ? $result : [];
}
/**
* 判断是否存在语言定义(不区分大小写)
* @access public
* @param string|null $name 语言变量
* @param string $range 语言作用域
* @return bool
*/
public function has(string $name, string $range = ''): bool
{
$range = $range ?: $this->range;
if ($this->config['allow_group'] && str_contains($name, '.')) {
[$name1, $name2] = explode('.', $name, 2);
return isset($this->lang[$range][strtolower($name1)][$name2]);
}
return isset($this->lang[$range][strtolower($name)]);
}
/**
* 获取语言定义(不区分大小写)
* @access public
* @param string|null $name 语言变量
* @param array $vars 变量替换
* @param string $range 语言作用域
* @return mixed
*/
public function get(string $name = null, array $vars = [], string $range = '')
{
$range = $range ?: $this->range;
if (!isset($this->lang[$range])) {
$this->switchLangSet($range);
}
// 空参数返回所有定义
if (is_null($name)) {
return $this->lang[$range] ?? [];
}
if ($this->config['allow_group'] && str_contains($name, '.')) {
[$name1, $name2] = explode('.', $name, 2);
$value = $this->lang[$range][strtolower($name1)][$name2] ?? $name;
} else {
$value = $this->lang[$range][strtolower($name)] ?? $name;
}
// 变量解析
if (!empty($vars) && is_array($vars)) {
/**
* Notes:
* 为了检测的方便数字索引的判断仅仅是参数数组的第一个元素的key为数字0
* 数字索引采用的是系统的 sprintf 函数替换,用法请参考 sprintf 函数
*/
if (key($vars) === 0) {
// 数字索引解析
array_unshift($vars, $value);
$value = call_user_func_array('sprintf', $vars);
} else {
// 关联索引解析
$replace = array_keys($vars);
foreach ($replace as &$v) {
$v = "{:{$v}}";
}
$value = str_replace($replace, $vars, $value);
}
}
return $value;
}
}

View File

@@ -0,0 +1,249 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerTrait;
use Stringable;
use think\event\LogWrite;
use think\helper\Arr;
use think\log\Channel;
use think\log\ChannelSet;
/**
* 日志管理类
* @package think
* @mixin Channel
*/
class Log extends Manager implements LoggerInterface
{
use LoggerTrait;
const EMERGENCY = 'emergency';
const ALERT = 'alert';
const CRITICAL = 'critical';
const ERROR = 'error';
const WARNING = 'warning';
const NOTICE = 'notice';
const INFO = 'info';
const DEBUG = 'debug';
const SQL = 'sql';
protected $namespace = '\\think\\log\\driver\\';
/**
* 默认驱动
* @return string|null
*/
public function getDefaultDriver(): ?string
{
return $this->getConfig('default');
}
/**
* 获取日志配置
* @access public
* @param null|string $name 名称
* @param mixed $default 默认值
* @return mixed
*/
public function getConfig(string $name = null, $default = null)
{
if (!is_null($name)) {
return $this->app->config->get('log.' . $name, $default);
}
return $this->app->config->get('log');
}
/**
* 获取渠道配置
* @param string $channel
* @param string $name
* @param mixed $default
* @return array
*/
public function getChannelConfig(string $channel, string $name = null, $default = null)
{
if ($config = $this->getConfig("channels.{$channel}")) {
return Arr::get($config, $name, $default);
}
throw new InvalidArgumentException("Channel [$channel] not found.");
}
/**
* driver()的别名
* @param string|array $name 渠道名
* @return Channel|ChannelSet
*/
public function channel(string|array $name = null)
{
if (is_array($name)) {
return new ChannelSet($this, $name);
}
return $this->driver($name);
}
protected function resolveType(string $name)
{
return $this->getChannelConfig($name, 'type', 'file');
}
public function createDriver(string $name)
{
$driver = parent::createDriver($name);
$lazy = !$this->getChannelConfig($name, "realtime_write", false) && !$this->app->runningInConsole();
$allow = array_merge($this->getConfig("level", []), $this->getChannelConfig($name, "level", []));
return new Channel($name, $driver, $allow, $lazy, $this->app->event);
}
protected function resolveConfig(string $name)
{
return $this->getChannelConfig($name);
}
/**
* 清空日志信息
* @access public
* @param string|array $channel 日志通道名
* @return $this
*/
public function clear(string|array $channel = '*')
{
if ('*' == $channel) {
$channel = array_keys($this->drivers);
}
$this->channel($channel)->clear();
return $this;
}
/**
* 关闭本次请求日志写入
* @access public
* @param string|array $channel 日志通道名
* @return $this
*/
public function close(string|array $channel = '*')
{
if ('*' == $channel) {
$channel = array_keys($this->drivers);
}
$this->channel($channel)->close();
return $this;
}
/**
* 获取日志信息
* @access public
* @param string $channel 日志通道名
* @return array
*/
public function getLog(string $channel = null): array
{
return $this->channel($channel)->getLog();
}
/**
* 保存日志信息
* @access public
* @return bool
*/
public function save(): bool
{
/** @var Channel $channel */
foreach ($this->drivers as $channel) {
$channel->save();
}
return true;
}
/**
* 记录日志信息
* @access public
* @param mixed $msg 日志信息
* @param string $type 日志级别
* @param array $context 替换内容
* @param bool $lazy
* @return $this
*/
public function record($msg, string $type = 'info', array $context = [], bool $lazy = true)
{
$channel = $this->getConfig('type_channel.' . $type);
$this->channel($channel)->record($msg, $type, $context, $lazy);
return $this;
}
/**
* 实时写入日志信息
* @access public
* @param mixed $msg 调试信息
* @param string $type 日志级别
* @param array $context 替换内容
* @return $this
*/
public function write($msg, string $type = 'info', array $context = [])
{
return $this->record($msg, $type, $context, false);
}
/**
* 注册日志写入事件监听
* @param $listener
* @return Event
*/
public function listen($listener)
{
return $this->app->event->listen(LogWrite::class, $listener);
}
/**
* 记录日志信息
* @access public
* @param mixed $level 日志级别
* @param string|Stringable $message 日志信息
* @param array $context 替换内容
* @return void
*/
public function log($level, $message, array $context = []): void
{
$this->record($message, $level, $context);
}
/**
* 记录sql信息
* @access public
* @param string|Stringable $message 日志信息
* @param array $context 替换内容
* @return void
*/
public function sql($message, array $context = []): void
{
$this->log(__FUNCTION__, $message, $context);
}
public function __call($method, $parameters)
{
$this->log($method, ...$parameters);
}
}

View File

@@ -0,0 +1,173 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think;
use InvalidArgumentException;
use think\helper\Str;
abstract class Manager
{
/**
* 驱动
* @var array
*/
protected $drivers = [];
/**
* 驱动的命名空间
* @var string
*/
protected $namespace = null;
public function __construct(protected App $app)
{
}
/**
* 获取驱动实例
* @param null|string $name
* @return mixed
*/
protected function driver(string $name = null)
{
$name = $name ?: $this->getDefaultDriver();
if (is_null($name)) {
throw new InvalidArgumentException(sprintf(
'Unable to resolve NULL driver for [%s].',
static::class
));
}
return $this->drivers[$name] = $this->getDriver($name);
}
/**
* 获取驱动实例
* @param string $name
* @return mixed
*/
protected function getDriver(string $name)
{
return $this->drivers[$name] ?? $this->createDriver($name);
}
/**
* 获取驱动类型
* @param string $name
* @return mixed
*/
protected function resolveType(string $name)
{
return $name;
}
/**
* 获取驱动配置
* @param string $name
* @return mixed
*/
protected function resolveConfig(string $name)
{
return $name;
}
/**
* 获取驱动类
* @param string $type
* @return string
*/
protected function resolveClass(string $type): string
{
if ($this->namespace || str_contains($type, '\\')) {
$class = str_contains($type, '\\') ? $type : $this->namespace . Str::studly($type);
if (class_exists($class)) {
return $class;
}
}
throw new InvalidArgumentException("Driver [$type] not supported.");
}
/**
* 获取驱动参数
* @param $name
* @return array
*/
protected function resolveParams($name): array
{
$config = $this->resolveConfig($name);
return [$config];
}
/**
* 创建驱动
*
* @param string $name
* @return mixed
*
*/
protected function createDriver(string $name)
{
$type = $this->resolveType($name);
$method = 'create' . Str::studly($type) . 'Driver';
$params = $this->resolveParams($name);
if (method_exists($this, $method)) {
return $this->$method(...$params);
}
$class = $this->resolveClass($type);
return $this->app->invokeClass($class, $params);
}
/**
* 移除一个驱动实例
*
* @param array|string|null $name
* @return $this
*/
public function forgetDriver($name = null)
{
$name = $name ?? $this->getDefaultDriver();
foreach ((array) $name as $cacheName) {
if (isset($this->drivers[$cacheName])) {
unset($this->drivers[$cacheName]);
}
}
return $this;
}
/**
* 默认驱动
* @return string|null
*/
abstract public function getDefaultDriver();
/**
* 动态调用
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return $this->driver()->$method(...$parameters);
}
}

View File

@@ -0,0 +1,244 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Slince <taosikai@yeah.net>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think;
use Closure;
use LogicException;
use think\exception\Handle;
use Throwable;
/**
* 中间件管理类
* @package think
*/
class Middleware
{
/**
* 中间件执行队列
* @var array
*/
protected $queue = [];
public function __construct(protected App $app)
{
}
/**
* 导入中间件
* @access public
* @param array $middlewares
* @param string $type 中间件类型
* @return void
*/
public function import(array $middlewares = [], string $type = 'global'): void
{
foreach ($middlewares as $middleware) {
$this->add($middleware, $type);
}
}
/**
* 注册中间件
* @access public
* @param mixed $middleware
* @param string $type 中间件类型
* @return void
*/
public function add(array|string|Closure $middleware, string $type = 'global'): void
{
$middleware = $this->buildMiddleware($middleware, $type);
if (!empty($middleware)) {
$this->queue[$type][] = $middleware;
$this->queue[$type] = array_unique($this->queue[$type], SORT_REGULAR);
}
}
/**
* 注册路由中间件
* @access public
* @param mixed $middleware
* @return void
*/
public function route(array|string|Closure $middleware): void
{
$this->add($middleware, 'route');
}
/**
* 注册控制器中间件
* @access public
* @param mixed $middleware
* @return void
*/
public function controller(array|string|Closure $middleware): void
{
$this->add($middleware, 'controller');
}
/**
* 注册中间件到开始位置
* @access public
* @param mixed $middleware
* @param string $type 中间件类型
*/
public function unshift(array|string|Closure $middleware, string $type = 'global')
{
$middleware = $this->buildMiddleware($middleware, $type);
if (!empty($middleware)) {
if (!isset($this->queue[$type])) {
$this->queue[$type] = [];
}
array_unshift($this->queue[$type], $middleware);
}
}
/**
* 获取注册的中间件
* @access public
* @param string $type 中间件类型
* @return array
*/
public function all(string $type = 'global'): array
{
return $this->queue[$type] ?? [];
}
/**
* 调度管道
* @access public
* @param string $type 中间件类型
* @return Pipeline
*/
public function pipeline(string $type = 'global')
{
return (new Pipeline())
->through(array_map(function ($middleware) {
return function ($request, $next) use ($middleware) {
[$call, $params] = $middleware;
if (is_array($call) && is_string($call[0])) {
$call = [$this->app->make($call[0]), $call[1]];
}
$response = call_user_func($call, $request, $next, ...$params);
if (!$response instanceof Response) {
throw new LogicException('The middleware must return Response instance');
}
return $response;
};
}, $this->sortMiddleware($this->queue[$type] ?? [])))
->whenException([$this, 'handleException']);
}
/**
* 结束调度
* @param Response $response
*/
public function end(Response $response)
{
foreach ($this->queue as $queue) {
foreach ($queue as $middleware) {
[$call] = $middleware;
if (is_array($call) && is_string($call[0])) {
$instance = $this->app->make($call[0]);
if (method_exists($instance, 'end')) {
$instance->end($response);
}
}
}
}
}
/**
* 异常处理
* @param Request $passable
* @param Throwable $e
* @return Response
*/
public function handleException($passable, Throwable $e)
{
/** @var Handle $handler */
$handler = $this->app->make(Handle::class);
$handler->report($e);
return $handler->render($passable, $e);
}
/**
* 解析中间件
* @access protected
* @param array|string|Closure $middleware
* @param string $type 中间件类型
* @return array
*/
protected function buildMiddleware(array|string|Closure $middleware, string $type): array
{
if (is_array($middleware)) {
[$middleware, $params] = $middleware;
}
if ($middleware instanceof Closure) {
return [$middleware, $params ?? []];
}
//中间件别名检查
$alias = $this->app->config->get('middleware.alias', []);
if (isset($alias[$middleware])) {
$middleware = $alias[$middleware];
}
if (is_array($middleware)) {
$this->import($middleware, $type);
return [];
}
return [[$middleware, 'handle'], $params ?? []];
}
/**
* 中间件排序
* @param array $middlewares
* @return array
*/
protected function sortMiddleware(array $middlewares)
{
$priority = $this->app->config->get('middleware.priority', []);
uasort($middlewares, function ($a, $b) use ($priority) {
$aPriority = $this->getMiddlewarePriority($priority, $a);
$bPriority = $this->getMiddlewarePriority($priority, $b);
return $bPriority - $aPriority;
});
return $middlewares;
}
/**
* 获取中间件优先级
* @param $priority
* @param $middleware
* @return int
*/
protected function getMiddlewarePriority($priority, $middleware)
{
[$call] = $middleware;
if (is_array($call) && is_string($call[0])) {
$index = array_search($call[0], array_reverse($priority));
return false === $index ? -1 : $index;
}
return -1;
}
}

View File

@@ -0,0 +1,106 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think;
use Closure;
use Exception;
use Throwable;
class Pipeline
{
protected $passable;
protected $pipes = [];
protected $exceptionHandler;
/**
* 初始数据
* @param $passable
* @return $this
*/
public function send($passable)
{
$this->passable = $passable;
return $this;
}
/**
* 调用栈
* @param $pipes
* @return $this
*/
public function through($pipes)
{
$this->pipes = is_array($pipes) ? $pipes : func_get_args();
return $this;
}
/**
* 执行
* @param Closure $destination
* @return mixed
*/
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes),
$this->carry(),
function ($passable) use ($destination) {
try {
return $destination($passable);
} catch (Throwable | Exception $e) {
return $this->handleException($passable, $e);
}
}
);
return $pipeline($this->passable);
}
/**
* 设置异常处理器
* @param callable $handler
* @return $this
*/
public function whenException($handler)
{
$this->exceptionHandler = $handler;
return $this;
}
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
try {
return $pipe($passable, $stack);
} catch (Throwable | Exception $e) {
return $this->handleException($passable, $e);
}
};
};
}
/**
* 异常处理
* @param $passable
* @param $e
* @return mixed
*/
protected function handleException($passable, Throwable $e)
{
if ($this->exceptionHandler) {
return call_user_func($this->exceptionHandler, $passable, $e);
}
throw $e;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,415 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think;
/**
* 响应输出基础类
* @package think
*/
abstract class Response
{
/**
* 原始数据
* @var mixed
*/
protected $data;
/**
* 当前contentType
* @var string
*/
protected $contentType = 'text/html';
/**
* 字符集
* @var string
*/
protected $charset = 'utf-8';
/**
* 状态码
* @var integer
*/
protected $code = 200;
/**
* 是否允许请求缓存
* @var bool
*/
protected $allowCache = true;
/**
* 输出参数
* @var array
*/
protected $options = [];
/**
* header参数
* @var array
*/
protected $header = [];
/**
* 输出内容
* @var string
*/
protected $content = null;
/**
* Cookie对象
* @var Cookie
*/
protected $cookie;
/**
* Session对象
* @var Session
*/
protected $session;
/**
* 初始化
* @access protected
* @param mixed $data 输出数据
* @param int $code 状态码
*/
protected function init($data = '', int $code = 200)
{
$this->data($data);
$this->code = $code;
$this->contentType($this->contentType, $this->charset);
}
/**
* 创建Response对象
* @access public
* @param mixed $data 输出数据
* @param string $type 输出类型
* @param int $code 状态码
* @return Response
*/
public static function create($data = '', string $type = 'html', int $code = 200): Response
{
$class = str_contains($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type));
return Container::getInstance()->invokeClass($class, [$data, $code]);
}
/**
* 设置Session对象
* @access public
* @param Session $session Session对象
* @return $this
*/
public function setSession(Session $session)
{
$this->session = $session;
return $this;
}
/**
* 发送数据到客户端
* @access public
* @return void
* @throws \InvalidArgumentException
*/
public function send(): void
{
// 处理输出数据
$data = $this->getContent();
if (!headers_sent()) {
if (!empty($this->header)) {
// 发送状态码
http_response_code($this->code);
// 发送头部信息
foreach ($this->header as $name => $val) {
header($name . (!is_null($val) ? ':' . $val : ''));
}
}
if ($this->cookie) {
$this->cookie->save();
}
}
$this->sendData($data);
if (function_exists('fastcgi_finish_request')) {
// 提高页面响应
fastcgi_finish_request();
}
}
/**
* 处理数据
* @access protected
* @param mixed $data 要处理的数据
* @return mixed
*/
protected function output($data)
{
return $data;
}
/**
* 输出数据
* @access protected
* @param string $data 要处理的数据
* @return void
*/
protected function sendData(string $data): void
{
echo $data;
}
/**
* 输出的参数
* @access public
* @param mixed $options 输出参数
* @return $this
*/
public function options(array $options = [])
{
$this->options = array_merge($this->options, $options);
return $this;
}
/**
* 输出数据设置
* @access public
* @param mixed $data 输出数据
* @return $this
*/
public function data($data)
{
$this->data = $data;
return $this;
}
/**
* 是否允许请求缓存
* @access public
* @param bool $cache 允许请求缓存
* @return $this
*/
public function allowCache(bool $cache)
{
$this->allowCache = $cache;
return $this;
}
/**
* 是否允许请求缓存
* @access public
* @return bool
*/
public function isAllowCache()
{
return $this->allowCache;
}
/**
* 设置Cookie
* @access public
* @param string $name cookie名称
* @param string $value cookie值
* @param mixed $option 可选参数
* @return $this
*/
public function cookie(string $name, string $value, $option = null)
{
$this->cookie->set($name, $value, $option);
return $this;
}
/**
* 设置响应头
* @access public
* @param array $header 参数
* @return $this
*/
public function header(array $header = [])
{
$this->header = array_merge($this->header, $header);
return $this;
}
/**
* 设置页面输出内容
* @access public
* @param mixed $content
* @return $this
*/
public function content($content)
{
if (
null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([
$content,
'__toString',
])
) {
throw new \InvalidArgumentException(sprintf('variable type error %s', gettype($content)));
}
$this->content = (string) $content;
return $this;
}
/**
* 发送HTTP状态
* @access public
* @param integer $code 状态码
* @return $this
*/
public function code(int $code)
{
$this->code = $code;
return $this;
}
/**
* LastModified
* @access public
* @param string $time
* @return $this
*/
public function lastModified(string $time)
{
$this->header['Last-Modified'] = $time;
return $this;
}
/**
* Expires
* @access public
* @param string $time
* @return $this
*/
public function expires(string $time)
{
$this->header['Expires'] = $time;
return $this;
}
/**
* ETag
* @access public
* @param string $eTag
* @return $this
*/
public function eTag(string $eTag)
{
$this->header['ETag'] = $eTag;
return $this;
}
/**
* 页面缓存控制
* @access public
* @param string $cache 状态码
* @return $this
*/
public function cacheControl(string $cache)
{
$this->header['Cache-control'] = $cache;
return $this;
}
/**
* 页面输出类型
* @access public
* @param string $contentType 输出类型
* @param string $charset 输出编码
* @return $this
*/
public function contentType(string $contentType, string $charset = 'utf-8')
{
$this->header['Content-Type'] = $contentType . '; charset=' . $charset;
return $this;
}
/**
* 获取头部信息
* @access public
* @param string $name 头部名称
* @return mixed
*/
public function getHeader(string $name = '')
{
if (!empty($name)) {
return $this->header[$name] ?? null;
}
return $this->header;
}
/**
* 获取原始数据
* @access public
* @return mixed
*/
public function getData()
{
return $this->data;
}
/**
* 获取输出数据
* @access public
* @return string
*/
public function getContent(): string
{
if (null == $this->content) {
$content = $this->output($this->data);
if (
null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([
$content,
'__toString',
])
) {
throw new \InvalidArgumentException(sprintf('variable type error %s', gettype($content)));
}
$this->content = (string) $content;
}
return $this->content;
}
/**
* 获取状态码
* @access public
* @return integer
*/
public function getCode(): int
{
return $this->code;
}
}

View File

@@ -0,0 +1,909 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think;
use Closure;
use think\exception\RouteNotFoundException;
use think\route\Dispatch;
use think\route\dispatch\Callback;
use think\route\dispatch\Url as UrlDispatch;
use think\route\Domain;
use think\route\Resource;
use think\route\ResourceRegister;
use think\route\Rule;
use think\route\RuleGroup;
use think\route\RuleItem;
use think\route\RuleName;
use think\route\Url as UrlBuild;
/**
* 路由管理类
* @package think
*/
class Route
{
/**
* REST定义
* @var array
*/
protected $rest = [
'index' => ['get', '', 'index'],
'create' => ['get', '/create', 'create'],
'edit' => ['get', '/<id>/edit', 'edit'],
'read' => ['get', '/<id>', 'read'],
'save' => ['post', '', 'save'],
'update' => ['put', '/<id>', 'update'],
'delete' => ['delete', '/<id>', 'delete'],
];
/**
* 配置参数
* @var array
*/
protected $config = [
// pathinfo分隔符
'pathinfo_depr' => '/',
// 是否开启路由延迟解析
'url_lazy_route' => false,
// 是否强制使用路由
'url_route_must' => false,
// 是否区分大小写
'url_case_sensitive' => false,
// 合并路由规则
'route_rule_merge' => false,
// 路由是否完全匹配
'route_complete_match' => false,
// 去除斜杠
'remove_slash' => false,
// 使用注解路由
'route_annotation' => false,
// 默认的路由变量规则
'default_route_pattern' => '[\w\.]+',
// URL伪静态后缀
'url_html_suffix' => 'html',
// 访问控制器层名称
'controller_layer' => 'controller',
// 空控制器名
'empty_controller' => 'Error',
// 是否使用控制器后缀
'controller_suffix' => false,
// 默认控制器名
'default_controller' => 'Index',
// 默认操作名
'default_action' => 'index',
// 操作方法后缀
'action_suffix' => '',
// 非路由变量是否使用普通参数方式用于URL生成
'url_common_param' => true,
];
/**
* 请求对象
* @var Request
*/
protected $request;
/**
* @var RuleName
*/
protected $ruleName;
/**
* 当前HOST
* @var string
*/
protected $host;
/**
* 当前分组对象
* @var RuleGroup
*/
protected $group;
/**
* 路由绑定
* @var array
*/
protected $bind = [];
/**
* 域名对象
* @var Domain[]
*/
protected $domains = [];
/**
* 跨域路由规则
* @var RuleGroup
*/
protected $cross;
/**
* 路由是否延迟解析
* @var bool
*/
protected $lazy = false;
/**
* (分组)路由规则是否合并解析
* @var bool
*/
protected $mergeRuleRegex = false;
/**
* 是否去除URL最后的斜线
* @var bool
*/
protected $removeSlash = false;
public function __construct(protected App $app)
{
$this->ruleName = new RuleName();
$this->setDefaultDomain();
if (is_file($this->app->getRuntimePath() . 'route.php')) {
// 读取路由映射文件
$this->import(include $this->app->getRuntimePath() . 'route.php');
}
$this->config = array_merge($this->config, $this->app->config->get('route'));
$this->init();
}
protected function init()
{
if (!empty($this->config['middleware'])) {
$this->app->middleware->import($this->config['middleware'], 'route');
}
$this->lazy($this->config['url_lazy_route']);
$this->mergeRuleRegex = $this->config['route_rule_merge'];
$this->removeSlash = $this->config['remove_slash'];
$this->group->removeSlash($this->removeSlash);
// 注册全局MISS路由
$this->miss(function () {
return Response::create('', 'html', 204)->header(['Allow' => 'GET, POST, PUT, DELETE']);
}, 'options');
}
public function config(string $name = null)
{
if (is_null($name)) {
return $this->config;
}
return $this->config[$name] ?? null;
}
/**
* 设置路由域名及分组(包括资源路由)是否延迟解析
* @access public
* @param bool $lazy 路由是否延迟解析
* @return $this
*/
public function lazy(bool $lazy = true)
{
$this->lazy = $lazy;
return $this;
}
/**
* 设置路由域名及分组(包括资源路由)是否合并解析
* @access public
* @param bool $merge 路由是否合并解析
* @return $this
*/
public function mergeRuleRegex(bool $merge = true)
{
$this->mergeRuleRegex = $merge;
$this->group->mergeRuleRegex($merge);
return $this;
}
/**
* 初始化默认域名
* @access protected
* @return void
*/
protected function setDefaultDomain(): void
{
// 注册默认域名
$domain = new Domain($this);
$this->domains['-'] = $domain;
// 默认分组
$this->group = $domain;
}
/**
* 设置当前分组
* @access public
* @param RuleGroup $group 域名
* @return void
*/
public function setGroup(RuleGroup $group): void
{
$this->group = $group;
}
/**
* 获取指定标识的路由分组 不指定则获取当前分组
* @access public
* @param string $name 分组标识
* @return RuleGroup
*/
public function getGroup(string $name = null)
{
return $name ? $this->ruleName->getGroup($name) : $this->group;
}
/**
* 注册变量规则
* @access public
* @param array $pattern 变量规则
* @return $this
*/
public function pattern(array $pattern)
{
$this->group->pattern($pattern);
return $this;
}
/**
* 注册路由参数
* @access public
* @param array $option 参数
* @return $this
*/
public function option(array $option)
{
$this->group->option($option);
return $this;
}
/**
* 注册域名路由
* @access public
* @param string|array $name 子域名
* @param mixed $rule 路由规则
* @return Domain
*/
public function domain(string | array $name, $rule = null): Domain
{
// 支持多个域名使用相同路由规则
$domainName = is_array($name) ? array_shift($name) : $name;
if (!isset($this->domains[$domainName])) {
$domain = (new Domain($this, $domainName, $rule, $this->lazy))
->removeSlash($this->removeSlash)
->mergeRuleRegex($this->mergeRuleRegex);
$this->domains[$domainName] = $domain;
} else {
$domain = $this->domains[$domainName];
$domain->parseGroupRule($rule);
}
if (is_array($name) && !empty($name)) {
foreach ($name as $item) {
$this->domains[$item] = $domainName;
}
}
// 返回域名对象
return $domain;
}
/**
* 获取域名
* @access public
* @return array
*/
public function getDomains(): array
{
return $this->domains;
}
/**
* 获取RuleName对象
* @access public
* @return RuleName
*/
public function getRuleName(): RuleName
{
return $this->ruleName;
}
/**
* 设置路由绑定
* @access public
* @param string $bind 绑定信息
* @param string $domain 域名
* @return $this
*/
public function bind(string $bind, string $domain = null)
{
$domain = is_null($domain) ? '-' : $domain;
$this->bind[$domain] = $bind;
return $this;
}
/**
* 读取路由绑定信息
* @access public
* @return array
*/
public function getBind(): array
{
return $this->bind;
}
/**
* 读取路由绑定
* @access public
* @param string $domain 域名
* @return string|null
*/
public function getDomainBind(string $domain = null)
{
if (is_null($domain)) {
$domain = $this->host;
} elseif (!str_contains($domain, '.') && $this->request) {
$domain .= '.' . $this->request->rootDomain();
}
if ($this->request) {
$subDomain = $this->request->subDomain();
if (str_contains($subDomain, '.')) {
$name = '*' . strstr($subDomain, '.');
}
}
if (isset($this->bind[$domain])) {
$result = $this->bind[$domain];
} elseif (isset($name) && isset($this->bind[$name])) {
$result = $this->bind[$name];
} elseif (!empty($subDomain) && isset($this->bind['*'])) {
$result = $this->bind['*'];
} else {
$result = null;
}
return $result;
}
/**
* 读取路由标识
* @access public
* @param string $name 路由标识
* @param string $domain 域名
* @param string $method 请求类型
* @return array
*/
public function getName(string $name = null, string $domain = null, string $method = '*'): array
{
return $this->ruleName->getName($name, $domain, $method);
}
/**
* 批量导入路由标识
* @access public
* @param array $name 路由标识
* @return void
*/
public function import(array $name): void
{
$this->ruleName->import($name);
}
/**
* 注册路由标识
* @access public
* @param string $name 路由标识
* @param RuleItem $ruleItem 路由规则
* @param bool $first 是否优先
* @return void
*/
public function setName(string $name, RuleItem $ruleItem, bool $first = false): void
{
$this->ruleName->setName($name, $ruleItem, $first);
}
/**
* 保存路由规则
* @access public
* @param string $rule 路由规则
* @param RuleItem $ruleItem RuleItem对象
* @return void
*/
public function setRule(string $rule, RuleItem $ruleItem = null): void
{
$this->ruleName->setRule($rule, $ruleItem);
}
/**
* 读取路由
* @access public
* @param string $rule 路由规则
* @return RuleItem[]
*/
public function getRule(string $rule): array
{
return $this->ruleName->getRule($rule);
}
/**
* 读取路由列表
* @access public
* @return array
*/
public function getRuleList(): array
{
return $this->ruleName->getRuleList();
}
/**
* 清空路由规则
* @access public
* @return void
*/
public function clear(): void
{
$this->ruleName->clear();
if ($this->group) {
$this->group->clear();
}
}
/**
* 注册路由规则
* @access public
* @param string $rule 路由规则
* @param mixed $route 路由地址
* @param string $method 请求类型
* @return RuleItem
*/
public function rule(string $rule, $route = null, string $method = '*'): RuleItem
{
return $this->group->addRule($rule, $route, $method);
}
/**
* 设置路由规则全局有效
* @access public
* @param Rule $rule 路由规则
* @return $this
*/
public function setCrossDomainRule(Rule $rule)
{
if (!isset($this->cross)) {
$this->cross = (new RuleGroup($this))->mergeRuleRegex($this->mergeRuleRegex);
}
$this->cross->addRuleItem($rule);
return $this;
}
/**
* 注册路由分组
* @access public
* @param string|Closure $name 分组名称或者参数
* @param mixed $route 分组路由
* @return RuleGroup
*/
public function group(string | Closure $name, $route = null): RuleGroup
{
if ($name instanceof Closure) {
$route = $name;
$name = '';
}
return (new RuleGroup($this, $this->group, $name, $route, $this->lazy))
->removeSlash($this->removeSlash)
->mergeRuleRegex($this->mergeRuleRegex);
}
/**
* 注册路由
* @access public
* @param string $rule 路由规则
* @param mixed $route 路由地址
* @return RuleItem
*/
public function any(string $rule, $route): RuleItem
{
return $this->rule($rule, $route, '*');
}
/**
* 注册GET路由
* @access public
* @param string $rule 路由规则
* @param mixed $route 路由地址
* @return RuleItem
*/
public function get(string $rule, $route): RuleItem
{
return $this->rule($rule, $route, 'GET');
}
/**
* 注册POST路由
* @access public
* @param string $rule 路由规则
* @param mixed $route 路由地址
* @return RuleItem
*/
public function post(string $rule, $route): RuleItem
{
return $this->rule($rule, $route, 'POST');
}
/**
* 注册PUT路由
* @access public
* @param string $rule 路由规则
* @param mixed $route 路由地址
* @return RuleItem
*/
public function put(string $rule, $route): RuleItem
{
return $this->rule($rule, $route, 'PUT');
}
/**
* 注册DELETE路由
* @access public
* @param string $rule 路由规则
* @param mixed $route 路由地址
* @return RuleItem
*/
public function delete(string $rule, $route): RuleItem
{
return $this->rule($rule, $route, 'DELETE');
}
/**
* 注册PATCH路由
* @access public
* @param string $rule 路由规则
* @param mixed $route 路由地址
* @return RuleItem
*/
public function patch(string $rule, $route): RuleItem
{
return $this->rule($rule, $route, 'PATCH');
}
/**
* 注册HEAD路由
* @access public
* @param string $rule 路由规则
* @param mixed $route 路由地址
* @return RuleItem
*/
public function head(string $rule, $route): RuleItem
{
return $this->rule($rule, $route, 'HEAD');
}
/**
* 注册OPTIONS路由
* @access public
* @param string $rule 路由规则
* @param mixed $route 路由地址
* @return RuleItem
*/
public function options(string $rule, $route): RuleItem
{
return $this->rule($rule, $route, 'OPTIONS');
}
/**
* 注册资源路由
* @access public
* @param string $rule 路由规则
* @param string $route 路由地址
* @return Resource|ResourceRegister
*/
public function resource(string $rule, string $route)
{
$resource = new Resource($this, $this->group, $rule, $route, $this->rest);
if (!$this->lazy) {
return new ResourceRegister($resource);
}
return $resource;
}
/**
* 注册视图路由
* @access public
* @param string $rule 路由规则
* @param string $template 路由模板地址
* @param array $vars 模板变量
* @return RuleItem
*/
public function view(string $rule, string $template = '', array $vars = []): RuleItem
{
return $this->rule($rule, function () use ($vars, $template) {
return Response::create($template, 'view')->assign($vars);
}, 'GET');
}
/**
* 注册重定向路由
* @access public
* @param string $rule 路由规则
* @param string $route 路由地址
* @param int $status 状态码
* @return RuleItem
*/
public function redirect(string $rule, string $route = '', int $status = 301): RuleItem
{
return $this->rule($rule, function (Request $request) use ($status, $route) {
$search = $replace = [];
$matches = $request->rule()->getVars();
foreach ($matches as $key => $value) {
$search[] = '<' . $key . '>';
$replace[] = $value;
$search[] = ':' . $key;
$replace[] = $value;
}
$route = str_replace($search, $replace, $route);
return Response::create($route, 'redirect')->code($status);
}, '*');
}
/**
* rest方法定义和修改
* @access public
* @param string|array $name 方法名称
* @param array|bool $resource 资源
* @return $this
*/
public function rest(string | array $name, array | bool $resource = [])
{
if (is_array($name)) {
$this->rest = $resource ? $name : array_merge($this->rest, $name);
} else {
$this->rest[$name] = $resource;
}
return $this;
}
/**
* 获取rest方法定义的参数
* @access public
* @param string $name 方法名称
* @return array|null
*/
public function getRest(string $name = null)
{
if (is_null($name)) {
return $this->rest;
}
return $this->rest[$name] ?? null;
}
/**
* 注册未匹配路由规则后的处理
* @access public
* @param string|Closure $route 路由地址
* @param string $method 请求类型
* @return RuleItem
*/
public function miss(string | Closure $route, string $method = '*'): RuleItem
{
return $this->group->miss($route, $method);
}
/**
* 路由调度
* @param Request $request
* @param Closure|bool $withRoute
* @return Response
*/
public function dispatch(Request $request, Closure | bool $withRoute = true)
{
$this->request = $request;
$this->host = $this->request->host(true);
if ($withRoute) {
//加载路由
if ($withRoute instanceof Closure) {
$withRoute();
}
$dispatch = $this->check();
} else {
$dispatch = $this->url($this->path());
}
$dispatch->init($this->app);
return $this->app->middleware->pipeline('route')
->send($request)
->then(function () use ($dispatch) {
return $dispatch->run();
});
}
/**
* 检测URL路由
* @access public
* @return Dispatch|false
* @throws RouteNotFoundException
*/
public function check()
{
// 自动检测域名路由
$url = str_replace($this->config['pathinfo_depr'], '|', $this->path());
$completeMatch = $this->config['route_complete_match'];
$result = $this->checkDomain()->check($this->request, $url, $completeMatch);
if (false === $result && !empty($this->cross)) {
// 检测跨域路由
$result = $this->cross->check($this->request, $url, $completeMatch);
}
if (false !== $result) {
return $result;
} elseif ($this->config['url_route_must']) {
throw new RouteNotFoundException();
}
return $this->url($url);
}
/**
* 获取当前请求URL的pathinfo信息(不含URL后缀)
* @access protected
* @return string
*/
protected function path(): string
{
$suffix = $this->config['url_html_suffix'];
$pathinfo = $this->request->pathinfo();
if (false === $suffix) {
// 禁止伪静态访问
$path = $pathinfo;
} elseif ($suffix) {
// 去除正常的URL后缀
$path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo);
} else {
// 允许任何后缀访问
$path = preg_replace('/\.' . $this->request->ext() . '$/i', '', $pathinfo);
}
return $path;
}
/**
* 默认URL解析
* @access public
* @param string $url URL地址
* @return Dispatch
*/
public function url(string $url): Dispatch
{
if ($this->request->method() == 'OPTIONS') {
// 自动响应options请求
return new Callback($this->request, $this->group, function () {
return Response::create('', 'html', 204)->header(['Allow' => 'GET, POST, PUT, DELETE']);
});
}
return new UrlDispatch($this->request, $this->group, $url);
}
/**
* 检测域名的路由规则
* @access protected
* @return Domain
*/
protected function checkDomain(): Domain
{
$item = false;
if (count($this->domains) > 1) {
// 获取当前子域名
$subDomain = $this->request->subDomain();
$domain = $subDomain ? explode('.', $subDomain) : [];
$domain2 = $domain ? array_pop($domain) : '';
if ($domain) {
// 存在三级域名
$domain3 = array_pop($domain);
}
if (isset($this->domains[$this->host])) {
// 子域名配置
$item = $this->domains[$this->host];
} elseif (isset($this->domains[$subDomain])) {
$item = $this->domains[$subDomain];
} elseif (isset($this->domains['*.' . $domain2]) && !empty($domain3)) {
// 泛三级域名
$item = $this->domains['*.' . $domain2];
$panDomain = $domain3;
} elseif (isset($this->domains['*']) && !empty($domain2)) {
// 泛二级域名
if ('www' != $domain2) {
$item = $this->domains['*'];
$panDomain = $domain2;
}
}
if (isset($panDomain)) {
// 保存当前泛域名
$this->request->setPanDomain($panDomain);
}
}
if (false === $item) {
// 检测全局域名规则
$item = $this->domains['-'];
}
if (is_string($item)) {
$item = $this->domains[$item];
}
return $item;
}
/**
* URL生成 支持路由反射
* @access public
* @param string $url 路由地址
* @param array $vars 参数 ['a'=>'val1', 'b'=>'val2']
* @return UrlBuild
*/
public function buildUrl(string $url = '', array $vars = []): UrlBuild
{
return $this->app->make(UrlBuild::class, [$this, $this->app, $url, $vars], true);
}
/**
* 设置全局的路由分组参数
* @access public
* @param string $method 方法名
* @param array $args 调用参数
* @return RuleGroup
*/
public function __call($method, $args)
{
return call_user_func_array([$this->group, $method], $args);
}
}

View File

@@ -0,0 +1,63 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think;
use Closure;
use think\event\RouteLoaded;
/**
* 系统服务基础类
* @method void register()
* @method void boot()
*/
abstract class Service
{
public function __construct(protected App $app)
{
}
/**
* 加载路由
* @access protected
* @param string $path 路由路径
*/
protected function loadRoutesFrom(string $path)
{
$this->registerRoutes(function () use ($path) {
include $path;
});
}
/**
* 注册路由
* @param Closure $closure
*/
protected function registerRoutes(Closure $closure)
{
$this->app->event->listen(RouteLoaded::class, $closure);
}
/**
* 添加指令
* @access protected
* @param array|string $commands 指令
*/
protected function commands($commands)
{
$commands = is_array($commands) ? $commands : func_get_args();
Console::starting(function (Console $console) use ($commands) {
$console->addCommands($commands);
});
}
}

View File

@@ -0,0 +1,65 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think;
use think\helper\Arr;
use think\session\Store;
/**
* Session管理类
* @package think
* @mixin Store
*/
class Session extends Manager
{
protected $namespace = '\\think\\session\\driver\\';
protected function createDriver(string $name)
{
$handler = parent::createDriver($name);
return new Store($this->getConfig('name') ?: 'PHPSESSID', $handler, $this->getConfig('serialize'));
}
/**
* 获取Session配置
* @access public
* @param null|string $name 名称
* @param mixed $default 默认值
* @return mixed
*/
public function getConfig(string $name = null, $default = null)
{
if (!is_null($name)) {
return $this->app->config->get('session.' . $name, $default);
}
return $this->app->config->get('session');
}
protected function resolveConfig(string $name)
{
$config = $this->app->config->get('session', []);
Arr::forget($config, 'type');
return $config;
}
/**
* 默认驱动
* @return string|null
*/
public function getDefaultDriver()
{
return $this->app->config->get('session.type', 'file');
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,187 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think;
use think\contract\TemplateHandlerInterface;
use think\helper\Arr;
/**
* 视图类
* @package think
*/
class View extends Manager
{
protected $namespace = '\\think\\view\\driver\\';
/**
* 模板变量
* @var array
*/
protected $data = [];
/**
* 内容过滤
* @var mixed
*/
protected $filter;
/**
* 获取模板引擎
* @access public
* @param string $type 模板引擎类型
* @return TemplateHandlerInterface
*/
public function engine(string $type = null)
{
return $this->driver($type);
}
/**
* 模板变量赋值
* @access public
* @param string|array $name 模板变量
* @param mixed $value 变量值
* @return $this
*/
public function assign(string|array $name, $value = null)
{
if (is_array($name)) {
$this->data = array_merge($this->data, $name);
} else {
$this->data[$name] = $value;
}
return $this;
}
/**
* 视图过滤
* @access public
* @param Callable $filter 过滤方法或闭包
* @return $this
*/
public function filter(callable $filter = null)
{
$this->filter = $filter;
return $this;
}
/**
* 解析和获取模板内容 用于输出
* @access public
* @param string $template 模板文件名或者内容
* @param array $vars 模板变量
* @return string
* @throws \Exception
*/
public function fetch(string $template = '', array $vars = []): string
{
return $this->getContent(function () use ($vars, $template) {
$this->engine()->fetch($template, array_merge($this->data, $vars));
});
}
/**
* 渲染内容输出
* @access public
* @param string $content 内容
* @param array $vars 模板变量
* @return string
*/
public function display(string $content, array $vars = []): string
{
return $this->getContent(function () use ($vars, $content) {
$this->engine()->display($content, array_merge($this->data, $vars));
});
}
/**
* 获取模板引擎渲染内容
* @param $callback
* @return string
* @throws \Exception
*/
protected function getContent($callback): string
{
// 页面缓存
ob_start();
ob_implicit_flush(false);
// 渲染输出
try {
$callback();
} catch (\Exception $e) {
ob_end_clean();
throw $e;
}
// 获取并清空缓存
$content = ob_get_clean();
if ($this->filter) {
$content = call_user_func_array($this->filter, [$content]);
}
return $content;
}
/**
* 模板变量赋值
* @access public
* @param string $name 变量名
* @param mixed $value 变量值
*/
public function __set($name, $value)
{
$this->data[$name] = $value;
}
/**
* 取得模板显示变量的值
* @access protected
* @param string $name 模板变量
* @return mixed
*/
public function __get($name)
{
return $this->data[$name];
}
/**
* 检测模板变量是否设置
* @access public
* @param string $name 模板变量名
* @return bool
*/
public function __isset($name)
{
return isset($this->data[$name]);
}
protected function resolveConfig(string $name)
{
$config = $this->app->config->get('view', []);
Arr::forget($config, 'type');
return $config;
}
/**
* 默认驱动
* @return string|null
*/
public function getDefaultDriver()
{
return $this->app->config->get('view.type', 'php');
}
}

View File

@@ -0,0 +1,360 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think\cache;
use Closure;
use DateInterval;
use DateTime;
use DateTimeInterface;
use Exception;
use think\Container;
use think\contract\CacheHandlerInterface;
use think\exception\InvalidArgumentException;
use throwable;
/**
* 缓存基础类
*/
abstract class Driver implements CacheHandlerInterface
{
/**
* 驱动句柄
* @var object
*/
protected $handler = null;
/**
* 缓存读取次数
* @var integer
*/
protected $readTimes = 0;
/**
* 缓存写入次数
* @var integer
*/
protected $writeTimes = 0;
/**
* 缓存参数
* @var array
*/
protected $options = [];
/**
* 缓存标签
* @var array
*/
protected $tag = [];
/**
* 获取有效期
* @access protected
* @param integer|DateInterval|DateTimeInterface $expire 有效期
* @return int
*/
protected function getExpireTime(int|DateInterval|DateTimeInterface $expire): int
{
if ($expire instanceof DateTimeInterface) {
$expire = $expire->getTimestamp() - time();
} elseif ($expire instanceof DateInterval) {
$expire = DateTime::createFromFormat('U', (string) time())
->add($expire)
->format('U') - time();
}
return $expire;
}
/**
* 获取实际的缓存标识
* @access public
* @param string $name 缓存名
* @return string
*/
public function getCacheKey(string $name): string
{
return $this->options['prefix'] . $name;
}
/**
* 读取缓存并删除
* @access public
* @param string $name 缓存变量名
* @return mixed
*/
public function pull($name)
{
$result = $this->get($name, false);
if ($result) {
$this->delete($name);
return $result;
}
}
/**
* 追加(数组)缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @return void
*/
public function push($name, $value): void
{
$item = $this->get($name, []);
if (!is_array($item)) {
throw new InvalidArgumentException('only array cache can be push');
}
$item[] = $value;
if (count($item) > 1000) {
array_shift($item);
}
$item = array_unique($item);
$this->set($name, $item);
}
/**
* 追加TagSet数据
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @return void
*/
public function append($name, $value): void
{
$this->push($name, $value);
}
/**
* 如果不存在则写入缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param int|DateInterval|DateTimeInterface $expire 有效时间 0为永久
* @return mixed
*/
public function remember($name, $value, $expire = null)
{
if ($this->has($name)) {
if (($hit = $this->get($name)) !== null) {
return $hit;
}
}
$time = time();
while ($time + 5 > time() && $this->has($name . '_lock')) {
// 存在锁定则等待
usleep(200000);
}
try {
// 锁定
$this->set($name . '_lock', true);
if ($value instanceof Closure) {
// 获取缓存数据
$value = Container::getInstance()->invokeFunction($value);
}
// 缓存数据
$this->set($name, $value, $expire);
// 解锁
$this->delete($name . '_lock');
} catch (Exception|throwable $e) {
$this->delete($name . '_lock');
throw $e;
}
return $value;
}
/**
* 缓存标签
* @access public
* @param string|array $name 标签名
* @return TagSet
*/
public function tag($name)
{
$name = (array) $name;
$key = implode('-', $name);
if (!isset($this->tag[$key])) {
$this->tag[$key] = new TagSet($name, $this);
}
return $this->tag[$key];
}
/**
* 获取标签包含的缓存标识
* @access public
* @param string $tag 标签标识
* @return array
*/
public function getTagItems(string $tag): array
{
$name = $this->getTagKey($tag);
return $this->get($name, []);
}
/**
* 获取实际标签名
* @access public
* @param string $tag 标签名
* @return string
*/
public function getTagKey(string $tag): string
{
return $this->options['tag_prefix'] . md5($tag);
}
/**
* 序列化数据
* @access protected
* @param mixed $data 缓存数据
* @return string
*/
protected function serialize($data): string
{
if (is_numeric($data)) {
return (string) $data;
}
$serialize = $this->options['serialize'][0] ?? "serialize";
return $serialize($data);
}
/**
* 反序列化数据
* @access protected
* @param string $data 缓存数据
* @return mixed
*/
protected function unserialize(string $data)
{
if (is_numeric($data)) {
return $data;
}
$unserialize = $this->options['serialize'][1] ?? "unserialize";
return $unserialize($data);
}
/**
* 返回句柄对象,可执行其它高级方法
*
* @access public
* @return object
*/
public function handler()
{
return $this->handler;
}
/**
* 返回缓存读取次数
* @return int
* @deprecated
* @access public
*/
public function getReadTimes(): int
{
return $this->readTimes;
}
/**
* 返回缓存写入次数
* @return int
* @deprecated
* @access public
*/
public function getWriteTimes(): int
{
return $this->writeTimes;
}
/**
* 读取缓存
* @access public
* @param iterable $keys 缓存变量名
* @param mixed $default 默认值
* @return iterable
* @throws InvalidArgumentException
*/
public function getMultiple($keys, $default = null): iterable
{
$result = [];
foreach ($keys as $key) {
$result[$key] = $this->get($key, $default);
}
return $result;
}
/**
* 写入缓存
* @access public
* @param iterable $values 缓存数据
* @param null|int|\DateInterval|DateTimeInterface $ttl 有效时间 0为永久
* @return bool
*/
public function setMultiple($values, $ttl = null): bool
{
foreach ($values as $key => $val) {
$result = $this->set($key, $val, $ttl);
if (false === $result) {
return false;
}
}
return true;
}
/**
* 删除缓存
* @access public
* @param iterable $keys 缓存变量名
* @return bool
* @throws InvalidArgumentException
*/
public function deleteMultiple($keys): bool
{
foreach ($keys as $key) {
$result = $this->delete($key);
if (false === $result) {
return false;
}
}
return true;
}
public function __call($method, $args)
{
return call_user_func_array([$this->handler, $method], $args);
}
}

View File

@@ -0,0 +1,121 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types = 1);
namespace think\cache;
use DateInterval;
use DateTimeInterface;
/**
* 标签集合
*/
class TagSet
{
/**
* 架构函数
* @access public
* @param array $tag 缓存标签
* @param Driver $handler 缓存对象
*/
public function __construct(protected array $tag, protected Driver $handler)
{
}
/**
* 写入缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param integer|DateInterval|DateTimeInterface $expire 有效时间(秒)
* @return bool
*/
public function set($name, $value, $expire = null): bool
{
$this->handler->set($name, $value, $expire);
$this->append($name);
return true;
}
/**
* 追加缓存标识到标签
* @access public
* @param string $name 缓存变量名
* @return void
*/
public function append(string $name): void
{
$name = $this->handler->getCacheKey($name);
foreach ($this->tag as $tag) {
$key = $this->handler->getTagKey($tag);
$this->handler->append($key, $name);
}
}
/**
* 写入缓存
* @access public
* @param iterable $values 缓存数据
* @param null|int|DateInterval|DateTimeInterface $ttl 有效时间 0为永久
* @return bool
*/
public function setMultiple($values, $ttl = null): bool
{
foreach ($values as $key => $val) {
$result = $this->set($key, $val, $ttl);
if (false === $result) {
return false;
}
}
return true;
}
/**
* 如果不存在则写入缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param int $expire 有效时间 0为永久
* @return mixed
*/
public function remember($name, $value, $expire = null)
{
$result = $this->handler->remember($name, $value, $expire);
$this->append($name);
return $result;
}
/**
* 清除缓存
* @access public
* @return bool
*/
public function clear(): bool
{
// 指定标签清除
foreach ($this->tag as $tag) {
$keys = $this->handler->getTagItems($tag);
if (!empty($keys)) $this->handler->clearTag($keys);
$key = $this->handler->getTagKey($tag);
$this->handler->delete($key);
}
return true;
}
}

View File

@@ -0,0 +1,302 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think\cache\driver;
use DateTimeInterface;
use FilesystemIterator;
use think\App;
use think\cache\Driver;
/**
* 文件缓存类
*/
class File extends Driver
{
/**
* 配置参数
* @var array
*/
protected $options = [
'expire' => 0,
'cache_subdir' => true,
'prefix' => '',
'path' => '',
'hash_type' => 'md5',
'data_compress' => false,
'tag_prefix' => 'tag:',
'serialize' => [],
];
/**
* 架构函数
* @param App $app
* @param array $options 参数
*/
public function __construct(App $app, array $options = [])
{
if (!empty($options)) {
$this->options = array_merge($this->options, $options);
}
if (empty($this->options['path'])) {
$this->options['path'] = $app->getRuntimePath() . 'cache';
}
if (!str_ends_with($this->options['path'], DIRECTORY_SEPARATOR)) {
$this->options['path'] .= DIRECTORY_SEPARATOR;
}
}
/**
* 取得变量的存储文件名
* @access public
* @param string $name 缓存变量名
* @return string
*/
public function getCacheKey(string $name): string
{
$name = hash($this->options['hash_type'], $name);
if ($this->options['cache_subdir']) {
// 使用子目录
$name = substr($name, 0, 2) . DIRECTORY_SEPARATOR . substr($name, 2);
}
if ($this->options['prefix']) {
$name = $this->options['prefix'] . DIRECTORY_SEPARATOR . $name;
}
return $this->options['path'] . $name . '.php';
}
/**
* 获取缓存数据
* @param string $name 缓存标识名
* @return array|null
*/
protected function getRaw(string $name)
{
$filename = $this->getCacheKey($name);
if (!is_file($filename)) {
return;
}
$content = @file_get_contents($filename);
if (false !== $content) {
$expire = (int) substr($content, 8, 12);
if (0 != $expire && time() - $expire > filemtime($filename)) {
//缓存过期删除缓存文件
$this->unlink($filename);
return;
}
$content = substr($content, 32);
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//启用数据压缩
$content = gzuncompress($content);
}
return is_string($content) ? ['content' => (string) $content, 'expire' => $expire] : null;
}
}
/**
* 判断缓存是否存在
* @access public
* @param string $name 缓存变量名
* @return bool
*/
public function has($name): bool
{
return $this->getRaw($name) !== null;
}
/**
* 读取缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $default 默认值
* @return mixed
*/
public function get($name, $default = null): mixed
{
$raw = $this->getRaw($name);
return is_null($raw) ? $default : $this->unserialize($raw['content']);
}
/**
* 写入缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param int|\DateInterval|DateTimeInterface|null $expire 有效时间 0为永久
* @return bool
*/
public function set($name, $value, $expire = null): bool
{
if (is_null($expire)) {
$expire = $this->options['expire'];
}
$expire = $this->getExpireTime($expire);
$filename = $this->getCacheKey($name);
$dir = dirname($filename);
if (!is_dir($dir)) {
try {
mkdir($dir, 0755, true);
} catch (\Exception $e) {
// 创建失败
}
}
$data = $this->serialize($value);
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
if (str_contains($filename, '://') && !str_starts_with($filename, 'file://')) {
//虚拟文件不加锁
$result = file_put_contents($filename, $data);
} else {
$result = file_put_contents($filename, $data, LOCK_EX);
}
if ($result) {
clearstatcache();
return true;
}
return false;
}
/**
* 自增缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function inc($name, $step = 1)
{
if ($raw = $this->getRaw($name)) {
$value = $this->unserialize($raw['content']) + $step;
$expire = $raw['expire'];
} else {
$value = $step;
$expire = 0;
}
return $this->set($name, $value, $expire) ? $value : false;
}
/**
* 自减缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function dec($name, $step = 1)
{
return $this->inc($name, -$step);
}
/**
* 删除缓存
* @access public
* @param string $name 缓存变量名
* @return bool
*/
public function delete($name): bool
{
return $this->unlink($this->getCacheKey($name));
}
/**
* 清除缓存
* @access public
* @return bool
*/
public function clear(): bool
{
$dirname = $this->options['path'] . $this->options['prefix'];
$this->rmdir($dirname);
return true;
}
/**
* 删除缓存标签
* @access public
* @param array $keys 缓存标识列表
* @return void
*/
public function clearTag($keys): void
{
foreach ($keys as $key) {
$this->unlink($key);
}
}
/**
* 判断文件是否存在后,删除
* @access private
* @param string $path
* @return bool
*/
private function unlink(string $path): bool
{
try {
return is_file($path) && unlink($path);
} catch (\Exception $e) {
return false;
}
}
/**
* 删除文件夹
* @param $dirname
* @return bool
*/
private function rmdir($dirname)
{
if (!is_dir($dirname)) {
return false;
}
$items = new FilesystemIterator($dirname);
foreach ($items as $item) {
if ($item->isDir() && !$item->isLink()) {
$this->rmdir($item->getPathname());
} else {
$this->unlink($item->getPathname());
}
}
@rmdir($dirname);
return true;
}
}

View File

@@ -0,0 +1,198 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think\cache\driver;
use DateInterval;
use DateTimeInterface;
use think\cache\Driver;
/**
* Memcache缓存类
*/
class Memcache extends Driver
{
/**
* 配置参数
* @var array
*/
protected $options = [
'host' => '127.0.0.1',
'port' => 11211,
'expire' => 0,
'timeout' => 0, // 超时时间(单位:毫秒)
'persistent' => true,
'prefix' => '',
'tag_prefix' => 'tag:',
'serialize' => [],
];
/**
* 架构函数
* @access public
* @param array $options 缓存参数
* @throws \BadFunctionCallException
*/
public function __construct(array $options = [])
{
if (!extension_loaded('memcache')) {
throw new \BadFunctionCallException('not support: memcache');
}
if (!empty($options)) {
$this->options = array_merge($this->options, $options);
}
$this->handler = new \Memcache;
// 支持集群
$hosts = (array) $this->options['host'];
$ports = (array) $this->options['port'];
if (empty($ports[0])) {
$ports[0] = 11211;
}
// 建立连接
foreach ($hosts as $i => $host) {
$port = $ports[$i] ?? $ports[0];
$this->options['timeout'] > 0 ?
$this->handler->addServer($host, (int) $port, $this->options['persistent'], 1, (int) $this->options['timeout']) :
$this->handler->addServer($host, (int) $port, $this->options['persistent'], 1);
}
}
/**
* 判断缓存
* @access public
* @param string $name 缓存变量名
* @return bool
*/
public function has($name): bool
{
$key = $this->getCacheKey($name);
return false !== $this->handler->get($key);
}
/**
* 读取缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $default 默认值
* @return mixed
*/
public function get($name, $default = null): mixed
{
$result = $this->handler->get($this->getCacheKey($name));
return false !== $result ? $this->unserialize($result) : $default;
}
/**
* 写入缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param int|DateTimeInterface|DateInterval $expire 有效时间(秒)
* @return bool
*/
public function set($name, $value, $expire = null): bool
{
if (is_null($expire)) {
$expire = $this->options['expire'];
}
$key = $this->getCacheKey($name);
$expire = $this->getExpireTime($expire);
$value = $this->serialize($value);
if ($this->handler->set($key, $value, 0, $expire)) {
return true;
}
return false;
}
/**
* 自增缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function inc($name, $step = 1)
{
$key = $this->getCacheKey($name);
if ($this->handler->get($key)) {
return $this->handler->increment($key, $step);
}
return $this->handler->set($key, $step);
}
/**
* 自减缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function dec($name, $step = 1)
{
$key = $this->getCacheKey($name);
$value = $this->handler->get($key) - $step;
$res = $this->handler->set($key, $value);
return !$res ? false : $value;
}
/**
* 删除缓存
* @access public
* @param string $name 缓存变量名
* @param bool|false $ttl
* @return bool
*/
public function delete($name, $ttl = false): bool
{
$key = $this->getCacheKey($name);
return false === $ttl ?
$this->handler->delete($key) :
$this->handler->delete($key, $ttl);
}
/**
* 清除缓存
* @access public
* @return bool
*/
public function clear(): bool
{
return $this->handler->flush();
}
/**
* 删除缓存标签
* @access public
* @param array $keys 缓存标识列表
* @return void
*/
public function clearTag($keys): void
{
foreach ($keys as $key) {
$this->handler->delete($key);
}
}
}

View File

@@ -0,0 +1,210 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think\cache\driver;
use DateInterval;
use DateTimeInterface;
use think\cache\Driver;
/**
* Memcached缓存类
*/
class Memcached extends Driver
{
/**
* 配置参数
* @var array
*/
protected $options = [
'host' => '127.0.0.1',
'port' => 11211,
'expire' => 0,
'timeout' => 0, // 超时时间(单位:毫秒)
'prefix' => '',
'username' => '', //账号
'password' => '', //密码
'option' => [],
'tag_prefix' => 'tag:',
'serialize' => [],
];
/**
* 架构函数
* @access public
* @param array $options 缓存参数
*/
public function __construct(array $options = [])
{
if (!extension_loaded('memcached')) {
throw new \BadFunctionCallException('not support: memcached');
}
if (!empty($options)) {
$this->options = array_merge($this->options, $options);
}
$this->handler = new \Memcached;
if (!empty($this->options['option'])) {
$this->handler->setOptions($this->options['option']);
}
// 设置连接超时时间(单位:毫秒)
if ($this->options['timeout'] > 0) {
$this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->options['timeout']);
}
// 支持集群
$hosts = (array) $this->options['host'];
$ports = (array) $this->options['port'];
if (empty($ports[0])) {
$ports[0] = 11211;
}
// 建立连接
$servers = [];
foreach ($hosts as $i => $host) {
$servers[] = [$host, $ports[$i] ?? $ports[0], 1];
}
$this->handler->addServers($servers);
if ('' != $this->options['username']) {
$this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
$this->handler->setSaslAuthData($this->options['username'], $this->options['password']);
}
}
/**
* 判断缓存
* @access public
* @param string $name 缓存变量名
* @return bool
*/
public function has($name): bool
{
$key = $this->getCacheKey($name);
return $this->handler->get($key) ? true : false;
}
/**
* 读取缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $default 默认值
* @return mixed
*/
public function get($name, $default = null): mixed
{
$result = $this->handler->get($this->getCacheKey($name));
return false !== $result ? $this->unserialize($result) : $default;
}
/**
* 写入缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param integer|DateInterval|DateTimeInterface $expire 有效时间(秒)
* @return bool
*/
public function set($name, $value, $expire = null): bool
{
if (is_null($expire)) {
$expire = $this->options['expire'];
}
$key = $this->getCacheKey($name);
$expire = $this->getExpireTime($expire);
$value = $this->serialize($value);
if ($this->handler->set($key, $value, $expire)) {
return true;
}
return false;
}
/**
* 自增缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function inc($name, $step = 1)
{
$key = $this->getCacheKey($name);
if ($this->handler->get($key)) {
return $this->handler->increment($key, $step);
}
return $this->handler->set($key, $step);
}
/**
* 自减缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function dec($name, $step = 1)
{
$key = $this->getCacheKey($name);
$value = $this->handler->get($key) - $step;
$res = $this->handler->set($key, $value);
return !$res ? false : $value;
}
/**
* 删除缓存
* @access public
* @param string $name 缓存变量名
* @param bool|false $ttl
* @return bool
*/
public function delete($name, $ttl = false): bool
{
$key = $this->getCacheKey($name);
return false === $ttl ?
$this->handler->delete($key) :
$this->handler->delete($key, $ttl);
}
/**
* 清除缓存
* @access public
* @return bool
*/
public function clear(): bool
{
return $this->handler->flush();
}
/**
* 删除缓存标签
* @access public
* @param array $keys 缓存标识列表
* @return void
*/
public function clearTag($keys): void
{
$this->handler->deleteMulti($keys);
}
}

View File

@@ -0,0 +1,242 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think\cache\driver;
use DateInterval;
use DateTimeInterface;
use think\cache\Driver;
class Redis extends Driver
{
/** @var \Predis\Client|\Redis */
protected $handler;
/**
* 配置参数
* @var array
*/
protected $options = [
'host' => '127.0.0.1',
'port' => 6379,
'password' => '',
'select' => 0,
'timeout' => 0,
'expire' => 0,
'persistent' => false,
'prefix' => '',
'tag_prefix' => 'tag:',
'serialize' => [],
];
/**
* 架构函数
* @access public
* @param array $options 缓存参数
*/
public function __construct(array $options = [])
{
if (!empty($options)) {
$this->options = array_merge($this->options, $options);
}
}
public function handler()
{
if (!$this->handler) {
if (extension_loaded('redis')) {
$this->handler = new \Redis;
if ($this->options['persistent']) {
$this->handler->pconnect($this->options['host'], (int) $this->options['port'], (int) $this->options['timeout'], 'persistent_id_' . $this->options['select']);
} else {
$this->handler->connect($this->options['host'], (int) $this->options['port'], (int) $this->options['timeout']);
}
if ('' != $this->options['password']) {
$this->handler->auth($this->options['password']);
}
} elseif (class_exists('\Predis\Client')) {
$params = [];
foreach ($this->options as $key => $val) {
if (in_array($key, ['aggregate', 'cluster', 'connections', 'exceptions', 'prefix', 'profile', 'replication', 'parameters'])) {
$params[$key] = $val;
unset($this->options[$key]);
}
}
if ('' == $this->options['password']) {
unset($this->options['password']);
}
$this->handler = new \Predis\Client($this->options, $params);
$this->options['prefix'] = '';
} else {
throw new \BadFunctionCallException('not support: redis');
}
if (0 != $this->options['select']) {
$this->handler->select((int) $this->options['select']);
}
}
return $this->handler;
}
/**
* 判断缓存
* @access public
* @param string $name 缓存变量名
* @return bool
*/
public function has($name): bool
{
return $this->handler()->exists($this->getCacheKey($name)) ? true : false;
}
/**
* 读取缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $default 默认值
* @return mixed
*/
public function get($name, $default = null): mixed
{
$key = $this->getCacheKey($name);
$value = $this->handler()->get($key);
if (false === $value || is_null($value)) {
return $default;
}
return $this->unserialize($value);
}
/**
* 写入缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param integer|DateInterval|DateTimeInterface $expire 有效时间(秒)
* @return bool
*/
public function set($name, $value, $expire = null): bool
{
if (is_null($expire)) {
$expire = $this->options['expire'];
}
$key = $this->getCacheKey($name);
$expire = $this->getExpireTime($expire);
$value = $this->serialize($value);
if ($expire) {
$this->handler()->setex($key, $expire, $value);
} else {
$this->handler()->set($key, $value);
}
return true;
}
/**
* 自增缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function inc($name, $step = 1)
{
$key = $this->getCacheKey($name);
return $this->handler()->incrby($key, $step);
}
/**
* 自减缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function dec($name, $step = 1)
{
$key = $this->getCacheKey($name);
return $this->handler()->decrby($key, $step);
}
/**
* 删除缓存
* @access public
* @param string $name 缓存变量名
* @return bool
*/
public function delete($name): bool
{
$key = $this->getCacheKey($name);
$result = $this->handler()->del($key);
return $result > 0;
}
/**
* 清除缓存
* @access public
* @return bool
*/
public function clear(): bool
{
$this->handler()->flushDB();
return true;
}
/**
* 删除缓存标签
* @access public
* @param array $keys 缓存标识列表
* @return void
*/
public function clearTag($keys): void
{
// 指定标签清除
$this->handler()->del($keys);
}
/**
* 追加TagSet数据
* @access public
* @param string $name 缓存标识
* @param mixed $value 数据
* @return void
*/
public function append($name, $value): void
{
$key = $this->getCacheKey($name);
$this->handler()->sAdd($key, $value);
}
/**
* 获取标签包含的缓存标识
* @access public
* @param string $tag 缓存标签
* @return array
*/
public function getTagItems($tag): array
{
$name = $this->getTagKey($tag);
$key = $this->getCacheKey($name);
return $this->handler()->sMembers($key);
}
}

View File

@@ -0,0 +1,165 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think\cache\driver;
use DateInterval;
use DateTimeInterface;
use think\cache\Driver;
/**
* Wincache缓存驱动
*/
class Wincache extends Driver
{
/**
* 配置参数
* @var array
*/
protected $options = [
'prefix' => '',
'expire' => 0,
'tag_prefix' => 'tag:',
'serialize' => [],
];
/**
* 架构函数
* @access public
* @param array $options 缓存参数
* @throws \BadFunctionCallException
*/
public function __construct(array $options = [])
{
if (!function_exists('wincache_ucache_info')) {
throw new \BadFunctionCallException('not support: WinCache');
}
if (!empty($options)) {
$this->options = array_merge($this->options, $options);
}
}
/**
* 判断缓存
* @access public
* @param string $name 缓存变量名
* @return bool
*/
public function has($name): bool
{
$this->readTimes++;
$key = $this->getCacheKey($name);
return wincache_ucache_exists($key);
}
/**
* 读取缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $default 默认值
* @return mixed
*/
public function get($name, $default = null): mixed
{
$key = $this->getCacheKey($name);
return wincache_ucache_exists($key) ? $this->unserialize(wincache_ucache_get($key)) : $default;
}
/**
* 写入缓存
* @access public
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param integer|DateInterval|DateTimeInterface $expire 有效时间(秒)
* @return bool
*/
public function set($name, $value, $expire = null): bool
{
if (is_null($expire)) {
$expire = $this->options['expire'];
}
$key = $this->getCacheKey($name);
$expire = $this->getExpireTime($expire);
$value = $this->serialize($value);
if (wincache_ucache_set($key, $value, $expire)) {
return true;
}
return false;
}
/**
* 自增缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function inc($name, $step = 1)
{
$key = $this->getCacheKey($name);
return wincache_ucache_inc($key, $step);
}
/**
* 自减缓存(针对数值缓存)
* @access public
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function dec($name, $step = 1)
{
$key = $this->getCacheKey($name);
return wincache_ucache_dec($key, $step);
}
/**
* 删除缓存
* @access public
* @param string $name 缓存变量名
* @return bool
*/
public function delete($name): bool
{
return wincache_ucache_delete($this->getCacheKey($name));
}
/**
* 清除缓存
* @access public
* @return bool
*/
public function clear(): bool
{
return wincache_ucache_clear();
}
/**
* 删除缓存标签
* @access public
* @param array $keys 缓存标识列表
* @return void
*/
public function clearTag($keys): void
{
wincache_ucache_delete($keys);
}
}

View File

@@ -0,0 +1,504 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think\console;
use Exception;
use InvalidArgumentException;
use LogicException;
use think\App;
use think\Console;
use think\console\input\Argument;
use think\console\input\Definition;
use think\console\input\Option;
abstract class Command
{
/** @var Console */
private $console;
private $name;
private $processTitle;
private $aliases = [];
private $definition;
private $help;
private $description;
private $ignoreValidationErrors = false;
private $consoleDefinitionMerged = false;
private $consoleDefinitionMergedWithArgs = false;
private $synopsis = [];
private $usages = [];
/** @var Input */
protected $input;
/** @var Output */
protected $output;
/** @var App */
protected $app;
/**
* 构造方法
* @throws LogicException
* @api
*/
public function __construct()
{
$this->definition = new Definition();
$this->configure();
if (!$this->name) {
throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this)));
}
}
/**
* 忽略验证错误
*/
public function ignoreValidationErrors(): void
{
$this->ignoreValidationErrors = true;
}
/**
* 设置控制台
* @param Console $console
*/
public function setConsole(Console $console = null): void
{
$this->console = $console;
}
/**
* 获取控制台
* @return Console
* @api
*/
public function getConsole(): Console
{
return $this->console;
}
/**
* 设置app
* @param App $app
*/
public function setApp(App $app)
{
$this->app = $app;
}
/**
* 获取app
* @return App
*/
public function getApp()
{
return $this->app;
}
/**
* 是否有效
* @return bool
*/
public function isEnabled(): bool
{
return true;
}
/**
* 配置指令
*/
protected function configure()
{
}
/**
* 执行指令
* @param Input $input
* @param Output $output
* @return null|int
* @throws LogicException
* @see setCode()
*/
protected function execute(Input $input, Output $output)
{
return $this->app->invoke([$this, 'handle']);
}
/**
* 用户验证
* @param Input $input
* @param Output $output
*/
protected function interact(Input $input, Output $output)
{
}
/**
* 初始化
* @param Input $input An InputInterface instance
* @param Output $output An OutputInterface instance
*/
protected function initialize(Input $input, Output $output)
{
}
/**
* 执行
* @param Input $input
* @param Output $output
* @return int
* @throws Exception
* @see setCode()
* @see execute()
*/
public function run(Input $input, Output $output): int
{
$this->input = $input;
$this->output = $output;
$this->getSynopsis(true);
$this->getSynopsis(false);
$this->mergeConsoleDefinition();
try {
$input->bind($this->definition);
} catch (Exception $e) {
if (!$this->ignoreValidationErrors) {
throw $e;
}
}
$this->initialize($input, $output);
if (null !== $this->processTitle) {
if (function_exists('cli_set_process_title')) {
if (false === @cli_set_process_title($this->processTitle)) {
if ('Darwin' === PHP_OS) {
$output->writeln('<comment>Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.</comment>');
} else {
$error = error_get_last();
trigger_error($error['message'], E_USER_WARNING);
}
}
} elseif (function_exists('setproctitle')) {
setproctitle($this->processTitle);
} elseif (Output::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) {
$output->writeln('<comment>Install the proctitle PECL to be able to change the process title.</comment>');
}
}
if ($input->isInteractive()) {
$this->interact($input, $output);
}
$input->validate();
$statusCode = $this->execute($input, $output);
return is_numeric($statusCode) ? (int) $statusCode : 0;
}
/**
* 合并参数定义
* @param bool $mergeArgs
*/
public function mergeConsoleDefinition(bool $mergeArgs = true)
{
if (null === $this->console
|| (true === $this->consoleDefinitionMerged
&& ($this->consoleDefinitionMergedWithArgs || !$mergeArgs))
) {
return;
}
if ($mergeArgs) {
$currentArguments = $this->definition->getArguments();
$this->definition->setArguments($this->console->getDefinition()->getArguments());
$this->definition->addArguments($currentArguments);
}
$this->definition->addOptions($this->console->getDefinition()->getOptions());
$this->consoleDefinitionMerged = true;
if ($mergeArgs) {
$this->consoleDefinitionMergedWithArgs = true;
}
}
/**
* 设置参数定义
* @param array|Definition $definition
* @return Command
* @api
*/
public function setDefinition($definition)
{
if ($definition instanceof Definition) {
$this->definition = $definition;
} else {
$this->definition->setDefinition($definition);
}
$this->consoleDefinitionMerged = false;
return $this;
}
/**
* 获取参数定义
* @return Definition
* @api
*/
public function getDefinition(): Definition
{
return $this->definition;
}
/**
* 获取当前指令的参数定义
* @return Definition
*/
public function getNativeDefinition(): Definition
{
return $this->getDefinition();
}
/**
* 添加参数
* @param string $name 名称
* @param int $mode 类型
* @param string $description 描述
* @param mixed $default 默认值
* @return Command
*/
public function addArgument(string $name, int $mode = null, string $description = '', $default = null)
{
$this->definition->addArgument(new Argument($name, $mode, $description, $default));
return $this;
}
/**
* 添加选项
* @param string $name 选项名称
* @param string $shortcut 别名
* @param int $mode 类型
* @param string $description 描述
* @param mixed $default 默认值
* @return Command
*/
public function addOption(string $name, string $shortcut = null, int $mode = null, string $description = '', $default = null)
{
$this->definition->addOption(new Option($name, $shortcut, $mode, $description, $default));
return $this;
}
/**
* 设置指令名称
* @param string $name
* @return Command
* @throws InvalidArgumentException
*/
public function setName(string $name)
{
$this->validateName($name);
$this->name = $name;
return $this;
}
/**
* 设置进程名称
*
* PHP 5.5+ or the proctitle PECL library is required
*
* @param string $title The process title
*
* @return $this
*/
public function setProcessTitle($title)
{
$this->processTitle = $title;
return $this;
}
/**
* 获取指令名称
* @return string
*/
public function getName(): string
{
return $this->name ?: '';
}
/**
* 设置描述
* @param string $description
* @return Command
*/
public function setDescription(string $description)
{
$this->description = $description;
return $this;
}
/**
* 获取描述
* @return string
*/
public function getDescription(): string
{
return $this->description ?: '';
}
/**
* 设置帮助信息
* @param string $help
* @return Command
*/
public function setHelp(string $help)
{
$this->help = $help;
return $this;
}
/**
* 获取帮助信息
* @return string
*/
public function getHelp(): string
{
return $this->help ?: '';
}
/**
* 描述信息
* @return string
*/
public function getProcessedHelp(): string
{
$name = $this->name;
$placeholders = [
'%command.name%',
'%command.full_name%',
];
$replacements = [
$name,
$_SERVER['PHP_SELF'] . ' ' . $name,
];
return str_replace($placeholders, $replacements, $this->getHelp());
}
/**
* 设置别名
* @param string[] $aliases
* @return Command
* @throws InvalidArgumentException
*/
public function setAliases(iterable $aliases)
{
foreach ($aliases as $alias) {
$this->validateName($alias);
}
$this->aliases = $aliases;
return $this;
}
/**
* 获取别名
* @return array
*/
public function getAliases(): array
{
return $this->aliases;
}
/**
* 获取简介
* @param bool $short 是否简单的
* @return string
*/
public function getSynopsis(bool $short = false): string
{
$key = $short ? 'short' : 'long';
if (!isset($this->synopsis[$key])) {
$this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short)));
}
return $this->synopsis[$key];
}
/**
* 添加用法介绍
* @param string $usage
* @return $this
*/
public function addUsage(string $usage)
{
if (!str_starts_with($usage, $this->name)) {
$usage = sprintf('%s %s', $this->name, $usage);
}
$this->usages[] = $usage;
return $this;
}
/**
* 获取用法介绍
* @return array
*/
public function getUsages(): array
{
return $this->usages;
}
/**
* 验证指令名称
* @param string $name
* @throws InvalidArgumentException
*/
private function validateName(string $name)
{
if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
}
}
/**
* 输出表格
* @param Table $table
* @return string
*/
protected function table(Table $table): string
{
$content = $table->render();
$this->output->writeln($content);
return $content;
}
}

View File

@@ -0,0 +1,465 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think\console;
use think\console\input\Argument;
use think\console\input\Definition;
use think\console\input\Option;
class Input
{
/**
* @var Definition
*/
protected $definition;
/**
* @var Option[]
*/
protected $options = [];
/**
* @var Argument[]
*/
protected $arguments = [];
protected $interactive = true;
private $tokens;
private $parsed;
public function __construct($argv = null)
{
if (null === $argv) {
$argv = $_SERVER['argv'];
// 去除命令名
array_shift($argv);
}
$this->tokens = $argv;
$this->definition = new Definition();
}
protected function setTokens(array $tokens)
{
$this->tokens = $tokens;
}
/**
* 绑定实例
* @param Definition $definition A InputDefinition instance
*/
public function bind(Definition $definition): void
{
$this->arguments = [];
$this->options = [];
$this->definition = $definition;
$this->parse();
}
/**
* 解析参数
*/
protected function parse(): void
{
$parseOptions = true;
$this->parsed = $this->tokens;
while (null !== $token = array_shift($this->parsed)) {
if ($parseOptions && '' == $token) {
$this->parseArgument($token);
} elseif ($parseOptions && '--' == $token) {
$parseOptions = false;
} elseif ($parseOptions && str_starts_with($token, '--')) {
$this->parseLongOption($token);
} elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
$this->parseShortOption($token);
} else {
$this->parseArgument($token);
}
}
}
/**
* 解析短选项
* @param string $token 当前的指令.
*/
private function parseShortOption(string $token): void
{
$name = substr($token, 1);
if (strlen($name) > 1) {
if ($this->definition->hasShortcut($name[0])
&& $this->definition->getOptionForShortcut($name[0])->acceptValue()
) {
$this->addShortOption($name[0], substr($name, 1));
} else {
$this->parseShortOptionSet($name);
}
} else {
$this->addShortOption($name, null);
}
}
/**
* 解析短选项
* @param string $name 当前指令
* @throws \RuntimeException
*/
private function parseShortOptionSet(string $name): void
{
$len = strlen($name);
for ($i = 0; $i < $len; ++$i) {
if (!$this->definition->hasShortcut($name[$i])) {
throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i]));
}
$option = $this->definition->getOptionForShortcut($name[$i]);
if ($option->acceptValue()) {
$this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));
break;
} else {
$this->addLongOption($option->getName(), null);
}
}
}
/**
* 解析完整选项
* @param string $token 当前指令
*/
private function parseLongOption(string $token): void
{
$name = substr($token, 2);
if (false !== $pos = strpos($name, '=')) {
$this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1));
} else {
$this->addLongOption($name, null);
}
}
/**
* 解析参数
* @param string $token 当前指令
* @throws \RuntimeException
*/
private function parseArgument(string $token): void
{
$c = count($this->arguments);
if ($this->definition->hasArgument($c)) {
$arg = $this->definition->getArgument($c);
$this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token;
} elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) {
$arg = $this->definition->getArgument($c - 1);
$this->arguments[$arg->getName()][] = $token;
} else {
throw new \RuntimeException('Too many arguments.');
}
}
/**
* 添加一个短选项的值
* @param string $shortcut 短名称
* @param mixed $value 值
* @throws \RuntimeException
*/
private function addShortOption(string $shortcut, $value): void
{
if (!$this->definition->hasShortcut($shortcut)) {
throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
}
$this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
}
/**
* 添加一个完整选项的值
* @param string $name 选项名
* @param mixed $value 值
* @throws \RuntimeException
*/
private function addLongOption(string $name, $value): void
{
if (!$this->definition->hasOption($name)) {
throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name));
}
$option = $this->definition->getOption($name);
if (false === $value) {
$value = null;
}
if (null !== $value && !$option->acceptValue()) {
throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name, $value));
}
if (null === $value && $option->acceptValue() && count($this->parsed)) {
$next = array_shift($this->parsed);
if (isset($next[0]) && '-' !== $next[0]) {
$value = $next;
} elseif (empty($next)) {
$value = '';
} else {
array_unshift($this->parsed, $next);
}
}
if (null === $value) {
if ($option->isValueRequired()) {
throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name));
}
if (!$option->isArray()) {
$value = $option->isValueOptional() ? $option->getDefault() : true;
}
}
if ($option->isArray()) {
$this->options[$name][] = $value;
} else {
$this->options[$name] = $value;
}
}
/**
* 获取第一个参数
* @return string|null
*/
public function getFirstArgument()
{
foreach ($this->tokens as $token) {
if ($token && '-' === $token[0]) {
continue;
}
return $token;
}
return;
}
/**
* 检查原始参数是否包含某个值
* @param string|array $values 需要检查的值
* @return bool
*/
public function hasParameterOption($values): bool
{
$values = (array) $values;
foreach ($this->tokens as $token) {
foreach ($values as $value) {
if ($token === $value || str_starts_with($token, $value . '=')) {
return true;
}
}
}
return false;
}
/**
* 获取原始选项的值
* @param string|array $values 需要检查的值
* @param mixed $default 默认值
* @return mixed The option value
*/
public function getParameterOption($values, $default = false)
{
$values = (array) $values;
$tokens = $this->tokens;
while (0 < count($tokens)) {
$token = array_shift($tokens);
foreach ($values as $value) {
if ($token === $value || str_starts_with($token, $value . '=')) {
if (false !== $pos = strpos($token, '=')) {
return substr($token, $pos + 1);
}
return array_shift($tokens);
}
}
}
return $default;
}
/**
* 验证输入
* @throws \RuntimeException
*/
public function validate()
{
if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) {
throw new \RuntimeException('Not enough arguments.');
}
}
/**
* 检查输入是否是交互的
* @return bool
*/
public function isInteractive(): bool
{
return $this->interactive;
}
/**
* 设置输入的交互
* @param bool
*/
public function setInteractive(bool $interactive): void
{
$this->interactive = $interactive;
}
/**
* 获取所有的参数
* @return Argument[]
*/
public function getArguments(): array
{
return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
}
/**
* 根据名称获取参数
* @param string $name 参数名
* @return mixed
* @throws \InvalidArgumentException
*/
public function getArgument(string $name)
{
if (!$this->definition->hasArgument($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
return $this->arguments[$name] ?? $this->definition->getArgument($name)
->getDefault();
}
/**
* 设置参数的值
* @param string $name 参数名
* @param string $value 值
* @throws \InvalidArgumentException
*/
public function setArgument(string $name, $value)
{
if (!$this->definition->hasArgument($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
$this->arguments[$name] = $value;
}
/**
* 检查是否存在某个参数
* @param string|int $name 参数名或位置
* @return bool
*/
public function hasArgument(string|int $name): bool
{
return $this->definition->hasArgument($name);
}
/**
* 获取所有的选项
* @return Option[]
*/
public function getOptions(): array
{
return array_merge($this->definition->getOptionDefaults(), $this->options);
}
/**
* 获取选项值
* @param string $name 选项名称
* @return mixed
* @throws \InvalidArgumentException
*/
public function getOption(string $name)
{
if (!$this->definition->hasOption($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}
return $this->options[$name] ?? $this->definition->getOption($name)->getDefault();
}
/**
* 设置选项值
* @param string $name 选项名
* @param string|bool $value 值
* @throws \InvalidArgumentException
*/
public function setOption(string $name, $value): void
{
if (!$this->definition->hasOption($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}
$this->options[$name] = $value;
}
/**
* 是否有某个选项
* @param string $name 选项名
* @return bool
*/
public function hasOption(string $name): bool
{
return $this->definition->hasOption($name) && isset($this->options[$name]);
}
/**
* 转义指令
* @param string $token
* @return string
*/
public function escapeToken(string $token): string
{
return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
}
/**
* 返回传递给命令的参数的字符串
* @return string
*/
public function __toString()
{
$tokens = array_map(function ($token) {
if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
return $match[1] . $this->escapeToken($match[2]);
}
if ($token && '-' !== $token[0]) {
return $this->escapeToken($token);
}
return $token;
}, $this->tokens);
return implode(' ', $tokens);
}
}

View File

@@ -0,0 +1,19 @@
Copyright (c) 2004-2016 Fabien Potencier
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.

View File

@@ -0,0 +1,231 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2020 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think\console;
use Exception;
use think\console\output\Ask;
use think\console\output\Descriptor;
use think\console\output\driver\Buffer;
use think\console\output\driver\Console;
use think\console\output\driver\Nothing;
use think\console\output\Question;
use think\console\output\question\Choice;
use think\console\output\question\Confirmation;
use Throwable;
/**
* Class Output
* @package think\console
*
* @see \think\console\output\driver\Console::setDecorated
* @method void setDecorated($decorated)
*
* @see \think\console\output\driver\Buffer::fetch
* @method string fetch()
*
* @method void info($message)
* @method void error($message)
* @method void comment($message)
* @method void warning($message)
* @method void highlight($message)
* @method void question($message)
*/
class Output
{
// 不显示信息(静默)
const VERBOSITY_QUIET = 0;
// 正常信息
const VERBOSITY_NORMAL = 1;
// 详细信息
const VERBOSITY_VERBOSE = 2;
// 非常详细的信息
const VERBOSITY_VERY_VERBOSE = 3;
// 调试信息
const VERBOSITY_DEBUG = 4;
const OUTPUT_NORMAL = 0;
const OUTPUT_RAW = 1;
const OUTPUT_PLAIN = 2;
// 输出信息级别
private $verbosity = self::VERBOSITY_NORMAL;
/** @var Buffer|Console|Nothing */
private $handle = null;
protected $styles = [
'info',
'error',
'comment',
'question',
'highlight',
'warning',
];
public function __construct($driver = 'console')
{
$class = '\\think\\console\\output\\driver\\' . ucwords($driver);
$this->handle = new $class($this);
}
public function ask(Input $input, $question, $default = null, $validator = null)
{
$question = new Question($question, $default);
$question->setValidator($validator);
return $this->askQuestion($input, $question);
}
public function askHidden(Input $input, $question, $validator = null)
{
$question = new Question($question);
$question->setHidden(true);
$question->setValidator($validator);
return $this->askQuestion($input, $question);
}
public function confirm(Input $input, $question, $default = true)
{
return $this->askQuestion($input, new Confirmation($question, $default));
}
/**
* {@inheritdoc}
*/
public function choice(Input $input, $question, array $choices, $default = null)
{
if (null !== $default) {
$values = array_flip($choices);
$default = $values[$default];
}
return $this->askQuestion($input, new Choice($question, $choices, $default));
}
protected function askQuestion(Input $input, Question $question)
{
$ask = new Ask($input, $this, $question);
$answer = $ask->run();
if ($input->isInteractive()) {
$this->newLine();
}
return $answer;
}
protected function block(string $style, string $message): void
{
$this->writeln("<{$style}>{$message}</$style>");
}
/**
* 输出空行
* @param int $count
*/
public function newLine(int $count = 1): void
{
$this->write(str_repeat(PHP_EOL, $count));
}
/**
* 输出信息并换行
* @param string $messages
* @param int $type
*/
public function writeln(string $messages, int $type = 0): void
{
$this->write($messages, true, $type);
}
/**
* 输出信息
* @param string $messages
* @param bool $newline
* @param int $type
*/
public function write(string $messages, bool $newline = false, int $type = 0): void
{
$this->handle->write($messages, $newline, $type);
}
public function renderException(Throwable $e): void
{
$this->handle->renderException($e);
}
/**
* 设置输出信息级别
* @param int $level 输出信息级别
*/
public function setVerbosity(int $level)
{
$this->verbosity = $level;
}
/**
* 获取输出信息级别
* @return int
*/
public function getVerbosity(): int
{
return $this->verbosity;
}
public function isQuiet(): bool
{
return self::VERBOSITY_QUIET === $this->verbosity;
}
public function isVerbose(): bool
{
return self::VERBOSITY_VERBOSE <= $this->verbosity;
}
public function isVeryVerbose(): bool
{
return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
}
public function isDebug(): bool
{
return self::VERBOSITY_DEBUG <= $this->verbosity;
}
public function describe($object, array $options = []): void
{
$descriptor = new Descriptor();
$options = array_merge([
'raw_text' => false,
], $options);
$descriptor->describe($this, $object, $options);
}
public function __call($method, $args)
{
if (in_array($method, $this->styles)) {
array_unshift($args, $method);
return call_user_func_array([$this, 'block'], $args);
}
if ($this->handle && method_exists($this->handle, $method)) {
return call_user_func_array([$this->handle, $method], $args);
} else {
throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
}
}
}

View File

@@ -0,0 +1,300 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think\console;
class Table
{
const ALIGN_LEFT = 1;
const ALIGN_RIGHT = 0;
const ALIGN_CENTER = 2;
/**
* 头信息数据
* @var array
*/
protected $header = [];
/**
* 头部对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
* @var int
*/
protected $headerAlign = 1;
/**
* 表格数据(二维数组)
* @var array
*/
protected $rows = [];
/**
* 单元格对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
* @var int
*/
protected $cellAlign = 1;
/**
* 单元格宽度信息
* @var array
*/
protected $colWidth = [];
/**
* 表格输出样式
* @var string
*/
protected $style = 'default';
/**
* 表格样式定义
* @var array
*/
protected $format = [
'compact' => [],
'default' => [
'top' => ['+', '-', '+', '+'],
'cell' => ['|', ' ', '|', '|'],
'middle' => ['+', '-', '+', '+'],
'bottom' => ['+', '-', '+', '+'],
'cross-top' => ['+', '-', '-', '+'],
'cross-bottom' => ['+', '-', '-', '+'],
],
'markdown' => [
'top' => [' ', ' ', ' ', ' '],
'cell' => ['|', ' ', '|', '|'],
'middle' => ['|', '-', '|', '|'],
'bottom' => [' ', ' ', ' ', ' '],
'cross-top' => ['|', ' ', ' ', '|'],
'cross-bottom' => ['|', ' ', ' ', '|'],
],
'borderless' => [
'top' => ['=', '=', ' ', '='],
'cell' => [' ', ' ', ' ', ' '],
'middle' => ['=', '=', ' ', '='],
'bottom' => ['=', '=', ' ', '='],
'cross-top' => ['=', '=', ' ', '='],
'cross-bottom' => ['=', '=', ' ', '='],
],
'box' => [
'top' => ['┌', '─', '┬', '┐'],
'cell' => ['│', ' ', '│', '│'],
'middle' => ['├', '─', '┼', '┤'],
'bottom' => ['└', '─', '┴', '┘'],
'cross-top' => ['├', '─', '┴', '┤'],
'cross-bottom' => ['├', '─', '┬', '┤'],
],
'box-double' => [
'top' => ['╔', '═', '╤', '╗'],
'cell' => ['║', ' ', '│', '║'],
'middle' => ['╠', '─', '╪', '╣'],
'bottom' => ['╚', '═', '╧', '╝'],
'cross-top' => ['╠', '═', '╧', '╣'],
'cross-bottom' => ['╠', '═', '╤', '╣'],
],
];
/**
* 设置表格头信息 以及对齐方式
* @access public
* @param array $header 要输出的Header信息
* @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
* @return void
*/
public function setHeader(array $header, int $align = 1): void
{
$this->header = $header;
$this->headerAlign = $align;
$this->checkColWidth($header);
}
/**
* 设置输出表格数据 及对齐方式
* @access public
* @param array $rows 要输出的表格数据(二维数组)
* @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
* @return void
*/
public function setRows(array $rows, int $align = 1): void
{
$this->rows = $rows;
$this->cellAlign = $align;
foreach ($rows as $row) {
$this->checkColWidth($row);
}
}
/**
* 设置全局单元格对齐方式
* @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
* @return $this
*/
public function setCellAlign(int $align = 1)
{
$this->cellAlign = $align;
return $this;
}
/**
* 检查列数据的显示宽度
* @access public
* @param mixed $row 行数据
* @return void
*/
protected function checkColWidth($row): void
{
if (is_array($row)) {
foreach ($row as $key => $cell) {
$width = mb_strwidth((string) $cell);
if (!isset($this->colWidth[$key]) || $width > $this->colWidth[$key]) {
$this->colWidth[$key] = $width;
}
}
}
}
/**
* 增加一行表格数据
* @access public
* @param mixed $row 行数据
* @param bool $first 是否在开头插入
* @return void
*/
public function addRow($row, bool $first = false): void
{
if ($first) {
array_unshift($this->rows, $row);
} else {
$this->rows[] = $row;
}
$this->checkColWidth($row);
}
/**
* 设置输出表格的样式
* @access public
* @param string $style 样式名
* @return void
*/
public function setStyle(string $style): void
{
$this->style = isset($this->format[$style]) ? $style : 'default';
}
/**
* 输出分隔行
* @access public
* @param string $pos 位置
* @return string
*/
protected function renderSeparator(string $pos): string
{
$style = $this->getStyle($pos);
$array = [];
foreach ($this->colWidth as $width) {
$array[] = str_repeat($style[1], $width + 2);
}
return $style[0] . implode($style[2], $array) . $style[3] . PHP_EOL;
}
/**
* 输出表格头部
* @access public
* @return string
*/
protected function renderHeader(): string
{
$style = $this->getStyle('cell');
$content = $this->renderSeparator('top');
foreach ($this->header as $key => $header) {
$array[] = ' ' . str_pad($header, $this->colWidth[$key], $style[1], $this->headerAlign);
}
if (!empty($array)) {
$content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL;
if (!empty($this->rows)) {
$content .= $this->renderSeparator('middle');
}
}
return $content;
}
protected function getStyle(string $style): array
{
if ($this->format[$this->style]) {
$style = $this->format[$this->style][$style];
} else {
$style = [' ', ' ', ' ', ' '];
}
return $style;
}
/**
* 输出表格
* @access public
* @param array $dataList 表格数据
* @return string
*/
public function render(array $dataList = []): string
{
if (!empty($dataList)) {
$this->setRows($dataList);
}
// 输出头部
$content = $this->renderHeader();
$style = $this->getStyle('cell');
if (!empty($this->rows)) {
foreach ($this->rows as $row) {
if (is_string($row) && '-' === $row) {
$content .= $this->renderSeparator('middle');
} elseif (is_scalar($row)) {
$content .= $this->renderSeparator('cross-top');
$width = 3 * (count($this->colWidth) - 1) + array_reduce($this->colWidth, function ($a, $b) {
return $a + $b;
});
$array = str_pad($row, $width);
$content .= $style[0] . ' ' . $array . ' ' . $style[3] . PHP_EOL;
$content .= $this->renderSeparator('cross-bottom');
} else {
$array = [];
foreach ($row as $key => $val) {
$width = $this->colWidth[$key];
// form https://github.com/symfony/console/blob/20c9821c8d1c2189f287dcee709b2f86353ea08f/Helper/Table.php#L467
// str_pad won't work properly with multi-byte strings, we need to fix the padding
if (false !== $encoding = mb_detect_encoding((string) $val, null, true)) {
$width += strlen((string) $val) - mb_strwidth((string) $val, $encoding);
}
$array[] = ' ' . str_pad((string) $val, $width, ' ', $this->cellAlign);
}
$content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL;
}
}
}
$content .= $this->renderSeparator('bottom');
return $content;
}
}

View File

@@ -0,0 +1 @@
console 工具使用 hiddeninput.exe 在 windows 上隐藏密码输入,该二进制文件由第三方提供,相关源码和其他细节可以在 [Hidden Input](https://github.com/Seldaek/hidden-input) 找到。

Binary file not shown.

View File

@@ -0,0 +1,85 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\console\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
class Clear extends Command
{
protected function configure()
{
// 指令配置
$this->setName('clear')
->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null)
->addOption('cache', 'c', Option::VALUE_NONE, 'clear cache file')
->addOption('log', 'l', Option::VALUE_NONE, 'clear log file')
->addOption('dir', 'r', Option::VALUE_NONE, 'clear empty dir')
->addOption('expire', 'e', Option::VALUE_NONE, 'clear cache file if cache has expired')
->setDescription('Clear runtime file');
}
protected function execute(Input $input, Output $output)
{
$runtimePath = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR;
if ($input->getOption('cache')) {
$path = $runtimePath . 'cache';
} elseif ($input->getOption('log')) {
$path = $runtimePath . 'log';
} else {
$path = $input->getOption('path') ?: $runtimePath;
}
$rmdir = $input->getOption('dir') ? true : false;
// --expire 仅当 --cache 时生效
$cache_expire = $input->getOption('expire') && $input->getOption('cache') ? true : false;
$this->clear(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir, $cache_expire);
$output->writeln("<info>Clear Successed</info>");
}
protected function clear(string $path, bool $rmdir, bool $cache_expire): void
{
$files = is_dir($path) ? scandir($path) : [];
foreach ($files as $file) {
if ('.' != $file && '..' != $file && is_dir($path . $file)) {
$this->clear($path . $file . DIRECTORY_SEPARATOR, $rmdir, $cache_expire);
if ($rmdir) {
@rmdir($path . $file);
}
} elseif ('.gitignore' != $file && is_file($path . $file)) {
if ($cache_expire) {
if ($this->cacheHasExpired($path . $file)) {
unlink($path . $file);
}
} else {
unlink($path . $file);
}
}
}
}
/**
* 缓存文件是否已过期
* @param $filename string 文件路径
* @return bool
*/
protected function cacheHasExpired($filename) {
$content = file_get_contents($filename);
$expire = (int) substr($content, 8, 12);
return 0 != $expire && time() - $expire > filemtime($filename);
}
}

View File

@@ -0,0 +1,70 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument as InputArgument;
use think\console\input\Option as InputOption;
use think\console\Output;
class Help extends Command
{
private $command;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this->ignoreValidationErrors();
$this->setName('help')->setDefinition([
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
])->setDescription('Displays help for a command')->setHelp(
<<<EOF
The <info>%command.name%</info> command displays help for a given command:
<info>php %command.full_name% list</info>
To display the list of available commands, please use the <info>list</info> command.
EOF
);
}
/**
* Sets the command.
* @param Command $command The command to set
*/
public function setCommand(Command $command): void
{
$this->command = $command;
}
/**
* {@inheritdoc}
*/
protected function execute(Input $input, Output $output)
{
if (null === $this->command) {
$this->command = $this->getConsole()->find($input->getArgument('command_name'));
}
$output->describe($this->command, [
'raw_text' => $input->getOption('raw'),
]);
$this->command = null;
}
}

View File

@@ -0,0 +1,74 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument as InputArgument;
use think\console\input\Definition as InputDefinition;
use think\console\input\Option as InputOption;
use think\console\Output;
class Lists extends Command
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this->setName('list')->setDefinition($this->createDefinition())->setDescription('Lists commands')->setHelp(
<<<EOF
The <info>%command.name%</info> command lists all commands:
<info>php %command.full_name%</info>
You can also display the commands for a specific namespace:
<info>php %command.full_name% test</info>
It's also possible to get raw list of commands (useful for embedding command runner):
<info>php %command.full_name% --raw</info>
EOF
);
}
/**
* {@inheritdoc}
*/
public function getNativeDefinition(): InputDefinition
{
return $this->createDefinition();
}
/**
* {@inheritdoc}
*/
protected function execute(Input $input, Output $output)
{
$output->describe($this->getConsole(), [
'raw_text' => $input->getOption('raw'),
'namespace' => $input->getArgument('namespace'),
]);
}
/**
* {@inheritdoc}
*/
private function createDefinition(): InputDefinition
{
return new InputDefinition([
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
]);
}
}

View File

@@ -0,0 +1,99 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 刘志淳 <chun@engineer.com>
// +----------------------------------------------------------------------
namespace think\console\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\Output;
abstract class Make extends Command
{
protected $type;
abstract protected function getStub();
protected function configure()
{
$this->addArgument('name', Argument::REQUIRED, "The name of the class");
}
protected function execute(Input $input, Output $output)
{
$name = trim($input->getArgument('name'));
$classname = $this->getClassName($name);
$pathname = $this->getPathName($classname);
if (is_file($pathname)) {
$output->writeln('<error>' . $this->type . ':' . $classname . ' already exists!</error>');
return false;
}
if (!is_dir(dirname($pathname))) {
mkdir(dirname($pathname), 0755, true);
}
file_put_contents($pathname, $this->buildClass($classname));
$output->writeln('<info>' . $this->type . ':' . $classname . ' created successfully.</info>');
}
protected function buildClass(string $name)
{
$stub = file_get_contents($this->getStub());
$namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\');
$class = str_replace($namespace . '\\', '', $name);
return str_replace(['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'], [
$class,
$this->app->config->get('route.action_suffix'),
$namespace,
$this->app->getNamespace(),
], $stub);
}
protected function getPathName(string $name): string
{
$name = substr($name, 4);
return $this->app->getBasePath() . ltrim(str_replace('\\', '/', $name), '/') . '.php';
}
protected function getClassName(string $name): string
{
if (str_contains($name, '\\')) {
return $name;
}
if (str_contains($name, '@')) {
[$app, $name] = explode('@', $name);
} else {
$app = '';
}
if (str_contains($name, '/')) {
$name = str_replace('/', '\\', $name);
}
return $this->getNamespace($app) . '\\' . $name;
}
protected function getNamespace(string $app): string
{
return 'app' . ($app ? '\\' . $app : '');
}
}

View File

@@ -0,0 +1,128 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\console\Table;
use think\event\RouteLoaded;
class RouteList extends Command
{
protected $sortBy = [
'rule' => 0,
'route' => 1,
'method' => 2,
'name' => 3,
'domain' => 4,
];
protected function configure()
{
$this->setName('route:list')
->addArgument('dir', Argument::OPTIONAL, 'dir name .')
->addArgument('style', Argument::OPTIONAL, "the style of the table.", 'default')
->addOption('sort', 's', Option::VALUE_OPTIONAL, 'order by rule name.', 0)
->addOption('more', 'm', Option::VALUE_NONE, 'show route options.')
->setDescription('show route list.');
}
protected function execute(Input $input, Output $output)
{
$dir = $input->getArgument('dir') ?: '';
$filename = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : '') . 'route_list.php';
if (is_file($filename)) {
unlink($filename);
} elseif (!is_dir(dirname($filename))) {
mkdir(dirname($filename), 0755);
}
$content = $this->getRouteList($dir);
file_put_contents($filename, 'Route List' . PHP_EOL . $content);
}
protected function getRouteList(string $dir = null): string
{
$this->app->route->clear();
$this->app->route->lazy(false);
if ($dir) {
$path = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR;
} else {
$path = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR;
}
$files = is_dir($path) ? scandir($path) : [];
foreach ($files as $file) {
if (str_contains($file, '.php')) {
include $path . $file;
}
}
//触发路由载入完成事件
$this->app->event->trigger(RouteLoaded::class);
$table = new Table();
if ($this->input->hasOption('more')) {
$header = ['Rule', 'Route', 'Method', 'Name', 'Domain', 'Option', 'Pattern'];
} else {
$header = ['Rule', 'Route', 'Method', 'Name'];
}
$table->setHeader($header);
$routeList = $this->app->route->getRuleList();
$rows = [];
foreach ($routeList as $item) {
$item['route'] = $item['route'] instanceof \Closure ? '<Closure>' : $item['route'];
$row = [$item['rule'], $item['route'], $item['method'], $item['name']];
if ($this->input->hasOption('more')) {
array_push($row, $item['domain'], json_encode($item['option']), json_encode($item['pattern']));
}
$rows[] = $row;
}
if ($this->input->getOption('sort')) {
$sort = strtolower($this->input->getOption('sort'));
if (isset($this->sortBy[$sort])) {
$sort = $this->sortBy[$sort];
}
uasort($rows, function ($a, $b) use ($sort) {
$itemA = $a[$sort] ?? null;
$itemB = $b[$sort] ?? null;
return strcasecmp($itemA, $itemB);
});
}
$table->setRows($rows);
if ($this->input->getArgument('style')) {
$style = $this->input->getArgument('style');
$table->setStyle($style);
}
return $this->table($table);
}
}

View File

@@ -0,0 +1,72 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Slince <taosikai@yeah.net>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think\console\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
class RunServer extends Command
{
public function configure()
{
$this->setName('run')
->addOption(
'host',
'H',
Option::VALUE_OPTIONAL,
'The host to server the application on',
'0.0.0.0'
)
->addOption(
'port',
'p',
Option::VALUE_OPTIONAL,
'The port to server the application on',
8000
)
->addOption(
'root',
'r',
Option::VALUE_OPTIONAL,
'The document root of the application',
''
)
->setDescription('PHP Built-in Server for ThinkPHP');
}
public function execute(Input $input, Output $output)
{
$host = $input->getOption('host');
$port = $input->getOption('port');
$root = $input->getOption('root');
if (empty($root)) {
$root = $this->app->getRootPath() . 'public';
}
$command = sprintf(
'"%s" -S %s:%d -t %s %s',
PHP_BINARY,
$host,
$port,
escapeshellarg($root),
escapeshellarg($root . DIRECTORY_SEPARATOR . 'router.php')
);
$output->writeln(sprintf('ThinkPHP Development server is started On <http://%s:%s/>', $host, $port));
$output->writeln(sprintf('You can exit with <info>`CTRL-C`</info>'));
$output->writeln(sprintf('Document root is: %s', $root));
passthru($command);
}
}

View File

@@ -0,0 +1,52 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\console\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
class ServiceDiscover extends Command
{
public function configure()
{
$this->setName('service:discover')
->setDescription('Discover Services for ThinkPHP');
}
public function execute(Input $input, Output $output)
{
if (is_file($path = $this->app->getRootPath() . 'vendor/composer/installed.json')) {
$packages = json_decode(@file_get_contents($path), true);
// Compatibility with Composer 2.0
if (isset($packages['packages'])) {
$packages = $packages['packages'];
}
$services = [];
foreach ($packages as $package) {
if (!empty($package['extra']['think']['services'])) {
$services = array_merge($services, (array) $package['extra']['think']['services']);
}
}
$header = '// This file is automatically generated at:' . date('Y-m-d H:i:s') . PHP_EOL . 'declare (strict_types = 1);' . PHP_EOL;
$content = '<?php ' . PHP_EOL . $header . "return " . var_export($services, true) . ';';
file_put_contents($this->app->getRootPath() . 'vendor/services.php', $content);
$output->writeln('<info>Succeed!</info>');
}
}
}

View File

@@ -0,0 +1,69 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace think\console\command;
use think\console\Command;
use think\console\input\Option;
class VendorPublish extends Command
{
public function configure()
{
$this->setName('vendor:publish')
->addOption('force', 'f', Option::VALUE_NONE, 'Overwrite any existing files')
->setDescription('Publish any publishable assets from vendor packages');
}
public function handle()
{
$force = $this->input->getOption('force');
if (is_file($path = $this->app->getRootPath() . 'vendor/composer/installed.json')) {
$packages = json_decode(@file_get_contents($path), true);
// Compatibility with Composer 2.0
if (isset($packages['packages'])) {
$packages = $packages['packages'];
}
foreach ($packages as $package) {
//配置
$configDir = $this->app->getConfigPath();
if (!empty($package['extra']['think']['config'])) {
$installPath = $this->app->getRootPath() . 'vendor/' . $package['name'] . DIRECTORY_SEPARATOR;
foreach ((array) $package['extra']['think']['config'] as $name => $file) {
$target = $configDir . $name . '.php';
$source = $installPath . $file;
if (is_file($target) && !$force) {
$this->output->info("File {$target} exist!");
continue;
}
if (!is_file($source)) {
$this->output->info("File {$source} not exist!");
continue;
}
copy($source, $target);
}
}
}
$this->output->writeln('<info>Succeed!</info>');
}
}
}

View File

@@ -0,0 +1,35 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think\console\command;
use Composer\InstalledVersions;
use think\console\Command;
use think\console\Input;
use think\console\Output;
class Version extends Command
{
protected function configure()
{
// 指令配置
$this->setName('version')
->setDescription('show thinkphp framework version');
}
protected function execute(Input $input, Output $output)
{
$version = InstalledVersions::getPrettyVersion('topthink/framework');
$output->writeln($version);
}
}

View File

@@ -0,0 +1,54 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\console\command\make;
use think\console\command\Make;
use think\console\input\Argument;
class Command extends Make
{
protected $type = "Command";
protected function configure()
{
parent::configure();
$this->setName('make:command')
->addArgument('commandName', Argument::OPTIONAL, "The name of the command")
->setDescription('Create a new command class');
}
protected function buildClass(string $name): string
{
$commandName = $this->input->getArgument('commandName') ?: strtolower(basename($name));
$namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\');
$class = str_replace($namespace . '\\', '', $name);
$stub = file_get_contents($this->getStub());
return str_replace(['{%commandName%}', '{%className%}', '{%namespace%}', '{%app_namespace%}'], [
$commandName,
$class,
$namespace,
$this->app->getNamespace(),
], $stub);
}
protected function getStub(): string
{
return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'command.stub';
}
protected function getNamespace(string $app): string
{
return parent::getNamespace($app) . '\\command';
}
}

View File

@@ -0,0 +1,55 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\console\command\make;
use think\console\command\Make;
use think\console\input\Option;
class Controller extends Make
{
protected $type = "Controller";
protected function configure()
{
parent::configure();
$this->setName('make:controller')
->addOption('api', null, Option::VALUE_NONE, 'Generate an api controller class.')
->addOption('plain', null, Option::VALUE_NONE, 'Generate an empty controller class.')
->setDescription('Create a new resource controller class');
}
protected function getStub(): string
{
$stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR;
if ($this->input->getOption('api')) {
return $stubPath . 'controller.api.stub';
}
if ($this->input->getOption('plain')) {
return $stubPath . 'controller.plain.stub';
}
return $stubPath . 'controller.stub';
}
protected function getClassName(string $name): string
{
return parent::getClassName($name) . ($this->app->config->get('route.controller_suffix') ? 'Controller' : '');
}
protected function getNamespace(string $app): string
{
return parent::getNamespace($app) . '\\controller';
}
}

View File

@@ -0,0 +1,35 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\console\command\make;
use think\console\command\Make;
class Event extends Make
{
protected $type = "Event";
protected function configure()
{
parent::configure();
$this->setName('make:event')
->setDescription('Create a new event class');
}
protected function getStub(): string
{
return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'event.stub';
}
protected function getNamespace(string $app): string
{
return parent::getNamespace($app) . '\\event';
}
}

View File

@@ -0,0 +1,35 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\console\command\make;
use think\console\command\Make;
class Listener extends Make
{
protected $type = "Listener";
protected function configure()
{
parent::configure();
$this->setName('make:listener')
->setDescription('Create a new listener class');
}
protected function getStub(): string
{
return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'listener.stub';
}
protected function getNamespace(string $app): string
{
return parent::getNamespace($app) . '\\listener';
}
}

View File

@@ -0,0 +1,36 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\console\command\make;
use think\console\command\Make;
class Middleware extends Make
{
protected $type = "Middleware";
protected function configure()
{
parent::configure();
$this->setName('make:middleware')
->setDescription('Create a new middleware class');
}
protected function getStub(): string
{
return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'middleware.stub';
}
protected function getNamespace(string $app): string
{
return parent::getNamespace($app) . '\\middleware';
}
}

View File

@@ -0,0 +1,36 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\console\command\make;
use think\console\command\Make;
class Model extends Make
{
protected $type = "Model";
protected function configure()
{
parent::configure();
$this->setName('make:model')
->setDescription('Create a new model class');
}
protected function getStub(): string
{
return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'model.stub';
}
protected function getNamespace(string $app): string
{
return parent::getNamespace($app) . '\\model';
}
}

View File

@@ -0,0 +1,36 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\console\command\make;
use think\console\command\Make;
class Service extends Make
{
protected $type = "Service";
protected function configure()
{
parent::configure();
$this->setName('make:service')
->setDescription('Create a new Service class');
}
protected function getStub(): string
{
return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'service.stub';
}
protected function getNamespace(string $app): string
{
return parent::getNamespace($app) . '\\service';
}
}

View File

@@ -0,0 +1,35 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\console\command\make;
use think\console\command\Make;
class Subscribe extends Make
{
protected $type = "Subscribe";
protected function configure()
{
parent::configure();
$this->setName('make:subscribe')
->setDescription('Create a new subscribe class');
}
protected function getStub(): string
{
return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'subscribe.stub';
}
protected function getNamespace(string $app): string
{
return parent::getNamespace($app) . '\\subscribe';
}
}

View File

@@ -0,0 +1,38 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think\console\command\make;
use think\console\command\Make;
class Validate extends Make
{
protected $type = "Validate";
protected function configure()
{
parent::configure();
$this->setName('make:validate')
->setDescription('Create a validate class');
}
protected function getStub(): string
{
$stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR;
return $stubPath . 'validate.stub';
}
protected function getNamespace(string $app): string
{
return parent::getNamespace($app) . '\\validate';
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare (strict_types = 1);
namespace {%namespace%};
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
class {%className%} extends Command
{
protected function configure()
{
// 指令配置
$this->setName('{%commandName%}')
->setDescription('the {%commandName%} command');
}
protected function execute(Input $input, Output $output)
{
// 指令输出
$output->writeln('{%commandName%}');
}
}

View File

@@ -0,0 +1,64 @@
<?php
declare (strict_types = 1);
namespace {%namespace%};
use think\Request;
class {%className%}
{
/**
* 显示资源列表
*
* @return \think\Response
*/
public function index{%actionSuffix%}()
{
//
}
/**
* 保存新建的资源
*
* @param \think\Request $request
* @return \think\Response
*/
public function save{%actionSuffix%}(Request $request)
{
//
}
/**
* 显示指定的资源
*
* @param int $id
* @return \think\Response
*/
public function read{%actionSuffix%}($id)
{
//
}
/**
* 保存更新的资源
*
* @param \think\Request $request
* @param int $id
* @return \think\Response
*/
public function update{%actionSuffix%}(Request $request, $id)
{
//
}
/**
* 删除指定资源
*
* @param int $id
* @return \think\Response
*/
public function delete{%actionSuffix%}($id)
{
//
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare (strict_types = 1);
namespace {%namespace%};
class {%className%}
{
//
}

View File

@@ -0,0 +1,85 @@
<?php
declare (strict_types = 1);
namespace {%namespace%};
use think\Request;
class {%className%}
{
/**
* 显示资源列表
*
* @return \think\Response
*/
public function index{%actionSuffix%}()
{
//
}
/**
* 显示创建资源表单页.
*
* @return \think\Response
*/
public function create{%actionSuffix%}()
{
//
}
/**
* 保存新建的资源
*
* @param \think\Request $request
* @return \think\Response
*/
public function save{%actionSuffix%}(Request $request)
{
//
}
/**
* 显示指定的资源
*
* @param int $id
* @return \think\Response
*/
public function read{%actionSuffix%}($id)
{
//
}
/**
* 显示编辑资源表单页.
*
* @param int $id
* @return \think\Response
*/
public function edit{%actionSuffix%}($id)
{
//
}
/**
* 保存更新的资源
*
* @param \think\Request $request
* @param int $id
* @return \think\Response
*/
public function update{%actionSuffix%}(Request $request, $id)
{
//
}
/**
* 删除指定资源
*
* @param int $id
* @return \think\Response
*/
public function delete{%actionSuffix%}($id)
{
//
}
}

View File

@@ -0,0 +1,8 @@
<?php
declare (strict_types = 1);
namespace {%namespace%};
class {%className%}
{
}

View File

@@ -0,0 +1,17 @@
<?php
declare (strict_types = 1);
namespace {%namespace%};
class {%className%}
{
/**
* 事件监听处理
*
* @return mixed
*/
public function handle($event)
{
//
}
}

View File

@@ -0,0 +1,19 @@
<?php
declare (strict_types = 1);
namespace {%namespace%};
class {%className%}
{
/**
* 处理请求
*
* @param \think\Request $request
* @param \Closure $next
* @return Response
*/
public function handle($request, \Closure $next)
{
//
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare (strict_types = 1);
namespace {%namespace%};
use think\Model;
/**
* @mixin \think\Model
*/
class {%className%} extends Model
{
//
}

View File

@@ -0,0 +1,27 @@
<?php
declare (strict_types = 1);
namespace {%namespace%};
class {%className%} extends \think\Service
{
/**
* 注册服务
*
* @return mixed
*/
public function register()
{
//
}
/**
* 执行服务
*
* @return mixed
*/
public function boot()
{
//
}
}

View File

@@ -0,0 +1,8 @@
<?php
declare (strict_types = 1);
namespace {%namespace%};
class {%className%}
{
}

View File

@@ -0,0 +1,25 @@
<?php
declare (strict_types = 1);
namespace {%namespace%};
use think\Validate;
class {%className%} extends Validate
{
/**
* 定义验证规则
* 格式:'字段名' => ['规则1','规则2'...]
*
* @var array
*/
protected $rule = [];
/**
* 定义错误信息
* 格式:'字段名.规则名' => '错误信息'
*
* @var array
*/
protected $message = [];
}

View File

@@ -0,0 +1,66 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\command\optimize;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\Output;
use think\event\RouteLoaded;
class Route extends Command
{
protected function configure()
{
$this->setName('optimize:route')
->addArgument('dir', Argument::OPTIONAL, 'dir name .')
->setDescription('Build app route cache.');
}
protected function execute(Input $input, Output $output)
{
$dir = $input->getArgument('dir') ?: '';
$path = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : '');
$filename = $path . 'route.php';
if (is_file($filename)) {
unlink($filename);
}
file_put_contents($filename, $this->buildRouteCache($dir));
$output->writeln('<info>Succeed!</info>');
}
protected function buildRouteCache(string $dir = null): string
{
$this->app->route->clear();
$this->app->route->lazy(false);
// 路由检测
$path = $this->app->getRootPath() . ($dir ? 'app' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR : '') . 'route' . DIRECTORY_SEPARATOR;
$files = is_dir($path) ? scandir($path) : [];
foreach ($files as $file) {
if (str_contains($file, '.php')) {
include $path . $file;
}
}
//触发路由载入完成事件
$this->app->event->trigger(RouteLoaded::class);
$rules = $this->app->route->getName();
return '<?php ' . PHP_EOL . 'return unserialize(\'' . serialize($rules) . '\');';
}
}

View File

@@ -0,0 +1,109 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\command\optimize;
use Exception;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\db\PDOConnection;
class Schema extends Command
{
protected function configure()
{
$this->setName('optimize:schema')
->addArgument('dir', Argument::OPTIONAL, 'dir name .')
->addOption('connection', null, Option::VALUE_REQUIRED, 'connection name .')
->addOption('table', null, Option::VALUE_REQUIRED, 'table name .')
->setDescription('Build database schema cache.');
}
protected function execute(Input $input, Output $output)
{
$dir = $input->getArgument('dir') ?: '';
if ($input->hasOption('table')) {
$connection = $this->app->db->connect($input->getOption('connection'));
if (!$connection instanceof PDOConnection) {
$output->error("only PDO connection support schema cache!");
return;
}
$table = $input->getOption('table');
if (!str_contains($table, '.')) {
$dbName = $connection->getConfig('database');
} else {
[$dbName, $table] = explode('.', $table);
}
if ($table == '*') {
$table = $connection->getTables($dbName);
}
$this->buildDataBaseSchema($connection, (array) $table, $dbName);
} else {
if ($dir) {
$appPath = $this->app->getBasePath() . $dir . DIRECTORY_SEPARATOR;
$namespace = 'app\\' . $dir;
} else {
$appPath = $this->app->getBasePath();
$namespace = 'app';
}
$path = $appPath . 'model';
$list = is_dir($path) ? scandir($path) : [];
foreach ($list as $file) {
if (str_starts_with($file, '.')) {
continue;
}
$class = '\\' . $namespace . '\\model\\' . pathinfo($file, PATHINFO_FILENAME);
if (!class_exists($class)) {
continue;
}
$this->buildModelSchema($class);
}
}
$output->writeln('<info>Succeed!</info>');
}
protected function buildModelSchema(string $class): void
{
$reflect = new \ReflectionClass($class);
if (!$reflect->isAbstract() && $reflect->isSubclassOf('\think\Model')) {
try {
/** @var \think\Model $model */
$model = new $class;
$connection = $model->db()->getConnection();
if ($connection instanceof PDOConnection) {
$table = $model->getTable();
//预读字段信息
$connection->getSchemaInfo($table, true);
}
} catch (Exception $e) {
}
}
}
protected function buildDataBaseSchema(PDOConnection $connection, array $tables, string $dbName): void
{
foreach ($tables as $table) {
//预读字段信息
$connection->getSchemaInfo("{$dbName}.{$table}", true);
}
}
}

View File

@@ -0,0 +1,138 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\input;
class Argument
{
// 必传参数
const REQUIRED = 1;
// 可选参数
const OPTIONAL = 2;
// 数组参数
const IS_ARRAY = 4;
/**
* 参数名
* @var string
*/
private $name;
/**
* 参数类型
* @var int
*/
private $mode;
/**
* 参数默认值
* @var mixed
*/
private $default;
/**
* 参数描述
* @var string
*/
private $description;
/**
* 构造方法
* @param string $name 参数名
* @param int $mode 参数类型: self::REQUIRED 或者 self::OPTIONAL
* @param string $description 描述
* @param mixed $default 默认值 (仅 self::OPTIONAL 类型有效)
* @throws \InvalidArgumentException
*/
public function __construct(string $name, int $mode = null, string $description = '', $default = null)
{
if (null === $mode) {
$mode = self::OPTIONAL;
} elseif (!is_int($mode) || $mode > 7 || $mode < 1) {
throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode));
}
$this->name = $name;
$this->mode = $mode;
$this->description = $description;
$this->setDefault($default);
}
/**
* 获取参数名
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* 是否必须
* @return bool
*/
public function isRequired(): bool
{
return self::REQUIRED === (self::REQUIRED & $this->mode);
}
/**
* 该参数是否接受数组
* @return bool
*/
public function isArray(): bool
{
return self::IS_ARRAY === (self::IS_ARRAY & $this->mode);
}
/**
* 设置默认值
* @param mixed $default 默认值
* @throws \LogicException
*/
public function setDefault($default = null): void
{
if (self::REQUIRED === $this->mode && null !== $default) {
throw new \LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.');
}
if ($this->isArray()) {
if (null === $default) {
$default = [];
} elseif (!is_array($default)) {
throw new \LogicException('A default value for an array argument must be an array.');
}
}
$this->default = $default;
}
/**
* 获取默认值
* @return mixed
*/
public function getDefault()
{
return $this->default;
}
/**
* 获取描述
* @return string
*/
public function getDescription(): string
{
return $this->description;
}
}

View File

@@ -0,0 +1,375 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\input;
class Definition
{
/**
* @var Argument[]
*/
private $arguments;
private $requiredCount;
private $hasAnArrayArgument = false;
private $hasOptional;
/**
* @var Option[]
*/
private $options;
private $shortcuts;
/**
* 构造方法
* @param array $definition
* @api
*/
public function __construct(array $definition = [])
{
$this->setDefinition($definition);
}
/**
* 设置指令的定义
* @param array $definition 定义的数组
*/
public function setDefinition(array $definition): void
{
$arguments = [];
$options = [];
foreach ($definition as $item) {
if ($item instanceof Option) {
$options[] = $item;
} else {
$arguments[] = $item;
}
}
$this->setArguments($arguments);
$this->setOptions($options);
}
/**
* 设置参数
* @param Argument[] $arguments 参数数组
*/
public function setArguments(array $arguments = []): void
{
$this->arguments = [];
$this->requiredCount = 0;
$this->hasOptional = false;
$this->hasAnArrayArgument = false;
$this->addArguments($arguments);
}
/**
* 添加参数
* @param Argument[] $arguments 参数数组
* @api
*/
public function addArguments(array $arguments = []): void
{
if (null !== $arguments) {
foreach ($arguments as $argument) {
$this->addArgument($argument);
}
}
}
/**
* 添加一个参数
* @param Argument $argument 参数
* @throws \LogicException
*/
public function addArgument(Argument $argument): void
{
if (isset($this->arguments[$argument->getName()])) {
throw new \LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName()));
}
if ($this->hasAnArrayArgument) {
throw new \LogicException('Cannot add an argument after an array argument.');
}
if ($argument->isRequired() && $this->hasOptional) {
throw new \LogicException('Cannot add a required argument after an optional one.');
}
if ($argument->isArray()) {
$this->hasAnArrayArgument = true;
}
if ($argument->isRequired()) {
++$this->requiredCount;
} else {
$this->hasOptional = true;
}
$this->arguments[$argument->getName()] = $argument;
}
/**
* 根据名称或者位置获取参数
* @param string|int $name 参数名或者位置
* @return Argument 参数
* @throws \InvalidArgumentException
*/
public function getArgument($name): Argument
{
if (!$this->hasArgument($name)) {
throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
$arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;
return $arguments[$name];
}
/**
* 根据名称或位置检查是否具有某个参数
* @param string|int $name 参数名或者位置
* @return bool
* @api
*/
public function hasArgument($name): bool
{
$arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;
return isset($arguments[$name]);
}
/**
* 获取所有的参数
* @return Argument[] 参数数组
*/
public function getArguments(): array
{
return $this->arguments;
}
/**
* 获取参数数量
* @return int
*/
public function getArgumentCount(): int
{
return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments);
}
/**
* 获取必填的参数的数量
* @return int
*/
public function getArgumentRequiredCount(): int
{
return $this->requiredCount;
}
/**
* 获取参数默认值
* @return array
*/
public function getArgumentDefaults(): array
{
$values = [];
foreach ($this->arguments as $argument) {
$values[$argument->getName()] = $argument->getDefault();
}
return $values;
}
/**
* 设置选项
* @param Option[] $options 选项数组
*/
public function setOptions(array $options = []): void
{
$this->options = [];
$this->shortcuts = [];
$this->addOptions($options);
}
/**
* 添加选项
* @param Option[] $options 选项数组
* @api
*/
public function addOptions(array $options = []): void
{
foreach ($options as $option) {
$this->addOption($option);
}
}
/**
* 添加一个选项
* @param Option $option 选项
* @throws \LogicException
* @api
*/
public function addOption(Option $option): void
{
if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) {
throw new \LogicException(sprintf('An option named "%s" already exists.', $option->getName()));
}
if ($option->getShortcut()) {
foreach (explode('|', $option->getShortcut()) as $shortcut) {
if (isset($this->shortcuts[$shortcut])
&& !$option->equals($this->options[$this->shortcuts[$shortcut]])
) {
throw new \LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut));
}
}
}
$this->options[$option->getName()] = $option;
if ($option->getShortcut()) {
foreach (explode('|', $option->getShortcut()) as $shortcut) {
$this->shortcuts[$shortcut] = $option->getName();
}
}
}
/**
* 根据名称获取选项
* @param string $name 选项名
* @return Option
* @throws \InvalidArgumentException
* @api
*/
public function getOption(string $name): Option
{
if (!$this->hasOption($name)) {
throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
}
return $this->options[$name];
}
/**
* 根据名称检查是否有这个选项
* @param string $name 选项名
* @return bool
* @api
*/
public function hasOption(string $name): bool
{
return isset($this->options[$name]);
}
/**
* 获取所有选项
* @return Option[]
* @api
*/
public function getOptions(): array
{
return $this->options;
}
/**
* 根据名称检查某个选项是否有短名称
* @param string $name 短名称
* @return bool
*/
public function hasShortcut(string $name): bool
{
return isset($this->shortcuts[$name]);
}
/**
* 根据短名称获取选项
* @param string $shortcut 短名称
* @return Option
*/
public function getOptionForShortcut(string $shortcut): Option
{
return $this->getOption($this->shortcutToName($shortcut));
}
/**
* 获取所有选项的默认值
* @return array
*/
public function getOptionDefaults(): array
{
$values = [];
foreach ($this->options as $option) {
$values[$option->getName()] = $option->getDefault();
}
return $values;
}
/**
* 根据短名称获取选项名
* @param string $shortcut 短名称
* @return string
* @throws \InvalidArgumentException
*/
private function shortcutToName(string $shortcut): string
{
if (!isset($this->shortcuts[$shortcut])) {
throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
}
return $this->shortcuts[$shortcut];
}
/**
* 获取该指令的介绍
* @param bool $short 是否简洁介绍
* @return string
*/
public function getSynopsis(bool $short = false): string
{
$elements = [];
if ($short && $this->getOptions()) {
$elements[] = '[options]';
} elseif (!$short) {
foreach ($this->getOptions() as $option) {
$value = '';
if ($option->acceptValue()) {
$value = sprintf(' %s%s%s', $option->isValueOptional() ? '[' : '', strtoupper($option->getName()), $option->isValueOptional() ? ']' : '');
}
$shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : '';
$elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value);
}
}
if (count($elements) && $this->getArguments()) {
$elements[] = '[--]';
}
foreach ($this->getArguments() as $argument) {
$element = '<' . $argument->getName() . '>';
if (!$argument->isRequired()) {
$element = '[' . $element . ']';
} elseif ($argument->isArray()) {
$element .= ' (' . $element . ')';
}
if ($argument->isArray()) {
$element .= '...';
}
$elements[] = $element;
}
return implode(' ', $elements);
}
}

View File

@@ -0,0 +1,221 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\input;
/**
* 命令行选项
* @package think\console\input
*/
class Option
{
// 无需传值
const VALUE_NONE = 1;
// 必须传值
const VALUE_REQUIRED = 2;
// 可选传值
const VALUE_OPTIONAL = 4;
// 传数组值
const VALUE_IS_ARRAY = 8;
/**
* 选项名
* @var string
*/
private $name = '';
/**
* 选项短名称
* @var string
*/
private $shortcut = '';
/**
* 选项类型
* @var int
*/
private $mode;
/**
* 选项默认值
* @var mixed
*/
private $default;
/**
* 选项描述
* @var string
*/
private $description = '';
/**
* 构造方法
* @param string $name 选项名
* @param string|array $shortcut 短名称,多个用|隔开或者使用数组
* @param int $mode 选项类型(可选类型为 self::VALUE_*)
* @param string $description 描述
* @param mixed $default 默认值 (类型为 self::VALUE_REQUIRED 或者 self::VALUE_NONE 的时候必须为null)
* @throws \InvalidArgumentException
*/
public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null)
{
if (str_starts_with($name, '--')) {
$name = substr($name, 2);
}
if (empty($name)) {
throw new \InvalidArgumentException('An option name cannot be empty.');
}
if (empty($shortcut)) {
$shortcut = '';
}
if ('' !== $shortcut) {
if (is_array($shortcut)) {
$shortcut = implode('|', $shortcut);
}
$shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-'));
$shortcuts = array_filter($shortcuts);
$shortcut = implode('|', $shortcuts);
if (empty($shortcut)) {
throw new \InvalidArgumentException('An option shortcut cannot be empty.');
}
}
if (null === $mode) {
$mode = self::VALUE_NONE;
} elseif (!is_int($mode) || $mode > 15 || $mode < 1) {
throw new \InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode));
}
$this->name = $name;
$this->shortcut = $shortcut;
$this->mode = $mode;
$this->description = $description;
if ($this->isArray() && !$this->acceptValue()) {
throw new \InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.');
}
$this->setDefault($default);
}
/**
* 获取短名称
* @return string
*/
public function getShortcut(): string
{
return $this->shortcut;
}
/**
* 获取选项名
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* 是否可以设置值
* @return bool 类型不是 self::VALUE_NONE 的时候返回true,其他均返回false
*/
public function acceptValue(): bool
{
return $this->isValueRequired() || $this->isValueOptional();
}
/**
* 是否必须
* @return bool 类型是 self::VALUE_REQUIRED 的时候返回true,其他均返回false
*/
public function isValueRequired(): bool
{
return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode);
}
/**
* 是否可选
* @return bool 类型是 self::VALUE_OPTIONAL 的时候返回true,其他均返回false
*/
public function isValueOptional(): bool
{
return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode);
}
/**
* 选项值是否接受数组
* @return bool 类型是 self::VALUE_IS_ARRAY 的时候返回true,其他均返回false
*/
public function isArray(): bool
{
return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode);
}
/**
* 设置默认值
* @param mixed $default 默认值
* @throws \LogicException
*/
public function setDefault($default = null)
{
if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) {
throw new \LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.');
}
if ($this->isArray()) {
if (null === $default) {
$default = [];
} elseif (!is_array($default)) {
throw new \LogicException('A default value for an array option must be an array.');
}
}
$this->default = $this->acceptValue() ? $default : false;
}
/**
* 获取默认值
* @return mixed
*/
public function getDefault()
{
return $this->default;
}
/**
* 获取描述文字
* @return string
*/
public function getDescription(): string
{
return $this->description;
}
/**
* 检查所给选项是否是当前这个
* @param Option $option
* @return bool
*/
public function equals(Option $option): bool
{
return $option->getName() === $this->getName()
&& $option->getShortcut() === $this->getShortcut()
&& $option->getDefault() === $this->getDefault()
&& $option->isArray() === $this->isArray()
&& $option->isValueRequired() === $this->isValueRequired()
&& $option->isValueOptional() === $this->isValueOptional();
}
}

View File

@@ -0,0 +1,336 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\output;
use think\console\Input;
use think\console\Output;
use think\console\output\question\Choice;
use think\console\output\question\Confirmation;
class Ask
{
private static $stty;
private static $shell;
/** @var Input */
protected $input;
/** @var Output */
protected $output;
/** @var Question */
protected $question;
public function __construct(Input $input, Output $output, Question $question)
{
$this->input = $input;
$this->output = $output;
$this->question = $question;
}
public function run()
{
if (!$this->input->isInteractive()) {
return $this->question->getDefault();
}
if (!$this->question->getValidator()) {
return $this->doAsk();
}
$that = $this;
$interviewer = function () use ($that) {
return $that->doAsk();
};
return $this->validateAttempts($interviewer);
}
protected function doAsk()
{
$this->writePrompt();
$inputStream = STDIN;
$autocomplete = $this->question->getAutocompleterValues();
if (null === $autocomplete || !$this->hasSttyAvailable()) {
$ret = false;
if ($this->question->isHidden()) {
try {
$ret = trim($this->getHiddenResponse($inputStream));
} catch (\RuntimeException $e) {
if (!$this->question->isHiddenFallback()) {
throw $e;
}
}
}
if (false === $ret) {
$ret = fgets($inputStream, 4096);
if (false === $ret) {
throw new \RuntimeException('Aborted');
}
$ret = trim($ret);
}
} else {
$ret = trim($this->autocomplete($inputStream));
}
$ret = strlen($ret) > 0 ? $ret : $this->question->getDefault();
if ($normalizer = $this->question->getNormalizer()) {
return $normalizer($ret);
}
return $ret;
}
private function autocomplete($inputStream)
{
$autocomplete = $this->question->getAutocompleterValues();
$ret = '';
$i = 0;
$ofs = -1;
$matches = $autocomplete;
$numMatches = count($matches);
$sttyMode = shell_exec('stty -g');
shell_exec('stty -icanon -echo');
while (!feof($inputStream)) {
$c = fread($inputStream, 1);
if ("\177" === $c) {
if (0 === $numMatches && 0 !== $i) {
--$i;
$this->output->write("\033[1D");
}
if ($i === 0) {
$ofs = -1;
$matches = $autocomplete;
$numMatches = count($matches);
} else {
$numMatches = 0;
}
$ret = substr($ret, 0, $i);
} elseif ("\033" === $c) {
$c .= fread($inputStream, 2);
if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
if ('A' === $c[2] && -1 === $ofs) {
$ofs = 0;
}
if (0 === $numMatches) {
continue;
}
$ofs += ('A' === $c[2]) ? -1 : 1;
$ofs = ($numMatches + $ofs) % $numMatches;
}
} elseif (ord($c) < 32) {
if ("\t" === $c || "\n" === $c) {
if ($numMatches > 0 && -1 !== $ofs) {
$ret = $matches[$ofs];
$this->output->write(substr($ret, $i));
$i = strlen($ret);
}
if ("\n" === $c) {
$this->output->write($c);
break;
}
$numMatches = 0;
}
continue;
} else {
$this->output->write($c);
$ret .= $c;
++$i;
$numMatches = 0;
$ofs = 0;
foreach ($autocomplete as $value) {
if (str_starts_with($value, $ret) && $i !== strlen($value)) {
$matches[$numMatches++] = $value;
}
}
}
$this->output->write("\033[K");
if ($numMatches > 0 && -1 !== $ofs) {
$this->output->write("\0337");
$this->output->highlight(substr($matches[$ofs], $i));
$this->output->write("\0338");
}
}
shell_exec(sprintf('stty %s', $sttyMode));
return $ret;
}
protected function getHiddenResponse($inputStream)
{
if ('\\' === DIRECTORY_SEPARATOR) {
$exe = __DIR__ . '/../bin/hiddeninput.exe';
$value = rtrim(shell_exec($exe));
$this->output->writeln('');
return $value;
}
if ($this->hasSttyAvailable()) {
$sttyMode = shell_exec('stty -g');
shell_exec('stty -echo');
$value = fgets($inputStream, 4096);
shell_exec(sprintf('stty %s', $sttyMode));
if (false === $value) {
throw new \RuntimeException('Aborted');
}
$value = trim($value);
$this->output->writeln('');
return $value;
}
if (false !== $shell = $this->getShell()) {
$readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword';
$command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd);
$value = rtrim(shell_exec($command));
$this->output->writeln('');
return $value;
}
throw new \RuntimeException('Unable to hide the response.');
}
protected function validateAttempts($interviewer)
{
/** @var \Exception $error */
$error = null;
$attempts = $this->question->getMaxAttempts();
while (null === $attempts || $attempts--) {
if (null !== $error) {
$this->output->error($error->getMessage());
}
try {
return call_user_func($this->question->getValidator(), $interviewer());
} catch (\Exception $error) {
}
}
throw $error;
}
/**
* 显示问题的提示信息
*/
protected function writePrompt()
{
$text = $this->question->getQuestion();
$default = $this->question->getDefault();
switch (true) {
case null === $default:
$text = sprintf(' <info>%s</info>:', $text);
break;
case $this->question instanceof Confirmation:
$text = sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $text, $default ? 'yes' : 'no');
break;
case $this->question instanceof Choice && $this->question->isMultiselect():
$choices = $this->question->getChoices();
$default = explode(',', $default);
foreach ($default as $key => $value) {
$default[$key] = $choices[trim($value)];
}
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, implode(', ', $default));
break;
case $this->question instanceof Choice:
$choices = $this->question->getChoices();
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, $choices[$default]);
break;
default:
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, $default);
}
$this->output->writeln($text);
if ($this->question instanceof Choice) {
$width = max(array_map('strlen', array_keys($this->question->getChoices())));
foreach ($this->question->getChoices() as $key => $value) {
$this->output->writeln(sprintf(" [<comment>%-{$width}s</comment>] %s", $key, $value));
}
}
$this->output->write(' > ');
}
private function getShell()
{
if (null !== self::$shell) {
return self::$shell;
}
self::$shell = false;
if (file_exists('/usr/bin/env')) {
$test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null";
foreach (['bash', 'zsh', 'ksh', 'csh'] as $sh) {
if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
self::$shell = $sh;
break;
}
}
}
return self::$shell;
}
private function hasSttyAvailable()
{
if (null !== self::$stty) {
return self::$stty;
}
exec('stty 2>&1', $output, $exitcode);
return self::$stty = $exitcode === 0;
}
}

View File

@@ -0,0 +1,323 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\output;
use think\Console;
use think\console\Command;
use think\console\input\Argument as InputArgument;
use think\console\input\Definition as InputDefinition;
use think\console\input\Option as InputOption;
use think\console\Output;
use think\console\output\descriptor\Console as ConsoleDescription;
class Descriptor
{
/**
* @var Output
*/
protected $output;
/**
* {@inheritdoc}
*/
public function describe(Output $output, $object, array $options = [])
{
$this->output = $output;
switch (true) {
case $object instanceof InputArgument:
$this->describeInputArgument($object, $options);
break;
case $object instanceof InputOption:
$this->describeInputOption($object, $options);
break;
case $object instanceof InputDefinition:
$this->describeInputDefinition($object, $options);
break;
case $object instanceof Command:
$this->describeCommand($object, $options);
break;
case $object instanceof Console:
$this->describeConsole($object, $options);
break;
default:
throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', $object::class));
}
}
/**
* 输出内容
* @param string $content
* @param bool $decorated
*/
protected function write($content, $decorated = false)
{
$this->output->write($content, false, $decorated ? Output::OUTPUT_NORMAL : Output::OUTPUT_RAW);
}
/**
* 描述参数
* @param InputArgument $argument
* @param array $options
* @return string|mixed
*/
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
if (null !== $argument->getDefault()
&& (!is_array($argument->getDefault())
|| count($argument->getDefault()))
) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
} else {
$default = '';
}
$totalWidth = $options['total_width'] ?? strlen($argument->getName());
$spacingWidth = $totalWidth - strlen($argument->getName()) + 2;
$this->writeText(sprintf(" <info>%s</info>%s%s%s", $argument->getName(), str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + <info> + </info> + 2 spaces
preg_replace('/\s*[\r\n]\s*/', PHP_EOL . str_repeat(' ', $totalWidth + 17), $argument->getDescription()), $default), $options);
}
/**
* 描述选项
* @param InputOption $option
* @param array $options
* @return string|mixed
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
if ($option->acceptValue() && null !== $option->getDefault()
&& (!is_array($option->getDefault())
|| count($option->getDefault()))
) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
} else {
$default = '';
}
$value = '';
if ($option->acceptValue()) {
$value = '=' . strtoupper($option->getName());
if ($option->isValueOptional()) {
$value = '[' . $value . ']';
}
}
$totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]);
$synopsis = sprintf('%s%s', $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', sprintf('--%s%s', $option->getName(), $value));
$spacingWidth = $totalWidth - strlen($synopsis) + 2;
$this->writeText(sprintf(" <info>%s</info>%s%s%s%s", $synopsis, str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + <info> + </info> + 2 spaces
preg_replace('/\s*[\r\n]\s*/', "\n" . str_repeat(' ', $totalWidth + 17), $option->getDescription()), $default, $option->isArray() ? '<comment> (multiple values allowed)</comment>' : ''), $options);
}
/**
* 描述输入
* @param InputDefinition $definition
* @param array $options
* @return string|mixed
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
$totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
foreach ($definition->getArguments() as $argument) {
$totalWidth = max($totalWidth, strlen($argument->getName()));
}
if ($definition->getArguments()) {
$this->writeText('<comment>Arguments:</comment>', $options);
$this->writeText("\n");
foreach ($definition->getArguments() as $argument) {
$this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth]));
$this->writeText("\n");
}
}
if ($definition->getArguments() && $definition->getOptions()) {
$this->writeText("\n");
}
if ($definition->getOptions()) {
$laterOptions = [];
$this->writeText('<comment>Options:</comment>', $options);
foreach ($definition->getOptions() as $option) {
if (strlen($option->getShortcut()) > 1) {
$laterOptions[] = $option;
continue;
}
$this->writeText("\n");
$this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
}
foreach ($laterOptions as $option) {
$this->writeText("\n");
$this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
}
}
}
/**
* 描述指令
* @param Command $command
* @param array $options
* @return string|mixed
*/
protected function describeCommand(Command $command, array $options = [])
{
$command->getSynopsis(true);
$command->getSynopsis(false);
$command->mergeConsoleDefinition(false);
$this->writeText('<comment>Usage:</comment>', $options);
foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) {
$this->writeText("\n");
$this->writeText(' ' . $usage, $options);
}
$this->writeText("\n");
$definition = $command->getNativeDefinition();
if ($definition->getOptions() || $definition->getArguments()) {
$this->writeText("\n");
$this->describeInputDefinition($definition, $options);
$this->writeText("\n");
}
if ($help = $command->getProcessedHelp()) {
$this->writeText("\n");
$this->writeText('<comment>Help:</comment>', $options);
$this->writeText("\n");
$this->writeText(' ' . str_replace("\n", "\n ", $help), $options);
$this->writeText("\n");
}
}
/**
* 描述控制台
* @param Console $console
* @param array $options
* @return string|mixed
*/
protected function describeConsole(Console $console, array $options = [])
{
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
$description = new ConsoleDescription($console, $describedNamespace);
if (isset($options['raw_text']) && $options['raw_text']) {
$width = $this->getColumnWidth($description->getNamespaces());
foreach ($description->getCommands() as $command) {
$this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options);
$this->writeText("\n");
}
} else {
if ('' != $help = $console->getHelp()) {
$this->writeText("$help\n\n", $options);
}
$this->writeText("<comment>Usage:</comment>\n", $options);
$this->writeText(" command [options] [arguments]\n\n", $options);
$this->describeInputDefinition(new InputDefinition($console->getDefinition()->getOptions()), $options);
$this->writeText("\n");
$this->writeText("\n");
$width = $this->getColumnWidth($description->getNamespaces());
if ($describedNamespace) {
$this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
} else {
$this->writeText('<comment>Available commands:</comment>', $options);
}
// add commands by namespace
foreach ($description->getNamespaces() as $namespace) {
if (!$describedNamespace && ConsoleDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
$this->writeText("\n");
$this->writeText(' <comment>' . $namespace['id'] . '</comment>', $options);
}
foreach ($namespace['commands'] as $name) {
$this->writeText("\n");
$spacingWidth = $width - strlen($name);
$this->writeText(sprintf(" <info>%s</info>%s%s", $name, str_repeat(' ', $spacingWidth), $description->getCommand($name)
->getDescription()), $options);
}
}
$this->writeText("\n");
}
}
/**
* {@inheritdoc}
*/
private function writeText($content, array $options = [])
{
$this->write(isset($options['raw_text'])
&& $options['raw_text'] ? strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : true);
}
/**
* 格式化
* @param mixed $default
* @return string
*/
private function formatDefaultValue($default)
{
return json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
/**
* @param Namespaces[] $namespaces
* @return int
*/
private function getColumnWidth(array $namespaces)
{
$width = 0;
foreach ($namespaces as $namespace) {
foreach ($namespace['commands'] as $name) {
if (strlen($name) > $width) {
$width = strlen($name);
}
}
}
return $width + 2;
}
/**
* @param InputOption[] $options
* @return int
*/
private function calculateTotalWidthForOptions($options)
{
$totalWidth = 0;
foreach ($options as $option) {
$nameLength = 4 + strlen($option->getName()) + 2; // - + shortcut + , + whitespace + name + --
if ($option->acceptValue()) {
$valueLength = 1 + strlen($option->getName()); // = + value
$valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]
$nameLength += $valueLength;
}
$totalWidth = max($totalWidth, $nameLength);
}
return $totalWidth;
}
}

View File

@@ -0,0 +1,198 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\output;
use think\console\output\formatter\Stack as StyleStack;
use think\console\output\formatter\Style;
class Formatter
{
private $decorated = false;
private $styles = [];
private $styleStack;
/**
* 转义
* @param string $text
* @return string
*/
public static function escape($text)
{
return preg_replace('/([^\\\\]?)</is', '$1\\<', $text);
}
/**
* 初始化命令行输出格式
*/
public function __construct()
{
$this->setStyle('error', new Style('white', 'red'));
$this->setStyle('info', new Style('green'));
$this->setStyle('comment', new Style('yellow'));
$this->setStyle('question', new Style('black', 'cyan'));
$this->setStyle('highlight', new Style('red'));
$this->setStyle('warning', new Style('black', 'yellow'));
$this->styleStack = new StyleStack();
}
/**
* 设置外观标识
* @param bool $decorated 是否美化文字
*/
public function setDecorated($decorated)
{
$this->decorated = (bool) $decorated;
}
/**
* 获取外观标识
* @return bool
*/
public function isDecorated()
{
return $this->decorated;
}
/**
* 添加一个新样式
* @param string $name 样式名
* @param Style $style 样式实例
*/
public function setStyle($name, Style $style)
{
$this->styles[strtolower($name)] = $style;
}
/**
* 是否有这个样式
* @param string $name
* @return bool
*/
public function hasStyle($name)
{
return isset($this->styles[strtolower($name)]);
}
/**
* 获取样式
* @param string $name
* @return Style
* @throws \InvalidArgumentException
*/
public function getStyle($name)
{
if (!$this->hasStyle($name)) {
throw new \InvalidArgumentException(sprintf('Undefined style: %s', $name));
}
return $this->styles[strtolower($name)];
}
/**
* 使用所给的样式格式化文字
* @param string $message 文字
* @return string
*/
public function format($message)
{
$offset = 0;
$output = '';
$tagRegex = '[a-z][a-z0-9_=;-]*';
preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#isx", $message, $matches, PREG_OFFSET_CAPTURE);
foreach ($matches[0] as $i => $match) {
$pos = $match[1];
$text = $match[0];
if (0 != $pos && '\\' == $message[$pos - 1]) {
continue;
}
$output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset));
$offset = $pos + strlen($text);
if ($open = '/' != $text[1]) {
$tag = $matches[1][$i][0];
} else {
$tag = $matches[3][$i][0] ?? '';
}
if (!$open && !$tag) {
// </>
$this->styleStack->pop();
} elseif (false === $style = $this->createStyleFromString(strtolower($tag))) {
$output .= $this->applyCurrentStyle($text);
} elseif ($open) {
$this->styleStack->push($style);
} else {
$this->styleStack->pop($style);
}
}
$output .= $this->applyCurrentStyle(substr($message, $offset));
return str_replace('\\<', '<', $output);
}
/**
* @return StyleStack
*/
public function getStyleStack()
{
return $this->styleStack;
}
/**
* 根据字符串创建新的样式实例
* @param string $string
* @return Style|bool
*/
private function createStyleFromString($string)
{
if (isset($this->styles[$string])) {
return $this->styles[$string];
}
if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($string), $matches, PREG_SET_ORDER)) {
return false;
}
$style = new Style();
foreach ($matches as $match) {
array_shift($match);
if ('fg' == $match[0]) {
$style->setForeground($match[1]);
} elseif ('bg' == $match[0]) {
$style->setBackground($match[1]);
} else {
try {
$style->setOption($match[1]);
} catch (\InvalidArgumentException $e) {
return false;
}
}
}
return $style;
}
/**
* 从堆栈应用样式到文字
* @param string $text 文字
* @return string
*/
private function applyCurrentStyle($text)
{
return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text;
}
}

View File

@@ -0,0 +1,211 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\output;
class Question
{
private $question;
private $attempts;
private $hidden = false;
private $hiddenFallback = true;
private $autocompleterValues;
private $validator;
private $default;
private $normalizer;
/**
* 构造方法
* @param string $question 问题
* @param mixed $default 默认答案
*/
public function __construct($question, $default = null)
{
$this->question = $question;
$this->default = $default;
}
/**
* 获取问题
* @return string
*/
public function getQuestion()
{
return $this->question;
}
/**
* 获取默认答案
* @return mixed
*/
public function getDefault()
{
return $this->default;
}
/**
* 是否隐藏答案
* @return bool
*/
public function isHidden()
{
return $this->hidden;
}
/**
* 隐藏答案
* @param bool $hidden
* @return Question
*/
public function setHidden($hidden)
{
if ($this->autocompleterValues) {
throw new \LogicException('A hidden question cannot use the autocompleter.');
}
$this->hidden = (bool) $hidden;
return $this;
}
/**
* 不能被隐藏是否撤销
* @return bool
*/
public function isHiddenFallback()
{
return $this->hiddenFallback;
}
/**
* 设置不能被隐藏的时候的操作
* @param bool $fallback
* @return Question
*/
public function setHiddenFallback($fallback)
{
$this->hiddenFallback = (bool) $fallback;
return $this;
}
/**
* 获取自动完成
* @return null|array|\Traversable
*/
public function getAutocompleterValues()
{
return $this->autocompleterValues;
}
/**
* 设置自动完成的值
* @param null|array|\Traversable $values
* @return Question
* @throws \InvalidArgumentException
* @throws \LogicException
*/
public function setAutocompleterValues($values)
{
if (is_array($values) && $this->isAssoc($values)) {
$values = array_merge(array_keys($values), array_values($values));
}
if (null !== $values && !is_array($values)) {
if (!$values instanceof \Traversable || $values instanceof \Countable) {
throw new \InvalidArgumentException('Autocompleter values can be either an array, `null` or an object implementing both `Countable` and `Traversable` interfaces.');
}
}
if ($this->hidden) {
throw new \LogicException('A hidden question cannot use the autocompleter.');
}
$this->autocompleterValues = $values;
return $this;
}
/**
* 设置答案的验证器
* @param null|callable $validator
* @return Question The current instance
*/
public function setValidator($validator)
{
$this->validator = $validator;
return $this;
}
/**
* 获取验证器
* @return null|callable
*/
public function getValidator()
{
return $this->validator;
}
/**
* 设置最大重试次数
* @param null|int $attempts
* @return Question
* @throws \InvalidArgumentException
*/
public function setMaxAttempts($attempts)
{
if (null !== $attempts && $attempts < 1) {
throw new \InvalidArgumentException('Maximum number of attempts must be a positive value.');
}
$this->attempts = $attempts;
return $this;
}
/**
* 获取最大重试次数
* @return null|int
*/
public function getMaxAttempts()
{
return $this->attempts;
}
/**
* 设置响应的回调
* @param string|\Closure $normalizer
* @return Question
*/
public function setNormalizer($normalizer)
{
$this->normalizer = $normalizer;
return $this;
}
/**
* 获取响应回调
* The normalizer can ba a callable (a string), a closure or a class implementing __invoke.
* @return string|\Closure
*/
public function getNormalizer()
{
return $this->normalizer;
}
protected function isAssoc($array)
{
return (bool) count(array_filter(array_keys($array), 'is_string'));
}
}

View File

@@ -0,0 +1,153 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\output\descriptor;
use think\Console as ThinkConsole;
use think\console\Command;
class Console
{
const GLOBAL_NAMESPACE = '_global';
/**
* @var ThinkConsole
*/
private $console;
/**
* @var null|string
*/
private $namespace;
/**
* @var array
*/
private $namespaces;
/**
* @var Command[]
*/
private $commands;
/**
* @var Command[]
*/
private $aliases;
/**
* 构造方法
* @param ThinkConsole $console
* @param string|null $namespace
*/
public function __construct(ThinkConsole $console, $namespace = null)
{
$this->console = $console;
$this->namespace = $namespace;
}
/**
* @return array
*/
public function getNamespaces(): array
{
if (null === $this->namespaces) {
$this->inspectConsole();
}
return $this->namespaces;
}
/**
* @return Command[]
*/
public function getCommands(): array
{
if (null === $this->commands) {
$this->inspectConsole();
}
return $this->commands;
}
/**
* @param string $name
* @return Command
* @throws \InvalidArgumentException
*/
public function getCommand(string $name): Command
{
if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name));
}
return $this->commands[$name] ?? $this->aliases[$name];
}
private function inspectConsole(): void
{
$this->commands = [];
$this->namespaces = [];
$all = $this->console->all($this->namespace ? $this->console->findNamespace($this->namespace) : null);
foreach ($this->sortCommands($all) as $namespace => $commands) {
$names = [];
/** @var Command $command */
foreach ($commands as $name => $command) {
if (is_string($command)) {
$command = new $command();
}
if (!$command->getName()) {
continue;
}
if ($command->getName() === $name) {
$this->commands[$name] = $command;
} else {
$this->aliases[$name] = $command;
}
$names[] = $name;
}
$this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names];
}
}
/**
* @param array $commands
* @return array
*/
private function sortCommands(array $commands): array
{
$namespacedCommands = [];
foreach ($commands as $name => $command) {
$key = $this->console->extractNamespace($name, 1);
if (!$key) {
$key = self::GLOBAL_NAMESPACE;
}
$namespacedCommands[$key][$name] = $command;
}
ksort($namespacedCommands);
foreach ($namespacedCommands as &$commandsSet) {
ksort($commandsSet);
}
// unset reference to keep scope clear
unset($commandsSet);
return $namespacedCommands;
}
}

View File

@@ -0,0 +1,52 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\output\driver;
use think\console\Output;
class Buffer
{
/**
* @var string
*/
private $buffer = '';
public function __construct(Output $output)
{
// do nothing
}
public function fetch()
{
$content = $this->buffer;
$this->buffer = '';
return $content;
}
public function write($messages, bool $newline = false, int $options = 0)
{
$messages = (array) $messages;
foreach ($messages as $message) {
$this->buffer .= $message;
}
if ($newline) {
$this->buffer .= "\n";
}
}
public function renderException(\Throwable $e)
{
// do nothing
}
}

View File

@@ -0,0 +1,369 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\output\driver;
use think\console\Output;
use think\console\output\Formatter;
class Console
{
/** @var Resource */
private $stdout;
/** @var Formatter */
private $formatter;
private $terminalDimensions;
/** @var Output */
private $output;
public function __construct(Output $output)
{
$this->output = $output;
$this->formatter = new Formatter();
$this->stdout = $this->openOutputStream();
$decorated = $this->hasColorSupport($this->stdout);
$this->formatter->setDecorated($decorated);
}
public function setDecorated($decorated)
{
$this->formatter->setDecorated($decorated);
}
public function write($messages, bool $newline = false, int $type = 0, $stream = null)
{
if (Output::VERBOSITY_QUIET === $this->output->getVerbosity()) {
return;
}
$messages = (array) $messages;
foreach ($messages as $message) {
switch ($type) {
case Output::OUTPUT_NORMAL:
$message = $this->formatter->format($message);
break;
case Output::OUTPUT_RAW:
break;
case Output::OUTPUT_PLAIN:
$message = strip_tags($this->formatter->format($message));
break;
default:
throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type));
}
$this->doWrite($message, $newline, $stream);
}
}
public function renderException(\Throwable $e)
{
$stderr = $this->openErrorStream();
$decorated = $this->hasColorSupport($stderr);
$this->formatter->setDecorated($decorated);
do {
$title = sprintf(' [%s] ', $e::class);
$len = $this->stringWidth($title);
$width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX;
if (defined('HHVM_VERSION') && $width > 1 << 31) {
$width = 1 << 31;
}
$lines = [];
foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) {
foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
$lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $line)) + 4;
$lines[] = [$line, $lineLength];
$len = max($lineLength, $len);
}
}
$messages = ['', ''];
$messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len));
$messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title))));
foreach ($lines as $line) {
$messages[] = sprintf('<error> %s %s</error>', $line[0], str_repeat(' ', $len - $line[1]));
}
$messages[] = $emptyLine;
$messages[] = '';
$messages[] = '';
$this->write($messages, true, Output::OUTPUT_NORMAL, $stderr);
if (Output::VERBOSITY_VERBOSE <= $this->output->getVerbosity()) {
$this->write('<comment>Exception trace:</comment>', true, Output::OUTPUT_NORMAL, $stderr);
// exception related properties
$trace = $e->getTrace();
array_unshift($trace, [
'function' => '',
'file' => $e->getFile() !== null ? $e->getFile() : 'n/a',
'line' => $e->getLine() !== null ? $e->getLine() : 'n/a',
'args' => [],
]);
for ($i = 0, $count = count($trace); $i < $count; ++$i) {
$class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
$type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
$function = $trace[$i]['function'];
$file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
$line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
$this->write(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line), true, Output::OUTPUT_NORMAL, $stderr);
}
$this->write('', true, Output::OUTPUT_NORMAL, $stderr);
$this->write('', true, Output::OUTPUT_NORMAL, $stderr);
}
} while ($e = $e->getPrevious());
}
/**
* 获取终端宽度
* @return int|null
*/
protected function getTerminalWidth()
{
$dimensions = $this->getTerminalDimensions();
return $dimensions[0];
}
/**
* 获取终端高度
* @return int|null
*/
protected function getTerminalHeight()
{
$dimensions = $this->getTerminalDimensions();
return $dimensions[1];
}
/**
* 获取当前终端的尺寸
* @return array
*/
public function getTerminalDimensions(): array
{
if ($this->terminalDimensions) {
return $this->terminalDimensions;
}
if ('\\' === DIRECTORY_SEPARATOR) {
if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) {
return [(int) $matches[1], (int) $matches[2]];
}
if (preg_match('/^(\d+)x(\d+)$/', $this->getMode(), $matches)) {
return [(int) $matches[1], (int) $matches[2]];
}
}
if ($sttyString = $this->getSttyColumns()) {
if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
return [(int) $matches[2], (int) $matches[1]];
}
if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
return [(int) $matches[2], (int) $matches[1]];
}
}
return [null, null];
}
/**
* 获取stty列数
* @return string
*/
private function getSttyColumns()
{
if (!function_exists('proc_open')) {
return;
}
$descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
$process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]);
if (is_resource($process)) {
$info = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
return $info;
}
return;
}
/**
* 获取终端模式
* @return string <width>x<height>
*/
private function getMode()
{
if (!function_exists('proc_open')) {
return '';
}
$descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
$process = proc_open('mode CON', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]);
if (is_resource($process)) {
$info = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
return $matches[2] . 'x' . $matches[1];
}
}
return '';
}
private function stringWidth(string $string): int
{
if (!function_exists('mb_strwidth')) {
return strlen($string);
}
if (false === $encoding = mb_detect_encoding($string)) {
return strlen($string);
}
return mb_strwidth($string, $encoding);
}
private function splitStringByWidth(string $string, int $width): array
{
if (!function_exists('mb_strwidth')) {
return str_split($string, $width);
}
if (false === $encoding = mb_detect_encoding($string)) {
return str_split($string, $width);
}
$utf8String = mb_convert_encoding($string, 'utf8', $encoding);
$lines = [];
$line = '';
foreach (preg_split('//u', $utf8String) as $char) {
if (mb_strwidth($line . $char, 'utf8') <= $width) {
$line .= $char;
continue;
}
$lines[] = str_pad($line, $width);
$line = $char;
}
if (strlen($line)) {
$lines[] = count($lines) ? str_pad($line, $width) : $line;
}
mb_convert_variables($encoding, 'utf8', $lines);
return $lines;
}
private function isRunningOS400(): bool
{
$checks = [
function_exists('php_uname') ? php_uname('s') : '',
getenv('OSTYPE'),
PHP_OS,
];
return false !== stripos(implode(';', $checks), 'OS400');
}
/**
* 当前环境是否支持写入控制台输出到stdout.
*
* @return bool
*/
protected function hasStdoutSupport(): bool
{
return false === $this->isRunningOS400();
}
/**
* 当前环境是否支持写入控制台输出到stderr.
*
* @return bool
*/
protected function hasStderrSupport(): bool
{
return false === $this->isRunningOS400();
}
/**
* @return resource
*/
private function openOutputStream()
{
if (!$this->hasStdoutSupport()) {
return fopen('php://output', 'w');
}
return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w');
}
/**
* @return resource
*/
private function openErrorStream()
{
return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w');
}
/**
* 将消息写入到输出。
* @param string $message 消息
* @param bool $newline 是否另起一行
* @param null $stream
*/
protected function doWrite($message, $newline, $stream = null)
{
if (null === $stream) {
$stream = $this->stdout;
}
if (false === @fwrite($stream, $message . ($newline ? PHP_EOL : ''))) {
throw new \RuntimeException('Unable to write output.');
}
fflush($stream);
}
/**
* 是否支持着色
* @param $stream
* @return bool
*/
protected function hasColorSupport($stream): bool
{
if (DIRECTORY_SEPARATOR === '\\') {
return
'10.0.10586' === PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD
|| false !== getenv('ANSICON')
|| 'ON' === getenv('ConEmuANSI')
|| 'xterm' === getenv('TERM');
}
return function_exists('posix_isatty') && @posix_isatty($stream);
}
}

View File

@@ -0,0 +1,33 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\output\driver;
use think\console\Output;
class Nothing
{
public function __construct(Output $output)
{
// do nothing
}
public function write($messages, bool $newline = false, int $options = 0)
{
// do nothing
}
public function renderException(\Throwable $e)
{
// do nothing
}
}

View File

@@ -0,0 +1,116 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\output\formatter;
class Stack
{
/**
* @var Style[]
*/
private $styles;
/**
* @var Style
*/
private $emptyStyle;
/**
* 构造方法
* @param Style|null $emptyStyle
*/
public function __construct(Style $emptyStyle = null)
{
$this->emptyStyle = $emptyStyle ?: new Style();
$this->reset();
}
/**
* 重置堆栈
*/
public function reset(): void
{
$this->styles = [];
}
/**
* 推一个样式进入堆栈
* @param Style $style
*/
public function push(Style $style): void
{
$this->styles[] = $style;
}
/**
* 从堆栈中弹出一个样式
* @param Style|null $style
* @return Style
* @throws \InvalidArgumentException
*/
public function pop(Style $style = null): Style
{
if (empty($this->styles)) {
return $this->emptyStyle;
}
if (null === $style) {
return array_pop($this->styles);
}
/**
* @var int $index
* @var Style $stackedStyle
*/
foreach (array_reverse($this->styles, true) as $index => $stackedStyle) {
if ($style->apply('') === $stackedStyle->apply('')) {
$this->styles = array_slice($this->styles, 0, $index);
return $stackedStyle;
}
}
throw new \InvalidArgumentException('Incorrectly nested style tag found.');
}
/**
* 计算堆栈的当前样式。
* @return Style
*/
public function getCurrent(): Style
{
if (empty($this->styles)) {
return $this->emptyStyle;
}
return $this->styles[count($this->styles) - 1];
}
/**
* @param Style $emptyStyle
* @return Stack
*/
public function setEmptyStyle(Style $emptyStyle)
{
$this->emptyStyle = $emptyStyle;
return $this;
}
/**
* @return Style
*/
public function getEmptyStyle(): Style
{
return $this->emptyStyle;
}
}

View File

@@ -0,0 +1,190 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\output\formatter;
class Style
{
protected static $availableForegroundColors = [
'black' => ['set' => 30, 'unset' => 39],
'red' => ['set' => 31, 'unset' => 39],
'green' => ['set' => 32, 'unset' => 39],
'yellow' => ['set' => 33, 'unset' => 39],
'blue' => ['set' => 34, 'unset' => 39],
'magenta' => ['set' => 35, 'unset' => 39],
'cyan' => ['set' => 36, 'unset' => 39],
'white' => ['set' => 37, 'unset' => 39],
];
protected static $availableBackgroundColors = [
'black' => ['set' => 40, 'unset' => 49],
'red' => ['set' => 41, 'unset' => 49],
'green' => ['set' => 42, 'unset' => 49],
'yellow' => ['set' => 43, 'unset' => 49],
'blue' => ['set' => 44, 'unset' => 49],
'magenta' => ['set' => 45, 'unset' => 49],
'cyan' => ['set' => 46, 'unset' => 49],
'white' => ['set' => 47, 'unset' => 49],
];
protected static $availableOptions = [
'bold' => ['set' => 1, 'unset' => 22],
'underscore' => ['set' => 4, 'unset' => 24],
'blink' => ['set' => 5, 'unset' => 25],
'reverse' => ['set' => 7, 'unset' => 27],
'conceal' => ['set' => 8, 'unset' => 28],
];
private $foreground;
private $background;
private $options = [];
/**
* 初始化输出的样式
* @param string|null $foreground 字体颜色
* @param string|null $background 背景色
* @param array $options 格式
* @api
*/
public function __construct($foreground = null, $background = null, array $options = [])
{
if (null !== $foreground) {
$this->setForeground($foreground);
}
if (null !== $background) {
$this->setBackground($background);
}
if (count($options)) {
$this->setOptions($options);
}
}
/**
* 设置字体颜色
* @param string|null $color 颜色名
* @throws \InvalidArgumentException
* @api
*/
public function setForeground($color = null)
{
if (null === $color) {
$this->foreground = null;
return;
}
if (!isset(static::$availableForegroundColors[$color])) {
throw new \InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableForegroundColors))));
}
$this->foreground = static::$availableForegroundColors[$color];
}
/**
* 设置背景色
* @param string|null $color 颜色名
* @throws \InvalidArgumentException
* @api
*/
public function setBackground($color = null)
{
if (null === $color) {
$this->background = null;
return;
}
if (!isset(static::$availableBackgroundColors[$color])) {
throw new \InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableBackgroundColors))));
}
$this->background = static::$availableBackgroundColors[$color];
}
/**
* 设置字体格式
* @param string $option 格式名
* @throws \InvalidArgumentException When the option name isn't defined
* @api
*/
public function setOption(string $option): void
{
if (!isset(static::$availableOptions[$option])) {
throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions))));
}
if (!in_array(static::$availableOptions[$option], $this->options)) {
$this->options[] = static::$availableOptions[$option];
}
}
/**
* 重置字体格式
* @param string $option 格式名
* @throws \InvalidArgumentException
*/
public function unsetOption(string $option): void
{
if (!isset(static::$availableOptions[$option])) {
throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions))));
}
$pos = array_search(static::$availableOptions[$option], $this->options);
if (false !== $pos) {
unset($this->options[$pos]);
}
}
/**
* 批量设置字体格式
* @param array $options
*/
public function setOptions(array $options)
{
$this->options = [];
foreach ($options as $option) {
$this->setOption($option);
}
}
/**
* 应用样式到文字
* @param string $text 文字
* @return string
*/
public function apply(string $text): string
{
$setCodes = [];
$unsetCodes = [];
if (null !== $this->foreground) {
$setCodes[] = $this->foreground['set'];
$unsetCodes[] = $this->foreground['unset'];
}
if (null !== $this->background) {
$setCodes[] = $this->background['set'];
$unsetCodes[] = $this->background['unset'];
}
if (count($this->options)) {
foreach ($this->options as $option) {
$setCodes[] = $option['set'];
$unsetCodes[] = $option['unset'];
}
}
if (0 === count($setCodes)) {
return $text;
}
return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes));
}
}

View File

@@ -0,0 +1,163 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\output\question;
use think\console\output\Question;
class Choice extends Question
{
private $choices;
private $multiselect = false;
private $prompt = ' > ';
private $errorMessage = 'Value "%s" is invalid';
/**
* 构造方法
* @param string $question 问题
* @param array $choices 选项
* @param mixed $default 默认答案
*/
public function __construct($question, array $choices, $default = null)
{
parent::__construct($question, $default);
$this->choices = $choices;
$this->setValidator($this->getDefaultValidator());
$this->setAutocompleterValues($choices);
}
/**
* 可选项
* @return array
*/
public function getChoices(): array
{
return $this->choices;
}
/**
* 设置可否多选
* @param bool $multiselect
* @return self
*/
public function setMultiselect(bool $multiselect)
{
$this->multiselect = $multiselect;
$this->setValidator($this->getDefaultValidator());
return $this;
}
public function isMultiselect(): bool
{
return $this->multiselect;
}
/**
* 获取提示
* @return string
*/
public function getPrompt(): string
{
return $this->prompt;
}
/**
* 设置提示
* @param string $prompt
* @return self
*/
public function setPrompt(string $prompt)
{
$this->prompt = $prompt;
return $this;
}
/**
* 设置错误提示信息
* @param string $errorMessage
* @return self
*/
public function setErrorMessage(string $errorMessage)
{
$this->errorMessage = $errorMessage;
$this->setValidator($this->getDefaultValidator());
return $this;
}
/**
* 获取默认的验证方法
* @return callable
*/
private function getDefaultValidator()
{
$choices = $this->choices;
$errorMessage = $this->errorMessage;
$multiselect = $this->multiselect;
$isAssoc = $this->isAssoc($choices);
return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) {
// Collapse all spaces.
$selectedChoices = str_replace(' ', '', $selected);
if ($multiselect) {
// Check for a separated comma values
if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) {
throw new \InvalidArgumentException(sprintf($errorMessage, $selected));
}
$selectedChoices = explode(',', $selectedChoices);
} else {
$selectedChoices = [$selected];
}
$multiselectChoices = [];
foreach ($selectedChoices as $value) {
$results = [];
foreach ($choices as $key => $choice) {
if ($choice === $value) {
$results[] = $key;
}
}
if (count($results) > 1) {
throw new \InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results)));
}
$result = array_search($value, $choices);
if (!$isAssoc) {
if (!empty($result)) {
$result = $choices[$result];
} elseif (isset($choices[$value])) {
$result = $choices[$value];
}
} elseif (empty($result) && array_key_exists($value, $choices)) {
$result = $value;
}
if (false === $result) {
throw new \InvalidArgumentException(sprintf($errorMessage, $value));
}
array_push($multiselectChoices, $result);
}
if ($multiselect) {
return $multiselectChoices;
}
return current($multiselectChoices);
};
}
}

View File

@@ -0,0 +1,57 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\console\output\question;
use think\console\output\Question;
class Confirmation extends Question
{
private $trueAnswerRegex;
/**
* 构造方法
* @param string $question 问题
* @param bool $default 默认答案
* @param string $trueAnswerRegex 验证正则
*/
public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y/i')
{
parent::__construct($question, (bool) $default);
$this->trueAnswerRegex = $trueAnswerRegex;
$this->setNormalizer($this->getDefaultNormalizer());
}
/**
* 获取默认的答案回调
* @return callable
*/
private function getDefaultNormalizer()
{
$default = $this->getDefault();
$regex = $this->trueAnswerRegex;
return function ($answer) use ($default, $regex) {
if (is_bool($answer)) {
return $answer;
}
$answerIsTrue = (bool) preg_match($regex, $answer);
if (false === $default) {
return $answer && $answerIsTrue;
}
return !$answer || $answerIsTrue;
};
}
}

View File

@@ -0,0 +1,71 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare(strict_types = 1);
namespace think\contract;
use DateInterval;
use DateTimeInterface;
use Psr\SimpleCache\CacheInterface;
use think\cache\TagSet;
/**
* 缓存驱动接口
*/
interface CacheHandlerInterface extends CacheInterface
{
/**
* 自增缓存(针对数值缓存)
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function inc($name, $step = 1);
/**
* 自减缓存(针对数值缓存)
* @param string $name 缓存变量名
* @param int $step 步长
* @return false|int
*/
public function dec($name, $step = 1);
/**
* 读取缓存并删除
* @param string $name 缓存变量名
* @return mixed
*/
public function pull($name);
/**
* 如果不存在则写入缓存
* @param string $name 缓存变量名
* @param mixed $value 存储数据
* @param int|DateInterval|DateTimeInterface $expire 有效时间 0为永久
* @return mixed
*/
public function remember($name, $value, $expire = null);
/**
* 缓存标签
* @param string|array $name 标签名
* @return TagSet
*/
public function tag($name);
/**
* 删除缓存标签
* @param array $keys 缓存标识列表
* @return void
*/
public function clearTag($keys);
}

View File

@@ -0,0 +1,28 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think\contract;
/**
* 日志驱动接口
*/
interface LogHandlerInterface
{
/**
* 日志写入接口
* @access public
* @param array $log 日志信息
* @return bool
*/
public function save(array $log): bool;
}

Some files were not shown because too many files have changed in this diff Show More