• 从零开始写一个PHP开发框架websocket框架


    前言

    本项目基于workerman实现支持websocket、tcp协议的PHP框架,mvc模式,跟传统http框架运行原理相差不多,只是入口依赖于workerman接收到的消息
    项目源码可以移步github参考,欢迎star
    https://github.com/feixuekeji/flysocket

    入口

    入口文件是GatewayWorker下Event.php
    入口处需要引入下自动加载函数,以便框架里函数调用
    require_once DIR . ‘/…/lib/Autoloader.php’;
    主要有两个函数
    onWorkerStart 项目启动时初始化框架
    onMessage 接受到客户端信息,收到消息转发给框架执行请求,相当于收到http请求信息

    require_once __DIR__ . '/../lib/Autoloader.php';
    
    /**
         * 进程启动后初始化
         */
        public static function onWorkerStart($worker)
        {
            // 执行应用并响应
            Container::get('app')->init($worker->id);
    
    
        }
    
        /**
    
    
       /**
        * 当客户端发来消息时触发
        * @param int $client_id 连接id
        * @param mixed $message 具体消息
        */
       public static function onMessage($client_id, $message)
       {
           if ($message == 'ping')
               return;
           App::run($client_id, $message);
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    app

    app主要包含框架初始化跟执行两个功能
    初始化init函数主要功能包括
    路由加载,Session,数据库等初始化

    运行函数run 负责收到请求后具体执行,
    解析请求数据
    组装request请求对象
    路由分发执行
    将执行结果返回客户端

    App.php

    public function __construct()
        {
            $this->rootPath = __DIR__ . DIRECTORY_SEPARATOR . '../';
            $this->routePath   = $this->rootPath . 'route' . DIRECTORY_SEPARATOR;
            $this->configPath  = $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
            $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
        }
    
        public function run($client_id, $message)
        {
            try {
                $message = json_decode($message,true);
                !is_array($message) && $message = [];
                $request = Container::get('request',[$message]);
                $res = Route::dispatch($request);
                $response = $request->response($res['data'],$res['code'],$res['msg']);
            } catch (\Throwable $e) {
                Error::exception($e);
                $response = $request->response('',$e->getCode() ?: 1,$e->getMessage());
            }
    //        Log::info('response',$response);
            // 向当前client_id发送数据
            Gateway::sendToClient($client_id, json_encode($response));
            //清空request
            Container::remove('request');
    
        }
    
        /**
         *初始化
         * @author xingxiong.fei@163.com
         * @date 2020-09-03 9:43
         */
        public function init($workerId)
        {
            try {
                $log = Container::get('log',[Config::get('','log')]);
                $cache = Container::get('cache',[Config::get('','cache')]);
                Container::get('session',[Config::get('','session')]);
                //加载路由
                Container::get('route')->import();
                //数据库初始化
                Db::setConfig(Config::get('','database'));
                Db::setCache($cache);
                Db::setLog($log);
                $workerId == 0 && $this->corn();
            } catch (\Exception $e) {
                Error::exception($e);
            }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    自动加载

    spl_autoload_register(‘\lib\Autoloader::load’); 注册自动加载函数,程序执行时如果该文件没有加载会调用自动加载函数进行文件引入。

    采用命名空间方式定义和自动加载类库文件,遵循psr-0规范,只需要给类库正确定义所在的命名空间,并且命名空间的路径与类库文件的目录一致,那么就可以实现类的自动加载
    命名空间跟文件路径一致,只需将类名中的斜杠转成反斜杠就是文件所在路径,如果有更多规则,可具体判断加载

    如Admin.php的命名空间为 namespace application\admin\controller;则加载的文件路径为application/admin/controller/Admin.php

    Autoloader.php

    namespace lib;
    
    /**
     * 自动加载
     */
    class Autoloader
    {
        public static function load($className)
        {
            $classPath = str_replace('\\', '/', $className);
            $classFile = __DIR__ .'/../'.$classPath.'.php';
            if (is_file($classFile)) {
                require_once($classFile);
                if (class_exists($className, false)) {
                    return true;
                }
            }
            return false;
        }
    }
    
    spl_autoload_register('\lib\Autoloader::load');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    路由分发

    dispatch函数
    根据request请求里的模块、控制器、方法名,拼装出需要调用的类名,
    调用目标类
    然后使用call_user_func_array调用类中方法执行

    import
    读取路由配置文件,加载路由表,Request里获取模块名,控制器,方法时用到

    Route.php

    class Route
    {
    
        /**
         * 路由表
         * @var array
         */
        protected $routeList = [];
    
        /**
         * 路由分发
         * @param Request $request
         * @return mixed|void
         */
        public static function dispatch(Request $request)
        {
            $module = $request->module();
            $controller = $request->controller();
            $action = $request->action();
            if (!$module || !$controller || !$action)
               throw new \Exception('api  is not exists',100);
            //将api转换为控制器方法的命名空间
            $className = '\\application\\' . $module . '\\controller\\' . ucfirst($controller);
            $obj = new $className($request);
            if (!method_exists($obj, $action))
                throw new \Exception('method ' . $action . ' is not exists',100);
            $res = call_user_func_array(array($obj, $action), array($request));
            return $res;
        }
    
        /**
         * desc:导入
         * author: xxf<waxiongfeifei@gmail.com>
         * date: 2021/4/22
         * time: 上午11:04
         */
        public function import()
        {
            $path = Container::get('app')->getRoutePath();
    
            $files = is_dir($path) ? scandir($path) : [];
    
            foreach ($files as $file) {
                if (strpos($file, '.php')) {
                    $filename = $path . DIRECTORY_SEPARATOR . $file;
                    // 导入路由配置
                    $rules = include $filename;
                    if (is_array($rules)) {
                        $this->routeList = array_merge($this->routeList,$rules);
                    }
                }
            }
        }
    
    
    
        public function getRoute($api)
        {
            if (array_key_exists($api, $this->routeList))//获取真实路径
                $api = $this->routeList[$api];
            return $api;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    请求

    主要功能包括请求信息(如IP,端口,参数)的初始化以及参数的获取安全过滤,以供在控制起来使用
    本项目里自定义请求信息如下,以json文本形式传输

    $param = [
                'api' => 'my-info',//接口地址
                'app' => 'iphone',//客户端设备
                'ver' => '1.0',//版本号
                'data' => [//具体数据
                ],
            ];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Request.php

        private static $_instance; //存储对象
    
        public function __construct(array $options = [])
        {
            $this->init($options);
            self::$_instance = $this;
        }
    
    
        public function init(array $options = [])
        {
            $this->filter = Config::get('default_filter');
            $this->param = $options['data'] ?? [];
            $this->api =  $options['api'] ?? '';
            $this->route =  $options['api'] ?? '';
            $this->app =  $options['app'] ?? '';
            $this->ver =  $options['ver'] ?? '';
            $this->route = Container::get('route')->getRoute($this->route);
            $api = explode('/',$this->route);
            $this->setModule($api[0] ?? '');
            $this->setController($api[1] ?? '');
            $this->setAction($api[2] ?? '');
            $this->ip =  $this->ip();
            $this->setPort();
            $this->setGatewayIp();
            $this->setGatewayPort();
            $this->setClientId();
            \lib\facade\Log::info('request',get_object_vars($this));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    配置文件

    用于从文件里获取配置信息

    Config.php

    class Config
    {
        protected static $config;
    
        // 加载配置文件
        static function load($file){
            if (!isset(self::$config[$file])){
                $confFile = __DIR__ . '/../config/' . $file .'.php';
                if (is_file($confFile)){
                    self::$config[$file] = include_once $confFile;
                }
            }
    
        }
    
    
    
    
        /**
         *获取配置参数 为空则获取所有配置
         * @param null $name
         * @param string $file
         * @param null $default
         * @return |null
         * @author xingxiong.fei@163.com
         * @date 2020-08-26 16:22
         */
        public static function get($name = null,$file = 'config', $default = null)
        {
            self::load($file);
            // 无参数时获取所有
            if (empty($name)) {
                return self::$config[$file];
            }
            $name    = explode('.', $name);
            $name[0] = strtolower($name[0]);
            $config  = self::$config[$file];
            // 按.拆分成多维数组进行判断
            foreach ($name as $val) {
                if (isset($config[$val])) {
                    $config = $config[$val];
                } else {
                    return $default;
                }
            }
            return $config;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    redis

    Redis.php

    class Redis
    {
    
        private static $_instance; //存储对象
        private function __construct( ){
            $config = Config::get('redis');
            self::$_instance = new \Redis();
            //从配置读取
            self::$_instance->connect($config['host'], $config['port']);
            self::$_instance->select($config['db_index']);
            if ('' != $config['password']) {
                self::$_instance->auth($config['password']);
            }
    
        }
    
    
    
    
        public static function getInstance( )
        {
            if (!self::$_instance) {
                new self();
            }
            else{
                try {
                    @trigger_error('flag', E_USER_NOTICE);
                    self::$_instance->ping();
                    $error = error_get_last();
                    if($error['message'] != 'flag')
                        throw new \Exception('Redis server went away');
                } catch (\Exception $e) {
                    // 断线重连
                    new self();
                }
            }
            return self::$_instance;
        }
    
    
    
    
    
        /**
        * 禁止clone
        */
        private function __clone(){}
    
        /**
         * 其他方法自动调用
         * @param $method
         * @param $args
         * @return mixed
         */
        public function __call($method,$args)
        {
            return call_user_func_array([self::$_instance, $method], $args);
        }
    
        /**
         * 静态调用
         * @param $method
         * @param $args
         * @return mixed
         */
        public static function __callStatic($method,$args)
        {
            self::getInstance();
            return call_user_func_array([self::$_instance, $method], $args);
        }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    缓存

    缓存遵循psr-16规范,本项目里只支持Redis的实现

    Cache.php

    /**
     * 缓存类
     * Class Cache
     * @package lib
     */
    class Cache implements Psr16CacheInterface
    {
        /**
         * 驱动句柄
         * @var object
         */
        protected $handler = null;
    
        /**
         * 缓存参数
         * @var array
         */
        protected $options = [
            'expire'     => 0,
            'prefix'     => '',
            'serialize'  => true,
        ];
        public function __construct($options = []){
            if (!empty($options)) {
                $this->options = array_merge($this->options, $options);
            }
        }
    
    
    
        /**
         * {@inheritdoc}
         */
        public function get($name, $default = null)
        {
            $this->handler = Redis::getInstance();
            $key    = $this->getCacheKey($name);
            $value = $this->handler->get($key);
            if (is_null($value) || false === $value) {
                return $default;
            }
            return $this->unserialize($value);
        }
    
        /**
         * 写入缓存
         * @access public
         * @param  string            $name 缓存变量名
         * @param  mixed             $value  存储数据
         * @param  integer|\DateTime $expire  有效时间(秒)
         * @return boolean
         * @throws \Psr\SimpleCache\InvalidArgumentException
         */
        public function set($name, $value, $expire = null)
        {
            $this->handler = Redis::getInstance();
            if (is_null($expire)) {
                $expire = $this->options['expire'];
            }
            $value = $this->serialize($value);
            $key    = $this->getCacheKey($name);
            if ($expire) {
                $result = $this->handler->setex($key, $expire, $value);
            } else {
                $result = $this->handler->set($key, $value);
            }
            return $result;
    
        }
    
        /**
         * 批量获取
         * @param iterable $keys
         * @param null $default
         * @return array|iterable
         * @throws \Psr\SimpleCache\InvalidArgumentException
         */
        public function getMultiple($keys, $default = null)
        {
            if (!\is_array($keys)) {
                throw new \Exception(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
            }
            $result = [];
            foreach ($keys as $key) {
                $result[$key] = $this->get($key);
            }
            return $result;
    
        }
    
        /**
         *批量设置
         * @param iterable $values
         * @param null $expire
         * @return bool
         * @throws \Exception
         * @author xxf
         * @date 2020-08-26 15:37
         */
        public function setMultiple($values, $expire = null)
        {
            if (!\is_array($values)) {
                throw new \Exception(sprintf('Cache values must be array or Traversable, "%s" given', \is_object($values) ? \get_class($values) : \gettype($values)));
            }
            if (is_null($expire)) {
                $expire = $this->options['expire'];
            }
            try {
                foreach ($values as $key => $value) {
                    if (\is_int($key)) {
                        $key = (string) $key;
                    }
                    $this->set($key,$value,$expire);
                }
                return true;
            } catch (\Exception $e) {
                return false;
            }
    
        }
    
        /**
         * 删除缓存
         * @access public
         * @param  string $name 缓存变量名
         * @return boolean
         */
        public function delete($name)
        {
            $this->handler = Redis::getInstance();
            $key = $this->getCacheKey($name);
            try {
                $this->handler->del($key);
                return true;
            } catch (\Exception $e) {
                return false;
            }
        }
    
        /**
         * {@inheritdoc}
         */
        public function deleteMultiple($keys)
        {
            $this->handler = Redis::getInstance();
           if (!\is_array($keys)) {
                throw new Exception(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
            }
           foreach ($keys as &$item){
               $item = $this->getCacheKey($item);
           }
    
            try {
                $this->handler->del($keys);
                return true;
            } catch (\Exception $e) {
                return false;
            }
    
    
    
        }
    
        /**
         * 清除缓存
         * @access public
         * @param  string $tag 标签名
         * @return boolean
         */
        public function clear()
        {
            $this->handler = Redis::getInstance();
            return $this->handler->flushDB();
        }
    
    
    
    
        /**
         * 判断缓存
         * @access public
         * @param  string $name 缓存变量名
         * @return bool
         */
        public function has($name)
        {
            $this->handler = Redis::getInstance();
            $key = $this->getCacheKey($name);
            return $this->handler->exists($key);
        }
    
    
        /**
         * 序列化数据
         * @access protected
         * @param  mixed $data
         * @return string
         */
        protected function serialize($data)
        {
            if (is_scalar($data) || !$this->options['serialize']) {
                return $data;
            }
            $data = 'serialize:'.serialize($data);
            return $data;
        }
    
        /**
         * 反序列化数据
         * @access protected
         * @param  string $data
         * @return mixed
         */
        protected function unserialize($data)
        {
            if ($this->options['serialize'] && 0 === strpos($data, 'serialize:')) {
                return unserialize(substr($data, 10));
            } else {
                return $data;
            }
        }
    
        /**
         * 获取实际的缓存标识
         * @access protected
         * @param  string $name 缓存名
         * @return string
         */
        protected function getCacheKey($name)
        {
            return $this->options['prefix'] . $name;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233

    数据库ORM

    ORM自己造轮子有点复杂就使用了ThinkOrm,大多数方法跟tp一致

    日志

    日志日志遵循PSR-3规范基于Monolog实现

    Log.php

    class Log implements LoggerInterface
    {
    
        protected $loggers;
    
    
        /**
         * 是否允许日志写入
         * @var bool
         */
        protected $allowWrite = true;
    
        protected $config = [
            'path'        => '',
            // 日志通道名
            'channel'        => 'app',
            'level' => 'debug',
            'max_files'   => 0,
            'file_permission'  => 0666,
            'sql_level'  => 'info',
        ];
    
        // 实例化并传入参数
        public function __construct($config = [])
        {
    
            if (is_array($config)) {
                $this->config = array_merge($this->config, $config);
            }
            if (!empty($config['close'])) {
                $this->allowWrite = false;
            }
    
            if (empty($this->config['path'])) {
                $this->config['path'] = \lib\facade\App::getRootPath() . 'runtime/logs' . DIRECTORY_SEPARATOR;
            } elseif (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) {
                $this->config['path'] .= DIRECTORY_SEPARATOR;
            }
        }
    
    
    
        private function createLogger($name,$fileName = '')
        {
            if (empty($this->loggers[$name.$fileName])) {
                // 根据业务域名与方法名进行日志名称的确定
                $channel       = $this->config['channel'];
                // 日志文件目录
                $path       = $this->config['path'];
                // 日志保存时间
                $maxFiles       = $this->config['max_files'];
                // 日志等级
                $level = Logger::toMonologLevel($this->config['level']);
                // 权限
                $filePermission =  $this->config['file_permission'];
                // 创建日志
                $logger    = new Logger($channel);
                // 日志文件相关操作
                $logFileName = empty($fileName) ? $name : $name . '-' .$fileName;
                $handler   = new RotatingFileHandler("{$path}{$logFileName}.log", $maxFiles, $level, true, $filePermission);
                // 日志格式
                $formatter = new LineFormatter("%datetime% %channel%:%level_name% %message% %context% %extra%\n", "Y-m-d H:i:s", false, true);
    
                $handler->setFormatter($formatter);
                $logger->pushHandler($handler);
    
                $this->loggers[$name.$fileName] = $logger;
            }
            return $this->loggers[$name.$fileName];
        }
    
    
    
        /**
         * 记录日志信息
         * @access public
         * @param  mixed  $message       日志信息
         * @param  string $level      日志级别
         * @param  array  $context   替换内容
         * @param string $fileName  文件名
         * @return $this
         */
        public function record($message, $level = 'info', array $context = [],$fileName = '')
        {
            if (!$this->allowWrite) {
                return;
            }
            $logger = $this->createLogger($level,$fileName);
            $level = Logger::toMonologLevel($level);
            if (!is_int($level)) $level = Logger::INFO;
            // $backtrace数组第$idx元素是当前行,第$idx+1元素表示上一层,另外function、class需再往上取一个层次
            // PHP7 不会包含'call_user_func'与'call_user_func_array',需减少一层
            if (version_compare(PCRE_VERSION, '7.0.0', '>=')) {
                $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
                $idx       = 0;
            } else {
                $backtrace = debug_backtrace();
                $idx       = 1;
            }
            $trace = basename($backtrace[$idx]['file']) . ":" . $backtrace[$idx]['line'];
            if (!empty($backtrace[$idx + 1]['function'])) {
                $trace .= '##';
                $trace .= $backtrace[$idx + 1]['function'];
            }
            $message = sprintf('==> LOG: %s -- %s', $message, $trace);
            return $logger->addRecord($level, $message, $context);
        }
    
    
    
    
        /**
         * 记录日志信息
         * @access public
         * @param  string $level     日志级别
         * @param  mixed  $message   日志信息
         * @param  array  $context   替换内容
         * @return void
         */
        public function log($level, $message, array $context = [], $fileName = '')
        {
            if ($level == 'sql')
                $level = $this->config['sql_level'];
            $this->record($message, $level, $context, $fileName);
        }
    
        /**
         * 记录emergency信息
         * @access public
         * @param  mixed  $message   日志信息
         * @param  array  $context   替换内容
         * @return void
         */
        public function emergency($message, array $context = [])
        {
            $this->log(__FUNCTION__, $message, $context);
        }
    
        /**
         * 记录警报信息
         * @access public
         * @param  mixed  $message   日志信息
         * @param  array  $context   替换内容
         * @return void
         */
        public function alert($message, array $context = [])
        {
            $this->log(__FUNCTION__, $message, $context);
        }
    
        /**
         * 记录紧急情况
         * @access public
         * @param  mixed  $message   日志信息
         * @param  array  $context   替换内容
         * @return void
         */
        public function critical($message, array $context = [])
        {
            $this->log(__FUNCTION__, $message, $context);
        }
    
        /**
         * 记录错误信息
         * @access public
         * @param  mixed  $message   日志信息
         * @param  array  $context   替换内容
         * @return void
         */
        public function error($message, array $context = [])
        {
            $this->log(__FUNCTION__, $message, $context);
        }
    
        /**
         * 记录warning信息
         * @access public
         * @param  mixed  $message   日志信息
         * @param  array  $context   替换内容
         * @return void
         */
        public function warning($message, array $context = [])
        {
            $this->log(__FUNCTION__, $message, $context);
        }
    
        /**
         * 记录notice信息
         * @access public
         * @param  mixed  $message   日志信息
         * @param  array  $context   替换内容
         * * @param string $fileName
         * @return void
         */
        public function notice($message, array $context = [],$fileName = '')
        {
            $this->log(__FUNCTION__, $message, $context, $fileName);
        }
    
        /**
         * 记录一般信息
         * @access public
         * @param  mixed  $message   日志信息
         * @param  array  $context   替换内容
         * @param string $fileName
         * @return void
         */
    
        public function info($message, array $context = [],$fileName = '')
        {
            $this->log(__FUNCTION__, $message, $context, $fileName);
        }
    
        /**
         * 记录调试信息
         * @access public
         * @param  mixed  $message   日志信息
         * @param  array  $context   替换内容
         * @return void
         */
        public function debug($message, array $context = [])
        {
            $this->log(__FUNCTION__, $message, $context);
        }
    
        /**
         * 记录sql信息
         * @access public
         * @param  mixed  $message   日志信息
         * @param  array  $context   替换内容
         * @return void
         */
        public function sql($message, array $context = [])
        {
            $this->log(__FUNCTION__, $message, $context);
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237

    容器

    session

    主要包括session的读写功能

    Session.php

    public function __construct(array $config = [])
        {
            $this->config = $config;
        }
    
        /**
         * 设置或者获取session作用域(前缀)
         * @access public
         * @param  string $prefix
         * @return string|void
         */
        public function prefix($prefix = '')
        {
            if (empty($prefix) && null !== $prefix) {
                return $this->prefix;
            } else {
                $this->prefix = $prefix;
            }
        }
    
        public static function __make(Config $config)
        {
            return new static($config->get('','session'));
        }
    
        /**
         * 配置
         * @access public
         * @param  array $config
         * @return void
         */
        public function setConfig(array $config = [])
        {
            $this->config = array_merge($this->config, array_change_key_case($config));
    
            if (isset($config['prefix'])) {
                $this->prefix = $config['prefix'];
            }
        }
    
    
        /**
         * session初始化
         * @access public
         * @param  array $config
         * @return void
         * @throws \think\Exception
         */
        public function init(array $config = [])
        {
            $config = $config ?: $this->config;
    
            if (isset($config['prefix'])) {
                $this->prefix = $config['prefix'];
            }
            $this->init = true;
            return $this;
        }
    
        /**
         *设置session
         * @param $name
         * @param $value
         * @param null $prefix
         * @throws \think\Exception
         * @date 2020-09-10 14:26
         */
        public function set($name, $value, $prefix = null)
        {
            is_null($this->init) && $this->init();
            $prefix = !is_null($prefix) ? $prefix : $this->prefix;
            if (strpos($name, '.')) {
                // 二维数组赋值
                list($name1, $name2) = explode('.', $name);
                if ($prefix) {
                    $_SESSION[$prefix][$name1][$name2] = $value;
                } else {
                    $_SESSION[$name1][$name2] = $value;
                }
            } elseif ($prefix) {
                $_SESSION[$prefix][$name] = $value;
            } else {
                $_SESSION[$name] = $value;
            }
        }
    
        /**
         *获取
         * @param string $name
         * @param null $prefix
         * @return array|mixed|null
         * @throws \think\Exception
         * @date 2020-09-10 14:26
         */
        public function get($name = '', $prefix = null)
        {
    
            is_null($this->init) && $this->init();
    
            $prefix = !is_null($prefix) ? $prefix : $this->prefix;
    
            $value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION;
    
            if ('' != $name) {
                $name = explode('.', $name);
    
                foreach ($name as $val) {
                    if (isset($value[$val])) {
                        $value = $value[$val];
                    } else {
                        $value = null;
                        break;
                    }
                }
            }
            return $value;
        }
    
    
        /**
         *获取后删除
         * @param $name
         * @param null $prefix
         * @return array|mixed|void|null
         * @throws \think\Exception
         * @date 2020-09-10 14:27
         */
        public function pull($name, $prefix = null)
        {
            $result = $this->get($name, $prefix);
    
            if ($result) {
                $this->delete($name, $prefix);
                return $result;
            } else {
                return;
            }
        }
    
    
        /**
         *删除
         * @param $name
         * @param null $prefix
         * @throws \think\Exception
         * @author xingxiong.fei@163.com
         * @date 2020-09-10 14:24
         */
        public function delete($name, $prefix = null)
        {
            is_null($this->init) && $this->init();
    
            $prefix = !is_null($prefix) ? $prefix : $this->prefix;
    
            if (is_array($name)) {
                foreach ($name as $key) {
                    $this->delete($key, $prefix);
                }
            } elseif (strpos($name, '.')) {
                list($name1, $name2) = explode('.', $name);
                if ($prefix) {
                    unset($_SESSION[$prefix][$name1][$name2]);
                } else {
                    unset($_SESSION[$name1][$name2]);
                }
            } else {
                if ($prefix) {
                    unset($_SESSION[$prefix][$name]);
                } else {
                    unset($_SESSION[$name]);
                }
            }
        }
    
        /**
         *清空
         * @param null $prefix
         * @throws \think\Exception
         * @author xingxiong.fei@163.com
         * @date 2020-09-10 14:25
         */
        public function clear($prefix = null)
        {
            is_null($this->init) && $this->init();
            $prefix = !is_null($prefix) ? $prefix : $this->prefix;
    
            if ($prefix) {
                unset($_SESSION[$prefix]);
            } else {
                $_SESSION = [];
            }
        }
    
        /**
         *判断是否存在
         * @param $name
         * @param null $prefix
         * @return bool
         * @throws \think\Exception
         * @author xingxiong.fei@163.com
         * @date 2020-09-10 14:22
         */
        public function has($name, $prefix = null)
        {
            is_null($this->init) && $this->init();
            $prefix = !is_null($prefix) ? $prefix : $this->prefix;
            $value  = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION;
    
            $name = explode('.', $name);
    
            foreach ($name as $val) {
                if (!isset($value[$val])) {
                    return false;
                } else {
                    $value = $value[$val];
                }
            }
            return true;
        }
    
        /**
         *追加到session数组
         * @param $key
         * @param $value
         * @throws \think\Exception
         * @date 2020-09-10 14:28
         */
        public function push($key, $value)
        {
            $array = $this->get($key);
    
            if (is_null($array)) {
                $array = [];
            }
            $array[] = $value;
    
            $this->set($key, $array);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238

    异常

    用来注册异常处理函数,捕捉全局异常并记录错误日志
    Error.php

    class Error
    {
        /**
         * 配置参数
         * @var array
         */
        protected static $exceptionHandler;
    
        /**
         * 注册异常处理
         * @access public
         * @return void
         */
        public static function register()
        {
            error_reporting(E_ALL);
            set_error_handler([__CLASS__, 'error']);
            set_exception_handler([__CLASS__, 'exception']);
            register_shutdown_function([__CLASS__, 'shutdown']);
        }
    
        /**
         *
         * @param $e
         * @author xingxiong.fei@163.com
         * @date 2020-09-02 17:36
         */
        public static function exception($e)
        {
            self::report($e);
        }
    
        public static function report(Throwable $exception)
        {
            $data = [
                'file'    => $exception->getFile(),
                'line'    => $exception->getLine(),
                'message' => $exception->getMessage(),
                'code'    => $exception->getCode(),
            ];
            \lib\facade\Log::error('错误信息',$data);
            \lib\facade\Log::error('错误跟踪',$exception->getTrace());
        }
    
        /**
         * Error Handler
         * @access public
         * @param  integer $errno   错误编号
         * @param  integer $errstr  详细错误信息
         * @param  string  $errfile 出错的文件
         * @param  integer $errline 出错行号
         * @throws ErrorException
         */
        public static function error($errno, $errstr, $errfile = '', $errline = 0): void
        {
            $data = [
                'file'    => $errfile,
                'line'    =>$errline,
                'message' => $errstr,
                'code'    => $errno,
            ];
            \lib\facade\Log::error('错误信息',$data);
    
        }
    
        public static function errorLog(\Error $error): void
        {
            $data = [
                'file'    => $error->getFile(),
                'line'    => $error->getLine(),
                'message' => $error->getMessage(),
                'code'    => $error->getCode(),
            ];
            \lib\facade\Log::error('错误信息',$data);
        }
    
        /**
         * Shutdown Handler
         * @access public
         */
        public static function shutdown()
        {
    
            if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) {
    
                self::error($error);
            }
        }
    
        /**
         * 确定错误类型是否致命
         *
         * @access protected
         * @param  int $type
         * @return bool
         */
        protected static function isFatal($type)
        {
            return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    控制器

    控制器基类 初始化时依赖注入Request对象,继承该类的控制器可直接使用。

    Controlle.php

    class Controller
    {
    
        /**
         * Request实例
         * @var \think\Request
         */
        protected $request;
    
        /**
         * 构造方法
         * @access public
         */
        public function __construct(Request $request)
        {
            $this->request = Container::get('request');
            // 控制器初始化
            $this->initialize();
        }
    
        // 初始化
        protected function initialize()
        {}
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    使用方法

    移步 https://www.kancloud.cn/xiongfeifei/ver1/2131252

  • 相关阅读:
    延迟绑定与retdlresolve
    前端性能分析
    悲惨经历:浙政钉 iPhone 手机访问页面空白,刷新后正常显示 问题排查(安卓、手机一切正常)
    第7章 项目进阶,构建安全高效的企业服务(上)
    AWS KMS加密和解密
    共享单车需求量登记分类及影响因素分析——基于机器学习模型的比较分析
    npm报错,显示certificate has expired
    java发送邮件
    箱形理论在交易策略中的实战应用与优化
    CV计算机视觉每日开源代码Paper with code速览-2023.10.13
  • 原文地址:https://blog.csdn.net/flysnownet/article/details/124954285