初始化

This commit is contained in:
2026-01-06 12:44:28 +08:00
parent 3f71f87bfa
commit d18044dbd7
186 changed files with 29885 additions and 1 deletions

663
src/think/App.php Normal file
View File

@@ -0,0 +1,663 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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
{
/**
* 核心框架版本
* @deprecated 已经废弃 请改用version()方法
*/
const VERSION = '8.0.0';
/**
* 应用调试模式
* @var bool
*/
protected $appDebug = false;
/**
* 公共环境变量标识
* @var string
*/
protected $baseEnvName = '';
/**
* 环境变量标识
* @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 setBaseEnvName(string $name)
{
$this->baseEnvName = $name;
return $this;
}
/**
* 设置环境变量标识
* @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();
// 加载环境变量
if ($this->baseEnvName) {
$this->loadEnv($this->baseEnvName);
}
$this->envName = $this->envName ?: (string) $this->env->get('env_name', '');
$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';
if (is_file($this->runtimePath . 'config.php')) {
$this->config->set(include $this->runtimePath . 'config.php');
} else {
$this->loadConfig();
}
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);
}
}
}
/**
* 加载配置文件
* @return void
*/
public function loadConfig()
{
$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));
}
}
/**
* 调试模式设置
* @access protected
* @return void
*/
protected function debugModeInit(): void
{
// 应用调试模式
if (!$this->appDebug) {
$this->appDebug = $this->env->get('app_debug') ? true : false;
}
if (!$this->appDebug) {
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;
}
}

200
src/think/Cache.php Normal file
View File

@@ -0,0 +1,200 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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);
}
}

218
src/think/Config.php Normal file
View File

