113 changed files with 19860 additions and 0 deletions
@ -0,0 +1,209 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: liu21st <liu21st@gmail.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\cache; |
|||
|
|||
/** |
|||
* 缓存基础类 |
|||
*/ |
|||
abstract class Driver |
|||
{ |
|||
protected $handler = null; |
|||
protected $options = []; |
|||
protected $tag; |
|||
|
|||
/** |
|||
* 判断缓存是否存在 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @return bool |
|||
*/ |
|||
abstract public function has($name); |
|||
|
|||
/** |
|||
* 读取缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param mixed $default 默认值 |
|||
* @return mixed |
|||
*/ |
|||
abstract public function get($name, $default = false); |
|||
|
|||
/** |
|||
* 写入缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param mixed $value 存储数据 |
|||
* @param int $expire 有效时间 0为永久 |
|||
* @return boolean |
|||
*/ |
|||
abstract public function set($name, $value, $expire = null); |
|||
|
|||
/** |
|||
* 自增缓存(针对数值缓存) |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param int $step 步长 |
|||
* @return false|int |
|||
*/ |
|||
abstract public function inc($name, $step = 1); |
|||
|
|||
/** |
|||
* 自减缓存(针对数值缓存) |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param int $step 步长 |
|||
* @return false|int |
|||
*/ |
|||
abstract public function dec($name, $step = 1); |
|||
|
|||
/** |
|||
* 删除缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @return boolean |
|||
*/ |
|||
abstract public function rm($name); |
|||
|
|||
/** |
|||
* 清除缓存 |
|||
* @access public |
|||
* @param string $tag 标签名 |
|||
* @return boolean |
|||
*/ |
|||
abstract public function clear($tag = null); |
|||
|
|||
/** |
|||
* 获取实际的缓存标识 |
|||
* @access public |
|||
* @param string $name 缓存名 |
|||
* @return string |
|||
*/ |
|||
protected function getCacheKey($name) |
|||
{ |
|||
return $this->options['prefix'] . $name; |
|||
} |
|||
|
|||
/** |
|||
* 读取缓存并删除 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @return mixed |
|||
*/ |
|||
public function pull($name) |
|||
{ |
|||
$result = $this->get($name, false); |
|||
if ($result) { |
|||
$this->rm($name); |
|||
return $result; |
|||
} else { |
|||
return; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 如果不存在则写入缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param mixed $value 存储数据 |
|||
* @param int $expire 有效时间 0为永久 |
|||
* @return mixed |
|||
*/ |
|||
public function remember($name, $value, $expire = null) |
|||
{ |
|||
if (!$this->has($name)) { |
|||
if ($value instanceof \Closure) { |
|||
$value = call_user_func($value); |
|||
} |
|||
$this->set($name, $value, $expire); |
|||
} else { |
|||
$value = $this->get($name); |
|||
} |
|||
return $value; |
|||
} |
|||
|
|||
/** |
|||
* 缓存标签 |
|||
* @access public |
|||
* @param string $name 标签名 |
|||
* @param string|array $keys 缓存标识 |
|||
* @param bool $overlay 是否覆盖 |
|||
* @return $this |
|||
*/ |
|||
public function tag($name, $keys = null, $overlay = false) |
|||
{ |
|||
if (is_null($keys)) { |
|||
$this->tag = $name; |
|||
} else { |
|||
$key = 'tag_' . md5($name); |
|||
if (is_string($keys)) { |
|||
$keys = explode(',', $keys); |
|||
} |
|||
$keys = array_map([$this, 'getCacheKey'], $keys); |
|||
if ($overlay) { |
|||
$value = $keys; |
|||
} else { |
|||
$value = array_unique(array_merge($this->getTagItem($name), $keys)); |
|||
} |
|||
$this->set($key, implode(',', $value)); |
|||
} |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 更新标签 |
|||
* @access public |
|||
* @param string $name 缓存标识 |
|||
* @return void |
|||
*/ |
|||
protected function setTagItem($name) |
|||
{ |
|||
if ($this->tag) { |
|||
$key = 'tag_' . md5($this->tag); |
|||
$this->tag = null; |
|||
if ($this->has($key)) { |
|||
$value = $this->get($key); |
|||
$value .= ',' . $name; |
|||
} else { |
|||
$value = $name; |
|||
} |
|||
$this->set($key, $value); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取标签包含的缓存标识 |
|||
* @access public |
|||
* @param string $tag 缓存标签 |
|||
* @return array |
|||
*/ |
|||
protected function getTagItem($tag) |
|||
{ |
|||
$key = 'tag_' . md5($tag); |
|||
$value = $this->get($key); |
|||
if ($value) { |
|||
return explode(',', $value); |
|||
} else { |
|||
return []; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 返回句柄对象,可执行其它高级方法 |
|||
* |
|||
* @access public |
|||
* @return object |
|||
*/ |
|||
public function handler() |
|||
{ |
|||
return $this->handler; |
|||
} |
|||
} |
|||
@ -0,0 +1,248 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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\Driver; |
|||
|
|||
/** |
|||
* 文件类型缓存类 |
|||
* @author liu21st <liu21st@gmail.com> |
|||
*/ |
|||
class File extends Driver |
|||
{ |
|||
protected $options = [ |
|||
'expire' => 0, |
|||
'cache_subdir' => true, |
|||
'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) != DS) { |
|||
$this->options['path'] .= DS; |
|||
} |
|||
$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 protected |
|||
* @param string $name 缓存变量名 |
|||
* @return string |
|||
*/ |
|||
protected function getCacheKey($name) |
|||
{ |
|||
$name = md5($name); |
|||
if ($this->options['cache_subdir']) { |
|||
// 使用子目录 |
|||
$name = substr($name, 0, 2) . DS . substr($name, 2); |
|||
} |
|||
if ($this->options['prefix']) { |
|||
$name = $this->options['prefix'] . DS . $name; |
|||
} |
|||
$filename = $this->options['path'] . $name . '.php'; |
|||
$dir = dirname($filename); |
|||
if (!is_dir($dir)) { |
|||
mkdir($dir, 0755, true); |
|||
} |
|||
return $filename; |
|||
} |
|||
|
|||
/** |
|||
* 判断缓存是否存在 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @return bool |
|||
*/ |
|||
public function has($name) |
|||
{ |
|||
return $this->get($name) ? true : false; |
|||
} |
|||
|
|||
/** |
|||
* 读取缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param mixed $default 默认值 |
|||
* @return mixed |
|||
*/ |
|||
public function get($name, $default = false) |
|||
{ |
|||
$filename = $this->getCacheKey($name); |
|||
if (!is_file($filename)) { |
|||
return $default; |
|||
} |
|||
$content = file_get_contents($filename); |
|||
if (false !== $content) { |
|||
$expire = (int) substr($content, 8, 12); |
|||
if (0 != $expire && $_SERVER['REQUEST_TIME'] > filemtime($filename) + $expire) { |
|||
//缓存过期删除缓存文件 |
|||
$this->unlink($filename); |
|||
return $default; |
|||
} |
|||
$content = substr($content, 20, -3); |
|||
if ($this->options['data_compress'] && function_exists('gzcompress')) { |
|||
//启用数据压缩 |
|||
$content = gzuncompress($content); |
|||
} |
|||
$content = unserialize($content); |
|||
return $content; |
|||
} else { |
|||
return $default; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 写入缓存 |
|||
* @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->getCacheKey($name); |
|||
if ($this->tag && !is_file($filename)) { |
|||
$first = true; |
|||
} |
|||
$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) { |
|||
isset($first) && $this->setTagItem($filename); |
|||
clearstatcache(); |
|||
return true; |
|||
} else { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 自增缓存(针对数值缓存) |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param int $step 步长 |
|||
* @return false|int |
|||
*/ |
|||
public function inc($name, $step = 1) |
|||
{ |
|||
if ($this->has($name)) { |
|||
$value = $this->get($name) + $step; |
|||
} else { |
|||
$value = $step; |
|||
} |
|||
return $this->set($name, $value, 0) ? $value : false; |
|||
} |
|||
|
|||
/** |
|||
* 自减缓存(针对数值缓存) |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param int $step 步长 |
|||
* @return false|int |
|||
*/ |
|||
public function dec($name, $step = 1) |
|||
{ |
|||
if ($this->has($name)) { |
|||
$value = $this->get($name) - $step; |
|||
} else { |
|||
$value = $step; |
|||
} |
|||
return $this->set($name, $value, 0) ? $value : false; |
|||
} |
|||
|
|||
/** |
|||
* 删除缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @return boolean |
|||
*/ |
|||
public function rm($name) |
|||
{ |
|||
return $this->unlink($this->getCacheKey($name)); |
|||
} |
|||
|
|||
/** |
|||
* 清除缓存 |
|||
* @access public |
|||
* @param string $tag 标签名 |
|||
* @return boolean |
|||
*/ |
|||
public function clear($tag = null) |
|||
{ |
|||
if ($tag) { |
|||
// 指定标签清除 |
|||
$keys = $this->getTagItem($tag); |
|||
foreach ($keys as $key) { |
|||
$this->unlink($key); |
|||
} |
|||
$this->rm('tag_' . md5($tag)); |
|||
return true; |
|||
} |
|||
$files = (array) glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*'); |
|||
foreach ($files as $path) { |
|||
if (is_dir($path)) { |
|||
array_map('unlink', glob($path . '/*.php')); |
|||
rmdir($path); |
|||
} else { |
|||
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); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,185 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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\Driver; |
|||
|
|||
/** |
|||
* 文件类型缓存类 |
|||
* @author liu21st <liu21st@gmail.com> |
|||
*/ |
|||
class Lite extends Driver |
|||
{ |
|||
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) != DS) { |
|||
$this->options['path'] .= DS; |
|||
} |
|||
|
|||
} |
|||
|
|||
/** |
|||
* 取得变量的存储文件名 |
|||
* @access protected |
|||
* @param string $name 缓存变量名 |
|||
* @return string |
|||
*/ |
|||
protected function getCacheKey($name) |
|||
{ |
|||
return $this->options['path'] . $this->options['prefix'] . md5($name) . '.php'; |
|||
} |
|||
|
|||
/** |
|||
* 判断缓存是否存在 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @return mixed |
|||
*/ |
|||
public function has($name) |
|||
{ |
|||
return $this->get($name) ? true : false; |
|||
} |
|||
|
|||
/** |
|||
* 读取缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param mixed $default 默认值 |
|||
* @return mixed |
|||
*/ |
|||
public function get($name, $default = false) |
|||
{ |
|||
$filename = $this->getCacheKey($name); |
|||
if (is_file($filename)) { |
|||
// 判断是否过期 |
|||
$mtime = filemtime($filename); |
|||
if ($mtime < $_SERVER['REQUEST_TIME']) { |
|||
// 清除已经过期的文件 |
|||
unlink($filename); |
|||
return $default; |
|||
} |
|||
return include $filename; |
|||
} else { |
|||
return $default; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 写入缓存 |
|||
* @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->getCacheKey($name); |
|||
if ($this->tag && !is_file($filename)) { |
|||
$first = true; |
|||
} |
|||
$ret = file_put_contents($filename, ("<?php return " . var_export($value, true) . ";"));
|
|||
// 通过设置修改时间实现有效期 |
|||
if ($ret) { |
|||
isset($first) && $this->setTagItem($filename); |
|||
touch($filename, $_SERVER['REQUEST_TIME'] + $expire); |
|||
} |
|||
return $ret; |
|||
} |
|||
|
|||
/** |
|||
* 自增缓存(针对数值缓存) |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param int $step 步长 |
|||
* @return false|int |
|||
*/ |
|||
public function inc($name, $step = 1) |
|||
{ |
|||
if ($this->has($name)) { |
|||
$value = $this->get($name) + $step; |
|||
} else { |
|||
$value = $step; |
|||
} |
|||
return $this->set($name, $value, 0) ? $value : false; |
|||
} |
|||
|
|||
/** |
|||
* 自减缓存(针对数值缓存) |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param int $step 步长 |
|||
* @return false|int |
|||
*/ |
|||
public function dec($name, $step = 1) |
|||
{ |
|||
if ($this->has($name)) { |
|||
$value = $this->get($name) - $step; |
|||
} else { |
|||
$value = $step; |
|||
} |
|||
return $this->set($name, $value, 0) ? $value : false; |
|||
} |
|||
|
|||
/** |
|||
* 删除缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @return boolean |
|||
*/ |
|||
public function rm($name) |
|||
{ |
|||
return unlink($this->getCacheKey($name)); |
|||
} |
|||
|
|||
/** |
|||
* 清除缓存 |
|||
* @access public |
|||
* @param string $tag 标签名 |
|||
* @return bool |
|||
*/ |
|||
public function clear($tag = null) |
|||
{ |
|||
if ($tag) { |
|||
// 指定标签清除 |
|||
$keys = $this->getTagItem($tag); |
|||
foreach ($keys as $key) { |
|||
unlink($key); |
|||
} |
|||
$this->rm('tag_' . md5($tag)); |
|||
return true; |
|||
} |
|||
array_map("unlink", glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*.php')); |
|||
} |
|||
} |
|||
@ -0,0 +1,171 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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\Driver; |
|||
|
|||
class Memcache extends Driver |
|||
{ |
|||
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 bool |
|||
*/ |
|||
public function has($name) |
|||
{ |
|||
$key = $this->getCacheKey($name); |
|||
return $this->handler->get($key) ? true : false; |
|||
} |
|||
|
|||
/** |
|||
* 读取缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param mixed $default 默认值 |
|||
* @return mixed |
|||
*/ |
|||
public function get($name, $default = false) |
|||
{ |
|||
$result = $this->handler->get($this->getCacheKey($name)); |
|||
return false !== $result ? $result : $default; |
|||
} |
|||
|
|||
/** |
|||
* 写入缓存 |
|||
* @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']; |
|||
} |
|||
if ($this->tag && !$this->has($name)) { |
|||
$first = true; |
|||
} |
|||
$key = $this->getCacheKey($name); |
|||
if ($this->handler->set($key, $value, 0, $expire)) { |
|||
isset($first) && $this->setTagItem($key); |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 自增缓存(针对数值缓存) |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param int $step 步长 |
|||
* @return false|int |
|||
*/ |
|||
public function inc($name, $step = 1) |
|||
{ |
|||
$key = $this->getCacheKey($name); |
|||
return $this->handler->increment($key, $step); |
|||
} |
|||
|
|||
/** |
|||
* 自减缓存(针对数值缓存) |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param int $step 步长 |
|||
* @return false|int |
|||
*/ |
|||
public function dec($name, $step = 1) |
|||
{ |
|||
$key = $this->getCacheKey($name); |
|||
$value = $this->handler->get($key) - $step; |
|||
$res = $this->handler->set($key, $value); |
|||
if (!$res) { |
|||
return false; |
|||
} else { |
|||
return $value; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 删除缓存 |
|||
* @param string $name 缓存变量名 |
|||
* @param bool|false $ttl |
|||
* @return bool |
|||
*/ |
|||
public function rm($name, $ttl = false) |
|||
{ |
|||
$key = $this->getCacheKey($name); |
|||
return false === $ttl ? |
|||
$this->handler->delete($key) : |
|||
$this->handler->delete($key, $ttl); |
|||
} |
|||
|
|||
/** |
|||
* 清除缓存 |
|||
* @access public |
|||
* @param string $tag 标签名 |
|||
* @return bool |
|||
*/ |
|||
public function clear($tag = null) |
|||
{ |
|||
if ($tag) { |
|||
// 指定标签清除 |
|||
$keys = $this->getTagItem($tag); |
|||
foreach ($keys as $key) { |
|||
$this->handler->delete($key); |
|||
} |
|||
$this->rm('tag_' . md5($tag)); |
|||
return true; |
|||
} |
|||
return $this->handler->flush(); |
|||
} |
|||
} |
|||
@ -0,0 +1,181 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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\Driver; |
|||
|
|||
class Memcached extends Driver |
|||
{ |
|||
protected $options = [ |
|||
'host' => '127.0.0.1', |
|||
'port' => 11211, |
|||
'expire' => 0, |
|||
'timeout' => 0, // 超时时间(单位:毫秒) |
|||
'prefix' => '', |
|||
'username' => '', //账号 |
|||
'password' => '', //密码 |
|||
'option' => [], |
|||
]; |
|||
|
|||
/** |
|||
* 构造函数 |
|||
* @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 (!empty($this->options['option'])) { |
|||
$this->handler->setOptions($this->options['option']); |
|||
} |
|||
// 设置连接超时时间(单位:毫秒) |
|||
if ($this->options['timeout'] > 0) { |
|||
$this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->options['timeout']); |
|||
} |
|||
// 支持集群 |
|||
$hosts = 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); |
|||
if ('' != $this->options['username']) { |
|||
$this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); |
|||
$this->handler->setSaslAuthData($this->options['username'], $this->options['password']); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 判断缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @return bool |
|||
*/ |
|||
public function has($name) |
|||
{ |
|||
$key = $this->getCacheKey($name); |
|||
return $this->handler->get($key) ? true : false; |
|||
} |
|||
|
|||
/** |
|||
* 读取缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param mixed $default 默认值 |
|||
* @return mixed |
|||
*/ |
|||
public function get($name, $default = false) |
|||
{ |
|||
$result = $this->handler->get($this->getCacheKey($name)); |
|||
return false !== $result ? $result : $default; |
|||
} |
|||
|
|||
/** |
|||
* 写入缓存 |
|||
* @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']; |
|||
} |
|||
if ($this->tag && !$this->has($name)) { |
|||
$first = true; |
|||
} |
|||
$key = $this->getCacheKey($name); |
|||
$expire = 0 == $expire ? 0 : $_SERVER['REQUEST_TIME'] + $expire; |
|||
if ($this->handler->set($key, $value, $expire)) { |
|||
isset($first) && $this->setTagItem($key); |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 自增缓存(针对数值缓存) |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param int $step 步长 |
|||
* @return false|int |
|||
*/ |
|||
public function inc($name, $step = 1) |
|||
{ |
|||
$key = $this->getCacheKey($name); |
|||
return $this->handler->increment($key, $step); |
|||
} |
|||
|
|||
/** |
|||
* 自减缓存(针对数值缓存) |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param int $step 步长 |
|||
* @return false|int |
|||
*/ |
|||
public function dec($name, $step = 1) |
|||
{ |
|||
$key = $this->getCacheKey($name); |
|||
$value = $this->handler->get($key) - $step; |
|||
$res = $this->handler->set($key, $value); |
|||
if (!$res) { |
|||
return false; |
|||
} else { |
|||
return $value; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 删除缓存 |
|||
* @param string $name 缓存变量名 |
|||
* @param bool|false $ttl |
|||
* @return bool |
|||
*/ |
|||
public function rm($name, $ttl = false) |
|||
{ |
|||
$key = $this->getCacheKey($name); |
|||
return false === $ttl ? |
|||
$this->handler->delete($key) : |
|||
$this->handler->delete($key, $ttl); |
|||
} |
|||
|
|||
/** |
|||
* 清除缓存 |
|||
* @access public |
|||
* @param string $tag 标签名 |
|||
* @return bool |
|||
*/ |
|||
public function clear($tag = null) |
|||
{ |
|||
if ($tag) { |
|||
// 指定标签清除 |
|||
$keys = $this->getTagItem($tag); |
|||
$this->handler->deleteMulti($keys); |
|||
$this->rm('tag_' . md5($tag)); |
|||
return true; |
|||
} |
|||
return $this->handler->flush(); |
|||
} |
|||
} |
|||
@ -0,0 +1,176 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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\Driver; |
|||
|
|||
/** |
|||
* Redis缓存驱动,适合单机部署、有前端代理实现高可用的场景,性能最好 |
|||
* 有需要在业务层实现读写分离、或者使用RedisCluster的需求,请使用Redisd驱动 |
|||
* |
|||
* 要求安装phpredis扩展:https://github.com/nicolasff/phpredis |
|||
* @author 尘缘 <130775@qq.com> |
|||
*/ |
|||
class Redis extends Driver |
|||
{ |
|||
protected $options = [ |
|||
'host' => '127.0.0.1', |
|||
'port' => 6379, |
|||
'password' => '', |
|||
'select' => 0, |
|||
'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']); |
|||
} |
|||
|
|||
if (0 != $this->options['select']) { |
|||
$this->handler->select($this->options['select']); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 判断缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @return bool |
|||
*/ |
|||
public function has($name) |
|||
{ |
|||
return $this->handler->get($this->getCacheKey($name)) ? true : false; |
|||
} |
|||
|
|||
/** |
|||
* 读取缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param mixed $default 默认值 |
|||
* @return mixed |
|||
*/ |
|||
public function get($name, $default = false) |
|||
{ |
|||
$value = $this->handler->get($this->getCacheKey($name)); |
|||
if (is_null($value)) { |
|||
return $default; |
|||
} |
|||
$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']; |
|||
} |
|||
if ($this->tag && !$this->has($name)) { |
|||
$first = true; |
|||
} |
|||
$key = $this->getCacheKey($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($key, $expire, $value); |
|||
} else { |
|||
$result = $this->handler->set($key, $value); |
|||
} |
|||
isset($first) && $this->setTagItem($key); |
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* 自增缓存(针对数值缓存) |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param int $step 步长 |
|||
* @return false|int |
|||
*/ |
|||
public function inc($name, $step = 1) |
|||
{ |
|||
$key = $this->getCacheKey($name); |
|||
return $this->handler->incrby($key, $step); |
|||
} |
|||
|
|||
/** |
|||
* 自减缓存(针对数值缓存) |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param int $step 步长 |
|||
* @return false|int |
|||
*/ |
|||
public function dec($name, $step = 1) |
|||
{ |
|||
$key = $this->getCacheKey($name); |
|||
return $this->handler->decrby($key, $step); |
|||
} |
|||
|
|||
/** |
|||
* 删除缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @return boolean |
|||
*/ |
|||
public function rm($name) |
|||
{ |
|||
return $this->handler->delete($this->getCacheKey($name)); |
|||
} |
|||
|
|||
/** |
|||
* 清除缓存 |
|||
* @access public |
|||
* @param string $tag 标签名 |
|||
* @return boolean |
|||
*/ |
|||
public function clear($tag = null) |
|||
{ |
|||
if ($tag) { |
|||
// 指定标签清除 |
|||
$keys = $this->getTagItem($tag); |
|||
foreach ($keys as $key) { |
|||
$this->handler->delete($key); |
|||
} |
|||
$this->rm('tag_' . md5($tag)); |
|||
return true; |
|||
} |
|||
return $this->handler->flushDB(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,195 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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\Driver; |
|||
|
|||
/** |
|||
* Sqlite缓存驱动 |
|||
* @author liu21st <liu21st@gmail.com> |
|||
*/ |
|||
class Sqlite extends Driver |
|||
{ |
|||
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 string |
|||
*/ |
|||
protected function getCacheKey($name) |
|||
{ |
|||
return $this->options['prefix'] . sqlite_escape_string($name); |
|||
} |
|||
|
|||
/** |
|||
* 判断缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @return bool |
|||
*/ |
|||
public function has($name) |
|||
{ |
|||
$name = $this->getCacheKey($name); |
|||
$sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1'; |
|||
$result = sqlite_query($this->handler, $sql); |
|||
return sqlite_num_rows($result); |
|||
} |
|||
|
|||
/** |
|||
* 读取缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param mixed $default 默认值 |
|||
* @return mixed |
|||
*/ |
|||
public function get($name, $default = false) |
|||
{ |
|||
$name = $this->getCacheKey($name); |
|||
$sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_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 $default; |
|||
} |
|||
|
|||
/** |
|||
* 写入缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param mixed $value 存储数据 |
|||
* @param integer $expire 有效时间(秒) |
|||
* @return boolean |
|||
*/ |
|||
public function set($name, $value, $expire = null) |
|||
{ |
|||
$name = $this->getCacheKey($name); |
|||
$value = sqlite_escape_string(serialize($value)); |
|||
if (is_null($expire)) { |
|||
$expire = $this->options['expire']; |
|||
} |
|||
$expire = (0 == $expire) ? 0 : ($_SERVER['REQUEST_TIME'] + $expire); //缓存有效期为0表示永久缓存 |
|||
if (function_exists('gzcompress')) { |
|||
//数据压缩 |
|||
$value = gzcompress($value, 3); |
|||
} |
|||
if ($this->tag) { |
|||
$tag = $this->tag; |
|||
$this->tag = null; |
|||
} else { |
|||
$tag = ''; |
|||
} |
|||
$sql = 'REPLACE INTO ' . $this->options['table'] . ' (var, value, expire, tag) VALUES (\'' . $name . '\', \'' . $value . '\', \'' . $expire . '\', \'' . $tag . '\')'; |
|||
if (sqlite_query($this->handler, $sql)) { |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 自增缓存(针对数值缓存) |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param int $step 步长 |
|||
* @return false|int |
|||
*/ |
|||
public function inc($name, $step = 1) |
|||
{ |
|||
if ($this->has($name)) { |
|||
$value = $this->get($name) + $step; |
|||
} else { |
|||
$value = $step; |
|||
} |
|||
return $this->set($name, $value, 0) ? $value : false; |
|||
} |
|||
|
|||
/** |
|||
* 自减缓存(针对数值缓存) |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param int $step 步长 |
|||
* @return false|int |
|||
*/ |
|||
public function dec($name, $step = 1) |
|||
{ |
|||
if ($this->has($name)) { |
|||
$value = $this->get($name) - $step; |
|||
} else { |
|||
$value = $step; |
|||
} |
|||
return $this->set($name, $value, 0) ? $value : false; |
|||
} |
|||
|
|||
/** |
|||
* 删除缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @return boolean |
|||
*/ |
|||
public function rm($name) |
|||
{ |
|||
$name = $this->getCacheKey($name); |
|||
$sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\''; |
|||
sqlite_query($this->handler, $sql); |
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* 清除缓存 |
|||
* @access public |
|||
* @param string $tag 标签名 |
|||
* @return boolean |
|||
*/ |
|||
public function clear($tag = null) |
|||
{ |
|||
if ($tag) { |
|||
$name = sqlite_escape_string($tag); |
|||
$sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE tag=\'' . $name . '\''; |
|||
sqlite_query($this->handler, $sql); |
|||
return true; |
|||
} |
|||
$sql = 'DELETE FROM ' . $this->options['table']; |
|||
sqlite_query($this->handler, $sql); |
|||
return true; |
|||
} |
|||
} |
|||
@ -0,0 +1,149 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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\Driver; |
|||
|
|||
/** |
|||
* Wincache缓存驱动 |
|||
* @author liu21st <liu21st@gmail.com> |
|||
*/ |
|||
class Wincache extends Driver |
|||
{ |
|||
protected $options = [ |
|||
'prefix' => '', |
|||
'expire' => 0, |
|||
]; |
|||
|
|||
/** |
|||
* 构造函数 |
|||
* @param array $options 缓存参数 |
|||
* @throws \BadFunctionCallException |
|||
* @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 bool |
|||
*/ |
|||
public function has($name) |
|||
{ |
|||
$key = $this->getCacheKey($name); |
|||
return wincache_ucache_exists($key); |
|||
} |
|||
|
|||
/** |
|||
* 读取缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param mixed $default 默认值 |
|||
* @return mixed |
|||
*/ |
|||
public function get($name, $default = false) |
|||
{ |
|||
$key = $this->getCacheKey($name); |
|||
return wincache_ucache_exists($key) ? wincache_ucache_get($key) : $default; |
|||
} |
|||
|
|||
/** |
|||
* 写入缓存 |
|||
* @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']; |
|||
} |
|||
$key = $this->getCacheKey($name); |
|||
if ($this->tag && !$this->has($name)) { |
|||
$first = true; |
|||
} |
|||
if (wincache_ucache_set($key, $value, $expire)) { |
|||
isset($first) && $this->setTagItem($key); |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 自增缓存(针对数值缓存) |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param int $step 步长 |
|||
* @return false|int |
|||
*/ |
|||
public function inc($name, $step = 1) |
|||
{ |
|||
$key = $this->getCacheKey($name); |
|||
return wincache_ucache_inc($key, $step); |
|||
} |
|||
|
|||
/** |
|||
* 自减缓存(针对数值缓存) |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param int $step 步长 |
|||
* @return false|int |
|||
*/ |
|||
public function dec($name, $step = 1) |
|||
{ |
|||
$key = $this->getCacheKey($name); |
|||
return wincache_ucache_dec($key, $step); |
|||
} |
|||
|
|||
/** |
|||
* 删除缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @return boolean |
|||
*/ |
|||
public function rm($name) |
|||
{ |
|||
return wincache_ucache_delete($this->getCacheKey($name)); |
|||
} |
|||
|
|||
/** |
|||
* 清除缓存 |
|||
* @access public |
|||
* @param string $tag 标签名 |
|||
* @return boolean |
|||
*/ |
|||
public function clear($tag = null) |
|||
{ |
|||
if ($tag) { |
|||
$keys = $this->getTagItem($tag); |
|||
foreach ($keys as $key) { |
|||
wincache_ucache_delete($key); |
|||
} |
|||
$this->rm('tag_' . md5($tag)); |
|||
return true; |
|||
} else { |
|||
return wincache_ucache_clear(); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,152 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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\Driver; |
|||
|
|||
/** |
|||
* Xcache缓存驱动 |
|||
* @author liu21st <liu21st@gmail.com> |
|||
*/ |
|||
class Xcache extends Driver |
|||
{ |
|||
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 bool |
|||
*/ |
|||
public function has($name) |
|||
{ |
|||
$key = $this->getCacheKey($name); |
|||
return xcache_isset($key); |
|||
} |
|||
|
|||
/** |
|||
* 读取缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param mixed $default 默认值 |
|||
* @return mixed |
|||
*/ |
|||
public function get($name, $default = false) |
|||
{ |
|||
$key = $this->getCacheKey($name); |
|||
return xcache_isset($key) ? xcache_get($key) : $default; |
|||
} |
|||
|
|||
/** |
|||
* 写入缓存 |
|||
* @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']; |
|||
} |
|||
if ($this->tag && !$this->has($name)) { |
|||
$first = true; |
|||
} |
|||
$key = $this->getCacheKey($name); |
|||
if (xcache_set($key, $value, $expire)) { |
|||
isset($first) && $this->setTagItem($key); |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 自增缓存(针对数值缓存) |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param int $step 步长 |
|||
* @return false|int |
|||
*/ |
|||
public function inc($name, $step = 1) |
|||
{ |
|||
$key = $this->getCacheKey($name); |
|||
return xcache_inc($key, $step); |
|||
} |
|||
|
|||
/** |
|||
* 自减缓存(针对数值缓存) |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @param int $step 步长 |
|||
* @return false|int |
|||
*/ |
|||
public function dec($name, $step = 1) |
|||
{ |
|||
$key = $this->getCacheKey($name); |
|||
return xcache_dec($key, $step); |
|||
} |
|||
|
|||
/** |
|||
* 删除缓存 |
|||
* @access public |
|||
* @param string $name 缓存变量名 |
|||
* @return boolean |
|||
*/ |
|||
public function rm($name) |
|||
{ |
|||
return xcache_unset($this->getCacheKey($name)); |
|||
} |
|||
|
|||
/** |
|||
* 清除缓存 |
|||
* @access public |
|||
* @param string $tag 标签名 |
|||
* @return boolean |
|||
*/ |
|||
public function clear($tag = null) |
|||
{ |
|||
if ($tag) { |
|||
// 指定标签清除 |
|||
$keys = $this->getTagItem($tag); |
|||
foreach ($keys as $key) { |
|||
xcache_unset($key); |
|||
} |
|||
$this->rm('tag_' . md5($tag)); |
|||
return true; |
|||
} |
|||
if (function_exists('xcache_unset_by_prefix')) { |
|||
return xcache_unset_by_prefix($this->options['prefix']); |
|||
} else { |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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~2017 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~2017 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,470 @@ |
|||
<?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; |
|||
use think\console\input\Argument; |
|||
use think\console\input\Definition; |
|||
use think\console\input\Option; |
|||
|
|||
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 Input */ |
|||
protected $input; |
|||
|
|||
/** @var Output */ |
|||
protected $output; |
|||
|
|||
/** |
|||
* 构造方法 |
|||
* @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; |
|||
} |
|||
|
|||
/** |
|||
* 获取控制台 |
|||
* @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->input = $input; |
|||
$this->output = $output; |
|||
|
|||
$this->getSynopsis(true); |
|||
$this->getSynopsis(false); |
|||
|
|||
$this->mergeConsoleDefinition(); |
|||
|
|||
try { |
|||
$input->bind($this->definition); |
|||
} catch (\Exception $e) { |
|||
if (!$this->ignoreValidationErrors) { |
|||
throw $e; |
|||
} |
|||
} |
|||
|
|||
$this->initialize($input, $output); |
|||
|
|||
if ($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 |
|||
* @return $this |
|||
*/ |
|||
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 |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
private function validateName($name) |
|||
{ |
|||
if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { |
|||
throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,464 @@ |
|||
<?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\Argument; |
|||
use think\console\input\Definition; |
|||
use think\console\input\Option; |
|||
|
|||
class Input |
|||
{ |
|||
|
|||
/** |
|||
* @var Definition |
|||
*/ |
|||
protected $definition; |
|||
|
|||
/** |
|||
* @var Option[] |
|||
*/ |
|||
protected $options = []; |
|||
|
|||
/** |
|||
* @var Argument[] |
|||
*/ |
|||
protected $arguments = []; |
|||
|
|||
protected $interactive = true; |
|||
|
|||
private $tokens; |
|||
private $parsed; |
|||
|
|||
public function __construct($argv = null) |
|||
{ |
|||
if (null === $argv) { |
|||
$argv = $_SERVER['argv']; |
|||
// 去除命令名 |
|||
array_shift($argv); |
|||
} |
|||
|
|||
$this->tokens = $argv; |
|||
|
|||
$this->definition = new Definition(); |
|||
} |
|||
|
|||
protected function setTokens(array $tokens) |
|||
{ |
|||
$this->tokens = $tokens; |
|||
} |
|||
|
|||
/** |
|||
* 绑定实例 |
|||
* @param Definition $definition A InputDefinition instance |
|||
*/ |
|||
public function bind(Definition $definition) |
|||
{ |
|||
$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; |
|||
} |
|||
|
|||
/** |
|||
* 检查原始参数是否包含某个值 |
|||
* @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,222 @@ |
|||
<?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 Exception; |
|||
use think\console\output\Ask; |
|||
use think\console\output\Descriptor; |
|||
use think\console\output\driver\Buffer; |
|||
use think\console\output\driver\Console; |
|||
use think\console\output\driver\Nothing; |
|||
use think\console\output\Question; |
|||
use think\console\output\question\Choice; |
|||
use think\console\output\question\Confirmation; |
|||
|
|||
/** |
|||
* Class Output |
|||
* @package think\console |
|||
* |
|||
* @see \think\console\output\driver\Console::setDecorated |
|||
* @method void setDecorated($decorated) |
|||
* |
|||
* @see \think\console\output\driver\Buffer::fetch |
|||
* @method string fetch() |
|||
* |
|||
* @method void info($message) |
|||
* @method void error($message) |
|||
* @method void comment($message) |
|||
* @method void warning($message) |
|||
* @method void highlight($message) |
|||
* @method void question($message) |
|||
*/ |
|||
class Output |
|||
{ |
|||
const VERBOSITY_QUIET = 0; |
|||
const VERBOSITY_NORMAL = 1; |
|||
const VERBOSITY_VERBOSE = 2; |
|||
const VERBOSITY_VERY_VERBOSE = 3; |
|||
const VERBOSITY_DEBUG = 4; |
|||
|
|||
const OUTPUT_NORMAL = 0; |
|||
const OUTPUT_RAW = 1; |
|||
const OUTPUT_PLAIN = 2; |
|||
|
|||
private $verbosity = self::VERBOSITY_NORMAL; |
|||
|
|||
/** @var Buffer|Console|Nothing */ |
|||
private $handle = null; |
|||
|
|||
protected $styles = [ |
|||
'info', |
|||
'error', |
|||
'comment', |
|||
'question', |
|||
'highlight', |
|||
'warning' |
|||
]; |
|||
|
|||
public function __construct($driver = 'console') |
|||
{ |
|||
$class = '\\think\\console\\output\\driver\\' . ucwords($driver); |
|||
|
|||
$this->handle = new $class($this); |
|||
} |
|||
|
|||
public function ask(Input $input, $question, $default = null, $validator = null) |
|||
{ |
|||
$question = new Question($question, $default); |
|||
$question->setValidator($validator); |
|||
|
|||
return $this->askQuestion($input, $question); |
|||
} |
|||
|
|||
public function askHidden(Input $input, $question, $validator = null) |
|||
{ |
|||
$question = new Question($question); |
|||
|
|||
$question->setHidden(true); |
|||
$question->setValidator($validator); |
|||
|
|||
return $this->askQuestion($input, $question); |
|||
} |
|||
|
|||
public function confirm(Input $input, $question, $default = true) |
|||
{ |
|||
return $this->askQuestion($input, new Confirmation($question, $default)); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function choice(Input $input, $question, array $choices, $default = null) |
|||
{ |
|||
if (null !== $default) { |
|||
$values = array_flip($choices); |
|||
$default = $values[$default]; |
|||
} |
|||
|
|||
return $this->askQuestion($input, new Choice($question, $choices, $default)); |
|||
} |
|||
|
|||
protected function askQuestion(Input $input, Question $question) |
|||
{ |
|||
$ask = new Ask($input, $this, $question); |
|||
$answer = $ask->run(); |
|||
|
|||
if ($input->isInteractive()) { |
|||
$this->newLine(); |
|||
} |
|||
|
|||
return $answer; |
|||
} |
|||
|
|||
protected function block($style, $message) |
|||
{ |
|||
$this->writeln("<{$style}>{$message}</$style>"); |
|||
} |
|||
|
|||
/** |
|||
* 输出空行 |
|||
* @param int $count |
|||
*/ |
|||
public function newLine($count = 1) |
|||
{ |
|||
$this->write(str_repeat(PHP_EOL, $count)); |
|||
} |
|||
|
|||
/** |
|||
* 输出信息并换行 |
|||
* @param string $messages |
|||
* @param int $type |
|||
*/ |
|||
public function writeln($messages, $type = self::OUTPUT_NORMAL) |
|||
{ |
|||
$this->write($messages, true, $type); |
|||
} |
|||
|
|||
/** |
|||
* 输出信息 |
|||
* @param string $messages |
|||
* @param bool $newline |
|||
* @param int $type |
|||
*/ |
|||
public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) |
|||
{ |
|||
$this->handle->write($messages, $newline, $type); |
|||
} |
|||
|
|||
public function renderException(\Exception $e) |
|||
{ |
|||
$this->handle->renderException($e); |
|||
} |
|||
|
|||
/** |
|||
* {@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; |
|||
} |
|||
|
|||
public function describe($object, array $options = []) |
|||
{ |
|||
$descriptor = new Descriptor(); |
|||
$options = array_merge([ |
|||
'raw_text' => false, |
|||
], $options); |
|||
|
|||
$descriptor->describe($this, $object, $options); |
|||
} |
|||
|
|||
public function __call($method, $args) |
|||
{ |
|||
if (in_array($method, $this->styles)) { |
|||
array_unshift($args, $method); |
|||
return call_user_func_array([$this, 'block'], $args); |
|||
} |
|||
|
|||
if ($this->handle && method_exists($this->handle, $method)) { |
|||
return call_user_func_array([$this->handle, $method], $args); |
|||
} else { |
|||
throw new Exception('method not exists:' . __CLASS__ . '->' . $method); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1 @@ |
|||
console 工具使用 hiddeninput.exe 在 windows 上隐藏密码输入,该二进制文件由第三方提供,相关源码和其他细节可以在 [Hidden Input](https://github.com/Seldaek/hidden-input) 找到。 |
|||
Binary file not shown.
@ -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\command; |
|||
|
|||
use 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,44 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: liu21st <liu21st@gmail.com> |
|||
// +---------------------------------------------------------------------- |
|||
namespace think\console\command; |
|||
|
|||
use think\console\Command; |
|||
use think\console\Input; |
|||
use think\console\input\Option; |
|||
use think\console\Output; |
|||
|
|||
class Clear extends Command |
|||
{ |
|||
protected function configure() |
|||
{ |
|||
// 指令配置 |
|||
$this |
|||
->setName('clear') |
|||
->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null) |
|||
->setDescription('Clear runtime file'); |
|||
} |
|||
|
|||
protected function execute(Input $input, Output $output) |
|||
{ |
|||
$path = $input->getOption('path') ?: RUNTIME_PATH; |
|||
$files = scandir($path); |
|||
if ($files) { |
|||
foreach ($files as $file) { |
|||
if ('.' != $file && '..' != $file && is_dir($path . $file)) { |
|||
array_map('unlink', glob($path . $file . '/*.*')); |
|||
} elseif (is_file($path . $file)) { |
|||
unlink($path . $file); |
|||
} |
|||
} |
|||
} |
|||
$output->writeln("<info>Clear Successed</info>"); |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\console\command; |
|||
|
|||
use think\console\Command; |
|||
use think\console\Input; |
|||
use think\console\input\Argument as InputArgument; |
|||
use think\console\input\Option as InputOption; |
|||
use think\console\Output; |
|||
|
|||
class Help extends Command |
|||
{ |
|||
|
|||
private $command; |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
protected function configure() |
|||
{ |
|||
$this->ignoreValidationErrors(); |
|||
|
|||
$this->setName('help')->setDefinition([ |
|||
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), |
|||
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), |
|||
])->setDescription('Displays help for a command')->setHelp(<<<EOF |
|||
The <info>%command.name%</info> command displays help for a given command: |
|||
|
|||
<info>php %command.full_name% list</info> |
|||
|
|||
To display the list of available commands, please use the <info>list</info> command. |
|||
EOF |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Sets the command. |
|||
* @param Command $command The command to set |
|||
*/ |
|||
public function setCommand(Command $command) |
|||
{ |
|||
$this->command = $command; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
protected function execute(Input $input, Output $output) |
|||
{ |
|||
if (null === $this->command) { |
|||
$this->command = $this->getConsole()->find($input->getArgument('command_name')); |
|||
} |
|||
|
|||
$output->describe($this->command, [ |
|||
'raw_text' => $input->getOption('raw'), |
|||
]); |
|||
|
|||
$this->command = null; |
|||
} |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\console\command; |
|||
|
|||
use think\console\Command; |
|||
use think\console\Input; |
|||
use think\console\Output; |
|||
use think\console\input\Argument as InputArgument; |
|||
use think\console\input\Option as InputOption; |
|||
use think\console\input\Definition as InputDefinition; |
|||
|
|||
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) |
|||
{ |
|||
$output->describe($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,109 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: 刘志淳 <chun@engineer.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\console\command; |
|||
|
|||
use think\Config; |
|||
use think\console\Command; |
|||
use think\console\Input; |
|||
use think\console\input\Argument; |
|||
use think\console\Output; |
|||
|
|||
abstract class Make extends Command |
|||
{ |
|||
|
|||
protected $type; |
|||
|
|||
abstract protected function getStub(); |
|||
|
|||
protected function configure() |
|||
{ |
|||
$this->addArgument('name', Argument::REQUIRED, "The name of the class"); |
|||
} |
|||
|
|||
protected function execute(Input $input, Output $output) |
|||
{ |
|||
|
|||
$name = trim($input->getArgument('name')); |
|||
|
|||
$classname = $this->getClassName($name); |
|||
|
|||
$pathname = $this->getPathName($classname); |
|||
|
|||
if (is_file($pathname)) { |
|||
$output->writeln('<error>' . $this->type . ' already exists!</error>'); |
|||
return false; |
|||
} |
|||
|
|||
if (!is_dir(dirname($pathname))) { |
|||
mkdir(strtolower(dirname($pathname)), 0755, true); |
|||
} |
|||
|
|||
file_put_contents($pathname, $this->buildClass($classname)); |
|||
|
|||
$output->writeln('<info>' . $this->type . ' created successfully.</info>'); |
|||
|
|||
} |
|||
|
|||
protected function buildClass($name) |
|||
{ |
|||
$stub = file_get_contents($this->getStub()); |
|||
|
|||
$namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\'); |
|||
|
|||
$class = str_replace($namespace . '\\', '', $name); |
|||
|
|||
return str_replace(['{%className%}', '{%namespace%}', '{%app_namespace%}'], [ |
|||
$class, |
|||
$namespace, |
|||
Config::get('app_namespace') |
|||
], $stub); |
|||
|
|||
} |
|||
|
|||
protected function getPathName($name) |
|||
{ |
|||
$name = str_replace(Config::get('app_namespace') . '\\', '', $name); |
|||
|
|||
return APP_PATH . str_replace('\\', '/', $name) . '.php'; |
|||
} |
|||
|
|||
protected function getClassName($name) |
|||
{ |
|||
$appNamespace = Config::get('app_namespace'); |
|||
|
|||
if (strpos($name, $appNamespace . '\\') === 0) { |
|||
return $name; |
|||
} |
|||
|
|||
if (Config::get('app_multi_module')) { |
|||
if (strpos($name, '/')) { |
|||
list($module, $name) = explode('/', $name, 2); |
|||
} else { |
|||
$module = 'common'; |
|||
} |
|||
} else { |
|||
$module = null; |
|||
} |
|||
|
|||
if (strpos($name, '/') !== false) { |
|||
$name = str_replace('/', '\\', $name); |
|||
} |
|||
|
|||
return $this->getNamespace($appNamespace, $module) . '\\' . $name; |
|||
} |
|||
|
|||
protected function getNamespace($appNamespace, $module) |
|||
{ |
|||
return $module ? ($appNamespace . '\\' . $module) : $appNamespace; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
<?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\Config; |
|||
use think\console\command\Make; |
|||
use think\console\input\Option; |
|||
|
|||
class Controller extends Make |
|||
{ |
|||
|
|||
protected $type = "Controller"; |
|||
|
|||
protected function configure() |
|||
{ |
|||
parent::configure(); |
|||
$this->setName('make:controller') |
|||
->addOption('plain', null, Option::VALUE_NONE, 'Generate an empty controller class.') |
|||
->setDescription('Create a new resource controller class'); |
|||
} |
|||
|
|||
protected function getStub() |
|||
{ |
|||
if ($this->input->getOption('plain')) { |
|||
return __DIR__ . '/stubs/controller.plain.stub'; |
|||
} |
|||
|
|||
return __DIR__ . '/stubs/controller.stub'; |
|||
} |
|||
|
|||
protected function getClassName($name) |
|||
{ |
|||
return parent::getClassName($name) . (Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''); |
|||
} |
|||
|
|||
protected function getNamespace($appNamespace, $module) |
|||
{ |
|||
return parent::getNamespace($appNamespace, $module) . '\controller'; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
<?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\console\command\Make; |
|||
|
|||
class Model extends Make |
|||
{ |
|||
protected $type = "Model"; |
|||
|
|||
protected function configure() |
|||
{ |
|||
parent::configure(); |
|||
$this->setName('make:model') |
|||
->setDescription('Create a new model class'); |
|||
} |
|||
|
|||
protected function getStub() |
|||
{ |
|||
return __DIR__ . '/stubs/model.stub'; |
|||
} |
|||
|
|||
protected function getNamespace($appNamespace, $module) |
|||
{ |
|||
return parent::getNamespace($appNamespace, $module) . '\model'; |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
<?php |
|||
|
|||
namespace {%namespace%}; |
|||
|
|||
use think\Controller; |
|||
|
|||
class {%className%} extends Controller |
|||
{ |
|||
// |
|||
} |
|||
@ -0,0 +1,85 @@ |
|||
<?php |
|||
|
|||
namespace {%namespace%}; |
|||
|
|||
use think\Controller; |
|||
use think\Request; |
|||
|
|||
class {%className%} extends Controller |
|||
{ |
|||
/** |
|||
* 显示资源列表 |
|||
* |
|||
* @return \think\Response |
|||
*/ |
|||
public function index() |
|||
{ |
|||
// |
|||
} |
|||
|
|||
/** |
|||
* 显示创建资源表单页. |
|||
* |
|||
* @return \think\Response |
|||
*/ |
|||
public function create() |
|||
{ |
|||
// |
|||
} |
|||
|
|||
/** |
|||
* 保存新建的资源 |
|||
* |
|||
* @param \think\Request $request |
|||
* @return \think\Response |
|||
*/ |
|||
public function save(Request $request) |
|||
{ |
|||
// |
|||
} |
|||
|
|||
/** |
|||
* 显示指定的资源 |
|||
* |
|||
* @param int $id |
|||
* @return \think\Response |
|||
*/ |
|||
public function read($id) |
|||
{ |
|||
// |
|||
} |
|||
|
|||
/** |
|||
* 显示编辑资源表单页. |
|||
* |
|||
* @param int $id |
|||
* @return \think\Response |
|||
*/ |
|||
public function edit($id) |
|||
{ |
|||
// |
|||
} |
|||
|
|||
/** |
|||
* 保存更新的资源 |
|||
* |
|||
* @param \think\Request $request |
|||
* @param int $id |
|||
* @return \think\Response |
|||
*/ |
|||
public function update(Request $request, $id) |
|||
{ |
|||
// |
|||
} |
|||
|
|||
/** |
|||
* 删除指定资源 |
|||
* |
|||
* @param int $id |
|||
* @return \think\Response |
|||
*/ |
|||
public function delete($id) |
|||
{ |
|||
// |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
<?php |
|||
|
|||
namespace {%namespace%}; |
|||
|
|||
use think\Model; |
|||
|
|||
class {%className%} extends Model |
|||
{ |
|||
// |
|||
} |
|||
@ -0,0 +1,281 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
namespace think\console\command\optimize; |
|||
|
|||
use think\App; |
|||
use think\console\Command; |
|||
use think\console\Input; |
|||
use think\console\Output; |
|||
|
|||
class Autoload extends Command |
|||
{ |
|||
|
|||
protected function configure() |
|||
{ |
|||
$this->setName('optimize:autoload') |
|||
->setDescription('Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'); |
|||
} |
|||
|
|||
protected function execute(Input $input, Output $output) |
|||
{ |
|||
|
|||
$classmapFile = <<<EOF |
|||
<?php |
|||
/** |
|||
* 类库映射 |
|||
*/ |
|||
|
|||
return [ |
|||
|
|||
EOF; |
|||
|
|||
$namespacesToScan = [ |
|||
App::$namespace . '\\' => realpath(rtrim(APP_PATH)), |
|||
'think\\' => LIB_PATH . 'think', |
|||
'behavior\\' => LIB_PATH . 'behavior', |
|||
'traits\\' => LIB_PATH . 'traits', |
|||
'' => realpath(rtrim(EXTEND_PATH)) |
|||
]; |
|||
|
|||
krsort($namespacesToScan); |
|||
$classMap = []; |
|||
foreach ($namespacesToScan as $namespace => $dir) { |
|||
|
|||
if (!is_dir($dir)) { |
|||
continue; |
|||
} |
|||
|
|||
$namespaceFilter = $namespace === '' ? null : $namespace; |
|||
$classMap = $this->addClassMapCode($dir, $namespaceFilter, $classMap); |
|||
} |
|||
|
|||
ksort($classMap); |
|||
foreach ($classMap as $class => $code) { |
|||
$classmapFile .= ' ' . var_export($class, true) . ' => ' . $code; |
|||
} |
|||
$classmapFile .= "];\n"; |
|||
|
|||
if (!is_dir(RUNTIME_PATH)) { |
|||
@mkdir(RUNTIME_PATH, 0755, true); |
|||
} |
|||
|
|||
file_put_contents(RUNTIME_PATH . 'classmap' . EXT, $classmapFile); |
|||
|
|||
$output->writeln('<info>Succeed!</info>'); |
|||
} |
|||
|
|||
protected function addClassMapCode($dir, $namespace, $classMap) |
|||
{ |
|||
foreach ($this->createMap($dir, $namespace) as $class => $path) { |
|||
|
|||
$pathCode = $this->getPathCode($path) . ",\n"; |
|||
|
|||
if (!isset($classMap[$class])) { |
|||
$classMap[$class] = $pathCode; |
|||
} elseif ($classMap[$class] !== $pathCode && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class] . ' ' . $path, '\\', '/'))) { |
|||
$this->output->writeln( |
|||
'<warning>Warning: Ambiguous class resolution, "' . $class . '"' . |
|||
' was found in both "' . str_replace(["',\n"], [ |
|||
'' |
|||
], $classMap[$class]) . '" and "' . $path . '", the first will be used.</warning>' |
|||
); |
|||
} |
|||
} |
|||
return $classMap; |
|||
} |
|||
|
|||
protected function getPathCode($path) |
|||
{ |
|||
|
|||
$baseDir = ''; |
|||
$appPath = $this->normalizePath(realpath(APP_PATH)); |
|||
$libPath = $this->normalizePath(realpath(LIB_PATH)); |
|||
$extendPath = $this->normalizePath(realpath(EXTEND_PATH)); |
|||
$path = $this->normalizePath($path); |
|||
|
|||
if (strpos($path, $libPath . '/') === 0) { |
|||
$path = substr($path, strlen(LIB_PATH)); |
|||
$baseDir = 'LIB_PATH'; |
|||
} elseif (strpos($path, $appPath . '/') === 0) { |
|||
$path = substr($path, strlen($appPath) + 1); |
|||
$baseDir = 'APP_PATH'; |
|||
} elseif (strpos($path, $extendPath . '/') === 0) { |
|||
$path = substr($path, strlen($extendPath) + 1); |
|||
$baseDir = 'EXTEND_PATH'; |
|||
} |
|||
|
|||
if ($path !== false) { |
|||
$baseDir .= " . "; |
|||
} |
|||
|
|||
return $baseDir . (($path !== false) ? var_export($path, true) : ""); |
|||
} |
|||
|
|||
protected function normalizePath($path) |
|||
{ |
|||
$parts = []; |
|||
$path = strtr($path, '\\', '/'); |
|||
$prefix = ''; |
|||
$absolute = false; |
|||
|
|||
if (preg_match('{^([0-9a-z]+:(?://(?:[a-z]:)?)?)}i', $path, $match)) { |
|||
$prefix = $match[1]; |
|||
$path = substr($path, strlen($prefix)); |
|||
} |
|||
|
|||
if (substr($path, 0, 1) === '/') { |
|||
$absolute = true; |
|||
$path = substr($path, 1); |
|||
} |
|||
|
|||
$up = false; |
|||
foreach (explode('/', $path) as $chunk) { |
|||
if ('..' === $chunk && ($absolute || $up)) { |
|||
array_pop($parts); |
|||
$up = !(empty($parts) || '..' === end($parts)); |
|||
} elseif ('.' !== $chunk && '' !== $chunk) { |
|||
$parts[] = $chunk; |
|||
$up = '..' !== $chunk; |
|||
} |
|||
} |
|||
|
|||
return $prefix . ($absolute ? '/' : '') . implode('/', $parts); |
|||
} |
|||
|
|||
protected function createMap($path, $namespace = null) |
|||
{ |
|||
if (is_string($path)) { |
|||
if (is_file($path)) { |
|||
$path = [new \SplFileInfo($path)]; |
|||
} elseif (is_dir($path)) { |
|||
|
|||
$objects = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::SELF_FIRST); |
|||
|
|||
$path = []; |
|||
|
|||
/** @var \SplFileInfo $object */ |
|||
foreach ($objects as $object) { |
|||
if ($object->isFile() && $object->getExtension() == 'php') { |
|||
$path[] = $object; |
|||
} |
|||
} |
|||
} else { |
|||
throw new \RuntimeException( |
|||
'Could not scan for classes inside "' . $path . |
|||
'" which does not appear to be a file nor a folder' |
|||
); |
|||
} |
|||
} |
|||
|
|||
$map = []; |
|||
|
|||
/** @var \SplFileInfo $file */ |
|||
foreach ($path as $file) { |
|||
$filePath = $file->getRealPath(); |
|||
|
|||
if (pathinfo($filePath, PATHINFO_EXTENSION) != 'php') { |
|||
continue; |
|||
} |
|||
|
|||
$classes = $this->findClasses($filePath); |
|||
|
|||
foreach ($classes as $class) { |
|||
if (null !== $namespace && 0 !== strpos($class, $namespace)) { |
|||
continue; |
|||
} |
|||
|
|||
if (!isset($map[$class])) { |
|||
$map[$class] = $filePath; |
|||
} elseif ($map[$class] !== $filePath && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($map[$class] . ' ' . $filePath, '\\', '/'))) { |
|||
$this->output->writeln( |
|||
'<warning>Warning: Ambiguous class resolution, "' . $class . '"' . |
|||
' was found in both "' . $map[$class] . '" and "' . $filePath . '", the first will be used.</warning>' |
|||
); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return $map; |
|||
} |
|||
|
|||
protected function findClasses($path) |
|||
{ |
|||
$extraTypes = '|trait'; |
|||
|
|||
$contents = @php_strip_whitespace($path); |
|||
if (!$contents) { |
|||
if (!file_exists($path)) { |
|||
$message = 'File at "%s" does not exist, check your classmap definitions'; |
|||
} elseif (!is_readable($path)) { |
|||
$message = 'File at "%s" is not readable, check its permissions'; |
|||
} elseif ('' === trim(file_get_contents($path))) { |
|||
return []; |
|||
} else { |
|||
$message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted'; |
|||
} |
|||
$error = error_get_last(); |
|||
if (isset($error['message'])) { |
|||
$message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message']; |
|||
} |
|||
throw new \RuntimeException(sprintf($message, $path)); |
|||
} |
|||
|
|||
if (!preg_match('{\b(?:class|interface' . $extraTypes . ')\s}i', $contents)) { |
|||
return []; |
|||
} |
|||
|
|||
// strip heredocs/nowdocs |
|||
$contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents); |
|||
// strip strings |
|||
$contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); |
|||
// strip leading non-php code if needed |
|||
if (substr($contents, 0, 2) !== '<?') { |
|||
$contents = preg_replace('{^.+?<\?}s', '<?', $contents, 1, $replacements); |
|||
if ($replacements === 0) { |
|||
return []; |
|||
} |
|||
} |
|||
// strip non-php blocks in the file |
|||
$contents = preg_replace('{\?>.+<\?}s', '?><?', $contents); |
|||
// strip trailing non-php code if needed |
|||
$pos = strrpos($contents, '?>'); |
|||
if (false !== $pos && false === strpos(substr($contents, $pos), '<?')) { |
|||
$contents = substr($contents, 0, $pos); |
|||
} |
|||
|
|||
preg_match_all('{ |
|||
(?: |
|||
\b(?<![\$:>])(?P<type>class|interface' . $extraTypes . ') \s++ (?P<name>[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+) |
|||
| \b(?<![\$:>])(?P<ns>namespace) (?P<nsname>\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] |
|||
) |
|||
}ix', $contents, $matches); |
|||
|
|||
$classes = []; |
|||
$namespace = ''; |
|||
|
|||
for ($i = 0, $len = count($matches['type']); $i < $len; $i++) { |
|||
if (!empty($matches['ns'][$i])) { |
|||
$namespace = str_replace([' ', "\t", "\r", "\n"], '', $matches['nsname'][$i]) . '\\'; |
|||
} else { |
|||
$name = $matches['name'][$i]; |
|||
if ($name[0] === ':') { |
|||
$name = 'xhp' . substr(str_replace(['-', ':'], ['_', '__'], $name), 1); |
|||
} elseif ($matches['type'][$i] === 'enum') { |
|||
$name = rtrim($name, ':'); |
|||
} |
|||
$classes[] = ltrim($namespace . $name, '\\'); |
|||
} |
|||
} |
|||
|
|||
return $classes; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
namespace think\console\command\optimize; |
|||
|
|||
use think\Config as ThinkConfig; |
|||
use think\console\Command; |
|||
use think\console\Input; |
|||
use think\console\input\Argument; |
|||
use think\console\Output; |
|||
|
|||
class Config extends Command |
|||
{ |
|||
/** @var Output */ |
|||
protected $output; |
|||
|
|||
protected function configure() |
|||
{ |
|||
$this->setName('optimize:config') |
|||
->addArgument('module', Argument::OPTIONAL, 'Build module config cache .') |
|||
->setDescription('Build config and common file cache.'); |
|||
} |
|||
|
|||
protected function execute(Input $input, Output $output) |
|||
{ |
|||
if ($input->hasArgument('module')) { |
|||
$module = $input->getArgument('module') . DS; |
|||
} else { |
|||
$module = ''; |
|||
} |
|||
|
|||
$content = '<?php ' . PHP_EOL . $this->buildCacheContent($module); |
|||
|
|||
if (!is_dir(RUNTIME_PATH . $module)) { |
|||
@mkdir(RUNTIME_PATH . $module, 0755, true); |
|||
} |
|||
|
|||
file_put_contents(RUNTIME_PATH . $module . 'init' . EXT, $content); |
|||
|
|||
$output->writeln('<info>Succeed!</info>'); |
|||
} |
|||
|
|||
protected function buildCacheContent($module) |
|||
{ |
|||
$content = ''; |
|||
$path = realpath(APP_PATH . $module) . DS; |
|||
|
|||
if ($module) { |
|||
// 加载模块配置 |
|||
$config = ThinkConfig::load(CONF_PATH . $module . 'config' . CONF_EXT); |
|||
|
|||
// 读取数据库配置文件 |
|||
$filename = CONF_PATH . $module . 'database' . CONF_EXT; |
|||
ThinkConfig::load($filename, 'database'); |
|||
|
|||
// 加载应用状态配置 |
|||
if ($config['app_status']) { |
|||
$config = ThinkConfig::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT); |
|||
} |
|||
// 读取扩展配置文件 |
|||
if (is_dir(CONF_PATH . $module . 'extra')) { |
|||
$dir = CONF_PATH . $module . 'extra'; |
|||
$files = scandir($dir); |
|||
foreach ($files as $file) { |
|||
if (strpos($file, CONF_EXT)) { |
|||
$filename = $dir . DS . $file; |
|||
ThinkConfig::load($filename, pathinfo($file, PATHINFO_FILENAME)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 加载行为扩展文件 |
|||
if (is_file(CONF_PATH . $module . 'tags' . EXT)) { |
|||
$content .= '\think\Hook::import(' . (var_export(include CONF_PATH . $module . 'tags' . EXT, true)) . ');' . PHP_EOL; |
|||
} |
|||
|
|||
// 加载公共文件 |
|||
if (is_file($path . 'common' . EXT)) { |
|||
$content .= substr(php_strip_whitespace($path . 'common' . EXT), 5) . PHP_EOL; |
|||
} |
|||
|
|||
$content .= '\think\Config::set(' . var_export(ThinkConfig::get(), true) . ');'; |
|||
return $content; |
|||
} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: liu21st <liu21st@gmail.com> |
|||
// +---------------------------------------------------------------------- |
|||
namespace think\console\command\optimize; |
|||
|
|||
use think\console\Command; |
|||
use think\console\Input; |
|||
use think\console\Output; |
|||
|
|||
class Route extends Command |
|||
{ |
|||
/** @var Output */ |
|||
protected $output; |
|||
|
|||
protected function configure() |
|||
{ |
|||
$this->setName('optimize:route') |
|||
->setDescription('Build route cache.'); |
|||
} |
|||
|
|||
protected function execute(Input $input, Output $output) |
|||
{ |
|||
file_put_contents(RUNTIME_PATH . 'route.php', $this->buildRouteCache()); |
|||
$output->writeln('<info>Succeed!</info>'); |
|||
} |
|||
|
|||
protected function buildRouteCache() |
|||
{ |
|||
$files = \think\Config::get('route_config_file'); |
|||
foreach ($files as $file) { |
|||
if (is_file(CONF_PATH . $file . CONF_EXT)) { |
|||
$config = include CONF_PATH . $file . CONF_EXT; |
|||
if (is_array($config)) { |
|||
\think\Route::import($config); |
|||
} |
|||
} |
|||
} |
|||
$rules = \think\Route::rules(true); |
|||
array_walk_recursive($rules, [$this, 'buildClosure']); |
|||
$content = '<?php ' . PHP_EOL . 'return '; |
|||
$content .= var_export($rules, true) . ';'; |
|||
$content = str_replace(['\'[__start__', '__end__]\''], '', stripcslashes($content)); |
|||
return $content; |
|||
} |
|||
|
|||
protected function buildClosure(&$value) |
|||
{ |
|||
if ($value instanceof \Closure) { |
|||
$reflection = new \ReflectionFunction($value); |
|||
$startLine = $reflection->getStartLine(); |
|||
$endLine = $reflection->getEndLine(); |
|||
$file = $reflection->getFileName(); |
|||
$item = file($file); |
|||
$content = ''; |
|||
for ($i = $startLine - 1; $i <= $endLine - 1; $i++) { |
|||
$content .= $item[$i]; |
|||
} |
|||
$start = strpos($content, 'function'); |
|||
$end = strrpos($content, '}'); |
|||
$value = '[__start__' . substr($content, $start, $end - $start + 1) . '__end__]'; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,111 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
namespace think\console\command\optimize; |
|||
|
|||
use think\App; |
|||
use think\console\Command; |
|||
use think\console\Input; |
|||
use think\console\input\Option; |
|||
use think\console\Output; |
|||
use think\Db; |
|||
|
|||
class Schema extends Command |
|||
{ |
|||
/** @var Output */ |
|||
protected $output; |
|||
|
|||
protected function configure() |
|||
{ |
|||
$this->setName('optimize:schema') |
|||
->addOption('db', null, Option::VALUE_REQUIRED, 'db name .') |
|||
->addOption('table', null, Option::VALUE_REQUIRED, 'table name .') |
|||
->addOption('module', null, Option::VALUE_REQUIRED, 'module name .') |
|||
->setDescription('Build database schema cache.'); |
|||
} |
|||
|
|||
protected function execute(Input $input, Output $output) |
|||
{ |
|||
if (!is_dir(RUNTIME_PATH . 'schema')) { |
|||
@mkdir(RUNTIME_PATH . 'schema', 0755, true); |
|||
} |
|||
if ($input->hasOption('module')) { |
|||
$module = $input->getOption('module'); |
|||
// 读取模型 |
|||
$list = scandir(APP_PATH . $module . DS . 'model'); |
|||
$app = App::$namespace; |
|||
foreach ($list as $file) { |
|||
if ('.' == $file || '..' == $file) { |
|||
continue; |
|||
} |
|||
$class = '\\' . $app . '\\' . $module . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); |
|||
$this->buildModelSchema($class); |
|||
} |
|||
$output->writeln('<info>Succeed!</info>'); |
|||
return; |
|||
} elseif ($input->hasOption('table')) { |
|||
$table = $input->getOption('table'); |
|||
if (!strpos($table, '.')) { |
|||
$dbName = Db::getConfig('database'); |
|||
} |
|||
$tables[] = $table; |
|||
} elseif ($input->hasOption('db')) { |
|||
$dbName = $input->getOption('db'); |
|||
$tables = Db::getTables($dbName); |
|||
} elseif (!\think\Config::get('app_multi_module')) { |
|||
$app = App::$namespace; |
|||
$list = scandir(APP_PATH . 'model'); |
|||
foreach ($list as $file) { |
|||
if ('.' == $file || '..' == $file) { |
|||
continue; |
|||
} |
|||
$class = '\\' . $app . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); |
|||
$this->buildModelSchema($class); |
|||
} |
|||
$output->writeln('<info>Succeed!</info>'); |
|||
return; |
|||
} else { |
|||
$tables = Db::getTables(); |
|||
} |
|||
|
|||
$db = isset($dbName) ? $dbName . '.' : ''; |
|||
$this->buildDataBaseSchema($tables, $db); |
|||
|
|||
$output->writeln('<info>Succeed!</info>'); |
|||
} |
|||
|
|||
protected function buildModelSchema($class) |
|||
{ |
|||
$reflect = new \ReflectionClass($class); |
|||
if (!$reflect->isAbstract() && $reflect->isSubclassOf('\think\Model')) { |
|||
$table = $class::getTable(); |
|||
$dbName = $class::getConfig('database'); |
|||
$content = '<?php ' . PHP_EOL . 'return '; |
|||
$info = $class::getConnection()->getFields($table); |
|||
$content .= var_export($info, true) . ';'; |
|||
file_put_contents(RUNTIME_PATH . 'schema' . DS . $dbName . '.' . $table . EXT, $content); |
|||
} |
|||
} |
|||
|
|||
protected function buildDataBaseSchema($tables, $db) |
|||
{ |
|||
if ('' == $db) { |
|||
$dbName = Db::getConfig('database') . '.'; |
|||
} else { |
|||
$dbName = $db; |
|||
} |
|||
foreach ($tables as $table) { |
|||
$content = '<?php ' . PHP_EOL . 'return '; |
|||
$info = Db::getFields($db . $table); |
|||
$content .= var_export($info, true) . ';'; |
|||
file_put_contents(RUNTIME_PATH . 'schema' . DS . $dbName . $table . EXT, $content); |
|||
} |
|||
} |
|||
} |
|||
@ -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,340 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\console\output; |
|||
|
|||
use think\console\Input; |
|||
use think\console\Output; |
|||
use think\console\output\question\Choice; |
|||
use think\console\output\question\Confirmation; |
|||
|
|||
class Ask |
|||
{ |
|||
private static $stty; |
|||
|
|||
private static $shell; |
|||
|
|||
/** @var Input */ |
|||
protected $input; |
|||
|
|||
/** @var Output */ |
|||
protected $output; |
|||
|
|||
/** @var Question */ |
|||
protected $question; |
|||
|
|||
public function __construct(Input $input, Output $output, Question $question) |
|||
{ |
|||
$this->input = $input; |
|||
$this->output = $output; |
|||
$this->question = $question; |
|||
} |
|||
|
|||
public function run() |
|||
{ |
|||
if (!$this->input->isInteractive()) { |
|||
return $this->question->getDefault(); |
|||
} |
|||
|
|||
if (!$this->question->getValidator()) { |
|||
return $this->doAsk(); |
|||
} |
|||
|
|||
$that = $this; |
|||
|
|||
$interviewer = function () use ($that) { |
|||
return $that->doAsk(); |
|||
}; |
|||
|
|||
return $this->validateAttempts($interviewer); |
|||
} |
|||
|
|||
protected function doAsk() |
|||
{ |
|||
$this->writePrompt(); |
|||
|
|||
$inputStream = STDIN; |
|||
$autocomplete = $this->question->getAutocompleterValues(); |
|||
|
|||
if (null === $autocomplete || !$this->hasSttyAvailable()) { |
|||
$ret = false; |
|||
if ($this->question->isHidden()) { |
|||
try { |
|||
$ret = trim($this->getHiddenResponse($inputStream)); |
|||
} catch (\RuntimeException $e) { |
|||
if (!$this->question->isHiddenFallback()) { |
|||
throw $e; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (false === $ret) { |
|||
$ret = fgets($inputStream, 4096); |
|||
if (false === $ret) { |
|||
throw new \RuntimeException('Aborted'); |
|||
} |
|||
$ret = trim($ret); |
|||
} |
|||
} else { |
|||
$ret = trim($this->autocomplete($inputStream)); |
|||
} |
|||
|
|||
$ret = strlen($ret) > 0 ? $ret : $this->question->getDefault(); |
|||
|
|||
if ($normalizer = $this->question->getNormalizer()) { |
|||
return $normalizer($ret); |
|||
} |
|||
|
|||
return $ret; |
|||
} |
|||
|
|||
private function autocomplete($inputStream) |
|||
{ |
|||
$autocomplete = $this->question->getAutocompleterValues(); |
|||
$ret = ''; |
|||
|
|||
$i = 0; |
|||
$ofs = -1; |
|||
$matches = $autocomplete; |
|||
$numMatches = count($matches); |
|||
|
|||
$sttyMode = shell_exec('stty -g'); |
|||
|
|||
shell_exec('stty -icanon -echo'); |
|||
|
|||
while (!feof($inputStream)) { |
|||
$c = fread($inputStream, 1); |
|||
|
|||
if ("\177" === $c) { |
|||
if (0 === $numMatches && 0 !== $i) { |
|||
--$i; |
|||
$this->output->write("\033[1D"); |
|||
} |
|||
|
|||
if ($i === 0) { |
|||
$ofs = -1; |
|||
$matches = $autocomplete; |
|||
$numMatches = count($matches); |
|||
} else { |
|||
$numMatches = 0; |
|||
} |
|||
|
|||
$ret = substr($ret, 0, $i); |
|||
} elseif ("\033" === $c) { |
|||
$c .= fread($inputStream, 2); |
|||
|
|||
if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { |
|||
if ('A' === $c[2] && -1 === $ofs) { |
|||
$ofs = 0; |
|||
} |
|||
|
|||
if (0 === $numMatches) { |
|||
continue; |
|||
} |
|||
|
|||
$ofs += ('A' === $c[2]) ? -1 : 1; |
|||
$ofs = ($numMatches + $ofs) % $numMatches; |
|||
} |
|||
} elseif (ord($c) < 32) { |
|||
if ("\t" === $c || "\n" === $c) { |
|||
if ($numMatches > 0 && -1 !== $ofs) { |
|||
$ret = $matches[$ofs]; |
|||
$this->output->write(substr($ret, $i)); |
|||
$i = strlen($ret); |
|||
} |
|||
|
|||
if ("\n" === $c) { |
|||
$this->output->write($c); |
|||
break; |
|||
} |
|||
|
|||
$numMatches = 0; |
|||
} |
|||
|
|||
continue; |
|||
} else { |
|||
$this->output->write($c); |
|||
$ret .= $c; |
|||
++$i; |
|||
|
|||
$numMatches = 0; |
|||
$ofs = 0; |
|||
|
|||
foreach ($autocomplete as $value) { |
|||
if (0 === strpos($value, $ret) && $i !== strlen($value)) { |
|||
$matches[$numMatches++] = $value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
$this->output->write("\033[K"); |
|||
|
|||
if ($numMatches > 0 && -1 !== $ofs) { |
|||
$this->output->write("\0337"); |
|||
$this->output->highlight(substr($matches[$ofs], $i)); |
|||
$this->output->write("\0338"); |
|||
} |
|||
} |
|||
|
|||
shell_exec(sprintf('stty %s', $sttyMode)); |
|||
|
|||
return $ret; |
|||
} |
|||
|
|||
protected function getHiddenResponse($inputStream) |
|||
{ |
|||
if ('\\' === DIRECTORY_SEPARATOR) { |
|||
$exe = __DIR__ . '/../bin/hiddeninput.exe'; |
|||
|
|||
$value = rtrim(shell_exec($exe)); |
|||
$this->output->writeln(''); |
|||
|
|||
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); |
|||
$this->output->writeln(''); |
|||
|
|||
return $value; |
|||
} |
|||
|
|||
if (false !== $shell = $this->getShell()) { |
|||
$readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword'; |
|||
$command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); |
|||
$value = rtrim(shell_exec($command)); |
|||
$this->output->writeln(''); |
|||
|
|||
return $value; |
|||
} |
|||
|
|||
throw new \RuntimeException('Unable to hide the response.'); |
|||
} |
|||
|
|||
protected function validateAttempts($interviewer) |
|||
{ |
|||
/** @var \Exception $error */ |
|||
$error = null; |
|||
$attempts = $this->question->getMaxAttempts(); |
|||
while (null === $attempts || $attempts--) { |
|||
if (null !== $error) { |
|||
$this->output->error($error->getMessage()); |
|||
} |
|||
|
|||
try { |
|||
return call_user_func($this->question->getValidator(), $interviewer()); |
|||
} catch (\Exception $error) { |
|||
} |
|||
} |
|||
|
|||
throw $error; |
|||
} |
|||
|
|||
/** |
|||
* 显示问题的提示信息 |
|||
*/ |
|||
protected function writePrompt() |
|||
{ |
|||
$text = $this->question->getQuestion(); |
|||
$default = $this->question->getDefault(); |
|||
|
|||
switch (true) { |
|||
case null === $default: |
|||
$text = sprintf(' <info>%s</info>:', $text); |
|||
|
|||
break; |
|||
|
|||
case $this->question instanceof Confirmation: |
|||
$text = sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $text, $default ? 'yes' : 'no'); |
|||
|
|||
break; |
|||
|
|||
case $this->question instanceof Choice && $this->question->isMultiselect(): |
|||
$choices = $this->question->getChoices(); |
|||
$default = explode(',', $default); |
|||
|
|||
foreach ($default as $key => $value) { |
|||
$default[$key] = $choices[trim($value)]; |
|||
} |
|||
|
|||
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, implode(', ', $default)); |
|||
|
|||
break; |
|||
|
|||
case $this->question instanceof Choice: |
|||
$choices = $this->question->getChoices(); |
|||
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, $choices[$default]); |
|||
|
|||
break; |
|||
|
|||
default: |
|||
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, $default); |
|||
} |
|||
|
|||
$this->output->writeln($text); |
|||
|
|||
if ($this->question instanceof Choice) { |
|||
$width = max(array_map('strlen', array_keys($this->question->getChoices()))); |
|||
|
|||
foreach ($this->question->getChoices() as $key => $value) { |
|||
$this->output->writeln(sprintf(" [<comment>%-${width}s</comment>] %s", $key, $value)); |
|||
} |
|||
} |
|||
|
|||
$this->output->write(' > '); |
|||
} |
|||
|
|||
private function getShell() |
|||
{ |
|||
if (null !== self::$shell) { |
|||
return self::$shell; |
|||
} |
|||
|
|||
self::$shell = false; |
|||
|
|||
if (file_exists('/usr/bin/env')) { |
|||
$test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; |
|||
foreach (['bash', 'zsh', 'ksh', 'csh'] as $sh) { |
|||
if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { |
|||
self::$shell = $sh; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return self::$shell; |
|||
} |
|||
|
|||
private function hasSttyAvailable() |
|||
{ |
|||
if (null !== self::$stty) { |
|||
return self::$stty; |
|||
} |
|||
|
|||
exec('stty 2>&1', $output, $exitcode); |
|||
|
|||
return self::$stty = $exitcode === 0; |
|||
} |
|||
} |
|||
@ -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\output; |
|||
|
|||
use think\Console; |
|||
use think\console\Command; |
|||
use think\console\input\Argument as InputArgument; |
|||
use think\console\input\Definition as InputDefinition; |
|||
use think\console\input\Option as InputOption; |
|||
use think\console\Output; |
|||
use think\console\output\descriptor\Console as ConsoleDescription; |
|||
|
|||
class Descriptor |
|||
{ |
|||
|
|||
/** |
|||
* @var Output |
|||
*/ |
|||
protected $output; |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function describe(Output $output, $object, array $options = []) |
|||
{ |
|||
$this->output = $output; |
|||
|
|||
switch (true) { |
|||
case $object instanceof InputArgument: |
|||
$this->describeInputArgument($object, $options); |
|||
break; |
|||
case $object instanceof InputOption: |
|||
$this->describeInputOption($object, $options); |
|||
break; |
|||
case $object instanceof InputDefinition: |
|||
$this->describeInputDefinition($object, $options); |
|||
break; |
|||
case $object instanceof Command: |
|||
$this->describeCommand($object, $options); |
|||
break; |
|||
case $object instanceof Console: |
|||
$this->describeConsole($object, $options); |
|||
break; |
|||
default: |
|||
throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', 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,198 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
namespace think\console\output; |
|||
|
|||
use think\console\output\formatter\Stack as StyleStack; |
|||
use think\console\output\formatter\Style; |
|||
|
|||
class Formatter |
|||
{ |
|||
|
|||
private $decorated = false; |
|||
private $styles = []; |
|||
private $styleStack; |
|||
|
|||
/** |
|||
* 转义 |
|||
* @param string $text |
|||
* @return string |
|||
*/ |
|||
public static function escape($text) |
|||
{ |
|||
return preg_replace('/([^\\\\]?)</is', '$1\\<', $text); |
|||
} |
|||
|
|||
/** |
|||
* 初始化命令行输出格式 |
|||
*/ |
|||
public function __construct() |
|||
{ |
|||
$this->setStyle('error', new Style('white', 'red')); |
|||
$this->setStyle('info', new Style('green')); |
|||
$this->setStyle('comment', new Style('yellow')); |
|||
$this->setStyle('question', new Style('black', 'cyan')); |
|||
$this->setStyle('highlight', new Style('red')); |
|||
$this->setStyle('warning', new Style('black', 'yellow')); |
|||
|
|||
$this->styleStack = new StyleStack(); |
|||
} |
|||
|
|||
/** |
|||
* 设置外观标识 |
|||
* @param bool $decorated 是否美化文字 |
|||
*/ |
|||
public function setDecorated($decorated) |
|||
{ |
|||
$this->decorated = (bool) $decorated; |
|||
} |
|||
|
|||
/** |
|||
* 获取外观标识 |
|||
* @return bool |
|||
*/ |
|||
public function isDecorated() |
|||
{ |
|||
return $this->decorated; |
|||
} |
|||
|
|||
/** |
|||
* 添加一个新样式 |
|||
* @param string $name 样式名 |
|||
* @param Style $style 样式实例 |
|||
*/ |
|||
public function setStyle($name, Style $style) |
|||
{ |
|||
$this->styles[strtolower($name)] = $style; |
|||
} |
|||
|
|||
/** |
|||
* 是否有这个样式 |
|||
* @param string $name |
|||
* @return bool |
|||
*/ |
|||
public function hasStyle($name) |
|||
{ |
|||
return isset($this->styles[strtolower($name)]); |
|||
} |
|||
|
|||
/** |
|||
* 获取样式 |
|||
* @param string $name |
|||
* @return Style |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
public function getStyle($name) |
|||
{ |
|||
if (!$this->hasStyle($name)) { |
|||
throw new \InvalidArgumentException(sprintf('Undefined style: %s', $name)); |
|||
} |
|||
|
|||
return $this->styles[strtolower($name)]; |
|||
} |
|||
|
|||
/** |
|||
* 使用所给的样式格式化文字 |
|||
* @param string $message 文字 |
|||
* @return string |
|||
*/ |
|||
public function format($message) |
|||
{ |
|||
$offset = 0; |
|||
$output = ''; |
|||
$tagRegex = '[a-z][a-z0-9_=;-]*'; |
|||
preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#isx", $message, $matches, PREG_OFFSET_CAPTURE); |
|||
foreach ($matches[0] as $i => $match) { |
|||
$pos = $match[1]; |
|||
$text = $match[0]; |
|||
|
|||
if (0 != $pos && '\\' == $message[$pos - 1]) { |
|||
continue; |
|||
} |
|||
|
|||
$output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset)); |
|||
$offset = $pos + strlen($text); |
|||
|
|||
if ($open = '/' != $text[1]) { |
|||
$tag = $matches[1][$i][0]; |
|||
} else { |
|||
$tag = 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,211 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\console\output; |
|||
|
|||
class Question |
|||
{ |
|||
|
|||
private $question; |
|||
private $attempts; |
|||
private $hidden = false; |
|||
private $hiddenFallback = true; |
|||
private $autocompleterValues; |
|||
private $validator; |
|||
private $default; |
|||
private $normalizer; |
|||
|
|||
/** |
|||
* 构造方法 |
|||
* @param string $question 问题 |
|||
* @param mixed $default 默认答案 |
|||
*/ |
|||
public function __construct($question, $default = null) |
|||
{ |
|||
$this->question = $question; |
|||
$this->default = $default; |
|||
} |
|||
|
|||
/** |
|||
* 获取问题 |
|||
* @return string |
|||
*/ |
|||
public function getQuestion() |
|||
{ |
|||
return $this->question; |
|||
} |
|||
|
|||
/** |
|||
* 获取默认答案 |
|||
* @return mixed |
|||
*/ |
|||
public function getDefault() |
|||
{ |
|||
return $this->default; |
|||
} |
|||
|
|||
/** |
|||
* 是否隐藏答案 |
|||
* @return bool |
|||
*/ |
|||
public function isHidden() |
|||
{ |
|||
return $this->hidden; |
|||
} |
|||
|
|||
/** |
|||
* 隐藏答案 |
|||
* @param bool $hidden |
|||
* @return Question |
|||
*/ |
|||
public function setHidden($hidden) |
|||
{ |
|||
if ($this->autocompleterValues) { |
|||
throw new \LogicException('A hidden question cannot use the autocompleter.'); |
|||
} |
|||
|
|||
$this->hidden = (bool) $hidden; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 不能被隐藏是否撤销 |
|||
* @return bool |
|||
*/ |
|||
public function isHiddenFallback() |
|||
{ |
|||
return $this->hiddenFallback; |
|||
} |
|||
|
|||
/** |
|||
* 设置不能被隐藏的时候的操作 |
|||
* @param bool $fallback |
|||
* @return Question |
|||
*/ |
|||
public function setHiddenFallback($fallback) |
|||
{ |
|||
$this->hiddenFallback = (bool) $fallback; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 获取自动完成 |
|||
* @return null|array|\Traversable |
|||
*/ |
|||
public function getAutocompleterValues() |
|||
{ |
|||
return $this->autocompleterValues; |
|||
} |
|||
|
|||
/** |
|||
* 设置自动完成的值 |
|||
* @param null|array|\Traversable $values |
|||
* @return Question |
|||
* @throws \InvalidArgumentException |
|||
* @throws \LogicException |
|||
*/ |
|||
public function setAutocompleterValues($values) |
|||
{ |
|||
if (is_array($values) && $this->isAssoc($values)) { |
|||
$values = array_merge(array_keys($values), array_values($values)); |
|||
} |
|||
|
|||
if (null !== $values && !is_array($values)) { |
|||
if (!$values instanceof \Traversable || $values instanceof \Countable) { |
|||
throw new \InvalidArgumentException('Autocompleter values can be either an array, `null` or an object implementing both `Countable` and `Traversable` interfaces.'); |
|||
} |
|||
} |
|||
|
|||
if ($this->hidden) { |
|||
throw new \LogicException('A hidden question cannot use the autocompleter.'); |
|||
} |
|||
|
|||
$this->autocompleterValues = $values; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 设置答案的验证器 |
|||
* @param null|callable $validator |
|||
* @return Question The current instance |
|||
*/ |
|||
public function setValidator($validator) |
|||
{ |
|||
$this->validator = $validator; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 获取验证器 |
|||
* @return null|callable |
|||
*/ |
|||
public function getValidator() |
|||
{ |
|||
return $this->validator; |
|||
} |
|||
|
|||
/** |
|||
* 设置最大重试次数 |
|||
* @param null|int $attempts |
|||
* @return Question |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
public function setMaxAttempts($attempts) |
|||
{ |
|||
if (null !== $attempts && $attempts < 1) { |
|||
throw new \InvalidArgumentException('Maximum number of attempts must be a positive value.'); |
|||
} |
|||
|
|||
$this->attempts = $attempts; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 获取最大重试次数 |
|||
* @return null|int |
|||
*/ |
|||
public function getMaxAttempts() |
|||
{ |
|||
return $this->attempts; |
|||
} |
|||
|
|||
/** |
|||
* 设置响应的回调 |
|||
* @param string|\Closure $normalizer |
|||
* @return Question |
|||
*/ |
|||
public function setNormalizer($normalizer) |
|||
{ |
|||
$this->normalizer = $normalizer; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 获取响应回调 |
|||
* The normalizer can ba a callable (a string), a closure or a class implementing __invoke. |
|||
* @return string|\Closure |
|||
*/ |
|||
public function getNormalizer() |
|||
{ |
|||
return $this->normalizer; |
|||
} |
|||
|
|||
protected function isAssoc($array) |
|||
{ |
|||
return (bool) count(array_filter(array_keys($array), 'is_string')); |
|||
} |
|||
} |
|||
@ -0,0 +1,149 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\console\output\descriptor; |
|||
|
|||
use think\Console as ThinkConsole; |
|||
use think\console\Command; |
|||
|
|||
class Console |
|||
{ |
|||
|
|||
const GLOBAL_NAMESPACE = '_global'; |
|||
|
|||
/** |
|||
* @var ThinkConsole |
|||
*/ |
|||
private $console; |
|||
|
|||
/** |
|||
* @var null|string |
|||
*/ |
|||
private $namespace; |
|||
|
|||
/** |
|||
* @var array |
|||
*/ |
|||
private $namespaces; |
|||
|
|||
/** |
|||
* @var Command[] |
|||
*/ |
|||
private $commands; |
|||
|
|||
/** |
|||
* @var Command[] |
|||
*/ |
|||
private $aliases; |
|||
|
|||
/** |
|||
* 构造方法 |
|||
* @param ThinkConsole $console |
|||
* @param string|null $namespace |
|||
*/ |
|||
public function __construct(ThinkConsole $console, $namespace = null) |
|||
{ |
|||
$this->console = $console; |
|||
$this->namespace = $namespace; |
|||
} |
|||
|
|||
/** |
|||
* @return array |
|||
*/ |
|||
public function getNamespaces() |
|||
{ |
|||
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 = self::GLOBAL_NAMESPACE; |
|||
} |
|||
|
|||
$namespacedCommands[$key][$name] = $command; |
|||
} |
|||
ksort($namespacedCommands); |
|||
|
|||
foreach ($namespacedCommands as &$commandsSet) { |
|||
ksort($commandsSet); |
|||
} |
|||
// unset reference to keep scope clear |
|||
unset($commandsSet); |
|||
|
|||
return $namespacedCommands; |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\console\output\driver; |
|||
|
|||
use think\console\Output; |
|||
|
|||
class Buffer |
|||
{ |
|||
/** |
|||
* @var string |
|||
*/ |
|||
private $buffer = ''; |
|||
|
|||
public function __construct(Output $output) |
|||
{ |
|||
// do nothing |
|||
} |
|||
|
|||
public function fetch() |
|||
{ |
|||
$content = $this->buffer; |
|||
$this->buffer = ''; |
|||
return $content; |
|||
} |
|||
|
|||
public function write($messages, $newline = false, $options = Output::OUTPUT_NORMAL) |
|||
{ |
|||
$messages = (array) $messages; |
|||
|
|||
foreach ($messages as $message) { |
|||
$this->buffer .= $message; |
|||
} |
|||
if ($newline) { |
|||
$this->buffer .= "\n"; |
|||
} |
|||
} |
|||
|
|||
public function renderException(\Exception $e) |
|||
{ |
|||
// do nothing |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,368 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\console\output\driver; |
|||
|
|||
use think\console\Output; |
|||
use think\console\output\Formatter; |
|||
|
|||
class Console |
|||
{ |
|||
|
|||
/** @var Resource */ |
|||
private $stdout; |
|||
|
|||
/** @var Formatter */ |
|||
private $formatter; |
|||
|
|||
private $terminalDimensions; |
|||
|
|||
/** @var Output */ |
|||
private $output; |
|||
|
|||
public function __construct(Output $output) |
|||
{ |
|||
$this->output = $output; |
|||
$this->formatter = new Formatter(); |
|||
$this->stdout = $this->openOutputStream(); |
|||
$decorated = $this->hasColorSupport($this->stdout); |
|||
$this->formatter->setDecorated($decorated); |
|||
} |
|||
|
|||
public function setDecorated($decorated) |
|||
{ |
|||
$this->formatter->setDecorated($decorated); |
|||
} |
|||
|
|||
public function write($messages, $newline = false, $type = Output::OUTPUT_NORMAL, $stream = null) |
|||
{ |
|||
if (Output::VERBOSITY_QUIET === $this->output->getVerbosity()) { |
|||
return; |
|||
} |
|||
|
|||
$messages = (array) $messages; |
|||
|
|||
foreach ($messages as $message) { |
|||
switch ($type) { |
|||
case Output::OUTPUT_NORMAL: |
|||
$message = $this->formatter->format($message); |
|||
break; |
|||
case Output::OUTPUT_RAW: |
|||
break; |
|||
case Output::OUTPUT_PLAIN: |
|||
$message = strip_tags($this->formatter->format($message)); |
|||
break; |
|||
default: |
|||
throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type)); |
|||
} |
|||
|
|||
$this->doWrite($message, $newline, $stream); |
|||
} |
|||
} |
|||
|
|||
public function renderException(\Exception $e) |
|||
{ |
|||
$stderr = $this->openErrorStream(); |
|||
$decorated = $this->hasColorSupport($stderr); |
|||
$this->formatter->setDecorated($decorated); |
|||
|
|||
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; |
|||
} |
|||
$lines = []; |
|||
foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { |
|||
foreach ($this->splitStringByWidth($line, $width - 4) as $line) { |
|||
|
|||
$lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $line)) + 4; |
|||
$lines[] = [$line, $lineLength]; |
|||
|
|||
$len = max($lineLength, $len); |
|||
} |
|||
} |
|||
|
|||
$messages = ['', '']; |
|||
$messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len)); |
|||
$messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title)))); |
|||
foreach ($lines as $line) { |
|||
$messages[] = sprintf('<error> %s %s</error>', $line[0], str_repeat(' ', $len - $line[1])); |
|||
} |
|||
$messages[] = $emptyLine; |
|||
$messages[] = ''; |
|||
$messages[] = ''; |
|||
|
|||
$this->write($messages, true, Output::OUTPUT_NORMAL, $stderr); |
|||
|
|||
if (Output::VERBOSITY_VERBOSE <= $this->output->getVerbosity()) { |
|||
$this->write('<comment>Exception trace:</comment>', true, Output::OUTPUT_NORMAL, $stderr); |
|||
|
|||
// exception related properties |
|||
$trace = $e->getTrace(); |
|||
array_unshift($trace, [ |
|||
'function' => '', |
|||
'file' => $e->getFile() !== null ? $e->getFile() : 'n/a', |
|||
'line' => $e->getLine() !== null ? $e->getLine() : 'n/a', |
|||
'args' => [], |
|||
]); |
|||
|
|||
for ($i = 0, $count = count($trace); $i < $count; ++$i) { |
|||
$class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; |
|||
$type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; |
|||
$function = $trace[$i]['function']; |
|||
$file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; |
|||
$line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; |
|||
|
|||
$this->write(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line), true, Output::OUTPUT_NORMAL, $stderr); |
|||
} |
|||
|
|||
$this->write('', true, Output::OUTPUT_NORMAL, $stderr); |
|||
$this->write('', true, Output::OUTPUT_NORMAL, $stderr); |
|||
} |
|||
} while ($e = $e->getPrevious()); |
|||
|
|||
} |
|||
|
|||
/** |
|||
* 获取终端宽度 |
|||
* @return int|null |
|||
*/ |
|||
protected function getTerminalWidth() |
|||
{ |
|||
$dimensions = $this->getTerminalDimensions(); |
|||
|
|||
return $dimensions[0]; |
|||
} |
|||
|
|||
/** |
|||
* 获取终端高度 |
|||
* @return int|null |
|||
*/ |
|||
protected function getTerminalHeight() |
|||
{ |
|||
$dimensions = $this->getTerminalDimensions(); |
|||
|
|||
return $dimensions[1]; |
|||
} |
|||
|
|||
/** |
|||
* 获取当前终端的尺寸 |
|||
* @return array |
|||
*/ |
|||
public function getTerminalDimensions() |
|||
{ |
|||
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->getMode(), $matches)) { |
|||
return [(int) $matches[1], (int) $matches[2]]; |
|||
} |
|||
} |
|||
|
|||
if ($sttyString = $this->getSttyColumns()) { |
|||
if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { |
|||
return [(int) $matches[2], (int) $matches[1]]; |
|||
} |
|||
if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { |
|||
return [(int) $matches[2], (int) $matches[1]]; |
|||
} |
|||
} |
|||
|
|||
return [null, null]; |
|||
} |
|||
|
|||
/** |
|||
* 获取stty列数 |
|||
* @return string |
|||
*/ |
|||
private function getSttyColumns() |
|||
{ |
|||
if (!function_exists('proc_open')) { |
|||
return; |
|||
} |
|||
|
|||
$descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; |
|||
$process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); |
|||
if (is_resource($process)) { |
|||
$info = stream_get_contents($pipes[1]); |
|||
fclose($pipes[1]); |
|||
fclose($pipes[2]); |
|||
proc_close($process); |
|||
|
|||
return $info; |
|||
} |
|||
return; |
|||
} |
|||
|
|||
/** |
|||
* 获取终端模式 |
|||
* @return string <width>x<height> 或 null |
|||
*/ |
|||
private function getMode() |
|||
{ |
|||
if (!function_exists('proc_open')) { |
|||
return; |
|||
} |
|||
|
|||
$descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; |
|||
$process = proc_open('mode CON', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); |
|||
if (is_resource($process)) { |
|||
$info = stream_get_contents($pipes[1]); |
|||
fclose($pipes[1]); |
|||
fclose($pipes[2]); |
|||
proc_close($process); |
|||
|
|||
if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { |
|||
return $matches[2] . 'x' . $matches[1]; |
|||
} |
|||
} |
|||
return; |
|||
} |
|||
|
|||
private function stringWidth($string) |
|||
{ |
|||
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; |
|||
} |
|||
|
|||
private function isRunningOS400() |
|||
{ |
|||
$checks = [ |
|||
function_exists('php_uname') ? php_uname('s') : '', |
|||
getenv('OSTYPE'), |
|||
PHP_OS, |
|||
]; |
|||
return false !== stripos(implode(';', $checks), 'OS400'); |
|||
} |
|||
|
|||
/** |
|||
* 当前环境是否支持写入控制台输出到stdout. |
|||
* |
|||
* @return bool |
|||
*/ |
|||
protected function hasStdoutSupport() |
|||
{ |
|||
return false === $this->isRunningOS400(); |
|||
} |
|||
|
|||
/** |
|||
* 当前环境是否支持写入控制台输出到stderr. |
|||
* |
|||
* @return bool |
|||
*/ |
|||
protected function hasStderrSupport() |
|||
{ |
|||
return false === $this->isRunningOS400(); |
|||
} |
|||
|
|||
/** |
|||
* @return resource |
|||
*/ |
|||
private function openOutputStream() |
|||
{ |
|||
if (!$this->hasStdoutSupport()) { |
|||
return fopen('php://output', 'w'); |
|||
} |
|||
return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w'); |
|||
} |
|||
|
|||
/** |
|||
* @return resource |
|||
*/ |
|||
private function openErrorStream() |
|||
{ |
|||
return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w'); |
|||
} |
|||
|
|||
/** |
|||
* 将消息写入到输出。 |
|||
* @param string $message 消息 |
|||
* @param bool $newline 是否另起一行 |
|||
* @param null $stream |
|||
*/ |
|||
protected function doWrite($message, $newline, $stream = null) |
|||
{ |
|||
if (null === $stream) { |
|||
$stream = $this->stdout; |
|||
} |
|||
if (false === @fwrite($stream, $message . ($newline ? PHP_EOL : ''))) { |
|||
throw new \RuntimeException('Unable to write output.'); |
|||
} |
|||
|
|||
fflush($stream); |
|||
} |
|||
|
|||
/** |
|||
* 是否支持着色 |
|||
* @param $stream |
|||
* @return bool |
|||
*/ |
|||
protected function hasColorSupport($stream) |
|||
{ |
|||
if (DIRECTORY_SEPARATOR === '\\') { |
|||
return |
|||
'10.0.10586' === PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD |
|||
|| false !== getenv('ANSICON') |
|||
|| 'ON' === getenv('ConEmuANSI') |
|||
|| 'xterm' === getenv('TERM'); |
|||
} |
|||
|
|||
return function_exists('posix_isatty') && @posix_isatty($stream); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\console\output\driver; |
|||
|
|||
use think\console\Output; |
|||
|
|||
class Nothing |
|||
{ |
|||
|
|||
public function __construct(Output $output) |
|||
{ |
|||
// do nothing |
|||
} |
|||
|
|||
public function write($messages, $newline = false, $options = Output::OUTPUT_NORMAL) |
|||
{ |
|||
// do nothing |
|||
} |
|||
|
|||
public function renderException(\Exception $e) |
|||
{ |
|||
// do nothing |
|||
} |
|||
} |
|||
@ -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,163 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\console\output\question; |
|||
|
|||
use think\console\output\Question; |
|||
|
|||
class Choice extends Question |
|||
{ |
|||
|
|||
private $choices; |
|||
private $multiselect = false; |
|||
private $prompt = ' > '; |
|||
private $errorMessage = 'Value "%s" is invalid'; |
|||
|
|||
/** |
|||
* 构造方法 |
|||
* @param string $question 问题 |
|||
* @param array $choices 选项 |
|||
* @param mixed $default 默认答案 |
|||
*/ |
|||
public function __construct($question, array $choices, $default = null) |
|||
{ |
|||
parent::__construct($question, $default); |
|||
|
|||
$this->choices = $choices; |
|||
$this->setValidator($this->getDefaultValidator()); |
|||
$this->setAutocompleterValues($choices); |
|||
} |
|||
|
|||
/** |
|||
* 可选项 |
|||
* @return array |
|||
*/ |
|||
public function getChoices() |
|||
{ |
|||
return $this->choices; |
|||
} |
|||
|
|||
/** |
|||
* 设置可否多选 |
|||
* @param bool $multiselect |
|||
* @return self |
|||
*/ |
|||
public function setMultiselect($multiselect) |
|||
{ |
|||
$this->multiselect = $multiselect; |
|||
$this->setValidator($this->getDefaultValidator()); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
public function isMultiselect() |
|||
{ |
|||
return $this->multiselect; |
|||
} |
|||
|
|||
/** |
|||
* 获取提示 |
|||
* @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,57 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\console\output\question; |
|||
|
|||
use think\console\output\Question; |
|||
|
|||
class Confirmation extends Question |
|||
{ |
|||
|
|||
private $trueAnswerRegex; |
|||
|
|||
/** |
|||
* 构造方法 |
|||
* @param string $question 问题 |
|||
* @param bool $default 默认答案 |
|||
* @param string $trueAnswerRegex 验证正则 |
|||
*/ |
|||
public function __construct($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,99 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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\Request; |
|||
use think\Response; |
|||
|
|||
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 方法名 |
|||
* @return mixed |
|||
* @throws \Exception |
|||
*/ |
|||
public function _empty($method) |
|||
{ |
|||
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 App::invokeMethod([$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,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,829 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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\Exception; |
|||
|
|||
abstract class Builder |
|||
{ |
|||
// connection对象实例 |
|||
protected $connection; |
|||
// 查询对象实例 |
|||
protected $query; |
|||
|
|||
// 数据库表达式 |
|||
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', '>= 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 数据库连接对象实例 |
|||
* @param Query $query 数据库查询对象实例 |
|||
*/ |
|||
public function __construct(Connection $connection, Query $query) |
|||
{ |
|||
$this->connection = $connection; |
|||
$this->query = $query; |
|||
} |
|||
|
|||
/** |
|||
* 获取当前的连接对象实例 |
|||
* @access public |
|||
* @return void |
|||
*/ |
|||
public function getConnection() |
|||
{ |
|||
return $this->connection; |
|||
} |
|||
|
|||
/** |
|||
* 获取当前的Query对象实例 |
|||
* @access public |
|||
* @return void |
|||
*/ |
|||
public function getQuery() |
|||
{ |
|||
return $this->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->getFieldsBind($options); |
|||
if ('*' == $options['field']) { |
|||
$fields = array_keys($bind); |
|||
} else { |
|||
$fields = $options['field']; |
|||
} |
|||
|
|||
$result = []; |
|||
foreach ($data as $key => $val) { |
|||
$item = $this->parseKey($key, $options); |
|||
if (false === strpos($key, '.') && !in_array($key, $fields, true)) { |
|||
if ($options['strict']) { |
|||
throw new Exception('fields not exists:[' . $key . ']'); |
|||
} |
|||
} elseif (isset($val[0]) && 'exp' == $val[0]) { |
|||
$result[$item] = $val[1]; |
|||
} elseif (is_null($val)) { |
|||
$result[$item] = 'NULL'; |
|||
} elseif (is_scalar($val)) { |
|||
// 过滤非标量数据 |
|||
if (0 === strpos($val, ':') && $this->query->isBind(substr($val, 1))) { |
|||
$result[$item] = $val; |
|||
} else { |
|||
$key = str_replace('.', '_', $key); |
|||
$this->query->bind('__data__' . $key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); |
|||
$result[$item] = ':__data__' . $key; |
|||
} |
|||
} elseif (is_object($val) && method_exists($val, '__toString')) { |
|||
// 对象数据写入 |
|||
$result[$item] = $val->__toString(); |
|||
} |
|||
} |
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* 字段名分析 |
|||
* @access protected |
|||
* @param string $key |
|||
* @param array $options |
|||
* @return string |
|||
*/ |
|||
protected function parseKey($key, $options = []) |
|||
{ |
|||
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)) { |
|||
$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 |
|||
* @param array $options |
|||
* @return string |
|||
*/ |
|||
protected function parseField($fields, $options = []) |
|||
{ |
|||
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, $options) . ' AS ' . $this->parseKey($field, $options); |
|||
} else { |
|||
$array[] = $this->parseKey($field, $options); |
|||
} |
|||
} |
|||
$fieldsStr = implode(',', $array); |
|||
} |
|||
return $fieldsStr; |
|||
} |
|||
|
|||
/** |
|||
* table分析 |
|||
* @access protected |
|||
* @param mixed $tables |
|||
* @param array $options |
|||
* @return string |
|||
*/ |
|||
protected function parseTable($tables, $options = []) |
|||
{ |
|||
$item = []; |
|||
foreach ((array) $tables as $key => $table) { |
|||
if (!is_numeric($key)) { |
|||
if (strpos($key, '@think')) { |
|||
$key = strstr($key, '@think', true); |
|||
} |
|||
$key = $this->parseSqlTable($key); |
|||
$item[] = $this->parseKey($key) . ' ' . $this->parseKey($table); |
|||
} else { |
|||
$table = $this->parseSqlTable($table); |
|||
if (isset($options['alias'][$table])) { |
|||
$item[] = $this->parseKey($table) . ' ' . $this->parseKey($options['alias'][$table]); |
|||
} else { |
|||
$item[] = $this->parseKey($table); |
|||
} |
|||
} |
|||
} |
|||
return implode(',', $item); |
|||
} |
|||
|
|||
/** |
|||
* where分析 |
|||
* @access protected |
|||
* @param mixed $where 查询条件 |
|||
* @param array $options 查询参数 |
|||
* @return string |
|||
*/ |
|||
protected function parseWhere($where, $options) |
|||
{ |
|||
$whereStr = $this->buildWhere($where, $options); |
|||
return empty($whereStr) ? '' : ' WHERE ' . $whereStr; |
|||
} |
|||
|
|||
/** |
|||
* 生成查询条件SQL |
|||
* @access public |
|||
* @param mixed $where |
|||
* @param array $options |
|||
* @return string |
|||
*/ |
|||
public function buildWhere($where, $options) |
|||
{ |
|||
if (empty($where)) { |
|||
$where = []; |
|||
} |
|||
|
|||
if ($where instanceof Query) { |
|||
return $this->buildWhere($where->getOptions('where'), $options); |
|||
} |
|||
|
|||
$whereStr = ''; |
|||
$binds = $this->query->getFieldsBind($options); |
|||
foreach ($where as $key => $val) { |
|||
$str = []; |
|||
foreach ($val as $field => $value) { |
|||
if ($value instanceof \Closure) { |
|||
// 使用闭包查询 |
|||
$query = new Query($this->connection); |
|||
call_user_func_array($value, [ & $query]); |
|||
$whereClause = $this->buildWhere($query->getOptions('where'), $options); |
|||
if (!empty($whereClause)) { |
|||
$str[] = ' ' . $key . ' ( ' . $whereClause . ' )'; |
|||
} |
|||
} elseif (strpos($field, '|')) { |
|||
// 不同字段使用相同查询条件(OR) |
|||
$array = explode('|', $field); |
|||
$item = []; |
|||
foreach ($array as $k) { |
|||
$item[] = $this->parseWhereItem($k, $value, '', $options, $binds); |
|||
} |
|||
$str[] = ' ' . $key . ' ( ' . implode(' OR ', $item) . ' )'; |
|||
} elseif (strpos($field, '&')) { |
|||
// 不同字段使用相同查询条件(AND) |
|||
$array = explode('&', $field); |
|||
$item = []; |
|||
foreach ($array as $k) { |
|||
$item[] = $this->parseWhereItem($k, $value, '', $options, $binds); |
|||
} |
|||
$str[] = ' ' . $key . ' ( ' . implode(' AND ', $item) . ' )'; |
|||
} else { |
|||
// 对字段使用表达式查询 |
|||
$field = is_string($field) ? $field : ''; |
|||
$str[] = ' ' . $key . ' ' . $this->parseWhereItem($field, $value, $key, $options, $binds); |
|||
} |
|||
} |
|||
|
|||
$whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($key) + 1) : implode(' ', $str); |
|||
} |
|||
return $whereStr; |
|||
} |
|||
|
|||
// where子单元分析 |
|||
protected function parseWhereItem($field, $val, $rule = '', $options = [], $binds = [], $bindName = null) |
|||
{ |
|||
// 字段分析 |
|||
$key = $field ? $this->parseKey($field, $options) : ''; |
|||
|
|||
// 查询规则和条件 |
|||
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 $k => $item) { |
|||
$bindName = 'where_' . str_replace('.', '_', $field) . '_' . $k; |
|||
$str[] = $this->parseWhereItem($field, $item, $rule, $options, $binds, $bindName); |
|||
} |
|||
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); |
|||
} |
|||
} |
|||
$bindName = $bindName ?: 'where_' . str_replace(['.', '-'], '_', $field); |
|||
if (preg_match('/\W/', $bindName)) { |
|||
// 处理带非单词字符的字段名 |
|||
$bindName = md5($bindName); |
|||
} |
|||
|
|||
$bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR; |
|||
if (is_scalar($value) && array_key_exists($field, $binds) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { |
|||
if (strpos($value, ':') !== 0 || !$this->query->isBind(substr($value, 1))) { |
|||
if ($this->query->isBind($bindName)) { |
|||
$bindName .= '_' . str_replace('.', '_', uniqid('', true)); |
|||
} |
|||
$this->query->bind($bindName, $value, $bindType); |
|||
$value = ':' . $bindName; |
|||
} |
|||
} |
|||
|
|||
$whereStr = ''; |
|||
if (in_array($exp, ['=', '<>', '>', '>=', '<', '<='])) { |
|||
// 比较运算 及 模糊匹配 |
|||
$whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); |
|||
} elseif ('LIKE' == $exp || 'NOT LIKE' == $exp) { |
|||
if (is_array($value)) { |
|||
foreach ($value as $item) { |
|||
$array[] = $key . ' ' . $exp . ' ' . $this->parseValue($item, $field); |
|||
} |
|||
$logic = isset($val[2]) ? $val[2] : 'AND'; |
|||
$whereStr .= '(' . implode($array, ' ' . strtoupper($logic) . ' ') . ')'; |
|||
} else { |
|||
$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); |
|||
if (array_key_exists($field, $binds)) { |
|||
$bind = []; |
|||
$array = []; |
|||
foreach ($value as $k => $v) { |
|||
if ($this->query->isBind($bindName . '_in_' . $k)) { |
|||
$bindKey = $bindName . '_in_' . uniqid() . '_' . $k; |
|||
} else { |
|||
$bindKey = $bindName . '_in_' . $k; |
|||
} |
|||
$bind[$bindKey] = [$v, $bindType]; |
|||
$array[] = ':' . $bindKey; |
|||
} |
|||
$this->query->bind($bind); |
|||
$zone = implode(',', $array); |
|||
} else { |
|||
$zone = implode(',', $this->parseValue($value, $field)); |
|||
} |
|||
$whereStr .= $key . ' ' . $exp . ' (' . (empty($zone) ? "''" : $zone) . ')'; |
|||
} |
|||
} elseif (in_array($exp, ['NOT BETWEEN', 'BETWEEN'])) { |
|||
// BETWEEN 查询 |
|||
$data = is_array($value) ? $value : explode(',', $value); |
|||
if (array_key_exists($field, $binds)) { |
|||
if ($this->query->isBind($bindName . '_between_1')) { |
|||
$bindKey1 = $bindName . '_between_1' . uniqid(); |
|||
$bindKey2 = $bindName . '_between_2' . uniqid(); |
|||
} else { |
|||
$bindKey1 = $bindName . '_between_1'; |
|||
$bindKey2 = $bindName . '_between_2'; |
|||
} |
|||
$bind = [ |
|||
$bindKey1 => [$data[0], $bindType], |
|||
$bindKey2 => [$data[1], $bindType], |
|||
]; |
|||
$this->query->bind($bind); |
|||
$between = ':' . $bindKey1 . ' AND :' . $bindKey2; |
|||
} else { |
|||
$between = $this->parseValue($data[0], $field) . ' AND ' . $this->parseValue($data[1], $field); |
|||
} |
|||
$whereStr .= $key . ' ' . $exp . ' ' . $between; |
|||
} 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', '<= TIME', '>= TIME'])) { |
|||
$whereStr .= $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($value, $field, $options, $bindName, $bindType); |
|||
} 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, $options, $bindName . '_between_1', $bindType) . ' AND ' . $this->parseDateTime($value[1], $field, $options, $bindName . '_between_2', $bindType); |
|||
} |
|||
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 |
|||
* @param array $options |
|||
* @param string $bindName |
|||
* @param integer $bindType |
|||
* @return string |
|||
*/ |
|||
protected function parseDateTime($value, $key, $options = [], $bindName = null, $bindType = null) |
|||
{ |
|||
// 获取时间字段类型 |
|||
if (strpos($key, '.')) { |
|||
list($table, $key) = explode('.', $key); |
|||
if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) { |
|||
$table = $pos; |
|||
} |
|||
} else { |
|||
$table = $options['table']; |
|||
} |
|||
$type = $this->query->getTableInfo($table, 'type'); |
|||
if (isset($type[$key])) { |
|||
$info = $type[$key]; |
|||
} |
|||
if (isset($info)) { |
|||
if (is_string($value)) { |
|||
$value = strtotime($value) ?: $value; |
|||
} |
|||
|
|||
if (preg_match('/(datetime|timestamp)/is', $info)) { |
|||
// 日期及时间戳类型 |
|||
$value = date('Y-m-d H:i:s', $value); |
|||
} elseif (preg_match('/(date)/is', $info)) { |
|||
// 日期及时间戳类型 |
|||
$value = date('Y-m-d', $value); |
|||
} |
|||
} |
|||
$bindName = $bindName ?: $key; |
|||
$this->query->bind($bindName, $value, $bindType); |
|||
return ':' . $bindName; |
|||
} |
|||
|
|||
/** |
|||
* limit分析 |
|||
* @access protected |
|||
* @param mixed $lmit |
|||
* @return string |
|||
*/ |
|||
protected function parseLimit($limit) |
|||
{ |
|||
return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : ''; |
|||
} |
|||
|
|||
/** |
|||
* join分析 |
|||
* @access protected |
|||
* @param array $join |
|||
* @param array $options 查询条件 |
|||
* @return string |
|||
*/ |
|||
protected function parseJoin($join, $options = []) |
|||
{ |
|||
$joinStr = ''; |
|||
if (!empty($join)) { |
|||
foreach ($join as $item) { |
|||
list($table, $type, $on) = $item; |
|||
$condition = []; |
|||
foreach ((array) $on as $val) { |
|||
if (strpos($val, '=')) { |
|||
list($val1, $val2) = explode('=', $val, 2); |
|||
$condition[] = $this->parseKey($val1, $options) . '=' . $this->parseKey($val2, $options); |
|||
} else { |
|||
$condition[] = $val; |
|||
} |
|||
} |
|||
|
|||
$table = $this->parseTable($table, $options); |
|||
$joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . implode(' AND ', $condition); |
|||
} |
|||
} |
|||
return $joinStr; |
|||
} |
|||
|
|||
/** |
|||
* order分析 |
|||
* @access protected |
|||
* @param mixed $order |
|||
* @param array $options 查询条件 |
|||
* @return string |
|||
*/ |
|||
protected function parseOrder($order, $options = []) |
|||
{ |
|||
if (is_array($order)) { |
|||
$array = []; |
|||
foreach ($order as $key => $val) { |
|||
if (is_numeric($key)) { |
|||
if ('[rand]' == $val) { |
|||
$array[] = $this->parseRand(); |
|||
} elseif (false === strpos($val, '(')) { |
|||
$array[] = $this->parseKey($val, $options); |
|||
} else { |
|||
$array[] = $val; |
|||
} |
|||
} else { |
|||
$sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : ''; |
|||
$array[] = $this->parseKey($key, $options) . ' ' . $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'], $options), |
|||
$this->parseDistinct($options['distinct']), |
|||
$this->parseField($options['field'], $options), |
|||
$this->parseJoin($options['join'], $options), |
|||
$this->parseWhere($options['where'], $options), |
|||
$this->parseGroup($options['group']), |
|||
$this->parseHaving($options['having']), |
|||
$this->parseOrder($options['order'], $options), |
|||
$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'], $options), |
|||
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 = array_keys($this->query->getFieldsType($options)); |
|||
} 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_null($val)) { |
|||
$data[$key] = 'NULL'; |
|||
} elseif (is_scalar($val)) { |
|||
$data[$key] = $this->parseValue($val, $key); |
|||
} elseif (is_object($val) && method_exists($val, '__toString')) { |
|||
// 对象数据写入 |
|||
$data[$key] = $val->__toString(); |
|||
} 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'], $options), |
|||
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, $options) . ' (' . 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'], $options); |
|||
$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'], $options), |
|||
implode(',', $set), |
|||
$this->parseJoin($options['join'], $options), |
|||
$this->parseWhere($options['where'], $options), |
|||
$this->parseOrder($options['order'], $options), |
|||
$this->parseLimit($options['limit']), |
|||
$this->parseLock($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'], $options), |
|||
!empty($options['using']) ? ' USING ' . $this->parseTable($options['using'], $options) . ' ' : '', |
|||
$this->parseJoin($options['join'], $options), |
|||
$this->parseWhere($options['where'], $options), |
|||
$this->parseOrder($options['order'], $options), |
|||
$this->parseLimit($options['limit']), |
|||
$this->parseLock($options['lock']), |
|||
$this->parseComment($options['comment']), |
|||
], $this->deleteSql); |
|||
|
|||
return $sql; |
|||
} |
|||
} |
|||
@ -0,0 +1,998 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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\Db; |
|||
use think\db\exception\BindParamException; |
|||
use think\Debug; |
|||
use think\Exception; |
|||
use think\exception\PDOException; |
|||
use think\Log; |
|||
|
|||
/** |
|||
* Class Connection |
|||
* @package think |
|||
* @method Query table(string $table) 指定数据表(含前缀) |
|||
* @method Query name(string $name) 指定数据表(不含前缀) |
|||
* |
|||
*/ |
|||
abstract class Connection |
|||
{ |
|||
|
|||
/** @var PDOStatement PDO操作实例 */ |
|||
protected $PDOStatement; |
|||
|
|||
/** @var string 当前SQL指令 */ |
|||
protected $queryStr = ''; |
|||
// 返回或者影响记录数 |
|||
protected $numRows = 0; |
|||
// 事务指令数 |
|||
protected $transTimes = 0; |
|||
// 错误信息 |
|||
protected $error = ''; |
|||
|
|||
/** @var PDO[] 数据库连接ID 支持多个连接 */ |
|||
protected $links = []; |
|||
|
|||
/** @var PDO 当前连接ID */ |
|||
protected $linkID; |
|||
protected $linkRead; |
|||
protected $linkWrite; |
|||
|
|||
// 查询结果类型 |
|||
protected $fetchType = PDO::FETCH_ASSOC; |
|||
// 字段属性大小写 |
|||
protected $attrCase = PDO::CASE_LOWER; |
|||
// 监听回调 |
|||
protected static $event = []; |
|||
// 查询对象 |
|||
protected $query = []; |
|||
// 使用Builder类 |
|||
protected $builder; |
|||
// 数据库连接参数配置 |
|||
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, |
|||
// 数据返回类型 |
|||
'result_type' => PDO::FETCH_ASSOC, |
|||
// 数据集返回类型 |
|||
'resultset_type' => 'array', |
|||
// 自动写入时间戳字段 |
|||
'auto_timestamp' => false, |
|||
// 时间字段取出后的默认时间格式 |
|||
'datetime_format' => 'Y-m-d H:i:s', |
|||
// 是否需要进行SQL性能分析 |
|||
'sql_explain' => false, |
|||
// Builder类 |
|||
'builder' => '', |
|||
// Query类 |
|||
'query' => '\\think\\db\\Query', |
|||
// 是否需要断线重连 |
|||
'break_reconnect' => false, |
|||
]; |
|||
|
|||
// PDO连接参数 |
|||
protected $params = [ |
|||
PDO::ATTR_CASE => PDO::CASE_NATURAL, |
|||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
|||
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, |
|||
PDO::ATTR_STRINGIFY_FETCHES => false, |
|||
PDO::ATTR_EMULATE_PREPARES => false, |
|||
]; |
|||
|
|||
// 绑定参数 |
|||
protected $bind = []; |
|||
|
|||
/** |
|||
* 构造函数 读取数据库配置信息 |
|||
* @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 模型类名称 |
|||
* @param string $queryClass 查询对象类名 |
|||
* @return Query |
|||
*/ |
|||
public function getQuery($model = 'db', $queryClass = '') |
|||
{ |
|||
if (!isset($this->query[$model])) { |
|||
$class = $queryClass ?: $this->config['query']; |
|||
$this->query[$model] = new $class($this, 'db' == $model ? '' : $model); |
|||
} |
|||
return $this->query[$model]; |
|||
} |
|||
|
|||
/** |
|||
* 获取当前连接器类对应的Builder类 |
|||
* @access public |
|||
* @return string |
|||
*/ |
|||
public function getBuilder() |
|||
{ |
|||
if (!empty($this->builder)) { |
|||
return $this->builder; |
|||
} else { |
|||
return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type')); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 调用Query类的查询方法 |
|||
* @access public |
|||
* @param string $method 方法名称 |
|||
* @param array $args 调用参数 |
|||
* @return mixed |
|||
*/ |
|||
public function __call($method, $args) |
|||
{ |
|||
return call_user_func_array([$this->getQuery(), $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 |
|||
*/ |
|||
public 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|array $config 配置名称 |
|||
* @param mixed $value 配置值 |
|||
* @return void |
|||
*/ |
|||
public function setConfig($config, $value = '') |
|||
{ |
|||
if (is_array($config)) { |
|||
$this->config = array_merge($this->config, $config); |
|||
} else { |
|||
$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 (!$config) { |
|||
$config = $this->config; |
|||
} else { |
|||
$config = array_merge($this->config, $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['result_type'])) { |
|||
$this->fetchType = $config['result_type']; |
|||
} |
|||
try { |
|||
if (empty($config['dsn'])) { |
|||
$config['dsn'] = $this->parseDsn($config); |
|||
} |
|||
if ($config['debug']) { |
|||
$startTime = microtime(true); |
|||
} |
|||
$this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params); |
|||
if ($config['debug']) { |
|||
// 记录数据库连接信息 |
|||
Log::record('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn'], 'sql'); |
|||
} |
|||
} catch (\PDOException $e) { |
|||
if ($autoConnection) { |
|||
Log::record($e->getMessage(), 'error'); |
|||
return $this->connect($autoConnection, $linkNum); |
|||
} else { |
|||
throw $e; |
|||
} |
|||
} |
|||
} |
|||
return $this->links[$linkNum]; |
|||
} |
|||
|
|||
/** |
|||
* 释放查询结果 |
|||
* @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 bool $master 是否在主服务器读操作 |
|||
* @param bool $class 是否返回PDO对象 |
|||
* @param string $sql sql指令 |
|||
* @param array $bind 参数绑定 |
|||
* @param boolean $master 是否在主服务器读操作 |
|||
* @param bool $pdo 是否返回PDO对象 |
|||
* @return mixed |
|||
* @throws BindParamException |
|||
* @throws PDOException |
|||
*/ |
|||
public function query($sql, $bind = [], $master = false, $pdo = false) |
|||
{ |
|||
$this->initConnect($master); |
|||
if (!$this->linkID) { |
|||
return false; |
|||
} |
|||
|
|||
// 记录SQL语句 |
|||
$this->queryStr = $sql; |
|||
if ($bind) { |
|||
$this->bind = $bind; |
|||
} |
|||
|
|||
// 释放前次的查询结果 |
|||
if (!empty($this->PDOStatement)) { |
|||
$this->free(); |
|||
} |
|||
|
|||
Db::$queryTimes++; |
|||
try { |
|||
// 调试开始 |
|||
$this->debug(true); |
|||
// 预处理 |
|||
if (empty($this->PDOStatement)) { |
|||
$this->PDOStatement = $this->linkID->prepare($sql); |
|||
} |
|||
// 是否为存储过程调用 |
|||
$procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); |
|||
// 参数绑定 |
|||
if ($procedure) { |
|||
$this->bindParam($bind); |
|||
} else { |
|||
$this->bindValue($bind); |
|||
} |
|||
// 执行查询 |
|||
$this->PDOStatement->execute(); |
|||
// 调试结束 |
|||
$this->debug(false); |
|||
// 返回结果集 |
|||
return $this->getResult($pdo, $procedure); |
|||
} catch (\PDOException $e) { |
|||
if ($this->config['break_reconnect'] && $this->isBreak($e)) { |
|||
return $this->close()->query($sql, $bind, $master, $pdo); |
|||
} |
|||
throw new PDOException($e, $this->config, $this->getLastsql()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 执行语句 |
|||
* @access public |
|||
* @param string $sql sql指令 |
|||
* @param array $bind 参数绑定 |
|||
* @return int |
|||
* @throws BindParamException |
|||
* @throws PDOException |
|||
*/ |
|||
public function execute($sql, $bind = []) |
|||
{ |
|||
$this->initConnect(true); |
|||
if (!$this->linkID) { |
|||
return false; |
|||
} |
|||
|
|||
// 记录SQL语句 |
|||
$this->queryStr = $sql; |
|||
if ($bind) { |
|||
$this->bind = $bind; |
|||
} |
|||
|
|||
//释放前次的查询结果 |
|||
if (!empty($this->PDOStatement) && $this->PDOStatement->queryString != $sql) { |
|||
$this->free(); |
|||
} |
|||
|
|||
Db::$executeTimes++; |
|||
try { |
|||
// 调试开始 |
|||
$this->debug(true); |
|||
// 预处理 |
|||
if (empty($this->PDOStatement)) { |
|||
$this->PDOStatement = $this->linkID->prepare($sql); |
|||
} |
|||
// 是否为存储过程调用 |
|||
$procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); |
|||
// 参数绑定 |
|||
if ($procedure) { |
|||
$this->bindParam($bind); |
|||
} else { |
|||
$this->bindValue($bind); |
|||
} |
|||
// 执行语句 |
|||
$this->PDOStatement->execute(); |
|||
// 调试结束 |
|||
$this->debug(false); |
|||
|
|||
$this->numRows = $this->PDOStatement->rowCount(); |
|||
return $this->numRows; |
|||
} catch (\PDOException $e) { |
|||
if ($this->config['break_reconnect'] && $this->isBreak($e)) { |
|||
return $this->close()->execute($sql, $bind); |
|||
} |
|||
throw new PDOException($e, $this->config, $this->getLastsql()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 根据参数绑定组装最终的SQL语句 便于调试 |
|||
* @access public |
|||
* @param string $sql 带参数绑定的sql语句 |
|||
* @param array $bind 参数绑定列表 |
|||
* @return string |
|||
*/ |
|||
public function getRealSql($sql, array $bind = []) |
|||
{ |
|||
foreach ($bind as $key => $val) { |
|||
$value = is_array($val) ? $val[0] : $val; |
|||
$type = is_array($val) ? $val[1] : PDO::PARAM_STR; |
|||
if (PDO::PARAM_STR == $type) { |
|||
$value = $this->quote($value); |
|||
} elseif (PDO::PARAM_INT == $type) { |
|||
$value = (float) $value; |
|||
} |
|||
// 判断占位符 |
|||
$sql = is_numeric($key) ? |
|||
substr_replace($sql, $value, strpos($sql, '?'), 1) : |
|||
str_replace( |
|||
[':' . $key . ')', ':' . $key . ',', ':' . $key . ' '], |
|||
[$value . ')', $value . ',', $value . ' '], |
|||
$sql . ' '); |
|||
} |
|||
return rtrim($sql); |
|||
} |
|||
|
|||
/** |
|||
* 参数绑定 |
|||
* 支持 ['name'=>'value','id'=>123] 对应命名占位符 |
|||
* 或者 ['value',123] 对应问号占位符 |
|||
* @access public |
|||
* @param array $bind 要绑定的参数列表 |
|||
* @return void |
|||
* @throws BindParamException |
|||
*/ |
|||
protected function bindValue(array $bind = []) |
|||
{ |
|||
foreach ($bind as $key => $val) { |
|||
// 占位符 |
|||
$param = is_numeric($key) ? $key + 1 : ':' . $key; |
|||
if (is_array($val)) { |
|||
if (PDO::PARAM_INT == $val[1] && '' === $val[0]) { |
|||
$val[0] = 0; |
|||
} |
|||
$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->getLastsql(), |
|||
$bind |
|||
); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 存储过程的输入输出参数绑定 |
|||
* @access public |
|||
* @param array $bind 要绑定的参数列表 |
|||
* @return void |
|||
* @throws BindParamException |
|||
*/ |
|||
protected function bindParam($bind) |
|||
{ |
|||
foreach ($bind as $key => $val) { |
|||
$param = is_numeric($key) ? $key + 1 : ':' . $key; |
|||
if (is_array($val)) { |
|||
array_unshift($val, $param); |
|||
$result = call_user_func_array([$this->PDOStatement, 'bindParam'], $val); |
|||
} else { |
|||
$result = $this->PDOStatement->bindValue($param, $val); |
|||
} |
|||
if (!$result) { |
|||
$param = array_shift($val); |
|||
throw new BindParamException( |
|||
"Error occurred when binding parameters '{$param}'", |
|||
$this->config, |
|||
$this->getLastsql(), |
|||
$bind |
|||
); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获得数据集数组 |
|||
* @access protected |
|||
* @param bool $pdo 是否返回PDOStatement |
|||
* @param bool $procedure 是否存储过程 |
|||
* @return array |
|||
*/ |
|||
protected function getResult($pdo = false, $procedure = false) |
|||
{ |
|||
if ($pdo) { |
|||
// 返回PDOStatement对象处理 |
|||
return $this->PDOStatement; |
|||
} |
|||
if ($procedure) { |
|||
// 存储过程返回结果 |
|||
return $this->procedure(); |
|||
} |
|||
$result = $this->PDOStatement->fetchAll($this->fetchType); |
|||
$this->numRows = count($result); |
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* 获得存储过程数据集 |
|||
* @access protected |
|||
* @return array |
|||
*/ |
|||
protected function procedure() |
|||
{ |
|||
$item = []; |
|||
do { |
|||
$result = $this->getResult(); |
|||
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 void |
|||
*/ |
|||
public function startTrans() |
|||
{ |
|||
$this->initConnect(true); |
|||
if (!$this->linkID) { |
|||
return false; |
|||
} |
|||
|
|||
++$this->transTimes; |
|||
|
|||
if (1 == $this->transTimes) { |
|||
$this->linkID->beginTransaction(); |
|||
} elseif ($this->transTimes > 1 && $this->supportSavepoint()) { |
|||
$this->linkID->exec( |
|||
$this->parseSavepoint('trans' . $this->transTimes) |
|||
); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 用于非自动提交状态下面的查询提交 |
|||
* @access public |
|||
* @return void |
|||
* @throws PDOException |
|||
*/ |
|||
public function commit() |
|||
{ |
|||
$this->initConnect(true); |
|||
|
|||
if (1 == $this->transTimes) { |
|||
$this->linkID->commit(); |
|||
} |
|||
|
|||
--$this->transTimes; |
|||
} |
|||
|
|||
/** |
|||
* 事务回滚 |
|||
* @access public |
|||
* @return void |
|||
* @throws PDOException |
|||
*/ |
|||
public function rollback() |
|||
{ |
|||
$this->initConnect(true); |
|||
|
|||
if (1 == $this->transTimes) { |
|||
$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 (\Exception $e) { |
|||
$this->rollback(); |
|||
throw $e; |
|||
} |
|||
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 |
|||
* @return $this |
|||
*/ |
|||
public function close() |
|||
{ |
|||
$this->linkID = null; |
|||
$this->linkWrite = null; |
|||
$this->linkRead = null; |
|||
$this->links = []; |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 是否断线 |
|||
* @access protected |
|||
* @param \PDOException $e 异常 |
|||
* @return bool |
|||
*/ |
|||
protected function isBreak($e) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 获取最近一次查询的sql语句 |
|||
* @access public |
|||
* @return string |
|||
*/ |
|||
public function getLastSql() |
|||
{ |
|||
return $this->getRealSql($this->queryStr, $this->bind); |
|||
} |
|||
|
|||
/** |
|||
* 获取最近插入的ID |
|||
* @access public |
|||
* @param string $sequence 自增序列名 |
|||
* @return string |
|||
*/ |
|||
public function getLastInsID($sequence = null) |
|||
{ |
|||
return $this->linkID->lastInsertId($sequence); |
|||
} |
|||
|
|||
/** |
|||
* 获取返回或者影响的记录数 |
|||
* @access public |
|||
* @return integer |
|||
*/ |
|||
public function getNumRows() |
|||
{ |
|||
return $this->numRows; |
|||
} |
|||
|
|||
/** |
|||
* 获取最近的错误信息 |
|||
* @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->getLastsql(); |
|||
} |
|||
return $error; |
|||
} |
|||
|
|||
/** |
|||
* SQL指令安全过滤 |
|||
* @access public |
|||
* @param string $str SQL字符串 |
|||
* @param bool $master 是否主库查询 |
|||
* @return string |
|||
*/ |
|||
public function quote($str, $master = true) |
|||
{ |
|||
$this->initConnect($master); |
|||
return $this->linkID ? $this->linkID->quote($str) : $str; |
|||
} |
|||
|
|||
/** |
|||
* 数据库调试 记录当前SQL及分析性能 |
|||
* @access protected |
|||
* @param boolean $start 调试开始标记 true 开始 false 结束 |
|||
* @param string $sql 执行的SQL语句 留空自动获取 |
|||
* @return void |
|||
*/ |
|||
protected function debug($start, $sql = '') |
|||
{ |
|||
if (!empty($this->config['debug'])) { |
|||
// 开启数据库调试模式 |
|||
if ($start) { |
|||
Debug::remark('queryStartTime', 'time'); |
|||
} else { |
|||
// 记录操作结束时间 |
|||
Debug::remark('queryEndTime', 'time'); |
|||
$runtime = Debug::getRangeTime('queryStartTime', 'queryEndTime'); |
|||
$sql = $sql ?: $this->getLastsql(); |
|||
$log = $sql . ' [ RunTime:' . $runtime . 's ]'; |
|||
$result = []; |
|||
// SQL性能分析 |
|||
if ($this->config['sql_explain'] && 0 === stripos(trim($sql), 'select')) { |
|||
$result = $this->getExplain($sql); |
|||
} |
|||
// SQL监听 |
|||
$this->trigger($sql, $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 ] ' . $sql . ' [ 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
@ -0,0 +1,65 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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 |
|||
{ |
|||
protected $updateSql = 'UPDATE %TABLE% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; |
|||
|
|||
/** |
|||
* 字段和表名处理 |
|||
* @access protected |
|||
* @param string $key |
|||
* @param array $options |
|||
* @return string |
|||
*/ |
|||
protected function parseKey($key, $options = []) |
|||
{ |
|||
$key = trim($key); |
|||
if (strpos($key, '$.') && false === strpos($key, '(')) { |
|||
// JSON字段支持 |
|||
list($field, $name) = explode('$.', $key); |
|||
$key = 'json_extract(' . $field . ', \'$.' . $name . '\')'; |
|||
} elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { |
|||
list($table, $key) = explode('.', $key, 2); |
|||
if ('__TABLE__' == $table) { |
|||
$table = $this->query->getTable(); |
|||
} |
|||
if (isset($options['alias'][$table])) { |
|||
$table = $options['alias'][$table]; |
|||
} |
|||
} |
|||
if (!preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { |
|||
$key = '`' . $key . '`'; |
|||
} |
|||
if (isset($table)) { |
|||
$key = '`' . $table . '`.' . $key; |
|||
} |
|||
return $key; |
|||
} |
|||
|
|||
/** |
|||
* 随机排序 |
|||
* @access protected |
|||
* @return string |
|||
*/ |
|||
protected function parseRand() |
|||
{ |
|||
return 'rand()'; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,81 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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 |
|||
* @param array $options |
|||
* @return string |
|||
*/ |
|||
protected function parseKey($key, $options = []) |
|||
{ |
|||
$key = trim($key); |
|||
if (strpos($key, '$.') && false === strpos($key, '(')) { |
|||
// JSON字段支持 |
|||
list($field, $name) = explode('$.', $key); |
|||
$key = $field . '->>\'' . $name . '\''; |
|||
} elseif (strpos($key, '.')) { |
|||
list($table, $key) = explode('.', $key, 2); |
|||
if ('__TABLE__' == $table) { |
|||
$table = $this->query->getTable(); |
|||
} |
|||
if (isset($options['alias'][$table])) { |
|||
$table = $options['alias'][$table]; |
|||
} |
|||
} |
|||
if (isset($table)) { |
|||
$key = $table . '.' . $key; |
|||
} |
|||
return $key; |
|||
} |
|||
|
|||
/** |
|||
* 随机排序 |
|||
* @access protected |
|||
* @return string |
|||
*/ |
|||
protected function parseRand() |
|||
{ |
|||
return 'RANDOM()'; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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()'; |
|||
} |
|||
|
|||
/** |
|||
* 字段和表名处理 |
|||
* @access protected |
|||
* @param string $key |
|||
* @param array $options |
|||
* @return string |
|||
*/ |
|||
protected function parseKey($key, $options = []) |
|||
{ |
|||
$key = trim($key); |
|||
if (strpos($key, '.')) { |
|||
list($table, $key) = explode('.', $key, 2); |
|||
if ('__TABLE__' == $table) { |
|||
$table = $this->query->getTable(); |
|||
} |
|||
if (isset($options['alias'][$table])) { |
|||
$table = $options['alias'][$table]; |
|||
} |
|||
} |
|||
if (isset($table)) { |
|||
$key = $table . '.' . $key; |
|||
} |
|||
return $key; |
|||
} |
|||
} |
|||
@ -0,0 +1,119 @@ |
|||
<?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% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; |
|||
protected $deleteSql = 'DELETE FROM %TABLE% %USING% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; |
|||
|
|||
/** |
|||
* order分析 |
|||
* @access protected |
|||
* @param mixed $order |
|||
* @param array $options |
|||
* @return string |
|||
*/ |
|||
protected function parseOrder($order, $options = []) |
|||
{ |
|||
if (is_array($order)) { |
|||
$array = []; |
|||
foreach ($order as $key => $val) { |
|||
if (is_numeric($key)) { |
|||
if (false === strpos($val, '(')) { |
|||
$array[] = $this->parseKey($val, $options); |
|||
} elseif ('[rand]' == $val) { |
|||
$array[] = $this->parseRand(); |
|||
} |
|||
} else { |
|||
$sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : ''; |
|||
$array[] = $this->parseKey($key, $options) . ' ' . $sort; |
|||
} |
|||
} |
|||
$order = implode(',', $array); |
|||
} |
|||
return !empty($order) ? ' ORDER BY ' . $order : ' ORDER BY rand()'; |
|||
} |
|||
|
|||
/** |
|||
* 随机排序 |
|||
* @access protected |
|||
* @return string |
|||
*/ |
|||
protected function parseRand() |
|||
{ |
|||
return 'rand()'; |
|||
} |
|||
|
|||
/** |
|||
* 字段和表名处理 |
|||
* @access protected |
|||
* @param string $key |
|||
* @param array $options |
|||
* @return string |
|||
*/ |
|||
protected function parseKey($key, $options = []) |
|||
{ |
|||
$key = trim($key); |
|||
if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) { |
|||
list($table, $key) = explode('.', $key, 2); |
|||
if ('__TABLE__' == $table) { |
|||
$table = $this->query->getTable(); |
|||
} |
|||
if (isset($options['alias'][$table])) { |
|||
$table = $options['alias'][$table]; |
|||
} |
|||
} |
|||
if (!is_numeric($key) && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) { |
|||
$key = '[' . $key . ']'; |
|||
} |
|||
if (isset($table)) { |
|||
$key = '[' . $table . '].' . $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,146 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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 |
|||
{ |
|||
|
|||
protected $builder = '\\think\\db\\builder\\Mysql'; |
|||
|
|||
/** |
|||
* 解析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 (false === strpos($tableName, '`')) { |
|||
if (strpos($tableName, '.')) { |
|||
$tableName = str_replace('.', '`.`', $tableName); |
|||
} |
|||
$tableName = '`' . $tableName . '`'; |
|||
} |
|||
$sql = 'SHOW COLUMNS FROM ' . $tableName; |
|||
// 调试开始 |
|||
$this->debug(true); |
|||
$pdo = $this->linkID->query($sql); |
|||
// 调试结束 |
|||
$this->debug(false, $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 = '') |
|||
{ |
|||
$this->initConnect(true); |
|||
$sql = !empty($dbName) ? 'SHOW TABLES FROM ' . $dbName : 'SHOW TABLES '; |
|||
// 调试开始 |
|||
$this->debug(true); |
|||
$pdo = $this->linkID->query($sql); |
|||
// 调试结束 |
|||
$this->debug(false, $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; |
|||
} |
|||
|
|||
/** |
|||
* 是否断线 |
|||
* @access protected |
|||
* @param \PDOException $e 异常对象 |
|||
* @return bool |
|||
*/ |
|||
protected function isBreak($e) |
|||
{ |
|||
if (false !== stripos($e->getMessage(), 'server has gone away')) { |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
} |
|||
@ -0,0 +1,111 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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 |
|||
{ |
|||
protected $builder = '\\think\\db\\builder\\Pgsql'; |
|||
|
|||
/** |
|||
* 解析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 . '\');'; |
|||
// 调试开始 |
|||
$this->debug(true); |
|||
$pdo = $this->linkID->query($sql); |
|||
// 调试结束 |
|||
$this->debug(false, $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']), |
|||
'default' => $val['default'], |
|||
'primary' => !empty($val['key']), |
|||
'autoinc' => (0 === strpos($val['extra'], 'nextval(')), |
|||
]; |
|||
} |
|||
} |
|||
return $this->fieldCase($info); |
|||
} |
|||
|
|||
/** |
|||
* 取得数据库的表信息 |
|||
* @access public |
|||
* @param string $dbName |
|||
* @return array |
|||
*/ |
|||
public function getTables($dbName = '') |
|||
{ |
|||
$this->initConnect(true); |
|||
$sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'"; |
|||
// 调试开始 |
|||
$this->debug(true); |
|||
$pdo = $this->linkID->query($sql); |
|||
// 调试结束 |
|||
$this->debug(false, $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,111 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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 |
|||
{ |
|||
|
|||
protected $builder = '\\think\\db\\builder\\Sqlite'; |
|||
|
|||
/** |
|||
* 解析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 . ' )'; |
|||
// 调试开始 |
|||
$this->debug(true); |
|||
$pdo = $this->linkID->query($sql); |
|||
// 调试结束 |
|||
$this->debug(false, $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 = '') |
|||
{ |
|||
$this->initConnect(true); |
|||
$sql = "SELECT name FROM sqlite_master WHERE type='table' " |
|||
. "UNION ALL SELECT name FROM sqlite_temp_master " |
|||
. "WHERE type='table' ORDER BY name"; |
|||
// 调试开始 |
|||
$this->debug(true); |
|||
$pdo = $this->linkID->query($sql); |
|||
// 调试结束 |
|||
$this->debug(false, $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,130 @@ |
|||
<?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_NATURAL, |
|||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
|||
PDO::ATTR_STRINGIFY_FETCHES => false, |
|||
]; |
|||
protected $builder = '\\think\\db\\builder\\Sqlsrv'; |
|||
/** |
|||
* 解析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'"; |
|||
// 调试开始 |
|||
$this->debug(true); |
|||
$pdo = $this->linkID->query($sql); |
|||
// 调试结束 |
|||
$this->debug(false, $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, |
|||
]; |
|||
} |
|||
} |
|||
$sql = "SELECT column_name FROM information_schema.key_column_usage WHERE table_name='$tableName'"; |
|||
// 调试开始 |
|||
$this->debug(true); |
|||
$pdo = $this->linkID->query($sql); |
|||
// 调试结束 |
|||
$this->debug(false, $sql); |
|||
$result = $pdo->fetch(PDO::FETCH_ASSOC); |
|||
if ($result) { |
|||
$info[$result['column_name']]['primary'] = true; |
|||
} |
|||
return $this->fieldCase($info); |
|||
} |
|||
|
|||
/** |
|||
* 取得数据表的字段信息 |
|||
* @access public |
|||
* @param string $dbName |
|||
* @return array |
|||
*/ |
|||
public function getTables($dbName = '') |
|||
{ |
|||
$this->initConnect(true); |
|||
$sql = "SELECT TABLE_NAME |
|||
FROM INFORMATION_SCHEMA.TABLES |
|||
WHERE TABLE_TYPE = 'BASE TABLE' |
|||
"; |
|||
// 调试开始 |
|||
$this->debug(true); |
|||
$pdo = $this->linkID->query($sql); |
|||
// 调试结束 |
|||
$this->debug(false, $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 '获得表信息'; |
|||
@ -0,0 +1,35 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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\db\exception; |
|||
|
|||
use think\exception\DbException; |
|||
|
|||
/** |
|||
* PDO参数绑定异常 |
|||
*/ |
|||
class BindParamException extends DbException |
|||
{ |
|||
|
|||
/** |
|||
* BindParamException constructor. |
|||
* @param string $message |
|||
* @param array $config |
|||
* @param string $sql |
|||
* @param array $bind |
|||
* @param int $code |
|||
*/ |
|||
public function __construct($message, $config, $sql, $bind, $code = 10502) |
|||
{ |
|||
$this->setData('Bind Param', $bind); |
|||
parent::__construct($message, $config, $sql, $code); |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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\db\exception; |
|||
|
|||
use think\exception\DbException; |
|||
|
|||
class DataNotFoundException extends DbException |
|||
{ |
|||
protected $table; |
|||
|
|||
/** |
|||
* DbException constructor. |
|||
* @param string $message |
|||
* @param string $table |
|||
* @param array $config |
|||
*/ |
|||
public function __construct($message, $table = '', array $config = []) |
|||
{ |
|||
$this->message = $message; |
|||
$this->table = $table; |
|||
|
|||
$this->setData('Database Config', $config); |
|||
} |
|||
|
|||
/** |
|||
* 获取数据表名 |
|||
* @access public |
|||
* @return string |
|||
*/ |
|||
public function getTable() |
|||
{ |
|||
return $this->table; |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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\db\exception; |
|||
|
|||
use think\exception\DbException; |
|||
|
|||
class ModelNotFoundException extends DbException |
|||
{ |
|||
protected $model; |
|||
|
|||
/** |
|||
* 构造方法 |
|||
* @param string $message |
|||
* @param string $model |
|||
*/ |
|||
public function __construct($message, $model = '', array $config = []) |
|||
{ |
|||
$this->message = $message; |
|||
$this->model = $model; |
|||
|
|||
$this->setData('Database Config', $config); |
|||
} |
|||
|
|||
/** |
|||
* 获取模型类名 |
|||
* @access public |
|||
* @return string |
|||
*/ |
|||
public function getModel() |
|||
{ |
|||
return $this->model; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,160 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yangweijie <yangweijiester@gmail.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\debug; |
|||
|
|||
use think\Cache; |
|||
use think\Config; |
|||
use think\Db; |
|||
use think\Debug; |
|||
use think\Request; |
|||
use think\Response; |
|||
|
|||
/** |
|||
* 浏览器调试输出 |
|||
*/ |
|||
class Console |
|||
{ |
|||
protected $config = [ |
|||
'trace_tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], |
|||
]; |
|||
|
|||
// 实例化并传入参数 |
|||
public function __construct($config = []) |
|||
{ |
|||
if (is_array($config)) { |
|||
$this->config = array_merge($this->config, $config); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 调试输出接口 |
|||
* @access public |
|||
* @param Response $response Response对象 |
|||
* @param array $log 日志信息 |
|||
* @return bool |
|||
*/ |
|||
public function output(Response $response, array $log = []) |
|||
{ |
|||
$request = Request::instance(); |
|||
$contentType = $response->getHeader('Content-Type'); |
|||
$accept = $request->header('accept'); |
|||
if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { |
|||
return false; |
|||
} elseif (!empty($contentType) && strpos($contentType, 'html') === false) { |
|||
return false; |
|||
} |
|||
// 获取基本信息 |
|||
$runtime = number_format(microtime(true) - THINK_START_TIME, 10); |
|||
$reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; |
|||
$mem = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); |
|||
|
|||
if (isset($_SERVER['HTTP_HOST'])) { |
|||
$uri = $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; |
|||
} else { |
|||
$uri = 'cmd:' . implode(' ', $_SERVER['argv']); |
|||
} |
|||
|
|||
// 页面Trace信息 |
|||
$base = [ |
|||
'请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, |
|||
'运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), |
|||
'查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', |
|||
'缓存信息' => Cache::$readTimes . ' reads,' . Cache::$writeTimes . ' writes', |
|||
'配置加载' => count(Config::get()), |
|||
]; |
|||
|
|||
if (session_id()) { |
|||
$base['会话信息'] = 'SESSION_ID=' . session_id(); |
|||
} |
|||
|
|||
$info = Debug::getFile(true); |
|||
|
|||
// 页面Trace信息 |
|||
$trace = []; |
|||
foreach ($this->config['trace_tabs'] as $name => $title) { |
|||
$name = strtolower($name); |
|||
switch ($name) { |
|||
case 'base': // 基本信息 |
|||
$trace[$title] = $base; |
|||
break; |
|||
case 'file': // 文件信息 |
|||
$trace[$title] = $info; |
|||
break; |
|||
default: // 调试信息 |
|||
if (strpos($name, '|')) { |
|||
// 多组信息 |
|||
$names = explode('|', $name); |
|||
$result = []; |
|||
foreach ($names as $name) { |
|||
$result = array_merge($result, isset($log[$name]) ? $log[$name] : []); |
|||
} |
|||
$trace[$title] = $result; |
|||
} else { |
|||
$trace[$title] = isset($log[$name]) ? $log[$name] : ''; |
|||
} |
|||
} |
|||
} |
|||
|
|||
//输出到控制台 |
|||
$lines = ''; |
|||
foreach ($trace as $type => $msg) { |
|||
$lines .= $this->console($type, $msg); |
|||
} |
|||
$js = <<<JS |
|||
|
|||
<script type='text/javascript'> |
|||
{$lines} |
|||
</script> |
|||
JS; |
|||
return $js; |
|||
} |
|||
|
|||
protected function console($type, $msg) |
|||
{ |
|||
$type = strtolower($type); |
|||
$trace_tabs = array_values($this->config['trace_tabs']); |
|||
$line[] = ($type == $trace_tabs[0] || '调试' == $type || '错误' == $type) |
|||
? "console.group('{$type}');" |
|||
: "console.groupCollapsed('{$type}');"; |
|||
|
|||
foreach ((array) $msg as $key => $m) { |
|||
switch ($type) { |
|||
case '调试': |
|||
$var_type = gettype($m); |
|||
if (in_array($var_type, ['array', 'string'])) { |
|||
$line[] = "console.log(" . json_encode($m) . ");"; |
|||
} else { |
|||
$line[] = "console.log(" . json_encode(var_export($m, 1)) . ");"; |
|||
} |
|||
break; |
|||
case '错误': |
|||
$msg = str_replace("\n", '\n', $m); |
|||
$style = 'color:#F4006B;font-size:14px;'; |
|||
$line[] = "console.error(\"%c{$msg}\", \"{$style}\");"; |
|||
break; |
|||
case 'sql': |
|||
$msg = str_replace("\n", '\n', $m); |
|||
$style = "color:#009bb4;"; |
|||
$line[] = "console.log(\"%c{$msg}\", \"{$style}\");"; |
|||
break; |
|||
default: |
|||
$m = is_string($key) ? $key . ' ' . $m : $key + 1 . ' ' . $m; |
|||
$msg = json_encode($m); |
|||
$line[] = "console.log({$msg});"; |
|||
break; |
|||
} |
|||
} |
|||
$line[] = "console.groupEnd();"; |
|||
return implode(PHP_EOL, $line); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,111 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: liu21st <liu21st@gmail.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\debug; |
|||
|
|||
use think\Cache; |
|||
use think\Config; |
|||
use think\Db; |
|||
use think\Debug; |
|||
use think\Request; |
|||
use think\Response; |
|||
|
|||
/** |
|||
* 页面Trace调试 |
|||
*/ |
|||
class Html |
|||
{ |
|||
protected $config = [ |
|||
'trace_file' => '', |
|||
'trace_tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], |
|||
]; |
|||
|
|||
// 实例化并传入参数 |
|||
public function __construct(array $config = []) |
|||
{ |
|||
$this->config['trace_file'] = THINK_PATH . 'tpl/page_trace.tpl'; |
|||
$this->config = array_merge($this->config, $config); |
|||
} |
|||
|
|||
/** |
|||
* 调试输出接口 |
|||
* @access public |
|||
* @param Response $response Response对象 |
|||
* @param array $log 日志信息 |
|||
* @return bool |
|||
*/ |
|||
public function output(Response $response, array $log = []) |
|||
{ |
|||
$request = Request::instance(); |
|||
$contentType = $response->getHeader('Content-Type'); |
|||
$accept = $request->header('accept'); |
|||
if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { |
|||
return false; |
|||
} elseif (!empty($contentType) && strpos($contentType, 'html') === false) { |
|||
return false; |
|||
} |
|||
// 获取基本信息 |
|||
$runtime = number_format(microtime(true) - THINK_START_TIME, 10); |
|||
$reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; |
|||
$mem = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); |
|||
|
|||
// 页面Trace信息 |
|||
if (isset($_SERVER['HTTP_HOST'])) { |
|||
$uri = $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; |
|||
} else { |
|||
$uri = 'cmd:' . implode(' ', $_SERVER['argv']); |
|||
} |
|||
$base = [ |
|||
'请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, |
|||
'运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), |
|||
'查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', |
|||
'缓存信息' => Cache::$readTimes . ' reads,' . Cache::$writeTimes . ' writes', |
|||
'配置加载' => count(Config::get()), |
|||
]; |
|||
|
|||
if (session_id()) { |
|||
$base['会话信息'] = 'SESSION_ID=' . session_id(); |
|||
} |
|||
|
|||
$info = Debug::getFile(true); |
|||
|
|||
// 页面Trace信息 |
|||
$trace = []; |
|||
foreach ($this->config['trace_tabs'] as $name => $title) { |
|||
$name = strtolower($name); |
|||
switch ($name) { |
|||
case 'base': // 基本信息 |
|||
$trace[$title] = $base; |
|||
break; |
|||
case 'file': // 文件信息 |
|||
$trace[$title] = $info; |
|||
break; |
|||
default: // 调试信息 |
|||
if (strpos($name, '|')) { |
|||
// 多组信息 |
|||
$names = explode('|', $name); |
|||
$result = []; |
|||
foreach ($names as $name) { |
|||
$result = array_merge($result, isset($log[$name]) ? $log[$name] : []); |
|||
} |
|||
$trace[$title] = $result; |
|||
} else { |
|||
$trace[$title] = isset($log[$name]) ? $log[$name] : ''; |
|||
} |
|||
} |
|||
} |
|||
// 调用Trace页面模板 |
|||
ob_start(); |
|||
include $this->config['trace_file']; |
|||
return ob_get_clean(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\exception; |
|||
|
|||
class ClassNotFoundException extends \RuntimeException |
|||
{ |
|||
protected $class; |
|||
public function __construct($message, $class = '') |
|||
{ |
|||
$this->message = $message; |
|||
$this->class = $class; |
|||
} |
|||
|
|||
/** |
|||
* 获取类名 |
|||
* @access public |
|||
* @return string |
|||
*/ |
|||
public function getClass() |
|||
{ |
|||
return $this->class; |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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\exception; |
|||
|
|||
use think\Exception; |
|||
|
|||
/** |
|||
* Database相关异常处理类 |
|||
*/ |
|||
class DbException extends Exception |
|||
{ |
|||
/** |
|||
* DbException constructor. |
|||
* @param string $message |
|||
* @param array $config |
|||
* @param string $sql |
|||
* @param int $code |
|||
*/ |
|||
public function __construct($message, array $config, $sql, $code = 10500) |
|||
{ |
|||
$this->message = $message; |
|||
$this->code = $code; |
|||
|
|||
$this->setData('Database Status', [ |
|||
'Error Code' => $code, |
|||
'Error Message' => $message, |
|||
'Error SQL' => $sql, |
|||
]); |
|||
|
|||
$this->setData('Database Config', $config); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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\exception; |
|||
|
|||
use think\Exception; |
|||
|
|||
/** |
|||
* ThinkPHP错误异常 |
|||
* 主要用于封装 set_error_handler 和 register_shutdown_function 得到的错误 |
|||
* 除开从 think\Exception 继承的功能 |
|||
* 其他和PHP系统\ErrorException功能基本一样 |
|||
*/ |
|||
class ErrorException extends Exception |
|||
{ |
|||
/** |
|||
* 用于保存错误级别 |
|||
* @var integer |
|||
*/ |
|||
protected $severity; |
|||
|
|||
/** |
|||
* 错误异常构造函数 |
|||
* @param integer $severity 错误级别 |
|||
* @param string $message 错误详细信息 |
|||
* @param string $file 出错文件路径 |
|||
* @param integer $line 出错行号 |
|||
* @param array $context 错误上下文,会包含错误触发处作用域内所有变量的数组 |
|||
*/ |
|||
public function __construct($severity, $message, $file, $line, array $context = []) |
|||
{ |
|||
$this->severity = $severity; |
|||
$this->message = $message; |
|||
$this->file = $file; |
|||
$this->line = $line; |
|||
$this->code = 0; |
|||
|
|||
empty($context) || $this->setData('Error Context', $context); |
|||
} |
|||
|
|||
/** |
|||
* 获取错误级别 |
|||
* @return integer 错误级别 |
|||
*/ |
|||
final public function getSeverity() |
|||
{ |
|||
return $this->severity; |
|||
} |
|||
} |
|||
@ -0,0 +1,267 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\exception; |
|||
|
|||
use Exception; |
|||
use think\App; |
|||
use think\Config; |
|||
use think\console\Output; |
|||
use think\Lang; |
|||
use think\Log; |
|||
use think\Response; |
|||
|
|||
class Handle |
|||
{ |
|||
|
|||
protected $ignoreReport = [ |
|||
'\\think\\exception\\HttpException', |
|||
]; |
|||
|
|||
/** |
|||
* Report or log an exception. |
|||
* |
|||
* @param \Exception $exception |
|||
* @return void |
|||
*/ |
|||
public function report(Exception $exception) |
|||
{ |
|||
if (!$this->isIgnoreReport($exception)) { |
|||
// 收集异常数据 |
|||
if (App::$debug) { |
|||
$data = [ |
|||
'file' => $exception->getFile(), |
|||
'line' => $exception->getLine(), |
|||
'message' => $this->getMessage($exception), |
|||
'code' => $this->getCode($exception), |
|||
]; |
|||
$log = "[{$data['code']}]{$data['message']}[{$data['file']}:{$data['line']}]"; |
|||
} else { |
|||
$data = [ |
|||
'code' => $this->getCode($exception), |
|||
'message' => $this->getMessage($exception), |
|||
]; |
|||
$log = "[{$data['code']}]{$data['message']}"; |
|||
} |
|||
|
|||
Log::record($log, 'error'); |
|||
} |
|||
} |
|||
|
|||
protected function isIgnoreReport(Exception $exception) |
|||
{ |
|||
foreach ($this->ignoreReport as $class) { |
|||
if ($exception instanceof $class) { |
|||
return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* Render an exception into an HTTP response. |
|||
* |
|||
* @param \Exception $e |
|||
* @return Response |
|||
*/ |
|||
public function render(Exception $e) |
|||
{ |
|||
if ($e instanceof HttpException) { |
|||
return $this->renderHttpException($e); |
|||
} else { |
|||
return $this->convertExceptionToResponse($e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param Output $output |
|||
* @param Exception $e |
|||
*/ |
|||
public function renderForConsole(Output $output, Exception $e) |
|||
{ |
|||
if (App::$debug) { |
|||
$output->setVerbosity(Output::VERBOSITY_DEBUG); |
|||
} |
|||
$output->renderException($e); |
|||
} |
|||
|
|||
/** |
|||
* @param HttpException $e |
|||
* @return Response |
|||
*/ |
|||
protected function renderHttpException(HttpException $e) |
|||
{ |
|||
$status = $e->getStatusCode(); |
|||
$template = Config::get('http_exception_template'); |
|||
if (!App::$debug && !empty($template[$status])) { |
|||
return Response::create($template[$status], 'view', $status)->assign(['e' => $e]); |
|||
} else { |
|||
return $this->convertExceptionToResponse($e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param Exception $exception |
|||
* @return Response |
|||
*/ |
|||
protected function convertExceptionToResponse(Exception $exception) |
|||
{ |
|||
// 收集异常数据 |
|||
if (App::$debug) { |
|||
// 调试模式,获取详细的错误信息 |
|||
$data = [ |
|||
'name' => get_class($exception), |
|||
'file' => $exception->getFile(), |
|||
'line' => $exception->getLine(), |
|||
'message' => $this->getMessage($exception), |
|||
'trace' => $exception->getTrace(), |
|||
'code' => $this->getCode($exception), |
|||
'source' => $this->getSourceCode($exception), |
|||
'datas' => $this->getExtendData($exception), |
|||
'tables' => [ |
|||
'GET Data' => $_GET, |
|||
'POST Data' => $_POST, |
|||
'Files' => $_FILES, |
|||
'Cookies' => $_COOKIE, |
|||
'Session' => isset($_SESSION) ? $_SESSION : [], |
|||
'Server/Request Data' => $_SERVER, |
|||
'Environment Variables' => $_ENV, |
|||
'ThinkPHP Constants' => $this->getConst(), |
|||
], |
|||
]; |
|||
} else { |
|||
// 部署模式仅显示 Code 和 Message |
|||
$data = [ |
|||
'code' => $this->getCode($exception), |
|||
'message' => $this->getMessage($exception), |
|||
]; |
|||
|
|||
if (!Config::get('show_error_msg')) { |
|||
// 不显示详细错误信息 |
|||
$data['message'] = Config::get('error_message'); |
|||
} |
|||
} |
|||
|
|||
//保留一层 |
|||
while (ob_get_level() > 1) { |
|||
ob_end_clean(); |
|||
} |
|||
|
|||
$data['echo'] = ob_get_clean(); |
|||
|
|||
ob_start(); |
|||
extract($data); |
|||
|
|||
include Config::get('exception_tmpl'); |
|||
// 获取并清空缓存 |
|||
$content = ob_get_clean(); |
|||
$response = new Response($content, 'html'); |
|||
|
|||
if ($exception instanceof HttpException) { |
|||
$statusCode = $exception->getStatusCode(); |
|||
$response->header($exception->getHeaders()); |
|||
} |
|||
|
|||
if (!isset($statusCode)) { |
|||
$statusCode = 500; |
|||
} |
|||
$response->code($statusCode); |
|||
return $response; |
|||
} |
|||
|
|||
/** |
|||
* 获取错误编码 |
|||
* ErrorException则使用错误级别作为错误编码 |
|||
* @param \Exception $exception |
|||
* @return integer 错误编码 |
|||
*/ |
|||
protected function getCode(Exception $exception) |
|||
{ |
|||
$code = $exception->getCode(); |
|||
if (!$code && $exception instanceof ErrorException) { |
|||
$code = $exception->getSeverity(); |
|||
} |
|||
return $code; |
|||
} |
|||
|
|||
/** |
|||
* 获取错误信息 |
|||
* ErrorException则使用错误级别作为错误编码 |
|||
* @param \Exception $exception |
|||
* @return string 错误信息 |
|||
*/ |
|||
protected function getMessage(Exception $exception) |
|||
{ |
|||
$message = $exception->getMessage(); |
|||
if (IS_CLI) { |
|||
return $message; |
|||
} |
|||
|
|||
if (strpos($message, ':')) { |
|||
$name = strstr($message, ':', true); |
|||
$message = Lang::has($name) ? Lang::get($name) . strstr($message, ':') : $message; |
|||
} elseif (strpos($message, ',')) { |
|||
$name = strstr($message, ',', true); |
|||
$message = Lang::has($name) ? Lang::get($name) . ':' . substr(strstr($message, ','), 1) : $message; |
|||
} elseif (Lang::has($message)) { |
|||
$message = Lang::get($message); |
|||
} |
|||
return $message; |
|||
} |
|||
|
|||
/** |
|||
* 获取出错文件内容 |
|||
* 获取错误的前9行和后9行 |
|||
* @param \Exception $exception |
|||
* @return array 错误文件内容 |
|||
*/ |
|||
protected function getSourceCode(Exception $exception) |
|||
{ |
|||
// 读取前9行和后9行 |
|||
$line = $exception->getLine(); |
|||
$first = ($line - 9 > 0) ? $line - 9 : 1; |
|||
|
|||
try { |
|||
$contents = file($exception->getFile()); |
|||
$source = [ |
|||
'first' => $first, |
|||
'source' => array_slice($contents, $first - 1, 19), |
|||
]; |
|||
} catch (Exception $e) { |
|||
$source = []; |
|||
} |
|||
return $source; |
|||
} |
|||
|
|||
/** |
|||
* 获取异常扩展信息 |
|||
* 用于非调试模式html返回类型显示 |
|||
* @param \Exception $exception |
|||
* @return array 异常类定义的扩展数据 |
|||
*/ |
|||
protected function getExtendData(Exception $exception) |
|||
{ |
|||
$data = []; |
|||
if ($exception instanceof \think\Exception) { |
|||
$data = $exception->getData(); |
|||
} |
|||
return $data; |
|||
} |
|||
|
|||
/** |
|||
* 获取常量列表 |
|||
* @return array 常量列表 |
|||
*/ |
|||
private static function getConst() |
|||
{ |
|||
return get_defined_constants(true)['user']; |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\exception; |
|||
|
|||
class HttpException extends \RuntimeException |
|||
{ |
|||
private $statusCode; |
|||
private $headers; |
|||
|
|||
public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = [], $code = 0) |
|||
{ |
|||
$this->statusCode = $statusCode; |
|||
$this->headers = $headers; |
|||
|
|||
parent::__construct($message, $code, $previous); |
|||
} |
|||
|
|||
public function getStatusCode() |
|||
{ |
|||
return $this->statusCode; |
|||
} |
|||
|
|||
public function getHeaders() |
|||
{ |
|||
return $this->headers; |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\exception; |
|||
|
|||
use think\Response; |
|||
|
|||
class HttpResponseException extends \RuntimeException |
|||
{ |
|||
/** |
|||
* @var Response |
|||
*/ |
|||
protected $response; |
|||
|
|||
public function __construct(Response $response) |
|||
{ |
|||
$this->response = $response; |
|||
} |
|||
|
|||
public function getResponse() |
|||
{ |
|||
return $this->response; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 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\exception; |
|||
|
|||
/** |
|||
* PDO异常处理类 |
|||
* 重新封装了系统的\PDOException类 |
|||
*/ |
|||
class PDOException extends DbException |
|||
{ |
|||
/** |
|||
* PDOException constructor. |
|||
* @param \PDOException $exception |
|||
* @param array $config |
|||
* @param string $sql |
|||
* @param int $code |
|||
*/ |
|||
public function __construct(\PDOException $exception, array $config, $sql, $code = 10501) |
|||
{ |
|||
$error = $exception->errorInfo; |
|||
|
|||
$this->setData('PDO Error Info', [ |
|||
'SQLSTATE' => $error[0], |
|||
'Driver Error Code' => isset($error[1]) ? $error[1] : 0, |
|||
'Driver Error Message' => isset($error[2]) ? $error[2] : '', |
|||
]); |
|||
|
|||
parent::__construct($exception->getMessage(), $config, $sql, $code); |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\exception; |
|||
|
|||
class RouteNotFoundException extends HttpException |
|||
{ |
|||
|
|||
public function __construct() |
|||
{ |
|||
parent::__construct(404); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\exception; |
|||
|
|||
class TemplateNotFoundException extends \RuntimeException |
|||
{ |
|||
protected $template; |
|||
|
|||
public function __construct($message, $template = '') |
|||
{ |
|||
$this->message = $message; |
|||
$this->template = $template; |
|||
} |
|||
|
|||
/** |
|||
* 获取模板文件 |
|||
* @access public |
|||
* @return string |
|||
*/ |
|||
public function getTemplate() |
|||
{ |
|||
return $this->template; |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\exception; |
|||
|
|||
class ThrowableError extends \ErrorException |
|||
{ |
|||
public function __construct(\Throwable $e) |
|||
{ |
|||
|
|||
if ($e instanceof \ParseError) { |
|||
$message = 'Parse error: ' . $e->getMessage(); |
|||
$severity = E_PARSE; |
|||
} elseif ($e instanceof \TypeError) { |
|||
$message = 'Type error: ' . $e->getMessage(); |
|||
$severity = E_RECOVERABLE_ERROR; |
|||
} else { |
|||
$message = 'Fatal error: ' . $e->getMessage(); |
|||
$severity = E_ERROR; |
|||
} |
|||
|
|||
parent::__construct( |
|||
$message, |
|||
$e->getCode(), |
|||
$severity, |
|||
$e->getFile(), |
|||
$e->getLine() |
|||
); |
|||
|
|||
$this->setTrace($e->getTrace()); |
|||
} |
|||
|
|||
protected function setTrace($trace) |
|||
{ |
|||
$traceReflector = new \ReflectionProperty('Exception', 'trace'); |
|||
$traceReflector->setAccessible(true); |
|||
$traceReflector->setValue($this, $trace); |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: yunwuxin <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\exception; |
|||
|
|||
class ValidateException extends \RuntimeException |
|||
{ |
|||
protected $error; |
|||
|
|||
public function __construct($error) |
|||
{ |
|||
$this->error = $error; |
|||
$this->message = is_array($error) ? implode("\n\r", $error) : $error; |
|||
} |
|||
|
|||
/** |
|||
* 获取验证错误信息 |
|||
* @access public |
|||
* @return array|string |
|||
*/ |
|||
public function getError() |
|||
{ |
|||
return $this->error; |
|||
} |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: liu21st <liu21st@gmail.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\log\driver; |
|||
|
|||
use think\App; |
|||
|
|||
/** |
|||
* 本地化调试输出到文件 |
|||
*/ |
|||
class File |
|||
{ |
|||
protected $config = [ |
|||
'time_format' => ' c ', |
|||
'file_size' => 2097152, |
|||
'path' => LOG_PATH, |
|||
'apart_level' => [], |
|||
]; |
|||
|
|||
// 实例化并传入参数 |
|||
public function __construct($config = []) |
|||
{ |
|||
if (is_array($config)) { |
|||
$this->config = array_merge($this->config, $config); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 日志写入接口 |
|||
* @access public |
|||
* @param array $log 日志信息 |
|||
* @param bool $depr 是否写入分割线 |
|||
* @return bool |
|||
*/ |
|||
public function save(array $log = [], $depr = true) |
|||
{ |
|||
$now = date($this->config['time_format']); |
|||
$destination = $this->config['path'] . date('Ym') . DS . date('d') . '.log'; |
|||
|
|||
$path = dirname($destination); |
|||
!is_dir($path) && mkdir($path, 0755, true); |
|||
|
|||
//检测日志文件大小,超过配置大小则备份日志文件重新生成 |
|||
if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { |
|||
rename($destination, dirname($destination) . DS . $_SERVER['REQUEST_TIME'] . '-' . basename($destination)); |
|||
} |
|||
|
|||
$depr = $depr ? "---------------------------------------------------------------\r\n" : ''; |
|||
$info = ''; |
|||
if (App::$debug) { |
|||
// 获取基本信息 |
|||
if (isset($_SERVER['HTTP_HOST'])) { |
|||
$current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; |
|||
} else { |
|||
$current_uri = "cmd:" . implode(' ', $_SERVER['argv']); |
|||
} |
|||
|
|||
$runtime = round(microtime(true) - THINK_START_TIME, 10); |
|||
$reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; |
|||
$time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; |
|||
$memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); |
|||
$memory_str = ' [内存消耗:' . $memory_use . 'kb]'; |
|||
$file_load = ' [文件加载:' . count(get_included_files()) . ']'; |
|||
|
|||
$info = '[ log ] ' . $current_uri . $time_str . $memory_str . $file_load . "\r\n"; |
|||
$server = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '0.0.0.0'; |
|||
$remote = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0'; |
|||
$method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI'; |
|||
$uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; |
|||
} |
|||
foreach ($log as $type => $val) { |
|||
$level = ''; |
|||
foreach ($val as $msg) { |
|||
if (!is_string($msg)) { |
|||
$msg = var_export($msg, true); |
|||
} |
|||
$level .= '[ ' . $type . ' ] ' . $msg . "\r\n"; |
|||
} |
|||
if (in_array($type, $this->config['apart_level'])) { |
|||
// 独立记录的日志级别 |
|||
$filename = $path . DS . date('d') . '_' . $type . '.log'; |
|||
error_log("[{$now}] {$level}\r\n{$depr}", 3, $filename); |
|||
} else { |
|||
$info .= $level; |
|||
} |
|||
} |
|||
if (App::$debug) { |
|||
$info = "{$server} {$remote} {$method} {$uri}\r\n" . $info; |
|||
} |
|||
return error_log("[{$now}] {$info}\r\n{$depr}", 3, $destination); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,250 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: luofei614 <weibo.com/luofei614> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\log\driver; |
|||
|
|||
use think\App; |
|||
|
|||
/** |
|||
* github: https://github.com/luofei614/SocketLog |
|||
* @author luofei614<weibo.com/luofei614> |
|||
*/ |
|||
class Socket |
|||
{ |
|||
public $port = 1116; //SocketLog 服务的http的端口号 |
|||
|
|||
protected $config = [ |
|||
// socket服务器地址 |
|||
'host' => 'localhost', |
|||
// 是否显示加载的文件列表 |
|||
'show_included_files' => false, |
|||
// 日志强制记录到配置的client_id |
|||
'force_client_ids' => [], |
|||
// 限制允许读取日志的client_id |
|||
'allow_client_ids' => [], |
|||
]; |
|||
|
|||
protected $css = [ |
|||
'sql' => 'color:#009bb4;', |
|||
'sql_warn' => 'color:#009bb4;font-size:14px;', |
|||
'error' => 'color:#f4006b;font-size:14px;', |
|||
'page' => 'color:#40e2ff;background:#171717;', |
|||
'big' => 'font-size:20px;color:red;', |
|||
]; |
|||
|
|||
protected $allowForceClientIds = []; //配置强制推送且被授权的client_id |
|||
|
|||
/** |
|||
* 构造函数 |
|||
* @param array $config 缓存参数 |
|||
* @access public |
|||
*/ |
|||
public function __construct(array $config = []) |
|||
{ |
|||
if (!empty($config)) { |
|||
$this->config = array_merge($this->config, $config); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 调试输出接口 |
|||
* @access public |
|||
* @param array $log 日志信息 |
|||
* @return bool |
|||
*/ |
|||
public function save(array $log = []) |
|||
{ |
|||
if (!$this->check()) { |
|||
return false; |
|||
} |
|||
$trace = []; |
|||
if (App::$debug) { |
|||
$runtime = round(microtime(true) - THINK_START_TIME, 10); |
|||
$reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; |
|||
$time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; |
|||
$memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); |
|||
$memory_str = ' [内存消耗:' . $memory_use . 'kb]'; |
|||
$file_load = ' [文件加载:' . count(get_included_files()) . ']'; |
|||
|
|||
if (isset($_SERVER['HTTP_HOST'])) { |
|||
$current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; |
|||
} else { |
|||
$current_uri = 'cmd:' . implode(' ', $_SERVER['argv']); |
|||
} |
|||
// 基本信息 |
|||
$trace[] = [ |
|||
'type' => 'group', |
|||
'msg' => $current_uri . $time_str . $memory_str . $file_load, |
|||
'css' => $this->css['page'], |
|||
]; |
|||
} |
|||
|
|||
foreach ($log as $type => $val) { |
|||
$trace[] = [ |
|||
'type' => 'groupCollapsed', |
|||
'msg' => '[ ' . $type . ' ]', |
|||
'css' => isset($this->css[$type]) ? $this->css[$type] : '', |
|||
]; |
|||
foreach ($val as $msg) { |
|||
if (!is_string($msg)) { |
|||
$msg = var_export($msg, true); |
|||
} |
|||
$trace[] = [ |
|||
'type' => 'log', |
|||
'msg' => $msg, |
|||
'css' => '', |
|||
]; |
|||
} |
|||
$trace[] = [ |
|||
'type' => 'groupEnd', |
|||
'msg' => '', |
|||
'css' => '', |
|||
]; |
|||
} |
|||
|
|||
if ($this->config['show_included_files']) { |
|||
$trace[] = [ |
|||
'type' => 'groupCollapsed', |
|||
'msg' => '[ file ]', |
|||
'css' => '', |
|||
]; |
|||
$trace[] = [ |
|||
'type' => 'log', |
|||
'msg' => implode("\n", get_included_files()), |
|||
'css' => '', |
|||
]; |
|||
$trace[] = [ |
|||
'type' => 'groupEnd', |
|||
'msg' => '', |
|||
'css' => '', |
|||
]; |
|||
} |
|||
|
|||
$trace[] = [ |
|||
'type' => 'groupEnd', |
|||
'msg' => '', |
|||
'css' => '', |
|||
]; |
|||
|
|||
$tabid = $this->getClientArg('tabid'); |
|||
if (!$client_id = $this->getClientArg('client_id')) { |
|||
$client_id = ''; |
|||
} |
|||
|
|||
if (!empty($this->allowForceClientIds)) { |
|||
//强制推送到多个client_id |
|||
foreach ($this->allowForceClientIds as $force_client_id) { |
|||
$client_id = $force_client_id; |
|||
$this->sendToClient($tabid, $client_id, $trace, $force_client_id); |
|||
} |
|||
} else { |
|||
$this->sendToClient($tabid, $client_id, $trace, ''); |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* 发送给指定客户端 |
|||
* @author Zjmainstay |
|||
* @param $tabid |
|||
* @param $client_id |
|||
* @param $logs |
|||
* @param $force_client_id |
|||
*/ |
|||
protected function sendToClient($tabid, $client_id, $logs, $force_client_id) |
|||
{ |
|||
$logs = [ |
|||
'tabid' => $tabid, |
|||
'client_id' => $client_id, |
|||
'logs' => $logs, |
|||
'force_client_id' => $force_client_id, |
|||
]; |
|||
$msg = @json_encode($logs); |
|||
$address = '/' . $client_id; //将client_id作为地址, server端通过地址判断将日志发布给谁 |
|||
$this->send($this->config['host'], $msg, $address); |
|||
} |
|||
|
|||
protected function check() |
|||
{ |
|||
$tabid = $this->getClientArg('tabid'); |
|||
//是否记录日志的检查 |
|||
if (!$tabid && !$this->config['force_client_ids']) { |
|||
return false; |
|||
} |
|||
//用户认证 |
|||
$allow_client_ids = $this->config['allow_client_ids']; |
|||
if (!empty($allow_client_ids)) { |
|||
//通过数组交集得出授权强制推送的client_id |
|||
$this->allowForceClientIds = array_intersect($allow_client_ids, $this->config['force_client_ids']); |
|||
if (!$tabid && count($this->allowForceClientIds)) { |
|||
return true; |
|||
} |
|||
|
|||
$client_id = $this->getClientArg('client_id'); |
|||
if (!in_array($client_id, $allow_client_ids)) { |
|||
return false; |
|||
} |
|||
} else { |
|||
$this->allowForceClientIds = $this->config['force_client_ids']; |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
protected function getClientArg($name) |
|||
{ |
|||
static $args = []; |
|||
|
|||
$key = 'HTTP_USER_AGENT'; |
|||
|
|||
if (isset($_SERVER['HTTP_SOCKETLOG'])) { |
|||
$key = 'HTTP_SOCKETLOG'; |
|||
} |
|||
|
|||
if (!isset($_SERVER[$key])) { |
|||
return; |
|||
} |
|||
if (empty($args)) { |
|||
if (!preg_match('/SocketLog\((.*?)\)/', $_SERVER[$key], $match)) { |
|||
$args = ['tabid' => null]; |
|||
return; |
|||
} |
|||
parse_str($match[1], $args); |
|||
} |
|||
if (isset($args[$name])) { |
|||
return $args[$name]; |
|||
} |
|||
return; |
|||
} |
|||
|
|||
/** |
|||
* @param string $host - $host of socket server |
|||
* @param string $message - 发送的消息 |
|||
* @param string $address - 地址 |
|||
* @return bool |
|||
*/ |
|||
protected function send($host, $message = '', $address = '/') |
|||
{ |
|||
$url = 'http://' . $host . ':' . $this->port . $address; |
|||
$ch = curl_init(); |
|||
curl_setopt($ch, CURLOPT_URL, $url); |
|||
curl_setopt($ch, CURLOPT_POST, true); |
|||
curl_setopt($ch, CURLOPT_POSTFIELDS, $message); |
|||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); |
|||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1); |
|||
curl_setopt($ch, CURLOPT_TIMEOUT, 10); |
|||
$headers = [ |
|||
"Content-Type: application/json;charset=UTF-8", |
|||
]; |
|||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); //设置header |
|||
return curl_exec($ch); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: liu21st <liu21st@gmail.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\log\driver; |
|||
|
|||
/** |
|||
* 模拟测试输出 |
|||
*/ |
|||
class Test |
|||
{ |
|||
/** |
|||
* 日志写入接口 |
|||
* @access public |
|||
* @param array $log 日志信息 |
|||
* @return bool |
|||
*/ |
|||
public function save(array $log = []) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: zhangyajun <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\model; |
|||
|
|||
use think\Collection as BaseCollection; |
|||
use think\Model; |
|||
|
|||
class Collection extends BaseCollection |
|||
{ |
|||
/** |
|||
* 延迟预载入关联查询 |
|||
* @access public |
|||
* @param mixed $relation 关联 |
|||
* @return $this |
|||
*/ |
|||
public function load($relation) |
|||
{ |
|||
$item = current($this->items); |
|||
$item->eagerlyResultSet($this->items, $relation); |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 设置需要隐藏的输出属性 |
|||
* @access public |
|||
* @param array $hidden 属性列表 |
|||
* @param bool $override 是否覆盖 |
|||
* @return $this |
|||
*/ |
|||
public function hidden($hidden = [], $override = false) |
|||
{ |
|||
$this->each(function ($model) use ($hidden, $override) { |
|||
/** @var Model $model */ |
|||
$model->hidden($hidden, $override); |
|||
}); |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 设置需要输出的属性 |
|||
* @param array $visible |
|||
* @param bool $override 是否覆盖 |
|||
* @return $this |
|||
*/ |
|||
public function visible($visible = [], $override = false) |
|||
{ |
|||
$this->each(function ($model) use ($visible, $override) { |
|||
/** @var Model $model */ |
|||
$model->visible($visible, $override); |
|||
}); |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 设置需要追加的输出属性 |
|||
* @access public |
|||
* @param array $append 属性列表 |
|||
* @param bool $override 是否覆盖 |
|||
* @return $this |
|||
*/ |
|||
public function append($append = [], $override = false) |
|||
{ |
|||
$this->each(function ($model) use ($append, $override) { |
|||
/** @var Model $model */ |
|||
$model->append($append, $override); |
|||
}); |
|||
return $this; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,315 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: liu21st <liu21st@gmail.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\model; |
|||
|
|||
use think\db\Query; |
|||
use think\Model; |
|||
|
|||
class Merge extends Model |
|||
{ |
|||
|
|||
protected $relationModel = []; // HAS ONE 关联的模型列表 |
|||
protected $fk = ''; // 外键名 默认为主表名_id |
|||
protected $mapFields = []; // 需要处理的模型映射字段,避免混淆 array( id => 'user.id' ) |
|||
|
|||
/** |
|||
* 构造函数 |
|||
* @access public |
|||
* @param array|object $data 数据 |
|||
*/ |
|||
public function __construct($data = []) |
|||
{ |
|||
parent::__construct($data); |
|||
|
|||
// 设置默认外键名 仅支持单一外键 |
|||
if (empty($this->fk)) { |
|||
$this->fk = strtolower($this->name) . '_id'; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 查找单条记录 |
|||
* @access public |
|||
* @param mixed $data 主键值或者查询条件(闭包) |
|||
* @param string|array $with 关联预查询 |
|||
* @param bool $cache 是否缓存 |
|||
* @return \think\Model |
|||
*/ |
|||
public static function get($data = null, $with = [], $cache = false) |
|||
{ |
|||
$query = self::parseQuery($data, $with, $cache); |
|||
$query = self::attachQuery($query); |
|||
return $query->find($data); |
|||
} |
|||
|
|||
/** |
|||
* 附加查询表达式 |
|||
* @access protected |
|||
* @param \think\db\Query $query 查询对象 |
|||
* @return \think\db\Query |
|||
*/ |
|||
protected static function attachQuery($query) |
|||
{ |
|||
$class = new static(); |
|||
$master = $class->name; |
|||
$fields = self::getModelField($query, $master, '', $class->mapFields, $class->field); |
|||
$query->alias($master)->field($fields); |
|||
|
|||
foreach ($class->relationModel as $key => $model) { |
|||
$name = is_int($key) ? $model : $key; |
|||
$table = is_int($key) ? $query->getTable($name) : $model; |
|||
$query->join($table . ' ' . $name, $name . '.' . $class->fk . '=' . $master . '.' . $class->getPk()); |
|||
$fields = self::getModelField($query, $name, $table, $class->mapFields, $class->field); |
|||
$query->field($fields); |
|||
} |
|||
return $query; |
|||
} |
|||
|
|||
/** |
|||
* 获取关联模型的字段 并解决混淆 |
|||
* @access protected |
|||
* @param \think\db\Query $query 查询对象 |
|||
* @param string $name 模型名称 |
|||
* @param string $table 关联表名称 |
|||
* @param array $map 字段映射 |
|||
* @param array $fields 查询字段 |
|||
* @return array |
|||
*/ |
|||
protected static function getModelField($query, $name, $table = '', $map = [], $fields = []) |
|||
{ |
|||
// 获取模型的字段信息 |
|||
$fields = $fields ?: $query->getTableInfo($table, 'fields'); |
|||
$array = []; |
|||
foreach ($fields as $field) { |
|||
if ($key = array_search($name . '.' . $field, $map)) { |
|||
// 需要处理映射字段 |
|||
$array[] = $name . '.' . $field . ' AS ' . $key; |
|||
} else { |
|||
$array[] = $field; |
|||
} |
|||
} |
|||
return $array; |
|||
} |
|||
|
|||
/** |
|||
* 查找所有记录 |
|||
* @access public |
|||
* @param mixed $data 主键列表或者查询条件(闭包) |
|||
* @param array|string $with 关联预查询 |
|||
* @param bool $cache |
|||
* @return array|false|string |
|||
*/ |
|||
public static function all($data = null, $with = [], $cache = false) |
|||
{ |
|||
$query = self::parseQuery($data, $with, $cache); |
|||
$query = self::attachQuery($query); |
|||
return $query->select($data); |
|||
} |
|||
|
|||
/** |
|||
* 处理写入的模型数据 |
|||
* @access public |
|||
* @param string $model 模型名称 |
|||
* @param array $data 数据 |
|||
* @param bool $insert 是否新增 |
|||
* @return array |
|||
*/ |
|||
protected function parseData($model, $data, $insert = false) |
|||
{ |
|||
$item = []; |
|||
foreach ($data as $key => $val) { |
|||
if ($insert || in_array($key, $this->change) || $this->isPk($key)) { |
|||
if ($this->fk != $key && array_key_exists($key, $this->mapFields)) { |
|||
list($name, $key) = explode('.', $this->mapFields[$key]); |
|||
if ($model == $name) { |
|||
$item[$key] = $val; |
|||
} |
|||
} else { |
|||
$item[$key] = $val; |
|||
} |
|||
} |
|||
} |
|||
return $item; |
|||
} |
|||
|
|||
/** |
|||
* 保存模型数据 以及关联数据 |
|||
* @access public |
|||
* @param mixed $data 数据 |
|||
* @param array $where 更新条件 |
|||
* @param string $sequence 自增序列名 |
|||
* @return false|int |
|||
* @throws \Exception |
|||
*/ |
|||
public function save($data = [], $where = [], $sequence = null) |
|||
{ |
|||
if (!empty($data)) { |
|||
// 数据自动验证 |
|||
if (!$this->validateData($data)) { |
|||
return false; |
|||
} |
|||
// 数据对象赋值 |
|||
foreach ($data as $key => $value) { |
|||
$this->setAttr($key, $value, $data); |
|||
} |
|||
if (!empty($where)) { |
|||
$this->isUpdate = true; |
|||
} |
|||
} |
|||
|
|||
// 数据自动完成 |
|||
$this->autoCompleteData($this->auto); |
|||
|
|||
// 自动写入更新时间 |
|||
if ($this->autoWriteTimestamp && $this->updateTime && !isset($this->data[$this->updateTime])) { |
|||
$this->setAttr($this->updateTime, null); |
|||
} |
|||
|
|||
$db = $this->db(); |
|||
$db->startTrans(); |
|||
$pk = $this->getPk(); |
|||
try { |
|||
if ($this->isUpdate) { |
|||
// 自动写入 |
|||
$this->autoCompleteData($this->update); |
|||
|
|||
if (false === $this->trigger('before_update', $this)) { |
|||
return false; |
|||
} |
|||
|
|||
if (empty($where) && !empty($this->updateWhere)) { |
|||
$where = $this->updateWhere; |
|||
} |
|||
|
|||
// 处理模型数据 |
|||
$data = $this->parseData($this->name, $this->data); |
|||
if (is_string($pk) && isset($data[$pk])) { |
|||
if (!isset($where[$pk])) { |
|||
unset($where); |
|||
$where[$pk] = $data[$pk]; |
|||
} |
|||
unset($data[$pk]); |
|||
} |
|||
// 写入主表数据 |
|||
$result = $db->strict(false)->where($where)->update($data); |
|||
|
|||
// 写入附表数据 |
|||
foreach ($this->relationModel as $key => $model) { |
|||
$name = is_int($key) ? $model : $key; |
|||
$table = is_int($key) ? $db->getTable($model) : $model; |
|||
// 处理关联模型数据 |
|||
$data = $this->parseData($name, $this->data); |
|||
$query = new Query; |
|||
if ($query->table($table)->strict(false)->where($this->fk, $this->data[$this->getPk()])->update($data)) { |
|||
$result = 1; |
|||
} |
|||
} |
|||
// 清空change |
|||
$this->change = []; |
|||
// 新增回调 |
|||
$this->trigger('after_update', $this); |
|||
} else { |
|||
// 自动写入 |
|||
$this->autoCompleteData($this->insert); |
|||
|
|||
// 自动写入创建时间 |
|||
if ($this->autoWriteTimestamp && $this->createTime && !isset($this->data[$this->createTime])) { |
|||
$this->setAttr($this->createTime, null); |
|||
} |
|||
|
|||
if (false === $this->trigger('before_insert', $this)) { |
|||
return false; |
|||
} |
|||
|
|||
// 处理模型数据 |
|||
$data = $this->parseData($this->name, $this->data, true); |
|||
// 写入主表数据 |
|||
$result = $db->name($this->name)->strict(false)->insert($data); |
|||
if ($result) { |
|||
$insertId = $db->getLastInsID($sequence); |
|||
// 写入外键数据 |
|||
if ($insertId) { |
|||
if (is_string($pk)) { |
|||
$this->data[$pk] = $insertId; |
|||
if ($this->fk == $pk) { |
|||
$this->change[] = $pk; |
|||
} |
|||
} |
|||
$this->data[$this->fk] = $insertId; |
|||
} |
|||
|
|||
// 写入附表数据 |
|||
$source = $this->data; |
|||
if ($insertId && is_string($pk) && isset($source[$pk]) && $this->fk != $pk) { |
|||
unset($source[$pk]); |
|||
} |
|||
foreach ($this->relationModel as $key => $model) { |
|||
$name = is_int($key) ? $model : $key; |
|||
$table = is_int($key) ? $db->getTable($model) : $model; |
|||
// 处理关联模型数据 |
|||
$data = $this->parseData($name, $source, true); |
|||
$query = new Query; |
|||
$query->table($table)->strict(false)->insert($data); |
|||
} |
|||
} |
|||
// 标记为更新 |
|||
$this->isUpdate = true; |
|||
// 清空change |
|||
$this->change = []; |
|||
// 新增回调 |
|||
$this->trigger('after_insert', $this); |
|||
} |
|||
$db->commit(); |
|||
return $result; |
|||
} catch (\Exception $e) { |
|||
$db->rollback(); |
|||
throw $e; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 删除当前的记录 并删除关联数据 |
|||
* @access public |
|||
* @return int |
|||
* @throws \Exception |
|||
*/ |
|||
public function delete() |
|||
{ |
|||
if (false === $this->trigger('before_delete', $this)) { |
|||
return false; |
|||
} |
|||
|
|||
$db = $this->db(); |
|||
$db->startTrans(); |
|||
try { |
|||
$result = $db->delete($this->data); |
|||
if ($result) { |
|||
// 获取主键数据 |
|||
$pk = $this->data[$this->getPk()]; |
|||
|
|||
// 删除关联数据 |
|||
foreach ($this->relationModel as $key => $model) { |
|||
$table = is_int($key) ? $db->getTable($model) : $model; |
|||
$query = new Query; |
|||
$query->table($table)->where($this->fk, $pk)->delete(); |
|||
} |
|||
} |
|||
$this->trigger('after_delete', $this); |
|||
$db->commit(); |
|||
return $result; |
|||
} catch (\Exception $e) { |
|||
$db->rollback(); |
|||
throw $e; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: liu21st <liu21st@gmail.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\model; |
|||
|
|||
use think\Model; |
|||
|
|||
class Pivot extends Model |
|||
{ |
|||
|
|||
/** |
|||
* 构造函数 |
|||
* @access public |
|||
* @param array|object $data 数据 |
|||
* @param string $table 中间数据表名 |
|||
*/ |
|||
public function __construct($data = [], $table = '') |
|||
{ |
|||
if (is_object($data)) { |
|||
$this->data = get_object_vars($data); |
|||
} else { |
|||
$this->data = $data; |
|||
} |
|||
|
|||
$this->table = $table; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,119 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: liu21st <liu21st@gmail.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\model; |
|||
|
|||
use think\db\Query; |
|||
use think\Exception; |
|||
use think\Model; |
|||
|
|||
/** |
|||
* Class Relation |
|||
* @package think\model |
|||
* |
|||
* @mixin Query |
|||
*/ |
|||
abstract class Relation |
|||
{ |
|||
// 父模型对象 |
|||
protected $parent; |
|||
/** @var Model 当前关联的模型类 */ |
|||
protected $model; |
|||
/** @var Query 关联模型查询对象 */ |
|||
protected $query; |
|||
// 关联表外键 |
|||
protected $foreignKey; |
|||
// 关联表主键 |
|||
protected $localKey; |
|||
// 关联查询参数 |
|||
protected $option; |
|||
// 基础查询 |
|||
protected $baseQuery; |
|||
|
|||
/** |
|||
* 获取关联的所属模型 |
|||
* @access public |
|||
* @return Model |
|||
*/ |
|||
public function getParent() |
|||
{ |
|||
return $this->parent; |
|||
} |
|||
|
|||
/** |
|||
* 获取当前的关联模型类 |
|||
* @access public |
|||
* @return string |
|||
*/ |
|||
public function getModel() |
|||
{ |
|||
return $this->model; |
|||
} |
|||
|
|||
/** |
|||
* 获取关联的查询对象 |
|||
* @access public |
|||
* @return Query |
|||
*/ |
|||
public function getQuery() |
|||
{ |
|||
return $this->query; |
|||
} |
|||
|
|||
/** |
|||
* 封装关联数据集 |
|||
* @access public |
|||
* @param array $resultSet 数据集 |
|||
* @return mixed |
|||
*/ |
|||
protected function resultSetBuild($resultSet) |
|||
{ |
|||
return (new $this->model)->toCollection($resultSet); |
|||
} |
|||
|
|||
/** |
|||
* 移除关联查询参数 |
|||
* @access public |
|||
* @return $this |
|||
*/ |
|||
public function removeOption() |
|||
{ |
|||
$this->query->removeOption(); |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 执行基础查询(进执行一次) |
|||
* @access protected |
|||
* @return void |
|||
*/ |
|||
abstract protected function baseQuery(); |
|||
|
|||
public function __call($method, $args) |
|||
{ |
|||
if ($this->query) { |
|||
// 执行基础查询 |
|||
$this->baseQuery(); |
|||
|
|||
$result = call_user_func_array([$this->query, $method], $args); |
|||
if ($result instanceof Query) { |
|||
$this->option = $result->getOptions(); |
|||
return $this; |
|||
} else { |
|||
$this->option = []; |
|||
$this->baseQuery = false; |
|||
return $result; |
|||
} |
|||
} else { |
|||
throw new Exception('method not exists:' . __CLASS__ . '->' . $method); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,171 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: liu21st <liu21st@gmail.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\model\relation; |
|||
|
|||
use think\Loader; |
|||
use think\Model; |
|||
|
|||
class BelongsTo extends OneToOne |
|||
{ |
|||
/** |
|||
* 构造函数 |
|||
* @access public |
|||
* @param Model $parent 上级模型对象 |
|||
* @param string $model 模型名 |
|||
* @param string $foreignKey 关联外键 |
|||
* @param string $localKey 关联主键 |
|||
* @param string $joinType JOIN类型 |
|||
*/ |
|||
public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER') |
|||
{ |
|||
$this->parent = $parent; |
|||
$this->model = $model; |
|||
$this->foreignKey = $foreignKey; |
|||
$this->localKey = $localKey; |
|||
$this->joinType = $joinType; |
|||
$this->query = (new $model)->db(); |
|||
} |
|||
|
|||
/** |
|||
* 延迟获取关联数据 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包查询条件 |
|||
* @access public |
|||
* @return array|false|\PDOStatement|string|Model |
|||
*/ |
|||
public function getRelation($subRelation = '', $closure = null) |
|||
{ |
|||
$foreignKey = $this->foreignKey; |
|||
if ($closure) { |
|||
call_user_func_array($closure, [ & $this->query]); |
|||
} |
|||
return $this->query->where($this->localKey, $this->parent->$foreignKey)->relation($subRelation)->find(); |
|||
} |
|||
|
|||
/** |
|||
* 根据关联条件查询当前模型 |
|||
* @access public |
|||
* @param string $operator 比较操作符 |
|||
* @param integer $count 个数 |
|||
* @param string $id 关联表的统计字段 |
|||
* @param string $joinType JOIN类型 |
|||
* @return Query |
|||
*/ |
|||
public function has($operator = '>=', $count = 1, $id = '*') |
|||
{ |
|||
return $this->parent; |
|||
} |
|||
|
|||
/** |
|||
* 根据关联条件查询当前模型 |
|||
* @access public |
|||
* @param mixed $where 查询条件(数组或者闭包) |
|||
* @return Query |
|||
*/ |
|||
public function hasWhere($where = []) |
|||
{ |
|||
$table = $this->query->getTable(); |
|||
$model = basename(str_replace('\\', '/', get_class($this->parent))); |
|||
$relation = basename(str_replace('\\', '/', $this->model)); |
|||
if (is_array($where)) { |
|||
foreach ($where as $key => $val) { |
|||
if (false === strpos($key, '.')) { |
|||
$where[$relation . '.' . $key] = $val; |
|||
unset($where[$key]); |
|||
} |
|||
} |
|||
} |
|||
return $this->parent->db()->alias($model) |
|||
->field($model . '.*') |
|||
->join($table . ' ' . $relation, $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $this->joinType) |
|||
->where($where); |
|||
} |
|||
|
|||
/** |
|||
* 预载入关联查询(数据集) |
|||
* @access public |
|||
* @param array $resultSet 数据集 |
|||
* @param string $relation 当前关联名 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包 |
|||
* @return void |
|||
*/ |
|||
protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) |
|||
{ |
|||
$localKey = $this->localKey; |
|||
$foreignKey = $this->foreignKey; |
|||
|
|||
$range = []; |
|||
foreach ($resultSet as $result) { |
|||
// 获取关联外键列表 |
|||
if (isset($result->$foreignKey)) { |
|||
$range[] = $result->$foreignKey; |
|||
} |
|||
} |
|||
|
|||
if (!empty($range)) { |
|||
$data = $this->eagerlyWhere($this, [ |
|||
$localKey => [ |
|||
'in', |
|||
$range, |
|||
], |
|||
], $localKey, $relation, $subRelation, $closure); |
|||
// 关联属性名 |
|||
$attr = Loader::parseName($relation); |
|||
// 关联数据封装 |
|||
foreach ($resultSet as $result) { |
|||
// 关联模型 |
|||
if (!isset($data[$result->$foreignKey])) { |
|||
$relationModel = null; |
|||
} else { |
|||
$relationModel = $data[$result->$foreignKey]; |
|||
} |
|||
|
|||
if ($relationModel && !empty($this->bindAttr)) { |
|||
// 绑定关联属性 |
|||
$this->bindAttr($relationModel, $result, $this->bindAttr); |
|||
} |
|||
// 设置关联属性 |
|||
$result->setAttr($attr, $relationModel); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 预载入关联查询(数据) |
|||
* @access public |
|||
* @param Model $result 数据对象 |
|||
* @param string $relation 当前关联名 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包 |
|||
* @return void |
|||
*/ |
|||
protected function eagerlyOne(&$result, $relation, $subRelation, $closure) |
|||
{ |
|||
$localKey = $this->localKey; |
|||
$foreignKey = $this->foreignKey; |
|||
$data = $this->eagerlyWhere($this, [$localKey => $result->$foreignKey], $localKey, $relation, $subRelation, $closure); |
|||
// 关联模型 |
|||
if (!isset($data[$result->$foreignKey])) { |
|||
$relationModel = null; |
|||
} else { |
|||
$relationModel = $data[$result->$foreignKey]; |
|||
} |
|||
if ($relationModel && !empty($this->bindAttr)) { |
|||
// 绑定关联属性 |
|||
$this->bindAttr($relationModel, $result, $this->bindAttr); |
|||
} |
|||
// 设置关联属性 |
|||
$result->setAttr(Loader::parseName($relation), $relationModel); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,390 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: liu21st <liu21st@gmail.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\model\relation; |
|||
|
|||
use think\db\Query; |
|||
use think\Exception; |
|||
use think\Loader; |
|||
use think\Model; |
|||
use think\model\Pivot; |
|||
use think\model\Relation; |
|||
|
|||
class BelongsToMany extends Relation |
|||
{ |
|||
// 中间表模型 |
|||
protected $middle; |
|||
|
|||
/** |
|||
* 构造函数 |
|||
* @access public |
|||
* @param Model $parent 上级模型对象 |
|||
* @param string $model 模型名 |
|||
* @param string $table 中间表名 |
|||
* @param string $foreignKey 关联模型外键 |
|||
* @param string $localKey 当前模型关联键 |
|||
*/ |
|||
public function __construct(Model $parent, $model, $table, $foreignKey, $localKey) |
|||
{ |
|||
$this->parent = $parent; |
|||
$this->model = $model; |
|||
$this->foreignKey = $foreignKey; |
|||
$this->localKey = $localKey; |
|||
$this->middle = $table; |
|||
$this->query = (new $model)->db(); |
|||
} |
|||
|
|||
/** |
|||
* 延迟获取关联数据 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包查询条件 |
|||
* @return false|\PDOStatement|string|\think\Collection |
|||
*/ |
|||
public function getRelation($subRelation = '', $closure = null) |
|||
{ |
|||
$foreignKey = $this->foreignKey; |
|||
$localKey = $this->localKey; |
|||
$middle = $this->middle; |
|||
if ($closure) { |
|||
call_user_func_array($closure, [ & $this->query]); |
|||
} |
|||
// 关联查询 |
|||
$pk = $this->parent->getPk(); |
|||
$condition['pivot.' . $localKey] = $this->parent->$pk; |
|||
$result = $this->belongsToManyQuery($middle, $foreignKey, $localKey, $condition)->relation($subRelation)->select(); |
|||
foreach ($result as $set) { |
|||
$pivot = []; |
|||
foreach ($set->getData() as $key => $val) { |
|||
if (strpos($key, '__')) { |
|||
list($name, $attr) = explode('__', $key, 2); |
|||
if ('pivot' == $name) { |
|||
$pivot[$attr] = $val; |
|||
unset($set->$key); |
|||
} |
|||
} |
|||
} |
|||
$set->pivot = new Pivot($pivot, $this->middle); |
|||
} |
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* 根据关联条件查询当前模型 |
|||
* @access public |
|||
* @param string $operator 比较操作符 |
|||
* @param integer $count 个数 |
|||
* @param string $id 关联表的统计字段 |
|||
* @param string $joinType JOIN类型 |
|||
* @return Query |
|||
*/ |
|||
public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') |
|||
{ |
|||
return $this->parent; |
|||
} |
|||
|
|||
/** |
|||
* 根据关联条件查询当前模型 |
|||
* @access public |
|||
* @param mixed $where 查询条件(数组或者闭包) |
|||
* @return Query |
|||
*/ |
|||
public function hasWhere($where = []) |
|||
{ |
|||
throw new Exception('relation not support: hasWhere'); |
|||
} |
|||
|
|||
/** |
|||
* 预载入关联查询(数据集) |
|||
* @access public |
|||
* @param array $resultSet 数据集 |
|||
* @param string $relation 当前关联名 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包 |
|||
* @return void |
|||
*/ |
|||
public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) |
|||
{ |
|||
$localKey = $this->localKey; |
|||
$foreignKey = $this->foreignKey; |
|||
|
|||
$pk = $resultSet[0]->getPk(); |
|||
$range = []; |
|||
foreach ($resultSet as $result) { |
|||
// 获取关联外键列表 |
|||
if (isset($result->$pk)) { |
|||
$range[] = $result->$pk; |
|||
} |
|||
} |
|||
|
|||
if (!empty($range)) { |
|||
// 查询关联数据 |
|||
$data = $this->eagerlyManyToMany([ |
|||
'pivot.' . $localKey => [ |
|||
'in', |
|||
$range, |
|||
], |
|||
], $relation, $subRelation); |
|||
// 关联属性名 |
|||
$attr = Loader::parseName($relation); |
|||
// 关联数据封装 |
|||
foreach ($resultSet as $result) { |
|||
if (!isset($data[$result->$pk])) { |
|||
$data[$result->$pk] = []; |
|||
} |
|||
|
|||
$result->setAttr($attr, $this->resultSetBuild($data[$result->$pk])); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 预载入关联查询(单个数据) |
|||
* @access public |
|||
* @param Model $result 数据对象 |
|||
* @param string $relation 当前关联名 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包 |
|||
* @return void |
|||
*/ |
|||
public function eagerlyResult(&$result, $relation, $subRelation, $closure) |
|||
{ |
|||
$pk = $result->getPk(); |
|||
if (isset($result->$pk)) { |
|||
$pk = $result->$pk; |
|||
// 查询管理数据 |
|||
$data = $this->eagerlyManyToMany(['pivot.' . $this->localKey => $pk], $relation, $subRelation); |
|||
|
|||
// 关联数据封装 |
|||
if (!isset($data[$pk])) { |
|||
$data[$pk] = []; |
|||
} |
|||
$result->setAttr(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 关联统计 |
|||
* @access public |
|||
* @param Model $result 数据对象 |
|||
* @param \Closure $closure 闭包 |
|||
* @return integer |
|||
*/ |
|||
public function relationCount($result, $closure) |
|||
{ |
|||
$pk = $result->getPk(); |
|||
$count = 0; |
|||
if (isset($result->$pk)) { |
|||
$pk = $result->$pk; |
|||
$count = $this->belongsToManyQuery($this->middle, $this->foreignKey, $this->localKey, ['pivot.' . $this->localKey => $pk])->count(); |
|||
} |
|||
return $count; |
|||
} |
|||
|
|||
/** |
|||
* 获取关联统计子查询 |
|||
* @access public |
|||
* @param \Closure $closure 闭包 |
|||
* @return string |
|||
*/ |
|||
public function getRelationCountQuery($closure) |
|||
{ |
|||
return $this->belongsToManyQuery($this->middle, $this->foreignKey, $this->localKey, [ |
|||
'pivot.' . $this->localKey => [ |
|||
'exp', |
|||
'=' . $this->parent->getTable() . '.' . $this->parent->getPk(), |
|||
], |
|||
])->fetchSql()->count(); |
|||
} |
|||
|
|||
/** |
|||
* 多对多 关联模型预查询 |
|||
* @access public |
|||
* @param array $where 关联预查询条件 |
|||
* @param string $relation 关联名 |
|||
* @param string $subRelation 子关联 |
|||
* @return array |
|||
*/ |
|||
protected function eagerlyManyToMany($where, $relation, $subRelation = '') |
|||
{ |
|||
// 预载入关联查询 支持嵌套预载入 |
|||
$list = $this->belongsToManyQuery($this->middle, $this->foreignKey, $this->localKey, $where)->with($subRelation)->select(); |
|||
|
|||
// 组装模型数据 |
|||
$data = []; |
|||
foreach ($list as $set) { |
|||
$pivot = []; |
|||
foreach ($set->getData() as $key => $val) { |
|||
if (strpos($key, '__')) { |
|||
list($name, $attr) = explode('__', $key, 2); |
|||
if ('pivot' == $name) { |
|||
$pivot[$attr] = $val; |
|||
unset($set->$key); |
|||
} |
|||
} |
|||
} |
|||
$set->pivot = new Pivot($pivot, $this->middle); |
|||
$data[$pivot[$this->localKey]][] = $set; |
|||
} |
|||
return $data; |
|||
} |
|||
|
|||
/** |
|||
* BELONGS TO MANY 关联查询 |
|||
* @access public |
|||
* @param string $table 中间表名 |
|||
* @param string $foreignKey 关联模型关联键 |
|||
* @param string $localKey 当前模型关联键 |
|||
* @param array $condition 关联查询条件 |
|||
* @return Query |
|||
*/ |
|||
protected function belongsToManyQuery($table, $foreignKey, $localKey, $condition = []) |
|||
{ |
|||
// 关联查询封装 |
|||
$tableName = $this->query->getTable(); |
|||
$relationFk = $this->query->getPk(); |
|||
return $this->query->field($tableName . '.*') |
|||
->field(true, false, $table, 'pivot', 'pivot__') |
|||
->join($table . ' pivot', 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) |
|||
->where($condition); |
|||
} |
|||
|
|||
/** |
|||
* 保存(新增)当前关联数据对象 |
|||
* @access public |
|||
* @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 |
|||
* @param array $pivot 中间表额外数据 |
|||
* @return integer |
|||
*/ |
|||
public function save($data, array $pivot = []) |
|||
{ |
|||
// 保存关联表/中间表数据 |
|||
return $this->attach($data, $pivot); |
|||
} |
|||
|
|||
/** |
|||
* 批量保存当前关联数据对象 |
|||
* @access public |
|||
* @param array $dataSet 数据集 |
|||
* @param array $pivot 中间表额外数据 |
|||
* @param bool $samePivot 额外数据是否相同 |
|||
* @return integer |
|||
*/ |
|||
public function saveAll(array $dataSet, array $pivot = [], $samePivot = false) |
|||
{ |
|||
$result = false; |
|||
foreach ($dataSet as $key => $data) { |
|||
if (!$samePivot) { |
|||
$pivotData = isset($pivot[$key]) ? $pivot[$key] : []; |
|||
} else { |
|||
$pivotData = $pivot; |
|||
} |
|||
$result = $this->attach($data, $pivotData); |
|||
} |
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* 附加关联的一个中间表数据 |
|||
* @access public |
|||
* @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 |
|||
* @param array $pivot 中间表额外数据 |
|||
* @return array|Pivot |
|||
* @throws Exception |
|||
*/ |
|||
public function attach($data, $pivot = []) |
|||
{ |
|||
if (is_array($data)) { |
|||
if (key($data) === 0) { |
|||
$id = $data; |
|||
} else { |
|||
// 保存关联表数据 |
|||
$model = new $this->model; |
|||
$model->save($data); |
|||
$id = $model->getLastInsID(); |
|||
} |
|||
} elseif (is_numeric($data) || is_string($data)) { |
|||
// 根据关联表主键直接写入中间表 |
|||
$id = $data; |
|||
} elseif ($data instanceof Model) { |
|||
// 根据关联表主键直接写入中间表 |
|||
$relationFk = $data->getPk(); |
|||
$id = $data->$relationFk; |
|||
} |
|||
|
|||
if ($id) { |
|||
// 保存中间表数据 |
|||
$pk = $this->parent->getPk(); |
|||
$pivot[$this->localKey] = $this->parent->$pk; |
|||
$ids = (array) $id; |
|||
foreach ($ids as $id) { |
|||
$pivot[$this->foreignKey] = $id; |
|||
$this->query->table($this->middle)->insert($pivot, true); |
|||
$result[] = new Pivot($pivot, $this->middle); |
|||
} |
|||
if (count($result) == 1) { |
|||
// 返回中间表模型对象 |
|||
$result = $result[0]; |
|||
} |
|||
return $result; |
|||
} else { |
|||
throw new Exception('miss relation data'); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 解除关联的一个中间表数据 |
|||
* @access public |
|||
* @param integer|array $data 数据 可以使用关联对象的主键 |
|||
* @param bool $relationDel 是否同时删除关联表数据 |
|||
* @return integer |
|||
*/ |
|||
public function detach($data = null, $relationDel = false) |
|||
{ |
|||
if (is_array($data)) { |
|||
$id = $data; |
|||
} elseif (is_numeric($data) || is_string($data)) { |
|||
// 根据关联表主键直接写入中间表 |
|||
$id = $data; |
|||
} elseif ($data instanceof Model) { |
|||
// 根据关联表主键直接写入中间表 |
|||
$relationFk = $data->getPk(); |
|||
$id = $data->$relationFk; |
|||
} |
|||
// 删除中间表数据 |
|||
$pk = $this->parent->getPk(); |
|||
$pivot[$this->localKey] = $this->parent->$pk; |
|||
if (isset($id)) { |
|||
$pivot[$this->foreignKey] = is_array($id) ? ['in', $id] : $id; |
|||
} |
|||
$this->query->table($this->middle)->where($pivot)->delete(); |
|||
|
|||
// 删除关联表数据 |
|||
if (isset($id) && $relationDel) { |
|||
$model = $this->model; |
|||
$model::destroy($id); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 执行基础查询(进执行一次) |
|||
* @access protected |
|||
* @return void |
|||
*/ |
|||
protected function baseQuery() |
|||
{ |
|||
if (empty($this->baseQuery)) { |
|||
$pk = $this->parent->getPk(); |
|||
$this->query->join($this->middle . ' pivot', 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk())->where('pivot.' . $this->localKey, $this->parent->$pk); |
|||
$this->baseQuery = true; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,273 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: liu21st <liu21st@gmail.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\model\relation; |
|||
|
|||
use think\Db; |
|||
use think\db\Query; |
|||
use think\Loader; |
|||
use think\Model; |
|||
use think\model\Relation; |
|||
|
|||
class HasMany extends Relation |
|||
{ |
|||
/** |
|||
* 构造函数 |
|||
* @access public |
|||
* @param Model $parent 上级模型对象 |
|||
* @param string $model 模型名 |
|||
* @param string $foreignKey 关联外键 |
|||
* @param string $localKey 关联主键 |
|||
*/ |
|||
public function __construct(Model $parent, $model, $foreignKey, $localKey) |
|||
{ |
|||
$this->parent = $parent; |
|||
$this->model = $model; |
|||
$this->foreignKey = $foreignKey; |
|||
$this->localKey = $localKey; |
|||
$this->query = (new $model)->db(); |
|||
} |
|||
|
|||
/** |
|||
* 延迟获取关联数据 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包查询条件 |
|||
* @return false|\PDOStatement|string|\think\Collection |
|||
*/ |
|||
public function getRelation($subRelation = '', $closure = null) |
|||
{ |
|||
if ($closure) { |
|||
call_user_func_array($closure, [ & $this->query]); |
|||
} |
|||
return $this->relation($subRelation)->select(); |
|||
} |
|||
|
|||
/** |
|||
* 预载入关联查询 |
|||
* @access public |
|||
* @param array $resultSet 数据集 |
|||
* @param string $relation 当前关联名 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包 |
|||
* @return void |
|||
*/ |
|||
public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) |
|||
{ |
|||
$localKey = $this->localKey; |
|||
$range = []; |
|||
foreach ($resultSet as $result) { |
|||
// 获取关联外键列表 |
|||
if (isset($result->$localKey)) { |
|||
$range[] = $result->$localKey; |
|||
} |
|||
} |
|||
|
|||
if (!empty($range)) { |
|||
$data = $this->eagerlyOneToMany($this, [ |
|||
$this->foreignKey => [ |
|||
'in', |
|||
$range, |
|||
], |
|||
], $relation, $subRelation, $closure); |
|||
// 关联属性名 |
|||
$attr = Loader::parseName($relation); |
|||
// 关联数据封装 |
|||
foreach ($resultSet as $result) { |
|||
if (!isset($data[$result->$localKey])) { |
|||
$data[$result->$localKey] = []; |
|||
} |
|||
$result->setAttr($attr, $this->resultSetBuild($data[$result->$localKey])); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 预载入关联查询 |
|||
* @access public |
|||
* @param Model $result 数据对象 |
|||
* @param string $relation 当前关联名 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包 |
|||
* @return void |
|||
*/ |
|||
public function eagerlyResult(&$result, $relation, $subRelation, $closure) |
|||
{ |
|||
$localKey = $this->localKey; |
|||
|
|||
if (isset($result->$localKey)) { |
|||
$data = $this->eagerlyOneToMany($this, [$this->foreignKey => $result->$localKey], $relation, $subRelation, $closure); |
|||
// 关联数据封装 |
|||
if (!isset($data[$result->$localKey])) { |
|||
$data[$result->$localKey] = []; |
|||
} |
|||
$result->setAttr(Loader::parseName($relation), $this->resultSetBuild($data[$result->$localKey])); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 关联统计 |
|||
* @access public |
|||
* @param Model $result 数据对象 |
|||
* @param \Closure $closure 闭包 |
|||
* @return integer |
|||
*/ |
|||
public function relationCount($result, $closure) |
|||
{ |
|||
$localKey = $this->localKey; |
|||
$count = 0; |
|||
if (isset($result->$localKey)) { |
|||
if ($closure) { |
|||
call_user_func_array($closure, [ & $this->query]); |
|||
} |
|||
$count = $this->query->where([$this->foreignKey => $result->$localKey])->count(); |
|||
} |
|||
return $count; |
|||
} |
|||
|
|||
/** |
|||
* 创建关联统计子查询 |
|||
* @access public |
|||
* @param \Closure $closure 闭包 |
|||
* @return string |
|||
*/ |
|||
public function getRelationCountQuery($closure) |
|||
{ |
|||
if ($closure) { |
|||
call_user_func_array($closure, [ & $this->query]); |
|||
} |
|||
|
|||
return $this->query->where([ |
|||
$this->foreignKey => [ |
|||
'exp', |
|||
'=' . $this->parent->getTable() . '.' . $this->parent->getPk(), |
|||
], |
|||
])->fetchSql()->count(); |
|||
} |
|||
|
|||
/** |
|||
* 一对多 关联模型预查询 |
|||
* @access public |
|||
* @param object $model 关联模型对象 |
|||
* @param array $where 关联预查询条件 |
|||
* @param string $relation 关联名 |
|||
* @param string $subRelation 子关联 |
|||
* @param bool $closure |
|||
* @return array |
|||
*/ |
|||
protected function eagerlyOneToMany($model, $where, $relation, $subRelation = '', $closure = false) |
|||
{ |
|||
$foreignKey = $this->foreignKey; |
|||
// 预载入关联查询 支持嵌套预载入 |
|||
if ($closure) { |
|||
call_user_func_array($closure, [ & $model]); |
|||
} |
|||
$list = $model->where($where)->with($subRelation)->select(); |
|||
|
|||
// 组装模型数据 |
|||
$data = []; |
|||
foreach ($list as $set) { |
|||
$data[$set->$foreignKey][] = $set; |
|||
} |
|||
return $data; |
|||
} |
|||
|
|||
/** |
|||
* 保存(新增)当前关联数据对象 |
|||
* @access public |
|||
* @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 |
|||
* @return integer |
|||
*/ |
|||
public function save($data) |
|||
{ |
|||
if ($data instanceof Model) { |
|||
$data = $data->getData(); |
|||
} |
|||
// 保存关联表数据 |
|||
$data[$this->foreignKey] = $this->parent->{$this->localKey}; |
|||
$model = new $this->model; |
|||
return $model->save($data); |
|||
} |
|||
|
|||
/** |
|||
* 批量保存当前关联数据对象 |
|||
* @access public |
|||
* @param array $dataSet 数据集 |
|||
* @return integer |
|||
*/ |
|||
public function saveAll(array $dataSet) |
|||
{ |
|||
$result = false; |
|||
foreach ($dataSet as $key => $data) { |
|||
$result = $this->save($data); |
|||
} |
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* 根据关联条件查询当前模型 |
|||
* @access public |
|||
* @param string $operator 比较操作符 |
|||
* @param integer $count 个数 |
|||
* @param string $id 关联表的统计字段 |
|||
* @param string $joinType JOIN类型 |
|||
* @return Query |
|||
*/ |
|||
public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') |
|||
{ |
|||
$table = $this->query->getTable(); |
|||
return $this->parent->db()->alias('a') |
|||
->join($table . ' b', 'a.' . $this->localKey . '=b.' . $this->foreignKey, $joinType) |
|||
->group('b.' . $this->foreignKey) |
|||
->having('count(' . $id . ')' . $operator . $count); |
|||
} |
|||
|
|||
/** |
|||
* 根据关联条件查询当前模型 |
|||
* @access public |
|||
* @param mixed $where 查询条件(数组或者闭包) |
|||
* @return Query |
|||
*/ |
|||
public function hasWhere($where = []) |
|||
{ |
|||
$table = $this->query->getTable(); |
|||
$model = basename(str_replace('\\', '/', get_class($this->parent))); |
|||
$relation = basename(str_replace('\\', '/', $this->model)); |
|||
if (is_array($where)) { |
|||
foreach ($where as $key => $val) { |
|||
if (false === strpos($key, '.')) { |
|||
$where[$relation . '.' . $key] = $val; |
|||
unset($where[$key]); |
|||
} |
|||
} |
|||
} |
|||
return $this->parent->db()->alias($model) |
|||
->field($model . '.*') |
|||
->join($table . ' ' . $relation, $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey) |
|||
->where($where); |
|||
} |
|||
|
|||
/** |
|||
* 执行基础查询(进执行一次) |
|||
* @access protected |
|||
* @return void |
|||
*/ |
|||
protected function baseQuery() |
|||
{ |
|||
if (empty($this->baseQuery)) { |
|||
if (isset($this->parent->{$this->localKey})) { |
|||
// 关联查询带入关联条件 |
|||
$this->query->where($this->foreignKey, $this->parent->{$this->localKey}); |
|||
} |
|||
$this->baseQuery = true; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,150 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: liu21st <liu21st@gmail.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\model\relation; |
|||
|
|||
use think\Db; |
|||
use think\db\Query; |
|||
use think\Exception; |
|||
use think\Loader; |
|||
use think\Model; |
|||
use think\model\Relation; |
|||
|
|||
class HasManyThrough extends Relation |
|||
{ |
|||
// 中间关联表外键 |
|||
protected $throughKey; |
|||
// 中间表模型 |
|||
protected $through; |
|||
|
|||
/** |
|||
* 构造函数 |
|||
* @access public |
|||
* @param Model $parent 上级模型对象 |
|||
* @param string $model 模型名 |
|||
* @param string $through 中间模型名 |
|||
* @param string $foreignKey 关联外键 |
|||
* @param string $throughKey 关联外键 |
|||
* @param string $localKey 关联主键 |
|||
*/ |
|||
public function __construct(Model $parent, $model, $through, $foreignKey, $throughKey, $localKey) |
|||
{ |
|||
$this->parent = $parent; |
|||
$this->model = $model; |
|||
$this->through = $through; |
|||
$this->foreignKey = $foreignKey; |
|||
$this->throughKey = $throughKey; |
|||
$this->localKey = $localKey; |
|||
$this->query = (new $model)->db(); |
|||
} |
|||
|
|||
/** |
|||
* 延迟获取关联数据 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包查询条件 |
|||
* @return false|\PDOStatement|string|\think\Collection |
|||
*/ |
|||
public function getRelation($subRelation = '', $closure = null) |
|||
{ |
|||
if ($closure) { |
|||
call_user_func_array($closure, [ & $this->query]); |
|||
} |
|||
return $this->relation($subRelation)->select(); |
|||
} |
|||
|
|||
/** |
|||
* 根据关联条件查询当前模型 |
|||
* @access public |
|||
* @param string $operator 比较操作符 |
|||
* @param integer $count 个数 |
|||
* @param string $id 关联表的统计字段 |
|||
* @param string $joinType JOIN类型 |
|||
* @return Query |
|||
*/ |
|||
public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') |
|||
{ |
|||
return $this->parent; |
|||
} |
|||
|
|||
/** |
|||
* 根据关联条件查询当前模型 |
|||
* @access public |
|||
* @param mixed $where 查询条件(数组或者闭包) |
|||
* @return Query |
|||
*/ |
|||
public function hasWhere($where = []) |
|||
{ |
|||
throw new Exception('relation not support: hasWhere'); |
|||
} |
|||
|
|||
/** |
|||
* 预载入关联查询 |
|||
* @access public |
|||
* @param array $resultSet 数据集 |
|||
* @param string $relation 当前关联名 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包 |
|||
* @param string $class 数据集对象名 为空表示数组 |
|||
* @return void |
|||
*/ |
|||
public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $class) |
|||
{ |
|||
} |
|||
|
|||
/** |
|||
* 预载入关联查询 返回模型对象 |
|||
* @access public |
|||
* @param Model $result 数据对象 |
|||
* @param string $relation 当前关联名 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包 |
|||
* @param string $class 数据集对象名 为空表示数组 |
|||
* @return void |
|||
*/ |
|||
public function eagerlyResult(&$result, $relation, $subRelation, $closure, $class) |
|||
{ |
|||
} |
|||
|
|||
/** |
|||
* 关联统计 |
|||
* @access public |
|||
* @param Model $result 数据对象 |
|||
* @param \Closure $closure 闭包 |
|||
* @return integer |
|||
*/ |
|||
public function relationCount($result, $closure) |
|||
{ |
|||
} |
|||
|
|||
/** |
|||
* 执行基础查询(进执行一次) |
|||
* @access protected |
|||
* @return void |
|||
*/ |
|||
protected function baseQuery() |
|||
{ |
|||
if (empty($this->baseQuery)) { |
|||
$through = $this->through; |
|||
$model = $this->model; |
|||
$alias = Loader::parseName(basename(str_replace('\\', '/', $model))); |
|||
$throughTable = $through::getTable(); |
|||
$pk = (new $this->model)->getPk(); |
|||
$throughKey = $this->throughKey; |
|||
$modelTable = $this->parent->getTable(); |
|||
$this->query->field($alias . '.*')->alias($alias) |
|||
->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) |
|||
->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) |
|||
->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey}); |
|||
$this->baseQuery = true; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,175 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: liu21st <liu21st@gmail.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\model\relation; |
|||
|
|||
use think\db\Query; |
|||
use think\Loader; |
|||
use think\Model; |
|||
|
|||
class HasOne extends OneToOne |
|||
{ |
|||
/** |
|||
* 构造函数 |
|||
* @access public |
|||
* @param Model $parent 上级模型对象 |
|||
* @param string $model 模型名 |
|||
* @param string $foreignKey 关联外键 |
|||
* @param string $localKey 关联主键 |
|||
* @param string $joinType JOIN类型 |
|||
*/ |
|||
public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER') |
|||
{ |
|||
$this->parent = $parent; |
|||
$this->model = $model; |
|||
$this->foreignKey = $foreignKey; |
|||
$this->localKey = $localKey; |
|||
$this->joinType = $joinType; |
|||
$this->query = (new $model)->db(); |
|||
} |
|||
|
|||
/** |
|||
* 延迟获取关联数据 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包查询条件 |
|||
* @return array|false|\PDOStatement|string|Model |
|||
*/ |
|||
public function getRelation($subRelation = '', $closure = null) |
|||
{ |
|||
// 执行关联定义方法 |
|||
$localKey = $this->localKey; |
|||
if ($closure) { |
|||
call_user_func_array($closure, [ & $this->query]); |
|||
} |
|||
// 判断关联类型执行查询 |
|||
return $this->query->where($this->foreignKey, $this->parent->$localKey)->relation($subRelation)->find(); |
|||
} |
|||
|
|||
/** |
|||
* 根据关联条件查询当前模型 |
|||
* @access public |
|||
* @return Query |
|||
*/ |
|||
public function has() |
|||
{ |
|||
$table = $this->query->getTable(); |
|||
$localKey = $this->localKey; |
|||
$foreignKey = $this->foreignKey; |
|||
return $this->parent->db()->alias('a') |
|||
->whereExists(function ($query) use ($table, $localKey, $foreignKey) { |
|||
$query->table([$table => 'b'])->field('b.' . $foreignKey)->whereExp('a.' . $localKey, '=b.' . $foreignKey); |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 根据关联条件查询当前模型 |
|||
* @access public |
|||
* @param mixed $where 查询条件(数组或者闭包) |
|||
* @return Query |
|||
*/ |
|||
public function hasWhere($where = []) |
|||
{ |
|||
$table = $this->query->getTable(); |
|||
$model = basename(str_replace('\\', '/', get_class($this->parent))); |
|||
$relation = basename(str_replace('\\', '/', $this->model)); |
|||
if (is_array($where)) { |
|||
foreach ($where as $key => $val) { |
|||
if (false === strpos($key, '.')) { |
|||
$where[$relation . '.' . $key] = $val; |
|||
unset($where[$key]); |
|||
} |
|||
} |
|||
} |
|||
return $this->parent->db()->alias($model) |
|||
->field($model . '.*') |
|||
->join($table . ' ' . $relation, $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $this->joinType) |
|||
->where($where); |
|||
} |
|||
|
|||
/** |
|||
* 预载入关联查询(数据集) |
|||
* @access public |
|||
* @param array $resultSet 数据集 |
|||
* @param string $relation 当前关联名 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包 |
|||
* @return void |
|||
*/ |
|||
protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) |
|||
{ |
|||
$localKey = $this->localKey; |
|||
$foreignKey = $this->foreignKey; |
|||
|
|||
$range = []; |
|||
foreach ($resultSet as $result) { |
|||
// 获取关联外键列表 |
|||
if (isset($result->$localKey)) { |
|||
$range[] = $result->$localKey; |
|||
} |
|||
} |
|||
|
|||
if (!empty($range)) { |
|||
$data = $this->eagerlyWhere($this, [ |
|||
$foreignKey => [ |
|||
'in', |
|||
$range, |
|||
], |
|||
], $foreignKey, $relation, $subRelation, $closure); |
|||
// 关联属性名 |
|||
$attr = Loader::parseName($relation); |
|||
// 关联数据封装 |
|||
foreach ($resultSet as $result) { |
|||
// 关联模型 |
|||
if (!isset($data[$result->$localKey])) { |
|||
$relationModel = null; |
|||
} else { |
|||
$relationModel = $data[$result->$localKey]; |
|||
} |
|||
if ($relationModel && !empty($this->bindAttr)) { |
|||
// 绑定关联属性 |
|||
$this->bindAttr($relationModel, $result, $this->bindAttr); |
|||
} |
|||
// 设置关联属性 |
|||
$result->setAttr($attr, $relationModel); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 预载入关联查询(数据) |
|||
* @access public |
|||
* @param Model $result 数据对象 |
|||
* @param string $relation 当前关联名 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包 |
|||
* @return void |
|||
*/ |
|||
protected function eagerlyOne(&$result, $relation, $subRelation, $closure) |
|||
{ |
|||
$localKey = $this->localKey; |
|||
$foreignKey = $this->foreignKey; |
|||
$data = $this->eagerlyWhere($this, [$foreignKey => $result->$localKey], $foreignKey, $relation, $subRelation, $closure); |
|||
|
|||
// 关联模型 |
|||
if (!isset($data[$result->$localKey])) { |
|||
$relationModel = null; |
|||
} else { |
|||
$relationModel = $data[$result->$localKey]; |
|||
} |
|||
|
|||
if ($relationModel && !empty($this->bindAttr)) { |
|||
// 绑定关联属性 |
|||
$this->bindAttr($relationModel, $result, $this->bindAttr); |
|||
} |
|||
$result->setAttr(Loader::parseName($relation), $relationModel); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,265 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: liu21st <liu21st@gmail.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\model\relation; |
|||
|
|||
use think\Db; |
|||
use think\db\Query; |
|||
use think\Exception; |
|||
use think\Loader; |
|||
use think\Model; |
|||
use think\model\Relation; |
|||
|
|||
class MorphMany extends Relation |
|||
{ |
|||
// 多态字段 |
|||
protected $morphKey; |
|||
protected $morphType; |
|||
// 多态类型 |
|||
protected $type; |
|||
|
|||
/** |
|||
* 构造函数 |
|||
* @access public |
|||
* @param Model $parent 上级模型对象 |
|||
* @param string $model 模型名 |
|||
* @param string $morphKey 关联外键 |
|||
* @param string $morphType 多态字段名 |
|||
* @param string $type 多态类型 |
|||
*/ |
|||
public function __construct(Model $parent, $model, $morphKey, $morphType, $type) |
|||
{ |
|||
$this->parent = $parent; |
|||
$this->model = $model; |
|||
$this->type = $type; |
|||
$this->morphKey = $morphKey; |
|||
$this->morphType = $morphType; |
|||
$this->query = (new $model)->db(); |
|||
} |
|||
|
|||
/** |
|||
* 延迟获取关联数据 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包查询条件 |
|||
* @return false|\PDOStatement|string|\think\Collection |
|||
*/ |
|||
public function getRelation($subRelation = '', $closure = null) |
|||
{ |
|||
if ($closure) { |
|||
call_user_func_array($closure, [ & $this->query]); |
|||
} |
|||
return $this->relation($subRelation)->select(); |
|||
} |
|||
|
|||
/** |
|||
* 根据关联条件查询当前模型 |
|||
* @access public |
|||
* @param string $operator 比较操作符 |
|||
* @param integer $count 个数 |
|||
* @param string $id 关联表的统计字段 |
|||
* @param string $joinType JOIN类型 |
|||
* @return Query |
|||
*/ |
|||
public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') |
|||
{ |
|||
throw new Exception('relation not support: has'); |
|||
} |
|||
|
|||
/** |
|||
* 根据关联条件查询当前模型 |
|||
* @access public |
|||
* @param mixed $where 查询条件(数组或者闭包) |
|||
* @return Query |
|||
*/ |
|||
public function hasWhere($where = []) |
|||
{ |
|||
throw new Exception('relation not support: hasWhere'); |
|||
} |
|||
|
|||
/** |
|||
* 预载入关联查询 |
|||
* @access public |
|||
* @param array $resultSet 数据集 |
|||
* @param string $relation 当前关联名 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包 |
|||
* @return void |
|||
*/ |
|||
public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) |
|||
{ |
|||
$morphType = $this->morphType; |
|||
$morphKey = $this->morphKey; |
|||
$type = $this->type; |
|||
$range = []; |
|||
foreach ($resultSet as $result) { |
|||
$pk = $result->getPk(); |
|||
// 获取关联外键列表 |
|||
if (isset($result->$pk)) { |
|||
$range[] = $result->$pk; |
|||
} |
|||
} |
|||
|
|||
if (!empty($range)) { |
|||
$data = $this->eagerlyMorphToMany([ |
|||
$morphKey => ['in', $range], |
|||
$morphType => $type, |
|||
], $relation, $subRelation, $closure); |
|||
// 关联属性名 |
|||
$attr = Loader::parseName($relation); |
|||
// 关联数据封装 |
|||
foreach ($resultSet as $result) { |
|||
if (!isset($data[$result->$pk])) { |
|||
$data[$result->$pk] = []; |
|||
} |
|||
$result->setAttr($attr, $this->resultSetBuild($data[$result->$pk])); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 预载入关联查询 |
|||
* @access public |
|||
* @param Model $result 数据对象 |
|||
* @param string $relation 当前关联名 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包 |
|||
* @return void |
|||
*/ |
|||
public function eagerlyResult(&$result, $relation, $subRelation, $closure) |
|||
{ |
|||
$pk = $result->getPk(); |
|||
if (isset($result->$pk)) { |
|||
$data = $this->eagerlyMorphToMany([ |
|||
$this->morphKey => $result->$pk, |
|||
$this->morphType => $this->type, |
|||
], $relation, $subRelation, $closure); |
|||
$result->setAttr(Loader::parseName($relation), $this->resultSetBuild($data[$result->$pk])); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 关联统计 |
|||
* @access public |
|||
* @param Model $result 数据对象 |
|||
* @param \Closure $closure 闭包 |
|||
* @return integer |
|||
*/ |
|||
public function relationCount($result, $closure) |
|||
{ |
|||
$pk = $result->getPk(); |
|||
$count = 0; |
|||
if (isset($result->$pk)) { |
|||
if ($closure) { |
|||
call_user_func_array($closure, [ & $this->query]); |
|||
} |
|||
$count = $this->query->where([$this->morphKey => $result->$pk, $this->morphType => $this->type])->count(); |
|||
} |
|||
return $count; |
|||
} |
|||
|
|||
/** |
|||
* 获取关联统计子查询 |
|||
* @access public |
|||
* @param \Closure $closure 闭包 |
|||
* @return string |
|||
*/ |
|||
public function getRelationCountQuery($closure) |
|||
{ |
|||
if ($closure) { |
|||
call_user_func_array($closure, [ & $this->query]); |
|||
} |
|||
|
|||
return $this->query->where([ |
|||
$this->morphKey => [ |
|||
'exp', |
|||
'=' . $this->parent->getTable() . '.' . $this->parent->getPk(), |
|||
], |
|||
$this->morphType => $this->type, |
|||
])->fetchSql()->count(); |
|||
} |
|||
|
|||
/** |
|||
* 多态一对多 关联模型预查询 |
|||
* @access public |
|||
* @param array $where 关联预查询条件 |
|||
* @param string $relation 关联名 |
|||
* @param string $subRelation 子关联 |
|||
* @param bool|\Closure $closure 闭包 |
|||
* @return array |
|||
*/ |
|||
protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false) |
|||
{ |
|||
// 预载入关联查询 支持嵌套预载入 |
|||
if ($closure) { |
|||
call_user_func_array($closure, [ & $this]); |
|||
} |
|||
$list = $this->query->where($where)->with($subRelation)->select(); |
|||
$morphKey = $this->morphKey; |
|||
// 组装模型数据 |
|||
$data = []; |
|||
foreach ($list as $set) { |
|||
$data[$set->$morphKey][] = $set; |
|||
} |
|||
return $data; |
|||
} |
|||
|
|||
/** |
|||
* 保存(新增)当前关联数据对象 |
|||
* @access public |
|||
* @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 |
|||
* @return integer |
|||
*/ |
|||
public function save($data) |
|||
{ |
|||
if ($data instanceof Model) { |
|||
$data = $data->getData(); |
|||
} |
|||
// 保存关联表数据 |
|||
$pk = $this->parent->getPk(); |
|||
|
|||
$data[$this->morphKey] = $this->parent->$pk; |
|||
$data[$this->morphType] = $this->type; |
|||
$model = new $this->model; |
|||
return $model->save($data); |
|||
} |
|||
|
|||
/** |
|||
* 批量保存当前关联数据对象 |
|||
* @access public |
|||
* @param array $dataSet 数据集 |
|||
* @return integer |
|||
*/ |
|||
public function saveAll(array $dataSet) |
|||
{ |
|||
$result = false; |
|||
foreach ($dataSet as $key => $data) { |
|||
$result = $this->save($data); |
|||
} |
|||
return $result; |
|||
} |
|||
|
|||
/** |
|||
* 执行基础查询(进执行一次) |
|||
* @access protected |
|||
* @return void |
|||
*/ |
|||
protected function baseQuery() |
|||
{ |
|||
if (empty($this->baseQuery)) { |
|||
$pk = $this->parent->getPk(); |
|||
$map[$this->morphKey] = $this->parent->$pk; |
|||
$map[$this->morphType] = $this->type; |
|||
$this->query->where($map); |
|||
$this->baseQuery = true; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,233 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: liu21st <liu21st@gmail.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\model\relation; |
|||
|
|||
use think\Exception; |
|||
use think\Loader; |
|||
use think\Model; |
|||
use think\model\Relation; |
|||
|
|||
class MorphTo extends Relation |
|||
{ |
|||
// 多态字段 |
|||
protected $morphKey; |
|||
protected $morphType; |
|||
// 多态别名 |
|||
protected $alias; |
|||
|
|||
/** |
|||
* 构造函数 |
|||
* @access public |
|||
* @param Model $parent 上级模型对象 |
|||
* @param string $morphType 多态字段名 |
|||
* @param string $morphKey 外键名 |
|||
* @param array $alias 多态别名定义 |
|||
*/ |
|||
public function __construct(Model $parent, $morphType, $morphKey, $alias = []) |
|||
{ |
|||
$this->parent = $parent; |
|||
$this->morphType = $morphType; |
|||
$this->morphKey = $morphKey; |
|||
$this->alias = $alias; |
|||
} |
|||
|
|||
/** |
|||
* 延迟获取关联数据 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包查询条件 |
|||
* @return mixed |
|||
*/ |
|||
public function getRelation($subRelation = '', $closure = null) |
|||
{ |
|||
$morphKey = $this->morphKey; |
|||
$morphType = $this->morphType; |
|||
// 多态模型 |
|||
$model = $this->parseModel($this->parent->$morphType); |
|||
// 主键数据 |
|||
$pk = $this->parent->$morphKey; |
|||
return (new $model)->relation($subRelation)->find($pk); |
|||
} |
|||
|
|||
/** |
|||
* 根据关联条件查询当前模型 |
|||
* @access public |
|||
* @param string $operator 比较操作符 |
|||
* @param integer $count 个数 |
|||
* @param string $id 关联表的统计字段 |
|||
* @param string $joinType JOIN类型 |
|||
* @return Query |
|||
*/ |
|||
public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') |
|||
{ |
|||
return $this->parent; |
|||
} |
|||
|
|||
/** |
|||
* 根据关联条件查询当前模型 |
|||
* @access public |
|||
* @param mixed $where 查询条件(数组或者闭包) |
|||
* @return Query |
|||
*/ |
|||
public function hasWhere($where = []) |
|||
{ |
|||
throw new Exception('relation not support: hasWhere'); |
|||
} |
|||
|
|||
/** |
|||
* 解析模型的完整命名空间 |
|||
* @access public |
|||
* @param string $model 模型名(或者完整类名) |
|||
* @return string |
|||
*/ |
|||
protected function parseModel($model) |
|||
{ |
|||
if (isset($this->alias[$model])) { |
|||
$model = $this->alias[$model]; |
|||
} |
|||
if (false === strpos($model, '\\')) { |
|||
$path = explode('\\', get_class($this->parent)); |
|||
array_pop($path); |
|||
array_push($path, Loader::parseName($model, 1)); |
|||
$model = implode('\\', $path); |
|||
} |
|||
return $model; |
|||
} |
|||
|
|||
/** |
|||
* 设置多态别名 |
|||
* @access public |
|||
* @param array $alias 别名定义 |
|||
* @return $this |
|||
*/ |
|||
public function setAlias($alias) |
|||
{ |
|||
$this->alias = $alias; |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 移除关联查询参数 |
|||
* @access public |
|||
* @return $this |
|||
*/ |
|||
public function removeOption() |
|||
{ |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 预载入关联查询 |
|||
* @access public |
|||
* @param array $resultSet 数据集 |
|||
* @param string $relation 当前关联名 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包 |
|||
* @return void |
|||
* @throws Exception |
|||
*/ |
|||
public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) |
|||
{ |
|||
$morphKey = $this->morphKey; |
|||
$morphType = $this->morphType; |
|||
$range = []; |
|||
foreach ($resultSet as $result) { |
|||
// 获取关联外键列表 |
|||
if (!empty($result->$morphKey)) { |
|||
$range[$result->$morphType][] = $result->$morphKey; |
|||
} |
|||
} |
|||
|
|||
if (!empty($range)) { |
|||
// 关联属性名 |
|||
$attr = Loader::parseName($relation); |
|||
foreach ($range as $key => $val) { |
|||
// 多态类型映射 |
|||
$model = $this->parseModel($key); |
|||
$obj = new $model; |
|||
$pk = $obj->getPk(); |
|||
$list = $obj->all($val, $subRelation); |
|||
$data = []; |
|||
foreach ($list as $k => $vo) { |
|||
$data[$vo->$pk] = $vo; |
|||
} |
|||
foreach ($resultSet as $result) { |
|||
if ($key == $result->$morphType) { |
|||
// 关联模型 |
|||
if (!isset($data[$result->$morphKey])) { |
|||
throw new Exception('relation data not exists :' . $this->model); |
|||
} else { |
|||
$result->setAttr($attr, $data[$result->$morphKey]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 预载入关联查询 |
|||
* @access public |
|||
* @param Model $result 数据对象 |
|||
* @param string $relation 当前关联名 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包 |
|||
* @return void |
|||
*/ |
|||
public function eagerlyResult(&$result, $relation, $subRelation, $closure) |
|||
{ |
|||
$morphKey = $this->morphKey; |
|||
$morphType = $this->morphType; |
|||
// 多态类型映射 |
|||
$model = $this->parseModel($result->{$this->morphType}); |
|||
$this->eagerlyMorphToOne($model, $relation, $result, $subRelation); |
|||
} |
|||
|
|||
/** |
|||
* 关联统计 |
|||
* @access public |
|||
* @param Model $result 数据对象 |
|||
* @param \Closure $closure 闭包 |
|||
* @return integer |
|||
*/ |
|||
public function relationCount($result, $closure) |
|||
{ |
|||
} |
|||
|
|||
/** |
|||
* 多态MorphTo 关联模型预查询 |
|||
* @access public |
|||
* @param object $model 关联模型对象 |
|||
* @param string $relation 关联名 |
|||
* @param $result |
|||
* @param string $subRelation 子关联 |
|||
* @return void |
|||
*/ |
|||
protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '') |
|||
{ |
|||
// 预载入关联查询 支持嵌套预载入 |
|||
$pk = $this->parent->{$this->morphKey}; |
|||
$data = (new $model)->with($subRelation)->find($pk); |
|||
if ($data) { |
|||
$data->isUpdate(true); |
|||
} |
|||
$result->setAttr(Loader::parseName($relation), $data ?: null); |
|||
} |
|||
|
|||
/** |
|||
* 执行基础查询(进执行一次) |
|||
* @access protected |
|||
* @return void |
|||
*/ |
|||
protected function baseQuery() |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,314 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: liu21st <liu21st@gmail.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\model\relation; |
|||
|
|||
use think\db\Query; |
|||
use think\Exception; |
|||
use think\Loader; |
|||
use think\Model; |
|||
use think\model\Relation; |
|||
|
|||
/** |
|||
* Class OneToOne |
|||
* @package think\model\relation |
|||
* |
|||
*/ |
|||
abstract class OneToOne extends Relation |
|||
{ |
|||
// 预载入方式 0 -JOIN 1 -IN |
|||
protected $eagerlyType = 1; |
|||
// 当前关联的JOIN类型 |
|||
protected $joinType; |
|||
// 要绑定的属性 |
|||
protected $bindAttr = []; |
|||
|
|||
/** |
|||
* 设置join类型 |
|||
* @access public |
|||
* @param string $type JOIN类型 |
|||
* @return $this |
|||
*/ |
|||
public function joinType($type) |
|||
{ |
|||
$this->joinType = $type; |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 预载入关联查询(JOIN方式) |
|||
* @access public |
|||
* @param Query $query 查询对象 |
|||
* @param string $relation 关联名 |
|||
* @param string $subRelation 子关联 |
|||
* @param \Closure $closure 闭包条件 |
|||
* @param bool $first |
|||
* @return void |
|||
*/ |
|||
public function eagerly(Query $query, $relation, $subRelation, $closure, $first) |
|||
{ |
|||
$name = Loader::parseName(basename(str_replace('\\', '/', $query->getModel()))); |
|||
$alias = $name; |
|||
if ($first) { |
|||
$table = $query->getTable(); |
|||
$query->table([$table => $alias]); |
|||
if ($query->getOptions('field')) { |
|||
$field = $query->getOptions('field'); |
|||
$query->removeOption('field'); |
|||
} else { |
|||
$field = true; |
|||
} |
|||
$query->field($field, false, $table, $alias); |
|||
$field = null; |
|||
} |
|||
|
|||
// 预载入封装 |
|||
$joinTable = $this->query->getTable(); |
|||
$joinAlias = $relation; |
|||
$query->via($joinAlias); |
|||
|
|||
if ($this instanceof BelongsTo) { |
|||
$query->join($joinTable . ' ' . $joinAlias, $alias . '.' . $this->foreignKey . '=' . $joinAlias . '.' . $this->localKey, $this->joinType); |
|||
} else { |
|||
$query->join($joinTable . ' ' . $joinAlias, $alias . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey, $this->joinType); |
|||
} |
|||
|
|||
if ($closure) { |
|||
// 执行闭包查询 |
|||
call_user_func_array($closure, [ & $query]); |
|||
// 使用withField指定获取关联的字段,如 |
|||
// $query->where(['id'=>1])->withField('id,name'); |
|||
if ($query->getOptions('with_field')) { |
|||
$field = $query->getOptions('with_field'); |
|||
$query->removeOption('with_field'); |
|||
} |
|||
} elseif (isset($this->option['field'])) { |
|||
$field = $this->option['field']; |
|||
} |
|||
$query->field(isset($field) ? $field : true, false, $joinTable, $joinAlias, $relation . '__'); |
|||
} |
|||
|
|||
/** |
|||
* 预载入关联查询(数据集) |
|||
* @param array $resultSet |
|||
* @param string $relation |
|||
* @param string $subRelation |
|||
* @param \Closure $closure |
|||
* @return mixed |
|||
*/ |
|||
abstract protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure); |
|||
|
|||
/** |
|||
* 预载入关联查询(数据) |
|||
* @param Model $result |
|||
* @param string $relation |
|||
* @param string $subRelation |
|||
* @param \Closure $closure |
|||
* @return mixed |
|||
*/ |
|||
abstract protected function eagerlyOne(&$result, $relation, $subRelation, $closure); |
|||
|
|||
/** |
|||
* 预载入关联查询(数据集) |
|||
* @access public |
|||
* @param array $resultSet 数据集 |
|||
* @param string $relation 当前关联名 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包 |
|||
* @return void |
|||
*/ |
|||
public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) |
|||
{ |
|||
if (1 == $this->eagerlyType) { |
|||
// IN查询 |
|||
$this->eagerlySet($resultSet, $relation, $subRelation, $closure); |
|||
} else { |
|||
// 模型关联组装 |
|||
foreach ($resultSet as $result) { |
|||
$this->match($this->model, $relation, $result); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 预载入关联查询(数据) |
|||
* @access public |
|||
* @param Model $result 数据对象 |
|||
* @param string $relation 当前关联名 |
|||
* @param string $subRelation 子关联名 |
|||
* @param \Closure $closure 闭包 |
|||
* @return void |
|||
*/ |
|||
public function eagerlyResult(&$result, $relation, $subRelation, $closure) |
|||
{ |
|||
if (1 == $this->eagerlyType) { |
|||
// IN查询 |
|||
$this->eagerlyOne($result, $relation, $subRelation, $closure); |
|||
} else { |
|||
// 模型关联组装 |
|||
$this->match($this->model, $relation, $result); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 保存(新增)当前关联数据对象 |
|||
* @access public |
|||
* @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 |
|||
* @return integer |
|||
*/ |
|||
public function save($data) |
|||
{ |
|||
if ($data instanceof Model) { |
|||
$data = $data->getData(); |
|||
} |
|||
// 保存关联表数据 |
|||
$data[$this->foreignKey] = $this->parent->{$this->localKey}; |
|||
$model = new $this->model; |
|||
return $model->save($data); |
|||
} |
|||
|
|||
/** |
|||
* 设置预载入方式 |
|||
* @access public |
|||
* @param integer $type 预载入方式 0 JOIN查询 1 IN查询 |
|||
* @return $this |
|||
*/ |
|||
public function setEagerlyType($type) |
|||
{ |
|||
$this->eagerlyType = $type; |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 获取预载入方式 |
|||
* @access public |
|||
* @return integer |
|||
*/ |
|||
public function getEagerlyType() |
|||
{ |
|||
return $this->eagerlyType; |
|||
} |
|||
|
|||
/** |
|||
* 绑定关联表的属性到父模型属性 |
|||
* @access public |
|||
* @param mixed $attr 要绑定的属性列表 |
|||
* @return $this |
|||
*/ |
|||
public function bind($attr) |
|||
{ |
|||
if (is_string($attr)) { |
|||
$attr = explode(',', $attr); |
|||
} |
|||
$this->bindAttr = $attr; |
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 关联统计 |
|||
* @access public |
|||
* @param Model $result 数据对象 |
|||
* @param \Closure $closure 闭包 |
|||
* @return integer |
|||
*/ |
|||
public function relationCount($result, $closure) |
|||
{ |
|||
} |
|||
|
|||
/** |
|||
* 一对一 关联模型预查询拼装 |
|||
* @access public |
|||
* @param string $model 模型名称 |
|||
* @param string $relation 关联名 |
|||
* @param Model $result 模型对象实例 |
|||
* @return void |
|||
*/ |
|||
protected function match($model, $relation, &$result) |
|||
{ |
|||
// 重新组装模型数据 |
|||
foreach ($result->getData() as $key => $val) { |
|||
if (strpos($key, '__')) { |
|||
list($name, $attr) = explode('__', $key, 2); |
|||
if ($name == $relation) { |
|||
$list[$name][$attr] = $val; |
|||
unset($result->$key); |
|||
} |
|||
} |
|||
} |
|||
if (isset($list[$relation])) { |
|||
$relationModel = new $model($list[$relation]); |
|||
if (!empty($this->bindAttr)) { |
|||
$this->bindAttr($relationModel, $result, $this->bindAttr); |
|||
} |
|||
} |
|||
$result->setAttr(Loader::parseName($relation), !isset($relationModel) ? null : $relationModel->isUpdate(true)); |
|||
} |
|||
|
|||
/** |
|||
* 绑定关联属性到父模型 |
|||
* @access protected |
|||
* @param Model $model 关联模型对象 |
|||
* @param Model $result 父模型对象 |
|||
* @param array $bindAttr 绑定属性 |
|||
* @return void |
|||
* @throws Exception |
|||
*/ |
|||
protected function bindAttr($model, &$result, $bindAttr) |
|||
{ |
|||
foreach ($bindAttr as $key => $attr) { |
|||
$key = is_numeric($key) ? $attr : $key; |
|||
if (isset($result->$key)) { |
|||
throw new Exception('bind attr has exists:' . $key); |
|||
} else { |
|||
$result->setAttr($key, $model->$attr); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 一对一 关联模型预查询(IN方式) |
|||
* @access public |
|||
* @param object $model 关联模型对象 |
|||
* @param array $where 关联预查询条件 |
|||
* @param string $key 关联键名 |
|||
* @param string $relation 关联名 |
|||
* @param string $subRelation 子关联 |
|||
* @param bool|\Closure $closure |
|||
* @return array |
|||
*/ |
|||
protected function eagerlyWhere($model, $where, $key, $relation, $subRelation = '', $closure = false) |
|||
{ |
|||
// 预载入关联查询 支持嵌套预载入 |
|||
if ($closure) { |
|||
call_user_func_array($closure, [ & $model]); |
|||
if ($field = $model->getOptions('with_field')) { |
|||
$model->field($field)->removeOption('with_field'); |
|||
} |
|||
} |
|||
$list = $model->where($where)->with($subRelation)->select(); |
|||
|
|||
// 组装模型数据 |
|||
$data = []; |
|||
foreach ($list as $set) { |
|||
$data[$set->$key] = $set; |
|||
} |
|||
return $data; |
|||
} |
|||
|
|||
/** |
|||
* 执行基础查询(进执行一次) |
|||
* @access protected |
|||
* @return void |
|||
*/ |
|||
protected function baseQuery() |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,205 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | ThinkPHP [ WE CAN DO IT JUST THINK ] |
|||
// +---------------------------------------------------------------------- |
|||
// | Copyright (c) 2006~2017 http://thinkphp.cn All rights reserved. |
|||
// +---------------------------------------------------------------------- |
|||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: zhangyajun <448901948@qq.com> |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace think\paginator\driver; |
|||
|
|||
use think\Paginator; |
|||
|
|||
class Bootstrap extends Paginator |
|||
{ |
|||
|
|||
/** |
|||
* 上一页按钮 |
|||
* @param string $text |
|||
* @return string |
|||
*/ |
|||
protected function getPreviousButton($text = "«") |
|||
{ |
|||
|
|||
if ($this->currentPage() <= 1) { |
|||
return $this->getDisabledTextWrapper($text); |
|||
} |
|||
|
|||
$url = $this->url( |
|||
$this->currentPage() - 1 |
|||
); |
|||
|
|||
return $this->getPageLinkWrapper($url, $text); |
|||
} |
|||
|
|||
/** |
|||
* 下一页按钮 |
|||
* @param string $text |
|||
* @return string |
|||
*/ |
|||
protected function getNextButton($text = '»') |
|||
{ |
|||
if (!$this->hasMore) { |
|||
return $this->getDisabledTextWrapper($text); |
|||
} |
|||
|
|||
$url = $this->url($this->currentPage() + 1); |
|||
|
|||
return $this->getPageLinkWrapper($url, $text); |
|||
} |
|||
|
|||
/** |
|||
* 页码按钮 |
|||
* @return string |
|||
*/ |
|||
protected function getLinks() |
|||
{ |
|||
if ($this->simple) |
|||
return ''; |
|||
|
|||
$block = [ |
|||
'first' => null, |
|||
'slider' => null, |
|||
'last' => null |
|||
]; |
|||
|
|||
$side = 2; |
|||
$window = $side * 2; |
|||
|
|||
if ($this->lastPage < $window + 6) { |
|||
$block['first'] = $this->getUrlRange(1, $this->lastPage); |
|||
} elseif ($this->currentPage <= $window) { |
|||
$block['first'] = $this->getUrlRange(1, $window + 2); |
|||
$block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); |
|||
} elseif ($this->currentPage > ($this->lastPage - $window)) { |
|||
$block['first'] = $this->getUrlRange(1, 2); |
|||
$block['last'] = $this->getUrlRange($this->lastPage - ($window + 2), $this->lastPage); |
|||
} else { |
|||
$block['first'] = $this->getUrlRange(1, 2); |
|||
$block['slider'] = $this->getUrlRange($this->currentPage - $side, $this->currentPage + $side); |
|||
$block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); |
|||
} |
|||
|
|||
$html = ''; |
|||
|
|||
if (is_array($block['first'])) { |
|||
$html .= $this->getUrlLinks($block['first']); |
|||
} |
|||
|
|||
if (is_array($block['slider'])) { |
|||
$html .= $this->getDots(); |
|||
$html .= $this->getUrlLinks($block['slider']); |
|||
} |
|||
|
|||
if (is_array($block['last'])) { |
|||
$html .= $this->getDots(); |
|||
$html .= $this->getUrlLinks($block['last']); |
|||
} |
|||
|
|||
return $html; |
|||
} |
|||
|
|||
/** |
|||
* 渲染分页html |
|||
* @return mixed |
|||
*/ |
|||
public function render() |
|||
{ |
|||
if ($this->hasPages()) { |
|||
if ($this->simple) { |
|||
return sprintf( |
|||
'<ul class="pager">%s %s</ul>', |
|||
$this->getPreviousButton(), |
|||
$this->getNextButton() |
|||
); |
|||
} else { |
|||
return sprintf( |
|||
'<ul class="pagination">%s %s %s</ul>', |
|||
$this->getPreviousButton(), |
|||
$this->getLinks(), |
|||
$this->getNextButton() |
|||
); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 生成一个可点击的按钮 |
|||
* |
|||
* @param string $url |
|||
* @param int $page |
|||
* @return string |
|||
*/ |
|||
protected function getAvailablePageWrapper($url, $page) |
|||
{ |
|||
return '<li><a href="' . htmlentities($url) . '">' . $page . '</a></li>'; |
|||
} |
|||
|
|||
/** |
|||
* 生成一个禁用的按钮 |
|||
* |
|||
* @param string $text |
|||
* @return string |
|||
*/ |
|||
protected function getDisabledTextWrapper($text) |
|||
{ |
|||
return '<li class="disabled"><span>' . $text . '</span></li>'; |
|||
} |
|||
|
|||
/** |
|||
* 生成一个激活的按钮 |
|||
* |
|||
* @param string $text |
|||
* @return string |
|||
*/ |
|||
protected function getActivePageWrapper($text) |
|||
{ |
|||
return '<li class="active"><span>' . $text . '</span></li>'; |
|||
} |
|||
|
|||
/** |
|||
* 生成省略号按钮 |
|||
* |
|||
* @return string |
|||
*/ |
|||
protected function getDots() |
|||
{ |
|||
return $this->getDisabledTextWrapper('...'); |
|||
} |
|||
|
|||
/** |
|||
* 批量生成页码按钮. |
|||
* |
|||
* @param array $urls |
|||
* @return string |
|||
*/ |
|||
protected function getUrlLinks(array $urls) |
|||
{ |
|||
$html = ''; |
|||
|
|||
foreach ($urls as $page => $url) { |
|||
$html .= $this->getPageLinkWrapper($url, $page); |
|||
} |
|||
|
|||
return $html; |
|||
} |
|||
|
|||
/** |
|||
* 生成普通页码按钮 |
|||
* |
|||
* @param string $url |
|||
* @param int $page |
|||
* @return string |
|||
*/ |
|||
protected function getPageLinkWrapper($url, $page) |
|||
{ |
|||
if ($page == $this->currentPage()) { |
|||
return $this->getActivePageWrapper($page); |
|||
} |
|||
|
|||
return $this->getAvailablePageWrapper($url, $page); |
|||
} |
|||
} |
|||
@ -0,0 +1,233 @@ |
|||
<?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\process; |
|||
|
|||
use think\Process; |
|||
|
|||
class Builder |
|||
{ |
|||
private $arguments; |
|||
private $cwd; |
|||
private $env = null; |
|||
private $input; |
|||
private $timeout = 60; |
|||
private $options = []; |
|||
private $inheritEnv = true; |
|||
private $prefix = []; |
|||
private $outputDisabled = false; |
|||
|
|||
/** |
|||
* 构造方法 |
|||
* @param string[] $arguments 参数 |
|||
*/ |
|||
public function __construct(array $arguments = []) |
|||
{ |
|||
$this->arguments = $arguments; |
|||
} |
|||
|
|||
/** |
|||
* 创建一个实例 |
|||
* @param string[] $arguments 参数 |
|||
* @return self |
|||
*/ |
|||
public static function create(array $arguments = []) |
|||
{ |
|||
return new static($arguments); |
|||
} |
|||
|
|||
/** |
|||
* 添加一个参数 |
|||
* @param string $argument 参数 |
|||
* @return self |
|||
*/ |
|||
public function add($argument) |
|||
{ |
|||
$this->arguments[] = $argument; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 添加一个前缀 |
|||
* @param string|array $prefix |
|||
* @return self |
|||
*/ |
|||
public function setPrefix($prefix) |
|||
{ |
|||
$this->prefix = is_array($prefix) ? $prefix : [$prefix]; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 设置参数 |
|||
* @param string[] $arguments |
|||
* @return self |
|||
*/ |
|||
public function setArguments(array $arguments) |
|||
{ |
|||
$this->arguments = $arguments; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 设置工作目录 |
|||
* @param null|string $cwd |
|||
* @return self |
|||
*/ |
|||
public function setWorkingDirectory($cwd) |
|||
{ |
|||
$this->cwd = $cwd; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 是否初始化环境变量 |
|||
* @param bool $inheritEnv |
|||
* @return self |
|||
*/ |
|||
public function inheritEnvironmentVariables($inheritEnv = true) |
|||
{ |
|||
$this->inheritEnv = $inheritEnv; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 设置环境变量 |
|||
* @param string $name |
|||
* @param null|string $value |
|||
* @return self |
|||
*/ |
|||
public function setEnv($name, $value) |
|||
{ |
|||
$this->env[$name] = $value; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 添加环境变量 |
|||
* @param array $variables |
|||
* @return self |
|||
*/ |
|||
public function addEnvironmentVariables(array $variables) |
|||
{ |
|||
$this->env = array_replace($this->env, $variables); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 设置输入 |
|||
* @param mixed $input |
|||
* @return self |
|||
*/ |
|||
public function setInput($input) |
|||
{ |
|||
$this->input = Utils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input); |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 设置超时时间 |
|||
* @param float|null $timeout |
|||
* @return self |
|||
*/ |
|||
public function setTimeout($timeout) |
|||
{ |
|||
if (null === $timeout) { |
|||
$this->timeout = null; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
$timeout = (float) $timeout; |
|||
|
|||
if ($timeout < 0) { |
|||
throw new \InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); |
|||
} |
|||
|
|||
$this->timeout = $timeout; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 设置proc_open选项 |
|||
* @param string $name |
|||
* @param string $value |
|||
* @return self |
|||
*/ |
|||
public function setOption($name, $value) |
|||
{ |
|||
$this->options[$name] = $value; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 禁止输出 |
|||
* @return self |
|||
*/ |
|||
public function disableOutput() |
|||
{ |
|||
$this->outputDisabled = true; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 开启输出 |
|||
* @return self |
|||
*/ |
|||
public function enableOutput() |
|||
{ |
|||
$this->outputDisabled = false; |
|||
|
|||
return $this; |
|||
} |
|||
|
|||
/** |
|||
* 创建一个Process实例 |
|||
* @return Process |
|||
*/ |
|||
public function getProcess() |
|||
{ |
|||
if (0 === count($this->prefix) && 0 === count($this->arguments)) { |
|||
throw new \LogicException('You must add() command arguments before calling getProcess().'); |
|||
} |
|||
|
|||
$options = $this->options; |
|||
|
|||
$arguments = array_merge($this->prefix, $this->arguments); |
|||
$script = implode(' ', array_map([__NAMESPACE__ . '\\Utils', 'escapeArgument'], $arguments)); |
|||
|
|||
if ($this->inheritEnv) { |
|||
// include $_ENV for BC purposes |
|||
$env = array_replace($_ENV, $_SERVER, $this->env); |
|||
} else { |
|||
$env = $this->env; |
|||
} |
|||
|
|||
$process = new Process($script, $this->cwd, $env, $this->input, $this->timeout, $options); |
|||
|
|||
if ($this->outputDisabled) { |
|||
$process->disableOutput(); |
|||
} |
|||
|
|||
return $process; |
|||
} |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
<?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\process; |
|||
|
|||
class Utils |
|||
{ |
|||
|
|||
/** |
|||
* 转义字符串 |
|||
* @param string $argument |
|||
* @return string |
|||
*/ |
|||
public static function escapeArgument($argument) |
|||
{ |
|||
|
|||
if ('' === $argument) { |
|||
return escapeshellarg($argument); |
|||
} |
|||
$escapedArgument = ''; |
|||
$quote = false; |
|||
foreach (preg_split('/(")/i', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) { |
|||
if ('"' === $part) { |
|||
$escapedArgument .= '\\"'; |
|||
} elseif (self::isSurroundedBy($part, '%')) { |
|||
// Avoid environment variable expansion |
|||
$escapedArgument .= '^%"' . substr($part, 1, -1) . '"^%'; |
|||
} else { |
|||
// escape trailing backslash |
|||
if ('\\' === substr($part, -1)) { |
|||
$part .= '\\'; |
|||
} |
|||
$quote = true; |
|||
$escapedArgument .= $part; |
|||
} |
|||
} |
|||
if ($quote) { |
|||
$escapedArgument = '"' . $escapedArgument . '"'; |
|||
} |
|||
return $escapedArgument; |
|||
} |
|||
|
|||
/** |
|||
* 验证并进行规范化Process输入。 |
|||
* @param string $caller |
|||
* @param mixed $input |
|||
* @return string |
|||
* @throws \InvalidArgumentException |
|||
*/ |
|||
public static function validateInput($caller, $input) |
|||
{ |
|||
if (null !== $input) { |
|||
if (is_resource($input)) { |
|||
return $input; |
|||
} |
|||
if (is_scalar($input)) { |
|||
return (string) $input; |
|||
} |
|||
throw new \InvalidArgumentException(sprintf('%s only accepts strings or stream resources.', $caller)); |
|||
} |
|||
return $input; |
|||
} |
|||
|
|||
private static function isSurroundedBy($arg, $char) |
|||
{ |
|||
return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1]; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
<?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\process\exception; |
|||
|
|||
use think\Process; |
|||
|
|||
class Failed extends \RuntimeException |
|||
{ |
|||
|
|||
private $process; |
|||
|
|||
public function __construct(Process $process) |
|||
{ |
|||
if ($process->isSuccessful()) { |
|||
throw new \InvalidArgumentException('Expected a failed process, but the given process was successful.'); |
|||
} |
|||
|
|||
$error = sprintf('The command "%s" failed.' . "\nExit Code: %s(%s)", $process->getCommandLine(), $process->getExitCode(), $process->getExitCodeText()); |
|||
|
|||
if (!$process->isOutputDisabled()) { |
|||
$error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", $process->getOutput(), $process->getErrorOutput()); |
|||
} |
|||
|
|||
parent::__construct($error); |
|||
|
|||
$this->process = $process; |
|||
} |
|||
|
|||
public function getProcess() |
|||
{ |
|||
return $this->process; |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
<?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\process\exception; |
|||
|
|||
use think\Process; |
|||
|
|||
class Timeout extends \RuntimeException |
|||
{ |
|||
|
|||
const TYPE_GENERAL = 1; |
|||
const TYPE_IDLE = 2; |
|||
|
|||
private $process; |
|||
private $timeoutType; |
|||
|
|||
public function __construct(Process $process, $timeoutType) |
|||
{ |
|||
$this->process = $process; |
|||
$this->timeoutType = $timeoutType; |
|||
|
|||
parent::__construct(sprintf('The process "%s" exceeded the timeout of %s seconds.', $process->getCommandLine(), $this->getExceededTimeout())); |
|||
} |
|||
|
|||
public function getProcess() |
|||
{ |
|||
return $this->process; |
|||
} |
|||
|
|||
public function isGeneralTimeout() |
|||
{ |
|||
return $this->timeoutType === self::TYPE_GENERAL; |
|||
} |
|||
|
|||
public function isIdleTimeout() |
|||
{ |
|||
return $this->timeoutType === self::TYPE_IDLE; |
|||
} |
|||
|
|||
public function getExceededTimeout() |
|||
{ |
|||
switch ($this->timeoutType) { |
|||
case self::TYPE_GENERAL: |
|||
return $this->process->getTimeout(); |
|||
|
|||
case self::TYPE_IDLE: |
|||
return $this->process->getIdleTimeout(); |
|||
|
|||
default: |
|||
throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
<?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\process\pipes; |
|||
|
|||
abstract class Pipes |
|||
{ |
|||
|
|||
/** @var array */ |
|||
public $pipes = []; |
|||
|
|||
/** @var string */ |
|||
protected $inputBuffer = ''; |
|||
/** @var resource|null */ |
|||
protected $input; |
|||
|
|||
/** @var bool */ |
|||
private $blocked = true; |
|||
|
|||
const CHUNK_SIZE = 16384; |
|||
|
|||
/** |
|||
* 返回用于 proc_open 描述符的数组 |
|||
* @return array |
|||
*/ |
|||
abstract public function getDescriptors(); |
|||
|
|||
/** |
|||
* 返回一个数组的索引由其相关的流,以防这些管道使用的临时文件的文件名。 |
|||
* @return string[] |
|||
*/ |
|||
abstract public function getFiles(); |
|||
|
|||
/** |
|||
* 文件句柄和管道中读取数据。 |
|||
* @param bool $blocking 是否使用阻塞调用 |
|||
* @param bool $close 是否要关闭管道,如果他们已经到达 EOF。 |
|||
* @return string[] |
|||
*/ |
|||
abstract public function readAndWrite($blocking, $close = false); |
|||
|
|||
/** |
|||
* 返回当前状态如果有打开的文件句柄或管道。 |
|||
* @return bool |
|||
*/ |
|||
abstract public function areOpen(); |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function close() |
|||
{ |
|||
foreach ($this->pipes as $pipe) { |
|||
fclose($pipe); |
|||
} |
|||
$this->pipes = []; |
|||
} |
|||
|
|||
/** |
|||
* 检查系统调用已被中断 |
|||
* @return bool |
|||
*/ |
|||
protected function hasSystemCallBeenInterrupted() |
|||
{ |
|||
$lastError = error_get_last(); |
|||
|
|||
return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); |
|||
} |
|||
|
|||
protected function unblock() |
|||
{ |
|||
if (!$this->blocked) { |
|||
return; |
|||
} |
|||
|
|||
foreach ($this->pipes as $pipe) { |
|||
stream_set_blocking($pipe, 0); |
|||
} |
|||
if (null !== $this->input) { |
|||
stream_set_blocking($this->input, 0); |
|||
} |
|||
|
|||
$this->blocked = false; |
|||
} |
|||
} |
|||
@ -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\process\pipes; |
|||
|
|||
use think\Process; |
|||
|
|||
class Unix extends Pipes |
|||
{ |
|||
|
|||
/** @var bool */ |
|||
private $ttyMode; |
|||
/** @var bool */ |
|||
private $ptyMode; |
|||
/** @var bool */ |
|||
private $disableOutput; |
|||
|
|||
public function __construct($ttyMode, $ptyMode, $input, $disableOutput) |
|||
{ |
|||
$this->ttyMode = (bool) $ttyMode; |
|||
$this->ptyMode = (bool) $ptyMode; |
|||
$this->disableOutput = (bool) $disableOutput; |
|||
|
|||
if (is_resource($input)) { |
|||
$this->input = $input; |
|||
} else { |
|||
$this->inputBuffer = (string) $input; |
|||
} |
|||
} |
|||
|
|||
public function __destruct() |
|||
{ |
|||
$this->close(); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function getDescriptors() |
|||
{ |
|||
if ($this->disableOutput) { |
|||
$nullstream = fopen('/dev/null', 'c'); |
|||
|
|||
return [ |
|||
['pipe', 'r'], |
|||
$nullstream, |
|||
$nullstream, |
|||
]; |
|||
} |
|||
|
|||
if ($this->ttyMode) { |
|||
return [ |
|||
['file', '/dev/tty', 'r'], |
|||
['file', '/dev/tty', 'w'], |
|||
['file', '/dev/tty', 'w'], |
|||
]; |
|||
} |
|||
|
|||
if ($this->ptyMode && Process::isPtySupported()) { |
|||
return [ |
|||
['pty'], |
|||
['pty'], |
|||
['pty'], |
|||
]; |
|||
} |
|||
|
|||
return [ |
|||
['pipe', 'r'], |
|||
['pipe', 'w'], // stdout |
|||
['pipe', 'w'], // stderr |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function getFiles() |
|||
{ |
|||
return []; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function readAndWrite($blocking, $close = false) |
|||
{ |
|||
|
|||
if (1 === count($this->pipes) && [0] === array_keys($this->pipes)) { |
|||
fclose($this->pipes[0]); |
|||
unset($this->pipes[0]); |
|||
} |
|||
|
|||
if (empty($this->pipes)) { |
|||
return []; |
|||
} |
|||
|
|||
$this->unblock(); |
|||
|
|||
$read = []; |
|||
|
|||
if (null !== $this->input) { |
|||
$r = array_merge($this->pipes, ['input' => $this->input]); |
|||
} else { |
|||
$r = $this->pipes; |
|||
} |
|||
|
|||
unset($r[0]); |
|||
|
|||
$w = isset($this->pipes[0]) ? [$this->pipes[0]] : null; |
|||
$e = null; |
|||
|
|||
if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { |
|||
|
|||
if (!$this->hasSystemCallBeenInterrupted()) { |
|||
$this->pipes = []; |
|||
} |
|||
|
|||
return $read; |
|||
} |
|||
|
|||
if (0 === $n) { |
|||
return $read; |
|||
} |
|||
|
|||
foreach ($r as $pipe) { |
|||
|
|||
$type = (false !== $found = array_search($pipe, $this->pipes)) ? $found : 'input'; |
|||
$data = ''; |
|||
while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) { |
|||
$data .= $dataread; |
|||
} |
|||
|
|||
if ('' !== $data) { |
|||
if ('input' === $type) { |
|||
$this->inputBuffer .= $data; |
|||
} else { |
|||
$read[$type] = $data; |
|||
} |
|||
} |
|||
|
|||
if (false === $data || (true === $close && feof($pipe) && '' === $data)) { |
|||
if ('input' === $type) { |
|||
$this->input = null; |
|||
} else { |
|||
fclose($this->pipes[$type]); |
|||
unset($this->pipes[$type]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (null !== $w && 0 < count($w)) { |
|||
while (strlen($this->inputBuffer)) { |
|||
$written = fwrite($w[0], $this->inputBuffer, 2 << 18); // write 512k |
|||
if ($written > 0) { |
|||
$this->inputBuffer = (string) substr($this->inputBuffer, $written); |
|||
} else { |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { |
|||
fclose($this->pipes[0]); |
|||
unset($this->pipes[0]); |
|||
} |
|||
|
|||
return $read; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function areOpen() |
|||
{ |
|||
return (bool) $this->pipes; |
|||
} |
|||
|
|||
/** |
|||
* 创建一个新的 UnixPipes 实例 |
|||
* @param Process $process |
|||
* @param string|resource $input |
|||
* @return self |
|||
*/ |
|||
public static function create(Process $process, $input) |
|||
{ |
|||
return new static($process->isTty(), $process->isPty(), $input, $process->isOutputDisabled()); |
|||
} |
|||
} |
|||
@ -0,0 +1,228 @@ |
|||
<?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\process\pipes; |
|||
|
|||
use think\Process; |
|||
|
|||
class Windows extends Pipes |
|||
{ |
|||
|
|||
/** @var array */ |
|||
private $files = []; |
|||
/** @var array */ |
|||
private $fileHandles = []; |
|||
/** @var array */ |
|||
private $readBytes = [ |
|||
Process::STDOUT => 0, |
|||
Process::STDERR => 0, |
|||
]; |
|||
/** @var bool */ |
|||
private $disableOutput; |
|||
|
|||
public function __construct($disableOutput, $input) |
|||
{ |
|||
$this->disableOutput = (bool) $disableOutput; |
|||
|
|||
if (!$this->disableOutput) { |
|||
|
|||
$this->files = [ |
|||
Process::STDOUT => tempnam(sys_get_temp_dir(), 'sf_proc_stdout'), |
|||
Process::STDERR => tempnam(sys_get_temp_dir(), 'sf_proc_stderr'), |
|||
]; |
|||
foreach ($this->files as $offset => $file) { |
|||
$this->fileHandles[$offset] = fopen($this->files[$offset], 'rb'); |
|||
if (false === $this->fileHandles[$offset]) { |
|||
throw new \RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (is_resource($input)) { |
|||
$this->input = $input; |
|||
} else { |
|||
$this->inputBuffer = $input; |
|||
} |
|||
} |
|||
|
|||
public function __destruct() |
|||
{ |
|||
$this->close(); |
|||
$this->removeFiles(); |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function getDescriptors() |
|||
{ |
|||
if ($this->disableOutput) { |
|||
$nullstream = fopen('NUL', 'c'); |
|||
|
|||
return [ |
|||
['pipe', 'r'], |
|||
$nullstream, |
|||
$nullstream, |
|||
]; |
|||
} |
|||
|
|||
return [ |
|||
['pipe', 'r'], |
|||
['file', 'NUL', 'w'], |
|||
['file', 'NUL', 'w'], |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function getFiles() |
|||
{ |
|||
return $this->files; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function readAndWrite($blocking, $close = false) |
|||
{ |
|||
$this->write($blocking, $close); |
|||
|
|||
$read = []; |
|||
$fh = $this->fileHandles; |
|||
foreach ($fh as $type => $fileHandle) { |
|||
if (0 !== fseek($fileHandle, $this->readBytes[$type])) { |
|||
continue; |
|||
} |
|||
$data = ''; |
|||
$dataread = null; |
|||
while (!feof($fileHandle)) { |
|||
if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) { |
|||
$data .= $dataread; |
|||
} |
|||
} |
|||
if (0 < $length = strlen($data)) { |
|||
$this->readBytes[$type] += $length; |
|||
$read[$type] = $data; |
|||
} |
|||
|
|||
if (false === $dataread || (true === $close && feof($fileHandle) && '' === $data)) { |
|||
fclose($this->fileHandles[$type]); |
|||
unset($this->fileHandles[$type]); |
|||
} |
|||
} |
|||
|
|||
return $read; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function areOpen() |
|||
{ |
|||
return (bool) $this->pipes && (bool) $this->fileHandles; |
|||
} |
|||
|
|||
/** |
|||
* {@inheritdoc} |
|||
*/ |
|||
public function close() |
|||
{ |
|||
parent::close(); |
|||
foreach ($this->fileHandles as $handle) { |
|||
fclose($handle); |
|||
} |
|||
$this->fileHandles = []; |
|||
} |
|||
|
|||
/** |
|||
* 创建一个新的 WindowsPipes 实例。 |
|||
* @param Process $process |
|||
* @param $input |
|||
* @return self |
|||
*/ |
|||
public static function create(Process $process, $input) |
|||
{ |
|||
return new static($process->isOutputDisabled(), $input); |
|||
} |
|||
|
|||
/** |
|||
* 删除临时文件 |
|||
*/ |
|||
private function removeFiles() |
|||
{ |
|||
foreach ($this->files as $filename) { |
|||
if (file_exists($filename)) { |
|||
@unlink($filename); |
|||
} |
|||
} |
|||
$this->files = []; |
|||
} |
|||
|
|||
/** |
|||
* 写入到 stdin 输入 |
|||
* @param bool $blocking |
|||
* @param bool $close |
|||
*/ |
|||
private function write($blocking, $close) |
|||
{ |
|||
if (empty($this->pipes)) { |
|||
return; |
|||
} |
|||
|
|||
$this->unblock(); |
|||
|
|||
$r = null !== $this->input ? ['input' => $this->input] : null; |
|||
$w = isset($this->pipes[0]) ? [$this->pipes[0]] : null; |
|||
$e = null; |
|||
|
|||
if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { |
|||
if (!$this->hasSystemCallBeenInterrupted()) { |
|||
$this->pipes = []; |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (0 === $n) { |
|||
return; |
|||
} |
|||
|
|||
if (null !== $w && 0 < count($r)) { |
|||
$data = ''; |
|||
while ($dataread = fread($r['input'], self::CHUNK_SIZE)) { |
|||
$data .= $dataread; |
|||
} |
|||
|
|||
$this->inputBuffer .= $data; |
|||
|
|||
if (false === $data || (true === $close && feof($r['input']) && '' === $data)) { |
|||
$this->input = null; |
|||
} |
|||
} |
|||
|
|||
if (null !== $w && 0 < count($w)) { |
|||
while (strlen($this->inputBuffer)) { |
|||
$written = fwrite($w[0], $this->inputBuffer, 2 << 18); |
|||
if ($written > 0) { |
|||
$this->inputBuffer = (string) substr($this->inputBuffer, $written); |
|||
} else { |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { |
|||
fclose($this->pipes[0]); |
|||
unset($this->pipes[0]); |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue