diff --git a/app/Repository/BaseRepository.php b/app/Repository/BaseRepository.php index c2c099c..f21e6e6 100644 --- a/app/Repository/BaseRepository.php +++ b/app/Repository/BaseRepository.php @@ -3,21 +3,17 @@ declare(strict_types=1); namespace App\Repository; -use App\Helper\ArrayHelper; -use App\Traits\PagingTrait; use Hyperf\Database\Model\Model; use Hyperf\Database\Model\Builder; use Hyperf\DbConnection\Db; -use Hyperf\Utils\Arr; use Hyperf\Utils\Collection; -use Exception; /** - * Class BaseRepository + * Class BaseRepository 基础 Repository 类 * * @method Model create(array $values) 新增数据 * @method boolean insert(array $values) 新增数据 - * @method int|mixed insertGetId(array $values, $sequence = null) 新增数据获取新增ID + * @method int|mixed insertGetId(array $values) 新增数据并获取新增ID * @method Model firstOrCreate(array $attributes, array $value = []) 查询数据没有就创建 * @method Model firstOrNew(array $attributes, array $value = []) 查询数据没有就实例化 * @method Model updateOrCreate(array $attributes, array $value = []) 查询修改没有就创建 @@ -46,235 +42,7 @@ use Exception; */ abstract class BaseRepository { - use PagingTrait; - - /** - * @var Model - */ - private $model; - - /** - * 自带查询的表达式 - * - * @var string[] - */ - private $operators = [ - '=', - '<', - '>', - '<=', - '>=', - '<>', - '!=', - '<=>', - 'like', - 'like binary', - 'not like', - 'ilike', - '&', - '|', - '^', - '<<', - '>>', - 'rlike', - 'regexp', - 'not regexp', - '~', - '~*', - '!~', - '!~*', - 'similar to', - 'not similar to', - 'not ilike', - '~~*', - '!~~*' - ]; - - /** - * 扩展查询的表达式 - * - * @var string[] - */ - private $expression = [ - 'eq' => '=', - 'neq' => '!=', - 'ne' => '!=', - 'gt' => '>', - 'egt' => '>=', - 'gte' => '>=', - 'ge' => '>=', - 'lt' => '<', - 'le' => '<=', - 'lte' => '<=', - 'elt' => '<=', - 'in' => 'In', - 'not in' => 'NotIn', - 'between' => 'Between', - 'not between' => 'NotBetween', - 'like' => 'like', - 'not like' => 'not like', - 'rlike' => 'rlike', - '<>' => '<>', - '<=>' => '<=>', - ]; - - /** - * Model 不需要查询条件的方法 - * - * @var string[] - */ - private $origin = [ - 'create', 'insert', 'insertGetId', 'getConnection', 'firstOrCreate', 'firstOrNew', - 'updateOrCreate', 'findOrFail', 'findOrNew', 'updateOrInsert', 'find' - ]; - - /** - * 父类需要查询条件的相关方法 - * - * @var string[] - */ - private $parent = [ - 'count', 'max', 'min', 'avg', 'sum', - 'increment', 'decrement', - 'value', 'pluck', - 'exists', 'doesntExist' - ]; - - /** - * BaseRepository constructor. - * @param Model $model - */ - public function __construct(Model $model) - { - $this->model = $model; - } - - /** - * 调用 model 的方法 - * - * @param string $method 调用model 自己的方法 - * @param array $arguments - * - * @return mixed - * @throws \Exception - */ - public function __call(string $method, array $arguments) - { - // 直接使用 model, 不需要查询条件的数据 - if (in_array($method, $this->origin)) { - return (new $this->model)->{$method}(...$arguments); - } - - // 调用 model 原生方法 - if (in_array($method, $this->parent)) { - $where = Arr::pull($arguments, '0', []); - return $this->buildWhere($where)->{$method}(...$arguments); - } - - throw new \Exception("Uncaught Error: Call to undefined method {$method}"); - } - - /** - * 获取新的查询 Model - * - * @return Builder - */ - protected function getNewModel(): Builder - { - return $this->model->newQuery(); - } - - /** - * 处理 where 条件 - * - * @param array $where - * @return Builder - */ - final public function buildWhere(array $where = []): Builder - { - $model = $this->getNewModel(); - - // 处理排序数据 - if ($order = Arr::pull($where, 'order by')) { - $this->addOrderBy($model, (array)$order); - } - - // 处理分组数据 - if ($group = Arr::pull($where, 'group by')) { - $this->addGroupBy($model, (array)$group); - } - - // 处理分组数据 - if ($having = Arr::pull($where, 'having by')) { - $this->addHaving($model, (array)$having); - } - - // 判断是否存在查询条件 - if (!empty($where)) { - $this->bindWhere($model, $where); - } - - return $model; - } - - /** - * @param Builder $model - * @param array $where - * @param bool $or - * @throws Exception - */ - final private function bindWhere(Builder $model, array $where, $or = false) - { - foreach ($where as $field => $item) { - if ($field === 'or' || $field === 'and') { - $this->addNewWhere($model, $item, $or, $field); - continue; - } - - if (is_int($field)) { - if ($this->isModelQueryArray($item)) { - $model->{$or ? 'orWhere' : 'where'}(...$item); - continue; - } - - $this->addNewWhere($model, $item, $or, $field); - continue; - } - - // 字段查询 - $this->setFieldWhere($model, $field, $item, $or); - } - } - - /** - * 添加 where 条件分组 - * - * @param Builder $model - * @param array $where - * @param bool $or - * @param string $field - * @throws Exception - */ - final private function addNewWhere(Builder $model, array $where, $or = false, $field = '') - { - $method = $or ? 'orWhere' : 'where'; - - $model->{$method}(function ($query) use ($where, $or, $field) { - $this->bindWhere($query, $where, $field === 'or'); - }); - } - - /** - * 根据条件更新数据 - * - * @param array $where - * @param array $values - * @return int - */ - final public function update(array $where, array $values): int - { - return $this->buildWhere($where)->update($values); - } + use RepositoryTrait; /** * 获取单条数据 @@ -284,9 +52,9 @@ abstract class BaseRepository * @param bool $is_array 是否返回数组格式 * @return Builder|Model|object|array|null */ - public function first(array $where = [], array $fields = ['*'], bool $is_array = false) + final public function first(array $where = [], array $fields = ['*'], bool $is_array = false) { - $this->handleFindField($fields); + $this->handleField($fields); $data = $this->buildWhere($where)->first($fields); @@ -305,9 +73,9 @@ abstract class BaseRepository * @param bool $is_array 是否返回数组格式 * @return Collection|array */ - public function get(array $where = [], array $fields = ['*'], bool $is_array = false) + final public function get(array $where = [], array $fields = ['*'], bool $is_array = false) { - $this->handleFindField($fields); + $this->handleField($fields); $data = $this->buildWhere($where)->get($fields); @@ -325,189 +93,110 @@ abstract class BaseRepository * @param int $size 每页条数 * @return array */ - public function paginate(array $where, $fields = ['*'], $page = 1, $size = 10): array + final public function paginate(array $where, $fields = ['*'], $page = 1, $size = 15): array { - $this->handleFindField($fields); + $this->handleField($fields); - $result = $this->buildWhere($where)->paginate($size, $fields, 'page', $page); + $model = $this->buildWhere($where); - if (empty($result)) { - return $this->getPagingRows([], 0, $page, $size); + return $this->toPaginate($model, $fields, $page, $size); + } + + /** + * 通过 model 读取分页信息 + * + * @param Builder $model 查询 Model + * @param array $fields 查询字段 + * @param int $page 当前分页 + * @param int $size 分页大小 + * @return array + */ + public function toPaginate(Builder $model, array $fields = ['*'], int $page = 1, int $size = 15): array + { + $total = $model->count(); + + $data = [ + 'rows' => [], + 'paginate' => [ + 'page' => $page, + 'size' => $size, + 'total' => $total, + ] + ]; + + if ($total > 0) { + $data['rows'] = $model->forPage($page, $size)->get($fields)->toArray(); } - return $this->getPagingRows(collect($result->items())->toArray(), $result->total(), $page, $size); + return $data; + } + + /** + * 根据条件更新数据 + * + * @param array $where 查询条件 + * @param array $values 更新字段 + * @return int + */ + final public function update(array $where, array $values): int + { + return $this->buildWhere($where)->update($values); + } + + /** + * 批量更新数据 + * + * @param array $where 查询条件 + * @param array $values 更新字段 + * @return int + */ + final public function batchUpdate(array $where, array $values): int + { + $data = []; + foreach ($values as $field => $item) { + if (!is_array($item)) { + $data[$field] = $item; + continue; + } + + $when = ''; + foreach ($item['filter'] as $k => $v) { + $when .= sprintf(" when '%s' then '%s'", $k, $v); + } + + $key = $item['field'] ?? $field; + + $string = "case $key {$when} else '{$item['default']}' end"; + + $data[$field] = Db::raw($string); + } + + if (empty($data)) return 0; + + return $this->buildWhere($where)->update($data); } /** * 打印查询 sql * - * @param array $where + * @param array $where 查询条件 * @return string */ - public function toSql(array $where): string + final public function toSql(array $where): string { return $this->buildWhere($where)->toSql(); } /** - * 添加排序信息 + * 原生 sql 查询 * - * @param Builder $model - * @param array $orders - */ - private function addOrderBy(Builder $model, array $orders) - { - foreach ($orders as $field => $sort) { - if ($this->isBackQuote($field)) { - $model->orderByRaw($this->trimBackQuote($field) . ' ' . $sort); - } else { - $model->orderBy($field, $sort); - } - } - } - - /** - * 添加分组信息 - * - * @param Builder $model - * @param array $groups - */ - private function addGroupBy(Builder $model, array $groups) - { - $model->groupBy(...$groups); - } - - /** - * @param Builder $model - * @param array $items - */ - private function addHaving(Builder $model, array $items = []) - { - foreach ($items as $having) { - $model->{count($having) == 2 ? 'havingRaw' : 'having'}(...$having); - } - } - - /** - * 设置条件查询 - * - * @param Builder $model - * @param string $field - * @param string|int|array $value - * @param bool $or - * @return void - * @throws \Exception - */ - private function setFieldWhere(Builder $model, string $field, $value, $or = false): void - { - [$field, $operator] = $this->formatField($field); - - // 查询数据是数组且未设置表达式,默认是 In 查询 - if ($operator === 'eq' && is_array($value)) { - $operator = 'in'; - } - - // 验证查询表达式 - if (!isset($this->expression[$operator])) { - throw new \Exception("无效的 {$operator} 操作符!"); - } - - $method = $or ? 'orWhere' : 'where'; - - // 数组查询方式 - if (in_array($this->expression[$operator], ['In', 'NotIn', 'Between', 'NotBetween'], true)) { - $method = $method . $this->expression[$operator]; - $model->{$method}($field, (array)$value); - return; - } - - $model->{$method}($field, $this->expression[$operator], $value); - } - - /** - * 解析查询字段信息 - * - * @param string $field 查询字段 + * @param string $query + * @param array $bindings + * @param bool $useReadPdo * @return array */ - private function formatField(string $field): array + final public function sql(string $query, array $bindings = [], bool $useReadPdo = true): array { - $item = explode(':', $field); - $field = $item[0]; - $operator = $item[1] ?? 'eq'; - - return [strtolower($field), trim($operator)]; - } - - /** - * 处理查询字段 - * - * @param array $fields 查询字段 - */ - private function handleFindField(array &$fields) - { - foreach ($fields as $k => $field) { - $fields[$k] = $this->raw($field); - } - } - - /** - * 去除字段串两端反引号 - * - * @param string $field - * @return \Hyperf\Database\Query\Expression|string - */ - private function raw(string $field) - { - // 匹配使用反引号的字段 - if (!$this->isBackQuote($field)) return $field; - - return Db::raw($this->trimBackQuote($field)); - } - - /** - * 判断字符串是否被反引号包含 - * - * @param string $string - * @return bool - */ - private function isBackQuote(string $string): bool - { - return (bool)preg_match("/^`.*?`$/", $string); - } - - /** - * 去除字符串两端的反引号 - * - * @param string $field - * @return string - */ - private function trimBackQuote(string $field): string - { - return substr($field, 1, strlen($field) - 2); - } - - /** - * 判断是否是调用 where 最基本的方式是需要传递三个参数 - * - * @param array $items - * @return bool - */ - private function isModelQueryArray(array $items): bool - { - if (count($items) != 3) { - return false; - } - - // 判断是否是关联数组 - if (ArrayHelper::isRelationArray($items)) { - return false; - } - - foreach ($items as $item) { - if (is_array($item)) return false; - } - - return in_array($items[1], $this->operators); + return Db::select($query, $bindings, $useReadPdo); } } diff --git a/app/Repository/ExampleRepository.php b/app/Repository/ExampleRepository.php index 7706ea5..2e9dd80 100644 --- a/app/Repository/ExampleRepository.php +++ b/app/Repository/ExampleRepository.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Repository; use App\Helper\HashHelper; +use App\Model\Talk\TalkRecords; use App\Model\User; use Hyperf\Utils\Str; @@ -131,6 +132,7 @@ class ExampleRepository extends BaseRepository // 根据主键ID查询数据 // $this->find(2054, ['id', 'mobile']); + // 主键查询没有就抛出错误 // $this->findOrFail(20540000, ['id', 'mobile']); @@ -157,10 +159,11 @@ class ExampleRepository extends BaseRepository // ], ['id', 'mobile'],true); // 分页获取数据 - // $this->paginate([ - // 'id:gt' => 20540000, - // 'gender' => 2 - // ], ['*'], 1, 15); + // $data = $this->paginate([ + // 'id:gt' => 2054, + // ], ['id', 'mobile'], 1, 5); + // + // var_dump($data); // 打印查询 sql 语句 // $this->toSql([ @@ -181,6 +184,24 @@ class ExampleRepository extends BaseRepository // ], // ] // ]); + + // 原生 SQL 查询 + // $this->sql('SELECT * FROM `lar_users` WHERE id = ?', [2054]); + + // 批量更新数据 + // $this->batchUpdate([ + // 'id:gt' => 2054 + // ], [ + // 'email' => '', // 不使用条件判断,默认更新 + // 'gender' => [ + // 'field' => 'id',//判断的字段,可选(不设置默认使用当前字段) + // 'default' => 0, // 默认字段值 + // 'filter' => [ // 数据判断 + // '2054' => 1, + // '2055' => 2, + // ] + // ], + // ]); } // where 条件查询案例 diff --git a/app/Repository/RepositoryTrait.php b/app/Repository/RepositoryTrait.php new file mode 100644 index 0000000..423b25a --- /dev/null +++ b/app/Repository/RepositoryTrait.php @@ -0,0 +1,395 @@ +', + '<=', + '>=', + '<>', + '!=', + '<=>', + 'like', + 'like binary', + 'not like', + 'ilike', + '&', + '|', + '^', + '<<', + '>>', + 'rlike', + 'regexp', + 'not regexp', + '~', + '~*', + '!~', + '!~*', + 'similar to', + 'not similar to', + 'not ilike', + '~~*', + '!~~*' + ]; + + /** + * 扩展查询的表达式 + * + * @var string[] + */ + private $expression = [ + 'eq' => '=', + 'neq' => '!=', + 'ne' => '!=', + 'gt' => '>', + 'egt' => '>=', + 'gte' => '>=', + 'ge' => '>=', + 'lt' => '<', + 'le' => '<=', + 'lte' => '<=', + 'elt' => '<=', + 'in' => 'In', + 'not in' => 'NotIn', + 'between' => 'Between', + 'not between' => 'NotBetween', + 'like' => 'like', + 'not like' => 'not like', + 'rlike' => 'rlike', + '<>' => '<>', + '<=>' => '<=>', + ]; + + /** + * Model 不需要查询条件的方法 + * + * @var string[] + */ + private $origin = [ + 'create', 'insert', 'insertGetId', + 'firstOrCreate', 'firstOrNew', + 'updateOrCreate', 'updateOrInsert', + 'findOrFail', 'findOrNew', 'find', + 'getConnection', + ]; + + /** + * 父类需要查询条件的相关方法 + * + * @var string[] + */ + private $parent = [ + 'count', 'max', 'min', 'avg', 'sum', + 'increment', 'decrement', + 'value', 'pluck', + 'exists', 'doesntExist' + ]; + + /** + * @var Model + */ + private $model; + + /** + * BaseRepository constructor. + * @param Model $model + */ + public function __construct(Model $model) + { + $this->model = $model; + } + + /** + * 调用 model 的方法 + * + * @param string $method 调用model 自己的方法 + * @param array $arguments + * + * @return mixed + * @throws \Exception + */ + public function __call(string $method, array $arguments) + { + // 直接使用 model, 不需要查询条件的数据 + if (in_array($method, $this->origin)) { + return (new $this->model)->{$method}(...$arguments); + } + + // 调用 model 原生方法 + if (in_array($method, $this->parent)) { + $where = Arr::pull($arguments, '0', []); + return $this->buildWhere($where)->{$method}(...$arguments); + } + + throw new \Exception("Uncaught Error: Call to undefined method {$method}"); + } + + /** + * 获取新的查询 Model + * + * @return Builder + */ + protected function getNewModel(): Builder + { + return $this->model->newQuery(); + } + + /** + * 构建 Where 查询 Model + * + * @param array $where + * @return Builder + */ + final public function buildWhere(array $where = []): Builder + { + $model = $this->getNewModel(); + + // 处理排序数据 + if ($order = Arr::pull($where, 'order by')) { + $this->addOrderBy($model, (array)$order); + } + + // 处理分组数据 + if ($group = Arr::pull($where, 'group by')) { + $this->addGroupBy($model, (array)$group); + } + + // 处理分组数据 + if ($having = Arr::pull($where, 'having by')) { + $this->addHaving($model, (array)$having); + } + + // 判断是否存在查询条件 + if (!empty($where)) { + $this->bindWhere($model, $where); + } + + return $model; + } + + /** + * @param Builder $model + * @param array $where + * @param bool $or + * @throws Exception + */ + private function bindWhere(Builder $model, array $where, $or = false) + { + foreach ($where as $field => $item) { + if ($field === 'or' || $field === 'and') { + $this->addNewWhere($model, $item, $or, $field); + continue; + } + + if (is_int($field)) { + if ($this->isModelQueryArray($item)) { + $model->{$or ? 'orWhere' : 'where'}(...$item); + continue; + } + + $this->addNewWhere($model, $item, $or, $field); + continue; + } + + // 字段查询 + $this->setFieldWhere($model, $field, $item, $or); + } + } + + /** + * 添加 where 条件分组 + * + * @param Builder $model + * @param array $where + * @param bool $or + * @param string $field + * @throws Exception + */ + private function addNewWhere(Builder $model, array $where, $or = false, $field = '') + { + $method = $or ? 'orWhere' : 'where'; + + $model->{$method}(function ($query) use ($where, $or, $field) { + $this->bindWhere($query, $where, $field === 'or'); + }); + } + + /** + * 添加排序信息 + * + * @param Builder $model + * @param array $orders + */ + private function addOrderBy(Builder $model, array $orders) + { + foreach ($orders as $field => $sort) { + if ($this->isBackQuote($field)) { + $model->orderByRaw($this->trimBackQuote($field) . ' ' . $sort); + } else { + $model->orderBy($field, $sort); + } + } + } + + /** + * 添加分组信息 + * + * @param Builder $model + * @param array $groups + */ + private function addGroupBy(Builder $model, array $groups) + { + $model->groupBy(...$groups); + } + + /** + * @param Builder $model + * @param array $items + */ + private function addHaving(Builder $model, array $items = []) + { + foreach ($items as $having) { + $model->{count($having) == 2 ? 'havingRaw' : 'having'}(...$having); + } + } + + /** + * 设置条件查询 + * + * @param Builder $model + * @param string $field + * @param string|int|array $value + * @param bool $or + * @return void + * @throws \Exception + */ + private function setFieldWhere(Builder $model, string $field, $value, $or = false): void + { + [$field, $operator] = $this->formatField($field); + + // 查询数据是数组且未设置表达式,默认是 In 查询 + if ($operator === 'eq' && is_array($value)) { + $operator = 'in'; + } + + // 验证查询表达式 + if (!isset($this->expression[$operator])) { + throw new \Exception("无效的 {$operator} 操作符!"); + } + + $method = $or ? 'orWhere' : 'where'; + + // 数组查询方式 + if (in_array($this->expression[$operator], ['In', 'NotIn', 'Between', 'NotBetween'], true)) { + $method = $method . $this->expression[$operator]; + $model->{$method}($field, (array)$value); + return; + } + + $model->{$method}($field, $this->expression[$operator], $value); + } + + /** + * 解析查询字段信息 + * + * @param string $field 查询字段 + * @return array + */ + private function formatField(string $field): array + { + $item = explode(':', $field); + $field = $item[0]; + $operator = $item[1] ?? 'eq'; + + return [strtolower($field), trim($operator)]; + } + + /** + * 处理查询字段 + * + * @param array $fields 查询字段 + */ + private function handleField(array &$fields) + { + foreach ($fields as $k => $field) { + $fields[$k] = $this->raw($field); + } + } + + /** + * 去除字段串两端反引号 + * + * @param string $field + * @return \Hyperf\Database\Query\Expression|string + */ + private function raw(string $field) + { + // 匹配使用反引号的字段 + if (!$this->isBackQuote($field)) return $field; + + return Db::raw($this->trimBackQuote($field)); + } + + /** + * 判断字符串是否被反引号包含 + * + * @param string $string + * @return bool + */ + private function isBackQuote(string $string): bool + { + return (bool)preg_match("/^`.*?`$/", $string); + } + + /** + * 去除字符串两端的反引号 + * + * @param string $field + * @return string + */ + private function trimBackQuote(string $field): string + { + return substr($field, 1, strlen($field) - 2); + } + + /** + * 判断是否是调用 where 最基本的方式是需要传递三个参数 + * + * @param array $items + * @return bool + */ + private function isModelQueryArray(array $items): bool + { + if (count($items) != 3) { + return false; + } + + // 判断是否是关联数组 + if (ArrayHelper::isRelationArray($items)) { + return false; + } + + foreach ($items as $item) { + if (is_array($item)) return false; + } + + return in_array($items[1], $this->operators); + } +} diff --git a/composer.json b/composer.json index 6ddb49b..dc9611c 100644 --- a/composer.json +++ b/composer.json @@ -37,8 +37,7 @@ "hyperf/view-engine": "2.1.*", "phpmailer/phpmailer": "^6.2", "96qbhy/hyperf-auth": "^2.3", - "hyperf/event": "2.1.*", - "hyperf/paginator": "2.1.*" + "hyperf/event": "2.1.*" }, "require-dev": { "swoole/ide-helper": "^4.5",