初始化项目
This commit is contained in:
+111
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* ThinkPHP 类库映射定义
|
||||
*/
|
||||
return [
|
||||
'think\App' => CORE_PATH . 'App' . EXT,
|
||||
'think\Build' => CORE_PATH . 'Build' . EXT,
|
||||
'think\Cache' => CORE_PATH . 'Cache' . EXT,
|
||||
'think\cache\driver\Apc' => CORE_PATH . 'cache' . DS . 'driver' . DS . 'Apc' . EXT,
|
||||
'think\cache\driver\File' => CORE_PATH . 'cache' . DS . 'driver' . DS . 'File' . EXT,
|
||||
'think\cache\driver\Lite' => CORE_PATH . 'cache' . DS . 'driver' . DS . 'Lite' . EXT,
|
||||
'think\cache\driver\Memcache' => CORE_PATH . 'cache' . DS . 'driver' . DS . 'Memcache' . EXT,
|
||||
'think\cache\driver\Memcached' => CORE_PATH . 'cache' . DS . 'driver' . DS . 'Memcached' . EXT,
|
||||
'think\cache\driver\Redis' => CORE_PATH . 'cache' . DS . 'driver' . DS . 'Redis' . EXT,
|
||||
'think\cache\driver\Redisd' => CORE_PATH . 'cache' . DS . 'driver' . DS . 'Redisd' . EXT,
|
||||
'think\cache\driver\Sae' => CORE_PATH . 'cache' . DS . 'driver' . DS . 'Sae' . EXT,
|
||||
'think\cache\driver\Secache' => CORE_PATH . 'cache' . DS . 'driver' . DS . 'Secache' . EXT,
|
||||
'think\cache\driver\Sqlite' => CORE_PATH . 'cache' . DS . 'driver' . DS . 'Sqlite' . EXT,
|
||||
'think\cache\driver\Wincache' => CORE_PATH . 'cache' . DS . 'driver' . DS . 'Wincache' . EXT,
|
||||
'think\cache\driver\Xcache' => CORE_PATH . 'cache' . DS . 'driver' . DS . 'Xcache' . EXT,
|
||||
'think\Collection' => CORE_PATH . 'Collection' . EXT,
|
||||
'think\Config' => CORE_PATH . 'Config' . EXT,
|
||||
'think\config\Ini' => CORE_PATH . 'config' . DS . 'driver' . DS . 'Ini' . EXT,
|
||||
'think\config\Json' => CORE_PATH . 'config' . DS . 'driver' . DS . 'Json' . EXT,
|
||||
'think\config\Xml' => CORE_PATH . 'config' . DS . 'driver' . DS . 'Xml' . EXT,
|
||||
'think\Console' => CORE_PATH . 'Console' . EXT,
|
||||
'think\Controller' => CORE_PATH . 'Controller' . EXT,
|
||||
'think\controller\Hprose' => CORE_PATH . 'controller' . DS . 'Hprose' . EXT,
|
||||
'think\controller\Jsonrpc' => CORE_PATH . 'controller' . DS . 'Jsonrpc' . EXT,
|
||||
'think\controller\Rest' => CORE_PATH . 'controller' . DS . 'Rest' . EXT,
|
||||
'think\controller\Rpc' => CORE_PATH . 'controller' . DS . 'Rpc' . EXT,
|
||||
'think\controller\Yar' => CORE_PATH . 'controller' . DS . 'Yar' . EXT,
|
||||
'think\Cookie' => CORE_PATH . 'Cookie' . EXT,
|
||||
'think\Db' => CORE_PATH . 'Db' . EXT,
|
||||
'think\db\Connection' => CORE_PATH . 'db' . DS . 'Connection' . EXT,
|
||||
'think\db\connector\Mysql' => CORE_PATH . 'db' . DS . 'connector' . DS . 'Mysql' . EXT,
|
||||
'think\db\connector\Oracle' => CORE_PATH . 'db' . DS . 'connector' . DS . 'Oracle' . EXT,
|
||||
'think\db\connector\Pgsql' => CORE_PATH . 'db' . DS . 'connector' . DS . 'Pgsql' . EXT,
|
||||
'think\db\connector\Sqlite' => CORE_PATH . 'db' . DS . 'connector' . DS . 'Sqlite' . EXT,
|
||||
'think\db\connector\Sqlsrv' => CORE_PATH . 'db' . DS . 'connector' . DS . 'Sqlsrv' . EXT,
|
||||
'think\db\Builder' => CORE_PATH . 'db' . DS . 'Builder' . EXT,
|
||||
'think\db\builder\Mysql' => CORE_PATH . 'db' . DS . 'builder' . DS . 'Mysql' . EXT,
|
||||
'think\db\builder\Oracle' => CORE_PATH . 'db' . DS . 'builder' . DS . 'Oracle' . EXT,
|
||||
'think\db\builder\Pgsql' => CORE_PATH . 'db' . DS . 'builder' . DS . 'Pgsql' . EXT,
|
||||
'think\db\builder\Sqlite' => CORE_PATH . 'db' . DS . 'builder' . DS . 'Sqlite' . EXT,
|
||||
'think\db\builder\Sqlsrv' => CORE_PATH . 'db' . DS . 'builder' . DS . 'Sqlsrv' . EXT,
|
||||
'think\db\Query' => CORE_PATH . 'db' . DS . 'Query' . EXT,
|
||||
'think\Debug' => CORE_PATH . 'Debug' . EXT,
|
||||
'think\Error' => CORE_PATH . 'Error' . EXT,
|
||||
'think\Exception' => CORE_PATH . 'Exception' . EXT,
|
||||
'think\exception\ClassNotFoundException' => CORE_PATH . 'exception' . DS . 'ClassNotFoundException' . EXT,
|
||||
'think\exception\DbException' => CORE_PATH . 'exception' . DS . 'DbException' . EXT,
|
||||
'think\exception\ErrorException' => CORE_PATH . 'exception' . DS . 'ErrorException' . EXT,
|
||||
'think\exception\Handle' => CORE_PATH . 'exception' . DS . 'Handle' . EXT,
|
||||
'think\exception\HttpException' => CORE_PATH . 'exception' . DS . 'HttpException' . EXT,
|
||||
'think\exception\HttpResponseException' => CORE_PATH . 'exception' . DS . 'HttpResponseException' . EXT,
|
||||
'think\exception\PDOException' => CORE_PATH . 'exception' . DS . 'PDOException' . EXT,
|
||||
'think\exception\TemplateNotFoundException' => CORE_PATH . 'exception' . DS . 'TemplateNotFoundException' . EXT,
|
||||
'think\exception\ThrowableError' => CORE_PATH . 'exception' . DS . 'ThrowableError' . EXT,
|
||||
'think\exception\ValidateException' => CORE_PATH . 'exception' . DS . 'ValidateException' . EXT,
|
||||
'think\File' => CORE_PATH . 'File' . EXT,
|
||||
'think\Hook' => CORE_PATH . 'Hook' . EXT,
|
||||
'think\Lang' => CORE_PATH . 'Lang' . EXT,
|
||||
'think\Log' => CORE_PATH . 'Log' . EXT,
|
||||
'think\log\driver\Browser' => CORE_PATH . 'log' . DS . 'driver' . DS . 'Browser' . EXT,
|
||||
'think\log\driver\File' => CORE_PATH . 'log' . DS . 'driver' . DS . 'File' . EXT,
|
||||
'think\log\driver\Sae' => CORE_PATH . 'log' . DS . 'driver' . DS . 'Sae' . EXT,
|
||||
'think\log\driver\Socket' => CORE_PATH . 'log' . DS . 'driver' . DS . 'Socket' . EXT,
|
||||
'think\log\driver\Trace' => CORE_PATH . 'log' . DS . 'driver' . DS . 'Trace' . EXT,
|
||||
'think\Model' => CORE_PATH . 'Model' . EXT,
|
||||
'think\model\Merge' => CORE_PATH . 'model' . DS . 'Merge' . EXT,
|
||||
'think\model\Pivot' => CORE_PATH . 'model' . DS . 'Pivot' . EXT,
|
||||
'think\model\Relation' => CORE_PATH . 'model' . DS . 'Relation' . EXT,
|
||||
'think\Process' => CORE_PATH . 'Process' . EXT,
|
||||
'think\Paginator' => CORE_PATH . 'Paginator' . EXT,
|
||||
'think\paginator\Collection' => CORE_PATH . 'paginator' . DS . 'Collection' . EXT,
|
||||
'think\paginator\driver\Bootstrap' => CORE_PATH . 'paginator' . DS . 'driver' . DS . 'Bootstrap' . EXT,
|
||||
'think\Request' => CORE_PATH . 'Request' . EXT,
|
||||
'think\Response' => CORE_PATH . 'Response' . EXT,
|
||||
'think\response\Json' => CORE_PATH . 'response' . DS . 'Json' . EXT,
|
||||
'think\response\Jsonp' => CORE_PATH . 'response' . DS . 'Jsonp' . EXT,
|
||||
'think\response\Redirect' => CORE_PATH . 'response' . DS . 'Redirect' . EXT,
|
||||
'think\response\View' => CORE_PATH . 'response' . DS . 'View' . EXT,
|
||||
'think\response\Xml' => CORE_PATH . 'response' . DS . 'Xml' . EXT,
|
||||
'think\Route' => CORE_PATH . 'Route' . EXT,
|
||||
'think\Session' => CORE_PATH . 'Session' . EXT,
|
||||
'think\session\driver\Memcache' => CORE_PATH . 'session' . DS . 'driver' . DS . 'Memcache' . EXT,
|
||||
'think\session\driver\Memcached' => CORE_PATH . 'session' . DS . 'driver' . DS . 'Memcached' . EXT,
|
||||
'think\session\driver\Redis' => CORE_PATH . 'session' . DS . 'driver' . DS . 'Redis' . EXT,
|
||||
'think\Template' => CORE_PATH . 'Template' . EXT,
|
||||
'think\template\Taglib' => CORE_PATH . 'template' . DS . 'Taglib' . EXT,
|
||||
'think\template\taglib\Cx' => CORE_PATH . 'template' . DS . 'taglib' . DS . 'Cx' . EXT,
|
||||
'think\template\driver\File' => CORE_PATH . 'template' . DS . 'driver' . DS . 'File' . EXT,
|
||||
'think\template\driver\Sae' => CORE_PATH . 'template' . DS . 'driver' . DS . 'Sae' . EXT,
|
||||
'think\Url' => CORE_PATH . 'Url' . EXT,
|
||||
'think\Validate' => CORE_PATH . 'Validate' . EXT,
|
||||
'think\View' => CORE_PATH . 'View' . EXT,
|
||||
'think\view\driver\Think' => CORE_PATH . 'view' . DS . 'driver' . DS . 'Think' . EXT,
|
||||
'think\view\driver\Php' => CORE_PATH . 'view' . DS . 'driver' . DS . 'Php' . EXT,
|
||||
'traits\controller\Jump' => TRAIT_PATH . 'controller' . DS . 'Jump' . EXT,
|
||||
];
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
define('THINK_VERSION', '5.0.0 RC3');
|
||||
define('START_TIME', microtime(true));
|
||||
define('START_MEM', memory_get_usage());
|
||||
define('EXT', '.php');
|
||||
define('DS', DIRECTORY_SEPARATOR);
|
||||
defined('THINK_PATH') or define('THINK_PATH', __DIR__ . DS);
|
||||
define('LIB_PATH', THINK_PATH . 'library' . DS);
|
||||
define('MODE_PATH', THINK_PATH . 'mode' . DS); // 系统应用模式目录
|
||||
define('CORE_PATH', LIB_PATH . 'think' . DS);
|
||||
define('TRAIT_PATH', LIB_PATH . 'traits' . DS);
|
||||
defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']) . DS);
|
||||
defined('ROOT_PATH') or define('ROOT_PATH', dirname(APP_PATH) . DS);
|
||||
defined('EXTEND_PATH') or define('EXTEND_PATH', ROOT_PATH . 'extend' . DS);
|
||||
defined('VENDOR_PATH') or define('VENDOR_PATH', ROOT_PATH . 'vendor' . DS);
|
||||
defined('RUNTIME_PATH') or define('RUNTIME_PATH', ROOT_PATH . 'runtime' . DS);
|
||||
defined('LOG_PATH') or define('LOG_PATH', RUNTIME_PATH . 'log' . DS);
|
||||
defined('CACHE_PATH') or define('CACHE_PATH', RUNTIME_PATH . 'cache' . DS);
|
||||
defined('TEMP_PATH') or define('TEMP_PATH', RUNTIME_PATH . 'temp' . DS);
|
||||
defined('CONF_PATH') or define('CONF_PATH', APP_PATH); // 配置文件目录
|
||||
defined('CONF_EXT') or define('CONF_EXT', EXT); // 配置文件后缀
|
||||
defined('ENV_PREFIX') or define('ENV_PREFIX', 'PHP_'); // 环境变量的配置前缀
|
||||
defined('IS_API') or define('IS_API', false); // 是否API接口
|
||||
defined('APP_AUTO_RUN') or define('APP_AUTO_RUN', true); // 是否自动运行
|
||||
|
||||
// 环境常量
|
||||
define('IS_CLI', PHP_SAPI == 'cli' ? true : false);
|
||||
define('IS_WIN', strstr(PHP_OS, 'WIN') ? true : false);
|
||||
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
// +----------------------------------------------------------------------
|
||||
// | 应用设置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
// 应用命名空间
|
||||
'app_namespace' => 'app',
|
||||
// 应用调试模式
|
||||
'app_debug' => true,
|
||||
// 应用模式状态
|
||||
'app_status' => '',
|
||||
// 是否支持多模块
|
||||
'app_multi_module' => true,
|
||||
// 注册的根命名空间
|
||||
'root_namespace' => [],
|
||||
// 扩展配置文件
|
||||
'extra_config_list' => ['database', 'route', 'validate'],
|
||||
// 扩展函数文件
|
||||
'extra_file_list' => [THINK_PATH . 'helper' . EXT],
|
||||
// 默认输出类型
|
||||
'default_return_type' => 'html',
|
||||
// 默认AJAX 数据返回格式,可选json xml ...
|
||||
'default_ajax_return' => 'json',
|
||||
// 默认JSONP格式返回的处理方法
|
||||
'default_jsonp_handler' => 'jsonpReturn',
|
||||
// 默认JSONP处理方法
|
||||
'var_jsonp_handler' => 'callback',
|
||||
// 默认时区
|
||||
'default_timezone' => 'PRC',
|
||||
// 是否开启多语言
|
||||
'lang_switch_on' => false,
|
||||
// 默认全局过滤方法 用逗号分隔多个
|
||||
'default_filter' => '',
|
||||
// 默认语言
|
||||
'default_lang' => 'zh-cn',
|
||||
// 应用类库后缀
|
||||
'class_suffix' => false,
|
||||
// 控制器类后缀
|
||||
'controller_suffix' => false,
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | 模块设置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
// 默认模块名
|
||||
'default_module' => 'index',
|
||||
// 禁止访问模块
|
||||
'deny_module_list' => ['common'],
|
||||
// 默认控制器名
|
||||
'default_controller' => 'Index',
|
||||
// 默认操作名
|
||||
'default_action' => 'index',
|
||||
// 默认验证器
|
||||
'default_validate' => '',
|
||||
// 默认的空控制器名
|
||||
'empty_controller' => 'Error',
|
||||
// 操作方法后缀
|
||||
'action_suffix' => '',
|
||||
// 自动搜索控制器
|
||||
'controller_auto_search' => false,
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | URL设置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
// PATHINFO变量名 用于兼容模式
|
||||
'var_pathinfo' => 's',
|
||||
// 兼容PATH_INFO获取
|
||||
'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
|
||||
// pathinfo分隔符
|
||||
'pathinfo_depr' => '/',
|
||||
// URL伪静态后缀
|
||||
'url_html_suffix' => 'html',
|
||||
// URL普通方式参数 用于自动生成
|
||||
'url_common_param' => false,
|
||||
//url禁止访问的后缀
|
||||
'url_deny_suffix' => 'ico|png|gif|jpg',
|
||||
// URL参数方式 0 按名称成对解析 1 按顺序解析
|
||||
'url_param_type' => 0,
|
||||
// 是否开启路由
|
||||
'url_route_on' => true,
|
||||
// 是否强制使用路由
|
||||
'url_route_must' => false,
|
||||
// 域名部署
|
||||
'url_domain_deploy' => false,
|
||||
// 域名根,如.thinkphp.cn
|
||||
'url_domain_root' => '',
|
||||
// 是否自动转换URL中的控制器和操作名
|
||||
'url_convert' => true,
|
||||
// 默认的访问控制器层
|
||||
'url_controller_layer' => 'controller',
|
||||
// 表单请求类型伪装变量
|
||||
'var_method' => '_method',
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | 模板引擎设置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
'template' => [
|
||||
// 模板引擎类型 支持 php think 支持扩展
|
||||
'type' => 'Think',
|
||||
// 模板路径
|
||||
'view_path' => '',
|
||||
// 模板后缀
|
||||
'view_suffix' => 'html',
|
||||
// 模板文件名分隔符
|
||||
'view_depr' => DS,
|
||||
// 模板引擎普通标签开始标记
|
||||
'tpl_begin' => '{',
|
||||
// 模板引擎普通标签结束标记
|
||||
'tpl_end' => '}',
|
||||
// 标签库标签开始标记
|
||||
'taglib_begin' => '{',
|
||||
// 标签库标签结束标记
|
||||
'taglib_end' => '}',
|
||||
],
|
||||
|
||||
// 视图输出字符串内容替换
|
||||
'view_replace_str' => [],
|
||||
// 默认跳转页面对应的模板文件
|
||||
'dispatch_success_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl',
|
||||
'dispatch_error_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl',
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | 异常及错误设置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
// 异常页面的模板文件
|
||||
'exception_tmpl' => THINK_PATH . 'tpl' . DS . 'think_exception.tpl',
|
||||
|
||||
// 错误显示信息,非调试模式有效
|
||||
'error_message' => '页面错误!请稍后再试~',
|
||||
// 显示错误信息
|
||||
'show_error_msg' => false,
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | 日志设置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
'log' => [
|
||||
// 日志记录方式,支持 file socket trace sae
|
||||
'type' => 'File',
|
||||
// 日志保存目录
|
||||
'path' => LOG_PATH,
|
||||
],
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | 缓存设置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
'cache' => [
|
||||
// 驱动方式
|
||||
'type' => 'File',
|
||||
// 缓存保存目录
|
||||
'path' => CACHE_PATH,
|
||||
// 缓存前缀
|
||||
'prefix' => '',
|
||||
// 缓存有效期 0表示永久缓存
|
||||
'expire' => 0,
|
||||
],
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | 会话设置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
'session' => [
|
||||
'id' => '',
|
||||
// SESSION_ID的提交变量,解决flash上传跨域
|
||||
'var_session_id' => '',
|
||||
// SESSION 前缀
|
||||
'prefix' => 'think',
|
||||
// 驱动方式 支持redis memcache memcached
|
||||
'type' => '',
|
||||
// 是否自动开启 SESSION
|
||||
'auto_start' => true,
|
||||
],
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | Cookie设置
|
||||
// +----------------------------------------------------------------------
|
||||
'cookie' => [
|
||||
// cookie 名称前缀
|
||||
'prefix' => '',
|
||||
// cookie 保存时间
|
||||
'expire' => 0,
|
||||
// cookie 保存路径
|
||||
'path' => '/',
|
||||
// cookie 有效域名
|
||||
'domain' => '',
|
||||
// cookie 启用安全传输
|
||||
'secure' => false,
|
||||
// httponly设置
|
||||
'httponly' => '',
|
||||
// 是否使用 setcookie
|
||||
'setcookie' => true,
|
||||
],
|
||||
|
||||
// +----------------------------------------------------------------------
|
||||
// | 数据库设置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
'database' => [
|
||||
// 数据库类型
|
||||
'type' => 'mysql',
|
||||
// 数据库连接DSN配置
|
||||
'dsn' => '',
|
||||
// 服务器地址
|
||||
'hostname' => 'localhost',
|
||||
// 数据库名
|
||||
'database' => '',
|
||||
// 数据库用户名
|
||||
'username' => 'root',
|
||||
// 数据库密码
|
||||
'password' => '',
|
||||
// 数据库连接端口
|
||||
'hostport' => '',
|
||||
// 数据库连接参数
|
||||
'params' => [],
|
||||
// 数据库编码默认采用utf8
|
||||
'charset' => 'utf8',
|
||||
// 数据库表前缀
|
||||
'prefix' => '',
|
||||
// 数据库调试模式
|
||||
'debug' => false,
|
||||
// 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
|
||||
'deploy' => 0,
|
||||
// 数据库读写是否分离 主从式有效
|
||||
'rw_separate' => false,
|
||||
// 读写分离后 主服务器数量
|
||||
'master_num' => 1,
|
||||
// 指定从服务器序号
|
||||
'slave_no' => '',
|
||||
// 是否严格检查字段是否存在
|
||||
'fields_strict' => true,
|
||||
// 自动写入时间戳字段
|
||||
'auto_timestamp' => false,
|
||||
],
|
||||
//分页配置
|
||||
'paginate' => [
|
||||
'type' => 'bootstrap',
|
||||
'var_page' => 'page',
|
||||
'list_rows' => 15,
|
||||
],
|
||||
|
||||
];
|
||||
+452
@@ -0,0 +1,452 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
//------------------------
|
||||
// ThinkPHP 助手函数
|
||||
//-------------------------
|
||||
|
||||
use think\Cache;
|
||||
use think\Config;
|
||||
use think\Cookie;
|
||||
use think\Db;
|
||||
use think\Debug;
|
||||
use think\Lang;
|
||||
use think\Loader;
|
||||
use think\Log;
|
||||
use think\Request;
|
||||
use think\Response;
|
||||
use think\Session;
|
||||
use think\Url;
|
||||
use think\View;
|
||||
|
||||
/**
|
||||
* 快速导入Traits PHP5.5以上无需调用
|
||||
* @param string $class trait库
|
||||
* @param string $ext 类库后缀
|
||||
* @return boolean
|
||||
*/
|
||||
function load_trait($class, $ext = EXT)
|
||||
{
|
||||
return Loader::import($class, TRAIT_PATH, $ext);
|
||||
}
|
||||
|
||||
/**
|
||||
* 抛出异常处理
|
||||
*
|
||||
* @param string $msg 异常消息
|
||||
* @param integer $code 异常代码 默认为0
|
||||
* @param string $exception 异常类
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
function exception($msg, $code = 0, $exception = '')
|
||||
{
|
||||
$e = $exception ?: '\think\Exception';
|
||||
throw new $e($msg, $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录时间(微秒)和内存使用情况
|
||||
* @param string $start 开始标签
|
||||
* @param string $end 结束标签
|
||||
* @param integer|string $dec 小数位 如果是m 表示统计内存占用
|
||||
* @return mixed
|
||||
*/
|
||||
function debug($start, $end = '', $dec = 6)
|
||||
{
|
||||
if ('' == $end) {
|
||||
Debug::remark($start);
|
||||
} else {
|
||||
return 'm' == $dec ? Debug::getRangeMem($start, $end) : Debug::getRangeTime($start, $end, $dec);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取语言变量值
|
||||
* @param string $name 语言变量名
|
||||
* @param array $vars 动态变量值
|
||||
* @param string $lang 语言
|
||||
* @return mixed
|
||||
*/
|
||||
function lang($name, $vars = [], $lang = '')
|
||||
{
|
||||
return Lang::get($name, $vars, $lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取和设置配置参数
|
||||
* @param string|array $name 参数名
|
||||
* @param mixed $value 参数值
|
||||
* @param string $range 作用域
|
||||
* @return mixed
|
||||
*/
|
||||
function config($name = '', $value = null, $range = '')
|
||||
{
|
||||
if (is_null($value) && is_string($name)) {
|
||||
return Config::get($name, $range);
|
||||
} else {
|
||||
return Config::set($name, $value, $range);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输入数据 支持默认值和过滤
|
||||
* @param string $key 获取的变量名
|
||||
* @param mixed $default 默认值
|
||||
* @param string $filter 过滤方法
|
||||
* @param bool $merge 是否合并系统默认过滤方法
|
||||
* @return mixed
|
||||
*/
|
||||
function input($key, $default = null, $filter = null)
|
||||
{
|
||||
if (0 === strpos($key, '?')) {
|
||||
$key = substr($key, 1);
|
||||
$has = true;
|
||||
}
|
||||
if ($pos = strpos($key, '.')) {
|
||||
// 指定参数来源
|
||||
$method = substr($key, 0, $pos);
|
||||
if (in_array($method, ['get', 'post', 'put', 'delete', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) {
|
||||
$key = substr($key, $pos + 1);
|
||||
} else {
|
||||
$method = 'param';
|
||||
}
|
||||
} else {
|
||||
// 默认为自动判断
|
||||
$method = 'param';
|
||||
}
|
||||
if(isset($has)){
|
||||
return request()->has($key, $method, $default);
|
||||
}else{
|
||||
return request()->$method($key, $default, $filter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染输出Widget
|
||||
* @param string $name Widget名称
|
||||
* @param array $data 传人的参数
|
||||
* @return mixed
|
||||
*/
|
||||
function widget($name, $data = [])
|
||||
{
|
||||
return Loader::action($name, $data, 'widget');
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化Model
|
||||
* @param string $name Model名称
|
||||
* @param string $layer 业务层名称
|
||||
* @param bool $appendSuffix 是否添加类名后缀
|
||||
* @return \think\Model
|
||||
*/
|
||||
function model($name = '', $layer = 'model', $appendSuffix = false)
|
||||
{
|
||||
return Loader::model($name, $layer, $appendSuffix);
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化验证器
|
||||
* @param string $name 验证器名称
|
||||
* @param string $layer 业务层名称
|
||||
* @param bool $appendSuffix 是否添加类名后缀
|
||||
* @return \think\Validate
|
||||
*/
|
||||
function validate($name = '', $layer = 'validate', $appendSuffix = false)
|
||||
{
|
||||
return Loader::validate($name, $layer, $appendSuffix);
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化数据库类
|
||||
* @param string $name 操作的数据表名称(不含前缀)
|
||||
* @param array|string $config 数据库配置参数
|
||||
* @return \think\db\Query
|
||||
*/
|
||||
function db($name = '', $config = [])
|
||||
{
|
||||
return Db::connect($config)->name($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化控制器 格式:[模块/]控制器
|
||||
* @param string $name 资源地址
|
||||
* @param string $layer 控制层名称
|
||||
* @param bool $appendSuffix 是否添加类名后缀
|
||||
* @return \think\Controller
|
||||
*/
|
||||
function controller($name, $layer = 'controller', $appendSuffix = false)
|
||||
{
|
||||
return Loader::controller($name, $layer, $appendSuffix);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用模块的操作方法 参数格式 [模块/控制器/]操作
|
||||
* @param string $url 调用地址
|
||||
* @param string|array $vars 调用参数 支持字符串和数组
|
||||
* @param string $layer 要调用的控制层名称
|
||||
* @param bool $appendSuffix 是否添加类名后缀
|
||||
* @return mixed
|
||||
*/
|
||||
function action($url, $vars = [], $layer = 'controller', $appendSuffix = false)
|
||||
{
|
||||
return Loader::action($url, $vars, $layer, $appendSuffix);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入所需的类库 同java的Import 本函数有缓存功能
|
||||
* @param string $class 类库命名空间字符串
|
||||
* @param string $baseUrl 起始路径
|
||||
* @param string $ext 导入的文件扩展名
|
||||
* @return boolean
|
||||
*/
|
||||
function import($class, $baseUrl = '', $ext = EXT)
|
||||
{
|
||||
return Loader::import($class, $baseUrl, $ext);
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速导入第三方框架类库 所有第三方框架的类库文件统一放到 系统的Vendor目录下面
|
||||
* @param string $class 类库
|
||||
* @param string $ext 类库后缀
|
||||
* @return boolean
|
||||
*/
|
||||
function vendor($class, $ext = EXT)
|
||||
{
|
||||
return Loader::import($class, VENDOR_PATH, $ext);
|
||||
}
|
||||
|
||||
/**
|
||||
* 浏览器友好的变量输出
|
||||
* @param mixed $var 变量
|
||||
* @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串
|
||||
* @param string $label 标签 默认为空
|
||||
* @return void|string
|
||||
*/
|
||||
function dump($var, $echo = true, $label = null)
|
||||
{
|
||||
return Debug::dump($var, $echo, $label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Url生成
|
||||
* @param string $url 路由地址
|
||||
* @param string|array $value 变量
|
||||
* @param bool|string $suffix 前缀
|
||||
* @param bool|string $domain 域名
|
||||
* @return string
|
||||
*/
|
||||
function url($url = '', $vars = '', $suffix = true, $domain = false)
|
||||
{
|
||||
return Url::build($url, $vars, $suffix, $domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Session管理
|
||||
* @param string|array $name session名称,如果为数组表示进行session设置
|
||||
* @param mixed $value session值
|
||||
* @param string $prefix 前缀
|
||||
* @return mixed
|
||||
*/
|
||||
function session($name, $value = '', $prefix = null)
|
||||
{
|
||||
if (is_array($name)) {
|
||||
// 初始化
|
||||
Session::init($name);
|
||||
} elseif (is_null($name)) {
|
||||
// 清除
|
||||
Session::clear($value);
|
||||
} elseif ('' === $value) {
|
||||
// 判断或获取
|
||||
return 0 === strpos($name, '?') ? Session::has(substr($name, 1), $prefix) : Session::get($name, $prefix);
|
||||
} elseif (is_null($value)) {
|
||||
// 删除
|
||||
return Session::delete($name, $prefix);
|
||||
} else {
|
||||
// 设置
|
||||
return Session::set($name, $value, $prefix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cookie管理
|
||||
* @param string|array $name cookie名称,如果为数组表示进行cookie设置
|
||||
* @param mixed $value cookie值
|
||||
* @param mixed $option 参数
|
||||
* @return mixed
|
||||
*/
|
||||
function cookie($name, $value = '', $option = null)
|
||||
{
|
||||
if (is_array($name)) {
|
||||
// 初始化
|
||||
Cookie::init($name);
|
||||
} elseif (is_null($name)) {
|
||||
// 清除
|
||||
Cookie::clear($value);
|
||||
} elseif ('' === $value) {
|
||||
// 获取
|
||||
return Cookie::get($name);
|
||||
} elseif (is_null($value)) {
|
||||
// 删除
|
||||
return Cookie::delete($name);
|
||||
} else {
|
||||
// 设置
|
||||
return Cookie::set($name, $value, $option);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存管理
|
||||
* @param mixed $name 缓存名称,如果为数组表示进行缓存设置
|
||||
* @param mixed $value 缓存值
|
||||
* @param mixed $options 缓存参数
|
||||
* @return mixed
|
||||
*/
|
||||
function cache($name, $value = '', $options = null)
|
||||
{
|
||||
if (is_array($options)) {
|
||||
// 缓存操作的同时初始化
|
||||
Cache::connect($options);
|
||||
} elseif (is_array($name)) {
|
||||
// 缓存初始化
|
||||
return Cache::connect($name);
|
||||
}
|
||||
if ('' === $value) {
|
||||
// 获取缓存
|
||||
return Cache::get($name);
|
||||
} elseif (is_null($value)) {
|
||||
// 删除缓存
|
||||
return Cache::rm($name);
|
||||
} else {
|
||||
// 缓存数据
|
||||
if (is_array($options)) {
|
||||
$expire = isset($options['expire']) ? $options['expire'] : null; //修复查询缓存无法设置过期时间
|
||||
} else {
|
||||
$expire = is_numeric($options) ? $options : null; //默认快捷缓存设置过期时间
|
||||
}
|
||||
return Cache::set($name, $value, $expire);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录日志信息
|
||||
* @param mixed $log log信息 支持字符串和数组
|
||||
* @param string $level 日志级别
|
||||
* @return void|array
|
||||
*/
|
||||
function trace($log = '[think]', $level = 'log')
|
||||
{
|
||||
if ('[think]' === $log) {
|
||||
return Log::getLog();
|
||||
} else {
|
||||
Log::record($log, $level);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前Request对象实例
|
||||
* @return Request
|
||||
*/
|
||||
function request()
|
||||
{
|
||||
return Request::instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建普通 Response 对象实例
|
||||
* @param mixed $data 输出数据
|
||||
* @param int|string $code 状态码
|
||||
* @param array $header 头信息
|
||||
* @param string $type
|
||||
* @return Response
|
||||
*/
|
||||
function response($data = [], $code = 200, $header = [], $type = 'html')
|
||||
{
|
||||
return Response::create($data, $type, $code, $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染模板输出
|
||||
* @param string $template 模板文件
|
||||
* @param array $vars 模板变量
|
||||
* @param integer $code 状态码
|
||||
* @return \think\response\View
|
||||
*/
|
||||
function view($template = '', $vars = [], $code = 200)
|
||||
{
|
||||
return Response::create($template, 'view', $code)->vars($vars);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取\think\response\Json对象实例
|
||||
* @param mixed $data 返回的数据
|
||||
* @param integer $code 状态码
|
||||
* @param array $header 头部
|
||||
* @param array $options 参数
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
function json($data = [], $code = 200, $header = [], $options = [])
|
||||
{
|
||||
return Response::create($data, 'json', $code, $header, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取\think\response\Jsonp对象实例
|
||||
* @param mixed $data 返回的数据
|
||||
* @param integer $code 状态码
|
||||
* @param array $header 头部
|
||||
* @param array $options 参数
|
||||
* @return \think\response\Jsonp
|
||||
*/
|
||||
function jsonp($data = [], $code = 200, $header = [], $options = [])
|
||||
{
|
||||
return Response::create($data, 'jsonp', $code, $header, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取\think\response\Xml对象实例
|
||||
* @param mixed $data 返回的数据
|
||||
* @param integer $code 状态码
|
||||
* @param array $header 头部
|
||||
* @param array $options 参数
|
||||
* @return \think\response\Xml
|
||||
*/
|
||||
function xml($data = [], $code = 200, $header = [], $options = [])
|
||||
{
|
||||
return Response::create($data, 'xml', $code, $header, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取\think\response\Redirect对象实例
|
||||
* @param mixed $url 重定向地址 支持Url::build方法的地址
|
||||
* @param array|integer $params 额外参数
|
||||
* @param integer $code 状态码
|
||||
* @return \think\response\Redirect
|
||||
*/
|
||||
function redirect($url = [], $params = [], $code = 302)
|
||||
{
|
||||
if (is_integer($params)) {
|
||||
$code = $params;
|
||||
$params = [];
|
||||
}
|
||||
return Response::create($url, 'redirect', $code)->params($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 抛出HTTP异常
|
||||
* @param integer $code 状态码
|
||||
* @param string $message 错误信息
|
||||
* @param array $header 参数
|
||||
*/
|
||||
function abort($code, $message = null, $header = [])
|
||||
{
|
||||
throw new \think\exception\HttpException($code, $message, null, $header);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
// 核心中文语言包
|
||||
return [
|
||||
// 系统错误提示
|
||||
'Undefined variable' => '未定义变量',
|
||||
'Undefined index' => '未定义索引',
|
||||
'Parse error' => '语法解析错误',
|
||||
'Type error' => '类型错误',
|
||||
'Fatal error' => '致命错误',
|
||||
|
||||
// 框架核心错误提示
|
||||
'dispatch type not support' => '不支持的调度类型',
|
||||
'method param miss' => '方法参数错误',
|
||||
'method not exists' => '方法不存在',
|
||||
'module not exists' => '模块不存在',
|
||||
'class not exists' => '类不存在',
|
||||
'template not exists' => '模板文件不存在',
|
||||
'illegal controller name' => '非法的控制器名称',
|
||||
'illegal action name' => '非法的操作名称',
|
||||
'url suffix deny' => '禁止的URL后缀访问',
|
||||
'Route Not Found' => '当前访问路由未定义',
|
||||
'Underfined db type' => '未定义数据库类型',
|
||||
'variable type error' => '变量类型错误',
|
||||
'PSR-4 error' => 'PSR-4 规范错误',
|
||||
'not support total' => '简洁模式下不能获取数据总数',
|
||||
'not support last' => '简洁模式下不能获取最后一页',
|
||||
'error session handler' => '错误的SESSION处理器类',
|
||||
'not allow php tag' => '模板不允许使用PHP语法',
|
||||
'not support' => '不支持',
|
||||
'redisd master' => 'Redisd 主服务器错误',
|
||||
'redisd slave' => 'Redisd 从服务器错误',
|
||||
'must run at sae' => '必须在SAE运行',
|
||||
'memcache init error' => '未开通Memcache服务,请在SAE管理平台初始化Memcache服务',
|
||||
'KVDB init error' => '没有初始化KVDB,请在SAE管理平台初始化KVDB服务',
|
||||
'fields not exists' => '数据表字段不存在',
|
||||
'where express error' => '查询表达式错误',
|
||||
'no data to update' => '没有任何数据需要更新',
|
||||
'miss data to insert' => '缺少需要写入的数据',
|
||||
'miss complex primary data' => '缺少复合主键数据',
|
||||
'miss update condition' => '缺少更新条件',
|
||||
'model data Not Found' => '模型数据不存在',
|
||||
'table data not Found' => '表数据不存在',
|
||||
'delete without condition' => '没有条件不会执行删除操作',
|
||||
'miss relation data' => '缺少关联表数据',
|
||||
'tag attr must' => '模板标签属性必须',
|
||||
'tag error' => '模板标签错误',
|
||||
'cache write error' => '缓存写入失败',
|
||||
'sae mc write error' => 'SAE mc 写入错误',
|
||||
];
|
||||
@@ -0,0 +1,478 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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;
|
||||
|
||||
use think\Config;
|
||||
use think\Exception;
|
||||
use think\exception\HttpException;
|
||||
use think\exception\HttpResponseException;
|
||||
use think\Hook;
|
||||
use think\Lang;
|
||||
use think\Loader;
|
||||
use think\Log;
|
||||
use think\Request;
|
||||
use think\Response;
|
||||
use think\Route;
|
||||
|
||||
/**
|
||||
* App 应用管理
|
||||
* @author liu21st <liu21st@gmail.com>
|
||||
*/
|
||||
class App
|
||||
{
|
||||
/**
|
||||
* @var bool 是否初始化过
|
||||
*/
|
||||
protected static $init = false;
|
||||
|
||||
/**
|
||||
* @var string 当前模块路径
|
||||
*/
|
||||
public static $modulePath;
|
||||
|
||||
/**
|
||||
* @var bool 应用调试模式
|
||||
*/
|
||||
public static $debug = true;
|
||||
|
||||
/**
|
||||
* @var string 应用类库命名空间
|
||||
*/
|
||||
public static $namespace = 'app';
|
||||
|
||||
/**
|
||||
* @var bool 应用类库后缀
|
||||
*/
|
||||
public static $suffix = false;
|
||||
|
||||
/**
|
||||
* @var bool 应用路由检测
|
||||
*/
|
||||
protected static $routeCheck;
|
||||
|
||||
/**
|
||||
* @var bool 严格路由检测
|
||||
*/
|
||||
protected static $routeMust;
|
||||
|
||||
/**
|
||||
* 执行应用程序
|
||||
* @access public
|
||||
* @param Request $request Request对象
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function run(Request $request = null)
|
||||
{
|
||||
is_null($request) && $request = Request::instance();
|
||||
|
||||
$config = self::initCommon();
|
||||
|
||||
try {
|
||||
|
||||
// 开启多语言机制
|
||||
if ($config['lang_switch_on']) {
|
||||
// 获取当前语言
|
||||
$request->langset(Lang::detect());
|
||||
// 加载系统语言包
|
||||
Lang::load(THINK_PATH . 'lang' . DS . $request->langset() . EXT);
|
||||
if (!$config['app_multi_module']) {
|
||||
Lang::load(APP_PATH . 'lang' . DS . $request->langset() . EXT);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前请求的调度信息
|
||||
$dispatch = $request->dispatch();
|
||||
if (empty($dispatch)) {
|
||||
// 未指定调度类型 则进行URL路由检测
|
||||
$dispatch = self::routeCheck($request, $config);
|
||||
}
|
||||
// 记录路由信息
|
||||
self::$debug && Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info');
|
||||
// 监听app_begin
|
||||
Hook::listen('app_begin', $dispatch);
|
||||
|
||||
switch ($dispatch['type']) {
|
||||
case 'redirect':
|
||||
// 执行重定向跳转
|
||||
$data = Response::create($dispatch['url'], 'redirect')->code($dispatch['status']);
|
||||
break;
|
||||
case 'module':
|
||||
// 模块/控制器/操作
|
||||
$data = self::module($dispatch['module'], $config, isset($dispatch['convert']) ? $dispatch['convert'] : null );
|
||||
break;
|
||||
case 'controller':
|
||||
// 执行控制器操作
|
||||
$data = Loader::action($dispatch['controller'], $dispatch['params']);
|
||||
break;
|
||||
case 'method':
|
||||
// 执行回调方法
|
||||
$data = self::invokeMethod($dispatch['method'], $dispatch['params']);
|
||||
break;
|
||||
case 'function':
|
||||
// 执行闭包
|
||||
$data = self::invokeFunction($dispatch['function'], $dispatch['params']);
|
||||
break;
|
||||
case 'response':
|
||||
$data = $dispatch['response'];
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException('dispatch type not support');
|
||||
}
|
||||
} catch (HttpResponseException $exception) {
|
||||
$data = $exception->getResponse();
|
||||
}
|
||||
|
||||
// 监听app_end
|
||||
Hook::listen('app_end', $data);
|
||||
// 清空类的实例化
|
||||
Loader::clearInstance();
|
||||
|
||||
// 输出数据到客户端
|
||||
if ($data instanceof Response) {
|
||||
return $data;
|
||||
} elseif(!is_null($data)) {
|
||||
// 默认自动识别响应输出类型
|
||||
$isAjax = $request->isAjax();
|
||||
$type = $isAjax ? Config::get('default_ajax_return') : Config::get('default_return_type');
|
||||
return Response::create($data, $type);
|
||||
} else {
|
||||
return Response::create();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行函数或者闭包方法 支持参数调用
|
||||
* @access public
|
||||
* @param string|array|\Closure $function 函数或者闭包
|
||||
* @param array $vars 变量
|
||||
* @return mixed
|
||||
*/
|
||||
public static function invokeFunction($function, $vars = [])
|
||||
{
|
||||
$reflect = new \ReflectionFunction($function);
|
||||
$args = self::bindParams($reflect, $vars);
|
||||
// 记录执行信息
|
||||
self::$debug && Log::record('[ RUN ] ' . $reflect->getFileName() . '[ ' . var_export($vars, true) . ' ]', 'info');
|
||||
return $reflect->invokeArgs($args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用反射执行类的方法 支持参数绑定
|
||||
* @access public
|
||||
* @param string|array $method 方法
|
||||
* @param array $vars 变量
|
||||
* @return mixed
|
||||
*/
|
||||
public static function invokeMethod($method, $vars = [])
|
||||
{
|
||||
if (empty($vars)) {
|
||||
// 自动获取请求变量
|
||||
$vars = Request::instance()->param();
|
||||
}
|
||||
if (is_array($method)) {
|
||||
$class = is_object($method[0]) ? $method[0] : new $method[0];
|
||||
$reflect = new \ReflectionMethod($class, $method[1]);
|
||||
} else {
|
||||
// 静态方法
|
||||
$reflect = new \ReflectionMethod($method);
|
||||
}
|
||||
$args = self::bindParams($reflect, $vars);
|
||||
// 记录执行信息
|
||||
self::$debug && Log::record('[ RUN ] ' . $reflect->getFileName() . '[ ' . var_export($args, true) . ' ]', 'info');
|
||||
return $reflect->invokeArgs(isset($class) ? $class : null, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定参数
|
||||
* @access public
|
||||
* @param \ReflectionMethod|\ReflectionFunction $reflect 反射类
|
||||
* @param array $vars 变量
|
||||
* @return array
|
||||
*/
|
||||
private static function bindParams($reflect, $vars)
|
||||
{
|
||||
$args = [];
|
||||
// 判断数组类型 数字数组时按顺序绑定参数
|
||||
$type = key($vars) === 0 ? 1 : 0;
|
||||
if ($reflect->getNumberOfParameters() > 0) {
|
||||
$params = $reflect->getParameters();
|
||||
foreach ($params as $param) {
|
||||
$name = $param->getName();
|
||||
$class = $param->getClass();
|
||||
if ($class && 'think\Request' == $class->getName()) {
|
||||
$args[] = Request::instance();
|
||||
} elseif (1 == $type && !empty($vars)) {
|
||||
$args[] = array_shift($vars);
|
||||
} elseif (0 == $type && isset($vars[$name])) {
|
||||
$args[] = $vars[$name];
|
||||
} elseif ($param->isDefaultValueAvailable()) {
|
||||
$args[] = $param->getDefaultValue();
|
||||
} else {
|
||||
throw new \InvalidArgumentException('method param miss:' . $name);
|
||||
}
|
||||
}
|
||||
// 全局过滤
|
||||
array_walk_recursive($args, [Request::instance(),'filterExp']);
|
||||
}
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行模块
|
||||
* @access public
|
||||
* @param array $result 模块/控制器/操作
|
||||
* @param array $config 配置参数
|
||||
* @param bool $convert 是否自动转换控制器和操作名
|
||||
* @return mixed
|
||||
*/
|
||||
public static function module($result, $config, $convert = null)
|
||||
{
|
||||
if (is_string($result)) {
|
||||
$result = explode('/', $result);
|
||||
}
|
||||
if ($config['app_multi_module']) {
|
||||
// 多模块部署
|
||||
$module = strip_tags(strtolower($result[0] ?: $config['default_module']));
|
||||
$bind = Route::bind('module');
|
||||
$available = false;
|
||||
if ($bind) {
|
||||
// 绑定模块
|
||||
list($bindModule) = explode('/', $bind);
|
||||
if ($module == $bindModule) {
|
||||
$available = true;
|
||||
}
|
||||
} elseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module)) {
|
||||
$available = true;
|
||||
}
|
||||
|
||||
// 模块初始化
|
||||
if ($module && $available) {
|
||||
// 初始化模块
|
||||
$config = self::init($module);
|
||||
} else {
|
||||
throw new HttpException(404, 'module not exists:' . $module);
|
||||
}
|
||||
} else {
|
||||
// 单一模块部署
|
||||
$module = '';
|
||||
}
|
||||
// 当前模块路径
|
||||
App::$modulePath = APP_PATH . ($module ? $module . DS : '');
|
||||
|
||||
// 是否自动转换控制器和操作名
|
||||
$convert = is_bool($convert) ? $convert : $config['url_convert'];
|
||||
// 获取控制器名
|
||||
$controller = strip_tags($result[1] ?: $config['default_controller']);
|
||||
$controller = $convert ? strtolower($controller) : $controller;
|
||||
|
||||
// 获取操作名
|
||||
$actionName = strip_tags($result[2] ?: $config['default_action']);
|
||||
$actionName = $convert ? strtolower($actionName) : $actionName;
|
||||
|
||||
// 执行操作
|
||||
if (!preg_match('/^[A-Za-z](\/|\.|\w)*$/', $controller)) {
|
||||
// 安全检测
|
||||
throw new \InvalidArgumentException('illegal controller name:' . $controller);
|
||||
}
|
||||
|
||||
// 设置当前请求的模块、控制器、操作
|
||||
$request = Request::instance();
|
||||
$request->module($module)->controller($controller)->action($actionName);
|
||||
|
||||
// 监听module_init
|
||||
Hook::listen('module_init', $request);
|
||||
|
||||
try {
|
||||
$instance = Loader::controller($controller, $config['url_controller_layer'], $config['controller_suffix'], $config['empty_controller']);
|
||||
|
||||
// 获取当前操作名
|
||||
$action = $actionName . $config['action_suffix'];
|
||||
if (!preg_match('/^[A-Za-z](\w)*$/', $action)) {
|
||||
// 非法操作
|
||||
throw new \ReflectionException('illegal action name:' . $actionName);
|
||||
}
|
||||
|
||||
// 执行操作方法
|
||||
$call = [$instance, $action];
|
||||
Hook::listen('action_begin', $call);
|
||||
|
||||
$data = self::invokeMethod($call);
|
||||
} catch (\ReflectionException $e) {
|
||||
// 操作不存在
|
||||
if (method_exists($instance, '_empty')) {
|
||||
$method = new \ReflectionMethod($instance, '_empty');
|
||||
$data = $method->invokeArgs($instance, [$action, '']);
|
||||
self::$debug && Log::record('[ RUN ] ' . $method->getFileName(), 'info');
|
||||
} else {
|
||||
throw new HttpException(404, 'method not exists:' . (new \ReflectionClass($instance))->getName() . '->' . $action);
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化应用
|
||||
*/
|
||||
public static function initCommon()
|
||||
{
|
||||
if (empty(self::$init)) {
|
||||
// 初始化应用
|
||||
$config = self::init();
|
||||
self::$suffix = $config['class_suffix'];
|
||||
|
||||
// 应用调试模式
|
||||
self::$debug = Config::get('app_debug');
|
||||
if (!self::$debug) {
|
||||
ini_set('display_errors', 'Off');
|
||||
}
|
||||
|
||||
// 应用命名空间
|
||||
self::$namespace = $config['app_namespace'];
|
||||
Loader::addNamespace($config['app_namespace'], APP_PATH);
|
||||
if (!empty($config['root_namespace'])) {
|
||||
Loader::addNamespace($config['root_namespace']);
|
||||
}
|
||||
|
||||
// 加载额外文件
|
||||
if (!empty($config['extra_file_list'])) {
|
||||
foreach ($config['extra_file_list'] as $file) {
|
||||
$file = strpos($file, '.') ? $file : APP_PATH . $file . EXT;
|
||||
if (is_file($file)) {
|
||||
include_once $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置系统时区
|
||||
date_default_timezone_set($config['default_timezone']);
|
||||
|
||||
// 监听app_init
|
||||
Hook::listen('app_init');
|
||||
|
||||
self::$init = $config;
|
||||
}
|
||||
return self::$init;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 初始化应用或模块
|
||||
* @access public
|
||||
* @param string $module 模块名
|
||||
* @return array
|
||||
*/
|
||||
private static function init($module = '')
|
||||
{
|
||||
// 定位模块目录
|
||||
$module = $module ? $module . DS : '';
|
||||
|
||||
// 加载初始化文件
|
||||
if (is_file(APP_PATH . $module . 'init' . EXT)) {
|
||||
include APP_PATH . $module . 'init' . EXT;
|
||||
} else {
|
||||
$path = APP_PATH . $module;
|
||||
// 加载模块配置
|
||||
$config = Config::load(CONF_PATH . $module . 'config' . CONF_EXT);
|
||||
|
||||
// 加载应用状态配置
|
||||
if ($config['app_status']) {
|
||||
$config = Config::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT);
|
||||
}
|
||||
|
||||
// 读取扩展配置文件
|
||||
if ($config['extra_config_list']) {
|
||||
foreach ($config['extra_config_list'] as $name => $file) {
|
||||
$filename = CONF_PATH . $module . $file . CONF_EXT;
|
||||
Config::load($filename, is_string($name) ? $name : pathinfo($filename, PATHINFO_FILENAME));
|
||||
}
|
||||
}
|
||||
|
||||
// 加载别名文件
|
||||
if (is_file(CONF_PATH . $module . 'alias' . EXT)) {
|
||||
Loader::addMap(include CONF_PATH . $module . 'alias' . EXT);
|
||||
}
|
||||
|
||||
// 加载行为扩展文件
|
||||
if (is_file(CONF_PATH . $module . 'tags' . EXT)) {
|
||||
Hook::import(include CONF_PATH . $module . 'tags' . EXT);
|
||||
}
|
||||
|
||||
// 加载公共文件
|
||||
if (is_file($path . 'common' . EXT)) {
|
||||
include $path . 'common' . EXT;
|
||||
}
|
||||
|
||||
// 加载当前模块语言包
|
||||
if ($config['lang_switch_on'] && $module) {
|
||||
Lang::load($path . 'lang' . DS . Request::instance()->langset() . EXT);
|
||||
}
|
||||
}
|
||||
return Config::get();
|
||||
}
|
||||
|
||||
/**
|
||||
* URL路由检测(根据PATH_INFO)
|
||||
* @access public
|
||||
* @param \think\Request $request
|
||||
* @param array $config
|
||||
* @return array
|
||||
* @throws \think\Exception
|
||||
*/
|
||||
public static function routeCheck($request, array $config)
|
||||
{
|
||||
// 检测URL禁用后缀
|
||||
if ($config['url_deny_suffix'] && preg_match('/\.(' . $config['url_deny_suffix'] . ')$/i', $request->pathinfo())) {
|
||||
throw new Exception('url suffix deny:'.$request->ext());
|
||||
}
|
||||
|
||||
$path = $request->path();
|
||||
$depr = $config['pathinfo_depr'];
|
||||
$result = false;
|
||||
// 路由检测
|
||||
$check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on'];
|
||||
if ($check) {
|
||||
// 开启路由
|
||||
if (!empty($config['route'])) {
|
||||
// 导入路由配置
|
||||
Route::import($config['route']);
|
||||
}
|
||||
// 路由检测(根据路由定义返回不同的URL调度)
|
||||
$result = Route::check($request, $path, $depr, $config['url_domain_deploy']);
|
||||
$must = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must'];
|
||||
if ($must && false === $result) {
|
||||
// 路由无效
|
||||
throw new HttpException(404, 'Route Not Found');
|
||||
}
|
||||
}
|
||||
if (false === $result) {
|
||||
// 路由无效 解析模块/控制器/操作/参数... 支持控制器自动搜索
|
||||
$result = Route::parseUrl($path, $depr, $config['controller_auto_search'], $config['url_param_type']);
|
||||
}
|
||||
|
||||
// 注册调度机制
|
||||
return $request->dispatch($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置应用的路由检测机制
|
||||
* @access public
|
||||
* @param bool $route 是否需要检测路由
|
||||
* @param bool $must 是否强制检测路由
|
||||
* @return void
|
||||
*/
|
||||
public static function route($route, $must = false)
|
||||
{
|
||||
self::$routeCheck = $route;
|
||||
self::$routeMust = $must;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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;
|
||||
|
||||
class Build
|
||||
{
|
||||
/**
|
||||
* 根据传入的build资料创建目录和文件
|
||||
* @access protected
|
||||
* @param array $build build列表
|
||||
* @param string $namespace 应用类库命名空间
|
||||
* @param bool $suffix 类库后缀
|
||||
* @return void
|
||||
*/
|
||||
public static function run(array $build = [], $namespace = 'app', $suffix = false )
|
||||
{
|
||||
// 锁定
|
||||
$lockfile = APP_PATH . 'build.lock';
|
||||
if (is_writable($lockfile)) {
|
||||
return;
|
||||
} elseif (!touch($lockfile)) {
|
||||
throw new Exception('应用目录[' . APP_PATH . ']不可写,目录无法自动生成!<BR>请手动生成项目目录~', 10006);
|
||||
}
|
||||
foreach ($build as $module => $list) {
|
||||
if ('__dir__' == $module) {
|
||||
// 创建目录列表
|
||||
self::buildDir($list);
|
||||
} elseif ('__file__' == $module) {
|
||||
// 创建文件列表
|
||||
self::buildFile($list);
|
||||
} else {
|
||||
// 创建模块
|
||||
self::module($module, $list, $namespace, $suffix);
|
||||
}
|
||||
}
|
||||
// 解除锁定
|
||||
unlink($lockfile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建目录
|
||||
* @access protected
|
||||
* @param array $list 目录列表
|
||||
* @return void
|
||||
*/
|
||||
protected static function buildDir($list)
|
||||
{
|
||||
foreach ($list as $dir) {
|
||||
if (!is_dir(APP_PATH . $dir)) {
|
||||
// 创建目录
|
||||
mkdir(APP_PATH . $dir, 0777, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文件
|
||||
* @access protected
|
||||
* @param array $list 文件列表
|
||||
* @return void
|
||||
*/
|
||||
protected static function buildFile($list)
|
||||
{
|
||||
foreach ($list as $file) {
|
||||
if (!is_dir(APP_PATH . dirname($file))) {
|
||||
// 创建目录
|
||||
mkdir(APP_PATH . dirname($file), 0777, true);
|
||||
}
|
||||
if (!is_file(APP_PATH . $file)) {
|
||||
file_put_contents(APP_PATH . $file, 'php' == pathinfo($file, PATHINFO_EXTENSION) ? "<?php\n" : '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建模块
|
||||
* @access public
|
||||
* @param string $module 模块名
|
||||
* @param array $list build列表
|
||||
* @param string $namespace 应用类库命名空间
|
||||
* @param bool $suffix 类库后缀
|
||||
* @return void
|
||||
*/
|
||||
public static function module($module = '', $list = [], $namespace = 'app', $suffix = false)
|
||||
{
|
||||
$module = $module ? $module : '';
|
||||
if (!is_dir(APP_PATH . $module)) {
|
||||
// 创建模块目录
|
||||
mkdir(APP_PATH . $module);
|
||||
}
|
||||
if (basename(RUNTIME_PATH) != $module) {
|
||||
// 创建配置文件和公共文件
|
||||
self::buildCommon($module);
|
||||
// 创建模块的默认页面
|
||||
self::buildHello($module, $namespace, $suffix);
|
||||
}
|
||||
if (empty($list)) {
|
||||
// 创建默认的模块目录和文件
|
||||
$list = [
|
||||
'__file__' => ['config.php', 'common.php'],
|
||||
'__dir__' => ['controller', 'model', 'view'],
|
||||
];
|
||||
}
|
||||
// 创建子目录和文件
|
||||
foreach ($list as $path => $file) {
|
||||
$modulePath = APP_PATH . $module . DS;
|
||||
if ('__dir__' == $path) {
|
||||
// 生成子目录
|
||||
foreach ($file as $dir) {
|
||||
if (!is_dir($modulePath . $dir)) {
|
||||
// 创建目录
|
||||
mkdir($modulePath . $dir, 0777, true);
|
||||
}
|
||||
}
|
||||
} elseif ('__file__' == $path) {
|
||||
// 生成(空白)文件
|
||||
foreach ($file as $name) {
|
||||
if (!is_file($modulePath . $name)) {
|
||||
file_put_contents($modulePath . $name, 'php' == pathinfo($name, PATHINFO_EXTENSION) ? "<?php\n" : '');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 生成相关MVC文件
|
||||
foreach ($file as $val) {
|
||||
$val = trim($val);
|
||||
$filename = $modulePath . $path . DS . $val . ($suffix ? ucfirst($path) : '') . EXT;
|
||||
$space = $namespace . '\\' . ($module ? $module . '\\' : '') . $path;
|
||||
$class = $val . ($suffix ? ucfirst($path) : '');
|
||||
switch ($path) {
|
||||
case 'controller': // 控制器
|
||||
$content = "<?php\nnamespace {$space};\n\nclass {$class}\n{\n\n}";
|
||||
break;
|
||||
case 'model': // 模型
|
||||
$content = "<?php\nnamespace {$space};\n\nuse think\Model;\n\nclass {$class} extends Model\n{\n\n}";
|
||||
break;
|
||||
case 'view': // 视图
|
||||
$filename = $modulePath . $path . DS . $val . '.html';
|
||||
if (!is_dir(dirname($filename))) {
|
||||
// 创建目录
|
||||
mkdir(dirname($filename), 0777, true);
|
||||
}
|
||||
$content = '';
|
||||
break;
|
||||
default:
|
||||
// 其他文件
|
||||
$content = "<?php\nnamespace {$space};\n\nclass {$class}\n{\n\n}";
|
||||
}
|
||||
|
||||
if (!is_file($filename)) {
|
||||
file_put_contents($filename, $content);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建模块的欢迎页面
|
||||
* @access public
|
||||
* @param string $module 模块名
|
||||
* @param string $namespace 应用类库命名空间
|
||||
* @param bool $suffix 类库后缀
|
||||
* @return void
|
||||
*/
|
||||
protected static function buildHello($module, $namespace, $suffix = false)
|
||||
{
|
||||
$filename = APP_PATH . ($module ? $module . DS : '') . 'controller' . DS . 'Index' . ($suffix ? 'Controller' : '') . EXT;
|
||||
if (!is_file($filename)) {
|
||||
$content = file_get_contents(THINK_PATH . 'tpl' . DS . 'default_index.tpl');
|
||||
$content = str_replace(['{$app}', '{$module}', '{layer}', '{$suffix}'], [$namespace, $module ? $module . '\\' : '', 'controller', $suffix ? 'Controller' : ''], $content);
|
||||
if (!is_dir(dirname($filename))) {
|
||||
mkdir(dirname($filename), 0777, true);
|
||||
}
|
||||
file_put_contents($filename, $content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建模块的公共文件
|
||||
* @access public
|
||||
* @param string $module 模块名
|
||||
* @return void
|
||||
*/
|
||||
protected static function buildCommon($module)
|
||||
{
|
||||
$filename = CONF_PATH . ($module ? $module . DS : '') . 'config.php';
|
||||
if (!is_file($filename)) {
|
||||
file_put_contents($filename, "<?php\n//配置文件\nreturn [\n\n];");
|
||||
}
|
||||
$filename = APP_PATH . ($module ? $module . DS : '') . 'common.php';
|
||||
if (!is_file($filename)) {
|
||||
file_put_contents($filename, "<?php\n;");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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;
|
||||
|
||||
use think\App;
|
||||
|
||||
class Cache
|
||||
{
|
||||
protected static $instance = [];
|
||||
public static $readTimes = 0;
|
||||
public static $writeTimes = 0;
|
||||
|
||||
/**
|
||||
* 操作句柄
|
||||
* @var object
|
||||
* @access protected
|
||||
*/
|
||||
protected static $handler;
|
||||
|
||||
/**
|
||||
* 连接缓存
|
||||
* @access public
|
||||
* @param array $options 配置数组
|
||||
* @param bool|string $name 缓存连接标识 true 强制重新连接
|
||||
* @return object
|
||||
*/
|
||||
public static function connect(array $options = [], $name = false)
|
||||
{
|
||||
$type = !empty($options['type']) ? $options['type'] : 'File';
|
||||
if (false === $name) {
|
||||
$name = $type;
|
||||
}
|
||||
|
||||
if (true === $name || !isset(self::$instance[$name])) {
|
||||
$class = false !== strpos($type, '\\') ? $type : '\\think\\cache\\driver\\' . ucwords($type);
|
||||
|
||||
// 记录初始化信息
|
||||
App::$debug && Log::record('[ CACHE ] INIT ' . $type . ':' . var_export($options, true), 'info');
|
||||
if (true === $name) {
|
||||
return new $class($options);
|
||||
} else {
|
||||
self::$instance[$name] = new $class($options);
|
||||
}
|
||||
}
|
||||
self::$handler = self::$instance[$name];
|
||||
return self::$handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动初始化缓存
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
if (is_null(self::$handler)) {
|
||||
// 自动初始化缓存
|
||||
self::connect(Config::get('cache'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取缓存
|
||||
* @access public
|
||||
* @param string $name 缓存标识
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get($name)
|
||||
{
|
||||
self::init();
|
||||
self::$readTimes++;
|
||||
return self::$handler->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入缓存
|
||||
* @access public
|
||||
* @param string $name 缓存标识
|
||||
* @param mixed $value 存储数据
|
||||
* @param int|null $expire 有效时间 0为永久
|
||||
* @return boolean
|
||||
*/
|
||||
public static function set($name, $value, $expire = null)
|
||||
{
|
||||
self::init();
|
||||
self::$writeTimes++;
|
||||
return self::$handler->set($name, $value, $expire);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
* @access public
|
||||
* @param string $name 缓存标识
|
||||
* @return boolean
|
||||
*/
|
||||
public static function rm($name)
|
||||
{
|
||||
self::init();
|
||||
return self::$handler->rm($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public static function clear()
|
||||
{
|
||||
self::init();
|
||||
return self::$handler->clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: zhangyajun <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think;
|
||||
|
||||
use ArrayAccess;
|
||||
use ArrayIterator;
|
||||
use Countable;
|
||||
use IteratorAggregate;
|
||||
use JsonSerializable;
|
||||
|
||||
class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable
|
||||
{
|
||||
protected $items = [];
|
||||
|
||||
public function __construct($items = [])
|
||||
{
|
||||
$this->items = $this->convertToArray($items);
|
||||
}
|
||||
|
||||
public static function make($items = [])
|
||||
{
|
||||
return new static($items);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为空
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty()
|
||||
{
|
||||
return empty($this->items);
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
return array_map(function ($value) {
|
||||
return ($value instanceof Model || $value instanceof self) ? $value->toArray() : $value;
|
||||
}, $this->items);
|
||||
}
|
||||
|
||||
public function all()
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并数组
|
||||
*
|
||||
* @param mixed $items
|
||||
* @return static
|
||||
*/
|
||||
public function merge($items)
|
||||
{
|
||||
return new static(array_merge($this->items, $this->convertToArray($items)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较数组,返回差集
|
||||
*
|
||||
* @param mixed $items
|
||||
* @return static
|
||||
*/
|
||||
public function diff($items)
|
||||
{
|
||||
return new static(array_diff($this->items, $this->convertToArray($items)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 交换数组中的键和值
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function flip()
|
||||
{
|
||||
return new static(array_flip($this->items));
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较数组,返回交集
|
||||
*
|
||||
* @param mixed $items
|
||||
* @return static
|
||||
*/
|
||||
public function intersect($items)
|
||||
{
|
||||
return new static(array_intersect($this->items, $this->convertToArray($items)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回数组中所有的键名
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function keys()
|
||||
{
|
||||
return new static(array_keys($this->items));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数组的最后一个元素(出栈)
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function pop()
|
||||
{
|
||||
return array_pop($this->items);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 通过使用用户自定义函数,以字符串返回数组
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param mixed $initial
|
||||
* @return mixed
|
||||
*/
|
||||
public function reduce(callable $callback, $initial = null)
|
||||
{
|
||||
return array_reduce($this->items, $callback, $initial);
|
||||
}
|
||||
|
||||
/**
|
||||
* 以相反的顺序返回数组。
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function reverse()
|
||||
{
|
||||
return new static(array_reverse($this->items));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数组中首个元素,并返回被删除元素的值
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function shift()
|
||||
{
|
||||
return array_shift($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* 把一个数组分割为新的数组块.
|
||||
*
|
||||
* @param int $size
|
||||
* @param bool $preserveKeys
|
||||
* @return static
|
||||
*/
|
||||
public function chunk($size, $preserveKeys = false)
|
||||
{
|
||||
$chunks = [];
|
||||
|
||||
foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) {
|
||||
$chunks[] = new static($chunk);
|
||||
}
|
||||
|
||||
return new static($chunks);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在数组开头插入一个元素
|
||||
* @param mixed $value
|
||||
* @param null $key
|
||||
* @return int
|
||||
*/
|
||||
public function unshift($value, $key = null)
|
||||
{
|
||||
if (is_null($key)) {
|
||||
array_unshift($this->items, $value);
|
||||
} else {
|
||||
$this->items = [$key => $value] + $this->items;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 给每个元素执行个回调
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function each(callable $callback)
|
||||
{
|
||||
foreach ($this->items as $key => $item) {
|
||||
if ($callback($item, $key) === false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用回调函数过滤数组中的元素
|
||||
* @param callable|null $callback
|
||||
* @return static
|
||||
*/
|
||||
public function filter(callable $callback = null)
|
||||
{
|
||||
if ($callback) {
|
||||
return new static(array_filter($this->items, $callback));
|
||||
}
|
||||
|
||||
return new static(array_filter($this->items));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回数组中指定的一列
|
||||
* @param $column_key
|
||||
* @param null $index_key
|
||||
* @return array
|
||||
*/
|
||||
public function column($column_key, $index_key = null)
|
||||
{
|
||||
if (function_exists('array_column')) {
|
||||
return array_column($this->items, $column_key, $index_key);
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($this->items as $row) {
|
||||
$key = $value = null;
|
||||
$keySet = $valueSet = false;
|
||||
if ($index_key !== null && array_key_exists($index_key, $row)) {
|
||||
$keySet = true;
|
||||
$key = (string)$row[$index_key];
|
||||
}
|
||||
if ($column_key === null) {
|
||||
$valueSet = true;
|
||||
$value = $row;
|
||||
} elseif (is_array($row) && array_key_exists($column_key, $row)) {
|
||||
$valueSet = true;
|
||||
$value = $row[$column_key];
|
||||
}
|
||||
if ($valueSet) {
|
||||
if ($keySet) {
|
||||
$result[$key] = $value;
|
||||
} else {
|
||||
$result[] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 对数组排序
|
||||
*
|
||||
* @param callable|null $callback
|
||||
* @return static
|
||||
*/
|
||||
public function sort(callable $callback = null)
|
||||
{
|
||||
$items = $this->items;
|
||||
|
||||
$callback ? uasort($items, $callback) : uasort($items, function ($a, $b) {
|
||||
|
||||
if ($a == $b) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ($a < $b) ? -1 : 1;
|
||||
});
|
||||
|
||||
return new static($items);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将数组打乱
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function shuffle()
|
||||
{
|
||||
$items = $this->items;
|
||||
|
||||
shuffle($items);
|
||||
|
||||
return new static($items);
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取数组
|
||||
*
|
||||
* @param int $offset
|
||||
* @param int $length
|
||||
* @param bool $preserveKeys
|
||||
* @return static
|
||||
*/
|
||||
public function slice($offset, $length = null, $preserveKeys = false)
|
||||
{
|
||||
return new static(array_slice($this->items, $offset, $length, $preserveKeys));
|
||||
}
|
||||
|
||||
// ArrayAccess
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return array_key_exists($offset, $this->items);
|
||||
}
|
||||
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->items[$offset];
|
||||
}
|
||||
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
if (is_null($offset)) {
|
||||
$this->items[] = $value;
|
||||
} else {
|
||||
$this->items[$offset] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
unset($this->items[$offset]);
|
||||
}
|
||||
|
||||
//Countable
|
||||
public function count()
|
||||
{
|
||||
return count($this->items);
|
||||
}
|
||||
|
||||
//IteratorAggregate
|
||||
public function getIterator()
|
||||
{
|
||||
return new ArrayIterator($this->items);
|
||||
}
|
||||
|
||||
//JsonSerializable
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换当前数据集为JSON字符串
|
||||
* @access public
|
||||
* @param integer $options json参数
|
||||
* @return string
|
||||
*/
|
||||
public function toJson($options = JSON_UNESCAPED_UNICODE)
|
||||
{
|
||||
return json_encode($this->toArray(), $options);
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->toJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换成数组
|
||||
*
|
||||
* @param mixed $items
|
||||
* @return array
|
||||
*/
|
||||
protected function convertToArray($items)
|
||||
{
|
||||
if ($items instanceof self) {
|
||||
return $items->all();
|
||||
}
|
||||
return (array)$items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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;
|
||||
|
||||
class Config
|
||||
{
|
||||
// 配置参数
|
||||
private static $config = [];
|
||||
// 参数作用域
|
||||
private static $range = '_sys_';
|
||||
|
||||
// 设定配置参数的作用域
|
||||
public static function range($range)
|
||||
{
|
||||
self::$range = $range;
|
||||
if (!isset(self::$config[$range])) {
|
||||
self::$config[$range] = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析配置文件或内容
|
||||
* @param string $config 配置文件路径或内容
|
||||
* @param string $type 配置解析类型
|
||||
* @param string $name 配置名(如设置即表示二级配置)
|
||||
* @param string $range 作用域
|
||||
*/
|
||||
public static function parse($config, $type = '', $name = '', $range = '')
|
||||
{
|
||||
$range = $range ?: self::$range;
|
||||
if (empty($type)) {
|
||||
$type = pathinfo($config, PATHINFO_EXTENSION);
|
||||
}
|
||||
$class = false !== strpos($type, '\\') ? $type : '\\think\\config\\driver\\' . ucwords($type);
|
||||
self::set((new $class())->parse($config), $name, $range);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载配置文件(PHP格式)
|
||||
* @param string $file 配置文件名
|
||||
* @param string $name 配置名(如设置即表示二级配置)
|
||||
* @param string $range 作用域
|
||||
* @return mixed
|
||||
*/
|
||||
public static function load($file, $name = '', $range = '')
|
||||
{
|
||||
$range = $range ?: self::$range;
|
||||
if (!isset(self::$config[$range])) {
|
||||
self::$config[$range] = [];
|
||||
}
|
||||
if (is_file($file)) {
|
||||
$type = pathinfo($file, PATHINFO_EXTENSION);
|
||||
if ('php' != $type) {
|
||||
return self::parse($file, $type, $name, $range);
|
||||
} else {
|
||||
return self::set(include $file, $name, $range);
|
||||
}
|
||||
} else {
|
||||
return self::$config[$range];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测配置是否存在
|
||||
* @param string $name 配置参数名(支持二级配置 .号分割)
|
||||
* @param string $range 作用域
|
||||
* @return bool
|
||||
*/
|
||||
public static function has($name, $range = '')
|
||||
{
|
||||
$range = $range ?: self::$range;
|
||||
|
||||
if (!strpos($name, '.')) {
|
||||
// 判断环境变量
|
||||
$result = getenv(ENV_PREFIX . $name);
|
||||
if (false !== $result) {
|
||||
return $result;
|
||||
}
|
||||
return isset(self::$config[$range][strtolower($name)]);
|
||||
} else {
|
||||
// 二维数组设置和获取支持
|
||||
$name = explode('.', $name);
|
||||
$result = getenv(ENV_PREFIX . $name[0] . '_' . $name[1]);
|
||||
// 判断环境变量
|
||||
if (false !== $result) {
|
||||
return $result;
|
||||
}
|
||||
return isset(self::$config[$range][strtolower($name[0])][$name[1]]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置参数 为空则获取所有配置
|
||||
* @param string $name 配置参数名(支持二级配置 .号分割)
|
||||
* @param string $range 作用域
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get($name = null, $range = '')
|
||||
{
|
||||
$range = $range ?: self::$range;
|
||||
// 无参数时获取所有
|
||||
if (empty($name) && isset(self::$config[$range])) {
|
||||
return self::$config[$range];
|
||||
}
|
||||
|
||||
if (!strpos($name, '.')) {
|
||||
$result = getenv(ENV_PREFIX . $name);
|
||||
if (false !== $result) {
|
||||
return $result;
|
||||
}
|
||||
$name = strtolower($name);
|
||||
return isset(self::$config[$range][$name]) ? self::$config[$range][$name] : null;
|
||||
} else {
|
||||
// 二维数组设置和获取支持
|
||||
$name = explode('.', $name);
|
||||
$result = getenv(ENV_PREFIX . $name[0] . '_' . $name[1]);
|
||||
// 判断环境变量
|
||||
if (false !== $result) {
|
||||
return $result;
|
||||
}
|
||||
$name[0] = strtolower($name[0]);
|
||||
return isset(self::$config[$range][$name[0]][$name[1]]) ? self::$config[$range][$name[0]][$name[1]] : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置配置参数 name为数组则为批量设置
|
||||
* @param string|array $name 配置参数名(支持二级配置 .号分割)
|
||||
* @param mixed $value 配置值
|
||||
* @param string $range 作用域
|
||||
* @return mixed
|
||||
*/
|
||||
public static function set($name, $value = null, $range = '')
|
||||
{
|
||||
$range = $range ?: self::$range;
|
||||
if (!isset(self::$config[$range])) {
|
||||
self::$config[$range] = [];
|
||||
}
|
||||
if (is_string($name)) {
|
||||
if (!strpos($name, '.')) {
|
||||
self::$config[$range][strtolower($name)] = $value;
|
||||
} else {
|
||||
// 二维数组设置和获取支持
|
||||
$name = explode('.', $name);
|
||||
self::$config[$range][strtolower($name[0])][$name[1]] = $value;
|
||||
}
|
||||
return;
|
||||
} elseif (is_array($name)) {
|
||||
// 批量设置
|
||||
if (!empty($value)) {
|
||||
self::$config[$range][$value] = isset(self::$config[$range][$value]) ?
|
||||
array_merge(self::$config[$range][$value], $name) :
|
||||
self::$config[$range][$value] = $name;
|
||||
return self::$config[$range][$value];
|
||||
} else {
|
||||
return self::$config[$range] = array_merge(self::$config[$range], array_change_key_case($name));
|
||||
}
|
||||
} else {
|
||||
// 为空直接返回 已有配置
|
||||
return self::$config[$range];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置配置参数
|
||||
*/
|
||||
public static function reset($range = '')
|
||||
{
|
||||
$range = $range ?: self::$range;
|
||||
if (true === $range) {
|
||||
self::$config = [];
|
||||
} else {
|
||||
self::$config[$range] = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,987 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | TopThink [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2015 http://www.topthink.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: zhangyajun <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think;
|
||||
|
||||
use think\console\command\Command;
|
||||
use think\console\command\Help as HelpCommand;
|
||||
use think\console\helper\Debug as DebugFormatterHelper;
|
||||
use think\console\helper\Formatter as FormatterHelper;
|
||||
use think\console\helper\Process as ProcessHelper;
|
||||
use think\console\helper\Question as QuestionHelper;
|
||||
use think\console\helper\Set as HelperSet;
|
||||
use think\console\Input as ConsoleInput;
|
||||
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\Nothing;
|
||||
use think\console\output\Stream;
|
||||
|
||||
class Console
|
||||
{
|
||||
|
||||
private $name;
|
||||
private $version;
|
||||
|
||||
/** @var Command[] */
|
||||
private $commands = [];
|
||||
|
||||
private $wantHelps = false;
|
||||
|
||||
/** @var Command */
|
||||
private $runningCommand;
|
||||
|
||||
private $catchExceptions = true;
|
||||
private $autoExit = true;
|
||||
private $definition;
|
||||
private $helperSet;
|
||||
private $terminalDimensions;
|
||||
private $defaultCommand;
|
||||
|
||||
private static $defaultCommands = [
|
||||
"think\\console\\command\\Help",
|
||||
"think\\console\\command\\Lists",
|
||||
"think\\console\\command\\Build",
|
||||
"think\\console\\command\\make\\Controller",
|
||||
"think\\console\\command\\make\\Model",
|
||||
];
|
||||
|
||||
public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->version = $version;
|
||||
|
||||
$this->defaultCommand = 'list';
|
||||
$this->helperSet = $this->getDefaultHelperSet();
|
||||
$this->definition = $this->getDefaultInputDefinition();
|
||||
|
||||
foreach ($this->getDefaultCommands() as $command) {
|
||||
$this->add($command);
|
||||
}
|
||||
}
|
||||
|
||||
public static function init($run = true)
|
||||
{
|
||||
static $console;
|
||||
if (!$console) {
|
||||
// 实例化console
|
||||
$console = new self('Think Console', '0.1');
|
||||
// 读取指令集
|
||||
if (is_file(CONF_PATH . 'command' . EXT)) {
|
||||
$commands = include CONF_PATH . 'command' . EXT;
|
||||
if (is_array($commands)) {
|
||||
foreach ($commands as $command) {
|
||||
if (class_exists($command) && is_subclass_of($command, "\\think\\console\\command\\Command")) {
|
||||
// 注册指令
|
||||
$console->add(new $command());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($run) {
|
||||
// 运行
|
||||
$console->run();
|
||||
} else {
|
||||
return $console;
|
||||
}
|
||||
}
|
||||
|
||||
public static function call($command, array $parameters = [])
|
||||
{
|
||||
$console = self::init(false);
|
||||
|
||||
array_unshift($parameters, $command);
|
||||
|
||||
$input = new ConsoleInput($parameters);
|
||||
|
||||
$console->find($command)->run($input, new Nothing());
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行当前的指令
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
* @api
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$input = new ConsoleInput();
|
||||
$output = new Output();
|
||||
|
||||
$this->configureIO($input, $output);
|
||||
|
||||
try {
|
||||
$exitCode = $this->doRun($input, $output);
|
||||
} catch (\Exception $e) {
|
||||
if (!$this->catchExceptions) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->renderException($e, $output->getErrorOutput());
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行指令
|
||||
* @param ConsoleInput $input
|
||||
* @param Output $output
|
||||
* @return int
|
||||
*/
|
||||
public function doRun(ConsoleInput $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 ConsoleInput(['help']);
|
||||
} else {
|
||||
$this->wantHelps = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$name) {
|
||||
$name = $this->defaultCommand;
|
||||
$input = new ConsoleInput([$this->defaultCommand]);
|
||||
}
|
||||
|
||||
$command = $this->find($name);
|
||||
|
||||
$this->runningCommand = $command;
|
||||
$exitCode = $this->doRunCommand($command, $input, $output);
|
||||
$this->runningCommand = null;
|
||||
|
||||
return $exitCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置助手集
|
||||
* @param HelperSet $helperSet
|
||||
*/
|
||||
public function setHelperSet(HelperSet $helperSet)
|
||||
{
|
||||
$this->helperSet = $helperSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取助手集
|
||||
* @return HelperSet
|
||||
*/
|
||||
public function getHelperSet()
|
||||
{
|
||||
return $this->helperSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输入参数定义
|
||||
* @param InputDefinition $definition
|
||||
*/
|
||||
public function setDefinition(InputDefinition $definition)
|
||||
{
|
||||
$this->definition = $definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输入参数定义
|
||||
* @return InputDefinition The InputDefinition instance
|
||||
*/
|
||||
public function getDefinition()
|
||||
{
|
||||
return $this->definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the help message.
|
||||
* @return string A help message.
|
||||
*/
|
||||
public function getHelp()
|
||||
{
|
||||
return $this->getLongVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否捕获异常
|
||||
* @param bool $boolean
|
||||
* @api
|
||||
*/
|
||||
public function setCatchExceptions($boolean)
|
||||
{
|
||||
$this->catchExceptions = (bool)$boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否自动退出
|
||||
* @param bool $boolean
|
||||
* @api
|
||||
*/
|
||||
public function setAutoExit($boolean)
|
||||
{
|
||||
$this->autoExit = (bool)$boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取名称
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置名称
|
||||
* @param string $name
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取版本
|
||||
* @return string
|
||||
* @api
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置版本
|
||||
* @param string $version
|
||||
*/
|
||||
public function setVersion($version)
|
||||
{
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取完整的版本号
|
||||
* @return string
|
||||
*/
|
||||
public function getLongVersion()
|
||||
{
|
||||
if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) {
|
||||
return sprintf('<info>%s</info> version <comment>%s</comment>', $this->getName(), $this->getVersion());
|
||||
}
|
||||
|
||||
return '<info>Console Tool</info>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册一个指令
|
||||
* @param string $name
|
||||
* @return Command
|
||||
*/
|
||||
public function register($name)
|
||||
{
|
||||
return $this->add(new Command($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加指令
|
||||
* @param Command[] $commands
|
||||
*/
|
||||
public function addCommands(array $commands)
|
||||
{
|
||||
foreach ($commands as $command) {
|
||||
$this->add($command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个指令
|
||||
* @param Command $command
|
||||
* @return Command
|
||||
*/
|
||||
public function add(Command $command)
|
||||
{
|
||||
$command->setConsole($this);
|
||||
|
||||
if (!$command->isEnabled()) {
|
||||
$command->setConsole(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (null === $command->getDefinition()) {
|
||||
throw new \LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
|
||||
}
|
||||
|
||||
$this->commands[$command->getName()] = $command;
|
||||
|
||||
foreach ($command->getAliases() as $alias) {
|
||||
$this->commands[$alias] = $command;
|
||||
}
|
||||
|
||||
return $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指令
|
||||
* @param string $name 指令名称
|
||||
* @return Command
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
if (!isset($this->commands[$name])) {
|
||||
throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
|
||||
}
|
||||
|
||||
$command = $this->commands[$name];
|
||||
|
||||
if ($this->wantHelps) {
|
||||
$this->wantHelps = false;
|
||||
|
||||
/** @var HelpCommand $helpCommand */
|
||||
$helpCommand = $this->get('help');
|
||||
$helpCommand->setCommand($command);
|
||||
|
||||
return $helpCommand;
|
||||
}
|
||||
|
||||
return $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* 某个指令是否存在
|
||||
* @param string $name 指令民初
|
||||
* @return bool
|
||||
*/
|
||||
public function has($name)
|
||||
{
|
||||
return isset($this->commands[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有的命名空间
|
||||
* @return array
|
||||
*/
|
||||
public function getNamespaces()
|
||||
{
|
||||
$namespaces = [];
|
||||
foreach ($this->commands as $command) {
|
||||
$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)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找注册命名空间中的名称或缩写。
|
||||
* @param string $namespace
|
||||
* @return string
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function findNamespace($namespace)
|
||||
{
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找指令
|
||||
* @param string $name 名称或者别名
|
||||
* @return Command
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function find($name)
|
||||
{
|
||||
$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);
|
||||
}
|
||||
|
||||
if (count($commands) > 1) {
|
||||
$commandList = $this->commands;
|
||||
$commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) {
|
||||
$commandName = $commandList[$nameOrAlias]->getName();
|
||||
|
||||
return $commandName === $nameOrAlias || !in_array($commandName, $commands);
|
||||
});
|
||||
}
|
||||
|
||||
$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->get($exact ? $name : reset($commands));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有的指令
|
||||
* @param string $namespace 命名空间
|
||||
* @return Command[]
|
||||
* @api
|
||||
*/
|
||||
public function all($namespace = null)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可能的指令名
|
||||
* @param array $names
|
||||
* @return array
|
||||
*/
|
||||
public static function getAbbreviations($names)
|
||||
{
|
||||
$abbrevs = [];
|
||||
foreach ($names as $name) {
|
||||
for ($len = strlen($name); $len > 0; --$len) {
|
||||
$abbrev = substr($name, 0, $len);
|
||||
$abbrevs[$abbrev][] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
return $abbrevs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 呈现捕获的异常
|
||||
* @param \Exception $e
|
||||
* @param Stream $output
|
||||
*/
|
||||
public function renderException(\Exception $e, Stream $output)
|
||||
{
|
||||
do {
|
||||
$title = sprintf(' [%s] ', get_class($e));
|
||||
|
||||
$len = $this->stringWidth($title);
|
||||
|
||||
$width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX;
|
||||
|
||||
if (defined('HHVM_VERSION') && $width > 1 << 31) {
|
||||
$width = 1 << 31;
|
||||
}
|
||||
$formatter = $output->getFormatter();
|
||||
$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/', '', $formatter->format($line))) + 4;
|
||||
$lines[] = [$line, $lineLength];
|
||||
|
||||
$len = max($lineLength, $len);
|
||||
}
|
||||
}
|
||||
|
||||
$messages = ['', ''];
|
||||
$messages[] = $emptyLine = $formatter->format(sprintf('<error>%s</error>', str_repeat(' ', $len)));
|
||||
$messages[] = $formatter->format(sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title)))));
|
||||
foreach ($lines as $line) {
|
||||
$messages[] = $formatter->format(sprintf('<error> %s %s</error>', $line[0], str_repeat(' ', $len - $line[1])));
|
||||
}
|
||||
$messages[] = $emptyLine;
|
||||
$messages[] = '';
|
||||
$messages[] = '';
|
||||
|
||||
$output->writeln($messages, Output::OUTPUT_RAW);
|
||||
|
||||
if (Output::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
|
||||
$output->writeln('<comment>Exception trace:</comment>');
|
||||
|
||||
// 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';
|
||||
|
||||
$output->writeln(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line));
|
||||
}
|
||||
|
||||
$output->writeln('');
|
||||
$output->writeln('');
|
||||
}
|
||||
} while ($e = $e->getPrevious());
|
||||
|
||||
if (null !== $this->runningCommand) {
|
||||
$output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())));
|
||||
$output->writeln('');
|
||||
$output->writeln('');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取终端宽度
|
||||
* @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()
|
||||
{
|
||||
if ($this->terminalDimensions) {
|
||||
return $this->terminalDimensions;
|
||||
}
|
||||
|
||||
if ('\\' === DS) {
|
||||
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->getConsoleMode(), $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];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置终端尺寸
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @return Console
|
||||
*/
|
||||
public function setTerminalDimensions($width, $height)
|
||||
{
|
||||
$this->terminalDimensions = [$width, $height];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置基于用户的参数和选项的输入和输出实例。
|
||||
* @param ConsoleInput $input 输入实例
|
||||
* @param Output $output 输出实例
|
||||
*/
|
||||
protected function configureIO(ConsoleInput $input, Output $output)
|
||||
{
|
||||
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);
|
||||
} elseif (function_exists('posix_isatty') && $this->getHelperSet()->has('question')) {
|
||||
$inputStream = $this->getHelperSet()->get('question')->getInputStream();
|
||||
if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) {
|
||||
$input->setInteractive(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (true === $input->hasParameterOption(['--quiet', '-q'])) {
|
||||
$output->setVerbosity(Output::VERBOSITY_QUIET);
|
||||
} else {
|
||||
if ($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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行指令
|
||||
* @param Command $command 指令实例
|
||||
* @param ConsoleInput $input 输入实例
|
||||
* @param Output $output 输出实例
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function doRunCommand(Command $command, ConsoleInput $input, Output $output)
|
||||
{
|
||||
return $command->run($input, $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指令的基础名称
|
||||
* @param ConsoleInput $input
|
||||
* @return string
|
||||
*/
|
||||
protected function getCommandName(ConsoleInput $input)
|
||||
{
|
||||
return $input->getFirstArgument();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认输入定义
|
||||
* @return InputDefinition
|
||||
*/
|
||||
protected function getDefaultInputDefinition()
|
||||
{
|
||||
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'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认命令
|
||||
* @return Command[] An array of default Command instances
|
||||
*/
|
||||
protected function getDefaultCommands()
|
||||
{
|
||||
$defaultCommands = [];
|
||||
|
||||
foreach (self::$defaultCommands as $classname) {
|
||||
if (class_exists($classname) && is_subclass_of($classname, "think\\console\\command\\Command")) {
|
||||
$defaultCommands[] = new $classname();
|
||||
}
|
||||
}
|
||||
|
||||
return $defaultCommands;
|
||||
}
|
||||
|
||||
public static function addDefaultCommands(array $classnames)
|
||||
{
|
||||
self::$defaultCommands = array_merge(self::$defaultCommands, $classnames);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认助手
|
||||
* @return HelperSet
|
||||
*/
|
||||
protected function getDefaultHelperSet()
|
||||
{
|
||||
return new HelperSet([
|
||||
new FormatterHelper(),
|
||||
new DebugFormatterHelper(),
|
||||
new ProcessHelper(),
|
||||
new QuestionHelper(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取stty列数
|
||||
* @return string
|
||||
*/
|
||||
private function getSttyColumns()
|
||||
{
|
||||
if (!function_exists('proc_open')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$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 null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取终端模式
|
||||
* @return string <width>x<height> 或 null
|
||||
*/
|
||||
private function getConsoleMode()
|
||||
{
|
||||
if (!function_exists('proc_open')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$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 null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可能的建议
|
||||
* @param array $abbrevs
|
||||
* @return string
|
||||
*/
|
||||
private function getAbbreviationSuggestions($abbrevs)
|
||||
{
|
||||
return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回命名空间部分
|
||||
* @param string $name 指令
|
||||
* @param string $limit 部分的命名空间的最大数量
|
||||
* @return string
|
||||
*/
|
||||
public function extractNamespace($name, $limit = null)
|
||||
{
|
||||
$parts = explode(':', $name);
|
||||
array_pop($parts);
|
||||
|
||||
return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找可替代的建议
|
||||
* @param string $name
|
||||
* @param array|\Traversable $collection
|
||||
* @return array
|
||||
*/
|
||||
private function findAlternatives($name, $collection)
|
||||
{
|
||||
$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 && false !== strpos($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 || false !== strpos($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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认的指令
|
||||
* @param string $commandName The Command name
|
||||
*/
|
||||
public function setDefaultCommand($commandName)
|
||||
{
|
||||
$this->defaultCommand = $commandName;
|
||||
}
|
||||
|
||||
private function stringWidth($string)
|
||||
{
|
||||
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, $width)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回所有的命名空间
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
private function extractAllNamespaces($name)
|
||||
{
|
||||
$parts = explode(':', $name, -1);
|
||||
$namespaces = [];
|
||||
|
||||
foreach ($parts as $part) {
|
||||
if (count($namespaces)) {
|
||||
$namespaces[] = end($namespaces) . ':' . $part;
|
||||
} else {
|
||||
$namespaces[] = $part;
|
||||
}
|
||||
}
|
||||
|
||||
return $namespaces;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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;
|
||||
|
||||
\think\Loader::import('controller/Jump', TRAIT_PATH, EXT);
|
||||
|
||||
use think\Exception;
|
||||
use think\exception\ValidateException;
|
||||
|
||||
class Controller
|
||||
{
|
||||
use \traits\controller\Jump;
|
||||
|
||||
// 视图类实例
|
||||
protected $view;
|
||||
// Request实例
|
||||
protected $request;
|
||||
// 验证失败是否抛出异常
|
||||
protected $failException = false;
|
||||
// 是否批量验证
|
||||
protected $batchValidate = false;
|
||||
|
||||
/**
|
||||
* 前置操作方法列表
|
||||
* @var array $beforeActionList
|
||||
* @access protected
|
||||
*/
|
||||
protected $beforeActionList = [];
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @param Request $request Request对象
|
||||
* @access public
|
||||
*/
|
||||
public function __construct(Request $request = null)
|
||||
{
|
||||
if (is_null($request)) {
|
||||
$request = Request::instance();
|
||||
}
|
||||
$this->view = View::instance(Config::get('template'), Config::get('view_replace_str'));
|
||||
$this->request = $request;
|
||||
|
||||
// 控制器初始化
|
||||
if (method_exists($this, '_initialize')) {
|
||||
$this->_initialize();
|
||||
}
|
||||
|
||||
// 前置操作方法
|
||||
if ($this->beforeActionList) {
|
||||
foreach ($this->beforeActionList as $method => $options) {
|
||||
is_numeric($method) ?
|
||||
$this->beforeAction($options) :
|
||||
$this->beforeAction($method, $options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 前置操作
|
||||
* @access protected
|
||||
* @param string $method 前置操作方法名
|
||||
* @param array $options 调用参数 ['only'=>[...]] 或者['except'=>[...]]
|
||||
*/
|
||||
protected function beforeAction($method, $options = [])
|
||||
{
|
||||
if (isset($options['only'])) {
|
||||
if (is_string($options['only'])) {
|
||||
$options['only'] = explode(',', $options['only']);
|
||||
}
|
||||
if (!in_array($this->request->action(), $options['only'])) {
|
||||
return;
|
||||
}
|
||||
} elseif (isset($options['except'])) {
|
||||
if (is_string($options['except'])) {
|
||||
$options['except'] = explode(',', $options['except']);
|
||||
}
|
||||
if (in_array($this->request->action(), $options['except'])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (method_exists($this, $method)) {
|
||||
call_user_func([$this, $method]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载模板输出
|
||||
* @access protected
|
||||
* @param string $template 模板文件名
|
||||
* @param array $vars 模板输出变量
|
||||
* @param array $replace 模板替换
|
||||
* @param array $config 模板参数
|
||||
* @return mixed
|
||||
*/
|
||||
protected function fetch($template = '', $vars = [], $replace = [], $config = [])
|
||||
{
|
||||
return $this->view->fetch($template, $vars, $replace, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染内容输出
|
||||
* @access protected
|
||||
* @param string $content 模板内容
|
||||
* @param array $vars 模板输出变量
|
||||
* @param array $replace 替换内容
|
||||
* @param array $config 模板参数
|
||||
* @return mixed
|
||||
*/
|
||||
protected function display($content = '', $vars = [], $replace = [], $config = [])
|
||||
{
|
||||
return $this->view->display($content, $vars, $replace, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 模板变量赋值
|
||||
* @access protected
|
||||
* @param mixed $name 要显示的模板变量
|
||||
* @param mixed $value 变量的值
|
||||
* @return void
|
||||
*/
|
||||
protected function assign($name, $value = '')
|
||||
{
|
||||
$this->view->assign($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化模板引擎
|
||||
* @access protected
|
||||
* @param array|string $engine 引擎参数
|
||||
* @return void
|
||||
*/
|
||||
protected function engine($engine)
|
||||
{
|
||||
$this->view->engine($engine);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置验证失败后是否抛出异常
|
||||
* @access protected
|
||||
* @param bool $fail 是否抛出异常
|
||||
* @return $this
|
||||
*/
|
||||
protected function validateFailException($fail = true)
|
||||
{
|
||||
$this->failException = $fail;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证数据
|
||||
* @access protected
|
||||
* @param array $data 数据
|
||||
* @param string|array $validate 验证器名或者验证规则数组
|
||||
* @param array $message 提示信息
|
||||
* @param bool $batch 是否批量验证
|
||||
* @param mixed $callback 回调方法(闭包)
|
||||
* @return array|string|true
|
||||
* @throws ValidateException
|
||||
*/
|
||||
protected function validate($data, $validate, $message = [], $batch = false, $callback = null)
|
||||
{
|
||||
if (is_array($validate)) {
|
||||
$v = Loader::validate();
|
||||
$v->rule($validate);
|
||||
} else {
|
||||
if (strpos($validate, '.')) {
|
||||
// 支持场景
|
||||
list($validate, $scene) = explode('.', $validate);
|
||||
}
|
||||
$v = Loader::validate($validate);
|
||||
if (!empty($scene)) {
|
||||
$v->scene($scene);
|
||||
}
|
||||
}
|
||||
// 是否批量验证
|
||||
if($batch || $this->batchValidate){
|
||||
$v->batch(true);
|
||||
}
|
||||
|
||||
if (is_array($message)) {
|
||||
$v->message($message);
|
||||
}
|
||||
|
||||
if ($callback && is_callable($callback)) {
|
||||
call_user_func_array($callback, [$v, &$data]);
|
||||
}
|
||||
|
||||
if (!$v->check($data)) {
|
||||
if ($this->failException) {
|
||||
throw new ValidateException($v->getError());
|
||||
} else {
|
||||
return $v->getError();
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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;
|
||||
|
||||
class Cookie
|
||||
{
|
||||
protected static $config = [
|
||||
// cookie 名称前缀
|
||||
'prefix' => '',
|
||||
// cookie 保存时间
|
||||
'expire' => 0,
|
||||
// cookie 保存路径
|
||||
'path' => '/',
|
||||
// cookie 有效域名
|
||||
'domain' => '',
|
||||
// cookie 启用安全传输
|
||||
'secure' => false,
|
||||
// httponly设置
|
||||
'httponly' => '',
|
||||
// 是否使用 setcookie
|
||||
'setcookie' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* Cookie初始化
|
||||
* @param array $config
|
||||
* @return void
|
||||
*/
|
||||
public static function init(array $config = [])
|
||||
{
|
||||
if (empty($config)) {
|
||||
$config = Config::get('cookie');
|
||||
}
|
||||
self::$config = array_merge(self::$config, array_change_key_case($config));
|
||||
if (!empty(self::$config['httponly'])) {
|
||||
ini_set('session.cookie_httponly', 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置或者获取cookie作用域(前缀)
|
||||
* @param string $prefix
|
||||
* @return string|void
|
||||
*/
|
||||
public static function prefix($prefix = '')
|
||||
{
|
||||
if (empty($prefix)) {
|
||||
return self::$config['prefix'];
|
||||
}
|
||||
self::$config['prefix'] = $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cookie 设置、获取、删除
|
||||
*
|
||||
* @param string $name cookie名称
|
||||
* @param mixed $value cookie值
|
||||
* @param mixed $option 可选参数 可能会是 null|integer|string
|
||||
*
|
||||
* @return mixed
|
||||
* @internal param mixed $options cookie参数
|
||||
*/
|
||||
public static function set($name, $value = '', $option = null)
|
||||
{
|
||||
// 参数设置(会覆盖黙认设置)
|
||||
if (!is_null($option)) {
|
||||
if (is_numeric($option)) {
|
||||
$option = ['expire' => $option];
|
||||
} elseif (is_string($option)) {
|
||||
parse_str($option, $option);
|
||||
}
|
||||
$config = array_merge(self::$config, array_change_key_case($option));
|
||||
} else {
|
||||
$config = self::$config;
|
||||
}
|
||||
$name = $config['prefix'] . $name;
|
||||
// 设置cookie
|
||||
if (is_array($value)) {
|
||||
array_walk_recursive($value, 'self::jsonFormatProtect', 'encode');
|
||||
$value = 'think:' . json_encode($value);
|
||||
}
|
||||
$expire = !empty($config['expire']) ? time() + intval($config['expire']) : 0;
|
||||
if ($config['setcookie']) {
|
||||
setcookie($name, $value, $expire, $config['path'], $config['domain'], $config['secure'], $config['httponly']);
|
||||
}
|
||||
$_COOKIE[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断Cookie数据
|
||||
* @param string $name cookie名称
|
||||
* @param string|null $prefix cookie前缀
|
||||
* @return bool
|
||||
*/
|
||||
public static function has($name, $prefix = null)
|
||||
{
|
||||
$prefix = !is_null($prefix) ? $prefix : self::$config['prefix'];
|
||||
$name = $prefix . $name;
|
||||
return isset($_COOKIE[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cookie获取
|
||||
* @param string $name cookie名称
|
||||
* @param string|null $prefix cookie前缀
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get($name, $prefix = null)
|
||||
{
|
||||
$prefix = !is_null($prefix) ? $prefix : self::$config['prefix'];
|
||||
$name = $prefix . $name;
|
||||
if (isset($_COOKIE[$name])) {
|
||||
$value = $_COOKIE[$name];
|
||||
if (0 === strpos($value, 'think:')) {
|
||||
$value = substr($value, 6);
|
||||
$value = json_decode($value, true);
|
||||
array_walk_recursive($value, 'self::jsonFormatProtect', 'decode');
|
||||
}
|
||||
return $value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cookie删除
|
||||
* @param string $name cookie名称
|
||||
* @param string|null $prefix cookie前缀
|
||||
* @return mixed
|
||||
*/
|
||||
public static function delete($name, $prefix = null)
|
||||
{
|
||||
$config = self::$config;
|
||||
$prefix = !is_null($prefix) ? $prefix : $config['prefix'];
|
||||
$name = $prefix . $name;
|
||||
if ($config['setcookie']) {
|
||||
setcookie($name, '', time() - 3600, $config['path'], $config['domain'], $config['secure'], $config['httponly']);
|
||||
}
|
||||
// 删除指定cookie
|
||||
unset($_COOKIE[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cookie清空
|
||||
* @param string|null $prefix cookie前缀
|
||||
* @return mixed
|
||||
*/
|
||||
public static function clear($prefix = null)
|
||||
{
|
||||
// 清除指定前缀的所有cookie
|
||||
if (empty($_COOKIE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 要删除的cookie前缀,不指定则删除config设置的指定前缀
|
||||
$config = self::$config;
|
||||
$prefix = !is_null($prefix) ? $prefix : $config['prefix'];
|
||||
if ($prefix) {
|
||||
// 如果前缀为空字符串将不作处理直接返回
|
||||
foreach ($_COOKIE as $key => $val) {
|
||||
if (0 === strpos($key, $prefix)) {
|
||||
if ($config['setcookie']) {
|
||||
setcookie($key, '', time() - 3600, $config['path'], $config['domain'], $config['secure'], $config['httponly']);
|
||||
}
|
||||
unset($_COOKIE[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private static function jsonFormatProtect(&$val, $key, $type = 'encode')
|
||||
{
|
||||
if (!empty($val) && true !== $val) {
|
||||
$val = 'decode' == $type ? urldecode($val) : urlencode($val);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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;
|
||||
|
||||
use think\App;
|
||||
use think\Collection;
|
||||
use think\db\Query;
|
||||
|
||||
/**
|
||||
* Class Db
|
||||
* @package think
|
||||
* @method Query table(string $table) static 指定数据表(含前缀)
|
||||
* @method Query name(string $name) static 指定数据表(不含前缀)
|
||||
* @method Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件
|
||||
* @method Query join(mixed $join, mixed $condition = null, string $type = 'INNER') static JOIN查询
|
||||
* @method Query union(mixed $union, boolean $all = false) static UNION查询
|
||||
* @method Query limit(mixed $offset, integer $length = null) static 查询LIMIT
|
||||
* @method Query order(mixed $field, string $order = null) static 查询ORDER
|
||||
* @method mixed value(string $field) static 获取某个字段的值
|
||||
* @method array column(string $field, string $key = '') static 获取某个列的值
|
||||
* @method mixed find(mixed $data = []) static 查询单个记录
|
||||
* @method mixed select(mixed $data = []) static 查询多个记录
|
||||
* @method integer insert(array $data, boolean $replace = false, boolean $getLastInsID = false, string $sequence = null) static 插入一条记录
|
||||
* @method integer insertAll(array $dataSet) static 插入多条记录
|
||||
* @method integer update(array $data) static 更新记录
|
||||
* @method integer delete(mixed $data = []) static 删除记录
|
||||
* @method boolean chunk(integer $count, callable $callback, string $column = null) static 分块获取数据
|
||||
* @method mixed query(string $sql, array $bind = [], boolean $fetch = false, boolean $master = false, mixed $class = false) static SQL查询
|
||||
* @method integer execute(string $sql, array $bind = [], boolean $fetch = false, boolean $getLastInsID = false, string $sequence = null) static SQL执行
|
||||
* @method PaginatorCollection paginate(integer $listRows = 15, boolean $simple = false, array $config = []) static 分页查询
|
||||
*/
|
||||
class Db
|
||||
{
|
||||
// 数据库连接实例
|
||||
private static $instance = [];
|
||||
// 查询次数
|
||||
public static $queryTimes = 0;
|
||||
// 执行次数
|
||||
public static $executeTimes = 0;
|
||||
|
||||
/**
|
||||
* 数据库初始化 并取得数据库类实例
|
||||
* @static
|
||||
* @access public
|
||||
* @param mixed $config 连接配置
|
||||
* @param bool|string $name 连接标识 true 强制重新连接
|
||||
* @return \think\db\Connection
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function connect($config = [], $name = false)
|
||||
{
|
||||
if (false === $name) {
|
||||
$name = md5(serialize($config));
|
||||
}
|
||||
if (true === $name || !isset(self::$instance[$name])) {
|
||||
// 解析连接参数 支持数组和字符串
|
||||
$options = self::parseConfig($config);
|
||||
if (empty($options['type'])) {
|
||||
throw new \InvalidArgumentException('Underfined db type');
|
||||
}
|
||||
$class = false !== strpos($options['type'], '\\') ? $options['type'] : '\\think\\db\\connector\\' . ucwords($options['type']);
|
||||
// 记录初始化信息
|
||||
App::$debug && Log::record('[ DB ] INIT ' . $options['type'] . ':' . var_export($options, true), 'info');
|
||||
if (true === $name) {
|
||||
return new $class($options);
|
||||
} else {
|
||||
self::$instance[$name] = new $class($options);
|
||||
}
|
||||
}
|
||||
return self::$instance[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库连接参数解析
|
||||
* @static
|
||||
* @access private
|
||||
* @param mixed $config
|
||||
* @return array
|
||||
*/
|
||||
private static function parseConfig($config)
|
||||
{
|
||||
if (empty($config)) {
|
||||
$config = Config::get('database');
|
||||
} elseif (is_string($config) && false === strpos($config, '/')) {
|
||||
// 支持读取配置参数
|
||||
$config = Config::get($config);
|
||||
}
|
||||
if (is_string($config)) {
|
||||
return self::parseDsn($config);
|
||||
} else {
|
||||
return $config;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DSN解析
|
||||
* 格式: mysql://username:passwd@localhost:3306/DbName?param1=val1¶m2=val2#utf8
|
||||
* @static
|
||||
* @access private
|
||||
* @param string $dsnStr
|
||||
* @return array
|
||||
*/
|
||||
private static function parseDsn($dsnStr)
|
||||
{
|
||||
$info = parse_url($dsnStr);
|
||||
if (!$info) {
|
||||
return [];
|
||||
}
|
||||
$dsn = [
|
||||
'type' => $info['scheme'],
|
||||
'username' => isset($info['user']) ? $info['user'] : '',
|
||||
'password' => isset($info['pass']) ? $info['pass'] : '',
|
||||
'hostname' => isset($info['host']) ? $info['host'] : '',
|
||||
'hostport' => isset($info['port']) ? $info['port'] : '',
|
||||
'database' => !empty($info['path']) ? ltrim($info['path'], '/') : '',
|
||||
'charset' => isset($info['fragment']) ? $info['fragment'] : 'utf8',
|
||||
];
|
||||
|
||||
if (isset($info['query'])) {
|
||||
parse_str($info['query'], $dsn['params']);
|
||||
} else {
|
||||
$dsn['params'] = [];
|
||||
}
|
||||
return $dsn;
|
||||
}
|
||||
|
||||
// 调用驱动类的方法
|
||||
public static function __callStatic($method, $params)
|
||||
{
|
||||
// 自动初始化数据库
|
||||
return call_user_func_array([self::connect(), $method], $params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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;
|
||||
|
||||
class Debug
|
||||
{
|
||||
// 区间时间信息
|
||||
protected static $info = [];
|
||||
// 区间内存信息
|
||||
protected static $mem = [];
|
||||
|
||||
/**
|
||||
* 记录时间(微秒)和内存使用情况
|
||||
* @param string $name 标记位置
|
||||
* @param mixed $value 标记值 留空则取当前 time 表示仅记录时间 否则同时记录时间和内存
|
||||
* @return mixed
|
||||
*/
|
||||
public static function remark($name, $value = '')
|
||||
{
|
||||
// 记录时间和内存使用
|
||||
self::$info[$name] = is_float($value) ? $value : microtime(true);
|
||||
if ('time' != $value) {
|
||||
self::$mem['mem'][$name] = is_float($value) ? $value : memory_get_usage();
|
||||
self::$mem['peak'][$name] = memory_get_peak_usage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计某个区间的时间(微秒)使用情况
|
||||
* @param string $start 开始标签
|
||||
* @param string $end 结束标签
|
||||
* @param integer|string $dec 小数位
|
||||
* @return integer
|
||||
*/
|
||||
public static function getRangeTime($start, $end, $dec = 6)
|
||||
{
|
||||
if (!isset(self::$info[$end])) {
|
||||
self::$info[$end] = microtime(true);
|
||||
}
|
||||
return number_format((self::$info[$end] - self::$info[$start]), $dec);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计从开始到统计时的时间(微秒)使用情况
|
||||
* @param integer|string $dec 小数位
|
||||
* @return integer
|
||||
*/
|
||||
public static function getUseTime($dec = 6)
|
||||
{
|
||||
return number_format((microtime(true) - START_TIME), $dec);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前访问的吞吐率情况
|
||||
* @return string
|
||||
*/
|
||||
public static function getThroughputRate()
|
||||
{
|
||||
return number_format(1 / self::getUseTime(), 2) . 'req/s';
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录区间的内存使用情况
|
||||
* @param string $start 开始标签
|
||||
* @param string $end 结束标签
|
||||
* @param integer|string $dec 小数位
|
||||
* @return string
|
||||
*/
|
||||
public static function getRangeMem($start, $end, $dec = 2)
|
||||
{
|
||||
if (!isset(self::$mem['mem'][$end])) {
|
||||
self::$mem['mem'][$end] = memory_get_usage();
|
||||
}
|
||||
$size = self::$mem['mem'][$end] - self::$mem['mem'][$start];
|
||||
$a = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
$pos = 0;
|
||||
while ($size >= 1024) {
|
||||
$size /= 1024;
|
||||
$pos++;
|
||||
}
|
||||
return round($size, $dec) . " " . $a[$pos];
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计从开始到统计时的内存使用情况
|
||||
* @param integer|string $dec 小数位
|
||||
* @return string
|
||||
*/
|
||||
public static function getUseMem($dec = 2)
|
||||
{
|
||||
$size = memory_get_usage() - START_MEM;
|
||||
$a = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
$pos = 0;
|
||||
while ($size >= 1024) {
|
||||
$size /= 1024;
|
||||
$pos++;
|
||||
}
|
||||
return round($size, $dec) . " " . $a[$pos];
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计区间的内存峰值情况
|
||||
* @param string $start 开始标签
|
||||
* @param string $end 结束标签
|
||||
* @param integer|string $dec 小数位
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getMemPeak($start, $end, $dec = 2)
|
||||
{
|
||||
if (!isset(self::$mem['peak'][$end])) {
|
||||
self::$mem['peak'][$end] = memory_get_peak_usage();
|
||||
}
|
||||
$size = self::$mem['peak'][$end] - self::$mem['peak'][$start];
|
||||
$a = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
$pos = 0;
|
||||
while ($size >= 1024) {
|
||||
$size /= 1024;
|
||||
$pos++;
|
||||
}
|
||||
return round($size, $dec) . " " . $a[$pos];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件加载信息
|
||||
* @param bool $detail 是否显示详细
|
||||
* @return integer|array
|
||||
*/
|
||||
public static function getFile($detail = false)
|
||||
{
|
||||
if ($detail) {
|
||||
$files = get_included_files();
|
||||
$info = [];
|
||||
foreach ($files as $key => $file) {
|
||||
$info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )';
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
return count(get_included_files());
|
||||
}
|
||||
|
||||
/**
|
||||
* 浏览器友好的变量输出
|
||||
* @param mixed $var 变量
|
||||
* @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串
|
||||
* @param string $label 标签 默认为空
|
||||
* @return void|string
|
||||
*/
|
||||
public static function dump($var, $echo = true, $label = null)
|
||||
{
|
||||
$label = (null === $label) ? '' : rtrim($label) . ':';
|
||||
ob_start();
|
||||
var_dump($var);
|
||||
$output = ob_get_clean();
|
||||
$output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output);
|
||||
if (IS_CLI) {
|
||||
$output = PHP_EOL . $label . $output . PHP_EOL;
|
||||
} else {
|
||||
if (!extension_loaded('xdebug')) {
|
||||
$output = htmlspecialchars($output, ENT_QUOTES);
|
||||
}
|
||||
$output = '<pre>' . $label . $output . '</pre>';
|
||||
}
|
||||
if ($echo) {
|
||||
echo ($output);
|
||||
return null;
|
||||
} else {
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think;
|
||||
|
||||
use think\console\Output as ConsoleOutput;
|
||||
use think\exception\ErrorException;
|
||||
use think\exception\Handle;
|
||||
use think\exception\ThrowableError;
|
||||
|
||||
class Error
|
||||
{
|
||||
/**
|
||||
* 注册异常处理
|
||||
* @return void
|
||||
*/
|
||||
public static function register()
|
||||
{
|
||||
error_reporting(E_ALL);
|
||||
set_error_handler([__CLASS__, 'appError']);
|
||||
set_exception_handler([__CLASS__, 'appException']);
|
||||
register_shutdown_function([__CLASS__, 'appShutdown']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception Handler
|
||||
* @param \Exception|\Throwable $e
|
||||
*/
|
||||
public static function appException($e)
|
||||
{
|
||||
if (!$e instanceof \Exception) {
|
||||
$e = new ThrowableError($e);
|
||||
}
|
||||
|
||||
self::getExceptionHandler()->report($e);
|
||||
if (IS_CLI) {
|
||||
self::getExceptionHandler()->renderForConsole(new ConsoleOutput, $e);
|
||||
} else {
|
||||
self::getExceptionHandler()->render($e)->send();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error Handler
|
||||
* @param integer $errno 错误编号
|
||||
* @param integer $errstr 详细错误信息
|
||||
* @param string $errfile 出错的文件
|
||||
* @param integer $errline 出错行号
|
||||
* @param array $errcontext
|
||||
* @throws ErrorException
|
||||
*/
|
||||
public static function appError($errno, $errstr, $errfile = '', $errline = 0, $errcontext = [])
|
||||
{
|
||||
$exception = new ErrorException($errno, $errstr, $errfile, $errline, $errcontext);
|
||||
if (error_reporting() & $errno) {
|
||||
// 将错误信息托管至 think\exception\ErrorException
|
||||
throw $exception;
|
||||
}else{
|
||||
self::getExceptionHandler()->report($exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown Handler
|
||||
*/
|
||||
public static function appShutdown()
|
||||
{
|
||||
if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) {
|
||||
// 将错误信息托管至think\ErrorException
|
||||
$exception = new ErrorException($error['type'], $error['message'], $error['file'], $error['line']);
|
||||
|
||||
self::appException($exception);
|
||||
}
|
||||
|
||||
// 写入日志
|
||||
Log::save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定错误类型是否致命
|
||||
*
|
||||
* @param int $type
|
||||
* @return bool
|
||||
*/
|
||||
protected static function isFatal($type)
|
||||
{
|
||||
return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of the exception handler.
|
||||
*
|
||||
* @return \think\exception\Handle
|
||||
*/
|
||||
public static function getExceptionHandler()
|
||||
{
|
||||
static $handle;
|
||||
|
||||
if (!$handle) {
|
||||
|
||||
if ($class = Config::get('exception_handle')) {
|
||||
if (class_exists($class) && is_subclass_of($class, "\\think\\exception\\Handle")) {
|
||||
$handle = new $class;
|
||||
}
|
||||
}
|
||||
if (!$handle) {
|
||||
$handle = new Handle();
|
||||
}
|
||||
}
|
||||
|
||||
return $handle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace 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
|
||||
*
|
||||
* @param string $label 数据分类,用于异常页面显示
|
||||
* @param array $data 需要显示的数据,必须为关联数组
|
||||
*/
|
||||
final protected function setData($label, array $data)
|
||||
{
|
||||
$this->data[$label] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取异常额外Debug数据
|
||||
* 主要用于输出到异常页面便于调试
|
||||
* @return array 由setData设置的Debug数据
|
||||
*/
|
||||
final public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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;
|
||||
|
||||
use SplFileObject;
|
||||
|
||||
class File extends SplFileObject
|
||||
{
|
||||
/**
|
||||
* 错误信息
|
||||
* @var string
|
||||
*/
|
||||
private $error = '';
|
||||
// 当前完整文件名
|
||||
protected $filename;
|
||||
// 文件上传命名规则
|
||||
protected $rule = 'date';
|
||||
// 单元测试
|
||||
protected $isTest;
|
||||
// 上传文件信息
|
||||
protected $info;
|
||||
|
||||
public function __construct($filename, $mode = 'r', $useIncludePath = false, $context = null)
|
||||
{
|
||||
parent::__construct($filename, $mode, $useIncludePath, $context);
|
||||
$this->filename = $this->getRealPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否测试
|
||||
* @param bool $test 是否测试
|
||||
* @return $this
|
||||
*/
|
||||
public function isTest($test = false)
|
||||
{
|
||||
$this->isTest = $test;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置上传信息
|
||||
* @param array $info 上传文件信息
|
||||
* @return $this
|
||||
*/
|
||||
public function setUploadInfo($info)
|
||||
{
|
||||
$this->info = $info;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上传文件的信息
|
||||
* @param string $name
|
||||
* @return array|string
|
||||
*/
|
||||
public function getInfo($name = '')
|
||||
{
|
||||
return isset($this->info[$name]) ? $this->info[$name] : $this->info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查目录是否可写
|
||||
* @param string $path 目录
|
||||
* @return boolean
|
||||
*/
|
||||
protected function checkPath($path)
|
||||
{
|
||||
if (is_dir($path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mkdir($path, 0777, true)) {
|
||||
return true;
|
||||
} else {
|
||||
$this->error = "目录 {$path} 创建失败!";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件类型信息
|
||||
* @return string
|
||||
*/
|
||||
public function getMime()
|
||||
{
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
return finfo_file($finfo, $this->filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文件的命名规则
|
||||
* @param string $rule 文件命名规则
|
||||
* @return $this
|
||||
*/
|
||||
public function rule($rule)
|
||||
{
|
||||
$this->rule = $rule;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否合法的上传文件
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid()
|
||||
{
|
||||
if ($this->isTest) {
|
||||
return is_file($this->filename);
|
||||
}
|
||||
return is_uploaded_file($this->filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动文件
|
||||
* @param string $path 保存路径
|
||||
* @param string|bool $savename 保存的文件名 默认自动生成
|
||||
* @param boolean $replace 同名文件是否覆盖
|
||||
* @return false|SplFileInfo false-失败 否则返回SplFileInfo实例
|
||||
*/
|
||||
public function move($path, $savename = true, $replace = true)
|
||||
{
|
||||
// 检测合法性
|
||||
if (!$this->isValid()) {
|
||||
$this->error = '非法上传文件';
|
||||
return false;
|
||||
}
|
||||
|
||||
$path = rtrim($path, DS) . DS;
|
||||
// 文件保存命名规则
|
||||
$savename = $this->getSaveName($savename);
|
||||
|
||||
// 检测目录
|
||||
if (false === $this->checkPath(dirname($path . $savename))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 不覆盖同名文件 */
|
||||
if (!$replace && is_file($path . $savename)) {
|
||||
$this->error = '存在同名文件' . $path . $savename;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 移动文件 */
|
||||
if ($this->isTest) {
|
||||
rename($this->filename, $path . $savename);
|
||||
} elseif (!move_uploaded_file($this->filename, $path . $savename)) {
|
||||
$this->error = '文件上传保存错误!';
|
||||
return false;
|
||||
}
|
||||
|
||||
return new \SplFileInfo($path . $savename);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取保存文件名
|
||||
* @param string|bool $savename 保存的文件名 默认自动生成
|
||||
* @return string
|
||||
*/
|
||||
protected function getSaveName($savename)
|
||||
{
|
||||
if (true === $savename) {
|
||||
// 自动生成文件名
|
||||
if ($this->rule instanceof \Closure) {
|
||||
$savename = call_user_func_array($this->rule, [$this]);
|
||||
} else {
|
||||
switch ($this->rule) {
|
||||
case 'md5':
|
||||
$md5 = md5_file($this->filename);
|
||||
$savename = substr($md5, 0, 2) . DS . substr($md5, 2);
|
||||
break;
|
||||
case 'sha1':
|
||||
$sha1 = sha1_file($this->filename);
|
||||
$savename = substr($sha1, 0, 2) . DS . substr($sha1, 2);
|
||||
break;
|
||||
case 'date':
|
||||
$savename = date('Ymd') . DS . md5(microtime(true));
|
||||
break;
|
||||
default:
|
||||
$savename = call_user_func($this->rule);
|
||||
}
|
||||
}
|
||||
if (!strpos($savename, '.')) {
|
||||
$savename .= '.' . pathinfo($this->getInfo('name'), PATHINFO_EXTENSION);
|
||||
}
|
||||
} elseif ('' === $savename) {
|
||||
$savename = $this->getInfo('name');
|
||||
}
|
||||
return $savename;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误信息
|
||||
* @return mixed
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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;
|
||||
|
||||
use think\App;
|
||||
use think\Debug;
|
||||
use think\Log;
|
||||
|
||||
class Hook
|
||||
{
|
||||
|
||||
private static $tags = [];
|
||||
|
||||
/**
|
||||
* 动态添加行为扩展到某个标签
|
||||
* @param string $tag 标签名称
|
||||
* @param mixed $behavior 行为名称
|
||||
* @param bool $first 是否放到开头执行
|
||||
* @return void
|
||||
*/
|
||||
public static function add($tag, $behavior, $first = false)
|
||||
{
|
||||
if (!isset(self::$tags[$tag])) {
|
||||
self::$tags[$tag] = [];
|
||||
}
|
||||
if (is_array($behavior)) {
|
||||
self::$tags[$tag] = array_merge(self::$tags[$tag], $behavior);
|
||||
} elseif ($first) {
|
||||
array_unshift(self::$tags[$tag], $behavior);
|
||||
} else {
|
||||
self::$tags[$tag][] = $behavior;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量导入插件
|
||||
* @param array $data 插件信息
|
||||
* @param boolean $recursive 是否递归合并
|
||||
* @return void
|
||||
*/
|
||||
public static function import($tags, $recursive = true)
|
||||
{
|
||||
empty($tags) && $tags = [];
|
||||
if (!$recursive) {
|
||||
// 覆盖导入
|
||||
self::$tags = array_merge(self::$tags, $tags);
|
||||
} else {
|
||||
// 合并导入
|
||||
foreach ($tags as $tag => $val) {
|
||||
if (!isset(self::$tags[$tag])) {
|
||||
self::$tags[$tag] = [];
|
||||
}
|
||||
|
||||
if (!empty($val['_overlay'])) {
|
||||
// 可以针对某个标签指定覆盖模式
|
||||
unset($val['_overlay']);
|
||||
self::$tags[$tag] = $val;
|
||||
} else {
|
||||
// 合并模式
|
||||
self::$tags[$tag] = array_merge(self::$tags[$tag], $val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件信息
|
||||
* @param string $tag 插件位置 留空获取全部
|
||||
* @return array
|
||||
*/
|
||||
public static function get($tag = '')
|
||||
{
|
||||
if (empty($tag)) {
|
||||
// 获取全部的插件信息
|
||||
return self::$tags;
|
||||
} else {
|
||||
return self::$tags[$tag];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听标签的行为
|
||||
* @param string $tag 标签名称
|
||||
* @param mixed $params 传入参数
|
||||
* @param mixed $extra 额外参数
|
||||
* @param bool $once 只获取一个有效返回值
|
||||
* @return mixed
|
||||
*/
|
||||
public static function listen($tag, &$params = null, $extra = null, $once = false)
|
||||
{
|
||||
$results = [];
|
||||
if (isset(self::$tags[$tag])) {
|
||||
foreach (self::$tags[$tag] as $name) {
|
||||
|
||||
if (App::$debug) {
|
||||
Debug::remark('behavior_start', 'time');
|
||||
}
|
||||
|
||||
$result = self::exec($name, $tag, $params, $extra);
|
||||
|
||||
if (!is_null($result) && $once) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (App::$debug) {
|
||||
Debug::remark('behavior_end', 'time');
|
||||
if ($name instanceof \Closure) {
|
||||
$name = 'Closure';
|
||||
} elseif (is_object($name)) {
|
||||
$name = get_class($name);
|
||||
}
|
||||
Log::record('[ BEHAVIOR ] Run ' . $name . ' @' . $tag . ' [ RunTime:' . Debug::getRangeTime('behavior_start', 'behavior_end') . 's ]', 'info');
|
||||
}
|
||||
if (false === $result) {
|
||||
// 如果返回false 则中断行为执行
|
||||
break;
|
||||
}
|
||||
$results[] = $result;
|
||||
}
|
||||
}
|
||||
return $once ? null : $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行某个行为
|
||||
* @param mixed $class 要执行的行为
|
||||
* @param string $tag 方法名(标签名)
|
||||
* @param Mixed $params 传人的参数
|
||||
* @param mixed $extra 额外参数
|
||||
* @return mixed
|
||||
*/
|
||||
public static function exec($class, $tag = '', &$params = null,$extra=null)
|
||||
{
|
||||
if ($class instanceof \Closure) {
|
||||
$result = call_user_func_array($class, [ & $params,$extra]);
|
||||
} elseif (is_object($class)) {
|
||||
$result = call_user_func_array([$class, $tag], [ & $params,$extra]);
|
||||
} else {
|
||||
$obj = new $class();
|
||||
$result = ($tag && is_callable([$obj, $tag])) ? $obj->$tag($params,$extra) : $obj->run($params,$extra);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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;
|
||||
|
||||
use think\App;
|
||||
use think\Cookie;
|
||||
use think\Log;
|
||||
|
||||
class Lang
|
||||
{
|
||||
// 语言数据
|
||||
private static $lang = [];
|
||||
// 语言作用域
|
||||
private static $range = 'zh-cn';
|
||||
// 语言自动侦测的变量
|
||||
protected static $langDetectVar = 'lang';
|
||||
// 语言Cookie变量
|
||||
protected static $langCookieVar = 'think_var';
|
||||
// 允许语言列表
|
||||
protected static $allowLangList = [];
|
||||
|
||||
// 设定当前的语言
|
||||
public static function range($range = '')
|
||||
{
|
||||
if ('' == $range) {
|
||||
return self::$range;
|
||||
} else {
|
||||
self::$range = $range;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置语言定义(不区分大小写)
|
||||
* @param string|array $name 语言变量
|
||||
* @param string $value 语言值
|
||||
* @param string $range 语言作用域
|
||||
* @return mixed
|
||||
*/
|
||||
public static function set($name, $value = null, $range = '')
|
||||
{
|
||||
$range = $range ?: self::$range;
|
||||
// 批量定义
|
||||
if (!isset(self::$lang[$range])) {
|
||||
self::$lang[$range] = [];
|
||||
}
|
||||
if (is_array($name)) {
|
||||
return self::$lang[$range] = array_change_key_case($name) + self::$lang[$range];
|
||||
} else {
|
||||
return self::$lang[$range][strtolower($name)] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载语言定义(不区分大小写)
|
||||
* @param string $file 语言文件
|
||||
* @param string $range 语言作用域
|
||||
* @return mixed
|
||||
*/
|
||||
public static function load($file, $range = '')
|
||||
{
|
||||
$range = $range ?: self::$range;
|
||||
if (!isset(self::$lang[$range])) {
|
||||
self::$lang[$range] = [];
|
||||
}
|
||||
// 批量定义
|
||||
if (is_string($file)) {
|
||||
$file = [$file];
|
||||
}
|
||||
$lang = [];
|
||||
foreach ($file as $_file) {
|
||||
if (is_file($_file)) {
|
||||
// 记录加载信息
|
||||
App::$debug && Log::record('[ LANG ] ' . $_file, 'info');
|
||||
$_lang = include $_file;
|
||||
} else {
|
||||
$_lang = [];
|
||||
}
|
||||
$lang = array_change_key_case($_lang) + $lang;
|
||||
}
|
||||
if (!empty($lang)) {
|
||||
self::$lang[$range] = $lang + self::$lang[$range];
|
||||
}
|
||||
return self::$lang[$range];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取语言定义(不区分大小写)
|
||||
* @param string|null $name 语言变量
|
||||
* @param array $vars 变量替换
|
||||
* @param string $range 语言作用域
|
||||
* @return mixed
|
||||
*/
|
||||
public static function has($name, $range = '')
|
||||
{
|
||||
$range = $range ?: self::$range;
|
||||
return isset(self::$lang[$range][strtolower($name)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取语言定义(不区分大小写)
|
||||
* @param string|null $name 语言变量
|
||||
* @param array $vars 变量替换
|
||||
* @param string $range 语言作用域
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get($name = null, $vars = [], $range = '')
|
||||
{
|
||||
$range = $range ?: self::$range;
|
||||
// 空参数返回所有定义
|
||||
if (empty($name)) {
|
||||
return self::$lang[$range];
|
||||
}
|
||||
$key = strtolower($name);
|
||||
$value = isset(self::$lang[$range][$key]) ? self::$lang[$range][$key] : $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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动侦测设置获取语言选择
|
||||
* @return void
|
||||
*/
|
||||
public static function detect()
|
||||
{
|
||||
// 自动侦测设置获取语言选择
|
||||
$langSet = '';
|
||||
if (isset($_GET[self::$langDetectVar])) {
|
||||
// url中设置了语言变量
|
||||
$langSet = strtolower($_GET[self::$langDetectVar]);
|
||||
Cookie::set(self::$langCookieVar, $langSet, 3600);
|
||||
} elseif (Cookie::get(self::$langCookieVar)) {
|
||||
// 获取上次用户的选择
|
||||
$langSet = strtolower(Cookie::get(self::$langCookieVar));
|
||||
} elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
||||
// 自动侦测浏览器语言
|
||||
preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches);
|
||||
$langSet = strtolower($matches[1]);
|
||||
Cookie::set(self::$langCookieVar, $langSet, 3600);
|
||||
}
|
||||
if (empty(self::$allowLangList) || in_array($langSet, self::$allowLangList)) {
|
||||
// 合法的语言
|
||||
self::$range = $langSet;
|
||||
}
|
||||
return self::$range;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置语言自动侦测的变量
|
||||
* @param string $var 变量名称
|
||||
* @return void
|
||||
*/
|
||||
public static function setLangDetectVar($var)
|
||||
{
|
||||
self::$langDetectVar = $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置语言的cookie保存变量
|
||||
* @param string $var 变量名称
|
||||
* @return void
|
||||
*/
|
||||
public static function setLangCookieVar($var)
|
||||
{
|
||||
self::$langCookieVar = $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置允许的语言列表
|
||||
* @param array $list 语言列表
|
||||
* @return void
|
||||
*/
|
||||
public static function setAllowLangList($list)
|
||||
{
|
||||
self::$allowLangList = $list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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;
|
||||
|
||||
use think\App;
|
||||
use think\exception\HttpException;
|
||||
use think\exception\ClassNotFoundException;
|
||||
use think\Request;
|
||||
|
||||
class Loader
|
||||
{
|
||||
protected static $instance = [];
|
||||
// 类名映射
|
||||
protected static $map = [];
|
||||
// 加载列表
|
||||
protected static $load = [];
|
||||
// 命名空间
|
||||
protected static $namespace = [];
|
||||
// 命名空间别名
|
||||
protected static $namespaceAlias = [];
|
||||
// PSR-4
|
||||
private static $prefixLengthsPsr4 = [];
|
||||
private static $prefixDirsPsr4 = [];
|
||||
// PSR-0
|
||||
private static $prefixesPsr0 = [];
|
||||
// Composer自动加载
|
||||
private static $composerLoader = false;
|
||||
|
||||
// 自动加载
|
||||
public static function autoload($class)
|
||||
{
|
||||
// 检测命名空间别名
|
||||
if (!empty(self::$namespaceAlias)) {
|
||||
$namespace = dirname($class);
|
||||
if (isset(self::$namespaceAlias[$namespace])) {
|
||||
$original = self::$namespaceAlias[$namespace] . '\\' . basename($class);
|
||||
if (class_exists($original)) {
|
||||
return class_alias($original, $class, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty(self::$map[$class])) {
|
||||
// 类库映射
|
||||
include self::$map[$class];
|
||||
} elseif (self::$composerLoader && $file = self::findFileInComposer($class)) {
|
||||
// Composer自动加载
|
||||
include $file;
|
||||
} else {
|
||||
// 命名空间自动加载
|
||||
if (!strpos($class, '\\')) {
|
||||
return false;
|
||||
}
|
||||
// 解析命名空间
|
||||
list($name, $class) = explode('\\', $class, 2);
|
||||
if (isset(self::$namespace[$name])) {
|
||||
// 注册的命名空间
|
||||
$path = self::$namespace[$name];
|
||||
} elseif (is_dir(EXTEND_PATH . $name)) {
|
||||
// 扩展类库命名空间
|
||||
$path = EXTEND_PATH . $name . DS;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
$filename = $path . str_replace('\\', DS, $class) . EXT;
|
||||
if (is_file($filename)) {
|
||||
// 开启调试模式Win环境严格区分大小写
|
||||
if (IS_WIN && false === strpos(realpath($filename), $class . EXT)) {
|
||||
return false;
|
||||
}
|
||||
include $filename;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 注册classmap
|
||||
public static function addMap($class, $map = '')
|
||||
{
|
||||
if (is_array($class)) {
|
||||
self::$map = array_merge(self::$map, $class);
|
||||
} else {
|
||||
self::$map[$class] = $map;
|
||||
}
|
||||
}
|
||||
|
||||
// 注册命名空间
|
||||
public static function addNamespace($namespace, $path = '')
|
||||
{
|
||||
if (is_array($namespace)) {
|
||||
self::$namespace = array_merge(self::$namespace, $namespace);
|
||||
} else {
|
||||
self::$namespace[$namespace] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
// 注册命名空间别名
|
||||
public static function addNamespaceAlias($namespace, $original = '')
|
||||
{
|
||||
if (is_array($namespace)) {
|
||||
self::$namespaceAlias = array_merge(self::$namespace, $namespace);
|
||||
} else {
|
||||
self::$namespaceAlias[$namespace] = $original;
|
||||
}
|
||||
}
|
||||
|
||||
// 注册自动加载机制
|
||||
public static function register($autoload = '')
|
||||
{
|
||||
// 注册系统自动加载
|
||||
spl_autoload_register($autoload ?: 'think\\Loader::autoload');
|
||||
|
||||
if (is_dir(VENDOR_PATH . 'composer')) {
|
||||
// 注册Composer自动加载
|
||||
self::registerComposerLoader();
|
||||
self::$composerLoader = true;
|
||||
} elseif(is_file(VENDOR_PATH . 'think_autoload.php')) {
|
||||
// 读取Composer自动加载文件
|
||||
$autoload = include VENDOR_PATH . 'think_autoload.php';
|
||||
if (is_array($autoload)) {
|
||||
self::addMap($autoload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 注册composer自动加载
|
||||
private static function registerComposerLoader()
|
||||
{
|
||||
if (is_file(VENDOR_PATH . 'composer/autoload_namespaces.php')) {
|
||||
$map = require VENDOR_PATH . 'composer/autoload_namespaces.php';
|
||||
foreach ($map as $namespace => $path) {
|
||||
self::$prefixesPsr0[$namespace[0]][$namespace] = (array) $path;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_file(VENDOR_PATH . 'composer/autoload_psr4.php')) {
|
||||
$map = require VENDOR_PATH . 'composer/autoload_psr4.php';
|
||||
foreach ($map as $namespace => $path) {
|
||||
$length = strlen($namespace);
|
||||
if ('\\' !== $namespace[$length - 1]) {
|
||||
throw new \InvalidArgumentException("PSR-4 error: A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
self::$prefixLengthsPsr4[$namespace[0]][$namespace] = $length;
|
||||
self::$prefixDirsPsr4[$namespace] = (array) $path;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_file(VENDOR_PATH . 'composer/autoload_classmap.php')) {
|
||||
$classMap = require VENDOR_PATH . 'composer/autoload_classmap.php';
|
||||
if ($classMap) {
|
||||
self::addMap($classMap);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_file(VENDOR_PATH . 'composer/autoload_files.php')) {
|
||||
$includeFiles = require VENDOR_PATH . 'composer/autoload_files.php';
|
||||
foreach ($includeFiles as $fileIdentifier => $file) {
|
||||
self::composerRequire($fileIdentifier, $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function composerRequire($fileIdentifier, $file)
|
||||
{
|
||||
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
|
||||
require $file;
|
||||
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static function findFileInComposer($class, $ext = '.php')
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DS) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset(self::$prefixLengthsPsr4[$first])) {
|
||||
foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach (self::$prefixDirsPsr4[$prefix] as $dir) {
|
||||
if (file_exists($file = $dir . DS . substr($logicalPathPsr4, $length))) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DS);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DS) . $ext;
|
||||
}
|
||||
|
||||
if (isset(self::$prefixesPsr0[$first])) {
|
||||
foreach (self::$prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DS . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remember that this class does not exist.
|
||||
return self::$map[$class] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入所需的类库 同java的Import 本函数有缓存功能
|
||||
* @param string $class 类库命名空间字符串
|
||||
* @param string $baseUrl 起始路径
|
||||
* @param string $ext 导入的文件扩展名
|
||||
* @return boolean
|
||||
*/
|
||||
public static function import($class, $baseUrl = '', $ext = EXT)
|
||||
{
|
||||
static $_file = [];
|
||||
$class = str_replace(['.', '#'], [DS, '.'], $class);
|
||||
if (isset($_file[$class . $baseUrl])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (empty($baseUrl)) {
|
||||
list($name, $class) = explode(DS, $class, 2);
|
||||
if (isset(self::$namespace[$name])) {
|
||||
// 注册的命名空间
|
||||
$baseUrl = self::$namespace[$name];
|
||||
} elseif ('@' == $name) {
|
||||
//加载当前模块应用类库
|
||||
$baseUrl = App::$modulePath;
|
||||
} elseif (is_dir(EXTEND_PATH . $name)) {
|
||||
$baseUrl = EXTEND_PATH;
|
||||
} else {
|
||||
// 加载其它模块的类库
|
||||
$baseUrl = APP_PATH . $name . DS;
|
||||
}
|
||||
} elseif (substr($baseUrl, -1) != DS) {
|
||||
$baseUrl .= DS;
|
||||
}
|
||||
// 如果类存在 则导入类库文件
|
||||
$filename = $baseUrl . $class . $ext;
|
||||
if (is_file($filename)) {
|
||||
// 开启调试模式Win环境严格区分大小写
|
||||
if (IS_WIN && false === strpos(realpath($filename), $class . $ext)) {
|
||||
return false;
|
||||
}
|
||||
include $filename;
|
||||
$_file[$class . $baseUrl] = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化(分层)模型
|
||||
* @param string $name Model名称
|
||||
* @param string $layer 业务层名称
|
||||
* @param bool $appendSuffix 是否添加类名后缀
|
||||
* @param string $common 公共模块名
|
||||
* @return Object
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public static function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common')
|
||||
{
|
||||
if (isset(self::$instance[$name . $layer])) {
|
||||
return self::$instance[$name . $layer];
|
||||
}
|
||||
if (strpos($name, '/')) {
|
||||
list($module, $name) = explode('/', $name, 2);
|
||||
} else {
|
||||
$module = Request::instance()->module();
|
||||
}
|
||||
$class = self::parseClass($module, $layer, $name, $appendSuffix);
|
||||
if (class_exists($class)) {
|
||||
$model = new $class();
|
||||
} else {
|
||||
$class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class);
|
||||
if (class_exists($class)) {
|
||||
$model = new $class();
|
||||
} else {
|
||||
throw new ClassNotFoundException('class not exists:' . $class, $class);
|
||||
}
|
||||
}
|
||||
self::$instance[$name . $layer] = $model;
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化(分层)控制器 格式:[模块名/]控制器名
|
||||
* @param string $name 资源地址
|
||||
* @param string $layer 控制层名称
|
||||
* @param bool $appendSuffix 是否添加类名后缀
|
||||
* @param string $empty 空控制器名称
|
||||
* @return Object|false
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')
|
||||
{
|
||||
if (strpos($name, '/')) {
|
||||
list($module, $name) = explode('/', $name);
|
||||
} else {
|
||||
$module = Request::instance()->module();
|
||||
}
|
||||
$class = self::parseClass($module, $layer, $name, $appendSuffix);
|
||||
if (class_exists($class)) {
|
||||
return new $class(Request::instance());
|
||||
} elseif ($empty && class_exists($emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix))) {
|
||||
return new $emptyClass(Request::instance());
|
||||
} else {
|
||||
throw new ClassNotFoundException('class not exists:' . $class, $class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化验证类 格式:[模块名/]验证器名
|
||||
* @param string $name 资源地址
|
||||
* @param string $layer 验证层名称
|
||||
* @param bool $appendSuffix 是否添加类名后缀
|
||||
* @param string $common 公共模块名
|
||||
* @return Object|false
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public static function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common')
|
||||
{
|
||||
$name = $name ?: Config::get('default_validate');
|
||||
if (empty($name)) {
|
||||
return new Validate;
|
||||
}
|
||||
|
||||
if (isset(self::$instance[$name . $layer])) {
|
||||
return self::$instance[$name . $layer];
|
||||
}
|
||||
if (strpos($name, '/')) {
|
||||
list($module, $name) = explode('/', $name);
|
||||
} else {
|
||||
$module = Request::instance()->module();
|
||||
}
|
||||
$class = self::parseClass($module, $layer, $name, $appendSuffix);
|
||||
if (class_exists($class)) {
|
||||
$validate = new $class;
|
||||
} else {
|
||||
$class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class);
|
||||
if (class_exists($class)) {
|
||||
$validate = new $class;
|
||||
} else {
|
||||
throw new ClassNotFoundException('class not exists:' . $class, $class);
|
||||
}
|
||||
}
|
||||
self::$instance[$name . $layer] = $validate;
|
||||
return $validate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实例化数据库
|
||||
* @param mixed $config 数据库配置
|
||||
* @return object
|
||||
*/
|
||||
public static function db($config = [])
|
||||
{
|
||||
return Db::connect($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 远程调用模块的操作方法 参数格式 [模块/控制器/]操作
|
||||
* @param string $url 调用地址
|
||||
* @param string|array $vars 调用参数 支持字符串和数组
|
||||
* @param string $layer 要调用的控制层名称
|
||||
* @param bool $appendSuffix 是否添加类名后缀
|
||||
* @return mixed
|
||||
*/
|
||||
public static function action($url, $vars = [], $layer = 'controller', $appendSuffix = false)
|
||||
{
|
||||
$info = pathinfo($url);
|
||||
$action = $info['basename'];
|
||||
$module = '.' != $info['dirname'] ? $info['dirname'] : Request::instance()->controller();
|
||||
$class = self::controller($module, $layer, $appendSuffix);
|
||||
if ($class) {
|
||||
if (is_scalar($vars)) {
|
||||
if (strpos($vars, '=')) {
|
||||
parse_str($vars, $vars);
|
||||
} else {
|
||||
$vars = [$vars];
|
||||
}
|
||||
}
|
||||
return App::invokeMethod([$class, $action . Config::get('action_suffix')], $vars);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串命名风格转换
|
||||
* type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
|
||||
* @param string $name 字符串
|
||||
* @param integer $type 转换类型
|
||||
* @return string
|
||||
*/
|
||||
public static function parseName($name, $type = 0)
|
||||
{
|
||||
if ($type) {
|
||||
return ucfirst(preg_replace_callback('/_([a-zA-Z])/', function ($match) {return strtoupper($match[1]);}, $name));
|
||||
} else {
|
||||
return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析应用类的类名
|
||||
* @param string $module 模块名
|
||||
* @param string $layer 层名 controller model ...
|
||||
* @param string $name 类名
|
||||
* @return string
|
||||
*/
|
||||
public static function parseClass($module, $layer, $name, $appendSuffix = false)
|
||||
{
|
||||
$name = str_replace(['/', '.'], '\\', $name);
|
||||
$array = explode('\\', $name);
|
||||
$class = self::parseName(array_pop($array), 1) . (App::$suffix || $appendSuffix ? ucfirst($layer) : '');
|
||||
$path = $array ? implode('\\', $array) . '\\' : '';
|
||||
return App::$namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $path . $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化类的实例
|
||||
* @return void
|
||||
*/
|
||||
public static function clearInstance()
|
||||
{
|
||||
self::$instance = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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;
|
||||
|
||||
use think\App;
|
||||
|
||||
class Log
|
||||
{
|
||||
const LOG = 'log';
|
||||
const ERROR = 'error';
|
||||
const INFO = 'info';
|
||||
const SQL = 'sql';
|
||||
const NOTICE = 'notice';
|
||||
const ALERT = 'alert';
|
||||
|
||||
// 日志信息
|
||||
protected static $log = [];
|
||||
// 配置参数
|
||||
protected static $config = [];
|
||||
// 日志类型
|
||||
protected static $type = ['log', 'error', 'info', 'sql', 'notice', 'alert'];
|
||||
// 日志写入驱动
|
||||
protected static $driver;
|
||||
// 通知发送驱动
|
||||
protected static $alarm;
|
||||
// 当前日志授权key
|
||||
protected static $key;
|
||||
|
||||
/**
|
||||
* 日志初始化
|
||||
* @return void
|
||||
*/
|
||||
public static function init($config = [])
|
||||
{
|
||||
$type = isset($config['type']) ? $config['type'] : 'File';
|
||||
$class = false !== strpos($type, '\\') ? $type : '\\think\\log\\driver\\' . ucwords($type);
|
||||
self::$config = $config;
|
||||
unset($config['type']);
|
||||
self::$driver = new $class($config);
|
||||
// 记录初始化信息
|
||||
App::$debug && Log::record('[ LOG ] INIT ' . $type . ': ' . var_export($config, true), 'info');
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知初始化
|
||||
* @return void
|
||||
*/
|
||||
public static function alarm($config = [])
|
||||
{
|
||||
$type = isset($config['type']) ? $config['type'] : 'Email';
|
||||
$class = false !== strpos($type, '\\') ? $type : '\\think\\log\\alarm\\' . ucwords($type);
|
||||
unset($config['type']);
|
||||
self::$alarm = new $class($config['alarm']);
|
||||
// 记录初始化信息
|
||||
App::$debug && Log::record('[ CACHE ] ALARM ' . $type . ': ' . var_export($config, true), 'info');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日志信息
|
||||
* @param string $type 信息类型
|
||||
* @return array
|
||||
*/
|
||||
public static function getLog($type = '')
|
||||
{
|
||||
return $type ? self::$log[$type] : self::$log;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录调试信息
|
||||
* @param mixed $msg 调试信息
|
||||
* @param string $type 信息类型
|
||||
* @return void
|
||||
*/
|
||||
public static function record($msg, $type = 'log')
|
||||
{
|
||||
self::$log[$type][] = $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空日志信息
|
||||
* @return void
|
||||
*/
|
||||
public static function clear()
|
||||
{
|
||||
self::$log = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前日志记录的授权key
|
||||
* @param string $key 授权key
|
||||
* @return void
|
||||
*/
|
||||
public static function key($key)
|
||||
{
|
||||
self::$key = $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查日志写入权限
|
||||
* @param array $config 当前日志配置参数
|
||||
* @return bool
|
||||
*/
|
||||
public static function check($config)
|
||||
{
|
||||
|
||||
if (self::$key && !empty($config['allow_key']) && !in_array(self::$key, $config['allow_key'])) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存调试信息
|
||||
* @return bool
|
||||
*/
|
||||
public static function save()
|
||||
{
|
||||
if (!empty(self::$log)) {
|
||||
if (is_null(self::$driver)) {
|
||||
self::init(Config::get('log'));
|
||||
}
|
||||
|
||||
if (!self::check(self::$config)) {
|
||||
// 检测日志写入权限
|
||||
return false;
|
||||
}
|
||||
$result = self::$driver->save(self::$log);
|
||||
|
||||
if ($result) {
|
||||
self::$log = [];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实时写入日志信息 并支持行为
|
||||
* @param mixed $msg 调试信息
|
||||
* @param string $type 信息类型
|
||||
* @return bool
|
||||
*/
|
||||
public static function write($msg, $type = 'log')
|
||||
{
|
||||
// 封装日志信息
|
||||
$log[$type][] = $msg;
|
||||
|
||||
// 监听log_write
|
||||
Hook::listen('log_write', $log);
|
||||
if (is_null(self::$driver)) {
|
||||
self::init(Config::get('log'));
|
||||
}
|
||||
// 写入日志
|
||||
return self::$driver->save($log);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送预警通知
|
||||
* @param mixed $msg 调试信息
|
||||
* @return void
|
||||
*/
|
||||
public static function send($msg)
|
||||
{
|
||||
self::$alarm && self::$alarm->send($msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态调用
|
||||
* @return void
|
||||
*/
|
||||
public static function __callStatic($method, $args)
|
||||
{
|
||||
if (in_array($method, self::$type)) {
|
||||
array_push($args, $method);
|
||||
return call_user_func_array('\\think\\Log::record', $args);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: zhangyajun <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think;
|
||||
|
||||
use think\paginator\Collection as PaginatorCollection;
|
||||
use think\Request;
|
||||
|
||||
abstract class Paginator
|
||||
{
|
||||
/** @var bool 是否为简洁模式 */
|
||||
protected $simple = false;
|
||||
|
||||
/** @var PaginatorCollection 数据集 */
|
||||
protected $items;
|
||||
|
||||
/** @var integer 当前页 */
|
||||
protected $currentPage;
|
||||
|
||||
/** @var integer 最后一页 */
|
||||
protected $lastPage;
|
||||
|
||||
/** @var integer|null 数据总数 */
|
||||
protected $total;
|
||||
|
||||
/** @var integer 每页的数量 */
|
||||
protected $listRows;
|
||||
|
||||
/** @var bool 是否有下一页 */
|
||||
protected $hasMore;
|
||||
|
||||
/** @var array 一些配置 */
|
||||
protected $options = [
|
||||
'var_page' => 'page',
|
||||
'path' => '/',
|
||||
'query' => [],
|
||||
'fragment' => ''
|
||||
];
|
||||
|
||||
public function __construct($items, $listRows, $currentPage = null, $simple = false, $total = null, $options = [])
|
||||
{
|
||||
$this->options = array_merge($this->options, $options);
|
||||
|
||||
$this->options['path'] = $this->options['path'] != '/' ? rtrim($this->options['path'], '/') : $this->options['path'];
|
||||
|
||||
$this->simple = $simple;
|
||||
$this->listRows = $listRows;
|
||||
|
||||
if ($simple) {
|
||||
if (!$items instanceof Collection) {
|
||||
$items = Collection::make($items);
|
||||
}
|
||||
$this->currentPage = $this->setCurrentPage($currentPage);
|
||||
$this->hasMore = count($items) > ($this->listRows);
|
||||
$items = $items->slice(0, $this->listRows);
|
||||
} else {
|
||||
$this->total = $total;
|
||||
$this->lastPage = (int)ceil($total / $listRows);
|
||||
$this->currentPage = $this->setCurrentPage($currentPage);
|
||||
$this->hasMore = $this->currentPage < $this->lastPage;
|
||||
}
|
||||
|
||||
$this->items = PaginatorCollection::make($items, $this);
|
||||
}
|
||||
|
||||
public function items()
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
|
||||
protected function setCurrentPage($currentPage)
|
||||
{
|
||||
if (!$this->simple && $currentPage > $this->lastPage) {
|
||||
return $this->lastPage > 0 ? $this->lastPage : 1;
|
||||
}
|
||||
|
||||
return $currentPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取页码对应的链接
|
||||
*
|
||||
* @param $page
|
||||
* @return string
|
||||
*/
|
||||
protected function url($page)
|
||||
{
|
||||
if ($page <= 0) {
|
||||
$page = 1;
|
||||
}
|
||||
|
||||
if (strpos($this->options['path'], '[PAGE]') === false) {
|
||||
$parameters = [$this->options['var_page'] => $page];
|
||||
$path = $this->options['path'];
|
||||
} else {
|
||||
$parameters = [];
|
||||
$path = str_replace('[PAGE]', $page, $this->options['path']);
|
||||
}
|
||||
if (count($this->options['query']) > 0) {
|
||||
$parameters = array_merge($this->options['query'], $parameters);
|
||||
}
|
||||
$url = $path;
|
||||
if (!empty($parameters)) {
|
||||
$url .= '?' . urldecode(http_build_query($parameters, null, '&'));
|
||||
}
|
||||
return $url . $this->buildFragment();
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动获取当前页码
|
||||
* @param string $varPage
|
||||
* @param int $default
|
||||
* @return int
|
||||
*/
|
||||
public static function getCurrentPage($varPage = 'page', $default = 1)
|
||||
{
|
||||
$page = Request::instance()->request($varPage);
|
||||
|
||||
if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int)$page >= 1) {
|
||||
return $page;
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动获取当前的path
|
||||
* @return string
|
||||
*/
|
||||
public static function getCurrentPath()
|
||||
{
|
||||
return Request::instance()->baseUrl();
|
||||
}
|
||||
|
||||
public function total()
|
||||
{
|
||||
if ($this->simple) {
|
||||
throw new \DomainException('not support total');
|
||||
}
|
||||
return $this->total;
|
||||
}
|
||||
|
||||
public function listRows()
|
||||
{
|
||||
return $this->listRows;
|
||||
}
|
||||
|
||||
public function currentPage()
|
||||
{
|
||||
return $this->currentPage;
|
||||
}
|
||||
|
||||
public function lastPage()
|
||||
{
|
||||
if ($this->simple) {
|
||||
throw new \DomainException('not support last');
|
||||
}
|
||||
return $this->lastPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据是否足够分页
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasPages()
|
||||
{
|
||||
return !($this->currentPage == 1 && !$this->hasMore);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一组分页链接
|
||||
*
|
||||
* @param int $start
|
||||
* @param int $end
|
||||
* @return array
|
||||
*/
|
||||
public function getUrlRange($start, $end)
|
||||
{
|
||||
$urls = [];
|
||||
|
||||
for ($page = $start; $page <= $end; $page++) {
|
||||
$urls[$page] = $this->url($page);
|
||||
}
|
||||
|
||||
return $urls;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置URL锚点
|
||||
*
|
||||
* @param string|null $fragment
|
||||
* @return $this
|
||||
*/
|
||||
public function fragment($fragment)
|
||||
{
|
||||
$this->options['fragment'] = $fragment;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加URL参数
|
||||
*
|
||||
* @param array|string $key
|
||||
* @param string|null $value
|
||||
* @return $this
|
||||
*/
|
||||
public function appends($key, $value = null)
|
||||
{
|
||||
if (!is_array($key)) {
|
||||
$queries = [$key => $value];
|
||||
} else {
|
||||
$queries = $key;
|
||||
}
|
||||
|
||||
foreach ($queries as $k => $v) {
|
||||
if ($k !== $this->options['var_page']) {
|
||||
$this->options['query'][$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构造锚点字符串
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function buildFragment()
|
||||
{
|
||||
return $this->options['fragment'] ? '#' . $this->options['fragment'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染分页html
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function render();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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;
|
||||
|
||||
use think\response\Json as JsonResponse;
|
||||
use think\response\Jsonp as JsonpResponse;
|
||||
use think\response\Redirect as RedirectResponse;
|
||||
use think\response\View as ViewResponse;
|
||||
use think\response\Xml as XmlResponse;
|
||||
|
||||
class Response
|
||||
{
|
||||
|
||||
// 原始数据
|
||||
protected $data;
|
||||
|
||||
// 当前的contentType
|
||||
protected $contentType = 'text/html';
|
||||
|
||||
// 字符集
|
||||
protected $charset = 'utf-8';
|
||||
|
||||
//状态
|
||||
protected $code = 200;
|
||||
|
||||
// 输出参数
|
||||
protected $options = [];
|
||||
// header参数
|
||||
protected $header = [];
|
||||
|
||||
protected $content = null;
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @access public
|
||||
* @param mixed $data 输出数据
|
||||
* @param int $code
|
||||
* @param array $header
|
||||
* @param array $options 输出参数
|
||||
*/
|
||||
public function __construct($data = '', $code = 200, array $header = [], $options = [])
|
||||
{
|
||||
$this->data($data);
|
||||
$this->header = $header;
|
||||
$this->code = $code;
|
||||
if (!empty($options)) {
|
||||
$this->options = array_merge($this->options, $options);
|
||||
}
|
||||
$this->contentType($this->contentType, $this->charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Response对象
|
||||
* @access public
|
||||
* @param mixed $data 输出数据
|
||||
* @param string $type 输出类型
|
||||
* @param int $code
|
||||
* @param array $header
|
||||
* @param array $options 输出参数
|
||||
* @return Response|JsonResponse|ViewResponse|XmlResponse|RedirectResponse|JsonpResponse
|
||||
*/
|
||||
public static function create($data = '', $type = '', $code = 200, array $header = [], $options = [])
|
||||
{
|
||||
$type = empty($type) ? 'null' : strtolower($type);
|
||||
|
||||
$class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst($type);
|
||||
if (class_exists($class)) {
|
||||
$response = new $class($data, $code, $header, $options);
|
||||
} else {
|
||||
$response = new static($data, $code, $header, $options);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送数据到客户端
|
||||
* @access public
|
||||
* @return mixed
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function send()
|
||||
{
|
||||
// 处理输出数据
|
||||
$data = $this->getContent();
|
||||
|
||||
// 监听response_data
|
||||
Hook::listen('response_data', $data, $this);
|
||||
|
||||
if (!headers_sent() && !empty($this->header)) {
|
||||
// 发送状态码
|
||||
http_response_code($this->code);
|
||||
// 发送头部信息
|
||||
foreach ($this->header as $name => $val) {
|
||||
header($name . ':' . $val);
|
||||
}
|
||||
}
|
||||
echo $data;
|
||||
|
||||
if (function_exists('fastcgi_finish_request')) {
|
||||
// 提高页面响应
|
||||
fastcgi_finish_request();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理数据
|
||||
* @access protected
|
||||
* @param mixed $data 要处理的数据
|
||||
* @return mixed
|
||||
*/
|
||||
protected function output($data)
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出的参数
|
||||
* @access public
|
||||
* @param mixed $options 输出参数
|
||||
* @return $this
|
||||
*/
|
||||
public function options($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 string|array $name 参数名
|
||||
* @param string $value 参数值
|
||||
* @return $this
|
||||
*/
|
||||
public function header($name, $value = null)
|
||||
{
|
||||
if (is_array($name)) {
|
||||
$this->header = array_merge($this->header, $name);
|
||||
} else {
|
||||
$this->header[$name] = $value;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送HTTP状态
|
||||
* @param integer $code 状态码
|
||||
* @return $this
|
||||
*/
|
||||
public function code($code)
|
||||
{
|
||||
$this->code = $code;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* LastModified
|
||||
* @param string $time
|
||||
* @return $this
|
||||
*/
|
||||
public function lastModified($time)
|
||||
{
|
||||
$this->header['Last-Modified'] = $time;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expires
|
||||
* @param string $time
|
||||
* @return $this
|
||||
*/
|
||||
public function expires($time)
|
||||
{
|
||||
$this->header['Expires'] = $time;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* ETag
|
||||
* @param string $eTag
|
||||
* @return $this
|
||||
*/
|
||||
public function eTag($eTag)
|
||||
{
|
||||
$this->header['ETag'] = $eTag;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面缓存控制
|
||||
* @param string $cache 状态码
|
||||
* @return $this
|
||||
*/
|
||||
public function cacheControl($cache)
|
||||
{
|
||||
$this->header['Cache-control'] = $cache;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面输出类型
|
||||
* @param string $contentType 输出类型
|
||||
* @param string $charset 输出编码
|
||||
* @return $this
|
||||
*/
|
||||
public function contentType($contentType, $charset = 'utf-8')
|
||||
{
|
||||
$this->header['Content-Type'] = $contentType . '; charset=' . $charset;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取头部信息
|
||||
* @param string $name 头部名称
|
||||
* @return mixed
|
||||
*/
|
||||
public function getHeader($name = '')
|
||||
{
|
||||
return !empty($name) ? $this->header[$name] : $this->header;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始数据
|
||||
* @return mixed
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输出数据
|
||||
* @return mixed
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
$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)));
|
||||
}
|
||||
|
||||
return (string) $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态码
|
||||
* @return integer
|
||||
*/
|
||||
public function getCode()
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,264 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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;
|
||||
|
||||
use think\App;
|
||||
use think\exception\ClassNotFoundException;
|
||||
|
||||
class Session
|
||||
{
|
||||
protected static $prefix = '';
|
||||
|
||||
/**
|
||||
* 设置或者获取session作用域(前缀)
|
||||
* @param string $prefix
|
||||
* @return string|void
|
||||
*/
|
||||
public static function prefix($prefix = '')
|
||||
{
|
||||
if (empty($prefix) && null !== $prefix) {
|
||||
return self::$prefix;
|
||||
} else {
|
||||
self::$prefix = $prefix;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* session初始化
|
||||
* @param array $config
|
||||
* @return void
|
||||
* @throws \think\Exception
|
||||
*/
|
||||
public static function init(array $config = [])
|
||||
{
|
||||
if (empty($config)) {
|
||||
$config = Config::get('session');
|
||||
}
|
||||
// 记录初始化信息
|
||||
App::$debug && Log::record('[ SESSION ] INIT ' . var_export($config, true), 'info');
|
||||
$isDoStart = false;
|
||||
if (isset($config['use_trans_sid'])) {
|
||||
ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0);
|
||||
}
|
||||
|
||||
// 启动session
|
||||
if (!empty($config['auto_start']) && PHP_SESSION_ACTIVE != session_status()) {
|
||||
ini_set('session.auto_start', 0);
|
||||
$isDoStart = true;
|
||||
}
|
||||
|
||||
if (isset($config['prefix'])) {
|
||||
self::$prefix = $config['prefix'];
|
||||
}
|
||||
if (isset($config['var_session_id']) && isset($_REQUEST[$config['var_session_id']])) {
|
||||
session_id($_REQUEST[$config['var_session_id']]);
|
||||
} elseif (isset($config['id']) && !empty($config['id'])) {
|
||||
session_id($config['id']);
|
||||
}
|
||||
if (isset($config['name'])) {
|
||||
session_name($config['name']);
|
||||
}
|
||||
if (isset($config['path'])) {
|
||||
session_save_path($config['path']);
|
||||
}
|
||||
if (isset($config['domain'])) {
|
||||
ini_set('session.cookie_domain', $config['domain']);
|
||||
}
|
||||
if (isset($config['expire'])) {
|
||||
ini_set('session.gc_maxlifetime', $config['expire']);
|
||||
ini_set('session.cookie_lifetime', $config['expire']);
|
||||
}
|
||||
|
||||
if (isset($config['use_cookies'])) {
|
||||
ini_set('session.use_cookies', $config['use_cookies'] ? 1 : 0);
|
||||
}
|
||||
if (isset($config['cache_limiter'])) {
|
||||
session_cache_limiter($config['cache_limiter']);
|
||||
}
|
||||
if (isset($config['cache_expire'])) {
|
||||
session_cache_expire($config['cache_expire']);
|
||||
}
|
||||
if (!empty($config['type'])) {
|
||||
// 读取session驱动
|
||||
$class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']);
|
||||
|
||||
// 检查驱动类
|
||||
if (!class_exists($class) || !session_set_save_handler(new $class($config))) {
|
||||
throw new ClassNotFoundException('error session handler:' . $class, $class);
|
||||
}
|
||||
}
|
||||
if ($isDoStart) {
|
||||
session_start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* session设置
|
||||
* @param string $name session名称
|
||||
* @param mixed $value session值
|
||||
* @param string|null $prefix 作用域(前缀)
|
||||
* @return void
|
||||
*/
|
||||
public static function set($name, $value = '', $prefix = null)
|
||||
{
|
||||
!isset($_SESSION) && self::init();
|
||||
$prefix = !is_null($prefix) ? $prefix : self::$prefix;
|
||||
if (strpos($name, '.')) {
|
||||
// 二维数组赋值
|
||||
list($name1, $name2) = explode('.', $name);
|
||||
if ($prefix) {
|
||||
$_SESSION[$prefix][$name1][$name2] = $value;
|
||||
} else {
|
||||
$_SESSION[$name1][$name2] = $value;
|
||||
}
|
||||
} elseif ($prefix) {
|
||||
$_SESSION[$prefix][$name] = $value;
|
||||
} else {
|
||||
$_SESSION[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* session获取
|
||||
* @param string $name session名称
|
||||
* @param string|null $prefix 作用域(前缀)
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get($name = '', $prefix = null)
|
||||
{
|
||||
!isset($_SESSION) && self::init();
|
||||
$prefix = !is_null($prefix) ? $prefix : self::$prefix;
|
||||
if ('' == $name) {
|
||||
// 获取全部的session
|
||||
$value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION;
|
||||
} elseif ($prefix) {
|
||||
// 获取session
|
||||
if (strpos($name, '.')) {
|
||||
list($name1, $name2) = explode('.', $name);
|
||||
$value = isset($_SESSION[$prefix][$name1][$name2]) ? $_SESSION[$prefix][$name1][$name2] : null;
|
||||
} else {
|
||||
$value = isset($_SESSION[$prefix][$name]) ? $_SESSION[$prefix][$name] : null;
|
||||
}
|
||||
} else {
|
||||
if (strpos($name, '.')) {
|
||||
list($name1, $name2) = explode('.', $name);
|
||||
$value = isset($_SESSION[$name1][$name2]) ? $_SESSION[$name1][$name2] : null;
|
||||
} else {
|
||||
$value = isset($_SESSION[$name]) ? $_SESSION[$name] : null;
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除session数据
|
||||
* @param string $name session名称
|
||||
* @param string|null $prefix 作用域(前缀)
|
||||
* @return void
|
||||
*/
|
||||
public static function delete($name, $prefix = null)
|
||||
{
|
||||
!isset($_SESSION) && self::init();
|
||||
$prefix = !is_null($prefix) ? $prefix : self::$prefix;
|
||||
if (strpos($name, '.')) {
|
||||
list($name1, $name2) = explode('.', $name);
|
||||
if ($prefix) {
|
||||
unset($_SESSION[$prefix][$name1][$name2]);
|
||||
} else {
|
||||
unset($_SESSION[$name1][$name2]);
|
||||
}
|
||||
} else {
|
||||
if ($prefix) {
|
||||
unset($_SESSION[$prefix][$name]);
|
||||
} else {
|
||||
unset($_SESSION[$name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空session数据
|
||||
* @param string|null $prefix 作用域(前缀)
|
||||
* @return void
|
||||
*/
|
||||
public static function clear($prefix = null)
|
||||
{
|
||||
!isset($_SESSION) && self::init();
|
||||
$prefix = !is_null($prefix) ? $prefix : self::$prefix;
|
||||
if ($prefix) {
|
||||
unset($_SESSION[$prefix]);
|
||||
} else {
|
||||
$_SESSION = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断session数据
|
||||
* @param string $name session名称
|
||||
* @param string|null $prefix
|
||||
* @return bool
|
||||
*/
|
||||
public static function has($name, $prefix = null)
|
||||
{
|
||||
!isset($_SESSION) && self::init();
|
||||
$prefix = !is_null($prefix) ? $prefix : self::$prefix;
|
||||
if (strpos($name, '.')) {
|
||||
// 支持数组
|
||||
list($name1, $name2) = explode('.', $name);
|
||||
return $prefix ? isset($_SESSION[$prefix][$name1][$name2]) : isset($_SESSION[$name1][$name2]);
|
||||
} else {
|
||||
return $prefix ? isset($_SESSION[$prefix][$name]) : isset($_SESSION[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动session
|
||||
* @return void
|
||||
*/
|
||||
public static function start()
|
||||
{
|
||||
session_start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁session
|
||||
* @return void
|
||||
*/
|
||||
public static function destroy()
|
||||
{
|
||||
if (!empty($_SESSION)) {
|
||||
$_SESSION = [];
|
||||
}
|
||||
session_unset();
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新生成session_id
|
||||
* @param bool $delete 是否删除关联会话文件
|
||||
* @return void
|
||||
*/
|
||||
private static function regenerate($delete = false)
|
||||
{
|
||||
session_regenerate_id($delete);
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停session
|
||||
* @return void
|
||||
*/
|
||||
public static function pause()
|
||||
{
|
||||
// 暂停session
|
||||
session_write_close();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,377 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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;
|
||||
|
||||
use think\App;
|
||||
use think\Cache;
|
||||
use think\Config;
|
||||
use think\Request;
|
||||
use think\Route;
|
||||
|
||||
class Url
|
||||
{
|
||||
/**
|
||||
* URL生成 支持路由反射
|
||||
* @param string $url URL表达式,
|
||||
* 格式:'[模块/控制器/操作]?参数1=值1&参数2=值2...@域名'
|
||||
* @控制器/操作?参数1=值1&参数2=值2...
|
||||
* \\命名空间类\\方法?参数1=值1&参数2=值2...
|
||||
* @param string|array $vars 传入的参数,支持数组和字符串
|
||||
* @param string|bool $suffix 伪静态后缀,默认为true表示获取配置值
|
||||
* @param boolean|string $domain 是否显示域名 或者直接传入域名
|
||||
* @return string
|
||||
*/
|
||||
public static function build($url = '', $vars = '', $suffix = true, $domain = false)
|
||||
{
|
||||
if (false === $domain && Config::get('url_domain_deploy')) {
|
||||
$domain = true;
|
||||
}
|
||||
// 解析URL
|
||||
$info = parse_url($url);
|
||||
$url = !empty($info['path']) ? $info['path'] : '';
|
||||
if (isset($info['fragment'])) {
|
||||
// 解析锚点
|
||||
$anchor = $info['fragment'];
|
||||
if (false !== strpos($anchor, '?')) {
|
||||
// 解析参数
|
||||
list($anchor, $info['query']) = explode('?', $anchor, 2);
|
||||
}
|
||||
if (false !== strpos($anchor, '@')) {
|
||||
// 解析域名
|
||||
list($anchor, $domain) = explode('@', $anchor, 2);
|
||||
}
|
||||
} elseif (strpos($url, '@')) {
|
||||
// 解析域名
|
||||
list($url, $domain) = explode('@', $url, 2);
|
||||
}
|
||||
|
||||
// 解析参数
|
||||
if (is_string($vars)) {
|
||||
// aaa=1&bbb=2 转换成数组
|
||||
parse_str($vars, $vars);
|
||||
}
|
||||
|
||||
if (isset($info['query'])) {
|
||||
// 解析地址里面参数 合并到vars
|
||||
parse_str($info['query'], $params);
|
||||
$vars = array_merge($params, $vars);
|
||||
}
|
||||
|
||||
// 获取路由别名
|
||||
$alias = self::getRouteAlias();
|
||||
// 检测路由
|
||||
if (0 !== strpos($url, '/') && isset($alias[$url]) && $match = self::getRouteUrl($alias[$url], $vars)) {
|
||||
// 处理路由规则中的特殊字符
|
||||
$url = str_replace('[--think--]', '', $match);
|
||||
} else {
|
||||
// 路由不存在 直接解析
|
||||
$url = self::parseUrl($url);
|
||||
}
|
||||
|
||||
// 检测URL绑定
|
||||
$type = Route::bind('type');
|
||||
if ($type) {
|
||||
$bind = Route::bind($type);
|
||||
if (0 === strpos($url, $bind)) {
|
||||
$url = substr($url, strlen($bind) + 1);
|
||||
}
|
||||
}
|
||||
// 还原URL分隔符
|
||||
$depr = Config::get('pathinfo_depr');
|
||||
$url = str_replace('/', $depr, $url);
|
||||
|
||||
// URL后缀
|
||||
$suffix = in_array($url, ['/', '']) ? '' : self::parseSuffix($suffix);
|
||||
// 锚点
|
||||
$anchor = !empty($anchor) ? '#' . $anchor : '';
|
||||
// 参数组装
|
||||
if (!empty($vars)) {
|
||||
// 添加参数
|
||||
if (Config::get('url_common_param')) {
|
||||
$vars = urldecode(http_build_query($vars));
|
||||
$url .= $suffix . '?' . $vars . $anchor;
|
||||
} else {
|
||||
foreach ($vars as $var => $val) {
|
||||
if ('' !== trim($val)) {
|
||||
$url .= $depr . $var . $depr . urlencode($val);
|
||||
}
|
||||
}
|
||||
$url .= $suffix . $anchor;
|
||||
}
|
||||
} else {
|
||||
$url .= $suffix . $anchor;
|
||||
}
|
||||
// 检测域名
|
||||
$domain = self::parseDomain($url, $domain);
|
||||
// URL组装
|
||||
$url = $domain . Request::instance()->root() . '/' . ltrim($url, '/');
|
||||
return $url;
|
||||
}
|
||||
|
||||
// 直接解析URL地址
|
||||
protected static function parseUrl($url)
|
||||
{
|
||||
$request = Request::instance();
|
||||
if (0 === strpos($url, '/')) {
|
||||
// 直接作为路由地址解析
|
||||
$url = substr($url, 1);
|
||||
} elseif (false !== strpos($url, '\\')) {
|
||||
// 解析到类
|
||||
$url = ltrim(str_replace('\\', '/', $url), '/');
|
||||
} elseif (0 === strpos($url, '@')) {
|
||||
// 解析到控制器
|
||||
$url = substr($url, 1);
|
||||
} else {
|
||||
// 解析到 模块/控制器/操作
|
||||
$module = $request->module();
|
||||
$module = $module ? $module . '/' : '';
|
||||
$controller = $request->controller();
|
||||
if ('' == $url) {
|
||||
// 空字符串输出当前的 模块/控制器/操作
|
||||
$url = $module . $controller . '/' . $request->action();
|
||||
} else {
|
||||
$path = explode('/', $url);
|
||||
$action = array_pop($path);
|
||||
$controller = empty($path) ? $controller : (Config::get('url_convert') ? Loader::parseName(array_pop($path)) : array_pop($path));
|
||||
$module = empty($path) ? $module : array_pop($path) . '/';
|
||||
$url = $module . $controller . '/' . $action;
|
||||
}
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
// 检测域名
|
||||
protected static function parseDomain(&$url, $domain)
|
||||
{
|
||||
if ($domain) {
|
||||
if (true === $domain) {
|
||||
// 自动判断域名
|
||||
$domain = $_SERVER['HTTP_HOST'];
|
||||
if (Config::get('url_domain_deploy')) {
|
||||
// 根域名
|
||||
$urlDomainRoot = Config::get('url_domain_root');
|
||||
$domains = Route::domain();
|
||||
$route_domain = array_keys($domains);
|
||||
foreach ($route_domain as $domain_prefix) {
|
||||
if (0 === strpos($domain_prefix, '*.') && strpos($domain, ltrim($domain_prefix, '*.')) !== false) {
|
||||
foreach ($domains as $key => $rule) {
|
||||
$rule = is_array($rule) ? $rule[0] : $rule;
|
||||
if (false === strpos($key, '*') && 0 === strpos($url, $rule)) {
|
||||
$url = ltrim($url, $rule);
|
||||
$domain = $key;
|
||||
// 生成对应子域名
|
||||
if (!empty($urlDomainRoot)) {
|
||||
$domain .= $urlDomainRoot;
|
||||
}
|
||||
break;
|
||||
} else if (false !== strpos($key, '*')) {
|
||||
if (!empty($urlDomainRoot)) {
|
||||
$domain .= $urlDomainRoot;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$domain .= strpos($domain, '.') ? '' : strstr($_SERVER['HTTP_HOST'], '.');
|
||||
}
|
||||
$domain = (self::isSsl() ? 'https://' : 'http://') . $domain;
|
||||
} else {
|
||||
$domain = '';
|
||||
}
|
||||
return $domain;
|
||||
}
|
||||
|
||||
// 检测路由规则中的变量是否有传入
|
||||
protected static function pattern($pattern, $vars)
|
||||
{
|
||||
foreach ($pattern as $key => $type) {
|
||||
if (1 == $type && !isset($vars[$key])) {
|
||||
// 变量未设置
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 解析URL后缀
|
||||
protected static function parseSuffix($suffix)
|
||||
{
|
||||
if ($suffix) {
|
||||
$suffix = true === $suffix ? Config::get('url_html_suffix') : $suffix;
|
||||
if ($pos = strpos($suffix, '|')) {
|
||||
$suffix = substr($suffix, 0, $pos);
|
||||
}
|
||||
}
|
||||
return (empty($suffix) || 0 === strpos($suffix, '.')) ? $suffix : '.' . $suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否SSL协议
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isSsl()
|
||||
{
|
||||
if (isset($_SERVER['HTTPS']) && ('1' == $_SERVER['HTTPS'] || 'on' == strtolower($_SERVER['HTTPS']))) {
|
||||
return true;
|
||||
} elseif (isset($_SERVER['SERVER_PORT']) && ('443' == $_SERVER['SERVER_PORT'])) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 匹配路由地址
|
||||
public static function getRouteUrl($alias, &$vars = [])
|
||||
{
|
||||
foreach ($alias as $key => $val) {
|
||||
list($url, $pattern, $param) = $val;
|
||||
// 解析安全替换
|
||||
if (strpos($url, '$')) {
|
||||
$url = str_replace('$', '[--think--]', $url);
|
||||
}
|
||||
// 检查变量匹配
|
||||
$array = $vars;
|
||||
$match = false;
|
||||
if ($pattern && self::pattern($pattern, $vars)) {
|
||||
foreach ($pattern as $key => $val) {
|
||||
if (isset($vars[$key])) {
|
||||
$url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key . '', '<' . $key . '>'], $vars[$key], $url);
|
||||
unset($array[$key]);
|
||||
} else {
|
||||
$url = str_replace(['[:' . $key . ']', '<' . $key . '?>'], '', $url);
|
||||
}
|
||||
}
|
||||
$match = true;
|
||||
}
|
||||
if (empty($pattern) && empty($param)) {
|
||||
// 没有任何变量
|
||||
return $url;
|
||||
} elseif ($match && (empty($param) || array_intersect_assoc($param, $array) == $param)) {
|
||||
// 存在变量定义
|
||||
$vars = array_diff_key($array, $param);
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 生成路由映射并缓存
|
||||
private static function getRouteAlias()
|
||||
{
|
||||
if ($item = Cache::get('think_route_map')) {
|
||||
return $item;
|
||||
}
|
||||
// 获取路由定义
|
||||
$rules = Route::getRules();
|
||||
foreach ($rules as $rule => $val) {
|
||||
if (!empty($val['routes'])) {
|
||||
foreach ($val['routes'] as $key => $route) {
|
||||
if (is_numeric($key)) {
|
||||
$key = array_shift($route);
|
||||
}
|
||||
if (is_array($route)) {
|
||||
$route = $route[0];
|
||||
}
|
||||
$param = [];
|
||||
if (is_array($route)) {
|
||||
$route = implode('\\', $route);
|
||||
} elseif ($route instanceof \Closure) {
|
||||
continue;
|
||||
} elseif (strpos($route, '?')) {
|
||||
list($route, $str) = explode('?', $route, 2);
|
||||
parse_str($str, $param);
|
||||
}
|
||||
$var = self::parseVar($rule . '/' . $key);
|
||||
$item[$route][] = [$rule . '/' . $key, $var, $param];
|
||||
}
|
||||
} else {
|
||||
$route = $val['route'];
|
||||
$param = [];
|
||||
if (is_array($route)) {
|
||||
$route = implode('\\', $route);
|
||||
} elseif ($route instanceof \Closure) {
|
||||
continue;
|
||||
} elseif (strpos($route, '?')) {
|
||||
list($route, $str) = explode('?', $route, 2);
|
||||
parse_str($str, $param);
|
||||
}
|
||||
$var = self::parseVar($rule);
|
||||
$item[$route][] = [$rule, $var, $param];
|
||||
}
|
||||
}
|
||||
|
||||
// 检测路由映射
|
||||
$maps = Route::map();
|
||||
foreach ($maps as $rule => $route) {
|
||||
$param = [];
|
||||
if (strpos($route, '?')) {
|
||||
list($route, $str) = explode('?', $route, 2);
|
||||
parse_str($str, $param);
|
||||
}
|
||||
$item[$route][] = [$rule, [], $param];
|
||||
}
|
||||
|
||||
// 检测路由别名
|
||||
$alias = Route::alias();
|
||||
foreach ($alias as $rule => $route) {
|
||||
$route = is_array($route) ? $route[0] : $route;
|
||||
$item[$route][] = [$rule, [], []];
|
||||
}
|
||||
!App::$debug && Cache::set('think_route_map', $item);
|
||||
return $item;
|
||||
}
|
||||
|
||||
// 分析路由规则中的变量
|
||||
private static function parseVar($rule)
|
||||
{
|
||||
// 检测是否设置了参数分隔符
|
||||
if ($depr = Config::get('url_params_depr')) {
|
||||
$rule = str_replace($depr, '/', $rule);
|
||||
}
|
||||
// 提取路由规则中的变量
|
||||
$var = [];
|
||||
foreach (explode('/', $rule) as $val) {
|
||||
$optional = false;
|
||||
if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) {
|
||||
foreach ($matches[1] as $name) {
|
||||
if (strpos($name, '?')) {
|
||||
$name = substr($name, 0, -1);
|
||||
$optional = true;
|
||||
} else {
|
||||
$optional = false;
|
||||
}
|
||||
$var[$name] = $optional ? 2 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 === strpos($val, '[:')) {
|
||||
// 可选参数
|
||||
$optional = true;
|
||||
$val = substr($val, 1, -1);
|
||||
}
|
||||
if (0 === strpos($val, ':')) {
|
||||
// URL变量
|
||||
$name = substr($val, 1);
|
||||
$var[$name] = $optional ? 2 : 1;
|
||||
}
|
||||
}
|
||||
return $var;
|
||||
}
|
||||
|
||||
// 清空路由别名缓存
|
||||
public static function clearAliasCache()
|
||||
{
|
||||
Cache::rm('think_route_map');
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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;
|
||||
|
||||
class View
|
||||
{
|
||||
// 视图实例
|
||||
protected static $instance;
|
||||
// 模板引擎实例
|
||||
public $engine;
|
||||
// 模板变量
|
||||
protected $data = [];
|
||||
// 视图输出替换
|
||||
protected $replace = [];
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @access public
|
||||
* @param array $engine 模板引擎参数
|
||||
* @param array $replace 字符串替换参数
|
||||
*/
|
||||
public function __construct($engine = [], $replace = [])
|
||||
{
|
||||
// 初始化模板引擎
|
||||
$this->engine((array) $engine);
|
||||
$this->replace = $replace;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化视图
|
||||
* @access public
|
||||
* @param array $engine 模板引擎参数
|
||||
* @param array $replace 字符串替换参数
|
||||
* @return object
|
||||
*/
|
||||
public static function instance($engine = [], $replace = [])
|
||||
{
|
||||
if (is_null(self::$instance)) {
|
||||
self::$instance = new self($engine, $replace);
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模板变量赋值
|
||||
* @access public
|
||||
* @param mixed $name 变量名
|
||||
* @param mixed $value 变量值
|
||||
* @return $this
|
||||
*/
|
||||
public function assign($name, $value = '')
|
||||
{
|
||||
if (is_array($name)) {
|
||||
$this->data = array_merge($this->data, $name);
|
||||
return $this;
|
||||
} else {
|
||||
$this->data[$name] = $value;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前模板解析的引擎
|
||||
* @access public
|
||||
* @param array|string $options 引擎参数
|
||||
* @return $this
|
||||
*/
|
||||
public function engine($options = [])
|
||||
{
|
||||
if (is_string($options)) {
|
||||
$type = $options;
|
||||
$options = [];
|
||||
} else {
|
||||
$type = !empty($options['type']) ? $options['type'] : 'Think';
|
||||
}
|
||||
|
||||
$class = false !== strpos($type, '\\') ? $type : '\\think\\view\\driver\\' . ucfirst($type);
|
||||
if (isset($options['type'])) {
|
||||
unset($options['type']);
|
||||
}
|
||||
$this->engine = new $class($options);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析和获取模板内容 用于输出
|
||||
* @param string $template 模板文件名或者内容
|
||||
* @param array $vars 模板输出变量
|
||||
* @param array $replace 替换内容
|
||||
* @param array $config 模板参数
|
||||
* @param bool $renderContent 是否渲染内容
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function fetch($template = '', $vars = [], $replace = [], $config = [], $renderContent = false)
|
||||
{
|
||||
// 模板变量
|
||||
$vars = array_merge($this->data, $vars);
|
||||
|
||||
// 页面缓存
|
||||
ob_start();
|
||||
ob_implicit_flush(0);
|
||||
|
||||
// 渲染输出
|
||||
$method = $renderContent ? 'display' : 'fetch';
|
||||
$this->engine->$method($template, $vars, $config);
|
||||
|
||||
// 获取并清空缓存
|
||||
$content = ob_get_clean();
|
||||
// 内容过滤标签
|
||||
Hook::listen('view_filter', $content);
|
||||
// 允许用户自定义模板的字符串替换
|
||||
$replace = array_merge($this->replace, $replace);
|
||||
if (!empty($replace)) {
|
||||
$content = strtr($content, $replace);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 视图内容替换
|
||||
* @access public
|
||||
* @param string|array $content 被替换内容(支持批量替换)
|
||||
* @param string $replace 替换内容
|
||||
* @return $this
|
||||
*/
|
||||
public function replace($content, $replace = '')
|
||||
{
|
||||
if (is_array($content)) {
|
||||
$this->replace = array_merge($this->replace, $content);
|
||||
} else {
|
||||
$this->replace[$content] = $replace;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染内容输出
|
||||
* @access public
|
||||
* @param string $content 内容
|
||||
* @param array $vars 模板输出变量
|
||||
* @param array $replace 替换内容
|
||||
* @param array $config 模板参数
|
||||
* @return mixed
|
||||
*/
|
||||
public function display($content, $vars = [], $replace = [], $config = [])
|
||||
{
|
||||
return $this->fetch($content, $vars, $replace, $config, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 模板变量赋值
|
||||
* @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]);
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\cache\driver;
|
||||
|
||||
use think\Cache;
|
||||
use think\Exception;
|
||||
|
||||
/**
|
||||
* Apc缓存驱动
|
||||
* @author liu21st <liu21st@gmail.com>
|
||||
*/
|
||||
class Apc
|
||||
{
|
||||
protected $options = [
|
||||
'expire' => 0,
|
||||
'prefix' => '',
|
||||
];
|
||||
/*****************************
|
||||
需要支持apc_cli模式
|
||||
******************************/
|
||||
/**
|
||||
* 架构函数
|
||||
* @param array $options 缓存参数
|
||||
* @throws Exception
|
||||
* @access public
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
if (!function_exists('apc_cache_info')) {
|
||||
throw new \BadFunctionCallException('not support: Apc');
|
||||
}
|
||||
if (!empty($options)) {
|
||||
$this->options = array_merge($this->options, $options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
return apc_fetch($this->options['prefix'] . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @param mixed $value 存储数据
|
||||
* @param integer $expire 有效时间(秒)
|
||||
* @return bool
|
||||
*/
|
||||
public function set($name, $value, $expire = null)
|
||||
{
|
||||
if (is_null($expire)) {
|
||||
$expire = $this->options['expire'];
|
||||
}
|
||||
$name = $this->options['prefix'] . $name;
|
||||
return apc_store($name, $value, $expire);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return bool|\string[]
|
||||
*/
|
||||
public function rm($name)
|
||||
{
|
||||
return apc_delete($this->options['prefix'] . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
return apc_clear_cache('user');
|
||||
}
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\cache\driver;
|
||||
|
||||
use think\Cache;
|
||||
|
||||
/**
|
||||
* 文件类型缓存类
|
||||
* @author liu21st <liu21st@gmail.com>
|
||||
*/
|
||||
class File
|
||||
{
|
||||
|
||||
protected $options = [
|
||||
'expire' => 0,
|
||||
'cache_subdir' => false,
|
||||
'path_level' => 1,
|
||||
'prefix' => '',
|
||||
'path' => CACHE_PATH,
|
||||
'data_compress' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
if (!empty($options)) {
|
||||
$this->options = array_merge($this->options, $options);
|
||||
}
|
||||
if (substr($this->options['path'], -1) != '/') {
|
||||
$this->options['path'] .= '/';
|
||||
}
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化检查
|
||||
* @access private
|
||||
* @return boolean
|
||||
*/
|
||||
private function init()
|
||||
{
|
||||
// 创建项目缓存目录
|
||||
if (!is_dir($this->options['path'])) {
|
||||
if (mkdir($this->options['path'], 0755, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得变量的存储文件名
|
||||
* @access private
|
||||
* @param string $name 缓存变量名
|
||||
* @return string
|
||||
*/
|
||||
private function filename($name)
|
||||
{
|
||||
$name = md5($name);
|
||||
if ($this->options['cache_subdir']) {
|
||||
// 使用子目录
|
||||
$dir = '';
|
||||
$len = $this->options['path_level'];
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
$dir .= $name{$i} . '/';
|
||||
}
|
||||
if (!is_dir($this->options['path'] . $dir)) {
|
||||
mkdir($this->options['path'] . $dir, 0755, true);
|
||||
}
|
||||
$filename = $dir . $this->options['prefix'] . $name . '.php';
|
||||
} else {
|
||||
$filename = $this->options['prefix'] . $name . '.php';
|
||||
}
|
||||
return $this->options['path'] . $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
$filename = $this->filename($name);
|
||||
if (!is_file($filename)) {
|
||||
return false;
|
||||
}
|
||||
$content = file_get_contents($filename);
|
||||
if (false !== $content) {
|
||||
$expire = (int) substr($content, 8, 12);
|
||||
if (0 != $expire && time() > filemtime($filename) + $expire) {
|
||||
//缓存过期删除缓存文件
|
||||
$this->unlink($filename);
|
||||
return false;
|
||||
}
|
||||
$content = substr($content, 20, -3);
|
||||
if ($this->options['data_compress'] && function_exists('gzcompress')) {
|
||||
//启用数据压缩
|
||||
$content = gzuncompress($content);
|
||||
}
|
||||
$content = unserialize($content);
|
||||
return $content;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @param mixed $value 存储数据
|
||||
* @param int $expire 有效时间 0为永久
|
||||
* @return boolean
|
||||
*/
|
||||
public function set($name, $value, $expire = null)
|
||||
{
|
||||
if (is_null($expire)) {
|
||||
$expire = $this->options['expire'];
|
||||
}
|
||||
$filename = $this->filename($name);
|
||||
$data = serialize($value);
|
||||
if ($this->options['data_compress'] && function_exists('gzcompress')) {
|
||||
//数据压缩
|
||||
$data = gzcompress($data, 3);
|
||||
}
|
||||
$data = "<?php\n//" . sprintf('%012d', $expire) . $data . "\n?>";
|
||||
$result = file_put_contents($filename, $data);
|
||||
if ($result) {
|
||||
clearstatcache();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return boolean
|
||||
*/
|
||||
public function rm($name)
|
||||
{
|
||||
return $this->unlink($this->filename($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$fileLsit = (array) glob($this->options['path'] . '*');
|
||||
foreach ($fileLsit as $path) {
|
||||
is_file($path) && unlink($path);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断文件是否存在后,删除
|
||||
* @param $path
|
||||
* @return bool
|
||||
* @author byron sampson <xiaobo.sun@qq.com>
|
||||
* @return boolean
|
||||
*/
|
||||
private function unlink($path)
|
||||
{
|
||||
return is_file($path) && unlink($path);
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\cache\driver;
|
||||
|
||||
use think\Cache;
|
||||
|
||||
/**
|
||||
* 文件类型缓存类
|
||||
* @author liu21st <liu21st@gmail.com>
|
||||
*/
|
||||
class Lite
|
||||
{
|
||||
protected $options = [
|
||||
'prefix' => '',
|
||||
'path' => '',
|
||||
'expire' => 0, // 等于 10*365*24*3600(10年)
|
||||
];
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @access public
|
||||
*
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
if (!empty($options)) {
|
||||
$this->options = array_merge($this->options, $options);
|
||||
}
|
||||
if (substr($this->options['path'], -1) != '/') {
|
||||
$this->options['path'] .= '/';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得变量的存储文件名
|
||||
* @access private
|
||||
* @param string $name 缓存变量名
|
||||
* @return string
|
||||
*/
|
||||
private function filename($name)
|
||||
{
|
||||
return $this->options['path'] . $this->options['prefix'] . md5($name) . '.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
$filename = $this->filename($name);
|
||||
if (is_file($filename)) {
|
||||
// 判断是否过期
|
||||
$mtime = filemtime($filename);
|
||||
if ($mtime < time()) {
|
||||
// 清除已经过期的文件
|
||||
unlink($filename);
|
||||
return false;
|
||||
}
|
||||
return include $filename;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @param mixed $value 存储数据
|
||||
* @param int $expire 有效时间 0为永久
|
||||
* @return bool
|
||||
*/
|
||||
public function set($name, $value, $expire = null)
|
||||
{
|
||||
if (is_null($expire)) {
|
||||
$expire = $this->options['expire'];
|
||||
}
|
||||
// 模拟永久
|
||||
if (0 === $expire) {
|
||||
$expire = 10 * 365 * 24 * 3600;
|
||||
}
|
||||
$filename = $this->filename($name);
|
||||
$ret = file_put_contents($filename, ("<?php return " . var_export($value, true) . ";"));
|
||||
// 通过设置修改时间实现有效期
|
||||
if ($ret) {
|
||||
touch($filename, time() + $expire);
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return boolean
|
||||
*/
|
||||
public function rm($name)
|
||||
{
|
||||
return unlink($this->filename($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
* @access public
|
||||
* @return bool
|
||||
* @internal param string $name 缓存变量名
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$filename = $this->filename('*');
|
||||
array_map("unlink", glob($filename));
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\cache\driver;
|
||||
|
||||
use think\Cache;
|
||||
use think\Exception;
|
||||
|
||||
class Memcache
|
||||
{
|
||||
protected $handler = null;
|
||||
protected $options = [
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 11211,
|
||||
'expire' => 0,
|
||||
'timeout' => 0, // 超时时间(单位:毫秒)
|
||||
'persistent' => true,
|
||||
'prefix' => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @param array $options 缓存参数
|
||||
* @access public
|
||||
* @throws \BadFunctionCallException
|
||||
*/
|
||||
public function __construct($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 = explode(',', $this->options['host']);
|
||||
$ports = explode(',', $this->options['port']);
|
||||
if (empty($ports[0])) {
|
||||
$ports[0] = 11211;
|
||||
}
|
||||
// 建立连接
|
||||
foreach ((array) $hosts as $i => $host) {
|
||||
$port = isset($ports[$i]) ? $ports[$i] : $ports[0];
|
||||
$this->options['timeout'] > 0 ?
|
||||
$this->handler->addServer($host, $port, $this->options['persistent'], 1, $this->options['timeout']) :
|
||||
$this->handler->addServer($host, $port, $this->options['persistent'], 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
return $this->handler->get($this->options['prefix'] . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @param mixed $value 存储数据
|
||||
* @param integer $expire 有效时间(秒)
|
||||
* @return bool
|
||||
*/
|
||||
public function set($name, $value, $expire = null)
|
||||
{
|
||||
if (is_null($expire)) {
|
||||
$expire = $this->options['expire'];
|
||||
}
|
||||
$name = $this->options['prefix'] . $name;
|
||||
if ($this->handler->set($name, $value, 0, $expire)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
* @param string $name 缓存变量名
|
||||
* @param bool|false $ttl
|
||||
* @return bool
|
||||
*/
|
||||
public function rm($name, $ttl = false)
|
||||
{
|
||||
$name = $this->options['prefix'] . $name;
|
||||
return false === $ttl ?
|
||||
$this->handler->delete($name) :
|
||||
$this->handler->delete($name, $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
return $this->handler->flush();
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\cache\driver;
|
||||
|
||||
use think\Cache;
|
||||
use think\Exception;
|
||||
|
||||
class Memcached
|
||||
{
|
||||
protected $handler = null;
|
||||
protected $options = [
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 11211,
|
||||
'expire' => 0,
|
||||
'timeout' => 0, // 超时时间(单位:毫秒)
|
||||
'prefix' => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @param array $options 缓存参数
|
||||
* @access public
|
||||
*/
|
||||
public function __construct($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 ($this->options['timeout'] > 0) {
|
||||
$this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->options['timeout']);
|
||||
}
|
||||
// 支持集群
|
||||
$hosts = explode(',', $this->options['host']);
|
||||
$ports = explode(',', $this->options['port']);
|
||||
if (empty($ports[0])) {
|
||||
$ports[0] = 11211;
|
||||
}
|
||||
// 建立连接
|
||||
$servers = [];
|
||||
foreach ((array) $hosts as $i => $host) {
|
||||
$servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1];
|
||||
}
|
||||
$this->handler->addServers($servers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
return $this->handler->get($this->options['prefix'] . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @param mixed $value 存储数据
|
||||
* @param integer $expire 有效时间(秒)
|
||||
* @return bool
|
||||
*/
|
||||
public function set($name, $value, $expire = null)
|
||||
{
|
||||
if (is_null($expire)) {
|
||||
$expire = $this->options['expire'];
|
||||
}
|
||||
$name = $this->options['prefix'] . $name;
|
||||
$expire = 0 == $expire ? 0 : time() + $expire;
|
||||
if ($this->handler->set($name, $value, $expire)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
* @param string $name 缓存变量名
|
||||
* @param bool|false $ttl
|
||||
* @return bool
|
||||
*/
|
||||
public function rm($name, $ttl = false)
|
||||
{
|
||||
$name = $this->options['prefix'] . $name;
|
||||
return false === $ttl ?
|
||||
$this->handler->delete($name) :
|
||||
$this->handler->delete($name, $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
return $this->handler->flush();
|
||||
}
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\cache\driver;
|
||||
|
||||
use think\Cache;
|
||||
use think\Exception;
|
||||
|
||||
/**
|
||||
* Redis缓存驱动,适合单机部署、有前端代理实现高可用的场景,性能最好
|
||||
* 有需要在业务层实现读写分离、或者使用RedisCluster的需求,请使用Redisd驱动
|
||||
*
|
||||
* 要求安装phpredis扩展:https://github.com/nicolasff/phpredis
|
||||
* @author 尘缘 <130775@qq.com>
|
||||
*/
|
||||
class Redis
|
||||
{
|
||||
protected $handler = null;
|
||||
protected $options = [
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 6379,
|
||||
'password' => '',
|
||||
'timeout' => 0,
|
||||
'expire' => 0,
|
||||
'persistent' => false,
|
||||
'prefix' => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @param array $options 缓存参数
|
||||
* @access public
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
if (!extension_loaded('redis')) {
|
||||
throw new \BadFunctionCallException('not support: redis');
|
||||
}
|
||||
if (!empty($options)) {
|
||||
$this->options = array_merge($this->options, $options);
|
||||
}
|
||||
$func = $this->options['persistent'] ? 'pconnect' : 'connect';
|
||||
$this->handler = new \Redis;
|
||||
$this->handler->$func($this->options['host'], $this->options['port'], $this->options['timeout']);
|
||||
|
||||
if ('' != $this->options['password']) {
|
||||
$this->handler->auth($this->options['password']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
$value = $this->handler->get($this->options['prefix'] . $name);
|
||||
$jsonData = json_decode($value, true);
|
||||
// 检测是否为JSON数据 true 返回JSON解析数组, false返回源数据 byron sampson<xiaobo.sun@qq.com>
|
||||
return (null === $jsonData) ? $value : $jsonData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @param mixed $value 存储数据
|
||||
* @param integer $expire 有效时间(秒)
|
||||
* @return boolean
|
||||
*/
|
||||
public function set($name, $value, $expire = null)
|
||||
{
|
||||
if (is_null($expire)) {
|
||||
$expire = $this->options['expire'];
|
||||
}
|
||||
$name = $this->options['prefix'] . $name;
|
||||
//对数组/对象数据进行缓存处理,保证数据完整性 byron sampson<xiaobo.sun@qq.com>
|
||||
$value = (is_object($value) || is_array($value)) ? json_encode($value) : $value;
|
||||
if (is_int($expire) && $expire) {
|
||||
$result = $this->handler->setex($name, $expire, $value);
|
||||
} else {
|
||||
$result = $this->handler->set($name, $value);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return boolean
|
||||
*/
|
||||
public function rm($name)
|
||||
{
|
||||
return $this->handler->delete($this->options['prefix'] . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
return $this->handler->flushDB();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回句柄对象,可执行其它高级方法
|
||||
*
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function handler()
|
||||
{
|
||||
return $this->handler;
|
||||
}
|
||||
}
|
||||
+325
@@ -0,0 +1,325 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\cache\driver;
|
||||
|
||||
use think\App;
|
||||
use think\Cache;
|
||||
use think\Exception;
|
||||
use think\Log;
|
||||
|
||||
/**
|
||||
配置参数:
|
||||
'cache' => [
|
||||
'type' => 'Redisd'
|
||||
'host' => 'A:6379,B:6379', //redis服务器ip,多台用逗号隔开;读写分离开启时,默认写A,当A主挂时,再尝试写B
|
||||
'slave' => 'B:6379,C:6379', //redis服务器ip,多台用逗号隔开;读写分离开启时,所有IP随机读,其中一台挂时,尝试读其它节点,可以配置权重
|
||||
'port' => 6379, //默认的端口号
|
||||
'password' => '', //AUTH认证密码,当redis服务直接暴露在外网时推荐
|
||||
'timeout' => 10, //连接超时时间
|
||||
'expire' => false, //默认过期时间,默认为永不过期
|
||||
'prefix' => '', //缓存前缀,不宜过长
|
||||
'persistent' => false, //是否长连接 false=短连接,推荐长连接
|
||||
],
|
||||
|
||||
单例获取:
|
||||
$redis = \think\Cache::connect(Config::get('cache'));
|
||||
$redis->master(true)->setnx('key');
|
||||
$redis->master(false)->get('key');
|
||||
*/
|
||||
|
||||
/**
|
||||
* ThinkPHP Redis简单主从实现的高可用方案
|
||||
*
|
||||
* 扩展依赖:https://github.com/phpredis/phpredis
|
||||
*
|
||||
* 一主一从的实践经验
|
||||
* 1, A、B为主从,正常情况下,A写,B读,通过异步同步到B(或者双写,性能有损失)
|
||||
* 2, B挂,则读写均落到A
|
||||
* 3, A挂,则尝试升级B为主,并断开主从尝试写入(要求开启slave-read-only no)
|
||||
* 4, 手工恢复A,并加入B的从
|
||||
*
|
||||
* 优化建议
|
||||
* 1,key不宜过长,value过大时请自行压缩
|
||||
* 2,gzcompress在php7下有兼容问题
|
||||
*
|
||||
* @todo
|
||||
* 1, 增加对redisCluster的兼容
|
||||
* 2, 增加tp5下的单元测试
|
||||
*
|
||||
* @author 尘缘 <130775@qq.com>
|
||||
*/
|
||||
class Redisd
|
||||
{
|
||||
protected static $redis_rw_handler;
|
||||
protected static $redis_err_pool;
|
||||
protected $handler = null;
|
||||
|
||||
protected $options = [
|
||||
'host' => '127.0.0.1',
|
||||
'slave' => '',
|
||||
'port' => 6379,
|
||||
'password' => '',
|
||||
'timeout' => 10,
|
||||
'expire' => false,
|
||||
'persistent' => false,
|
||||
'prefix' => '',
|
||||
'serialize' => \Redis::SERIALIZER_PHP,
|
||||
];
|
||||
|
||||
/**
|
||||
* 为了在单次php请求中复用redis连接,第一次获取的options会被缓存,第二次使用不同的$options,将会无效
|
||||
*
|
||||
* @param array $options 缓存参数
|
||||
* @access public
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
if (!extension_loaded('redis')) {
|
||||
throw new \BadFunctionCallException('not support: redis');
|
||||
}
|
||||
|
||||
$this->options = $options = array_merge($this->options, $options);
|
||||
$this->options['func'] = $options['persistent'] ? 'pconnect' : 'connect';
|
||||
|
||||
$host = explode(",", trim($this->options['host'], ","));
|
||||
$host = array_map("trim", $host);
|
||||
$slave = explode(",", trim($this->options['slave'], ","));
|
||||
$slave = array_map("trim", $slave);
|
||||
|
||||
$this->options["server_slave"] = empty($slave) ? $host : $slave;
|
||||
$this->options["servers"] = count($slave);
|
||||
$this->options["server_master"] = array_shift($host);
|
||||
$this->options["server_master_failover"] = $host;
|
||||
}
|
||||
|
||||
/**
|
||||
* 主从选择器,配置多个Host则自动启用读写分离,默认主写,随机从读
|
||||
* 随机从读的场景适合读频繁,且php与redis从位于单机的架构,这样可以减少网络IO
|
||||
* 一致Hash适合超高可用,跨网络读取,且从节点较多的情况,本业务不考虑该需求
|
||||
*
|
||||
* @access public
|
||||
* @param bool $master true 默认主写
|
||||
* @return Redisd
|
||||
*/
|
||||
public function master($master = true)
|
||||
{
|
||||
if (isset(self::$redis_rw_handler[$master])) {
|
||||
$this->handler = self::$redis_rw_handler[$master];
|
||||
return $this;
|
||||
}
|
||||
|
||||
//如果不为主,则从配置的host剔除主,并随机读从,失败以后再随机选择从
|
||||
//另外一种方案是根据key的一致性hash选择不同的node,但读写频繁的业务中可能打开大量的文件句柄
|
||||
if (!$master && $this->options["servers"] > 1) {
|
||||
shuffle($this->options["server_slave"]);
|
||||
$host = array_shift($this->options["server_slave"]);
|
||||
} else {
|
||||
$host = $this->options["server_master"];
|
||||
}
|
||||
|
||||
$this->handler = new \Redis();
|
||||
$func = $this->options['func'];
|
||||
|
||||
$parse = parse_url($host);
|
||||
$host = isset($parse['host']) ? $parse['host'] : $host;
|
||||
$port = isset($parse['host']) ? $parse['port'] : $this->options['port'];
|
||||
|
||||
//发生错误则摘掉当前节点
|
||||
try {
|
||||
$result = $this->handler->$func($host, $port, $this->options['timeout']);
|
||||
if (false === $result) {
|
||||
$this->handler->getLastError();
|
||||
}
|
||||
|
||||
if (null != $this->options['password']) {
|
||||
$this->handler->auth($this->options['password']);
|
||||
}
|
||||
|
||||
$this->handler->setOption(\Redis::OPT_SERIALIZER, $this->options['serialize']);
|
||||
if (strlen($this->options['prefix'])) {
|
||||
$this->handler->setOption(\Redis::OPT_PREFIX, $this->options['prefix']);
|
||||
}
|
||||
|
||||
App::$debug && Log::record("[ CACHE ] INIT Redisd : {$host}:{$port} master->" . var_export($master, true), Log::ALERT);
|
||||
} catch (\RedisException $e) {
|
||||
//phpredis throws a RedisException object if it can't reach the Redis server.
|
||||
//That can happen in case of connectivity issues, if the Redis service is down, or if the redis host is overloaded.
|
||||
//In any other problematic case that does not involve an unreachable server
|
||||
//(such as a key not existing, an invalid command, etc), phpredis will return FALSE.
|
||||
|
||||
Log::record(sprintf("redisd->%s:%s:%s:%s", $master ? "master" : "salve", $host, $port, $e->getMessage()), Log::ALERT);
|
||||
|
||||
//主节点挂了以后,尝试连接主备,断开主备的主从连接进行升主
|
||||
if ($master) {
|
||||
if (!count($this->options["server_master_failover"])) {
|
||||
throw new Exception("redisd master: no more server_master_failover. {$host}:{$port} : " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->options["server_master"] = array_shift($this->options["server_master_failover"]);
|
||||
$this->master();
|
||||
|
||||
Log::record(sprintf("master is down, try server_master_failover : %s", $this->options["server_master"]), Log::ERROR);
|
||||
|
||||
//如果是slave,断开主从升主,需要手工同步新主的数据到旧主上
|
||||
//目前这块的逻辑未经过严格测试
|
||||
//$this->handler->slaveof();
|
||||
} else {
|
||||
//尝试failover,如果有其它节点则进行其它节点的尝试
|
||||
foreach ($this->options["server_slave"] as $k => $v) {
|
||||
if (trim($v) == trim($host)) {
|
||||
unset($this->options["server_slave"][$k]);
|
||||
}
|
||||
}
|
||||
|
||||
//如果无可用节点,则抛出异常
|
||||
if (!count($this->options["server_slave"])) {
|
||||
Log::record("已无可用Redis读节点", Log::ERROR);
|
||||
throw new Exception("redisd slave: no more server_slave. {$host}:{$port} : " . $e->getMessage());
|
||||
return false;
|
||||
} else {
|
||||
Log::record("salve {$host}:{$port} is down, try another one.", Log::ALERT);
|
||||
return $this->master(false);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
throw new Exception($e->getMessage(), $e->getCode());
|
||||
}
|
||||
|
||||
self::$redis_rw_handler[$master] = $this->handler;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取缓存
|
||||
*
|
||||
* @access public
|
||||
* @param string $name 缓存key
|
||||
* @param bool $master 指定主从节点,可以从主节点获取结果
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($name, $master = false)
|
||||
{
|
||||
$this->master($master);
|
||||
|
||||
try {
|
||||
$value = $this->handler->get($name);
|
||||
} catch (\RedisException $e) {
|
||||
unset(self::$redis_rw_handler[0]);
|
||||
|
||||
$this->master();
|
||||
return $this->get($name);
|
||||
} catch (\Exception $e) {
|
||||
Log::record($e->getMessage(), Log::ERROR);
|
||||
}
|
||||
|
||||
return isset($value) ? $value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入缓存
|
||||
*
|
||||
* @access public
|
||||
* @param string $name 缓存key
|
||||
* @param mixed $value 缓存value
|
||||
* @param integer $expire 过期时间,单位秒
|
||||
* @return boolen
|
||||
*/
|
||||
public function set($name, $value, $expire = null)
|
||||
{
|
||||
$this->master(true);
|
||||
|
||||
if (is_null($expire)) {
|
||||
$expire = $this->options['expire'];
|
||||
}
|
||||
|
||||
try {
|
||||
if (null === $value) {
|
||||
return $this->handler->delete($name);
|
||||
}
|
||||
|
||||
if (is_int($expire) && $expire) {
|
||||
$result = $this->handler->setex($name, $expire, $value);
|
||||
} else {
|
||||
$result = $this->handler->set($name, $value);
|
||||
}
|
||||
} catch (\RedisException $e) {
|
||||
unset(self::$redis_rw_handler[1]);
|
||||
|
||||
$this->master(true);
|
||||
return $this->set($name, $value, $expire);
|
||||
} catch (\Exception $e) {
|
||||
Log::record($e->getMessage());
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
*
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return boolen
|
||||
*/
|
||||
public function rm($name)
|
||||
{
|
||||
$this->master(true);
|
||||
return $this->handler->delete($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
*
|
||||
* @access public
|
||||
* @return boolen
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->master(true);
|
||||
return $this->handler->flushDB();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回句柄对象,可执行其它高级方法
|
||||
* 需要先执行 $redis->master() 连接到 DB
|
||||
*
|
||||
* @access public
|
||||
* @param bool $master 指定主从节点,可以从主节点获取结果
|
||||
* @return \Redis
|
||||
*/
|
||||
public function handler($master = true)
|
||||
{
|
||||
$this->master($master);
|
||||
return $this->handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 析构释放连接
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
//该方法仅在connect连接时有效
|
||||
//当使用pconnect时,连接会被重用,连接的生命周期是fpm进程的生命周期,而非一次php的执行。
|
||||
//如果代码中使用pconnect, close的作用仅是使当前php不能再进行redis请求,但无法真正关闭redis长连接,连接在后续请求中仍然会被重用,直至fpm进程生命周期结束。
|
||||
|
||||
try {
|
||||
if (method_exists($this->handler, "close")) {
|
||||
$this->handler->close();
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\cache\driver;
|
||||
|
||||
use think\Cache;
|
||||
use think\Exception;
|
||||
|
||||
/**
|
||||
* SAE Memcache缓存驱动
|
||||
* @author liu21st <liu21st@gmail.com>
|
||||
*/
|
||||
class Sae
|
||||
{
|
||||
protected $handler = null;
|
||||
protected $options = [
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 11211,
|
||||
'expire' => 0,
|
||||
'timeout' => false,
|
||||
'persistent' => false,
|
||||
'prefix' => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @param array $options 缓存参数
|
||||
* @access public
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
if (!function_exists('memcache_init')) {
|
||||
throw new \BadFunctionCallException('must run at sae');
|
||||
}
|
||||
$this->handler = memcache_init();
|
||||
if (!$this->handler) {
|
||||
throw new Exception('memcache init error');
|
||||
}
|
||||
if (!empty($options)) {
|
||||
$this->options = array_merge($this->options, $options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
return $this->handler->get($_SERVER['HTTP_APPVERSION'] . '/' . $this->options['prefix'] . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @param mixed $value 存储数据
|
||||
* @param integer $expire 有效时间(秒)
|
||||
* @return bool
|
||||
*/
|
||||
public function set($name, $value, $expire = null)
|
||||
{
|
||||
if (is_null($expire)) {
|
||||
$expire = $this->options['expire'];
|
||||
}
|
||||
$name = $this->options['prefix'] . $name;
|
||||
if ($this->handler->set($_SERVER['HTTP_APPVERSION'] . '/' . $name, $value, 0, $expire)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
* @param string $name 缓存变量名
|
||||
* @param bool|false $ttl
|
||||
* @return bool
|
||||
*/
|
||||
public function rm($name, $ttl = false)
|
||||
{
|
||||
$name = $_SERVER['HTTP_APPVERSION'] . '/' . $this->options['prefix'] . $name;
|
||||
return false === $ttl ?
|
||||
$this->handler->delete($name) :
|
||||
$this->handler->delete($name, $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
return $this->handler->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得SaeKv对象
|
||||
*/
|
||||
private function getKv()
|
||||
{
|
||||
static $kv;
|
||||
if (!$kv) {
|
||||
$kv = new \SaeKV();
|
||||
if (!$kv->init()) {
|
||||
throw new Exception('KVDB init error');
|
||||
}
|
||||
}
|
||||
return $kv;
|
||||
}
|
||||
|
||||
}
|
||||
+763
@@ -0,0 +1,763 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\cache\driver;
|
||||
|
||||
use think\Cache;
|
||||
|
||||
/**
|
||||
* Secache缓存驱动
|
||||
* @author liu21st <liu21st@gmail.com>
|
||||
*/
|
||||
class Secache
|
||||
{
|
||||
protected $handler = null;
|
||||
protected $options = [
|
||||
'project' => '',
|
||||
'path' => '',
|
||||
'expire' => 0,
|
||||
'prefix' => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @param array $options 缓存参数
|
||||
* @access public
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
if (!empty($options)) {
|
||||
$this->options = array_merge($this->options, $options);
|
||||
}
|
||||
if (substr($this->options['path'], -1) != '/') {
|
||||
$this->options['path'] .= '/';
|
||||
}
|
||||
|
||||
$this->handler = new SecacheClient;
|
||||
$this->handler->workat($this->options['path'] . $this->options['project']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
$name = $this->options['prefix'] . $name;
|
||||
$key = md5($name);
|
||||
$this->handler->fetch($key, $return);
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @param mixed $value 存储数据
|
||||
* @param integer $expire 有效时间(秒)
|
||||
* @return boolean
|
||||
*/
|
||||
public function set($name, $value)
|
||||
{
|
||||
$name = $this->options['prefix'] . $name;
|
||||
$key = md5($name);
|
||||
return $this->handler->store($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return boolen
|
||||
*/
|
||||
public function rm($name)
|
||||
{
|
||||
$name = $this->options['prefix'] . $name;
|
||||
$key = md5($name);
|
||||
return $this->handler->delete($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
* @access public
|
||||
* @return boolen
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
return $this->handler->_format(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!defined('SECACHE_SIZE')) {
|
||||
define('SECACHE_SIZE', '15M');
|
||||
}
|
||||
class SecacheClient
|
||||
{
|
||||
|
||||
public $idx_node_size = 40;
|
||||
public $data_base_pos = 262588; //40+20+24*16+16*16*16*16*4;
|
||||
public $schema_item_size = 24;
|
||||
public $header_padding = 20; //保留空间 放置php标记防止下载
|
||||
public $info_size = 20; //保留空间 4+16 maxsize|ver
|
||||
|
||||
//40起 添加20字节保留区域
|
||||
public $idx_seq_pos = 40; //id 计数器节点地址
|
||||
public $dfile_cur_pos = 44; //id 计数器节点地址
|
||||
public $idx_free_pos = 48; //id 空闲链表入口地址
|
||||
|
||||
public $idx_base_pos = 444; //40+20+24*16
|
||||
public $min_size = 10240; //10M最小值
|
||||
public $schema_struct = array('size', 'free', 'lru_head', 'lru_tail', 'hits', 'miss');
|
||||
public $ver = '$Rev: 3 $';
|
||||
public $name = '系统默认缓存(文件型)';
|
||||
|
||||
public function workat($file)
|
||||
{
|
||||
|
||||
$this->_file = $file . '.php';
|
||||
$this->_bsize_list = array(
|
||||
512 => 10,
|
||||
3 << 10 => 10,
|
||||
8 << 10 => 10,
|
||||
20 << 10 => 4,
|
||||
30 << 10 => 2,
|
||||
50 << 10 => 2,
|
||||
80 << 10 => 2,
|
||||
96 << 10 => 2,
|
||||
128 << 10 => 2,
|
||||
224 << 10 => 2,
|
||||
256 << 10 => 2,
|
||||
512 << 10 => 1,
|
||||
1024 << 10 => 1,
|
||||
);
|
||||
|
||||
$this->_node_struct = array(
|
||||
'next' => array(0, 'V'),
|
||||
'prev' => array(4, 'V'),
|
||||
'data' => array(8, 'V'),
|
||||
'size' => array(12, 'V'),
|
||||
'lru_right' => array(16, 'V'),
|
||||
'lru_left' => array(20, 'V'),
|
||||
'key' => array(24, 'H*'),
|
||||
);
|
||||
|
||||
if (!file_exists($this->_file)) {
|
||||
$this->create();
|
||||
} else {
|
||||
$this->_rs = fopen($this->_file, 'rb+') || $this->triggerError('Can\'t open the cachefile: ' . realpath($this->_file), E_USER_ERROR);
|
||||
$this->_seek($this->header_padding);
|
||||
$info = unpack('V1max_size/a*ver', fread($this->_rs, $this->info_size));
|
||||
if ($info['ver'] != $this->ver) {
|
||||
$this->_format(true);
|
||||
} else {
|
||||
$this->max_size = $info['max_size'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->idx_node_base = $this->data_base_pos + $this->max_size;
|
||||
$this->_block_size_list = array_keys($this->_bsize_list);
|
||||
sort($this->_block_size_list);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$this->_rs = fopen($this->_file, 'wb+') || $this->triggerError('Can\'t open the cachefile: ' . realpath($this->_file), E_USER_ERROR);
|
||||
fseek($this->_rs, 0);
|
||||
fputs($this->_rs, '<' . '?php exit()?' . '>');
|
||||
return $this->_format();
|
||||
}
|
||||
|
||||
public function _puts($offset, $data)
|
||||
{
|
||||
if ($offset < $this->max_size * 1.5) {
|
||||
$this->_seek($offset);
|
||||
return fputs($this->_rs, $data);
|
||||
} else {
|
||||
$this->triggerError('Offset over quota:' . $offset, E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public function _seek($offset)
|
||||
{
|
||||
return fseek($this->_rs, $offset);
|
||||
}
|
||||
|
||||
public function clear()
|
||||
{
|
||||
return $this->_format(true);
|
||||
}
|
||||
|
||||
public function fetch($key, &$return)
|
||||
{
|
||||
$locked = false;
|
||||
if ($this->lock(false)) {
|
||||
$locked = true;
|
||||
}
|
||||
|
||||
if ($this->search($key, $offset)) {
|
||||
$info = $this->_get_node($offset);
|
||||
$schema_id = $this->_get_size_schema_id($info['size']);
|
||||
if (false === $schema_id) {
|
||||
if ($locked) {
|
||||
$this->unlock();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->_seek($info['data']);
|
||||
$data = fread($this->_rs, $info['size']);
|
||||
$return = unserialize($data);
|
||||
|
||||
if (false === $return) {
|
||||
if ($locked) {
|
||||
$this->unlock();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($locked) {
|
||||
$this->_lru_push($schema_id, $info['offset']);
|
||||
$this->_set_schema($schema_id, 'hits', $this->_get_schema($schema_id, 'hits') + 1);
|
||||
return $this->unlock();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if ($locked) {
|
||||
$this->unlock();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* lock
|
||||
* 如果flock不管用,请继承本类,并重载此方法
|
||||
*
|
||||
* @param mixed $is_block 是否阻塞
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function lock($is_block, $whatever = false)
|
||||
{
|
||||
return flock($this->_rs, $is_block ? LOCK_EX : LOCK_EX + LOCK_NB);
|
||||
}
|
||||
|
||||
/**
|
||||
* unlock
|
||||
* 如果flock不管用,请继承本类,并重载此方法
|
||||
*
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function unlock()
|
||||
{
|
||||
return flock($this->_rs, LOCK_UN);
|
||||
}
|
||||
|
||||
public function delete($key, $pos = false)
|
||||
{
|
||||
if ($pos || $this->search($key, $pos)) {
|
||||
if ($info = $this->_get_node($pos)) {
|
||||
//删除data区域
|
||||
if ($info['prev']) {
|
||||
$this->_set_node($info['prev'], 'next', $info['next']);
|
||||
$this->_set_node($info['next'], 'prev', $info['prev']);
|
||||
} else {
|
||||
//改入口位置
|
||||
$this->_set_node($info['next'], 'prev', 0);
|
||||
$this->_set_node_root($key, $info['next']);
|
||||
}
|
||||
$this->_free_dspace($info['size'], $info['data']);
|
||||
$this->_lru_delete($info);
|
||||
$this->_free_node($pos);
|
||||
return $info['prev'];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function store($key, $value)
|
||||
{
|
||||
|
||||
if ($this->lock(true)) {
|
||||
//save data
|
||||
$data = serialize($value);
|
||||
$size = strlen($data);
|
||||
|
||||
//get list_idx
|
||||
$has_key = $this->search($key, $list_idx_offset);
|
||||
$schema_id = $this->_get_size_schema_id($size);
|
||||
if (false === $schema_id) {
|
||||
$this->unlock();
|
||||
return false;
|
||||
}
|
||||
if ($has_key) {
|
||||
$hdseq = $list_idx_offset;
|
||||
|
||||
$info = $this->_get_node($hdseq);
|
||||
if ($this->_get_size_schema_id($info['size']) == $schema_id) {
|
||||
$dataoffset = $info['data'];
|
||||
} else {
|
||||
//破掉原有lru
|
||||
$this->_lru_delete($info);
|
||||
if (!($dataoffset = $this->_dalloc($schema_id))) {
|
||||
$this->unlock();
|
||||
return false;
|
||||
}
|
||||
$this->_free_dspace($info['size'], $info['data']);
|
||||
$this->_set_node($hdseq, 'lru_left', 0);
|
||||
$this->_set_node($hdseq, 'lru_right', 0);
|
||||
}
|
||||
|
||||
$this->_set_node($hdseq, 'size', $size);
|
||||
$this->_set_node($hdseq, 'data', $dataoffset);
|
||||
} else {
|
||||
|
||||
if (!($dataoffset = $this->_dalloc($schema_id))) {
|
||||
$this->unlock();
|
||||
return false;
|
||||
}
|
||||
$hdseq = $this->_alloc_idx(array(
|
||||
'next' => 0,
|
||||
'prev' => $list_idx_offset,
|
||||
'data' => $dataoffset,
|
||||
'size' => $size,
|
||||
'lru_right' => 0,
|
||||
'lru_left' => 0,
|
||||
'key' => $key,
|
||||
));
|
||||
|
||||
if ($list_idx_offset > 0) {
|
||||
$this->_set_node($list_idx_offset, 'next', $hdseq);
|
||||
} else {
|
||||
$this->_set_node_root($key, $hdseq);
|
||||
}
|
||||
}
|
||||
|
||||
if ($dataoffset > $this->max_size) {
|
||||
$this->triggerError('alloc datasize:' . $dataoffset, E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
$this->_puts($dataoffset, $data);
|
||||
|
||||
$this->_set_schema($schema_id, 'miss', $this->_get_schema($schema_id, 'miss') + 1);
|
||||
|
||||
$this->_lru_push($schema_id, $hdseq);
|
||||
$this->unlock();
|
||||
return true;
|
||||
} else {
|
||||
$this->triggerError("Couldn't lock the file !", E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* search
|
||||
* 查找指定的key
|
||||
* 如果找到节点则$pos=节点本身 返回true
|
||||
* 否则 $pos=树的末端 返回false
|
||||
*
|
||||
* @param mixed $key
|
||||
* @access public
|
||||
* @return mixed
|
||||
*/
|
||||
public function search($key, &$pos)
|
||||
{
|
||||
return $this->_get_pos_by_key($this->_get_node_root($key), $key, $pos);
|
||||
}
|
||||
|
||||
public function _get_size_schema_id($size)
|
||||
{
|
||||
foreach ($this->_block_size_list as $k => $block_size) {
|
||||
if ($size <= $block_size) {
|
||||
return $k;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function _parse_str_size($str_size, $default)
|
||||
{
|
||||
if (preg_match('/^([0-9]+)\s*([gmk]|)$/i', $str_size, $match)) {
|
||||
switch (strtolower($match[2])) {
|
||||
case 'g':
|
||||
if ($match[1] > 1) {
|
||||
$this->triggerError('Max cache size 1G', E_USER_ERROR);
|
||||
}
|
||||
$size = $match[1] << 30;
|
||||
break;
|
||||
case 'm':
|
||||
$size = $match[1] << 20;
|
||||
break;
|
||||
case 'k':
|
||||
$size = $match[1] << 10;
|
||||
break;
|
||||
default:
|
||||
$size = $match[1];
|
||||
}
|
||||
if ($size <= 0) {
|
||||
$this->triggerError('Error cache size ' . $this->max_size, E_USER_ERROR);
|
||||
return false;
|
||||
} elseif ($size < 10485760) {
|
||||
return 10485760;
|
||||
} else {
|
||||
return $size;
|
||||
}
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
public function _format($truncate = false)
|
||||
{
|
||||
if ($this->lock(true, true)) {
|
||||
|
||||
if ($truncate) {
|
||||
$this->_seek(0);
|
||||
ftruncate($this->_rs, $this->idx_node_base);
|
||||
}
|
||||
|
||||
$this->max_size = $this->_parse_str_size(SECACHE_SIZE, 15728640); //default:15m
|
||||
$this->_puts($this->header_padding, pack('V1a*', $this->max_size, $this->ver));
|
||||
|
||||
ksort($this->_bsize_list);
|
||||
$ds_offset = $this->data_base_pos;
|
||||
$i = 0;
|
||||
foreach ($this->_bsize_list as $size => $count) {
|
||||
|
||||
//将预分配的空间注册到free链表里
|
||||
$count *= min(3, floor($this->max_size / 10485760));
|
||||
$next_free_node = 0;
|
||||
for ($j = 0; $j < $count; $j++) {
|
||||
$this->_puts($ds_offset, pack('V', $next_free_node));
|
||||
$next_free_node = $ds_offset;
|
||||
$ds_offset += intval($size);
|
||||
}
|
||||
|
||||
$code = pack(str_repeat('V1', count($this->schema_struct)), $size, $next_free_node, 0, 0, 0, 0);
|
||||
|
||||
$this->_puts(60 + $i * $this->schema_item_size, $code);
|
||||
$i++;
|
||||
}
|
||||
$this->_set_dcur_pos($ds_offset);
|
||||
|
||||
$this->_puts($this->idx_base_pos, str_repeat("\0", 262144));
|
||||
$this->_puts($this->idx_seq_pos, pack('V', 1));
|
||||
$this->unlock();
|
||||
return true;
|
||||
} else {
|
||||
$this->triggerError("Couldn't lock the file !", E_USER_ERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function _get_node_root($key)
|
||||
{
|
||||
$this->_seek(hexdec(substr($key, 0, 4)) * 4 + $this->idx_base_pos);
|
||||
$a = fread($this->_rs, 4);
|
||||
list(, $offset) = unpack('V', $a);
|
||||
return $offset;
|
||||
}
|
||||
|
||||
public function _set_node_root($key, $value)
|
||||
{
|
||||
return $this->_puts(hexdec(substr($key, 0, 4)) * 4 + $this->idx_base_pos, pack('V', $value));
|
||||
}
|
||||
|
||||
public function _set_node($pos, $key, $value)
|
||||
{
|
||||
|
||||
if (!$pos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($this->_node_struct[$key])) {
|
||||
return $this->_puts($pos * $this->idx_node_size + $this->idx_node_base + $this->_node_struct[$key][0], pack($this->_node_struct[$key][1], $value));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function _get_pos_by_key($offset, $key, &$pos)
|
||||
{
|
||||
if (!$offset) {
|
||||
$pos = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
$info = $this->_get_node($offset);
|
||||
|
||||
if ($info['key'] == $key) {
|
||||
$pos = $info['offset'];
|
||||
return true;
|
||||
} elseif ($info['next'] && $info['next'] != $offset) {
|
||||
return $this->_get_pos_by_key($info['next'], $key, $pos);
|
||||
} else {
|
||||
$pos = $offset;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function _lru_delete($info)
|
||||
{
|
||||
|
||||
if ($info['lru_right']) {
|
||||
$this->_set_node($info['lru_right'], 'lru_left', $info['lru_left']);
|
||||
} else {
|
||||
$this->_set_schema($this->_get_size_schema_id($info['size']), 'lru_tail', $info['lru_left']);
|
||||
}
|
||||
|
||||
if ($info['lru_left']) {
|
||||
$this->_set_node($info['lru_left'], 'lru_right', $info['lru_right']);
|
||||
} else {
|
||||
$this->_set_schema($this->_get_size_schema_id($info['size']), 'lru_head', $info['lru_right']);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function _lru_push($schema_id, $offset)
|
||||
{
|
||||
$lru_head = $this->_get_schema($schema_id, 'lru_head');
|
||||
$lru_tail = $this->_get_schema($schema_id, 'lru_tail');
|
||||
|
||||
if ((!$offset) || ($lru_head == $offset)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$info = $this->_get_node($offset);
|
||||
|
||||
$this->_set_node($info['lru_right'], 'lru_left', $info['lru_left']);
|
||||
$this->_set_node($info['lru_left'], 'lru_right', $info['lru_right']);
|
||||
|
||||
$this->_set_node($offset, 'lru_right', $lru_head);
|
||||
$this->_set_node($offset, 'lru_left', 0);
|
||||
|
||||
$this->_set_node($lru_head, 'lru_left', $offset);
|
||||
$this->_set_schema($schema_id, 'lru_head', $offset);
|
||||
|
||||
if (0 == $lru_tail) {
|
||||
$this->_set_schema($schema_id, 'lru_tail', $offset);
|
||||
} elseif ($lru_tail == $offset && $info['lru_left']) {
|
||||
$this->_set_schema($schema_id, 'lru_tail', $info['lru_left']);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function _get_node($offset)
|
||||
{
|
||||
$this->_seek($offset * $this->idx_node_size + $this->idx_node_base);
|
||||
$info = unpack('V1next/V1prev/V1data/V1size/V1lru_right/V1lru_left/H*key', fread($this->_rs, $this->idx_node_size));
|
||||
$info['offset'] = $offset;
|
||||
return $info;
|
||||
}
|
||||
|
||||
public function _lru_pop($schema_id)
|
||||
{
|
||||
if ($node = $this->_get_schema($schema_id, 'lru_tail')) {
|
||||
$info = $this->_get_node($node);
|
||||
if (!$info['data']) {
|
||||
return false;
|
||||
}
|
||||
$this->delete($info['key'], $info['offset']);
|
||||
if (!$this->_get_schema($schema_id, 'free')) {
|
||||
$this->triggerError('pop lru,But nothing free...', E_USER_ERROR);
|
||||
}
|
||||
return $info;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function _dalloc($schema_id, $lru_freed = false)
|
||||
{
|
||||
|
||||
if ($free = $this->_get_schema($schema_id, 'free')) {
|
||||
//如果lru里有链表
|
||||
$this->_seek($free);
|
||||
list(, $next) = unpack('V', fread($this->_rs, 4));
|
||||
$this->_set_schema($schema_id, 'free', $next);
|
||||
return $free;
|
||||
} elseif ($lru_freed) {
|
||||
$this->triggerError('Bat lru poped freesize', E_USER_ERROR);
|
||||
return false;
|
||||
} else {
|
||||
$ds_offset = $this->_get_dcur_pos();
|
||||
$size = $this->_get_schema($schema_id, 'size');
|
||||
|
||||
if ($size + $ds_offset > $this->max_size) {
|
||||
if ($info = $this->_lru_pop($schema_id)) {
|
||||
return $this->_dalloc($schema_id, $info);
|
||||
} else {
|
||||
$this->triggerError('Can\'t alloc dataspace', E_USER_ERROR);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->_set_dcur_pos($ds_offset + $size);
|
||||
return $ds_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function _get_dcur_pos()
|
||||
{
|
||||
$this->_seek($this->dfile_cur_pos);
|
||||
list(, $ds_offset) = unpack('V', fread($this->_rs, 4));
|
||||
return $ds_offset;
|
||||
}
|
||||
public function _set_dcur_pos($pos)
|
||||
{
|
||||
return $this->_puts($this->dfile_cur_pos, pack('V', $pos));
|
||||
}
|
||||
|
||||
public function _free_dspace($size, $pos)
|
||||
{
|
||||
|
||||
if ($pos > $this->max_size) {
|
||||
$this->triggerError('free dspace over quota:' . $pos, E_USER_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
$schema_id = $this->_get_size_schema_id($size);
|
||||
if ($free = $this->_get_schema($schema_id, 'free')) {
|
||||
$this->_puts($free, pack('V1', $pos));
|
||||
} else {
|
||||
$this->_set_schema($schema_id, 'free', $pos);
|
||||
}
|
||||
$this->_puts($pos, pack('V1', 0));
|
||||
}
|
||||
|
||||
public function _dfollow($pos, &$c)
|
||||
{
|
||||
$c++;
|
||||
$this->_seek($pos);
|
||||
list(, $next) = unpack('V1', fread($this->_rs, 4));
|
||||
if ($next) {
|
||||
return $this->_dfollow($next, $c);
|
||||
} else {
|
||||
return $pos;
|
||||
}
|
||||
}
|
||||
|
||||
public function _free_node($pos)
|
||||
{
|
||||
$this->_seek($this->idx_free_pos);
|
||||
list(, $prev_free_node) = unpack('V', fread($this->_rs, 4));
|
||||
$this->_puts($pos * $this->idx_node_size + $this->idx_node_base, pack('V', $prev_free_node) . str_repeat("\0", $this->idx_node_size - 4));
|
||||
return $this->_puts($this->idx_free_pos, pack('V', $pos));
|
||||
}
|
||||
|
||||
public function _alloc_idx($data)
|
||||
{
|
||||
$this->_seek($this->idx_free_pos);
|
||||
list(, $list_pos) = unpack('V', fread($this->_rs, 4));
|
||||
if ($list_pos) {
|
||||
|
||||
$this->_seek($list_pos * $this->idx_node_size + $this->idx_node_base);
|
||||
list(, $prev_free_node) = unpack('V', fread($this->_rs, 4));
|
||||
$this->_puts($this->idx_free_pos, pack('V', $prev_free_node));
|
||||
|
||||
} else {
|
||||
$this->_seek($this->idx_seq_pos);
|
||||
list(, $list_pos) = unpack('V', fread($this->_rs, 4));
|
||||
$this->_puts($this->idx_seq_pos, pack('V', $list_pos + 1));
|
||||
}
|
||||
return $this->_create_node($list_pos, $data);
|
||||
}
|
||||
|
||||
public function _create_node($pos, $data)
|
||||
{
|
||||
$this->_puts($pos * $this->idx_node_size + $this->idx_node_base
|
||||
, pack('V1V1V1V1V1V1H*', $data['next'], $data['prev'], $data['data'], $data['size'], $data['lru_right'], $data['lru_left'], $data['key']));
|
||||
return $pos;
|
||||
}
|
||||
|
||||
public function _set_schema($schema_id, $key, $value)
|
||||
{
|
||||
$info = array_flip($this->schema_struct);
|
||||
return $this->_puts(60 + $schema_id * $this->schema_item_size + $info[$key] * 4, pack('V', $value));
|
||||
}
|
||||
|
||||
public function _get_schema($id, $key)
|
||||
{
|
||||
$info = array_flip($this->schema_struct);
|
||||
|
||||
$this->_seek(60 + $id * $this->schema_item_size);
|
||||
unpack('V1' . implode('/V1', $this->schema_struct), fread($this->_rs, $this->schema_item_size));
|
||||
|
||||
$this->_seek(60 + $id * $this->schema_item_size + $info[$key] * 4);
|
||||
list(, $value) = unpack('V', fread($this->_rs, 4));
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function _all_schemas()
|
||||
{
|
||||
$schema = [];
|
||||
for ($i = 0; $i < 16; $i++) {
|
||||
$this->_seek(60 + $i * $this->schema_item_size);
|
||||
$info = unpack('V1' . implode('/V1', $this->schema_struct), fread($this->_rs, $this->schema_item_size));
|
||||
if ($info['size']) {
|
||||
$info['id'] = $i;
|
||||
$schema[$i] = $info;
|
||||
} else {
|
||||
return $schema;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function schemaStatus()
|
||||
{
|
||||
$return = [];
|
||||
foreach ($this->_all_schemas() as $k => $schemaItem) {
|
||||
if ($schemaItem['free']) {
|
||||
$this->_dfollow($schemaItem['free'], $schemaItem['freecount']);
|
||||
}
|
||||
$return[] = $schemaItem;
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
public function status(&$curBytes, &$totalBytes)
|
||||
{
|
||||
$totalBytes = $curBytes = 0;
|
||||
$hits = $miss = 0;
|
||||
|
||||
$schemaStatus = $this->schemaStatus();
|
||||
$totalBytes = $this->max_size;
|
||||
$freeBytes = $this->max_size - $this->_get_dcur_pos();
|
||||
|
||||
foreach ($schemaStatus as $schema) {
|
||||
$freeBytes += $schema['freecount'] * $schema['size'];
|
||||
$miss += $schema['miss'];
|
||||
$hits += $schema['hits'];
|
||||
}
|
||||
$curBytes = $totalBytes - $freeBytes;
|
||||
|
||||
$return[] = array('name' => '缓存命中', 'value' => $hits);
|
||||
$return[] = array('name' => '缓存未命中', 'value' => $miss);
|
||||
return $return;
|
||||
}
|
||||
|
||||
public function triggerError($errstr, $errno)
|
||||
{
|
||||
triggerError($errstr, $errno);
|
||||
}
|
||||
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\cache\driver;
|
||||
|
||||
use think\Cache;
|
||||
use think\Exception;
|
||||
|
||||
/**
|
||||
* Sqlite缓存驱动
|
||||
* @author liu21st <liu21st@gmail.com>
|
||||
*/
|
||||
class Sqlite implements CacheInterface
|
||||
{
|
||||
|
||||
protected $options = [
|
||||
'db' => ':memory:',
|
||||
'table' => 'sharedmemory',
|
||||
'prefix' => '',
|
||||
'expire' => 0,
|
||||
'persistent' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @param array $options 缓存参数
|
||||
* @throws \BadFunctionCallException
|
||||
* @access public
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
if (!extension_loaded('sqlite')) {
|
||||
throw new \BadFunctionCallException('not support: sqlite');
|
||||
}
|
||||
if (!empty($options)) {
|
||||
$this->options = array_merge($this->options, $options);
|
||||
}
|
||||
$func = $this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open';
|
||||
$this->handler = $func($this->options['db']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
$name = $this->options['prefix'] . sqlite_escape_string($name);
|
||||
$sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . time() . ') LIMIT 1';
|
||||
$result = sqlite_query($this->handler, $sql);
|
||||
if (sqlite_num_rows($result)) {
|
||||
$content = sqlite_fetch_single($result);
|
||||
if (function_exists('gzcompress')) {
|
||||
//启用数据压缩
|
||||
$content = gzuncompress($content);
|
||||
}
|
||||
return unserialize($content);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @param mixed $value 存储数据
|
||||
* @param integer $expire 有效时间(秒)
|
||||
* @return boolean
|
||||
*/
|
||||
public function set($name, $value, $expire = null)
|
||||
{
|
||||
$name = $this->options['prefix'] . sqlite_escape_string($name);
|
||||
$value = sqlite_escape_string(serialize($value));
|
||||
if (is_null($expire)) {
|
||||
$expire = $this->options['expire'];
|
||||
}
|
||||
$expire = (0 == $expire) ? 0 : (time() + $expire); //缓存有效期为0表示永久缓存
|
||||
if (function_exists('gzcompress')) {
|
||||
//数据压缩
|
||||
$value = gzcompress($value, 3);
|
||||
}
|
||||
$sql = 'REPLACE INTO ' . $this->options['table'] . ' (var, value,expire) VALUES (\'' . $name . '\', \'' . $value . '\', \'' . $expire . '\')';
|
||||
if (sqlite_query($this->handler, $sql)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return boolean
|
||||
*/
|
||||
public function rm($name)
|
||||
{
|
||||
$name = $this->options['prefix'] . sqlite_escape_string($name);
|
||||
$sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\'';
|
||||
sqlite_query($this->handler, $sql);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$sql = 'DELETE FROM ' . $this->options['table'];
|
||||
sqlite_query($this->handler, $sql);
|
||||
return;
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\cache\driver;
|
||||
|
||||
use think\Cache;
|
||||
|
||||
/**
|
||||
* 测试缓存类
|
||||
* @author liu21st <liu21st@gmail.com>
|
||||
*/
|
||||
class Test
|
||||
{
|
||||
|
||||
/**
|
||||
* 读取缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @param mixed $value 存储数据
|
||||
* @param int $expire 有效时间 0为永久
|
||||
* @return boolean
|
||||
*/
|
||||
public function set($name, $value, $expire = null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return boolean
|
||||
*/
|
||||
public function rm($name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\cache\driver;
|
||||
|
||||
use think\Cache;
|
||||
use think\Exception;
|
||||
|
||||
/**
|
||||
* Wincache缓存驱动
|
||||
* @author liu21st <liu21st@gmail.com>
|
||||
*/
|
||||
class Wincache
|
||||
{
|
||||
protected $options = [
|
||||
'prefix' => '',
|
||||
'expire' => 0,
|
||||
];
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @param array $options 缓存参数
|
||||
* @throws Exception
|
||||
* @access public
|
||||
*/
|
||||
public function __construct($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 mixed
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
$name = $this->options['prefix'] . $name;
|
||||
return wincache_ucache_exists($name) ? wincache_ucache_get($name) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @param mixed $value 存储数据
|
||||
* @param integer $expire 有效时间(秒)
|
||||
* @return boolean
|
||||
*/
|
||||
public function set($name, $value, $expire = null)
|
||||
{
|
||||
if (is_null($expire)) {
|
||||
$expire = $this->options['expire'];
|
||||
}
|
||||
$name = $this->options['prefix'] . $name;
|
||||
if (wincache_ucache_set($name, $value, $expire)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return boolean
|
||||
*/
|
||||
public function rm($name)
|
||||
{
|
||||
return wincache_ucache_delete($this->options['prefix'] . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\cache\driver;
|
||||
|
||||
use think\Cache;
|
||||
use think\Exception;
|
||||
|
||||
/**
|
||||
* Xcache缓存驱动
|
||||
* @author liu21st <liu21st@gmail.com>
|
||||
*/
|
||||
class Xcache
|
||||
{
|
||||
protected $options = [
|
||||
'prefix' => '',
|
||||
'expire' => 0,
|
||||
];
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @param array $options 缓存参数
|
||||
* @access public
|
||||
* @throws \BadFunctionCallException
|
||||
*/
|
||||
public function __construct($options = [])
|
||||
{
|
||||
if (!function_exists('xcache_info')) {
|
||||
throw new \BadFunctionCallException('not support: Xcache');
|
||||
}
|
||||
if (!empty($options)) {
|
||||
$this->options = array_merge($this->options, $options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
$name = $this->options['prefix'] . $name;
|
||||
if (xcache_isset($name)) {
|
||||
return xcache_get($name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @param mixed $value 存储数据
|
||||
* @param integer $expire 有效时间(秒)
|
||||
* @return boolean
|
||||
*/
|
||||
public function set($name, $value, $expire = null)
|
||||
{
|
||||
if (is_null($expire)) {
|
||||
$expire = $this->options['expire'];
|
||||
}
|
||||
$name = $this->options['prefix'] . $name;
|
||||
if (xcache_set($name, $value, $expire)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
* @access public
|
||||
* @param string $name 缓存变量名
|
||||
* @return boolean
|
||||
*/
|
||||
public function rm($name)
|
||||
{
|
||||
return xcache_unset($this->options['prefix'] . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
* @access public
|
||||
* @return boolean
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\config\driver;
|
||||
|
||||
class Ini
|
||||
{
|
||||
public function parse($config)
|
||||
{
|
||||
if (is_file($config)) {
|
||||
return parse_ini_file($config, true);
|
||||
} else {
|
||||
return parse_ini_string($config, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\config\driver;
|
||||
|
||||
class Json
|
||||
{
|
||||
public function parse($config)
|
||||
{
|
||||
if (is_file($config)) {
|
||||
$config = file_get_contents($config);
|
||||
}
|
||||
$result = json_decode($config, true);
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\config\driver;
|
||||
|
||||
class Xml
|
||||
{
|
||||
public function parse($config)
|
||||
{
|
||||
if (is_file($config)) {
|
||||
$content = simplexml_load_file($config);
|
||||
} else {
|
||||
$content = simplexml_load_string($config);
|
||||
}
|
||||
$result = (array) $content;
|
||||
foreach ($result as $key => $val) {
|
||||
if (is_object($val)) {
|
||||
$result[$key] = (array) $val;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,471 @@
|
||||
<?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;
|
||||
|
||||
use think\console\input\Definition;
|
||||
use think\console\input\Argument;
|
||||
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)
|
||||
{
|
||||
$this->arguments = [];
|
||||
$this->options = [];
|
||||
$this->definition = $definition;
|
||||
|
||||
$this->parse();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解析参数
|
||||
*/
|
||||
protected function parse()
|
||||
{
|
||||
$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 && 0 === strpos($token, '--')) {
|
||||
$this->parseLongOption($token);
|
||||
} elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
|
||||
$this->parseShortOption($token);
|
||||
} else {
|
||||
$this->parseArgument($token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解析短选项
|
||||
* @param string $token 当前的指令.
|
||||
*/
|
||||
private function parseShortOption($token)
|
||||
{
|
||||
$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($name)
|
||||
{
|
||||
$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($token)
|
||||
{
|
||||
$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($token)
|
||||
{
|
||||
$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($shortcut, $value)
|
||||
{
|
||||
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($name, $value)
|
||||
{
|
||||
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 null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查原始参数是否包含某个值
|
||||
* @param string|array $values 需要检查的值
|
||||
* @return bool
|
||||
*/
|
||||
public function hasParameterOption($values)
|
||||
{
|
||||
$values = (array)$values;
|
||||
|
||||
foreach ($this->tokens as $token) {
|
||||
foreach ($values as $value) {
|
||||
if ($token === $value || 0 === strpos($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 || 0 === strpos($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()
|
||||
{
|
||||
return $this->interactive;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输入的交互
|
||||
* @param bool
|
||||
*/
|
||||
public function setInteractive($interactive)
|
||||
{
|
||||
$this->interactive = (bool)$interactive;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有的参数
|
||||
* @return Argument[]
|
||||
*/
|
||||
public function getArguments()
|
||||
{
|
||||
return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名称获取参数
|
||||
* @param string $name 参数名
|
||||
* @return mixed
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function getArgument($name)
|
||||
{
|
||||
if (!$this->definition->hasArgument($name)) {
|
||||
throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
|
||||
}
|
||||
|
||||
return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)
|
||||
->getDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置参数的值
|
||||
* @param string $name 参数名
|
||||
* @param string $value 值
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setArgument($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($name)
|
||||
{
|
||||
return $this->definition->hasArgument($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有的选项
|
||||
* @return Option[]
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return array_merge($this->definition->getOptionDefaults(), $this->options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取选项值
|
||||
* @param string $name 选项名称
|
||||
* @return mixed
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function getOption($name)
|
||||
{
|
||||
if (!$this->definition->hasOption($name)) {
|
||||
throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
|
||||
}
|
||||
|
||||
return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置选项值
|
||||
* @param string $name 选项名
|
||||
* @param string|bool $value 值
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setOption($name, $value)
|
||||
{
|
||||
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($name)
|
||||
{
|
||||
return $this->definition->hasOption($name) && isset($this->options[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转义指令
|
||||
* @param string $token
|
||||
* @return string
|
||||
*/
|
||||
public function escapeToken($token)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -0,0 +1,86 @@
|
||||
<?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;
|
||||
|
||||
use think\console\output\Formatter;
|
||||
use think\console\output\Stream;
|
||||
|
||||
class Output extends Stream
|
||||
{
|
||||
|
||||
/** @var Stream */
|
||||
private $stderr;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$outputStream = 'php://stdout';
|
||||
if (!$this->hasStdoutSupport()) {
|
||||
$outputStream = 'php://output';
|
||||
}
|
||||
|
||||
parent::__construct(fopen($outputStream, 'w'));
|
||||
|
||||
$this->stderr = new Stream(fopen('php://stderr', 'w'), $this->getFormatter());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setDecorated($decorated)
|
||||
{
|
||||
parent::setDecorated($decorated);
|
||||
$this->stderr->setDecorated($decorated);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setFormatter(Formatter $formatter)
|
||||
{
|
||||
parent::setFormatter($formatter);
|
||||
$this->stderr->setFormatter($formatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setVerbosity($level)
|
||||
{
|
||||
parent::setVerbosity($level);
|
||||
$this->stderr->setVerbosity($level);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getErrorOutput()
|
||||
{
|
||||
return $this->stderr;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setErrorOutput(Output $error)
|
||||
{
|
||||
$this->stderr = $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前环境是否支持控制台输出写入标准输出。
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasStdoutSupport()
|
||||
{
|
||||
return ('OS400' != php_uname('s'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
console 工具使用 hiddeninput.exe 在 windows 上隐藏密码输入,该二进制文件由第三方提供,相关源码和其他细节可以在 [Hidden Input](https://github.com/Seldaek/hidden-input) 找到。
|
||||
Binary file not shown.
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\console\command;
|
||||
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
class Build extends Command
|
||||
{
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('build')
|
||||
->setDefinition([
|
||||
new Option('config', null, Option::VALUE_OPTIONAL, "build.php path"),
|
||||
new Option('module', null, Option::VALUE_OPTIONAL, "module name")
|
||||
])
|
||||
->setDescription('Build Application Dirs');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
if ($input->hasOption('module')) {
|
||||
\think\Build::module($input->getOption('module'));
|
||||
$output->writeln("Successed");
|
||||
return;
|
||||
}
|
||||
|
||||
if ($input->hasOption('config')) {
|
||||
$build = include $input->getOption('config');
|
||||
} else {
|
||||
$build = include APP_PATH . 'build.php';
|
||||
}
|
||||
if (empty($build)) {
|
||||
$output->writeln("Build Config Is Empty");
|
||||
return;
|
||||
}
|
||||
\think\Build::run($build);
|
||||
$output->writeln("Successed");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,501 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\console\command;
|
||||
|
||||
use think\Console;
|
||||
use think\console\Input;
|
||||
use think\console\input\Argument;
|
||||
use think\console\input\Definition;
|
||||
use think\console\helper\Set as HelperSet;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
class Command
|
||||
{
|
||||
|
||||
/** @var Console */
|
||||
private $console;
|
||||
private $name;
|
||||
private $aliases = [];
|
||||
private $definition;
|
||||
private $help;
|
||||
private $description;
|
||||
private $ignoreValidationErrors = false;
|
||||
private $consoleDefinitionMerged = false;
|
||||
private $consoleDefinitionMergedWithArgs = false;
|
||||
private $code;
|
||||
private $synopsis = [];
|
||||
private $usages = [];
|
||||
|
||||
/** @var HelperSet */
|
||||
private $helperSet;
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
* @param string|null $name 命令名称,如果没有设置则比如在 configure() 里设置
|
||||
* @throws \LogicException
|
||||
* @api
|
||||
*/
|
||||
public function __construct($name = null)
|
||||
{
|
||||
$this->definition = new Definition();
|
||||
|
||||
if (null !== $name) {
|
||||
$this->setName($name);
|
||||
}
|
||||
|
||||
$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()
|
||||
{
|
||||
$this->ignoreValidationErrors = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置控制台
|
||||
* @param Console $console
|
||||
*/
|
||||
public function setConsole(Console $console = null)
|
||||
{
|
||||
$this->console = $console;
|
||||
if ($console) {
|
||||
$this->setHelperSet($console->getHelperSet());
|
||||
} else {
|
||||
$this->helperSet = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置帮助集
|
||||
* @param HelperSet $helperSet
|
||||
*/
|
||||
public function setHelperSet(HelperSet $helperSet)
|
||||
{
|
||||
$this->helperSet = $helperSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取帮助集
|
||||
* @return HelperSet
|
||||
*/
|
||||
public function getHelperSet()
|
||||
{
|
||||
return $this->helperSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取控制台
|
||||
* @return Console
|
||||
* @api
|
||||
*/
|
||||
public function getConsole()
|
||||
{
|
||||
return $this->console;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有效
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnabled()
|
||||
{
|
||||
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)
|
||||
{
|
||||
throw new \LogicException('You must override the execute() method in the concrete command class.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户验证
|
||||
* @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)
|
||||
{
|
||||
$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 ($input->isInteractive()) {
|
||||
$this->interact($input, $output);
|
||||
}
|
||||
|
||||
$input->validate();
|
||||
|
||||
if ($this->code) {
|
||||
$statusCode = call_user_func($this->code, $input, $output);
|
||||
} else {
|
||||
$statusCode = $this->execute($input, $output);
|
||||
}
|
||||
|
||||
return is_numeric($statusCode) ? (int)$statusCode : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置执行代码
|
||||
* @param callable $code callable(InputInterface $input, OutputInterface $output)
|
||||
* @return Command
|
||||
* @throws \InvalidArgumentException
|
||||
* @see execute()
|
||||
*/
|
||||
public function setCode(callable $code)
|
||||
{
|
||||
if (!is_callable($code)) {
|
||||
throw new \InvalidArgumentException('Invalid callable provided to Command::setCode.');
|
||||
}
|
||||
|
||||
if (PHP_VERSION_ID >= 50400 && $code instanceof \Closure) {
|
||||
$r = new \ReflectionFunction($code);
|
||||
if (null === $r->getClosureThis()) {
|
||||
$code = \Closure::bind($code, $this);
|
||||
}
|
||||
}
|
||||
|
||||
$this->code = $code;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并参数定义
|
||||
* @param bool $mergeArgs
|
||||
*/
|
||||
public function mergeConsoleDefinition($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()
|
||||
{
|
||||
return $this->definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前指令的参数定义
|
||||
* @return Definition
|
||||
*/
|
||||
public function getNativeDefinition()
|
||||
{
|
||||
return $this->getDefinition();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加参数
|
||||
* @param string $name 名称
|
||||
* @param int $mode 类型
|
||||
* @param string $description 描述
|
||||
* @param mixed $default 默认值
|
||||
* @return Command
|
||||
*/
|
||||
public function addArgument($name, $mode = null, $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($name, $shortcut = null, $mode = null, $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($name)
|
||||
{
|
||||
$this->validateName($name);
|
||||
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指令名称
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置描述
|
||||
* @param string $description
|
||||
* @return Command
|
||||
*/
|
||||
public function setDescription($description)
|
||||
{
|
||||
$this->description = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取描述
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置帮助信息
|
||||
* @param string $help
|
||||
* @return Command
|
||||
*/
|
||||
public function setHelp($help)
|
||||
{
|
||||
$this->help = $help;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取帮助信息
|
||||
* @return string
|
||||
*/
|
||||
public function getHelp()
|
||||
{
|
||||
return $this->help;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 描述信息
|
||||
* @return string
|
||||
*/
|
||||
public function getProcessedHelp()
|
||||
{
|
||||
$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($aliases)
|
||||
{
|
||||
if (!is_array($aliases) && !$aliases instanceof \Traversable) {
|
||||
throw new \InvalidArgumentException('$aliases must be an array or an instance of \Traversable');
|
||||
}
|
||||
|
||||
foreach ($aliases as $alias) {
|
||||
$this->validateName($alias);
|
||||
}
|
||||
|
||||
$this->aliases = $aliases;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取别名
|
||||
* @return array
|
||||
*/
|
||||
public function getAliases()
|
||||
{
|
||||
return $this->aliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取简介
|
||||
* @param bool $short 是否简单的
|
||||
* @return string
|
||||
*/
|
||||
public function getSynopsis($short = false)
|
||||
{
|
||||
$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
|
||||
*/
|
||||
public function addUsage($usage)
|
||||
{
|
||||
if (0 !== strpos($usage, $this->name)) {
|
||||
$usage = sprintf('%s %s', $this->name, $usage);
|
||||
}
|
||||
|
||||
$this->usages[] = $usage;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用法介绍
|
||||
* @return array
|
||||
*/
|
||||
public function getUsages()
|
||||
{
|
||||
return $this->usages;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取助手
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function getHelper($name)
|
||||
{
|
||||
return $this->helperSet->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证指令名称
|
||||
* @param string $name
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
private function validateName($name)
|
||||
{
|
||||
if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
|
||||
throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\console\command;
|
||||
|
||||
use think\console\Input;
|
||||
use think\console\input\Argument as InputArgument;
|
||||
use think\console\input\Option as InputOption;
|
||||
use think\console\Output;
|
||||
use think\console\helper\Descriptor as DescriptorHelper;
|
||||
|
||||
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)
|
||||
{
|
||||
$this->command = $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
if (null === $this->command) {
|
||||
$this->command = $this->getConsole()->find($input->getArgument('command_name'));
|
||||
}
|
||||
|
||||
|
||||
$helper = new DescriptorHelper();
|
||||
$helper->describe($output, $this->command, [
|
||||
'raw_text' => $input->getOption('raw'),
|
||||
]);
|
||||
|
||||
$this->command = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\console\command;
|
||||
|
||||
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\console\input\Argument as InputArgument;
|
||||
use think\console\input\Option as InputOption;
|
||||
use think\console\input\Definition as InputDefinition;
|
||||
use think\console\helper\Descriptor as DescriptorHelper;
|
||||
|
||||
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()
|
||||
{
|
||||
return $this->createDefinition();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
|
||||
$helper = new DescriptorHelper();
|
||||
$helper->describe($output, $this->getConsole(), [
|
||||
'raw_text' => $input->getOption('raw'),
|
||||
'namespace' => $input->getArgument('namespace'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
private function createDefinition()
|
||||
{
|
||||
return new InputDefinition([
|
||||
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
|
||||
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list')
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?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;
|
||||
|
||||
class Make extends Command
|
||||
{
|
||||
// 创建目录
|
||||
protected static function buildDir($dir)
|
||||
{
|
||||
if (!is_dir(APP_PATH . $dir)) {
|
||||
mkdir(APP_PATH . strtolower($dir), 0777, true);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建文件
|
||||
protected static function buildFile($file, $content)
|
||||
{
|
||||
if (is_file(APP_PATH . $file)) {
|
||||
exception('file already exists');
|
||||
}
|
||||
file_put_contents(APP_PATH . $file, $content);
|
||||
}
|
||||
|
||||
protected static function formatNameSpace($namespace)
|
||||
{
|
||||
$namespace = explode('\\', $namespace);
|
||||
|
||||
foreach ($namespace as $key => $value) {
|
||||
if ($key == count($namespace) - 1) {
|
||||
$newNameSpace[1] = $value;
|
||||
} else {
|
||||
$newNameSpace[0][$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $newNameSpace;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?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\make;
|
||||
|
||||
use think\App;
|
||||
use think\console\Input;
|
||||
use think\console\input\Argument;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
class Controller extends \think\console\command\Make
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('make:controller')
|
||||
->setDescription('Create a new controller class')
|
||||
->addArgument('namespace', Argument::OPTIONAL, null)
|
||||
->addOption('module', 'm', Option::VALUE_OPTIONAL, 'Module Name', null)
|
||||
->addOption('extend', 'e', Option::VALUE_OPTIONAL, 'Base on Controller class', null);
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$namespace = $input->getArgument('namespace');
|
||||
$module = $input->getOption('module');
|
||||
|
||||
|
||||
// 处理命名空间
|
||||
if (!empty($module)) {
|
||||
$namespace = App::$namespace . "\\" . $module . "\\" . 'controller' . "\\" . $namespace;
|
||||
}
|
||||
|
||||
// 处理继承
|
||||
$extend = $input->getOption('extend');
|
||||
|
||||
if (empty($extend)) {
|
||||
$extend = "\\think\\Controller";
|
||||
} else {
|
||||
if (!preg_match("/\\\/", $extend)) {
|
||||
if (!empty($module)) {
|
||||
$extend = "\\" . App::$namespace . "\\" . $module . "\\" . 'controller' . "\\" . $extend;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$result = $this->build($namespace, $extend);
|
||||
$output->writeln("output:" . $result);
|
||||
}
|
||||
|
||||
private function build($namespace, $extend)
|
||||
{
|
||||
$tpl = file_get_contents(THINK_PATH . 'tpl' . DS . 'make_controller.tpl');
|
||||
|
||||
// comminute namespace
|
||||
$allNamespace = self::formatNameSpace($namespace);
|
||||
$namespace = implode('\\', $allNamespace[0]);
|
||||
$className = ucwords($allNamespace[1]);
|
||||
|
||||
// 处理内容
|
||||
$content = str_replace("{%extend%}", $extend,
|
||||
str_replace("{%className%}", $className,
|
||||
str_replace("{%namespace%}", $namespace, $tpl)
|
||||
)
|
||||
);
|
||||
|
||||
// 处理文件夹
|
||||
$path = '';
|
||||
foreach ($allNamespace[0] as $key => $value) {
|
||||
if ($key >= 1) {
|
||||
self::buildDir($path . $value);
|
||||
$path .= $value . "\\";
|
||||
}
|
||||
}
|
||||
|
||||
// 处理文件
|
||||
$file = $path . $className . '.php';
|
||||
self::buildFile($file, $content);
|
||||
|
||||
return APP_PATH . $file;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\console\command\make;
|
||||
|
||||
|
||||
use think\console\command\Command;
|
||||
|
||||
|
||||
class Model extends Command
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct("make:model");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?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\helper;
|
||||
|
||||
class Debug extends Helper
|
||||
{
|
||||
|
||||
private $colors = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'];
|
||||
private $started = [];
|
||||
private $count = -1;
|
||||
|
||||
/**
|
||||
* 启动调试会话的格式
|
||||
* @param string $id 会话的 id
|
||||
* @param string $message 要显示的消息
|
||||
* @param string $prefix 要使用的前缀
|
||||
* @return string
|
||||
*/
|
||||
public function start($id, $message, $prefix = 'RUN')
|
||||
{
|
||||
$this->started[$id] = ['border' => ++$this->count % count($this->colors)];
|
||||
|
||||
return sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加设置会话的进度条格式
|
||||
* @param string $id 会话的 id
|
||||
* @param string $buffer 要显示的消息
|
||||
* @param bool $error 是否输出错误
|
||||
* @param string $prefix 输出的前缀
|
||||
* @param string $errorPrefix 输出错误的前缀
|
||||
* @return string
|
||||
*/
|
||||
public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR')
|
||||
{
|
||||
$message = '';
|
||||
|
||||
if ($error) {
|
||||
if (isset($this->started[$id]['out'])) {
|
||||
$message .= "\n";
|
||||
unset($this->started[$id]['out']);
|
||||
}
|
||||
if (!isset($this->started[$id]['err'])) {
|
||||
$message .= sprintf("%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix);
|
||||
$this->started[$id]['err'] = true;
|
||||
}
|
||||
|
||||
$message .= str_replace("\n", sprintf("\n%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix), $buffer);
|
||||
} else {
|
||||
if (isset($this->started[$id]['err'])) {
|
||||
$message .= "\n";
|
||||
unset($this->started[$id]['err']);
|
||||
}
|
||||
if (!isset($this->started[$id]['out'])) {
|
||||
$message .= sprintf("%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix);
|
||||
$this->started[$id]['out'] = true;
|
||||
}
|
||||
|
||||
$message .= str_replace("\n", sprintf("\n%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix), $buffer);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止一个会话
|
||||
* @param string $id 会话的 id
|
||||
* @param string $message 要显示的消息
|
||||
* @param bool $successful 是否显示成功消息
|
||||
* @param string $prefix 前缀
|
||||
* @return string
|
||||
*/
|
||||
public function stop($id, $message, $successful, $prefix = 'RES')
|
||||
{
|
||||
$trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : '';
|
||||
|
||||
if ($successful) {
|
||||
return sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
|
||||
}
|
||||
|
||||
$message = sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
|
||||
|
||||
unset($this->started[$id]['out'], $this->started[$id]['err']);
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @return string
|
||||
*/
|
||||
private function getBorder($id)
|
||||
{
|
||||
return sprintf('<bg=%s> </>', $this->colors[$this->started[$id]['border']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'debug_formatter';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | TopThink [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2015 http://www.topthink.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: zhangyajun <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\console\helper;
|
||||
|
||||
use think\console\helper\descriptor\Descriptor as OutputDescriptor;
|
||||
use think\console\Output;
|
||||
|
||||
class Descriptor extends Helper
|
||||
{
|
||||
|
||||
/**
|
||||
* @var OutputDescriptor
|
||||
*/
|
||||
private $descriptor;
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->descriptor = new OutputDescriptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* 描述
|
||||
* @param Output $output
|
||||
* @param object $object
|
||||
* @param array $options
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function describe(Output $output, $object, array $options = [])
|
||||
{
|
||||
$options = array_merge([
|
||||
'raw_text' => false
|
||||
], $options);
|
||||
|
||||
$this->descriptor->describe($output, $object, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'descriptor';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\console\helper;
|
||||
|
||||
use think\console\output\Formatter as OutputFormatter;
|
||||
|
||||
class Formatter extends Helper
|
||||
{
|
||||
|
||||
/**
|
||||
* 设置消息在某一节的格式
|
||||
* @param string $section 节名称
|
||||
* @param string $message 消息
|
||||
* @param string $style 样式
|
||||
* @return string
|
||||
*/
|
||||
public function formatSection($section, $message, $style = 'info')
|
||||
{
|
||||
return sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置消息作为文本块的格式
|
||||
* @param string|array $messages 消息
|
||||
* @param string $style 样式
|
||||
* @param bool $large 是否返回一个大段文本
|
||||
* @return string The formatter message
|
||||
*/
|
||||
public function formatBlock($messages, $style, $large = false)
|
||||
{
|
||||
if (!is_array($messages)) {
|
||||
$messages = [$messages];
|
||||
}
|
||||
|
||||
$len = 0;
|
||||
$lines = [];
|
||||
foreach ($messages as $message) {
|
||||
$message = OutputFormatter::escape($message);
|
||||
$lines[] = sprintf($large ? ' %s ' : ' %s ', $message);
|
||||
$len = max($this->strlen($message) + ($large ? 4 : 2), $len);
|
||||
}
|
||||
|
||||
$messages = $large ? [str_repeat(' ', $len)] : [];
|
||||
for ($i = 0; isset($lines[$i]); ++$i) {
|
||||
$messages[] = $lines[$i] . str_repeat(' ', $len - $this->strlen($lines[$i]));
|
||||
}
|
||||
if ($large) {
|
||||
$messages[] = str_repeat(' ', $len);
|
||||
}
|
||||
|
||||
for ($i = 0; isset($messages[$i]); ++$i) {
|
||||
$messages[$i] = sprintf('<%s>%s</%s>', $style, $messages[$i], $style);
|
||||
}
|
||||
|
||||
return implode("\n", $messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'formatter';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?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\helper;
|
||||
|
||||
use think\console\helper\Set as HelperSet;
|
||||
use think\console\output\Formatter;
|
||||
|
||||
abstract class Helper
|
||||
{
|
||||
|
||||
protected $helperSet = null;
|
||||
|
||||
/**
|
||||
* 设置与此助手关联的助手集。
|
||||
* @param HelperSet $helperSet
|
||||
*/
|
||||
public function setHelperSet(HelperSet $helperSet = null)
|
||||
{
|
||||
$this->helperSet = $helperSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取与此助手关联的助手集。
|
||||
* @return HelperSet
|
||||
*/
|
||||
public function getHelperSet()
|
||||
{
|
||||
return $this->helperSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取名称
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getName();
|
||||
|
||||
/**
|
||||
* 返回字符串的长度
|
||||
* @param string $string
|
||||
* @return int
|
||||
*/
|
||||
public static function strlen($string)
|
||||
{
|
||||
if (!function_exists('mb_strwidth')) {
|
||||
return strlen($string);
|
||||
}
|
||||
|
||||
if (false === $encoding = mb_detect_encoding($string)) {
|
||||
return strlen($string);
|
||||
}
|
||||
|
||||
return mb_strwidth($string, $encoding);
|
||||
}
|
||||
|
||||
public static function formatTime($secs)
|
||||
{
|
||||
static $timeFormats = [
|
||||
[0, '< 1 sec'],
|
||||
[2, '1 sec'],
|
||||
[59, 'secs', 1],
|
||||
[60, '1 min'],
|
||||
[3600, 'mins', 60],
|
||||
[5400, '1 hr'],
|
||||
[86400, 'hrs', 3600],
|
||||
[129600, '1 day'],
|
||||
[604800, 'days', 86400],
|
||||
];
|
||||
|
||||
foreach ($timeFormats as $format) {
|
||||
if ($secs >= $format[0]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (2 == count($format)) {
|
||||
return $format[1];
|
||||
}
|
||||
|
||||
return ceil($secs / $format[2]) . ' ' . $format[1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function formatMemory($memory)
|
||||
{
|
||||
if ($memory >= 1024 * 1024 * 1024) {
|
||||
return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024);
|
||||
}
|
||||
|
||||
if ($memory >= 1024 * 1024) {
|
||||
return sprintf('%.1f MiB', $memory / 1024 / 1024);
|
||||
}
|
||||
|
||||
if ($memory >= 1024) {
|
||||
return sprintf('%d KiB', $memory / 1024);
|
||||
}
|
||||
|
||||
return sprintf('%d B', $memory);
|
||||
}
|
||||
|
||||
public static function strlenWithoutDecoration(Formatter $formatter, $string)
|
||||
{
|
||||
$isDecorated = $formatter->isDecorated();
|
||||
$formatter->setDecorated(false);
|
||||
// remove <...> formatting
|
||||
$string = $formatter->format($string);
|
||||
// remove already formatted characters
|
||||
$string = preg_replace("/\033\[[^m]*m/", '', $string);
|
||||
$formatter->setDecorated($isDecorated);
|
||||
|
||||
return self::strlen($string);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?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\helper;
|
||||
|
||||
use think\console\Output;
|
||||
use think\Process as ThinkProcess;
|
||||
use think\process\Builder as ProcessBuilder;
|
||||
use think\process\exception\Failed as ProcessFailedException;
|
||||
|
||||
class Process extends Helper
|
||||
{
|
||||
|
||||
/**
|
||||
* 运行一个外部进程。
|
||||
* @param Output $output 一个Output实例
|
||||
* @param string|array|ThinkProcess $cmd 指令
|
||||
* @param string|null $error 错误信息
|
||||
* @param callable|null $callback 回调
|
||||
* @param int $verbosity
|
||||
* @return ThinkProcess
|
||||
*/
|
||||
public function run(Output $output, $cmd, $error = null, $callback = null, $verbosity = Output::VERBOSITY_VERY_VERBOSE)
|
||||
{
|
||||
/** @var Debug $formatter */
|
||||
$formatter = $this->getHelperSet()->get('debug_formatter');
|
||||
|
||||
if (is_array($cmd)) {
|
||||
$process = ProcessBuilder::create($cmd)->getProcess();
|
||||
} elseif ($cmd instanceof ThinkProcess) {
|
||||
$process = $cmd;
|
||||
} else {
|
||||
$process = new ThinkProcess($cmd);
|
||||
}
|
||||
|
||||
if ($verbosity <= $output->getVerbosity()) {
|
||||
$output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine())));
|
||||
}
|
||||
|
||||
if ($output->isDebug()) {
|
||||
$callback = $this->wrapCallback($output, $process, $callback);
|
||||
}
|
||||
|
||||
$process->run($callback);
|
||||
|
||||
if ($verbosity <= $output->getVerbosity()) {
|
||||
$message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode());
|
||||
$output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful()));
|
||||
}
|
||||
|
||||
if (!$process->isSuccessful() && null !== $error) {
|
||||
$output->writeln(sprintf('<error>%s</error>', $this->escapeString($error)));
|
||||
}
|
||||
|
||||
return $process;
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行指令
|
||||
* @param Output $output
|
||||
* @param string|ThinkProcess $cmd
|
||||
* @param string|null $error
|
||||
* @param callable|null $callback
|
||||
* @return ThinkProcess
|
||||
*/
|
||||
public function mustRun(Output $output, $cmd, $error = null, $callback = null)
|
||||
{
|
||||
$process = $this->run($output, $cmd, $error, $callback);
|
||||
|
||||
if (!$process->isSuccessful()) {
|
||||
throw new ProcessFailedException($process);
|
||||
}
|
||||
|
||||
return $process;
|
||||
}
|
||||
|
||||
/**
|
||||
* 包装过程回调来添加调试输出
|
||||
* @param Output $output
|
||||
* @param ThinkProcess $process
|
||||
* @param callable|null $callback
|
||||
* @return callable
|
||||
*/
|
||||
public function wrapCallback(Output $output, ThinkProcess $process, $callback = null)
|
||||
{
|
||||
/** @var Debug $formatter */
|
||||
$formatter = $this->getHelperSet()->get('debug_formatter');
|
||||
|
||||
return function ($type, $buffer) use ($output, $process, $callback, $formatter) {
|
||||
$output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), ThinkProcess::ERR === $type));
|
||||
|
||||
if (null !== $callback) {
|
||||
call_user_func($callback, $type, $buffer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private function escapeString($str)
|
||||
{
|
||||
return str_replace('<', '\\<', $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'process';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
<?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\helper;
|
||||
|
||||
use think\console\helper\question\Choice as ChoiceQuestion;
|
||||
use think\console\helper\question\Question as OutputQuestion;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\console\output\formatter\Style as OutputFormatterStyle;
|
||||
|
||||
class Question extends Helper
|
||||
{
|
||||
|
||||
private $inputStream;
|
||||
private static $shell;
|
||||
private static $stty;
|
||||
|
||||
/**
|
||||
* 向用户提问
|
||||
* @param Input $input
|
||||
* @param Output $output
|
||||
* @param OutputQuestion $question
|
||||
* @return string
|
||||
*/
|
||||
public function ask(Input $input, Output $output, OutputQuestion $question)
|
||||
{
|
||||
if (!$input->isInteractive()) {
|
||||
return $question->getDefault();
|
||||
}
|
||||
|
||||
if (!$question->getValidator()) {
|
||||
return $this->doAsk($output, $question);
|
||||
}
|
||||
|
||||
$interviewer = function () use ($output, $question) {
|
||||
return $this->doAsk($output, $question);
|
||||
};
|
||||
|
||||
return $this->validateAttempts($interviewer, $output, $question);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输入流
|
||||
* @param resource $stream
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setInputStream($stream)
|
||||
{
|
||||
if (!is_resource($stream)) {
|
||||
throw new \InvalidArgumentException('Input stream must be a valid resource.');
|
||||
}
|
||||
|
||||
$this->inputStream = $stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输入流
|
||||
* @return resource
|
||||
*/
|
||||
public function getInputStream()
|
||||
{
|
||||
return $this->inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'question';
|
||||
}
|
||||
|
||||
/**
|
||||
* 提问
|
||||
* @param Output $output
|
||||
* @param OutputQuestion $question
|
||||
* @return bool|mixed|null|string
|
||||
* @throws \Exception
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function doAsk(Output $output, OutputQuestion $question)
|
||||
{
|
||||
$this->writePrompt($output, $question);
|
||||
|
||||
$inputStream = $this->inputStream ?: STDIN;
|
||||
$autocomplete = $question->getAutocompleterValues();
|
||||
|
||||
if (null === $autocomplete || !$this->hasSttyAvailable()) {
|
||||
$ret = false;
|
||||
if ($question->isHidden()) {
|
||||
try {
|
||||
$ret = trim($this->getHiddenResponse($output, $inputStream));
|
||||
} catch (\RuntimeException $e) {
|
||||
if (!$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($output, $question, $inputStream));
|
||||
}
|
||||
|
||||
$ret = strlen($ret) > 0 ? $ret : $question->getDefault();
|
||||
|
||||
if ($normalizer = $question->getNormalizer()) {
|
||||
return $normalizer($ret);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示提示
|
||||
* @param Output $output
|
||||
* @param OutputQuestion $question
|
||||
*/
|
||||
protected function writePrompt(Output $output, OutputQuestion $question)
|
||||
{
|
||||
$message = $question->getQuestion();
|
||||
|
||||
if ($question instanceof ChoiceQuestion) {
|
||||
$width = max(array_map('strlen', array_keys($question->getChoices())));
|
||||
|
||||
$messages = (array) $question->getQuestion();
|
||||
foreach ($question->getChoices() as $key => $value) {
|
||||
$messages[] = sprintf(" [<info>%-${width}s</info>] %s", $key, $value);
|
||||
}
|
||||
|
||||
$output->writeln($messages);
|
||||
|
||||
$message = $question->getPrompt();
|
||||
}
|
||||
|
||||
$output->write($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出错误
|
||||
* @param Output $output
|
||||
* @param \Exception $error
|
||||
*/
|
||||
protected function writeError(Output $output, \Exception $error)
|
||||
{
|
||||
if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
|
||||
$message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
|
||||
} else {
|
||||
$message = '<error>' . $error->getMessage() . '</error>';
|
||||
}
|
||||
|
||||
$output->writeln($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动完成问题
|
||||
* @param Output $output
|
||||
* @param OutputQuestion $question
|
||||
* @param $inputStream
|
||||
* @return string
|
||||
*/
|
||||
private function autocomplete(Output $output, OutputQuestion $question, $inputStream)
|
||||
{
|
||||
$autocomplete = $question->getAutocompleterValues();
|
||||
$ret = '';
|
||||
|
||||
$i = 0;
|
||||
$ofs = -1;
|
||||
$matches = $autocomplete;
|
||||
$numMatches = count($matches);
|
||||
|
||||
$sttyMode = shell_exec('stty -g');
|
||||
|
||||
shell_exec('stty -icanon -echo');
|
||||
|
||||
$output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));
|
||||
|
||||
while (!feof($inputStream)) {
|
||||
$c = fread($inputStream, 1);
|
||||
|
||||
if ("\177" === $c) {
|
||||
if (0 === $numMatches && 0 !== $i) {
|
||||
$i--;
|
||||
$output->write("\033[1D");
|
||||
}
|
||||
|
||||
if (0 === $i) {
|
||||
$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];
|
||||
$output->write(substr($ret, $i));
|
||||
$i = strlen($ret);
|
||||
}
|
||||
|
||||
if ("\n" === $c) {
|
||||
$output->write($c);
|
||||
break;
|
||||
}
|
||||
|
||||
$numMatches = 0;
|
||||
}
|
||||
|
||||
continue;
|
||||
} else {
|
||||
$output->write($c);
|
||||
$ret .= $c;
|
||||
$i++;
|
||||
|
||||
$numMatches = 0;
|
||||
$ofs = 0;
|
||||
|
||||
foreach ($autocomplete as $value) {
|
||||
if (0 === strpos($value, $ret) && strlen($value) !== $i) {
|
||||
$matches[$numMatches++] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$output->write("\033[K");
|
||||
|
||||
if ($numMatches > 0 && -1 !== $ofs) {
|
||||
$output->write("\0337");
|
||||
$output->write('<hl>' . substr($matches[$ofs], $i) . '</hl>');
|
||||
$output->write("\0338");
|
||||
}
|
||||
}
|
||||
|
||||
shell_exec(sprintf('stty %s', $sttyMode));
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从用户获取隐藏的响应
|
||||
* @param Output $output
|
||||
* @return string
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function getHiddenResponse(Output $output, $inputStream)
|
||||
{
|
||||
if ('\\' === DS) {
|
||||
$exe = __DIR__ . '/../bin/hiddeninput.exe';
|
||||
|
||||
if ('phar:' === substr(__FILE__, 0, 5)) {
|
||||
$tmpExe = sys_get_temp_dir() . '/hiddeninput.exe';
|
||||
copy($exe, $tmpExe);
|
||||
$exe = $tmpExe;
|
||||
}
|
||||
|
||||
$value = rtrim(shell_exec($exe));
|
||||
$output->writeln('');
|
||||
|
||||
if (isset($tmpExe)) {
|
||||
unlink($tmpExe);
|
||||
}
|
||||
|
||||
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);
|
||||
$output->writeln('');
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (false !== $shell = $this->getShell()) {
|
||||
$readCmd = 'csh' === $shell ? '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));
|
||||
$output->writeln('');
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
throw new \RuntimeException('Unable to hide the response.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证重试次数
|
||||
* @param callable $interviewer
|
||||
* @param Output $output
|
||||
* @param OutputQuestion $question
|
||||
* @return string
|
||||
* @throws null
|
||||
*/
|
||||
private function validateAttempts($interviewer, Output $output, OutputQuestion $question)
|
||||
{
|
||||
$error = null;
|
||||
$attempts = $question->getMaxAttempts();
|
||||
while (null === $attempts || $attempts--) {
|
||||
if (null !== $error) {
|
||||
$this->writeError($output, $error);
|
||||
}
|
||||
|
||||
try {
|
||||
return call_user_func($question->getValidator(), $interviewer());
|
||||
} catch (\Exception $error) {
|
||||
}
|
||||
}
|
||||
|
||||
throw $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个有效的 unix 终端。
|
||||
* @return string|bool
|
||||
*/
|
||||
private function getShell()
|
||||
{
|
||||
if (null !== self::$shell) {
|
||||
return self::$shell;
|
||||
}
|
||||
|
||||
self::$shell = false;
|
||||
|
||||
if (file_exists('/usr/bin/env')) {
|
||||
// handle other OSs with bash/zsh/ksh/csh if available to hide the answer
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查有用的stty
|
||||
* @return bool
|
||||
*/
|
||||
private function hasSttyAvailable()
|
||||
{
|
||||
if (null !== self::$stty) {
|
||||
return self::$stty;
|
||||
}
|
||||
|
||||
exec('stty 2>&1', $output, $exitcode);
|
||||
|
||||
return self::$stty = 0 === $exitcode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?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\helper;
|
||||
|
||||
use think\console\command\Command;
|
||||
|
||||
class Set implements \IteratorAggregate
|
||||
{
|
||||
|
||||
private $helpers = [];
|
||||
private $command;
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
* @param Helper[] $helpers 助手实例数组
|
||||
*/
|
||||
public function __construct(array $helpers = [])
|
||||
{
|
||||
/**
|
||||
* @var int|string $alias
|
||||
* @var Helper $helper
|
||||
*/
|
||||
foreach ($helpers as $alias => $helper) {
|
||||
$this->set($helper, is_int($alias) ? null : $alias);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个助手
|
||||
* @param Helper $helper 助手实例
|
||||
* @param string $alias 别名
|
||||
*/
|
||||
public function set(Helper $helper, $alias = null)
|
||||
{
|
||||
$this->helpers[$helper->getName()] = $helper;
|
||||
if (null !== $alias) {
|
||||
$this->helpers[$alias] = $helper;
|
||||
}
|
||||
|
||||
$helper->setHelperSet($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有某个助手
|
||||
* @param string $name 助手名称
|
||||
* @return bool
|
||||
*/
|
||||
public function has($name)
|
||||
{
|
||||
return isset($this->helpers[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取助手
|
||||
* @param string $name 助手名称
|
||||
* @return Helper
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
if (!$this->has($name)) {
|
||||
throw new \InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name));
|
||||
}
|
||||
|
||||
return $this->helpers[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置与这个助手关联的命令集
|
||||
* @param Command $command
|
||||
*/
|
||||
public function setCommand(Command $command = null)
|
||||
{
|
||||
$this->command = $command;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取与这个助手关联的命令集
|
||||
* @return Command
|
||||
*/
|
||||
public function getCommand()
|
||||
{
|
||||
return $this->command;
|
||||
}
|
||||
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->helpers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?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\helper\descriptor;
|
||||
|
||||
|
||||
use think\console\command\Command;
|
||||
use think\Console as ThinkConsole;
|
||||
|
||||
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()
|
||||
{
|
||||
if (null === $this->namespaces) {
|
||||
$this->inspectConsole();
|
||||
}
|
||||
|
||||
return $this->namespaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Command[]
|
||||
*/
|
||||
public function getCommands()
|
||||
{
|
||||
if (null === $this->commands) {
|
||||
$this->inspectConsole();
|
||||
}
|
||||
|
||||
return $this->commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return Command
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function getCommand($name)
|
||||
{
|
||||
if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
|
||||
throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name));
|
||||
}
|
||||
|
||||
return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name];
|
||||
}
|
||||
|
||||
private function inspectConsole()
|
||||
{
|
||||
$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 (!$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)
|
||||
{
|
||||
$namespacedCommands = [];
|
||||
foreach ($commands as $name => $command) {
|
||||
$key = $this->console->extractNamespace($name, 1);
|
||||
if (!$key) {
|
||||
$key = '_global';
|
||||
}
|
||||
|
||||
$namespacedCommands[$key][$name] = $command;
|
||||
}
|
||||
ksort($namespacedCommands);
|
||||
|
||||
foreach ($namespacedCommands as &$commandsSet) {
|
||||
ksort($commandsSet);
|
||||
}
|
||||
// unset reference to keep scope clear
|
||||
unset($commandsSet);
|
||||
|
||||
return $namespacedCommands;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
<?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\helper\descriptor;
|
||||
|
||||
use think\console\Output;
|
||||
use think\console\input\Argument as InputArgument;
|
||||
use think\console\input\Option as InputOption;
|
||||
use think\console\input\Definition as InputDefinition;
|
||||
use think\console\command\Command;
|
||||
use think\Console;
|
||||
use think\console\helper\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.', get_class($object)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出内容
|
||||
* @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 = isset($options['total_width']) ? $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\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 = isset($options['total_width']) ? $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\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->getCommands());
|
||||
|
||||
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->getCommands());
|
||||
|
||||
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 Command[] $commands
|
||||
* @return int
|
||||
*/
|
||||
private function getColumnWidth(array $commands)
|
||||
{
|
||||
$width = 0;
|
||||
foreach ($commands as $command) {
|
||||
$width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?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\helper\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()
|
||||
{
|
||||
return $this->choices;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置可否多选
|
||||
* @param bool $multiselect
|
||||
* @return self
|
||||
*/
|
||||
public function setMultiselect($multiselect)
|
||||
{
|
||||
$this->multiselect = $multiselect;
|
||||
$this->setValidator($this->getDefaultValidator());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取提示
|
||||
* @return string
|
||||
*/
|
||||
public function getPrompt()
|
||||
{
|
||||
return $this->prompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置提示
|
||||
* @param string $prompt
|
||||
* @return self
|
||||
*/
|
||||
public function setPrompt($prompt)
|
||||
{
|
||||
$this->prompt = $prompt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置错误提示信息
|
||||
* @param string $errorMessage
|
||||
* @return self
|
||||
*/
|
||||
public function setErrorMessage($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 (empty($result)) {
|
||||
throw new \InvalidArgumentException(sprintf($errorMessage, $value));
|
||||
}
|
||||
array_push($multiselectChoices, $result);
|
||||
}
|
||||
|
||||
if ($multiselect) {
|
||||
return $multiselectChoices;
|
||||
}
|
||||
|
||||
return current($multiselectChoices);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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\helper\question;
|
||||
|
||||
|
||||
class Confirmation extends Question
|
||||
{
|
||||
|
||||
private $trueAnswerRegex;
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
* @param string $question 问题
|
||||
* @param bool $default 默认答案
|
||||
* @param string $trueAnswerRegex 验证正则
|
||||
*/
|
||||
public function __construct($question, $default = true, $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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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\helper\question;
|
||||
|
||||
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'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?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;
|
||||
|
||||
private $name;
|
||||
private $mode;
|
||||
private $default;
|
||||
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($name, $mode = null, $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()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否必须
|
||||
* @return bool
|
||||
*/
|
||||
public function isRequired()
|
||||
{
|
||||
return self::REQUIRED === (self::REQUIRED & $this->mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该参数是否接受数组
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray()
|
||||
{
|
||||
return self::IS_ARRAY === (self::IS_ARRAY & $this->mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认值
|
||||
* @param mixed $default 默认值
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function setDefault($default = null)
|
||||
{
|
||||
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()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
$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($arguments = [])
|
||||
{
|
||||
$this->arguments = [];
|
||||
$this->requiredCount = 0;
|
||||
$this->hasOptional = false;
|
||||
$this->hasAnArrayArgument = false;
|
||||
$this->addArguments($arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加参数
|
||||
* @param Argument[] $arguments 参数数组
|
||||
* @api
|
||||
*/
|
||||
public function addArguments($arguments = [])
|
||||
{
|
||||
if (null !== $arguments) {
|
||||
foreach ($arguments as $argument) {
|
||||
$this->addArgument($argument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个参数
|
||||
* @param Argument $argument 参数
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function addArgument(Argument $argument)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
$arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;
|
||||
|
||||
return isset($arguments[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有的参数
|
||||
* @return Argument[] 参数数组
|
||||
*/
|
||||
public function getArguments()
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数数量
|
||||
* @return int
|
||||
*/
|
||||
public function getArgumentCount()
|
||||
{
|
||||
return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取必填的参数的数量
|
||||
* @return int
|
||||
*/
|
||||
public function getArgumentRequiredCount()
|
||||
{
|
||||
return $this->requiredCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数默认值
|
||||
* @return array
|
||||
*/
|
||||
public function getArgumentDefaults()
|
||||
{
|
||||
$values = [];
|
||||
foreach ($this->arguments as $argument) {
|
||||
$values[$argument->getName()] = $argument->getDefault();
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置选项
|
||||
* @param Option[] $options 选项数组
|
||||
*/
|
||||
public function setOptions($options = [])
|
||||
{
|
||||
$this->options = [];
|
||||
$this->shortcuts = [];
|
||||
$this->addOptions($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加选项
|
||||
* @param Option[] $options 选项数组
|
||||
* @api
|
||||
*/
|
||||
public function addOptions($options = [])
|
||||
{
|
||||
foreach ($options as $option) {
|
||||
$this->addOption($option);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个选项
|
||||
* @param Option $option 选项
|
||||
* @throws \LogicException
|
||||
* @api
|
||||
*/
|
||||
public function addOption(Option $option)
|
||||
{
|
||||
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($name)
|
||||
{
|
||||
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($name)
|
||||
{
|
||||
return isset($this->options[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有选项
|
||||
* @return Option[]
|
||||
* @api
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名称检查某个选项是否有短名称
|
||||
* @param string $name 短名称
|
||||
* @return bool
|
||||
*/
|
||||
public function hasShortcut($name)
|
||||
{
|
||||
return isset($this->shortcuts[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据短名称获取选项
|
||||
* @param string $shortcut 短名称
|
||||
* @return Option
|
||||
*/
|
||||
public function getOptionForShortcut($shortcut)
|
||||
{
|
||||
return $this->getOption($this->shortcutToName($shortcut));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有选项的默认值
|
||||
* @return array
|
||||
*/
|
||||
public function getOptionDefaults()
|
||||
{
|
||||
$values = [];
|
||||
foreach ($this->options as $option) {
|
||||
$values[$option->getName()] = $option->getDefault();
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据短名称获取选项名
|
||||
* @param string $shortcut 短名称
|
||||
* @return string
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
private function shortcutToName($shortcut)
|
||||
{
|
||||
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($short = false)
|
||||
{
|
||||
$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);
|
||||
}
|
||||
}
|
||||
@@ -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\input;
|
||||
|
||||
class Option
|
||||
{
|
||||
|
||||
const VALUE_NONE = 1;
|
||||
const VALUE_REQUIRED = 2;
|
||||
const VALUE_OPTIONAL = 4;
|
||||
const VALUE_IS_ARRAY = 8;
|
||||
|
||||
private $name;
|
||||
private $shortcut;
|
||||
private $mode;
|
||||
private $default;
|
||||
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 (0 === strpos($name, '--')) {
|
||||
$name = substr($name, 2);
|
||||
}
|
||||
|
||||
if (empty($name)) {
|
||||
throw new \InvalidArgumentException('An option name cannot be empty.');
|
||||
}
|
||||
|
||||
if (empty($shortcut)) {
|
||||
$shortcut = null;
|
||||
}
|
||||
|
||||
if (null !== $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()
|
||||
{
|
||||
return $this->shortcut;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取选项名
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可以设置值
|
||||
* @return bool 类型不是 self::VALUE_NONE 的时候返回true,其他均返回false
|
||||
*/
|
||||
public function acceptValue()
|
||||
{
|
||||
return $this->isValueRequired() || $this->isValueOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否必须
|
||||
* @return bool 类型是 self::VALUE_REQUIRED 的时候返回true,其他均返回false
|
||||
*/
|
||||
public function isValueRequired()
|
||||
{
|
||||
return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可选
|
||||
* @return bool 类型是 self::VALUE_OPTIONAL 的时候返回true,其他均返回false
|
||||
*/
|
||||
public function isValueOptional()
|
||||
{
|
||||
return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 选项值是否接受数组
|
||||
* @return bool 类型是 self::VALUE_IS_ARRAY 的时候返回true,其他均返回false
|
||||
*/
|
||||
public function isArray()
|
||||
{
|
||||
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()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查所给选项是否是当前这个
|
||||
* @param Option $option
|
||||
* @return bool
|
||||
*/
|
||||
public function equals(Option $option)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
<?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\Style;
|
||||
use think\console\output\formatter\Stack as StyleStack;
|
||||
|
||||
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->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 = isset($matches[3][$i][0]) ? $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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?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;
|
||||
|
||||
class Nothing extends Output
|
||||
{
|
||||
/** @noinspection PhpMissingParentConstructorInspection */
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setFormatter(Formatter $formatter)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormatter()
|
||||
{
|
||||
// to comply with the interface we must return a OutputFormatterInterface
|
||||
return new Formatter();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setDecorated($decorated)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isDecorated()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setVerbosity($level)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getVerbosity()
|
||||
{
|
||||
return self::VERBOSITY_QUIET;
|
||||
}
|
||||
|
||||
public function isQuiet()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isVerbose()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isVeryVerbose()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isDebug()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function writeln($messages, $options = self::OUTPUT_NORMAL)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?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 Stream
|
||||
{
|
||||
|
||||
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;
|
||||
private $formatter;
|
||||
|
||||
|
||||
private $stream;
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*/
|
||||
public function __construct($stream, Formatter $formatter = null)
|
||||
{
|
||||
if (!is_resource($stream) || 'stream' !== get_resource_type($stream)) {
|
||||
throw new \InvalidArgumentException('The StreamOutput class needs a stream as its first argument.');
|
||||
}
|
||||
|
||||
$this->stream = $stream;
|
||||
|
||||
$decorated = $this->hasColorSupport();
|
||||
|
||||
$this->formatter = $formatter ?: new Formatter();
|
||||
$this->formatter->setDecorated($decorated);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setFormatter(Formatter $formatter)
|
||||
{
|
||||
$this->formatter = $formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormatter()
|
||||
{
|
||||
return $this->formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setDecorated($decorated)
|
||||
{
|
||||
$this->formatter->setDecorated($decorated);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isDecorated()
|
||||
{
|
||||
return $this->formatter->isDecorated();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setVerbosity($level)
|
||||
{
|
||||
$this->verbosity = (int)$level;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getVerbosity()
|
||||
{
|
||||
return $this->verbosity;
|
||||
}
|
||||
|
||||
public function isQuiet()
|
||||
{
|
||||
return self::VERBOSITY_QUIET === $this->verbosity;
|
||||
}
|
||||
|
||||
public function isVerbose()
|
||||
{
|
||||
return self::VERBOSITY_VERBOSE <= $this->verbosity;
|
||||
}
|
||||
|
||||
public function isVeryVerbose()
|
||||
{
|
||||
return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
|
||||
}
|
||||
|
||||
public function isDebug()
|
||||
{
|
||||
return self::VERBOSITY_DEBUG <= $this->verbosity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function writeln($messages, $type = self::OUTPUT_NORMAL)
|
||||
{
|
||||
$this->write($messages, true, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
|
||||
{
|
||||
if (self::VERBOSITY_QUIET === $this->verbosity) {
|
||||
return;
|
||||
}
|
||||
|
||||
$messages = (array)$messages;
|
||||
|
||||
foreach ($messages as $message) {
|
||||
switch ($type) {
|
||||
case self::OUTPUT_NORMAL:
|
||||
$message = $this->formatter->format($message);
|
||||
break;
|
||||
case self::OUTPUT_RAW:
|
||||
break;
|
||||
case self::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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将消息写入到输出。
|
||||
* @param string $message 消息
|
||||
* @param bool $newline 是否另起一行
|
||||
*/
|
||||
protected function doWrite($message, $newline)
|
||||
{
|
||||
if (false === @fwrite($this->stream, $message . ($newline ? PHP_EOL : ''))) {
|
||||
throw new \RuntimeException('Unable to write output.');
|
||||
}
|
||||
|
||||
fflush($this->stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
public function getStream()
|
||||
{
|
||||
return $this->stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否支持着色
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasColorSupport()
|
||||
{
|
||||
if (DIRECTORY_SEPARATOR == '\\') {
|
||||
return false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI');
|
||||
}
|
||||
|
||||
return function_exists('posix_isatty') && @posix_isatty($this->stream);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
$this->styles = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 推一个样式进入堆栈
|
||||
* @param Style $style
|
||||
*/
|
||||
public function push(Style $style)
|
||||
{
|
||||
$this->styles[] = $style;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从堆栈中弹出一个样式
|
||||
* @param Style|null $style
|
||||
* @return Style
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function pop(Style $style = null)
|
||||
{
|
||||
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()
|
||||
{
|
||||
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()
|
||||
{
|
||||
return $this->emptyStyle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?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
|
||||
{
|
||||
|
||||
private 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],
|
||||
];
|
||||
private 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],
|
||||
];
|
||||
private 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($option)
|
||||
{
|
||||
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($option)
|
||||
{
|
||||
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($text)
|
||||
{
|
||||
$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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\controller;
|
||||
|
||||
use think\App;
|
||||
use think\Loader;
|
||||
/**
|
||||
* ThinkPHP Hprose控制器类
|
||||
*/
|
||||
abstract class Hprose
|
||||
{
|
||||
|
||||
protected $allowMethodList = '';
|
||||
protected $crossDomain = false;
|
||||
protected $P3P = false;
|
||||
protected $get = true;
|
||||
protected $debug = false;
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @access public
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//控制器初始化
|
||||
if (method_exists($this, '_initialize')) {
|
||||
$this->_initialize();
|
||||
}
|
||||
|
||||
//导入类库
|
||||
Loader::import('vendor.Hprose.HproseHttpServer');
|
||||
//实例化HproseHttpServer
|
||||
$server = new \HproseHttpServer();
|
||||
if ($this->allowMethodList) {
|
||||
$methods = $this->allowMethodList;
|
||||
} else {
|
||||
$methods = get_class_methods($this);
|
||||
$methods = array_diff($methods, ['__construct', '__call', '_initialize']);
|
||||
}
|
||||
$server->addMethods($methods, $this);
|
||||
if (App::$debug || $this->debug) {
|
||||
$server->setDebugEnabled(true);
|
||||
}
|
||||
// Hprose设置
|
||||
$server->setCrossDomainEnabled($this->crossDomain);
|
||||
$server->setP3PEnabled($this->P3P);
|
||||
$server->setGetEnabled($this->get);
|
||||
// 启动server
|
||||
$server->start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 魔术方法 有不存在的操作的时候执行
|
||||
* @access public
|
||||
* @param string $method 方法名
|
||||
* @param array $args 参数
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\controller;
|
||||
|
||||
use think\Loader;
|
||||
/**
|
||||
* ThinkPHP JsonRPC控制器类
|
||||
*/
|
||||
abstract class Jsonrpc
|
||||
{
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @access public
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//控制器初始化
|
||||
if (method_exists($this, '_initialize')) {
|
||||
$this->_initialize();
|
||||
}
|
||||
|
||||
//导入类库
|
||||
Loader::import('vendor.jsonrpc.jsonRPCServer');
|
||||
// 启动server
|
||||
\jsonRPCServer::handle($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 魔术方法 有不存在的操作的时候执行
|
||||
* @access public
|
||||
* @param string $method 方法名
|
||||
* @param array $args 参数
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\controller;
|
||||
|
||||
use think\Response;
|
||||
use think\Request;
|
||||
|
||||
abstract class Rest
|
||||
{
|
||||
|
||||
protected $method; // 当前请求类型
|
||||
protected $type; // 当前资源类型
|
||||
// 输出类型
|
||||
protected $restMethodList = 'get|post|put|delete';
|
||||
protected $restDefaultMethod = 'get';
|
||||
protected $restTypeList = 'html|xml|json|rss';
|
||||
protected $restDefaultType = 'html';
|
||||
protected $restOutputType = [ // REST允许输出的资源类型列表
|
||||
'xml' => 'application/xml',
|
||||
'json' => 'application/json',
|
||||
'html' => 'text/html',
|
||||
];
|
||||
|
||||
/**
|
||||
* 架构函数 取得模板对象实例
|
||||
* @access public
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// 资源类型检测
|
||||
$request = Request::instance();
|
||||
$ext = $request->ext();
|
||||
if ('' == $ext) {
|
||||
// 自动检测资源类型
|
||||
$this->type = $request->type();
|
||||
} elseif (!preg_match('/\(' . $this->restTypeList . '\)$/i', $ext)) {
|
||||
// 资源类型非法 则用默认资源类型访问
|
||||
$this->type = $this->restDefaultType;
|
||||
} else {
|
||||
$this->type = $ext;
|
||||
}
|
||||
// 请求方式检测
|
||||
$method = strtolower($request->method());
|
||||
if (false === stripos($this->restMethodList, $method)) {
|
||||
// 请求方式非法 则用默认请求方法
|
||||
$method = $this->restDefaultMethod;
|
||||
}
|
||||
$this->method = $method;
|
||||
}
|
||||
|
||||
/**
|
||||
* REST 调用
|
||||
* @access public
|
||||
* @param string $method 方法名
|
||||
* @param array $args 参数
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function _empty($method, $args)
|
||||
{
|
||||
if (method_exists($this, $method . '_' . $this->method . '_' . $this->type)) {
|
||||
// RESTFul方法支持
|
||||
$fun = $method . '_' . $this->method . '_' . $this->type;
|
||||
} elseif ($this->_method == $this->restDefaultMethod && method_exists($this, $method . '_' . $this->type)) {
|
||||
$fun = $method . '_' . $this->type;
|
||||
} elseif ($this->type == $this->restDefaultType && method_exists($this, $method . '_' . $this->method)) {
|
||||
$fun = $method . '_' . $this->method;
|
||||
}
|
||||
if (isset($fun)) {
|
||||
return $this->$fun();
|
||||
} else {
|
||||
// 抛出异常
|
||||
throw new \Exception('error action :' . $method);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出返回数据
|
||||
* @access protected
|
||||
* @param mixed $data 要返回的数据
|
||||
* @param String $type 返回类型 JSON XML
|
||||
* @param integer $code HTTP状态码
|
||||
* @return Response
|
||||
*/
|
||||
protected function response($data, $type = 'json', $code = 200)
|
||||
{
|
||||
return Response::create($data, $type)->code($code);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\controller;
|
||||
|
||||
use think\App;
|
||||
use think\Loader;
|
||||
/**
|
||||
* ThinkPHP RPC控制器类
|
||||
*/
|
||||
abstract class Rpc
|
||||
{
|
||||
|
||||
protected $allowMethodList = '';
|
||||
protected $debug = false;
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @access public
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//控制器初始化
|
||||
if (method_exists($this, '_initialize')) {
|
||||
$this->_initialize();
|
||||
}
|
||||
|
||||
//导入类库
|
||||
Loader::import('vendor.phprpc.phprpc_server');
|
||||
//实例化phprpc
|
||||
$server = new \PHPRPC_Server();
|
||||
if ($this->allowMethodList) {
|
||||
$methods = $this->allowMethodList;
|
||||
} else {
|
||||
$methods = get_class_methods($this);
|
||||
$methods = array_diff($methods, ['__construct', '__call', '_initialize']);
|
||||
}
|
||||
$server->add($methods, $this);
|
||||
|
||||
if (App::$debug || $this->debug) {
|
||||
$server->setDebugMode(true);
|
||||
}
|
||||
$server->setEnableGZIP(true);
|
||||
$server->start();
|
||||
echo $server->comment();
|
||||
}
|
||||
|
||||
/**
|
||||
* 魔术方法 有不存在的操作的时候执行
|
||||
* @access public
|
||||
* @param string $method 方法名
|
||||
* @param array $args 参数
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\controller;
|
||||
|
||||
/**
|
||||
* ThinkPHP Yar控制器类
|
||||
*/
|
||||
abstract class Yar
|
||||
{
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @access public
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//控制器初始化
|
||||
if (method_exists($this, '_initialize')) {
|
||||
$this->_initialize();
|
||||
}
|
||||
|
||||
//判断扩展是否存在
|
||||
if (!extension_loaded('yar')) {
|
||||
throw new Exception('not support yar');
|
||||
}
|
||||
|
||||
//实例化Yar_Server
|
||||
$server = new \Yar_Server($this);
|
||||
// 启动server
|
||||
$server->handle();
|
||||
}
|
||||
|
||||
/**
|
||||
* 魔术方法 有不存在的操作的时候执行
|
||||
* @access public
|
||||
* @param string $method 方法名
|
||||
* @param array $args 参数
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{}
|
||||
}
|
||||
@@ -0,0 +1,717 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\db;
|
||||
|
||||
use PDO;
|
||||
use think\Db;
|
||||
use think\db\Connection;
|
||||
use think\db\Query;
|
||||
use think\Exception;
|
||||
|
||||
abstract class Builder
|
||||
{
|
||||
// connection对象实例
|
||||
protected $connection;
|
||||
// 查询对象实例
|
||||
protected $query;
|
||||
|
||||
// 查询参数
|
||||
protected $options = [];
|
||||
|
||||
// 数据库表达式
|
||||
protected $exp = ['eq' => '=', 'neq' => '<>', 'gt' => '>', 'egt' => '>=', 'lt' => '<', 'elt' => '<=', 'notlike' => 'NOT LIKE', 'like' => 'LIKE', 'in' => 'IN', 'exp' => 'EXP', 'notin' => 'NOT IN', 'not in' => 'NOT IN', 'between' => 'BETWEEN', 'not between' => 'NOT BETWEEN', 'notbetween' => 'NOT BETWEEN', 'exists' => 'EXISTS', 'notexists' => 'NOT EXISTS', 'not exists' => 'NOT EXISTS', 'null' => 'NULL', 'notnull' => 'NOT NULL', 'not null' => 'NOT NULL', '> time' => '> TIME', '< time' => '< TIME', 'between time' => 'BETWEEN TIME', 'not between time' => 'NOT BETWEEN TIME', 'notbetween time' => 'NOT BETWEEN TIME'];
|
||||
|
||||
// SQL表达式
|
||||
protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%%LIMIT% %UNION%%LOCK%%COMMENT%';
|
||||
protected $insertSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%';
|
||||
protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%';
|
||||
protected $updateSql = 'UPDATE %TABLE% SET %SET% %JOIN% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%';
|
||||
protected $deleteSql = 'DELETE FROM %TABLE% %USING% %JOIN% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%';
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @access public
|
||||
* @param Connection $connection 数据库连接对象实例
|
||||
*/
|
||||
public function __construct(Connection $connection)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前的Query对象实例
|
||||
* @access protected
|
||||
* @param Query $query 当前查询对象实例
|
||||
* @return void
|
||||
*/
|
||||
public function setQuery(Query $query)
|
||||
{
|
||||
$this->query = $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写)
|
||||
* @access protected
|
||||
* @param string $sql sql语句
|
||||
* @return string
|
||||
*/
|
||||
protected function parseSqlTable($sql)
|
||||
{
|
||||
return $this->query->parseSqlTable($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据分析
|
||||
* @access protected
|
||||
* @param array $data 数据
|
||||
* @param array $options 查询参数
|
||||
* @return array
|
||||
*/
|
||||
protected function parseData($data, $options)
|
||||
{
|
||||
if (empty($data)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 获取绑定信息
|
||||
$bind = $this->query->getTableInfo($options['table'], 'bind');
|
||||
if ('*' == $options['field']) {
|
||||
$fields = array_keys($bind);
|
||||
} else {
|
||||
$fields = $options['field'];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($data as $key => $val) {
|
||||
if (!in_array($key, $fields, true)) {
|
||||
if ($options['strict']) {
|
||||
throw new Exception('fields not exists:[' . $key . ']');
|
||||
}
|
||||
} else {
|
||||
$item = $this->parseKey($key);
|
||||
if (isset($val[0]) && 'exp' == $val[0]) {
|
||||
$result[$item] = $val[1];
|
||||
} elseif (is_null($val)) {
|
||||
$result[$item] = 'NULL';
|
||||
} elseif (is_scalar($val)) {
|
||||
// 过滤非标量数据
|
||||
if ($this->query->isBind(substr($val, 1))) {
|
||||
$result[$item] = $val;
|
||||
} else {
|
||||
$this->query->bind($key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR);
|
||||
$result[$item] = ':' . $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段名分析
|
||||
* @access protected
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
protected function parseKey($key)
|
||||
{
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* value分析
|
||||
* @access protected
|
||||
* @param mixed $value
|
||||
* @param string $field
|
||||
* @return string|array
|
||||
*/
|
||||
protected function parseValue($value, $field = '')
|
||||
{
|
||||
if (is_string($value)) {
|
||||
$value = strpos($value, ':') === 0 && $this->query->isBind(substr($value, 1)) ? $value : $this->connection->quote($value);
|
||||
} elseif (is_array($value) && is_string($value[0]) && strtolower($value[0]) == 'exp') {
|
||||
$value = $value[1];
|
||||
} elseif (is_array($value)) {
|
||||
$value = array_map([$this, 'parseValue'], $value);
|
||||
} elseif (is_bool($value)) {
|
||||
$value = $value ? '1' : '0';
|
||||
} elseif (is_null($value)) {
|
||||
$value = 'null';
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* field分析
|
||||
* @access protected
|
||||
* @param mixed $fields
|
||||
* @return string
|
||||
*/
|
||||
protected function parseField($fields)
|
||||
{
|
||||
if ('*' == $fields || empty($fields)) {
|
||||
$fieldsStr = '*';
|
||||
} elseif (is_array($fields)) {
|
||||
// 支持 'field1'=>'field2' 这样的字段别名定义
|
||||
$array = [];
|
||||
foreach ($fields as $key => $field) {
|
||||
if (!is_numeric($key)) {
|
||||
$array[] = $this->parseKey($key) . ' AS ' . $this->parseKey($field);
|
||||
} else {
|
||||
$array[] = $this->parseKey($field);
|
||||
}
|
||||
}
|
||||
$fieldsStr = implode(',', $array);
|
||||
}
|
||||
return $fieldsStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* table分析
|
||||
* @access protected
|
||||
* @param mixed $table
|
||||
* @return string
|
||||
*/
|
||||
protected function parseTable($tables)
|
||||
{
|
||||
if (is_array($tables)) {
|
||||
// 支持别名定义
|
||||
foreach ($tables as $table => $alias) {
|
||||
$array[] = !is_numeric($table) ?
|
||||
$this->parseKey($table) . ' ' . $this->parseKey($alias) :
|
||||
$this->parseKey($alias);
|
||||
}
|
||||
$tables = $array;
|
||||
} elseif (is_string($tables)) {
|
||||
$tables = $this->parseSqlTable($tables);
|
||||
$tables = array_map([$this, 'parseKey'], explode(',', $tables));
|
||||
}
|
||||
return implode(',', $tables);
|
||||
}
|
||||
|
||||
/**
|
||||
* where分析
|
||||
* @access protected
|
||||
* @param mixed $where
|
||||
* @return string
|
||||
*/
|
||||
protected function parseWhere($where, $table)
|
||||
{
|
||||
$whereStr = $this->buildWhere($where, $table);
|
||||
return empty($whereStr) ? '' : ' WHERE ' . $whereStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成查询条件SQL
|
||||
* @access public
|
||||
* @param mixed $where
|
||||
* @param string $table
|
||||
* @return string
|
||||
*/
|
||||
public function buildWhere($where, $table)
|
||||
{
|
||||
if (empty($where)) {
|
||||
$where = [];
|
||||
}
|
||||
|
||||
if ($where instanceof Query) {
|
||||
return $this->buildWhere($where->getOptions('where'), $table);
|
||||
}
|
||||
|
||||
$whereStr = '';
|
||||
// 获取字段信息
|
||||
$fields = $this->query->getTableInfo($table, 'fields');
|
||||
$binds = $this->query->getTableInfo($table, 'bind');
|
||||
foreach ($where as $key => $val) {
|
||||
$str = [];
|
||||
foreach ($val as $field => $value) {
|
||||
if ($fields && in_array($field, $fields, true) && is_scalar($value) && !$this->query->isBind($field)) {
|
||||
$this->query->bind($field, $value, isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR);
|
||||
$value = ':' . $field;
|
||||
}
|
||||
if ($value instanceof \Closure) {
|
||||
// 使用闭包查询
|
||||
$query = new Query($this->connection);
|
||||
call_user_func_array($value, [ & $query]);
|
||||
$str[] = ' ' . $key . ' ( ' . $this->buildWhere($query->getOptions('where'), $table) . ' )';
|
||||
} else {
|
||||
if (strpos($field, '|')) {
|
||||
// 不同字段使用相同查询条件(OR)
|
||||
$array = explode('|', $field);
|
||||
$item = [];
|
||||
foreach ($array as $k) {
|
||||
$item[] = $this->parseWhereItem($k, $value);
|
||||
}
|
||||
$str[] = ' ' . $key . ' ( ' . implode(' OR ', $item) . ' )';
|
||||
} elseif (strpos($field, '&')) {
|
||||
// 不同字段使用相同查询条件(AND)
|
||||
$array = explode('&', $field);
|
||||
$item = [];
|
||||
foreach ($array as $k) {
|
||||
$item[] = $this->parseWhereItem($k, $value);
|
||||
}
|
||||
$str[] = ' ' . $key . ' ( ' . implode(' AND ', $item) . ' )';
|
||||
} else {
|
||||
// 对字段使用表达式查询
|
||||
$field = is_string($field) ? $field : '';
|
||||
$str[] = ' ' . $key . ' ' . $this->parseWhereItem($field, $value, $key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($key) + 1) : implode(' ', $str);
|
||||
}
|
||||
return $whereStr;
|
||||
}
|
||||
|
||||
// where子单元分析
|
||||
protected function parseWhereItem($field, $val, $rule = '')
|
||||
{
|
||||
// 字段分析
|
||||
$key = $field ? $this->parseKey($field) : '';
|
||||
|
||||
// 查询规则和条件
|
||||
if (!is_array($val)) {
|
||||
$val = ['=', $val];
|
||||
}
|
||||
list($exp, $value) = $val;
|
||||
|
||||
// 对一个字段使用多个查询条件
|
||||
if (is_array($exp)) {
|
||||
$item = array_pop($val);
|
||||
// 传入 or 或者 and
|
||||
if (is_string($item) && in_array($item, ['AND', 'and', 'OR', 'or'])) {
|
||||
$rule = $item;
|
||||
} else {
|
||||
array_push($val, $item);
|
||||
}
|
||||
foreach ($val as $item) {
|
||||
$str[] = $this->parseWhereItem($key, $item, $rule);
|
||||
}
|
||||
return '( ' . implode(' ' . $rule . ' ', $str) . ' )';
|
||||
}
|
||||
|
||||
// 检测操作符
|
||||
if (!in_array($exp, $this->exp)) {
|
||||
$exp = strtolower($exp);
|
||||
if (isset($this->exp[$exp])) {
|
||||
$exp = $this->exp[$exp];
|
||||
} else {
|
||||
throw new Exception('where express error:' . $exp);
|
||||
}
|
||||
}
|
||||
|
||||
$whereStr = '';
|
||||
if (in_array($exp, ['=', '<>', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE'])) {
|
||||
// 比较运算 及 模糊匹配
|
||||
$whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field);
|
||||
} elseif ('EXP' == $exp) {
|
||||
// 表达式查询
|
||||
$whereStr .= '( ' . $key . ' ' . $value . ' )';
|
||||
} elseif (in_array($exp, ['NOT NULL', 'NULL'])) {
|
||||
// NULL 查询
|
||||
$whereStr .= $key . ' IS ' . $exp;
|
||||
} elseif (in_array($exp, ['NOT IN', 'IN'])) {
|
||||
// IN 查询
|
||||
if ($value instanceof \Closure) {
|
||||
$whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value);
|
||||
} else {
|
||||
$value = is_array($value) ? $value : explode(',', $value);
|
||||
$zone = implode(',', $this->parseValue($value, $field));
|
||||
$whereStr .= $key . ' ' . $exp . ' (' . $zone . ')';
|
||||
}
|
||||
} elseif (in_array($exp, ['NOT BETWEEN', 'BETWEEN'])) {
|
||||
// BETWEEN 查询
|
||||
$data = is_array($value) ? $value : explode(',', $value);
|
||||
$whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($data[0], $field) . ' AND ' . $this->parseValue($data[1], $field);
|
||||
} elseif (in_array($exp, ['NOT EXISTS', 'EXISTS'])) {
|
||||
// EXISTS 查询
|
||||
if ($value instanceof \Closure) {
|
||||
$whereStr .= $exp . ' ' . $this->parseClosure($value);
|
||||
} else {
|
||||
$whereStr .= $exp . ' (' . $value . ')';
|
||||
}
|
||||
} elseif (in_array($exp, ['< TIME', '> TIME'])) {
|
||||
$whereStr .= $key . ' ' . substr($exp, 0, 1) . ' ' . $this->parseDateTime($value, $field);
|
||||
} elseif (in_array($exp, ['BETWEEN TIME', 'NOT BETWEEN TIME'])) {
|
||||
if (is_string($value)) {
|
||||
$value = explode(',', $value);
|
||||
}
|
||||
$whereStr .= $key . ' ' . substr($exp, 0, -4) . $this->parseDateTime($value[0], $field) . ' AND ' . $this->parseDateTime($value[1], $field);
|
||||
}
|
||||
return $whereStr;
|
||||
}
|
||||
|
||||
// 执行闭包子查询
|
||||
protected function parseClosure($call, $show = true)
|
||||
{
|
||||
$query = new Query($this->connection);
|
||||
call_user_func_array($call, [ & $query]);
|
||||
return $query->buildSql($show);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期时间条件解析
|
||||
* @access protected
|
||||
* @param string $value
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
protected function parseDateTime($value, $key)
|
||||
{
|
||||
// 获取时间字段类型
|
||||
$type = $this->query->getTableInfo('', 'type');
|
||||
if (isset($type[$key])) {
|
||||
$value = strtotime($value) ?: $value;
|
||||
if (preg_match('/(datetime|timestamp)/is', $type[$key])) {
|
||||
// 日期及时间戳类型
|
||||
$value = date('Y-m-d H:i:s', $value);
|
||||
} elseif (preg_match('/(date)/is', $type[$key])) {
|
||||
// 日期及时间戳类型
|
||||
$value = date('Y-m-d', $value);
|
||||
}
|
||||
}
|
||||
return is_int($value) ? $value : $this->connection->quote($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* limit分析
|
||||
* @access protected
|
||||
* @param mixed $lmit
|
||||
* @return string
|
||||
*/
|
||||
protected function parseLimit($limit)
|
||||
{
|
||||
return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* join分析
|
||||
* @access protected
|
||||
* @param mixed $join
|
||||
* @return string
|
||||
*/
|
||||
protected function parseJoin($join)
|
||||
{
|
||||
$joinStr = '';
|
||||
if (!empty($join)) {
|
||||
$joinStr = ' ' . implode(' ', $join) . ' ';
|
||||
}
|
||||
return $joinStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* order分析
|
||||
* @access protected
|
||||
* @param mixed $order
|
||||
* @return string
|
||||
*/
|
||||
protected function parseOrder($order)
|
||||
{
|
||||
if (is_array($order)) {
|
||||
$array = [];
|
||||
foreach ($order as $key => $val) {
|
||||
if (is_numeric($key)) {
|
||||
if (false === strpos($val, '(')) {
|
||||
$array[] = $this->parseKey($val);
|
||||
} elseif ('[rand]' == $val) {
|
||||
$array[] = $this->parseRand();
|
||||
}
|
||||
} else {
|
||||
$sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : '';
|
||||
$array[] = $this->parseKey($key) . ' ' . $sort;
|
||||
}
|
||||
}
|
||||
$order = implode(',', $array);
|
||||
}
|
||||
return !empty($order) ? ' ORDER BY ' . $order : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* group分析
|
||||
* @access protected
|
||||
* @param mixed $group
|
||||
* @return string
|
||||
*/
|
||||
protected function parseGroup($group)
|
||||
{
|
||||
return !empty($group) ? ' GROUP BY ' . $group : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* having分析
|
||||
* @access protected
|
||||
* @param string $having
|
||||
* @return string
|
||||
*/
|
||||
protected function parseHaving($having)
|
||||
{
|
||||
return !empty($having) ? ' HAVING ' . $having : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* comment分析
|
||||
* @access protected
|
||||
* @param string $comment
|
||||
* @return string
|
||||
*/
|
||||
protected function parseComment($comment)
|
||||
{
|
||||
return !empty($comment) ? ' /* ' . $comment . ' */' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* distinct分析
|
||||
* @access protected
|
||||
* @param mixed $distinct
|
||||
* @return string
|
||||
*/
|
||||
protected function parseDistinct($distinct)
|
||||
{
|
||||
return !empty($distinct) ? ' DISTINCT ' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* union分析
|
||||
* @access protected
|
||||
* @param mixed $union
|
||||
* @return string
|
||||
*/
|
||||
protected function parseUnion($union)
|
||||
{
|
||||
if (empty($union)) {
|
||||
return '';
|
||||
}
|
||||
$type = $union['type'];
|
||||
unset($union['type']);
|
||||
foreach ($union as $u) {
|
||||
if ($u instanceof \Closure) {
|
||||
$sql[] = $type . ' ' . $this->parseClosure($u, false);
|
||||
} elseif (is_string($u)) {
|
||||
$sql[] = $type . ' ' . $this->parseSqlTable($u);
|
||||
}
|
||||
}
|
||||
return implode(' ', $sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* index分析,可在操作链中指定需要强制使用的索引
|
||||
* @access protected
|
||||
* @param mixed $index
|
||||
* @return string
|
||||
*/
|
||||
protected function parseForce($index)
|
||||
{
|
||||
if (empty($index)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (is_array($index)) {
|
||||
$index = join(",", $index);
|
||||
}
|
||||
|
||||
return sprintf(" FORCE INDEX ( %s ) ", $index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置锁机制
|
||||
* @access protected
|
||||
* @param bool $locl
|
||||
* @return string
|
||||
*/
|
||||
protected function parseLock($lock = false)
|
||||
{
|
||||
return $lock ? ' FOR UPDATE ' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成查询SQL
|
||||
* @access public
|
||||
* @param array $options 表达式
|
||||
* @return string
|
||||
*/
|
||||
public function select($options = [])
|
||||
{
|
||||
$sql = str_replace(
|
||||
['%TABLE%', '%DISTINCT%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'],
|
||||
[
|
||||
$this->parseTable($options['table']),
|
||||
$this->parseDistinct($options['distinct']),
|
||||
$this->parseField($options['field']),
|
||||
$this->parseJoin($options['join']),
|
||||
$this->parseWhere($options['where'], $options['table']),
|
||||
$this->parseGroup($options['group']),
|
||||
$this->parseHaving($options['having']),
|
||||
$this->parseOrder($options['order']),
|
||||
$this->parseLimit($options['limit']),
|
||||
$this->parseUnion($options['union']),
|
||||
$this->parseLock($options['lock']),
|
||||
$this->parseComment($options['comment']),
|
||||
$this->parseForce($options['force']),
|
||||
], $this->selectSql);
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成insert SQL
|
||||
* @access public
|
||||
* @param array $data 数据
|
||||
* @param array $options 表达式
|
||||
* @param bool $replace 是否replace
|
||||
* @return string
|
||||
*/
|
||||
public function insert(array $data, $options = [], $replace = false)
|
||||
{
|
||||
// 分析并处理数据
|
||||
$data = $this->parseData($data, $options);
|
||||
if (empty($data)) {
|
||||
return 0;
|
||||
}
|
||||
$fields = array_keys($data);
|
||||
$values = array_values($data);
|
||||
|
||||
$sql = str_replace(
|
||||
['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'],
|
||||
[
|
||||
$replace ? 'REPLACE' : 'INSERT',
|
||||
$this->parseTable($options['table']),
|
||||
implode(' , ', $fields),
|
||||
implode(' , ', $values),
|
||||
$this->parseComment($options['comment']),
|
||||
], $this->insertSql);
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成insertall SQL
|
||||
* @access public
|
||||
* @param array $dataSet 数据集
|
||||
* @param array $options 表达式
|
||||
* @return string
|
||||
*/
|
||||
public function insertAll($dataSet, $options)
|
||||
{
|
||||
// 获取合法的字段
|
||||
if ('*' == $options['field']) {
|
||||
$fields = $this->query->getTableInfo($options['table'], 'fields');
|
||||
} else {
|
||||
$fields = $options['field'];
|
||||
}
|
||||
|
||||
foreach ($dataSet as &$data) {
|
||||
foreach ($data as $key => $val) {
|
||||
if (!in_array($key, $fields, true)) {
|
||||
if ($options['strict']) {
|
||||
throw new Exception('fields not exists:[' . $key . ']');
|
||||
}
|
||||
unset($data[$key]);
|
||||
} elseif (is_scalar($val)) {
|
||||
$data[$key] = $this->parseValue($val, $key);
|
||||
} else {
|
||||
// 过滤掉非标量数据
|
||||
unset($data[$key]);
|
||||
}
|
||||
}
|
||||
$value = array_values($data);
|
||||
$values[] = 'SELECT ' . implode(',', $value);
|
||||
}
|
||||
$fields = array_map([$this, 'parseKey'], array_keys(reset($dataSet)));
|
||||
$sql = str_replace(
|
||||
['%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'],
|
||||
[
|
||||
$this->parseTable($options['table']),
|
||||
implode(' , ', $fields),
|
||||
implode(' UNION ALL ', $values),
|
||||
$this->parseComment($options['comment']),
|
||||
], $this->insertAllSql);
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成slectinsert SQL
|
||||
* @access public
|
||||
* @param array $fields 数据
|
||||
* @param string $table 数据表
|
||||
* @param array $options 表达式
|
||||
* @return string
|
||||
*/
|
||||
public function selectInsert($fields, $table, $options)
|
||||
{
|
||||
if (is_string($fields)) {
|
||||
$fields = explode(',', $fields);
|
||||
}
|
||||
|
||||
$fields = array_map([$this, 'parseKey'], $fields);
|
||||
$sql = 'INSERT INTO ' . $this->parseTable($table) . ' (' . implode(',', $fields) . ') ' . $this->select($options);
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成update SQL
|
||||
* @access public
|
||||
* @param array $fields 数据
|
||||
* @param array $options 表达式
|
||||
* @return string
|
||||
*/
|
||||
public function update($data, $options)
|
||||
{
|
||||
$table = $this->parseTable($options['table']);
|
||||
$data = $this->parseData($data, $options);
|
||||
if (empty($data)) {
|
||||
return '';
|
||||
}
|
||||
foreach ($data as $key => $val) {
|
||||
$set[] = $key . '=' . $val;
|
||||
}
|
||||
|
||||
$sql = str_replace(
|
||||
['%TABLE%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
|
||||
[
|
||||
$this->parseTable($options['table']),
|
||||
implode(',', $set),
|
||||
$this->parseJoin($options['join']),
|
||||
$this->parseWhere($options['where'], $options['table']),
|
||||
$this->parseOrder($options['order']),
|
||||
$this->parseLimit($options['limit']),
|
||||
$this->parseLimit($options['lock']),
|
||||
$this->parseComment($options['comment']),
|
||||
], $this->updateSql);
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成delete SQL
|
||||
* @access public
|
||||
* @param array $options 表达式
|
||||
* @return string
|
||||
*/
|
||||
public function delete($options)
|
||||
{
|
||||
$sql = str_replace(
|
||||
['%TABLE%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'],
|
||||
[
|
||||
$this->parseTable($options['table']),
|
||||
!empty($options['using']) ? ' USING ' . $this->parseTable($options['using']) . ' ' : '',
|
||||
$this->parseJoin($options['join']),
|
||||
$this->parseWhere($options['where'], $options['table']),
|
||||
$this->parseOrder($options['order']),
|
||||
$this->parseLimit($options['limit']),
|
||||
$this->parseLimit($options['lock']),
|
||||
$this->parseComment($options['comment']),
|
||||
], $this->deleteSql);
|
||||
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,889 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\db;
|
||||
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use think\App;
|
||||
use think\Collection;
|
||||
use think\Db;
|
||||
use think\db\Query;
|
||||
use think\Debug;
|
||||
use think\Exception;
|
||||
use think\exception\PDOException;
|
||||
use think\db\exception\BindParamException;
|
||||
use think\Log;
|
||||
|
||||
abstract class Connection
|
||||
{
|
||||
|
||||
/** @var PDOStatement PDO操作实例 */
|
||||
protected $PDOStatement;
|
||||
|
||||
/** @var string 当前SQL指令 */
|
||||
protected $queryStr = '';
|
||||
// 最后插入ID
|
||||
protected $lastInsID;
|
||||
// 返回或者影响记录数
|
||||
protected $numRows = 0;
|
||||
// 事务指令数
|
||||
protected $transTimes = 0;
|
||||
// 错误信息
|
||||
protected $error = '';
|
||||
|
||||
/** @var PDO[] 数据库连接ID 支持多个连接 */
|
||||
protected $links = [];
|
||||
|
||||
/** @var PDO 当前连接ID */
|
||||
protected $linkID;
|
||||
protected $linkRead;
|
||||
protected $linkWrite;
|
||||
|
||||
// 查询结果类型
|
||||
protected $resultSetType = 'array';
|
||||
// 查询结果类型
|
||||
protected $fetchType = PDO::FETCH_ASSOC;
|
||||
// 字段属性大小写
|
||||
protected $attrCase = PDO::CASE_LOWER;
|
||||
// 监听回调
|
||||
protected static $event = [];
|
||||
// 查询对象
|
||||
protected $query = [];
|
||||
// 数据库连接参数配置
|
||||
protected $config = [
|
||||
// 数据库类型
|
||||
'type' => '',
|
||||
// 服务器地址
|
||||
'hostname' => '',
|
||||
// 数据库名
|
||||
'database' => '',
|
||||
// 用户名
|
||||
'username' => '',
|
||||
// 密码
|
||||
'password' => '',
|
||||
// 端口
|
||||
'hostport' => '',
|
||||
// 连接dsn
|
||||
'dsn' => '',
|
||||
// 数据库连接参数
|
||||
'params' => [],
|
||||
// 数据库编码默认采用utf8
|
||||
'charset' => 'utf8',
|
||||
// 数据库表前缀
|
||||
'prefix' => '',
|
||||
// 数据库调试模式
|
||||
'debug' => false,
|
||||
// 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
|
||||
'deploy' => 0,
|
||||
// 数据库读写是否分离 主从式有效
|
||||
'rw_separate' => false,
|
||||
// 读写分离后 主服务器数量
|
||||
'master_num' => 1,
|
||||
// 指定从服务器序号
|
||||
'slave_no' => '',
|
||||
// 是否严格检查字段是否存在
|
||||
'fields_strict' => true,
|
||||
// 数据集返回类型
|
||||
'resultset_type' => 'array',
|
||||
// 自动写入时间戳字段
|
||||
'auto_timestamp' => false,
|
||||
// 是否需要进行SQL性能分析
|
||||
'sql_explain' => false,
|
||||
];
|
||||
|
||||
// PDO连接参数
|
||||
protected $params = [
|
||||
PDO::ATTR_CASE => PDO::CASE_LOWER,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
|
||||
PDO::ATTR_STRINGIFY_FETCHES => false,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* 架构函数 读取数据库配置信息
|
||||
* @access public
|
||||
* @param array $config 数据库配置数组
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
if (!empty($config)) {
|
||||
$this->config = array_merge($this->config, $config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指定模型的查询对象
|
||||
* @access public
|
||||
* @param string $model 模型类名称
|
||||
* @return Query
|
||||
*/
|
||||
public function model($model)
|
||||
{
|
||||
if (!isset($this->query[$model])) {
|
||||
$this->query[$model] = new Query($this, $model);
|
||||
}
|
||||
return $this->query[$model];
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用Query类的查询方法
|
||||
* @access public
|
||||
* @param string $method 方法名称
|
||||
* @param array $args 调用参数
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if (!isset($this->query['database'])) {
|
||||
$this->query['database'] = new Query($this);
|
||||
}
|
||||
return call_user_func_array([$this->query['database'], $method], $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析pdo连接的dsn信息
|
||||
* @access protected
|
||||
* @param array $config 连接信息
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function parseDsn($config);
|
||||
|
||||
/**
|
||||
* 取得数据表的字段信息
|
||||
* @access public
|
||||
* @param string $tableName
|
||||
* @return array
|
||||
*/
|
||||
abstract public function getFields($tableName);
|
||||
|
||||
/**
|
||||
* 取得数据库的表信息
|
||||
* @access public
|
||||
* @param string $dbName
|
||||
* @return array
|
||||
*/
|
||||
abstract public function getTables($dbName);
|
||||
|
||||
/**
|
||||
* SQL性能分析
|
||||
* @access protected
|
||||
* @param string $sql
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function getExplain($sql);
|
||||
|
||||
/**
|
||||
* 对返数据表字段信息进行大小写转换出来
|
||||
* @access public
|
||||
* @param array $info 字段信息
|
||||
* @return array
|
||||
*/
|
||||
protected function fieldCase($info)
|
||||
{
|
||||
// 字段大小写转换
|
||||
switch ($this->attrCase) {
|
||||
case PDO::CASE_LOWER:
|
||||
$info = array_change_key_case($info);
|
||||
break;
|
||||
case PDO::CASE_UPPER:
|
||||
$info = array_change_key_case($info, CASE_UPPER);
|
||||
break;
|
||||
case PDO::CASE_NATURAL:
|
||||
default:
|
||||
// 不做转换
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库的配置参数
|
||||
* @access public
|
||||
* @param string $config 配置名称
|
||||
* @return mixed
|
||||
*/
|
||||
public function getConfig($config = '')
|
||||
{
|
||||
return $config ? $this->config[$config] : $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据库的配置参数
|
||||
* @access public
|
||||
* @param string $config 配置名称
|
||||
* @param mixed $value 配置值
|
||||
* @return void
|
||||
*/
|
||||
public function setConfig($config, $value)
|
||||
{
|
||||
$this->config[$config] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接数据库方法
|
||||
* @access public
|
||||
* @param array $config 连接参数
|
||||
* @param integer $linkNum 连接序号
|
||||
* @param array|bool $autoConnection 是否自动连接主数据库(用于分布式)
|
||||
* @return PDO
|
||||
* @throws Exception
|
||||
*/
|
||||
public function connect(array $config = [], $linkNum = 0, $autoConnection = false)
|
||||
{
|
||||
if (!isset($this->links[$linkNum])) {
|
||||
if (empty($config)) {
|
||||
$config = $this->config;
|
||||
}
|
||||
// 连接参数
|
||||
if (isset($config['params']) && is_array($config['params'])) {
|
||||
$params = $config['params'] + $this->params;
|
||||
} else {
|
||||
$params = $this->params;
|
||||
}
|
||||
// 记录当前字段属性大小写设置
|
||||
$this->attrCase = $params[PDO::ATTR_CASE];
|
||||
// 记录数据集返回类型
|
||||
if (isset($config['resultset_type'])) {
|
||||
$this->resultSetType = $config['resultset_type'];
|
||||
}
|
||||
try {
|
||||
if (empty($config['dsn'])) {
|
||||
$config['dsn'] = $this->parseDsn($config);
|
||||
}
|
||||
$this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params);
|
||||
// 记录数据库连接信息
|
||||
App::$debug && Log::record('[ DB ] CONNECT: ' . $config['dsn'], 'info');
|
||||
} catch (\PDOException $e) {
|
||||
if ($autoConnection) {
|
||||
Log::record($e->getMessage(), 'error');
|
||||
return $this->connect($autoConnection, $linkNum);
|
||||
} else {
|
||||
throw new Exception($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->links[$linkNum];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前数据库的驱动类型
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getDriverName()
|
||||
{
|
||||
if ($this->linkID) {
|
||||
return $this->linkID->getAttribute(PDO::ATTR_DRIVER_NAME);
|
||||
} else {
|
||||
return basename(str_replace('\\', '/', $this->config['type']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放查询结果
|
||||
* @access public
|
||||
*/
|
||||
public function free()
|
||||
{
|
||||
$this->PDOStatement = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取PDO对象
|
||||
* @access public
|
||||
* @return \PDO|false
|
||||
*/
|
||||
public function getPdo()
|
||||
{
|
||||
if (!$this->linkID) {
|
||||
return false;
|
||||
} else {
|
||||
return $this->linkID;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行查询 返回数据集
|
||||
* @access public
|
||||
* @param string $sql sql指令
|
||||
* @param array $bind 参数绑定
|
||||
* @param boolean $master 是否在主服务器读操作
|
||||
* @param bool|string $class 指定返回的数据集对象
|
||||
* @return mixed
|
||||
* @throws BindParamException
|
||||
* @throws PDOException
|
||||
*/
|
||||
public function query($sql, $bind = [], $master = false, $class = false)
|
||||
{
|
||||
$this->initConnect($master);
|
||||
if (!$this->linkID) {
|
||||
return false;
|
||||
}
|
||||
// 根据参数绑定组装最终的SQL语句
|
||||
$this->queryStr = $this->getRealSql($sql, $bind);
|
||||
|
||||
//释放前次的查询结果
|
||||
if (!empty($this->PDOStatement)) {
|
||||
$this->free();
|
||||
}
|
||||
|
||||
Db::$queryTimes++;
|
||||
try {
|
||||
// 调试开始
|
||||
$this->debug(true);
|
||||
// 预处理
|
||||
$this->PDOStatement = $this->linkID->prepare($sql);
|
||||
// 参数绑定
|
||||
$this->bindValue($bind);
|
||||
// 执行查询
|
||||
$result = $this->PDOStatement->execute();
|
||||
// 调试结束
|
||||
$this->debug(false);
|
||||
$procedure = 0 === strpos(strtolower(substr(trim($sql), 0, 4)), 'call');
|
||||
return $this->getResult($class, $procedure);
|
||||
} catch (\PDOException $e) {
|
||||
throw new PDOException($e, $this->config, $this->queryStr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行语句
|
||||
* @access public
|
||||
* @param string $sql sql指令
|
||||
* @param array $bind 参数绑定
|
||||
* @param boolean $getLastInsID 是否获取自增ID
|
||||
* @param string $sequence 自增序列名
|
||||
* @return int
|
||||
* @throws BindParamException
|
||||
* @throws PDOException
|
||||
*/
|
||||
public function execute($sql, $bind = [], $getLastInsID = false, $sequence = null)
|
||||
{
|
||||
$this->initConnect(true);
|
||||
if (!$this->linkID) {
|
||||
return false;
|
||||
}
|
||||
// 根据参数绑定组装最终的SQL语句
|
||||
$this->queryStr = $this->getRealSql($sql, $bind);
|
||||
|
||||
//释放前次的查询结果
|
||||
if (!empty($this->PDOStatement)) {
|
||||
$this->free();
|
||||
}
|
||||
|
||||
Db::$executeTimes++;
|
||||
try {
|
||||
// 调试开始
|
||||
$this->debug(true);
|
||||
// 预处理
|
||||
$this->PDOStatement = $this->linkID->prepare($sql);
|
||||
// 参数绑定操作
|
||||
$this->bindValue($bind);
|
||||
// 执行语句
|
||||
$result = $this->PDOStatement->execute();
|
||||
// 调试结束
|
||||
$this->debug(false);
|
||||
|
||||
$this->numRows = $this->PDOStatement->rowCount();
|
||||
if (preg_match("/^\s*(INSERT\s+INTO|REPLACE\s+INTO)\s+/i", $sql)) {
|
||||
$this->lastInsID = $this->linkID->lastInsertId($sequence);
|
||||
if ($getLastInsID) {
|
||||
return $this->lastInsID;
|
||||
}
|
||||
}
|
||||
return $this->numRows;
|
||||
} catch (\PDOException $e) {
|
||||
throw new PDOException($e, $this->config, $this->queryStr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据参数绑定组装最终的SQL语句 便于调试
|
||||
* @access public
|
||||
* @param string $sql 带参数绑定的sql语句
|
||||
* @param array $bind 参数绑定列表
|
||||
* @return string
|
||||
*/
|
||||
public function getRealSql($sql, array $bind = [])
|
||||
{
|
||||
if ($bind) {
|
||||
foreach ($bind as $key => $val) {
|
||||
$val = $this->quote(is_array($val) ? $val[0] : $val);
|
||||
// 判断占位符
|
||||
$sql = is_numeric($key) ?
|
||||
substr_replace($sql, $val, strpos($sql, '?'), 1) :
|
||||
str_replace(
|
||||
[':' . $key . ')', ':' . $key . ',', ':' . $key . ' '],
|
||||
[$val . ')', $val . ',', $val . ' '],
|
||||
$sql . ' ');
|
||||
}
|
||||
}
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数绑定
|
||||
* 支持 ['name'=>'value','id'=>123] 对应命名占位符
|
||||
* 或者 ['value',123] 对应问号占位符
|
||||
* @access public
|
||||
* @param array $bind 要绑定的参数列表
|
||||
* @return void
|
||||
* @throws \think\Exception
|
||||
*/
|
||||
protected function bindValue(array $bind = [])
|
||||
{
|
||||
foreach ($bind as $key => $val) {
|
||||
// 占位符
|
||||
$param = is_numeric($key) ? $key + 1 : ':' . $key;
|
||||
if (is_array($val)) {
|
||||
$result = $this->PDOStatement->bindValue($param, $val[0], $val[1]);
|
||||
} else {
|
||||
$result = $this->PDOStatement->bindValue($param, $val);
|
||||
}
|
||||
if (!$result) {
|
||||
throw new BindParamException(
|
||||
"Error occurred when binding parameters '{$param}'",
|
||||
$this->config,
|
||||
$this->queryStr,
|
||||
$bind
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得数据集
|
||||
* @access protected
|
||||
* @param bool|string $class true 返回PDOStatement 字符串用于指定返回的类名
|
||||
* @param bool $procedure 是否存储过程
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getResult($class = '', $procedure = false)
|
||||
{
|
||||
if (true === $class) {
|
||||
// 返回PDOStatement对象处理
|
||||
return $this->PDOStatement;
|
||||
}
|
||||
if ($procedure) {
|
||||
return $this->procedure($class);
|
||||
}
|
||||
$result = $this->PDOStatement->fetchAll($this->fetchType);
|
||||
$this->numRows = count($result);
|
||||
|
||||
if (!empty($class)) {
|
||||
// 返回指定数据集对象类
|
||||
$result = new $class($result);
|
||||
} elseif ('collection' == $this->resultSetType){
|
||||
// 返回数据集Collection对象
|
||||
$result = new Collection($result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得存储过程数据集
|
||||
* @access protected
|
||||
* @param bool|string $class true 返回PDOStatement 字符串用于指定返回的类名
|
||||
* @return array
|
||||
*/
|
||||
protected function procedure($class)
|
||||
{
|
||||
$item = [];
|
||||
do {
|
||||
$result = $this->getResult($class);
|
||||
if ($result) {
|
||||
$item[] = $result;
|
||||
}
|
||||
} while ($this->PDOStatement->nextRowset());
|
||||
$this->numRows = count($item);
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据库事务
|
||||
* @access public
|
||||
* @param callable $callback 数据操作方法回调
|
||||
* @return mixed
|
||||
* @throws PDOException
|
||||
* @throws \Exception
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function transaction($callback)
|
||||
{
|
||||
$this->startTrans();
|
||||
try {
|
||||
$result = null;
|
||||
if (is_callable($callback)) {
|
||||
$result = call_user_func_array($callback, [$this]);
|
||||
}
|
||||
$this->commit();
|
||||
return $result;
|
||||
} catch (\Exception $e) {
|
||||
$this->rollback();
|
||||
throw $e;
|
||||
} catch (\Throwable $e) {
|
||||
$this->rollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动事务
|
||||
* @access public
|
||||
* @return bool|null
|
||||
*/
|
||||
public function startTrans()
|
||||
{
|
||||
$this->initConnect(true);
|
||||
if (!$this->linkID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
++$this->transTimes;
|
||||
|
||||
if ($this->transTimes == 1) {
|
||||
$this->linkID->beginTransaction();
|
||||
} elseif ($this->transTimes > 1 && $this->supportSavepoint()) {
|
||||
$this->linkID->exec(
|
||||
$this->parseSavepoint('trans' . $this->transTimes)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于非自动提交状态下面的查询提交
|
||||
* @access public
|
||||
* @return boolean
|
||||
* @throws PDOException
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
$this->initConnect(true);
|
||||
|
||||
if ($this->transTimes == 1) {
|
||||
$this->linkID->commit();
|
||||
}
|
||||
|
||||
--$this->transTimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 事务回滚
|
||||
* @access public
|
||||
* @return boolean
|
||||
* @throws PDOException
|
||||
*/
|
||||
public function rollback()
|
||||
{
|
||||
$this->initConnect(true);
|
||||
|
||||
if ($this->transTimes == 1) {
|
||||
$this->linkID->rollBack();
|
||||
} elseif ($this->transTimes > 1 && $this->supportSavepoint()) {
|
||||
$this->linkID->exec(
|
||||
$this->parseSavepointRollBack('trans' . $this->transTimes)
|
||||
);
|
||||
}
|
||||
|
||||
$this->transTimes = max(0, $this->transTimes - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否支持事务嵌套
|
||||
* @return bool
|
||||
*/
|
||||
protected function supportSavepoint()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成定义保存点的SQL
|
||||
* @param $name
|
||||
* @return string
|
||||
*/
|
||||
protected function parseSavepoint($name)
|
||||
{
|
||||
return 'SAVEPOINT ' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成回滚到保存点的SQL
|
||||
* @param $name
|
||||
* @return string
|
||||
*/
|
||||
protected function parseSavepointRollBack($name)
|
||||
{
|
||||
return 'ROLLBACK TO SAVEPOINT ' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批处理执行SQL语句
|
||||
* 批处理的指令都认为是execute操作
|
||||
* @access public
|
||||
* @param array $sqlArray SQL批处理指令
|
||||
* @return boolean
|
||||
*/
|
||||
public function batchQuery($sqlArray = [])
|
||||
{
|
||||
if (!is_array($sqlArray)) {
|
||||
return false;
|
||||
}
|
||||
// 自动启动事务支持
|
||||
$this->startTrans();
|
||||
try {
|
||||
foreach ($sqlArray as $sql) {
|
||||
$this->execute($sql);
|
||||
}
|
||||
// 提交事务
|
||||
$this->commit();
|
||||
} catch (\PDOException $e) {
|
||||
$this->rollback();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得查询次数
|
||||
* @access public
|
||||
* @param boolean $execute 是否包含所有查询
|
||||
* @return integer
|
||||
*/
|
||||
public function getQueryTimes($execute = false)
|
||||
{
|
||||
return $execute ? Db::$queryTimes + Db::$executeTimes : Db::$queryTimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得执行次数
|
||||
* @access public
|
||||
* @return integer
|
||||
*/
|
||||
public function getExecuteTimes()
|
||||
{
|
||||
return Db::$executeTimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭数据库
|
||||
* @access public
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
$this->linkID = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最近一次查询的sql语句
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLastSql()
|
||||
{
|
||||
return $this->queryStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最近插入的ID
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getLastInsID()
|
||||
{
|
||||
return $this->lastInsID;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最近的错误信息
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
if ($this->PDOStatement) {
|
||||
$error = $this->PDOStatement->errorInfo();
|
||||
$error = $error[1] . ':' . $error[2];
|
||||
} else {
|
||||
$error = '';
|
||||
}
|
||||
if ('' != $this->queryStr) {
|
||||
$error .= "\n [ SQL语句 ] : " . $this->queryStr;
|
||||
}
|
||||
return $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL指令安全过滤
|
||||
* @access public
|
||||
* @param string $str SQL字符串
|
||||
* @return string
|
||||
*/
|
||||
public function quote($str)
|
||||
{
|
||||
$this->initConnect();
|
||||
return $this->linkID ? $this->linkID->quote($str) : $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库调试 记录当前SQL及分析性能
|
||||
* @access protected
|
||||
* @param boolean $start 调试开始标记 true 开始 false 结束
|
||||
* @return void
|
||||
*/
|
||||
protected function debug($start)
|
||||
{
|
||||
if (!empty($this->config['debug'])) {
|
||||
// 开启数据库调试模式
|
||||
if ($start) {
|
||||
Debug::remark('queryStartTime', 'time');
|
||||
} else {
|
||||
// 记录操作结束时间
|
||||
Debug::remark('queryEndTime', 'time');
|
||||
$runtime = Debug::getRangeTime('queryStartTime', 'queryEndTime');
|
||||
$log = $this->queryStr . ' [ RunTime:' . $runtime . 's ]';
|
||||
$result = [];
|
||||
// SQL性能分析
|
||||
if ($this->config['sql_explain'] && 0 === stripos(trim($this->queryStr), 'select')) {
|
||||
$result = $this->getExplain($this->queryStr);
|
||||
}
|
||||
// SQL监听
|
||||
$this->trigger($this->queryStr, $runtime, $result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听SQL执行
|
||||
* @access public
|
||||
* @param callable $callback 回调方法
|
||||
* @return void
|
||||
*/
|
||||
public function listen($callback)
|
||||
{
|
||||
self::$event[] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发SQL事件
|
||||
* @access protected
|
||||
* @param string $sql SQL语句
|
||||
* @param float $runtime SQL运行时间
|
||||
* @param mixed $explain SQL分析
|
||||
* @return bool
|
||||
*/
|
||||
protected function trigger($sql, $runtime, $explain = [])
|
||||
{
|
||||
if (!empty(self::$event)) {
|
||||
foreach (self::$event as $callback) {
|
||||
if (is_callable($callback)) {
|
||||
call_user_func_array($callback, [$sql, $runtime, $explain]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 未注册监听则记录到日志中
|
||||
Log::record('[ SQL ] ' . $this->queryStr . ' [ RunTime:' . $runtime . 's ]', 'sql');
|
||||
if (!empty($explain)) {
|
||||
Log::record('[ EXPLAIN : ' . var_export($explain, true) . ' ]', 'sql');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据库连接
|
||||
* @access protected
|
||||
* @param boolean $master 是否主服务器
|
||||
* @return void
|
||||
*/
|
||||
protected function initConnect($master = true)
|
||||
{
|
||||
if (!empty($this->config['deploy'])) {
|
||||
// 采用分布式数据库
|
||||
if ($master) {
|
||||
if (!$this->linkWrite) {
|
||||
$this->linkWrite = $this->multiConnect(true);
|
||||
}
|
||||
$this->linkID = $this->linkWrite;
|
||||
} else {
|
||||
if (!$this->linkRead) {
|
||||
$this->linkRead = $this->multiConnect(false);
|
||||
}
|
||||
$this->linkID = $this->linkRead;
|
||||
}
|
||||
} elseif (!$this->linkID) {
|
||||
// 默认单数据库
|
||||
$this->linkID = $this->connect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接分布式服务器
|
||||
* @access protected
|
||||
* @param boolean $master 主服务器
|
||||
* @return PDO
|
||||
*/
|
||||
protected function multiConnect($master = false)
|
||||
{
|
||||
$_config = [];
|
||||
// 分布式数据库配置解析
|
||||
foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) {
|
||||
$_config[$name] = explode(',', $this->config[$name]);
|
||||
}
|
||||
|
||||
// 主服务器序号
|
||||
$m = floor(mt_rand(0, $this->config['master_num'] - 1));
|
||||
|
||||
if ($this->config['rw_separate']) {
|
||||
// 主从式采用读写分离
|
||||
if ($master) // 主服务器写入
|
||||
{
|
||||
$r = $m;
|
||||
} elseif (is_numeric($this->config['slave_no'])) {
|
||||
// 指定服务器读
|
||||
$r = $this->config['slave_no'];
|
||||
} else {
|
||||
// 读操作连接从服务器 每次随机连接的数据库
|
||||
$r = floor(mt_rand($this->config['master_num'], count($_config['hostname']) - 1));
|
||||
}
|
||||
} else {
|
||||
// 读写操作不区分服务器 每次随机连接的数据库
|
||||
$r = floor(mt_rand(0, count($_config['hostname']) - 1));
|
||||
}
|
||||
$dbMaster = false;
|
||||
if ($m != $r) {
|
||||
$dbMaster = [];
|
||||
foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) {
|
||||
$dbMaster[$name] = isset($_config[$name][$m]) ? $_config[$name][$m] : $_config[$name][0];
|
||||
}
|
||||
}
|
||||
$dbConfig = [];
|
||||
foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) {
|
||||
$dbConfig[$name] = isset($_config[$name][$r]) ? $_config[$name][$r] : $_config[$name][0];
|
||||
}
|
||||
return $this->connect($dbConfig, $r, $r == $m ? false : $dbMaster);
|
||||
}
|
||||
|
||||
/**
|
||||
* 析构方法
|
||||
* @access public
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
// 释放查询
|
||||
if ($this->PDOStatement) {
|
||||
$this->free();
|
||||
}
|
||||
// 关闭连接
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\db\builder;
|
||||
|
||||
use think\db\Builder;
|
||||
|
||||
/**
|
||||
* mysql数据库驱动
|
||||
*/
|
||||
class Mysql extends Builder
|
||||
{
|
||||
|
||||
/**
|
||||
* 字段和表名处理
|
||||
* @access protected
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
protected function parseKey($key)
|
||||
{
|
||||
$key = trim($key);
|
||||
if (strpos($key, '$.') && false === strpos($key, '(')) {
|
||||
// JSON字段支持
|
||||
list($field, $name) = explode($key, '$.');
|
||||
$key = 'jsn_extract(' . $field . ', \'$.\'.' . $name . ')';
|
||||
}
|
||||
if (!preg_match('/[,\'\"\*\(\)`.\s]/', $key)) {
|
||||
$key = '`' . $key . '`';
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机排序
|
||||
* @access protected
|
||||
* @return string
|
||||
*/
|
||||
protected function parseRand()
|
||||
{
|
||||
return 'rand()';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\db\builder;
|
||||
|
||||
use think\Db;
|
||||
use think\db\Builder;
|
||||
|
||||
/**
|
||||
* Oracle数据库驱动
|
||||
*/
|
||||
class Oracle extends Builder
|
||||
{
|
||||
|
||||
protected $selectSql = 'SELECT * FROM (SELECT thinkphp.*, rownum AS numrow FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%) thinkphp ) %LIMIT%%COMMENT%';
|
||||
|
||||
/**
|
||||
* limit
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function parseLimit($limit)
|
||||
{
|
||||
$limitStr = '';
|
||||
if (!empty($limit)) {
|
||||
$limit = explode(',', $limit);
|
||||
if (count($limit) > 1) {
|
||||
$limitStr = "(numrow>" . $limit[0] . ") AND (numrow<=" . ($limit[0] + $limit[1]) . ")";
|
||||
} else {
|
||||
$limitStr = "(numrow>0 AND numrow<=" . $limit[0] . ")";
|
||||
}
|
||||
|
||||
}
|
||||
return $limitStr ? ' WHERE ' . $limitStr : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置锁机制
|
||||
* @access protected
|
||||
* @param bool|false $lock
|
||||
* @return string
|
||||
*/
|
||||
protected function parseLock($lock = false)
|
||||
{
|
||||
if (!$lock) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return ' FOR UPDATE NOWAIT ';
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段和表名处理
|
||||
* @access protected
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
protected function parseKey($key)
|
||||
{
|
||||
$key = trim($key);
|
||||
if (strpos($key, '$.') && false === strpos($key, '(')) {
|
||||
// JSON字段支持
|
||||
list($field, $name) = explode($key, '$.');
|
||||
$key = $field . '."' . $name . '"';
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机排序
|
||||
* @access protected
|
||||
* @return string
|
||||
*/
|
||||
protected function parseRand()
|
||||
{
|
||||
return 'DBMS_RANDOM.value';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\db\builder;
|
||||
|
||||
use think\db\Builder;
|
||||
|
||||
/**
|
||||
* Pgsql数据库驱动
|
||||
*/
|
||||
class Pgsql extends Builder
|
||||
{
|
||||
|
||||
/**
|
||||
* limit分析
|
||||
* @access protected
|
||||
* @param mixed $limit
|
||||
* @return string
|
||||
*/
|
||||
public function parseLimit($limit)
|
||||
{
|
||||
$limitStr = '';
|
||||
if (!empty($limit)) {
|
||||
$limit = explode(',', $limit);
|
||||
if (count($limit) > 1) {
|
||||
$limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' ';
|
||||
} else {
|
||||
$limitStr .= ' LIMIT ' . $limit[0] . ' ';
|
||||
}
|
||||
}
|
||||
return $limitStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段和表名处理
|
||||
* @access protected
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
protected function parseKey($key)
|
||||
{
|
||||
$key = trim($key);
|
||||
if (strpos($key, '$.') && false === strpos($key, '(')) {
|
||||
// JSON字段支持
|
||||
list($field, $name) = explode($key, '$.');
|
||||
$key = $field . '->>\'' . $name . '\'';
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机排序
|
||||
* @access protected
|
||||
* @return string
|
||||
*/
|
||||
protected function parseRand()
|
||||
{
|
||||
return 'RANDOM()';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\db\builder;
|
||||
|
||||
use think\db\Builder;
|
||||
|
||||
/**
|
||||
* Sqlite数据库驱动
|
||||
*/
|
||||
class Sqlite extends Builder
|
||||
{
|
||||
|
||||
/**
|
||||
* limit
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function parseLimit($limit)
|
||||
{
|
||||
$limitStr = '';
|
||||
if (!empty($limit)) {
|
||||
$limit = explode(',', $limit);
|
||||
if (count($limit) > 1) {
|
||||
$limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' ';
|
||||
} else {
|
||||
$limitStr .= ' LIMIT ' . $limit[0] . ' ';
|
||||
}
|
||||
}
|
||||
return $limitStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机排序
|
||||
* @access protected
|
||||
* @return string
|
||||
*/
|
||||
protected function parseRand()
|
||||
{
|
||||
return 'RANDOM()';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2012 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\db\builder;
|
||||
|
||||
use think\db\Builder;
|
||||
|
||||
/**
|
||||
* Sqlsrv数据库驱动
|
||||
*/
|
||||
class Sqlsrv extends Builder
|
||||
{
|
||||
protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 %LIMIT%%COMMENT%';
|
||||
protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%';
|
||||
protected $updateSql = 'UPDATE %TABLE% SET %SET% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%';
|
||||
protected $deleteSql = 'DELETE FROM %TABLE% %USING% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%';
|
||||
|
||||
/**
|
||||
* order分析
|
||||
* @access protected
|
||||
* @param mixed $order
|
||||
* @return string
|
||||
*/
|
||||
protected function parseOrder($order)
|
||||
{
|
||||
return !empty($order) ? ' ORDER BY ' . $order : ' ORDER BY rand()';
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机排序
|
||||
* @access protected
|
||||
* @return string
|
||||
*/
|
||||
protected function parseRand()
|
||||
{
|
||||
return 'rand()';
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段名分析
|
||||
* @access protected
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
protected function parseKey($key)
|
||||
{
|
||||
$key = trim($key);
|
||||
if (!is_numeric($key) && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) {
|
||||
$key = '[' . $key . ']';
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* limit
|
||||
* @access protected
|
||||
* @param mixed $limit
|
||||
* @return string
|
||||
*/
|
||||
protected function parseLimit($limit)
|
||||
{
|
||||
if (empty($limit)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$limit = explode(',', $limit);
|
||||
if (count($limit) > 1) {
|
||||
$limitStr = '(T1.ROW_NUMBER BETWEEN ' . $limit[0] . ' + 1 AND ' . $limit[0] . ' + ' . $limit[1] . ')';
|
||||
} else {
|
||||
$limitStr = '(T1.ROW_NUMBER BETWEEN 1 AND ' . $limit[0] . ")";
|
||||
}
|
||||
return 'WHERE ' . $limitStr;
|
||||
}
|
||||
public function selectInsert($fields, $table, $options)
|
||||
{
|
||||
$this->selectSql=$this->selectInsertSql;
|
||||
return parent::selectInsert($fields, $table, $options);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\db\connector;
|
||||
|
||||
use PDO;
|
||||
use think\db\Connection;
|
||||
use think\Log;
|
||||
|
||||
/**
|
||||
* mysql数据库驱动
|
||||
*/
|
||||
class Mysql extends Connection
|
||||
{
|
||||
|
||||
/**
|
||||
* 解析pdo连接的dsn信息
|
||||
* @access protected
|
||||
* @param array $config 连接信息
|
||||
* @return string
|
||||
*/
|
||||
protected function parseDsn($config)
|
||||
{
|
||||
$dsn = 'mysql:dbname=' . $config['database'] . ';host=' . $config['hostname'];
|
||||
if (!empty($config['hostport'])) {
|
||||
$dsn .= ';port=' . $config['hostport'];
|
||||
} elseif (!empty($config['socket'])) {
|
||||
$dsn .= ';unix_socket=' . $config['socket'];
|
||||
}
|
||||
if (!empty($config['charset'])) {
|
||||
$dsn .= ';charset=' . $config['charset'];
|
||||
}
|
||||
return $dsn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据表的字段信息
|
||||
* @access public
|
||||
* @param string $tableName
|
||||
* @return array
|
||||
*/
|
||||
public function getFields($tableName)
|
||||
{
|
||||
$this->initConnect(true);
|
||||
list($tableName) = explode(' ', $tableName);
|
||||
if (strpos($tableName, '.')) {
|
||||
$tableName = str_replace('.', '`.`', $tableName);
|
||||
}
|
||||
$sql = 'SHOW COLUMNS FROM `' . $tableName . '`';
|
||||
$pdo = $this->linkID->query($sql);
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
if ($result) {
|
||||
foreach ($result as $key => $val) {
|
||||
$val = array_change_key_case($val);
|
||||
$info[$val['field']] = [
|
||||
'name' => $val['field'],
|
||||
'type' => $val['type'],
|
||||
'notnull' => (bool) ('' === $val['null']), // not null is empty, null is yes
|
||||
'default' => $val['default'],
|
||||
'primary' => (strtolower($val['key']) == 'pri'),
|
||||
'autoinc' => (strtolower($val['extra']) == 'auto_increment'),
|
||||
];
|
||||
}
|
||||
}
|
||||
return $this->fieldCase($info);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据库的表信息
|
||||
* @access public
|
||||
* @param string $dbName
|
||||
* @return array
|
||||
*/
|
||||
public function getTables($dbName = '')
|
||||
{
|
||||
$sql = !empty($dbName) ? 'SHOW TABLES FROM ' . $dbName : 'SHOW TABLES ';
|
||||
$pdo = $this->linkID->query($sql);
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
foreach ($result as $key => $val) {
|
||||
$info[$key] = current($val);
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL性能分析
|
||||
* @access protected
|
||||
* @param string $sql
|
||||
* @return array
|
||||
*/
|
||||
protected function getExplain($sql)
|
||||
{
|
||||
$pdo = $this->linkID->query("EXPLAIN " . $sql);
|
||||
$result = $pdo->fetch(PDO::FETCH_ASSOC);
|
||||
$result = array_change_key_case($result);
|
||||
if (isset($result['extra'])) {
|
||||
if (strpos($result['extra'], 'filesort') || strpos($result['extra'], 'temporary')) {
|
||||
Log::record('SQL:' . $this->queryStr . '[' . $result['extra'] . ']', 'warn');
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function supportSavepoint(){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\db\connector;
|
||||
|
||||
use PDO;
|
||||
use think\Db;
|
||||
use think\db\Connection;
|
||||
|
||||
/**
|
||||
* Oracle数据库驱动
|
||||
*/
|
||||
class Oracle extends Connection
|
||||
{
|
||||
|
||||
/**
|
||||
* 解析pdo连接的dsn信息
|
||||
* @access protected
|
||||
* @param array $config 连接信息
|
||||
* @return string
|
||||
*/
|
||||
protected function parseDsn($config)
|
||||
{
|
||||
$dsn = 'oci:dbname=';
|
||||
if (!empty($config['hostname'])) {
|
||||
// Oracle Instant Client
|
||||
$dsn .= '//' . $config['hostname'] . ($config['hostport'] ? ':' . $config['hostport'] : '') . '/';
|
||||
}
|
||||
$dsn .= $config['database'];
|
||||
if (!empty($config['charset'])) {
|
||||
$dsn .= ';charset=' . $config['charset'];
|
||||
}
|
||||
return $dsn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行语句
|
||||
* @access public
|
||||
* @param string $sql sql指令
|
||||
* @param array $bind 参数绑定
|
||||
* @param boolean $getLastInsID 是否获取自增ID
|
||||
* @param string $sequence 序列名
|
||||
* @return integer
|
||||
* @throws \Exception
|
||||
* @throws \think\Exception
|
||||
*/
|
||||
public function execute($sql, $bind = [], $getLastInsID = false, $sequence = null)
|
||||
{
|
||||
$this->initConnect(true);
|
||||
if (!$this->linkID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 根据参数绑定组装最终的SQL语句
|
||||
$this->queryStr = $this->getBindSql($sql, $bind);
|
||||
|
||||
$flag = false;
|
||||
if (preg_match("/^\s*(INSERT\s+INTO)\s+(\w+)\s+/i", $sql, $match)) {
|
||||
if(is_null($sequence)){
|
||||
$sequence = Config::get("db_sequence_prefix") . str_ireplace(Config::get("database.prefix"), "", $match[2]);
|
||||
}
|
||||
$flag = (boolean) $this->query("SELECT * FROM all_sequences WHERE sequence_name='" . strtoupper($sequence) . "'");
|
||||
}
|
||||
|
||||
//释放前次的查询结果
|
||||
if (!empty($this->PDOStatement)) {
|
||||
$this->free();
|
||||
}
|
||||
|
||||
Db::$executeTimes++;
|
||||
try {
|
||||
// 记录开始执行时间
|
||||
$this->debug(true);
|
||||
$this->PDOStatement = $this->linkID->prepare($sql);
|
||||
// 参数绑定操作
|
||||
$this->bindValue($bind);
|
||||
$result = $this->PDOStatement->execute();
|
||||
$this->debug(false);
|
||||
$this->numRows = $this->PDOStatement->rowCount();
|
||||
if ($flag || preg_match("/^\s*(INSERT\s+INTO|REPLACE\s+INTO)\s+/i", $sql)) {
|
||||
$this->lastInsID = $this->linkID->lastInsertId();
|
||||
if ($getLastInsID) {
|
||||
return $this->lastInsID;
|
||||
}
|
||||
}
|
||||
return $this->numRows;
|
||||
} catch (\PDOException $e) {
|
||||
throw new \think\Exception($this->getError());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据表的字段信息
|
||||
* @access public
|
||||
* @param string $tableName
|
||||
* @return array
|
||||
*/
|
||||
public function getFields($tableName)
|
||||
{
|
||||
$this->initConnect(true);
|
||||
list($tableName) = explode(' ', $tableName);
|
||||
$sql = "select a.column_name,data_type,DECODE (nullable, 'Y', 0, 1) notnull,data_default, DECODE (A .column_name,b.column_name,1,0) pk from all_tab_columns a,(select column_name from all_constraints c, all_cons_columns col where c.constraint_name = col.constraint_name and c.constraint_type = 'P' and c.table_name = '" . strtoupper($tableName) . "' ) b where table_name = '" . strtoupper($tableName) . "' and a.column_name = b.column_name (+)";
|
||||
$pdo = $this->linkID->query($sql);
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
if ($result) {
|
||||
foreach ($result as $key => $val) {
|
||||
$val = array_change_key_case($val);
|
||||
$info[$val['column_name']] = [
|
||||
'name' => $val['column_name'],
|
||||
'type' => $val['data_type'],
|
||||
'notnull' => $val['notnull'],
|
||||
'default' => $val['data_default'],
|
||||
'primary' => $val['pk'],
|
||||
'autoinc' => $val['pk'],
|
||||
];
|
||||
}
|
||||
}
|
||||
return $this->fieldCase($info);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据库的表信息(暂时实现取得用户表信息)
|
||||
* @access public
|
||||
* @param string $dbName
|
||||
* @return array
|
||||
*/
|
||||
public function getTables($dbName = '')
|
||||
{
|
||||
$pdo = $this->linkID->query("select table_name from all_tables");
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
foreach ($result as $key => $val) {
|
||||
$info[$key] = current($val);
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL性能分析
|
||||
* @access protected
|
||||
* @param string $sql
|
||||
* @return array
|
||||
*/
|
||||
protected function getExplain($sql)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function supportSavepoint(){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\db\connector;
|
||||
|
||||
use PDO;
|
||||
use think\db\Connection;
|
||||
|
||||
/**
|
||||
* Pgsql数据库驱动
|
||||
*/
|
||||
class Pgsql extends Connection
|
||||
{
|
||||
|
||||
/**
|
||||
* 解析pdo连接的dsn信息
|
||||
* @access protected
|
||||
* @param array $config 连接信息
|
||||
* @return string
|
||||
*/
|
||||
protected function parseDsn($config)
|
||||
{
|
||||
$dsn = 'pgsql:dbname=' . $config['database'] . ';host=' . $config['hostname'];
|
||||
if (!empty($config['hostport'])) {
|
||||
$dsn .= ';port=' . $config['hostport'];
|
||||
}
|
||||
return $dsn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据表的字段信息
|
||||
* @access public
|
||||
* @param string $tableName
|
||||
* @return array
|
||||
*/
|
||||
public function getFields($tableName)
|
||||
{
|
||||
$this->initConnect(true);
|
||||
list($tableName) = explode(' ', $tableName);
|
||||
$sql = 'select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg(\'' . $tableName . '\');';
|
||||
$pdo = $this->linkID->query($sql);
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
if ($result) {
|
||||
foreach ($result as $key => $val) {
|
||||
$val = array_change_key_case($val);
|
||||
$info[$val['field']] = [
|
||||
'name' => $val['field'],
|
||||
'type' => $val['type'],
|
||||
'notnull' => (bool) ('' === $val['null']), // not null is empty, null is yes
|
||||
'default' => $val['default'],
|
||||
'primary' => (strtolower($val['key']) == 'pri'),
|
||||
'autoinc' => (strtolower($val['extra']) == 'auto_increment'),
|
||||
];
|
||||
}
|
||||
}
|
||||
return $this->fieldCase($info);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据库的表信息
|
||||
* @access public
|
||||
* @param string $dbName
|
||||
* @return array
|
||||
*/
|
||||
public function getTables($dbName = '')
|
||||
{
|
||||
$sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'";
|
||||
$pdo = $this->linkID->query($sql);
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
foreach ($result as $key => $val) {
|
||||
$info[$key] = current($val);
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL性能分析
|
||||
* @access protected
|
||||
* @param string $sql
|
||||
* @return array
|
||||
*/
|
||||
protected function getExplain($sql)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function supportSavepoint(){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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\db\connector;
|
||||
|
||||
use PDO;
|
||||
use think\db\Connection;
|
||||
|
||||
/**
|
||||
* Sqlite数据库驱动
|
||||
*/
|
||||
class Sqlite extends Connection
|
||||
{
|
||||
|
||||
/**
|
||||
* 解析pdo连接的dsn信息
|
||||
* @access protected
|
||||
* @param array $config 连接信息
|
||||
* @return string
|
||||
*/
|
||||
protected function parseDsn($config)
|
||||
{
|
||||
$dsn = 'sqlite:' . $config['database'];
|
||||
return $dsn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据表的字段信息
|
||||
* @access public
|
||||
* @param string $tableName
|
||||
* @return array
|
||||
*/
|
||||
public function getFields($tableName)
|
||||
{
|
||||
$this->initConnect(true);
|
||||
list($tableName) = explode(' ', $tableName);
|
||||
$sql = 'PRAGMA table_info( ' . $tableName . ' )';
|
||||
$pdo = $this->linkID->query($sql);
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
if ($result) {
|
||||
foreach ($result as $key => $val) {
|
||||
$val = array_change_key_case($val);
|
||||
$info[$val['name']] = [
|
||||
'name' => $val['name'],
|
||||
'type' => $val['type'],
|
||||
'notnull' => 1 === $val['notnull'],
|
||||
'default' => $val['dflt_value'],
|
||||
'primary' => '1' == $val['pk'],
|
||||
'autoinc' => '1' == $val['pk'],
|
||||
];
|
||||
}
|
||||
}
|
||||
return $this->fieldCase($info);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据库的表信息
|
||||
* @access public
|
||||
* @param string $dbName
|
||||
* @return array
|
||||
*/
|
||||
public function getTables($dbName = '')
|
||||
{
|
||||
$sql = "SELECT name FROM sqlite_master WHERE type='table' "
|
||||
. "UNION ALL SELECT name FROM sqlite_temp_master "
|
||||
. "WHERE type='table' ORDER BY name";
|
||||
$pdo = $this->linkID->query($sql);
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
foreach ($result as $key => $val) {
|
||||
$info[$key] = current($val);
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL性能分析
|
||||
* @access protected
|
||||
* @param string $sql
|
||||
* @return array
|
||||
*/
|
||||
protected function getExplain($sql)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function supportSavepoint(){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2012 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: liu21st <liu21st@gmail.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\db\connector;
|
||||
|
||||
use PDO;
|
||||
use think\db\Connection;
|
||||
|
||||
/**
|
||||
* Sqlsrv数据库驱动
|
||||
*/
|
||||
class Sqlsrv extends Connection
|
||||
{
|
||||
// PDO连接参数
|
||||
protected $params = [
|
||||
PDO::ATTR_CASE => PDO::CASE_LOWER,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_STRINGIFY_FETCHES => false,
|
||||
PDO::SQLSRV_ATTR_ENCODING => PDO::SQLSRV_ENCODING_UTF8,
|
||||
];
|
||||
|
||||
/**
|
||||
* 解析pdo连接的dsn信息
|
||||
* @access protected
|
||||
* @param array $config 连接信息
|
||||
* @return string
|
||||
*/
|
||||
protected function parseDsn($config)
|
||||
{
|
||||
$dsn = 'sqlsrv:Database=' . $config['database'] . ';Server=' . $config['hostname'];
|
||||
if (!empty($config['hostport'])) {
|
||||
$dsn .= ',' . $config['hostport'];
|
||||
}
|
||||
return $dsn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据表的字段信息
|
||||
* @access public
|
||||
* @param string $tableName
|
||||
* @return array
|
||||
*/
|
||||
public function getFields($tableName)
|
||||
{
|
||||
$this->initConnect(true);
|
||||
list($tableName) = explode(' ', $tableName);
|
||||
$sql = "SELECT column_name, data_type, column_default, is_nullable
|
||||
FROM information_schema.tables AS t
|
||||
JOIN information_schema.columns AS c
|
||||
ON t.table_catalog = c.table_catalog
|
||||
AND t.table_schema = c.table_schema
|
||||
AND t.table_name = c.table_name
|
||||
WHERE t.table_name = '$tableName'";
|
||||
$pdo = $this->linkID->query($sql);
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
if ($result) {
|
||||
foreach ($result as $key => $val) {
|
||||
$val = array_change_key_case($val);
|
||||
$info[$val['column_name']] = [
|
||||
'name' => $val['column_name'],
|
||||
'type' => $val['data_type'],
|
||||
'notnull' => (bool) ('' === $val['is_nullable']), // not null is empty, null is yes
|
||||
'default' => $val['column_default'],
|
||||
'primary' => false,
|
||||
'autoinc' => false,
|
||||
];
|
||||
}
|
||||
}
|
||||
return $this->fieldCase($info);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据表的字段信息
|
||||
* @access public
|
||||
* @param string $dbName
|
||||
* @return array
|
||||
*/
|
||||
public function getTables($dbName = '')
|
||||
{
|
||||
$sql = "SELECT TABLE_NAME
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_TYPE = 'BASE TABLE'
|
||||
";
|
||||
$pdo = $this->linkID->query($sql);
|
||||
$result = $pdo->fetchAll(PDO::FETCH_ASSOC);
|
||||
$info = [];
|
||||
foreach ($result as $key => $val) {
|
||||
$info[$key] = current($val);
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL性能分析
|
||||
* @access protected
|
||||
* @param string $sql
|
||||
* @return array
|
||||
*/
|
||||
protected function getExplain($sql)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
CREATE OR REPLACE FUNCTION pgsql_type(a_type varchar) RETURNS varchar AS
|
||||
$BODY$
|
||||
DECLARE
|
||||
v_type varchar;
|
||||
BEGIN
|
||||
IF a_type='int8' THEN
|
||||
v_type:='bigint';
|
||||
ELSIF a_type='int4' THEN
|
||||
v_type:='integer';
|
||||
ELSIF a_type='int2' THEN
|
||||
v_type:='smallint';
|
||||
ELSIF a_type='bpchar' THEN
|
||||
v_type:='char';
|
||||
ELSE
|
||||
v_type:=a_type;
|
||||
END IF;
|
||||
RETURN v_type;
|
||||
END;
|
||||
$BODY$
|
||||
LANGUAGE PLPGSQL;
|
||||
|
||||
CREATE TYPE "public"."tablestruct" AS (
|
||||
"fields_key_name" varchar(100),
|
||||
"fields_name" VARCHAR(200),
|
||||
"fields_type" VARCHAR(20),
|
||||
"fields_length" BIGINT,
|
||||
"fields_not_null" VARCHAR(10),
|
||||
"fields_default" VARCHAR(500),
|
||||
"fields_comment" VARCHAR(1000)
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION "public"."table_msg" (a_schema_name varchar, a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS
|
||||
$body$
|
||||
DECLARE
|
||||
v_ret tablestruct;
|
||||
v_oid oid;
|
||||
v_sql varchar;
|
||||
v_rec RECORD;
|
||||
v_key varchar;
|
||||
BEGIN
|
||||
SELECT
|
||||
pg_class.oid INTO v_oid
|
||||
FROM
|
||||
pg_class
|
||||
INNER JOIN pg_namespace ON (pg_class.relnamespace = pg_namespace.oid AND lower(pg_namespace.nspname) = a_schema_name)
|
||||
WHERE
|
||||
pg_class.relname=a_table_name;
|
||||
IF NOT FOUND THEN
|
||||
RETURN;
|
||||
END IF;
|
||||
|
||||
v_sql='
|
||||
SELECT
|
||||
pg_attribute.attname AS fields_name,
|
||||
pg_attribute.attnum AS fields_index,
|
||||
pgsql_type(pg_type.typname::varchar) AS fields_type,
|
||||
pg_attribute.atttypmod-4 as fields_length,
|
||||
CASE WHEN pg_attribute.attnotnull THEN ''not null''
|
||||
ELSE ''''
|
||||
END AS fields_not_null,
|
||||
pg_attrdef.adsrc AS fields_default,
|
||||
pg_description.description AS fields_comment
|
||||
FROM
|
||||
pg_attribute
|
||||
INNER JOIN pg_class ON pg_attribute.attrelid = pg_class.oid
|
||||
INNER JOIN pg_type ON pg_attribute.atttypid = pg_type.oid
|
||||
LEFT OUTER JOIN pg_attrdef ON pg_attrdef.adrelid = pg_class.oid AND pg_attrdef.adnum = pg_attribute.attnum
|
||||
LEFT OUTER JOIN pg_description ON pg_description.objoid = pg_class.oid AND pg_description.objsubid = pg_attribute.attnum
|
||||
WHERE
|
||||
pg_attribute.attnum > 0
|
||||
AND attisdropped <> ''t''
|
||||
AND pg_class.oid = ' || v_oid || '
|
||||
ORDER BY pg_attribute.attnum' ;
|
||||
|
||||
FOR v_rec IN EXECUTE v_sql LOOP
|
||||
v_ret.fields_name=v_rec.fields_name;
|
||||
v_ret.fields_type=v_rec.fields_type;
|
||||
IF v_rec.fields_length > 0 THEN
|
||||
v_ret.fields_length:=v_rec.fields_length;
|
||||
ELSE
|
||||
v_ret.fields_length:=NULL;
|
||||
END IF;
|
||||
v_ret.fields_not_null=v_rec.fields_not_null;
|
||||
v_ret.fields_default=v_rec.fields_default;
|
||||
v_ret.fields_comment=v_rec.fields_comment;
|
||||
SELECT constraint_name INTO v_key FROM information_schema.key_column_usage WHERE table_schema=a_schema_name AND table_name=a_table_name AND column_name=v_rec.fields_name;
|
||||
IF FOUND THEN
|
||||
v_ret.fields_key_name=v_key;
|
||||
ELSE
|
||||
v_ret.fields_key_name='';
|
||||
END IF;
|
||||
RETURN NEXT v_ret;
|
||||
END LOOP;
|
||||
RETURN ;
|
||||
END;
|
||||
$body$
|
||||
LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER;
|
||||
|
||||
COMMENT ON FUNCTION "public"."table_msg"(a_schema_name varchar, a_table_name varchar)
|
||||
IS '获得表信息';
|
||||
|
||||
---重载一个函数
|
||||
CREATE OR REPLACE FUNCTION "public"."table_msg" (a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS
|
||||
$body$
|
||||
DECLARE
|
||||
v_ret tablestruct;
|
||||
BEGIN
|
||||
FOR v_ret IN SELECT * FROM table_msg('public',a_table_name) LOOP
|
||||
RETURN NEXT v_ret;
|
||||
END LOOP;
|
||||
RETURN;
|
||||
END;
|
||||
$body$
|
||||
LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER;
|
||||
|
||||
COMMENT ON FUNCTION "public"."table_msg"(a_table_name varchar)
|
||||
IS '获得表信息';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user