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异常'.$line.(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);
 | 
						||
    }
 | 
						||
}
 |