307 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			PHP
		
	
	
		
		
			
		
	
	
			307 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			PHP
		
	
	
|  | <?php | |||
|  | 
 | |||
|  | namespace app\service; | |||
|  | 
 | |||
|  | use Exception; | |||
|  | use think\db\exception\DataNotFoundException; | |||
|  | use think\db\exception\DbException; | |||
|  | use think\db\exception\ModelNotFoundException; | |||
|  | use think\Model; | |||
|  | use think\facade\Db; | |||
|  | use think\Collection; | |||
|  | use think\facade\Env; | |||
|  | use think\facade\Log; | |||
|  | use think\Paginator; | |||
|  | use app\exception\RepositoryException; | |||
|  | 
 | |||
|  | /** | |||
|  |  * 领域模型-仓储 | |||
|  |  * | |||
|  |  * Class Repository | |||
|  |  * @package app\service | |||
|  |  */ | |||
|  | class Repository | |||
|  | { | |||
|  |     /** | |||
|  |      * @var array 已加载对象列表 | |||
|  |      */ | |||
|  |     private static $objects = []; | |||
|  | 
 | |||
|  |     /** | |||
|  |      * @var Model 模型对象 | |||
|  |      */ | |||
|  |     protected $model; | |||
|  | 
 | |||
|  |     // 布尔值数字关系
 | |||
|  |     public const BOOL_FALSE = 0; | |||
|  |     public const BOOL_TRUE  = 1; | |||
|  | 
 | |||
|  |     /** | |||
|  |      * 获取当前子对象实列(单例形式返回) | |||
|  |      * | |||
|  |      * @param  Model|null  $model  模型对象。未指定则自动获取 | |||
|  |      * @return mixed | |||
|  |      */ | |||
|  |     public static function getInstance(Model $model = null) | |||
|  |     { | |||
|  |         $class = get_called_class(); | |||
|  | 
 | |||
|  |         if (isset(self::$objects[$class]) && self::$objects[$class] !== null) { | |||
|  |             return self::$objects[$class]; | |||
|  |         } | |||
|  | 
 | |||
|  |         $obj = new $class(); | |||
|  |         if ($model) { | |||
|  |             $obj->model = $model; | |||
|  |         } elseif (strpos($class, '\\repository') > 0) { | |||
|  |             $model = str_replace('\\repository', '\\model', $class); | |||
|  |             //去掉末尾Repository app\model\AccountRepository  =》 app\model\Account
 | |||
|  |             $model = substr($model, 0, strlen($model) - strlen('Repository')); | |||
|  |             if (class_exists($model)) { | |||
|  |                 $obj->model = new $model; | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         self::$objects[$class] = $obj; | |||
|  |         return $obj; | |||
|  |     } | |||
|  | 
 | |||
|  |     /** | |||
|  |      * @param  callable  $callback | |||
|  |      * @param  mixed  $failReturn | |||
|  |      * @param  bool  $transaction | |||
|  |      * @return mixed | |||
|  |      * @throws RepositoryException | |||
|  |      */ | |||
|  |     protected function access(callable $callback, $failReturn, bool $transaction = false) | |||
|  |     { | |||
|  |         $exception = null; | |||
|  |         try { | |||
|  |             if ($transaction) { | |||
|  |                 Db::startTrans(); | |||
|  |             } | |||
|  | 
 | |||
|  |             $r = $callback(); | |||
|  | 
 | |||
|  |             if ($transaction) { | |||
|  |                 Db::commit(); | |||
|  |             } | |||
|  | 
 | |||
|  |             if ($r) { | |||
|  |                 return $r; | |||
|  |             } | |||
|  | 
 | |||
|  |             if ($failReturn instanceof Exception) { | |||
|  |                 return null; | |||
|  |             } | |||
|  | 
 | |||
|  |             return $failReturn; | |||
|  |         } catch (Exception $e) { | |||
|  |             if ($transaction) { | |||
|  |                 Db::rollback(); | |||
|  |             } | |||
|  | 
 | |||
|  |             if ($e instanceof RepositoryException) { | |||
|  |                 throw $e; | |||
|  |             } | |||
|  | 
 | |||
|  |             $name = 'Domain - Repository - 未知错误'; | |||
|  | 
 | |||
|  |             $traces = $e->getTrace(); | |||
|  | 
 | |||
|  |             foreach ($traces as $i => $trace) { | |||
|  |                 if (!empty($trace['class']) && $trace['class'] === Repository::class && $trace['function'] === 'access') { | |||
|  |                     $trace = $traces[$i - 2] ?? null; | |||
|  |                     break; | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             if (!empty($trace) && !empty($trace['file'][1]) && !empty($trace['line'])) { | |||
|  |                 $parts = explode('application\\', $trace['file']); | |||
|  |                 if (!empty($parts[1])) { | |||
|  |                     $name = $parts[1].':'.$trace['line']; | |||
|  |                 } else { | |||
|  |                     $name = $trace['file'].':'.$trace['line']; | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             $exception = $e; | |||
|  | 
 | |||
|  |             $line = '['.$name.'] '.$e->getMessage(); | |||
|  |             Log::error($line); | |||
|  | 
 | |||
|  |             throw new RepositoryException('Repository异常'.(Env::get('app_debug') ? ': '.$line : ''), 5009); | |||
|  |         } finally { | |||
|  |             if ($exception && $failReturn instanceof RepositoryException) { | |||
|  |                 if (Env::get('app_debug')) { | |||
|  |                     $failReturn = new RepositoryException($failReturn->getMessage().': '.$exception->getMessage(), | |||
|  |                         $failReturn->getCode()); | |||
|  |                 } | |||
|  |                 throw $failReturn; | |||
|  |             } | |||
|  |         } | |||
|  |     } | |||
|  | 
 | |||
|  |     /** | |||
|  |      * 获取当前model对象 | |||
|  |      * | |||
|  |      * @return Model | |||
|  |      */ | |||
|  |     public function getModel(): Model | |||
|  |     { | |||
|  |         return $this->model; | |||
|  |     } | |||
|  | 
 | |||
|  |     /** | |||
|  |      * 根据条件查询列表 | |||
|  |      * | |||
|  |      * @param  array  $where  查询条件 | |||
|  |      * @param  array  $fields  查询字段 []表示全部 | |||
|  |      * @param  int  $page  默认第一页 0不限制 | |||
|  |      * @param  int  $limit  限制条数 0不限制 | |||
|  |      * @param  callable|null  $callback 更为复杂的条件 使用闭包查询 | |||
|  |      * @return array | |||
|  |      * @throws RepositoryException | |||
|  |      */ | |||
|  |     public function findList(array $where = [], array $fields = [], int $page = 1, int $limit = 0, callable $callback = null, array $order = []): ?array | |||
|  |     { | |||
|  |         $failData = [ | |||
|  |             'total'   => 0, | |||
|  |             'current' => $page, | |||
|  |             'size'    => $limit, | |||
|  |             'list'    => new Collection(), | |||
|  |         ]; | |||
|  |         return $this->access(function () use ($where, $fields, $page, $limit, $callback, $order) { | |||
|  |             return $this->model->findList($where, $fields, $page, $limit, $callback, $order); | |||
|  |         }, $failData); | |||
|  |     } | |||
|  | 
 | |||
|  |     /** | |||
|  |      * 根据条件查询列表[带分页 适用于后台] | |||
|  |      * | |||
|  |      * @param  array  $data  查询数据 | |||
|  |      * @param  array  $pageParams  分页参数 | |||
|  |      * @param  callable|null  $callback 复杂查询条件  使用闭包查询 | |||
|  |      * @return Paginator | |||
|  |      */ | |||
|  |     public function findListWithPaginate(array $data = [], array $pageParams = [], callable $callback = null): Paginator | |||
|  |     { | |||
|  |         return $this->model->findListWithPaginate($data, $pageParams, $callback); | |||
|  |     } | |||
|  | 
 | |||
|  |     /** | |||
|  |      * 根据主键 ID 查询 | |||
|  |      * | |||
|  |      * @param  int  $id  ID | |||
|  |      * @param  array  $fields  要返回的字段,默认全部 | |||
|  |      * @return Mixed | |||
|  |      * @throws RepositoryException | |||
|  |      */ | |||
|  |     public function findById(int $id, array $fields = [], callable $callback = null) | |||
|  |     { | |||
|  |         return $this->access(function () use ($id, $fields, $callback) { | |||
|  |             return $this->model->findById($id, $fields, $callback); | |||
|  |         }, null); | |||
|  |     } | |||
|  | 
 | |||
|  |     /** | |||
|  |      * @param  array  $where | |||
|  |      * @param  array  $fields | |||
|  |      * @return array|Model|null | |||
|  |      * @throws DataNotFoundException | |||
|  |      * @throws DbException | |||
|  |      * @throws ModelNotFoundException | |||
|  |      */ | |||
|  |     public function findOneByWhere(array $where, array $fields = []) | |||
|  |     { | |||
|  |         return $this->model->field($fields)->where($where)->find(); | |||
|  |     } | |||
|  | 
 | |||
|  |     /** | |||
|  |      * 创建 | |||
|  |      * | |||
|  |      * @param  array  $data  数据 | |||
|  |      * @return Model | |||
|  |      * @throws RepositoryException | |||
|  |      */ | |||
|  |     public function create(array $data): Model | |||
|  |     { | |||
|  |         return $this->access(function () use ($data) { | |||
|  |             return $this->model->create($data); | |||
|  |         }, new RepositoryException('创建失败')); | |||
|  |     } | |||
|  | 
 | |||
|  |     /** | |||
|  |      * 更新 | |||
|  |      * | |||
|  |      * @param  array  $data  数据 | |||
|  |      * @param  array  $where  条件 | |||
|  |      * @return bool|Exception | |||
|  |      * @throws RepositoryException | |||
|  |      */ | |||
|  |     public function update(array $data, array $where): bool | |||
|  |     { | |||
|  |         return $this->access(function () use ($data, $where) { | |||
|  |             return $this->model->where($where)->find()->save($data); | |||
|  |         }, new RepositoryException('更新失败')); | |||
|  |     } | |||
|  | 
 | |||
|  |     /** | |||
|  |      * 删除 | |||
|  |      * | |||
|  |      * @param  array  $where  删除条件 | |||
|  |      * @param  bool  $softDelete  是否软删除 默认false | |||
|  |      * @param  string  $softDeleteTime  删除时间 softDelete=true时有效 | |||
|  |      * @param  string  $softDeleteField  软删除字段 softDelete=true时有效 | |||
|  |      * @return bool | |||
|  |      * @throws RepositoryException | |||
|  |      */ | |||
|  |     public function delete(array $where, bool $softDelete = false, string $softDeleteTime = '', string $softDeleteField = 'deleted_at'): bool | |||
|  |     { | |||
|  |         return $this->access(function () use ($where, $softDelete, $softDeleteField, $softDeleteTime) { | |||
|  |             // 注意:如果model中引入了软删除trait,$softDelete又设置false 将无法正确删除
 | |||
|  |             return $this->model->where($where) | |||
|  |                 ->when($softDelete, function ($q) use ($softDeleteField, $softDeleteTime) { | |||
|  |                     $softDeleteTime = $softDeleteTime ?: date('Y-m-d H:i:s'); | |||
|  |                     $q->useSoftDelete($softDeleteField, $softDeleteTime); | |||
|  |                 })->delete(); | |||
|  |         }, false); | |||
|  |     } | |||
|  | 
 | |||
|  |     /** | |||
|  |      * 排序 | |||
|  |      * | |||
|  |      * @param  int  $id 排序ID | |||
|  |      * @param  string  $type 排序类型 向上、向下 | |||
|  |      * @param  int  $num 移动位数 | |||
|  |      * @param  string  $listType 列表的排序类型  降序|升序 | |||
|  |      * @param  array  $where 额外条件 格式如: | |||
|  |      *                                  $map[] = ['name','like','think']; | |||
|  |      *                                  $map[] = ['status','=',1]; | |||
|  |      * @return array | |||
|  |      */ | |||
|  |     public function sort(int $id,string $type,int $num,string $listType, array $where = []): array | |||
|  |     { | |||
|  |         return $this->model->sort($id, $type, $num, $listType, $where); | |||
|  |     } | |||
|  | 
 | |||
|  |     /** | |||
|  |      * 日志记录 | |||
|  |      * | |||
|  |      * @param  string  $msg | |||
|  |      * @param  Exception|null  $e | |||
|  |      * @param  string  $level | |||
|  |      * @param  string  $channel | |||
|  |      */ | |||
|  |     public static function log(string $msg, Exception $e = null, string $level = 'error', string $channel = 'file') | |||
|  |     { | |||
|  |         if ($e != null) { | |||
|  |             $msg = sprintf("[%s]%s:%s %s", $msg, $e->getFile(), $e->getLine(), $e->getMessage()); | |||
|  |         } else { | |||
|  |             $msg = sprintf("%s", $msg); | |||
|  | 
 | |||
|  |         } | |||
|  |         Log::channel($channel)->$level($msg); | |||
|  |     } | |||
|  | } |