diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..723ef36f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/convention.php b/convention.php index 0f979e7c..78b731d7 100644 --- a/convention.php +++ b/convention.php @@ -213,5 +213,10 @@ return [ // 指定从服务器序号 'slave_no' => '', ], + //分页配置 + 'paginate' => [ + 'type' => 'bootstrap', + 'var_page' => 'page' + ] ]; diff --git a/library/think/Collection.php b/library/think/Collection.php new file mode 100644 index 00000000..af2dc0ff --- /dev/null +++ b/library/think/Collection.php @@ -0,0 +1,127 @@ + +// +---------------------------------------------------------------------- + +namespace think; + + +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; + +class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable +{ + protected $items = []; + + public function __construct($items = []) + { + $this->items = $items instanceof self ? $items->all() : (array)$items; + } + + public static function make($items = []) + { + return new static($items); + } + + /** + * 是否为空 + * @return bool + */ + public function isEmpty() + { + return empty($this->items); + } + + public function toArray() + { + return array_map(function ($value) { + return ($value instanceof Model || $value instanceof self) ? $value->toArray() : $value; + }, $this->items); + } + + public function all() + { + return $this->items; + } + + /** + * 截取数组 + * + * @param int $offset + * @param int $length + * @param bool $preserveKeys + * @return static + */ + public function slice($offset, $length = null, $preserveKeys = false) + { + return new static(array_slice($this->items, $offset, $length, $preserveKeys)); + } + + // ArrayAccess + public function offsetExists($offset) + { + return array_key_exists($offset, $this->items); + } + + public function offsetGet($offset) + { + return $this->items[$offset]; + } + + public function offsetSet($offset, $value) + { + if (is_null($offset)) { + $this->items[] = $value; + } else { + $this->items[$offset] = $value; + } + } + + public function offsetUnset($offset) + { + unset($this->items[$offset]); + } + + //Countable + public function count() + { + return count($this->items); + } + + //IteratorAggregate + public function getIterator() + { + return new ArrayIterator($this->items); + } + + //JsonSerializable + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * 转换当前数据集为JSON字符串 + * @access public + * @param integer $options json参数 + * @return string + */ + public function toJson($options = JSON_UNESCAPED_UNICODE) + { + return json_encode($this->toArray(), $options); + } + + public function __toString() + { + return $this->toJson(); + } +} \ No newline at end of file diff --git a/library/think/Db.php b/library/think/Db.php index a8a86972..bf67da62 100644 --- a/library/think/Db.php +++ b/library/think/Db.php @@ -29,7 +29,8 @@ class Db * @access public * @param mixed $config 连接配置 * @param bool|string $name 连接标识 true 强制重新连接 - * @return \think\db\Connection + * @return db\Connection + * @throws Exception */ public static function connect($config = [], $name = false) { diff --git a/library/think/Model.php b/library/think/Model.php index 1e5d4400..d140d839 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -16,7 +16,13 @@ use think\Db; use think\db\Query; use think\Loader; use think\model\Relation; +use think\paginator\Collection as PaginatorCollection; +/** + * Class Model + * @package think + * @method PaginatorCollection paginate(integer $listRows = 15, boolean $simple = false, array $config = []) static 分页查询 + */ abstract class Model implements \JsonSerializable, \ArrayAccess { @@ -172,7 +178,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess continue; } - if ($val instanceof Model) { + if ($val instanceof Model || $val instanceof Collection) { // 关联模型对象 $item[$key] = $val->toArray(); } elseif (is_array($val) && isset($val[0]) && $val[0] instanceof Model) { diff --git a/library/think/Paginator.php b/library/think/Paginator.php new file mode 100644 index 00000000..3688e045 --- /dev/null +++ b/library/think/Paginator.php @@ -0,0 +1,245 @@ + +// +---------------------------------------------------------------------- + +namespace think; + + +use think\paginator\Collection as PaginatorCollection; + +abstract class Paginator +{ + /** @var bool 是否为简洁模式 */ + protected $simple = false; + + /** @var PaginatorCollection 数据集 */ + protected $items; + + /** @var integer 当前页 */ + protected $currentPage; + + /** @var integer 最后一页 */ + protected $lastPage; + + /** @var integer|null 数据总数 */ + protected $total; + + /** @var integer 每页的数量 */ + protected $listRows; + + /** @var bool 是否有下一页 */ + protected $hasMore; + + /** @var array 一些配置 */ + protected $options = [ + 'var_page' => 'page', + 'path' => '/', + 'query' => [], + 'fragment' => '' + ]; + + public function __construct(Collection $items, $listRows, $currentPage = null, $simple = false, $total = null, $options = []) + { + $this->options = array_merge($this->options, $options); + + $this->options['path'] = $this->options['path'] != '/' ? rtrim($this->options['path'], '/') : $this->options['path']; + + $this->simple = $simple; + $this->listRows = $listRows; + + if ($simple) { + $this->currentPage = $this->setCurrentPage($currentPage); + $this->hasMore = count($items) > ($this->listRows); + $items = $items->slice(0, $this->listRows); + } else { + $this->lastPage = (int)ceil($total / $listRows); + $this->currentPage = $this->setCurrentPage($currentPage); + $this->hasMore = $this->currentPage < $this->lastPage; + } + + $this->items = PaginatorCollection::make($items, $this); + } + + public function items() + { + return $this->items; + } + + + protected function setCurrentPage($currentPage) + { + if (!$this->simple && $currentPage > $this->lastPage) { + return $this->lastPage > 0 ? $this->lastPage : 1; + } + + return $currentPage; + } + + /** + * 获取页码对应的链接 + * + * @param $page + * @return string + */ + protected function url($page) + { + if ($page <= 0) { + $page = 1; + } + + if (strpos($this->options['path'], '[PAGE]') === false) { + $parameters = [$this->options['var_page'] => $page]; + $path = $this->options['path']; + } else { + $parameters = []; + $path = str_replace('[PAGE]', $page, $this->options['path']); + } + if (count($this->options['query']) > 0) { + $parameters = array_merge($this->options['query'], $parameters); + } + $url = $path; + if (!empty($parameters)) { + $url .= '?' . urldecode(http_build_query($parameters, null, '&')); + } + return $url . $this->buildFragment(); + } + + /** + * 自动获取当前页码 + * @param string $varPage + * @param int $default + * @return int + */ + public static function getCurrentPage($varPage = 'page', $default = 1) + { + $page = Input::request($varPage); + + if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int)$page >= 1) { + return $page; + } + + return $default; + } + + /** + * 自动获取当前的path + * @return string + */ + public static function getCurrentPath() + { + //TODO 待Request类完善后这里再完善 + return '/' . $_SERVER['PATH_INFO']; + } + + public function total() + { + if ($this->simple) { + throw new \Exception('简洁模式下不能获取数据总数'); + } + return $this->total; + } + + public function listRows() + { + return $this->listRows; + } + + public function currentPage() + { + return $this->currentPage; + } + + public function lastPage() + { + if ($this->simple) { + throw new \Exception('简洁模式下不能获取最后一页'); + } + return $this->lastPage; + } + + /** + * 数据是否足够分页 + */ + public function hasPages() + { + return !($this->currentPage == 1 && !$this->hasMore); + } + + /** + * 创建一组分页链接 + * + * @param int $start + * @param int $end + * @return array + */ + public function getUrlRange($start, $end) + { + $urls = []; + + for ($page = $start; $page <= $end; $page++) { + $urls[$page] = $this->url($page); + } + + return $urls; + } + + /** + * 设置URL锚点 + * + * @param string|null $fragment + * @return $this + */ + public function fragment($fragment) + { + $this->options['fragment'] = $fragment; + return $this; + } + + /** + * 添加URL参数 + * + * @param array|string $key + * @param string|null $value + * @return $this + */ + public function appends($key, $value = null) + { + if (!is_array($key)) { + $queries = [$key => $value]; + } else { + $queries = $key; + } + + foreach ($queries as $k => $v) { + if ($k !== $this->options['var_page']) { + $this->options['query'][$k] = $v; + } + } + + return $this; + } + + + /** + * 构造锚点字符串 + * + * @return string + */ + protected function buildFragment() + { + return $this->options['fragment'] ? '#' . $this->options['fragment'] : ''; + } + + /** + * 渲染分页html + * @return mixed + */ + abstract public function render(); +} \ No newline at end of file diff --git a/library/think/db/Connection.php b/library/think/db/Connection.php index 96767870..0c5880a0 100644 --- a/library/think/db/Connection.php +++ b/library/think/db/Connection.php @@ -12,9 +12,8 @@ namespace think\db; use PDO; -use think\Config; +use PDOStatement; use think\Db; -use think\db\Query; use think\Debug; use think\Exception; use think\exception\DbBindParamException; @@ -23,13 +22,16 @@ use think\Log; abstract class Connection { - // PDO操作实例 + + /** @var PDOStatement PDO操作实例 */ protected $PDOStatement; + // 当前操作的数据表名 protected $table = ''; // 当前操作的数据对象名 protected $name = ''; - // 当前SQL指令 + + /** @var string 当前SQL指令 */ protected $queryStr = ''; // 最后插入ID protected $lastInsID; @@ -43,10 +45,13 @@ abstract class Connection protected $transLabel = ''; // 错误信息 protected $error = ''; - // 数据库连接ID 支持多个连接 + + /** @var PDO[] 数据库连接ID 支持多个连接 */ protected $links = []; - // 当前连接ID + + /** @var PDO 当前连接ID */ protected $linkID; + // 查询结果类型 protected $fetchType = PDO::FETCH_ASSOC; // 字段属性大小写 @@ -207,8 +212,9 @@ abstract class Connection * @access public * @param array $config 连接参数 * @param integer $linkNum 连接序号 - * @param false|array $autoConnection 是否自动连接主数据库(用于分布式) - * @return \PDO + * @param array|bool $autoConnection 是否自动连接主数据库(用于分布式) + * @return PDO + * @throws Exception */ public function connect(array $config = [], $linkNum = 0, $autoConnection = false) { @@ -284,12 +290,14 @@ abstract class Connection /** * 执行查询 返回数据集 * @access public - * @param string $sql sql指令 + * @param string $sql sql指令 * @param array $bind 参数绑定 - * @param boolean $fetch 不执行只是获取SQL - * @param boolean $master 是否在主服务器读操作 - * @param bool $returnPdo 是否返回 PDOStatement 对象 + * @param boolean $fetch 不执行只是获取SQL + * @param boolean $master 是否在主服务器读操作 + * @param bool $returnPdo 是否返回 PDOStatement 对象 * @return mixed + * @throws DbBindParamException + * @throws PDOException */ public function query($sql, $bind = [], $fetch = false, $master = false, $returnPdo = false) { @@ -330,11 +338,13 @@ abstract class Connection /** * 执行语句 * @access public - * @param string $sql sql指令 + * @param string $sql sql指令 * @param array $bind 参数绑定 - * @param boolean $fetch 不执行只是获取SQL - * @param boolean $getLastInsID 是否获取自增ID - * @return integer + * @param boolean $fetch 不执行只是获取SQL + * @param boolean $getLastInsID 是否获取自增ID + * @return int + * @throws DbBindParamException + * @throws PDOException */ public function execute($sql, $bind = [], $fetch = false, $getLastInsID = false) { @@ -393,8 +403,8 @@ abstract class Connection $val = $this->quote(is_array($val) ? $val[0] : $val); // 判断占位符 $sql = is_numeric($key) ? - substr_replace($sql, $val, strpos($sql, '?'), 1) : - str_replace([':' . $key . ')', ':' . $key . ' '], [$val . ')', $val . ' '], $sql . ' '); + substr_replace($sql, $val, strpos($sql, '?'), 1) : + str_replace([':' . $key . ')', ':' . $key . ' '], [$val . ')', $val . ' '], $sql . ' '); } } return $sql; @@ -452,6 +462,7 @@ abstract class Connection { $this->startTrans(NOW_TIME); try { + $result = null; if (is_callable($callback)) { $result = call_user_func_array($callback, []); } @@ -466,8 +477,8 @@ abstract class Connection /** * 启动事务 * @access public - * @param string $label 事务标识 - * @return void + * @param string $label 事务标识 + * @return bool|null */ public function startTrans($label = '') { @@ -485,14 +496,15 @@ abstract class Connection } } $this->transTimes++; - return; + return null; } /** * 用于非自动提交状态下面的查询提交 * @access public - * @param string $label 事务标识 - * @return boolen + * @param string $label 事务标识 + * @return boolean + * @throws PDOException */ public function commit($label = '') { @@ -513,7 +525,8 @@ abstract class Connection /** * 事务回滚 * @access public - * @return boolen + * @return boolean + * @throws PDOException */ public function rollback() { @@ -535,7 +548,7 @@ abstract class Connection * 批处理执行SQL语句 * 批处理的指令都认为是execute操作 * @access public - * @param array $sql SQL批处理指令 + * @param array $sql SQL批处理指令 * @return boolean */ public function batchQuery($sql = []) @@ -647,7 +660,7 @@ abstract class Connection /** * SQL指令安全过滤 * @access public - * @param string $str SQL字符串 + * @param string $str SQL字符串 * @return string */ public function quote($str) @@ -659,7 +672,7 @@ abstract class Connection /** * 数据库调试 记录当前SQL * @access protected - * @param boolean $start 调试开始标记 true 开始 false 结束 + * @param boolean $start 调试开始标记 true 开始 false 结束 */ protected function debug($start) { @@ -745,10 +758,11 @@ abstract class Connection * 连接分布式服务器 * @access protected * @param boolean $master 主服务器 - * @return void + * @return PDO */ protected function multiConnect($master = false) { + $_config = []; // 分布式数据库配置解析 foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { $_config[$name] = explode(',', $this->config[$name]); @@ -759,8 +773,7 @@ abstract class Connection if ($this->config['rw_separate']) { // 主从式采用读写分离 - if ($master) - // 主服务器写入 + if ($master) // 主服务器写入 { $r = $m; } elseif (is_numeric($this->config['slave_no'])) { @@ -774,12 +787,14 @@ abstract class Connection // 读写操作不区分服务器 每次随机连接的数据库 $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]; } diff --git a/library/think/db/Query.php b/library/think/db/Query.php index 1483eb5e..bcd46730 100644 --- a/library/think/db/Query.php +++ b/library/think/db/Query.php @@ -13,12 +13,15 @@ namespace think\db; use PDO; use think\Cache; +use think\Collection; +use think\Config; use think\Db; use think\Exception; use think\exception\DbException; use think\Loader; use think\Model; use think\model\Relation; +use think\Paginator; class Query { @@ -35,7 +38,8 @@ class Query /** * 架构函数 * @access public - * @param object $connection 数据库对象实例 + * @param object|string $connection 数据库对象实例 + * @throws Exception */ public function __construct($connection = '') { @@ -49,6 +53,8 @@ class Query * @param string $method 方法名称 * @param array $args 调用参数 * @return mixed + * @throws DbException + * @throws Exception */ public function __call($method, $args) { @@ -75,7 +81,7 @@ class Query protected function builder() { static $builder = []; - $driver = $this->driver; + $driver = $this->driver; if (!isset($builder[$driver])) { $class = '\\think\\db\\builder\\' . ucfirst($driver); $builder[$driver] = new $class($this->connection); @@ -88,7 +94,7 @@ class Query /** * 得到某个字段的值 * @access public - * @param string $field 字段名 + * @param string $field 字段名 * @return mixed */ public function value($field) @@ -117,8 +123,8 @@ class Query /** * 得到某个列的数组 * @access public - * @param string $field 字段名 多个字段用逗号分隔 - * @param string $key 索引 + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 * @return array */ public function column($field, $key = '') @@ -156,7 +162,7 @@ class Query $result = []; } } - if (isset($cache)) { + if (isset($cache) && isset($guid)) { // 缓存数据 Cache::set($guid, $result, $cache['expire']); } @@ -170,7 +176,7 @@ class Query /** * COUNT查询 * @access public - * @param string $field 字段名 + * @param string $field 字段名 * @return integer */ public function count($field = '*') @@ -181,7 +187,7 @@ class Query /** * SUM查询 * @access public - * @param string $field 字段名 + * @param string $field 字段名 * @return integer */ public function sum($field = '*') @@ -193,7 +199,7 @@ class Query /** * MIN查询 * @access public - * @param string $field 字段名 + * @param string $field 字段名 * @return integer */ public function min($field = '*') @@ -205,7 +211,7 @@ class Query /** * MAX查询 * @access public - * @param string $field 字段名 + * @param string $field 字段名 * @return integer */ public function max($field = '*') @@ -217,7 +223,7 @@ class Query /** * AVG查询 * @access public - * @param string $field 字段名 + * @param string $field 字段名 * @return integer */ public function avg($field = '*') @@ -230,8 +236,8 @@ class Query * 设置记录的某个字段值 * 支持使用数据库字段和方法 * @access public - * @param string|array $field 字段名 - * @param string $value 字段值 + * @param string|array $field 字段名 + * @param string $value 字段值 * @return integer */ public function setField($field, $value = '') @@ -247,9 +253,9 @@ class Query /** * 字段值(延迟)增长 * @access public - * @param string $field 字段名 - * @param integer $step 增长值 - * @param integer $lazyTime 延时时间(s) + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) * @return integer|true * @throws \think\Exception */ @@ -274,9 +280,9 @@ class Query /** * 字段值(延迟)减少 * @access public - * @param string $field 字段名 - * @param integer $step 减少值 - * @param integer $lazyTime 延时时间(s) + * @param string $field 字段名 + * @param integer $step 减少值 + * @param integer $lazyTime 延时时间(s) * @return integer|true * @throws \think\Exception */ @@ -302,9 +308,9 @@ class Query * 延时更新检查 返回false表示需要延时 * 否则返回实际写入的数值 * @access public - * @param string $guid 写入标识 - * @param integer $step 写入步进值 - * @param integer $lazyTime 延时时间(s) + * @param string $guid 写入标识 + * @param integer $step 写入步进值 + * @param integer $lazyTime 延时时间(s) * @return false|integer */ protected function lazyWrite($guid, $step, $lazyTime) @@ -359,7 +365,7 @@ class Query } if (count($join)) { // 有设置第二个元素则把第二元素作为表前缀 - $table = (string) current($join) . $table; + $table = (string)current($join) . $table; } elseif (false === strpos($table, '.')) { // 加上默认的表前缀 $table = $prefix . $table; @@ -482,7 +488,7 @@ class Query /** * 分析查询表达式 * @access public - * @param string|array|Closure $field 查询字段 + * @param string|array|\Closure $field 查询字段 * @param mixed $op 查询表达式 * @param mixed $condition 查询条件 * @param string $operator and or @@ -491,7 +497,7 @@ class Query protected function parseWhereExp($operator, $field, $op, $condition, $param = []) { if ($field instanceof \Closure) { - call_user_func_array($field, [ & $this]); + call_user_func_array($field, [& $this]); return; } @@ -613,6 +619,51 @@ class Query return $this; } + /** + * 分页查询 + * @param int $listRows 每页数量 + * @param bool $simple 简洁模式 + * @param array $config 配置参数 + * page:当前页, + * path:url路径, + * query:url额外参数, + * fragment:url锚点, + * var_page:分页变量, + * type:分页类名, + * namespace:分页类命名空间 + * @return \think\paginator\Collection + * @throws DbException + */ + public function paginate($listRows = 15, $simple = false, $config = []) + { + $config = array_merge(Config::get('paginate'), $config); + + $class = (!empty($config['namespace']) ? $config['namespace'] : '\\think\\paginator\\driver\\') . ucwords($config['type']); + + $page = isset($config['page']) ? (int)$config['page'] : call_user_func([ + $class, + 'getCurrentPage' + ], $config['var_page']); + + $page = $page < 1 ? 1 : $page; + + $config['path'] = isset($config['path']) ? $config['path'] : call_user_func([$class, 'getCurrentPath']); + + + /** @var Paginator $paginator */ + if (!$simple) { + $options = $this->getOptions(); + $total = $this->count(); + $results = $this->options($options)->page($page, $listRows)->select(); + $paginator = new $class($results, $listRows, $page, $simple, $total, $config); + } else { + $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select(); + $paginator = new $class($results, $listRows, $page, $simple, null, $config); + } + + return $paginator->items(); + } + /** * 指定数据表 * @access public @@ -833,7 +884,7 @@ class Query /** * 指定当前模型 * @access public - * @param string $model 模型类名称 + * @param string $model 模型类名称 * @return $this */ public function model($model) @@ -876,7 +927,7 @@ class Query * 获取数据表信息 * @access public * @param string $fetch 获取信息类型 包括 fields type bind pk - * @param string $tableName 数据表名 留空自动获取 + * @param string $tableName 数据表名 留空自动获取 * @return mixed */ public function getTableInfo($tableName = '', $fetch = '') @@ -896,7 +947,7 @@ class Query if (!isset($_info[$guid])) { $info = $this->connection->getFields($tableName); $fields = array_keys($info); - $bind = $type = []; + $bind = $type = []; foreach ($info as $key => $val) { // 记录字段类型 $type[$key] = $val['type']; @@ -937,8 +988,8 @@ class Query /** * 参数绑定 * @access public - * @param mixed $key 参数名 - * @param mixed $value 绑定变量值 + * @param mixed $key 参数名 + * @param mixed $value 绑定变量值 * @param integer $type 绑定类型 * @return $this */ @@ -992,7 +1043,9 @@ class Query $i = 0; $currentModel = $this->options['model']; - $class = new $currentModel; + + /** @var Model $class */ + $class = new $currentModel; foreach ($with as $key => $relation) { $closure = false; if ($relation instanceof \Closure) { @@ -1001,10 +1054,11 @@ class Query $relation = $key; $with[$key] = $key; } elseif (is_string($relation) && strpos($relation, '.')) { - $with[$key] = $relation; + $with[$key] = $relation; list($relation, $subRelation) = explode('.', $relation, 2); } + /** @var Relation $model */ $model = $class->$relation(); $info = $class->getRelationInfo(); if (in_array($info['type'], [Relation::HAS_ONE, Relation::BELONGS_TO])) { @@ -1020,7 +1074,7 @@ class Query $this->join($joinTable . ' ' . $joinName, $name . '.' . $info['localKey'] . '=' . $joinName . '.' . $info['foreignKey'])->field(true, false, $joinTable, $joinName, $joinName . '__'); if ($closure) { // 执行闭包查询 - call_user_func_array($closure, [ & $this]); + call_user_func_array($closure, [& $this]); } $i++; } elseif ($closure) { @@ -1035,7 +1089,7 @@ class Query /** * 设置当前字段添加的表别名 * @access public - * @param string $relation 关联名称 + * @param string $via * @return Db */ public function via($via = '') @@ -1100,7 +1154,7 @@ class Query * @access public * @param mixed $data 数据 * @param boolean $replace 是否replace - * @param boolean $getLastInsID 是否获取自增ID + * @param boolean $getLastInsID 是否获取自增ID * @return integer */ public function insert(array $data, $replace = false, $getLastInsID = false) @@ -1149,8 +1203,8 @@ class Query * @access public * @param string $fields 要插入的数据表字段名 * @param string $table 要插入的数据表名 - * @param array $option 查询数据参数 - * @return integer + * @return int + * @throws \think\exception\PDOException */ public function selectInsert($fields, $table) { @@ -1166,7 +1220,9 @@ class Query * 更新记录 * @access public * @param mixed $data 数据 - * @return integer + * @return int + * @throws Exception + * @throws \think\exception\PDOException */ public function update(array $data) { @@ -1208,15 +1264,18 @@ class Query /** * 查找记录 * @access public - * @param array $options 表达式 - * @return \PDOStatement|array|string|false + * @param array $data + * @return Collection|false|\PDOStatement|string + * @throws DbException + * @throws Exception + * @throws \think\exception\PDOException */ public function select($data = []) { if ($data instanceof Query) { return $data->select(); } elseif ($data instanceof \Closure) { - call_user_func_array($data, [ & $this]); + call_user_func_array($data, [& $this]); } // 分析查询表达式 $options = $this->parseExpress(); @@ -1265,6 +1324,7 @@ class Query // 生成模型对象 $model = $options['model']; foreach ($resultSet as $key => $result) { + /** @var Model $result */ $result = new $model($result); $result->isUpdate(true); // 关联查询 @@ -1281,21 +1341,24 @@ class Query } elseif (!empty($options['fail'])) { throw new DbException('Data not Found', $options, $sql); } - return $resultSet; + return Collection::make($resultSet); } /** * 查找单条记录 * @access public * @param array $data 表达式 - * @return \think\Model|\PDOStatement|array|string|false + * @return array|false|\PDOStatement|string|Model + * @throws DbException + * @throws Exception + * @throws \think\exception\PDOException */ public function find($data = []) { if ($data instanceof Query) { return $data->find(); } elseif ($data instanceof \Closure) { - call_user_func_array($data, [ & $this]); + call_user_func_array($data, [& $this]); } // 分析查询表达式 $options = $this->parseExpress(); @@ -1403,8 +1466,9 @@ class Query /** * 创建子查询SQL * @access public - * @param array $data 表达式 + * @param bool $sub * @return string + * @throws DbException */ public function buildSql($sub = true) { @@ -1415,7 +1479,9 @@ class Query * 删除记录 * @access public * @param array $data 表达式 - * @return integer + * @return int + * @throws Exception + * @throws \think\exception\PDOException */ public function delete($data = []) { @@ -1483,10 +1549,10 @@ class Query if (isset($options['page'])) { // 根据页数计算limit list($page, $listRows) = $options['page']; - $page = $page > 0 ? $page : 1; - $listRows = $listRows > 0 ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20); - $offset = $listRows * ($page - 1); - $options['limit'] = $offset . ',' . $listRows; + $page = $page > 0 ? $page : 1; + $listRows = $listRows > 0 ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20); + $offset = $listRows * ($page - 1); + $options['limit'] = $offset . ',' . $listRows; } $this->options = []; diff --git a/library/think/exception/DbException.php b/library/think/exception/DbException.php index 1a14899e..11c1c932 100644 --- a/library/think/exception/DbException.php +++ b/library/think/exception/DbException.php @@ -18,6 +18,13 @@ use think\Exception; */ 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; diff --git a/library/think/exception/PDOException.php b/library/think/exception/PDOException.php index 971b1c54..969a0e6c 100644 --- a/library/think/exception/PDOException.php +++ b/library/think/exception/PDOException.php @@ -19,6 +19,13 @@ use think\exception\DbException; */ 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; diff --git a/library/think/model/Relation.php b/library/think/model/Relation.php index f1eecad4..c9948ea4 100644 --- a/library/think/model/Relation.php +++ b/library/think/model/Relation.php @@ -11,6 +11,7 @@ namespace think\model; +use think\Collection; use think\Db; use think\Exception; use think\Loader; @@ -26,7 +27,7 @@ class Relation // 父模型对象 protected $parent; - // 当前关联的模型类 + /** @var Model 当前关联的模型类 */ protected $model; // 中间表模型 protected $middle; @@ -118,6 +119,7 @@ class Relation */ public function eagerlyResultSet($resultSet, $relation) { + /** @var array $relations */ $relations = is_string($relation) ? explode(',', $relation) : $relation; foreach ($relations as $key => $relation) { @@ -153,15 +155,19 @@ class Relation } if (!empty($range)) { - $data = $this->eagerlyOneToMany($model, [$foreignKey => ['in', $range]], $relation, $subRelation, $closure); + $data = $this->eagerlyOneToMany($model, [ + $foreignKey => [ + 'in', + $range + ] + ], $relation, $subRelation, $closure); // 关联数据封装 foreach ($resultSet as $result) { - if (isset($data[$result->$localKey])) { - $result->__set($relation, $data[$result->$localKey]); - } else { - $result->__set($relation, []); + if (!isset($data[$result->$localKey])) { + $data[$result->$localKey] = []; } + $result->__set($relation, Collection::make($data[$result->$localKey])); } } break; @@ -177,20 +183,24 @@ class Relation if (!empty($range)) { // 查询关联数据 - $data = $this->eagerlyManyToMany($model, ['pivot.' . $foreignKey => ['in', $range]], $relation, $subRelation); + $data = $this->eagerlyManyToMany($model, [ + 'pivot.' . $foreignKey => [ + 'in', + $range + ] + ], $relation, $subRelation); // 关联数据封装 foreach ($resultSet as $result) { - if (isset($data[$result->$pk])) { - $result->__set($relation, $data[$result->$pk]); - } else { - $result->__set($relation, []); + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; } + + $result->__set($relation, Collection::make($data[$result->$pk])); } } break; } - $this->relation = []; } return $resultSet; } @@ -233,7 +243,7 @@ class Relation if (!isset($data[$result->$localKey])) { $data[$result->$localKey] = []; } - $result->__set($relation, $data[$result->$localKey]); + $result->__set($relation, Collection::make($data[$result->$localKey])); } break; case self::BELONGS_TO_MANY: @@ -247,7 +257,7 @@ class Relation if (!isset($data[$pk])) { $data[$pk] = []; } - $result->__set($relation, $data[$pk]); + $result->__set($relation, Collection::make($data[$pk])); } break; @@ -292,7 +302,8 @@ class Relation * @param array $where 关联预查询条件 * @param string $relation 关联名 * @param string $subRelation 子关联 - * @return void + * @param bool $closure + * @return array */ protected function eagerlyOneToMany($model, $where, $relation, $subRelation = '', $closure = false) { @@ -315,7 +326,7 @@ class Relation * @param array $where 关联预查询条件 * @param string $relation 关联名 * @param string $subRelation 子关联 - * @return void + * @return array */ protected function eagerlyManyToMany($model, $where, $relation, $subRelation = '') { @@ -367,7 +378,7 @@ class Relation * @access public * @param string $model 模型名 * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 + * @param string $otherKey 关联主键 * @return \think\db\Query|string */ public function belongsTo($model, $foreignKey, $otherKey) diff --git a/library/think/paginator/Collection.php b/library/think/paginator/Collection.php new file mode 100644 index 00000000..a7939a86 --- /dev/null +++ b/library/think/paginator/Collection.php @@ -0,0 +1,72 @@ + +// +---------------------------------------------------------------------- + +namespace think\paginator; + +use Exception; +use think\Paginator; + +/** + * Class Collection + * @package think\paginator + * @method integer total() + * @method integer listRows() + * @method integer currentPage() + * @method string render() + * @method Paginator fragment($fragment) + * @method Paginator appends($key, $value) + */ +class Collection extends \think\Collection +{ + + /** @var Paginator */ + protected $paginator; + + public function __construct($items = [], Paginator $paginator = null) + { + if (!$paginator instanceof Paginator) { + throw new \RuntimeException('Paginator Required!'); + } + $this->paginator = $paginator; + parent::__construct($items); + } + + public static function make($items = [], Paginator $paginator = null) + { + return new static($items, $paginator); + } + + + public function toArray() + { + try { + $total = $this->total(); + } catch (Exception $e) { + $total = null; + } + + return [ + 'total' => $total, + 'per_page' => $this->listRows(), + 'current_page' => $this->currentPage(), + 'data' => parent::toArray() + ]; + } + + public function __call($method, $args) + { + if (method_exists($this->paginator, $method)) { + return call_user_func_array([$this->paginator, $method], $args); + } else { + throw new Exception(__CLASS__ . ':' . $method . ' method not exist'); + } + } +} \ No newline at end of file diff --git a/library/think/paginator/driver/Bootstrap.php b/library/think/paginator/driver/Bootstrap.php new file mode 100644 index 00000000..be65ecfc --- /dev/null +++ b/library/think/paginator/driver/Bootstrap.php @@ -0,0 +1,207 @@ + +// +---------------------------------------------------------------------- + +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 + ]; + + $length = 3; + + if ($this->lastPage < $length * 4) { + $block['first'] = $this->getUrlRange(1, $this->lastPage); + } elseif ($this->currentPage <= $length * 2) { + $block['first'] = $this->getUrlRange(1, $length * 2 + 2); + $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); + } elseif ($this->currentPage > ($this->lastPage - $length * 2)) { + $block['first'] = $this->getUrlRange(1, 2); + $block['last'] = $this->getUrlRange($this->lastPage - $length * 2 + 2, $this->lastPage); + } else { + $block['first'] = $this->getUrlRange(1, 2); + $block['slider'] = $this->getUrlRange($this->currentPage - $length, $this->currentPage + $length); + $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( + '', + $this->getPreviousButton(), + $this->getNextButton() + ); + } else { + return sprintf( + '', + $this->getPreviousButton(), + $this->getLinks(), + $this->getNextButton() + ); + } + } + } + + + /** + * 生成一个可点击的按钮 + * + * @param string $url + * @param int $page + * @return string + */ + protected function getAvailablePageWrapper($url, $page) + { + return '
  • ' . $page . '
  • '; + } + + /** + * 生成一个禁用的按钮 + * + * @param string $text + * @return string + */ + protected function getDisabledTextWrapper($text) + { + return '
  • ' . $text . '
  • '; + } + + /** + * 生成一个激活的按钮 + * + * @param string $text + * @return string + */ + protected function getActivePageWrapper($text) + { + return '
  • ' . $text . '
  • '; + } + + /** + * 生成省略号按钮 + * + * @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); + } +} \ No newline at end of file