@@ -0,0 +1,218 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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;
/**
* 配置管理类
* @package think
*/
class Config
{
/**
* 配置参数
* @var array
*/
protected $config = [];
/**
* 注册配置获取器
* @var Closure[]
*/
protected $hook = [];
/**
* 构造方法
* @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
{
return $this->config[$name] ?? [];
}
/**
* 注册配置获取器
* @access public
* @param Closure $callback
* @param string|null $key
* @return void
*/
public function hook(Closure $callback, ?string $key = null)
{
$this->hook[$key ?? 'global'] = $callback;
}
/**
* 获取配置参数 为空则获取所有配置
* @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, '.')) {
$name = strtolower($name);
$result = $this->pull($name);
return $this->hook ? $this->lazy($name, $result, []) : $result;
}
$item = explode('.', $name);
$item[0] = strtolower($item[0]);
$config = $this->config;
foreach ($item as $val) {
if (isset($config[$val])) {
$config = $config[$val];
} else {
return $this->hook ? $this->lazy($name, null, $default) : $default;
}
}
return $this->hook ? $this->lazy($name, $config, $default) : $config;
}
/**
* 通过获取器加载配置
* @access public
* @param string $name 配置参数
* @param mixed $value 配置值
* @param mixed $default 默认值
* @return mixed
*/
protected function lazy(string $name, $value = null, $default = null)
{
// 通过获取器返回
$key = strpos($name, '.') ? strstr($name, '.', true) : $name;
if (isset($this->hook[$key])) {
$call = $this->hook[$key];
} elseif (isset($this->hook['global'])) {
$call = $this->hook['global'];
}
if (isset($call)) {
$result = call_user_func_array($call, [$name, $value]);
if (is_null($result)) {
return $default;
}
}
return $result ?? ($value ?: $default);
}
/**
* 设置配置参数 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;
}
}

789
src/think/Console.php Normal file
View File

@@ -0,0 +1,789 @@
<?php
// +----------------------------------------------------------------------
// | TopThink [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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\Config;
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:config' => Config::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;
}
}

223
src/think/Cookie.php Normal file
View File

@@ -0,0 +1,223 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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);
$this->request->setCookie($name, $value);
}
/**
* 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);
$this->request->setCookie($name, null);
}
/**
* 获取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(
(string) $name,
$value,
$expire,
$option['path'],
$option['domain'],
(bool) $option['secure'],
(bool) $option['httponly'],
$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,
]);
}
}

117
src/think/Db.php Normal file
View File

@@ -0,0 +1,117 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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);
}
}
}

199
src/think/Env.php Normal file
View File

@@ -0,0 +1,199 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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);
}
}

291
src/think/Event.php Normal file
View File

@@ -0,0 +1,291 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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;
use think\helper\Str;
/**
* 事件管理类
* @package think
*/
class Event
{
/**
* 监听者
* @var array
*/
protected $listener = [];
/**
* 观察者
* @var array
*/
protected $observer = [];
/**
* 事件别名
* @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 = is_object($subscriber) ? [$subscriber] : (array) $subscriber;
foreach ($subscribers as $name => $subscriber) {
if (is_string($subscriber)) {
$subscriber = $this->app->make($subscriber);
}
if (method_exists($subscriber, 'subscribe')) {
// 手动订阅
$subscriber->subscribe($this);
} elseif (!is_numeric($name)) {
// 注册观察者
$this->observer[$name] = $subscriber;
} 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, $name] = explode('.', $event, 2);
if (isset($this->observer[$prefix])) {
// 检查观察者事件响应方法
$observer = $this->observer[$prefix];
$method = 'on' . Str::studly($name);
if (method_exists($observer, $method)) {
return $this->dispatch([$observer, $method], $params);
}
}
$name = substr($event, 0, strrpos($event, '.'));
if (isset($this->listener[$name . '.*'])) {
$listeners = array_merge($listeners, $this->listener[$name . '.*']);
}
}
$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]);
}
}

59
src/think/Exception.php Normal file
View File

@@ -0,0 +1,59 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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;
}
}

198
src/think/File.php Normal file
View File

@@ -0,0 +1,198 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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 : '');
}
}

279
src/think/Http.php Normal file
View File

@@ -0,0 +1,279 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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();
}
}

273
src/think/Lang.php Normal file
View File

@@ -0,0 +1,273 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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',
// 自动侦测浏览器语言
'auto_detect_browser' => true,
// 允许的语言列表
'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 $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;
}
}

249
src/think/Log.php Normal file
View File

@@ -0,0 +1,249 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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|null $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);
}
}

173
src/think/Manager.php Normal file
View File

@@ -0,0 +1,173 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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);
}
}

248
src/think/Middleware.php Normal file
View File

@@ -0,0 +1,248 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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 (empty($middleware)) {
return [];
}
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;
}
}

106
src/think/Pipeline.php Normal file
View File

@@ -0,0 +1,106 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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;
}
}

2215
src/think/Request.php Normal file

File diff suppressed because it is too large Load Diff

425
src/think/Response.php Normal file
View File

@@ -0,0 +1,425 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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;
}
/**
* 获取Cookie对象
* @access public
* @return Cookie
*/
public function getCookie()
{
return $this->cookie;
}
}

889
src/think/Route.php Normal file
View File

@@ -0,0 +1,889 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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\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,
// 子目录是否自动路由分组
'route_auto_group' => false,
// 去除斜杠
'remove_slash' => false,
// 默认的路由变量规则
'default_route_pattern' => '[\w\.]+',
// URL伪静态后缀
'url_html_suffix' => 'html',
// 访问控制器层名称
'controller_layer' => 'controller',
// 空控制器名
'empty_controller' => 'Error',
// 是否使用控制器后缀
'controller_suffix' => false,
// 默认模块名
'default_module' => 'index',
// 默认控制器名
'default_controller' => 'Index',
// 默认操作名
'default_action' => 'index',
// 操作方法后缀
'action_suffix' => '',
// 非路由变量是否使用普通参数方式用于URL生成
'url_common_param' => true,
// 操作方法的参数绑定方式 route get param
'action_bind_param' => 'get',
// API版本header变量
'api_version' => 'Api-Version',
];
/**
* 请求对象
* @var Request
*/
protected $request;
/**
* @var RuleName
*/
protected $ruleName;
/**
* 当前HOST
* @var string
*/
protected $host;
/**
* 当前分组对象
* @var RuleGroup
*/
protected $group;
/**
* 域名对象
* @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;
}
/**
* 获取域名路由的绑定信息
* @access public
* @param string $domain 子域名
* @return string|null
*/
public function getDomainBind(?string $domain = null)
{
if ($domain && isset($this->domains[$domain])) {
$item = $this->domains[$domain];
if (is_string($item)) {
$item = $this->domains[$item];
}
return $item->getBind();
}
}
/**
* 获取RuleName对象
* @access public
* @return RuleName
*/
public function getRuleName(): RuleName
{
return $this->ruleName;
}
/**
* 读取路由标识
* @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 路由地址
* @param Closure $extend 扩展规则
* @return Resource|ResourceRegister
*/
public function resource(string $rule, string $route, ?Closure $extend = null)
{
$resource = (new Resource($this, $this->group, $rule, $route, $this->rest))->extend($extend);
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;
$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);
$completeMatch = (bool) $this->config['route_complete_match'];
$url = str_replace($this->config['pathinfo_depr'], '|', $this->path());
if ($withRoute) {
if ($withRoute instanceof Closure) {
$withRoute();
}
// 路由检测
$dispatch = $this->check($url, $completeMatch);
}
if (empty($dispatch)) {
// 默认URL调度
$dispatch = $this->checkUrlDispatch($url);
}
$dispatch->init($this->app);
return $this->app->middleware->pipeline('route')
->send($request)
->then(function () use ($dispatch) {
return $dispatch->run();
});
}
/**
* 检测URL路由
* @access public
* @param bool $completeMatch
* @return Dispatch|false
* @throws RouteNotFoundException
*/
public function check(string $url, bool $completeMatch = false)
{
// 检测域名路由
$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 && $this->config['url_route_must']) {
// 开启强制路由
throw new RouteNotFoundException();
}
return $result;
}
/**
* 获取当前请求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 $rule 路由规则
* @param mixed $route 路由地址
* @param bool $middleware 自动注册中间件
* @return RuleItem
*/
public function auto(string $rule = '[:__module__]/[:__controller__]/[:__action__]', $route = ':__module__/:__controller__/:__action__', bool $middleware = false): RuleItem
{
return $this->rule($rule, $route)
->name('__think_auto_route__')
->pattern([
'__module__' => '[A-Za-z0-9\.\_]+',
'__controller__' => '[A-Za-z0-9\.\_]+',
'__action__' => '[A-Za-z0-9\_]+',
])->default([
'__module__' => $this->config['default_module'],
'__controller__' => $this->config['default_controller'],
'__action__' => $this->config['default_action'],
])->autoMiddleware($middleware);
}
/**
* 检测默认URL解析路由
* @access public
* @param string $url URL
* @return Dispatch
*/
protected function checkUrlDispatch(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 $this->group->auto()->checkBind($this->request, $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);
}
}

63
src/think/Service.php Normal file
View File

@@ -0,0 +1,63 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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);
});
}
}

65
src/think/Session.php Normal file
View File

@@ -0,0 +1,65 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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');
}
}

187
src/think/View.php Normal file
View File

@@ -0,0 +1,187 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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');
}
}

385
src/think/cache/Driver.php vendored Normal file
View File

@@ -0,0 +1,385 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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 think\exception\InvalidCacheException;
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 缓存变量名
* @param mixed $default 默认值
* @return mixed
*/
public function pull($name, $default = null)
{
if ($this->has($name)) {
$result = $this->get($name, $default);
$this->delete($name);
return $result;
}
return $this->getDefaultValue($name, $default);
}
/**
* 追加(数组)缓存
* @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)
{
if (is_numeric($data)) {
return $data;
}
$serialize = $this->options['serialize'][0] ?? "serialize";
return $serialize($data);
}
/**
* 反序列化数据
* @access protected
* @param string $data 缓存数据
* @return mixed
*/
protected function unserialize($data)
{
if (is_numeric($data)) {
return $data;
}
try {
$unserialize = $this->options['serialize'][1] ?? "unserialize";
$content = $unserialize($data);
if (is_null($content)) {
throw new InvalidCacheException;
} else {
return $content;
}
} catch (Exception | Throwable $e) {
throw new InvalidCacheException;
}
}
/**
* 获取默认值
* @access protected
* @param string $name 缓存标识
* @param mixed $default 默认值
* @param bool $fail 是否有异常
* @return mixed
*/
protected function getDefaultValue($name, $default, $fail = false)
{
if ($fail && $this->options['fail_delete']) {
$this->delete($name);
}
return $default instanceof Closure ? $default() : $default;
}
/**
* 返回句柄对象,可执行其它高级方法
*
* @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);
}
}

121
src/think/cache/TagSet.php vendored Normal file
View File

@@ -0,0 +1,121 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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;
}
}

308
src/think/cache/driver/File.php vendored Normal file
View File

@@ -0,0 +1,308 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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;
use think\exception\InvalidCacheException;
/**
* 文件缓存类
*/
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' => [],
'fail_delete' => false,
];
/**
* 架构函数
* @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);
try {
return is_null($raw) ? $this->getDefaultValue($name, $default) : $this->unserialize($raw['content']);
} catch (InvalidCacheException $e) {
return $this->getDefaultValue($name, $default, true);
}
}
/**
* 写入缓存
* @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;
}
}

204
src/think/cache/driver/Memcache.php vendored Normal file
View File

@@ -0,0 +1,204 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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;
use think\exception\InvalidCacheException;
/**
* 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' => [],
'fail_delete' => false,
];
/**
* 架构函数
* @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));
try {
return false !== $result ? $this->unserialize($result) : $this->getDefaultValue($name, $default);
} catch (InvalidCacheException $e) {
return $this->getDefaultValue($name, $default, true);
}
}
/**
* 写入缓存
* @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);
}
}
}

215
src/think/cache/driver/Memcached.php vendored Normal file
View File

@@ -0,0 +1,215 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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;
use think\exception\InvalidCacheException;
/**
* Memcached缓存类
*/
class Memcached extends Driver
{
/**
* 配置参数
* @var array
*/
protected $options = [
'host' => '127.0.0.1',
'port' => 11211,
'expire' => 0,
'timeout' => 0,
'prefix' => '',
'option' => [],
'username' => '',
'password' => '',
'tag_prefix' => 'tag:',
'serialize' => [],
'fail_delete' => false,
];
/**
* 架构函数
* @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));
try {
return false !== $result ? $this->unserialize($result) : $this->getDefaultValue($name, $default);
} catch (InvalidCacheException $e) {
return $this->getDefaultValue($name, $default, true);
}
}
/**
* 写入缓存
* @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);
}
}

254
src/think/cache/driver/Redis.php vendored Normal file
View File

@@ -0,0 +1,254 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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;
use think\exception\InvalidCacheException;
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' => [],
'fail_delete' => false,
];
/**
* 架构函数
* @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 $this->getDefaultValue($name, $default);
}
try {
return $this->unserialize($value);
} catch (InvalidCacheException $e) {
return $this->getDefaultValue($name, $default, true);
}
}
/**
* 写入缓存
* @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(string $tag): array
{
$name = $this->getTagKey($tag);
$key = $this->getCacheKey($name);
return $this->handler()->sMembers($key);
}
public function __call($method, $args)
{
return call_user_func_array([$this->handler(), $method], $args);
}
}

170
src/think/cache/driver/Wincache.php vendored Normal file
View File

@@ -0,0 +1,170 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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;
use think\exception\InvalidCacheException;
/**
* Wincache缓存驱动
*/
class Wincache extends Driver
{
/**
* 配置参数
* @var array
*/
protected $options = [
'prefix' => '',
'expire' => 0,
'tag_prefix' => 'tag:',
'serialize' => [],
'fail_delete' => false,
];
/**
* 架构函数
* @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);
try {
return wincache_ucache_exists($key) ? $this->unserialize(wincache_ucache_get($key)) : $this->getDefaultValue($name, $default);
} catch (InvalidCacheException $e) {
return $this->getDefaultValue($name, $default, true);
}
}
/**
* 写入缓存
* @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~2025 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;
}
}

465
src/think/console/Input.php Normal file
View File

@@ -0,0 +1,465 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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);
}
}

19
src/think/console/LICENSE Normal file
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~2025 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);
}
}
}

300
src/think/console/Table.php Normal file
View File

@@ -0,0 +1,300 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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-2025 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~2025 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~2025 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,141 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2025 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 DirectoryIterator;
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('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)
{
$filename = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . 'route_list.php';
if (is_file($filename)) {
unlink($filename);
} elseif (!is_dir(dirname($filename))) {
mkdir(dirname($filename), 0755);
}
$content = $this->getRouteList();
file_put_contents($filename, 'Route List' . PHP_EOL . $content);
}
protected function scanRoute($path, $root, $autoGroup)
{
$iterator = new DirectoryIterator($path);
foreach ($iterator as $fileinfo) {
if ($fileinfo->isDot()) {
continue;
}
if ($fileinfo->getType() == 'file' && $fileinfo->getExtension() == 'php') {
$groupName = str_replace('\\', '/', substr_replace($fileinfo->getPath(), '', 0, strlen($root)));
if ($groupName) {
$this->app->route->group($groupName, function() use ($fileinfo) {
include $fileinfo->getRealPath();
});
} else {
include $fileinfo->getRealPath();
}
} elseif ($autoGroup && $fileinfo->isDir()) {
$this->scanRoute($fileinfo->getPathname(), $root, $autoGroup);
}
}
}
protected function getRouteList(?string $dir = null): string
{
$this->app->route->clear();
$this->app->route->lazy(false);
$autoGroup = $this->app->route->config('route_auto_group');
$path = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR;
$this->scanRoute($path, $path, $autoGroup);
//触发路由载入完成事件
$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) {
if (is_array($item['route'])) {
$item['route'] = '[' . $item['route'][0] .' , ' . $item['route'][1] . ']';
} else {
$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~2025 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~2025 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~2025 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-2025 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~2025 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~2025 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~2025 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~2025 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~2025 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~2025 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~2025 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~2025 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~2025 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,64 @@
<?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\optimize;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\Output;
class Config extends Command
{
protected function configure()
{
$this->setName('optimize:config')
->addArgument('dir', Argument::OPTIONAL, 'dir name .')
->setDescription('Build config cache.');
}
protected function execute(Input $input, Output $output)
{
// 加载配置文件
$dir = $input->getArgument('dir') ?: '';
$path = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : '');
if (!is_dir($path)) {
try {
mkdir($path, 0755, true);
} catch (\Exception $e) {
// 创建失败
}
}
$file = $path . 'config.php';
$config = $this->loadConfig($dir);
$content = '<?php ' . PHP_EOL . 'return ' . var_export($config, true) . ';';
if (file_put_contents($file, $content)) {
$output->writeln("<info>Succeed!</info>");
} else {
$output->writeln("<error>config build fail</error>");
}
}
public function loadConfig($dir = '')
{
$configPath = $this->app->getRootPath() . ($dir ? 'app' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR : '') . 'config' . DIRECTORY_SEPARATOR;
$files = [];
if (is_dir($configPath)) {
$files = glob($configPath . '*' . $this->app->getConfigExt());
}
foreach ($files as $file) {
$this->app->config->load($file, pathinfo($file, PATHINFO_FILENAME));
}
return $this->app->config->get();
}
}

View File

@@ -0,0 +1,86 @@
<?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 DirectoryIterator;
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 : '');
if (!is_dir($path)) {
try {
mkdir($path, 0755, true);
} catch (\Exception $e) {
// 创建失败
}
}
file_put_contents($path . 'route.php', $this->buildRouteCache($dir));
$output->writeln('<info>Succeed!</info>');
}
protected function scanRoute($path, $root, $autoGroup)
{
$iterator = new DirectoryIterator($path);
foreach ($iterator as $fileinfo) {
if ($fileinfo->isDot()) {
continue;
}
if ($fileinfo->getType() == 'file' && $fileinfo->getExtension() == 'php') {
$groupName = str_replace('\\', '/', substr_replace($fileinfo->getPath(), '', 0, strlen($root)));
if ($groupName) {
$this->app->route->group($groupName, function() use ($fileinfo) {
include $fileinfo->getRealPath();
});
} else {
include $fileinfo->getRealPath();
}
} elseif ($autoGroup && $fileinfo->isDir()) {
$this->scanRoute($fileinfo->getPathname(), $root, $autoGroup);
}
}
}
protected function buildRouteCache(?string $dir = null): string
{
$this->app->route->clear();
$this->app->route->lazy(false);
// 路由检测
$autoGroup = $this->app->route->config('route_auto_group');
$path = $this->app->getRootPath() . ($dir ? 'app' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR : '') . 'route' . DIRECTORY_SEPARATOR;
$this->scanRoute($path, $path, $autoGroup);
//触发路由载入完成事件
$this->app->event->trigger(RouteLoaded::class);
$rules = $this->app->route->getName();
return '<?php ' . PHP_EOL . 'return ' . var_export($rules, true) . ';';
}
}

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~2025 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-2025 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;
}

View File

@@ -0,0 +1,98 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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 Closure;
use think\db\BaseQuery as Query;
use think\Model;
/**
* 模型关联接口
*/
interface ModelRelationInterface
{
/**
* 延迟获取关联数据
* @access public
* @param array $subRelation 子关联
* @param Closure $closure 闭包查询条件
* @return mixed
*/
public function getRelation(array $subRelation = [], ?Closure $closure = null);
/**
* 预载入关联查询
* @access public
* @param array $resultSet 数据集
* @param string $relation 当前关联名
* @param array $subRelation 子关联名
* @param Closure $closure 闭包条件
* @return void
*/
public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, ?Closure $closure = null): void;
/**
* 预载入关联查询
* @access public
* @param Model $result 数据对象
* @param string $relation 当前关联名
* @param array $subRelation 子关联名
* @param Closure $closure 闭包条件
* @return void
*/
public function eagerlyResult(Model $result, string $relation, array $subRelation = [], ?Closure $closure = null): void;
/**
* 关联统计
* @access public
* @param Model $result 模型对象
* @param Closure $closure 闭包
* @param string $aggregate 聚合查询方法
* @param string $field 字段
* @param string $name 统计字段别名
* @return integer
*/
public function relationCount(Model $result, Closure $closure, string $aggregate = 'count', string $field = '*', ?string &$name = null);
/**
* 创建关联统计子查询
* @access public
* @param Closure $closure 闭包
* @param string $aggregate 聚合查询方法
* @param string $field 字段
* @param string $name 统计字段别名
* @return string
*/
public function getRelationCountQuery(?Closure $closure = null, string $aggregate = 'count', string $field = '*', ?string &$name = null): string;
/**
* 根据关联条件查询当前模型
* @access public
* @param string $operator 比较操作符
* @param integer $count 个数
* @param string $id 关联表的统计字段
* @param string $joinType JOIN类型
* @return Query
*/
public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = 'INNER', ?Query $query = null): Query;
/**
* 根据关联条件查询当前模型
* @access public
* @param mixed $where 查询条件(数组或者闭包)
* @param mixed $fields 字段
* @param string $joinType JOIN类型
* @return Query
*/
public function hasWhere($where = [], $fields = null, string $joinType = ''): Query;
}

View File

@@ -0,0 +1,23 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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;
/**
* Session驱动接口
*/
interface SessionHandlerInterface
{
public function read(string $sessionId): string;
public function delete(string $sessionId): bool;
public function write(string $sessionId, string $data): bool;
}

View File

@@ -0,0 +1,56 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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 TemplateHandlerInterface
{
/**
* 检测是否存在模板文件
* @param string $template 模板文件或者模板规则
* @return bool
*/
public function exists(string $template): bool;
/**
* 渲染模板文件
* @param string $template 模板文件
* @param array $data 模板变量
* @return void
*/
public function fetch(string $template, array $data = []): void;
/**
* 渲染模板内容
* @param string $content 模板内容
* @param array $data 模板变量
* @return void
*/
public function display(string $content, array $data = []): void;
/**
* 配置模板引擎
* @param array $config 参数
* @return void
*/
public function config(array $config): void;
/**
* 获取模板引擎配置
* @param string $name 参数名
* @return mixed
*/
public function getConfig(string $name);
}

View File

@@ -0,0 +1,20 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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\event;
/**
* AppInit事件类
*/
class AppInit
{
}

View File

@@ -0,0 +1,20 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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\event;
/**
* HttpEnd事件类
*/
class HttpEnd
{
}

View File

@@ -0,0 +1,20 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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\event;
/**
* HttpRun事件类
*/
class HttpRun
{
}

View File

@@ -0,0 +1,35 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\event;
use DateTimeImmutable;
/**
* LogRecord事件类
*/
class LogRecord
{
/** @var string */
public string $type;
/** @var string|array */
public $message;
/** @var DateTimeImmutable */
public DateTimeImmutable $time;
public function __construct($type, $message)
{
$this->type = $type;
$this->message = $message;
$this->time = new DateTimeImmutable();
}
}

View File

@@ -0,0 +1,23 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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\event;
/**
* LogWrite事件类
*/
class LogWrite
{
public function __construct(public string $channel, public array $log)
{
}
}

View File

@@ -0,0 +1,20 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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\event;
/**
* 路由加载完成事件
*/
class RouteLoaded
{
}

View File

@@ -0,0 +1,57 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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\exception;
use think\Exception;
/**
* ThinkPHP错误异常
* 主要用于封装 set_error_handler 和 register_shutdown_function 得到的错误
* 除开从 think\Exception 继承的功能
* 其他和PHP系统\ErrorException功能基本一样
*/
class ErrorException extends Exception
{
/**
* 用于保存错误级别
* @var integer
*/
protected $severity;
/**
* 错误异常构造函数
* @access public
* @param integer $severity 错误级别
* @param string $message 错误详细信息
* @param string $file 出错文件路径
* @param integer $line 出错行号
*/
public function __construct(int $severity, string $message, string $file, int $line)
{
$this->severity = $severity;
$this->message = $message;
$this->file = $file;
$this->line = $line;
$this->code = 0;
}
/**
* 获取错误级别
* @access public
* @return integer 错误级别
*/
final public function getSeverity()
{
return $this->severity;
}
}

View File

@@ -0,0 +1,17 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 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\exception;
class FileException extends \RuntimeException
{
}

View File

@@ -0,0 +1,371 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2025 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\exception;
use Exception;
use think\App;
use think\console\Output;
use think\db\exception\DataNotFoundException;
use think\db\exception\ModelNotFoundException;
use think\Request;
use think\Response;
use Throwable;
/**
* 系统异常处理类
*/
class Handle
{
protected $ignoreReport = [
HttpException::class,
HttpResponseException::class,
ModelNotFoundException::class,
DataNotFoundException::class,
ValidateException::class,
];
protected $showErrorMsg = [
];
public function __construct(protected App $app)
{
}
/**
* Report or log an exception.
*
* @access public
* @param Throwable $exception
* @return void
*/
public function report(Throwable $exception): void
{
if (!$this->isIgnoreReport($exception)) {
// 收集异常数据
if ($this->app->isDebug()) {
$data = [
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'message' => $this->getMessage($exception),
'code' => $this->getCode($exception),
];
$log = "[{$data['code']}]{$data['message']}[{$data['file']}:{$data['line']}]";
} else {
$data = [
'code' => $this->getCode($exception),
'message' => $this->getMessage($exception),
];
$log = "[{$data['code']}]{$data['message']}";
}
if ($this->app->config->get('log.record_trace')) {
$log .= PHP_EOL . $exception->getTraceAsString();
}
try {
$this->app->log->record($log, 'error');
} catch (Exception $e) {
}
}
}
protected function isIgnoreReport(Throwable $exception): bool
{
foreach ($this->ignoreReport as $class) {
if ($exception instanceof $class) {
return true;
}
}
return false;
}
/**
* Render an exception into an HTTP response.
*
* @access public
* @param Request $request
* @param Throwable $e
* @return Response
*/
public function render(Request $request, Throwable $e): Response
{
if ($e instanceof HttpResponseException) {
return $e->getResponse();
} elseif ($e instanceof HttpException) {
return $this->renderHttpException($request, $e);
} else {
return $this->convertExceptionToResponse($request, $e);
}
}
/**
* @access public
* @param Output $output
* @param Throwable $e
*/
public function renderForConsole(Output $output, Throwable $e): void
{
if ($this->app->isDebug()) {
$output->setVerbosity(Output::VERBOSITY_DEBUG);
}
$output->renderException($e);
}
/**
* @access protected
* @param HttpException $e
* @return Response
*/
protected function renderHttpException(Request $request, HttpException $e): Response
{
$status = $e->getStatusCode();
$template = $this->app->config->get('app.http_exception_template');
if (!$this->app->isDebug() && !empty($template[$status])) {
return Response::create($template[$status], 'view', $status)->assign(['e' => $e]);
} else {
return $this->convertExceptionToResponse($request, $e);
}
}
/**
* 收集异常数据
* @param Throwable $exception
* @return array
*/
protected function convertExceptionToArray(Throwable $exception): array
{
return $this->app->isDebug() ? $this->getDebugMsg($exception) : $this->getDeployMsg($exception);
}
/**
* 是否显示错误信息
* @param \Throwable $exception
* @return bool
*/
protected function isShowErrorMsg(Throwable $exception)
{
foreach ($this->showErrorMsg as $class) {
if ($exception instanceof $class) {
return true;
}
}
return false;
}
/**
* 获取部署模式异常数据
* @access protected
* @param Throwable $exception
* @return array
*/
protected function getDeployMsg(Throwable $exception): array
{
$showErrorMsg = $this->isShowErrorMsg($exception);
if ($showErrorMsg || $this->app->config->get('app.show_error_msg', false)) {
$message = $this->getMessage($exception);
} else {
// 不显示详细错误信息
$message = $this->app->config->get('app.error_message');
}
return [
'code' => $this->getCode($exception),
'message' => $message,
];
}
/**
* 收集调试模式异常数据
* @access protected
* @param Throwable $exception
* @return array
*/
protected function getDebugMsg(Throwable $exception): array
{
// 调试模式,获取详细的错误信息
$traces = [];
$nextException = $exception;
do {
$traces[] = [
'name' => $nextException::class,
'file' => $nextException->getFile(),
'line' => $nextException->getLine(),
'code' => $this->getCode($nextException),
'message' => $this->getMessage($nextException),
'trace' => $nextException->getTrace(),
'source' => $this->getSourceCode($nextException),
];
} while ($nextException = $nextException->getPrevious());
return [
'code' => $this->getCode($exception),
'message' => $this->getMessage($exception),
'traces' => $traces,
'datas' => $this->getExtendData($exception),
'tables' => [
'GET Data' => $this->app->request->get(),
'POST Data' => $this->app->request->post(),
'Files' => $this->app->request->file(),
'Cookies' => $this->app->request->cookie(),
'Session' => $this->app->exists('session') ? $this->app->session->all() : [],
'Server/Request Data' => $this->app->request->server(),
],
];
}
protected function isJson(Request $request)
{
return $request->isJson();
}
/**
* @access protected
* @param Throwable $exception
* @return Response
*/
protected function convertExceptionToResponse(Request $request, Throwable $exception): Response
{
if ($this->isJson($request)) {
$response = Response::create($this->convertExceptionToArray($exception), 'json');
} else {
$response = Response::create($this->renderExceptionContent($exception));
}
if ($exception instanceof HttpException) {
$statusCode = $exception->getStatusCode();
$response->header($exception->getHeaders());
}
return $response->code($statusCode ?? 500);
}
protected function renderExceptionContent(Throwable $exception): string
{
ob_start();
$data = $this->convertExceptionToArray($exception);
extract($data);
include $this->app->config->get('app.exception_tmpl') ?: __DIR__ . '/../../tpl/think_exception.tpl';
return ob_get_clean();
}
/**
* 获取错误编码
* ErrorException则使用错误级别作为错误编码
* @access protected
* @param Throwable $exception
* @return integer 错误编码
*/
protected function getCode(Throwable $exception)
{
$code = $exception->getCode();
if (!$code && $exception instanceof ErrorException) {
$code = $exception->getSeverity();
}
return $code;
}
/**
* 获取错误信息
* ErrorException则使用错误级别作为错误编码
* @access protected
* @param Throwable $exception
* @return string 错误信息
*/
protected function getMessage(Throwable $exception): string
{
$message = $exception->getMessage();
if ($this->app->runningInConsole()) {
return $message;
}
$lang = $this->app->lang;
if (str_contains($message, ':')) {
$name = strstr($message, ':', true);
$message = $lang->has($name) ? $lang->get($name) . strstr($message, ':') : $message;
} elseif (str_contains($message, ',')) {
$name = strstr($message, ',', true);
$message = $lang->has($name) ? $lang->get($name) . ':' . substr(strstr($message, ','), 1) : $message;
} elseif ($lang->has($message)) {
$message = $lang->get($message);
}
return $message;
}
/**
* 获取出错文件内容
* 获取错误的前9行和后9行
* @access protected
* @param Throwable $exception
* @return array 错误文件内容
*/
protected function getSourceCode(Throwable $exception): array
{
// 读取前9行和后9行
$line = $exception->getLine();
$first = ($line - 9 > 0) ? $line - 9 : 1;
try {
$contents = file($exception->getFile()) ?: [];
$source = [
'first' => $first,
'source' => array_slice($contents, $first - 1, 19),
];
} catch (Exception $e) {
$source = [];
}
return $source;
}
/**
* 获取异常扩展信息
* 用于非调试模式html返回类型显示
* @access protected
* @param Throwable $exception
* @return array 异常类定义的扩展数据
*/
protected function getExtendData(Throwable $exception): array
{
$data = [];
if ($exception instanceof \think\Exception) {
$data = $exception->getData();
}
return $data;
}
/**
* 获取常量列表
* @access protected
* @return array 常量列表
*/
protected function getConst(): array
{
$const = get_defined_constants(true);
return $const['user'] ?? [];
}
}

View File

@@ -0,0 +1,36 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2025 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\exception;
use Exception;
/**
* HTTP异常
*/
class HttpException extends \RuntimeException
{
public function __construct(private int $statusCode, string $message = '', ?Exception $previous = null, private array $headers = [], $code = 0)
{
parent::__construct($message, $code, $previous);
}
public function getStatusCode()
{
return $this->statusCode;
}
public function getHeaders()
{
return $this->headers;
}
}

View File

@@ -0,0 +1,31 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2025 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\exception;
use think\Response;
/**
* HTTP响应异常
*/
class HttpResponseException extends \RuntimeException
{
public function __construct(protected Response $response)
{
}
public function getResponse()
{
return $this->response;
}
}

View File

@@ -0,0 +1,21 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2025 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\exception;
use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInvalidArgumentInterface;
/**
* 非法数据异常
*/
class InvalidArgumentException extends \InvalidArgumentException implements SimpleCacheInvalidArgumentInterface
{
}

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