├── .gitignore ├── LICENSE ├── README.md ├── boot.php ├── composer.json ├── docs └── README.md ├── src ├── Auth │ ├── CookieAuth.php │ ├── RedisAuth.php │ └── SessionAuth.php ├── Cache │ ├── Driver │ │ ├── FileCacheDriver.php │ │ ├── MemcacheDriver.php │ │ └── RedisDriver.php │ ├── Request │ │ ├── FileCache.php │ │ ├── Memcache.php │ │ └── RedisCache.php │ └── RequestCache.php ├── Core │ ├── Annotate.php │ ├── Application.php │ ├── Config.php │ ├── CrossArray.php │ ├── Delegate.php │ ├── FrameBase.php │ ├── Helper.php │ ├── HttpAuth.php │ ├── Loader.php │ ├── Rest.php │ └── Router.php ├── DB │ ├── Connector │ │ ├── BaseConnector.php │ │ ├── MySQLConnector.php │ │ ├── OracleConnector.php │ │ ├── PgSQLConnector.php │ │ └── SQLiteConnector.php │ ├── DBFactory.php │ ├── Drivers │ │ ├── CouchDriver.php │ │ ├── MongoDriver.php │ │ ├── PDOOracleDriver.php │ │ └── PDOSqlDriver.php │ └── SQLAssembler │ │ ├── MySQLAssembler.php │ │ ├── OracleAssembler.php │ │ ├── PgSQLAssembler.php │ │ ├── SQLAssembler.php │ │ └── SQLiteAssembler.php ├── Exception │ ├── CacheException.php │ ├── CoreException.php │ ├── CrossException.php │ ├── DBConnectException.php │ ├── FrontException.php │ ├── LogicStatusException.php │ └── tpl │ │ ├── cli_error.tpl.php │ │ └── front_error.tpl.php ├── Http │ ├── Request.php │ └── Response.php ├── I │ ├── CacheInterface.php │ ├── HttpAuthInterface.php │ ├── ILog.php │ ├── IModelInfo.php │ ├── PDOConnector.php │ ├── RequestCacheInterface.php │ ├── RouterInterface.php │ └── SqlInterface.php ├── Interactive │ ├── DataFilter.php │ └── ResponseData.php ├── Lib │ ├── Array2XML.php │ ├── Document │ │ ├── CallTree.php │ │ ├── CallTreeToHTML.php │ │ └── HTML.php │ ├── StringToPHPStream.php │ └── Upload │ │ ├── Filter │ │ ├── Image.php │ │ └── MimeType.php │ │ ├── IFilter.php │ │ └── Uploader.php ├── MVC │ ├── Controller.php │ ├── Module.php │ └── View.php ├── Model │ ├── RedisModel.php │ └── SQLModel.php └── Runtime │ ├── ClosureContainer.php │ ├── RequestMapping.php │ └── Rules.php └── tests ├── MainTest.php ├── README.md └── project └── app └── test ├── controllers └── Main.php ├── init.php ├── templates └── default │ └── default.layer.php └── views └── MainView.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /.idea/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010 - 2015 crossphp.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #CrossPHP 简洁的开发框架# 2 | 3 | MVC,视图Layer布局,灵活的Module,注释配置,智能路由别名,PSR标准, PHP7.2+ 4 | 5 | 官方网站: [http://www.crossphp.com](http://www.crossphp.com "www.crossphp.com") 6 | 文档地址: [http://document.crossphp.com](http://document.crossphp.com "document.crossphp.com") 7 | 8 | 欢迎加入QQ群120801063 -------------------------------------------------------------------------------- /boot.php: -------------------------------------------------------------------------------- 1 | 18 | * Class CookieAuth 19 | * @package cross\auth 20 | */ 21 | class CookieAuth implements HttpAuthInterface 22 | { 23 | /** 24 | * 加解密默认key 25 | * 26 | * @var string 27 | */ 28 | private $authKey = '!wl<@>c(r#%o*s&s'; 29 | 30 | function __construct(string $authKey = '') 31 | { 32 | if ($authKey) { 33 | $this->authKey = $authKey; 34 | } 35 | } 36 | 37 | /** 38 | * 生成加密cookie 39 | * 40 | * @param string $name 41 | * @param string|array $params 42 | * @param int $expire 43 | * @return bool 44 | */ 45 | function set(string $name, $params, int $expire = 0): bool 46 | { 47 | if ($params === '' || $params === null) { 48 | $expire = time() - 3600; 49 | $value = null; 50 | } else { 51 | $encryptKey = $this->getEncryptKey($name); 52 | if (is_array($params)) { 53 | $params = json_encode($params, JSON_UNESCAPED_UNICODE); 54 | } 55 | $value = Helper::authCode($params, 'ENCODE', $encryptKey); 56 | if ($expire > 0) { 57 | $expire = time() + $expire; 58 | } 59 | } 60 | 61 | Response::getInstance()->setRawCookie($name, $value, $expire, '/', Delegate::env('cookie.domain') ?? ''); 62 | return true; 63 | } 64 | 65 | /** 66 | * 从已加密的cookie中取出值 67 | * 68 | * @param string $params cookie的key 69 | * @param bool $deCode 70 | * @return bool|string 71 | */ 72 | function get(string $params, bool $deCode = false) 73 | { 74 | if (false !== strpos($params, ':') && $deCode) { 75 | list($name, $arrKey) = explode(':', $params); 76 | } else { 77 | $name = $params; 78 | } 79 | 80 | if (!isset($_COOKIE[$name])) { 81 | return false; 82 | } 83 | 84 | $value = $_COOKIE[$name]; 85 | $encryptKey = $this->getEncryptKey($name); 86 | $result = Helper::authCode($value, 'DECODE', $encryptKey); 87 | if (!$result) { 88 | return false; 89 | } 90 | 91 | if ($deCode) { 92 | $result = json_decode($result, true); 93 | if (isset($arrKey) && isset($result[$arrKey])) { 94 | return $result[$arrKey]; 95 | } 96 | } 97 | 98 | return $result; 99 | } 100 | 101 | /** 102 | * 生成密钥 103 | * 104 | * @param string $cookieName 105 | * @return string 106 | */ 107 | protected function getEncryptKey(string $cookieName): string 108 | { 109 | return md5($cookieName . $this->authKey); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Auth/RedisAuth.php: -------------------------------------------------------------------------------- 1 | 17 | * Class RedisAuth 18 | * @package Cross\Auth 19 | */ 20 | class RedisAuth implements HttpAuthInterface 21 | { 22 | /** 23 | * auth key前缀 24 | * 25 | * @var string 26 | */ 27 | protected $authKeyPrefix; 28 | 29 | /** 30 | * RedisAuth constructor. 31 | */ 32 | function __construct() 33 | { 34 | if (session_status() == PHP_SESSION_NONE) { 35 | session_start(); 36 | } 37 | 38 | $this->authKeyPrefix = '_@CPA@_' . session_id(); 39 | } 40 | 41 | /** 42 | * 设置session的值 43 | * 44 | * @param string $key 45 | * @param string|array $value 46 | * @param int $expire 47 | * @return bool|mixed 48 | * @throws CoreException 49 | */ 50 | function set(string $key, $value, int $expire = 0): bool 51 | { 52 | $key = $this->authKeyPrefix . '@' . $key; 53 | if (is_array($value)) { 54 | $value = json_encode($value, JSON_UNESCAPED_UNICODE); 55 | } 56 | 57 | RedisModel::use('auth')->set($key, $value, $expire); 58 | return true; 59 | } 60 | 61 | /** 62 | * 获取session的值 63 | * 64 | * @param string $key 65 | * @param bool $deCode 66 | * @return bool|mixed 67 | * @throws CoreException 68 | */ 69 | function get(string $key, bool $deCode = false) 70 | { 71 | $key = $this->authKeyPrefix . '@' . $key; 72 | if (false !== strpos($key, ':') && $deCode) { 73 | list($key, $arrKey) = explode(':', $key); 74 | } 75 | 76 | $result = RedisModel::use('auth')->get($key); 77 | if (!$result) { 78 | return false; 79 | } 80 | 81 | if ($deCode) { 82 | $result = json_decode($result, true); 83 | if (isset($arrKey) && isset($result[$arrKey])) { 84 | return $result[$arrKey]; 85 | } 86 | } 87 | 88 | return $result; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Auth/SessionAuth.php: -------------------------------------------------------------------------------- 1 | 15 | * Class SessionAuth 16 | * @package Cross\Auth 17 | */ 18 | class SessionAuth implements HttpAuthInterface 19 | { 20 | function __construct() 21 | { 22 | if (session_status() == PHP_SESSION_NONE) { 23 | session_start(); 24 | } 25 | } 26 | 27 | /** 28 | * 设置session的值 29 | * 30 | * @param string $key 31 | * @param string|array $value 32 | * @param int $expire 33 | * @return bool|mixed 34 | */ 35 | function set(string $key, $value, int $expire = 0): bool 36 | { 37 | if (is_array($value)) { 38 | $value = json_encode($value, JSON_UNESCAPED_UNICODE); 39 | } 40 | 41 | $_SESSION[$key] = $value; 42 | return true; 43 | } 44 | 45 | /** 46 | * 获取session的值 47 | * 48 | * @param string $key 49 | * @param bool $deCode 50 | * @return bool|mixed 51 | */ 52 | function get(string $key, bool $deCode = false) 53 | { 54 | if (false !== strpos($key, ':') && $deCode) { 55 | list($key, $arrKey) = explode(':', $key); 56 | } 57 | 58 | if (!isset($_SESSION[$key])) { 59 | return false; 60 | } 61 | 62 | $result = $_SESSION[$key]; 63 | if ($deCode) { 64 | $result = json_decode($result, true); 65 | if (isset($arrKey) && isset($result[$arrKey])) { 66 | return $result[$arrKey]; 67 | } 68 | } 69 | 70 | return $result; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Cache/Driver/FileCacheDriver.php: -------------------------------------------------------------------------------- 1 | 16 | * Class FileCacheDriver 17 | * @package Cross\Cache\Driver 18 | */ 19 | class FileCacheDriver implements CacheInterface 20 | { 21 | /** 22 | * 缓存文件路径 23 | * 24 | * @var string 25 | */ 26 | private $cachePath; 27 | 28 | function __construct(string $cachePath) 29 | { 30 | $cachePath = rtrim($cachePath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; 31 | $this->cachePath = $cachePath; 32 | } 33 | 34 | /** 35 | * 返回缓存文件 36 | * 37 | * @param string $key 38 | * @return mixed 39 | */ 40 | function get(string $key = '') 41 | { 42 | $cacheFile = $this->cachePath . $key; 43 | if (!file_exists($cacheFile)) { 44 | return false; 45 | } 46 | 47 | return file_get_contents($cacheFile); 48 | } 49 | 50 | /** 51 | * 保存缓存 52 | * 53 | * @param string $key 54 | * @param mixed $value 55 | * @return mixed|void 56 | * @throws CoreException 57 | */ 58 | function set(string $key, $value) 59 | { 60 | $cacheFile = $this->cachePath . $key; 61 | if (!file_exists($cacheFile)) { 62 | $filePath = dirname($cacheFile); 63 | if (!is_dir($filePath)) { 64 | $createDir = mkdir($filePath, 0755, true); 65 | if (!$createDir) { 66 | throw new CoreException('创建缓存目录失败'); 67 | } 68 | } 69 | } 70 | 71 | file_put_contents($cacheFile, $value, LOCK_EX); 72 | } 73 | 74 | /** 75 | * 删除缓存 76 | * 77 | * @param string $key 78 | * @return mixed|void 79 | */ 80 | function del(string $key) 81 | { 82 | $cacheFile = $this->cachePath . $key; 83 | if (file_exists($cacheFile)) { 84 | unlink($cacheFile); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Cache/Driver/MemcacheDriver.php: -------------------------------------------------------------------------------- 1 | 17 | * Class MemcacheDriver 18 | * @package Cross\Cache\Driver 19 | */ 20 | class MemcacheDriver 21 | { 22 | /** 23 | * @var Memcache 24 | */ 25 | public $link; 26 | 27 | /** 28 | * 集群参数默认配置 29 | * 30 | * @var array 31 | */ 32 | protected $defaultOptions = [ 33 | 'persistent' => true, 34 | 'weight' => 1, 35 | 'timeout' => 1, 36 | 'retry_interval' => 15, 37 | 'status' => true, 38 | 'failure_callback' => null 39 | ]; 40 | 41 | /** 42 | * MemcacheDriver constructor. 43 | * 44 | * @param array $option 45 | * @throws CoreException 46 | */ 47 | function __construct(array $option) 48 | { 49 | if (!extension_loaded('memcache')) { 50 | throw new CoreException('Not support memcache extension !'); 51 | } 52 | 53 | if (!isset($option['host'])) { 54 | $option['host'] = '127.0.0.1'; 55 | } 56 | 57 | if (!isset($option['port'])) { 58 | $option['port'] = 11211; 59 | } 60 | 61 | if (!isset($option['timeout'])) { 62 | $option['timeout'] = 1; 63 | } 64 | 65 | try { 66 | $mc = new Memcache(); 67 | //集群服务器IP用|分隔 68 | if (false !== strpos($option['host'], '|')) { 69 | $opt = &$this->defaultOptions; 70 | foreach ($opt as $k => &$v) { 71 | if (isset($option[$k])) { 72 | $v = $option[$k]; 73 | } 74 | } 75 | 76 | $serverList = explode('|', $option['host']); 77 | foreach ($serverList as $server) { 78 | $host = $server; 79 | $port = $option['port']; 80 | if (false !== strpos($server, ':')) { 81 | list($host, $port) = explode(':', $server); 82 | } 83 | 84 | $host = trim($host); 85 | $port = trim($port); 86 | $mc->addServer($host, $port, $opt['persistent'], $opt['weight'], $opt['timeout'], 87 | $opt['retry_interval'], $opt['status'], $opt['failure_callback']); 88 | } 89 | } else { 90 | $mc->connect($option['host'], $option['port'], $option['timeout']); 91 | } 92 | 93 | $this->link = $mc; 94 | } catch (Exception $e) { 95 | throw new CoreException($e->getMessage()); 96 | } 97 | } 98 | 99 | /** 100 | * 从缓存中获取内容 101 | * 102 | * @param string $key 103 | * @param int|array $flag 104 | * @return array|string 105 | */ 106 | function get(string $key, &$flag = 0) 107 | { 108 | return $this->link->get($key, $flag); 109 | } 110 | 111 | /** 112 | * 调用Memcache类提供的方法 113 | * 114 | * @param $method 115 | * @param $argv 116 | * @return mixed|null 117 | */ 118 | public function __call($method, $argv) 119 | { 120 | $result = null; 121 | if (method_exists($this->link, $method)) { 122 | $result = ($argv == null) 123 | ? $this->link->$method() 124 | : call_user_func_array(array($this->link, $method), $argv); 125 | } 126 | 127 | return $result; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/Cache/Driver/RedisDriver.php: -------------------------------------------------------------------------------- 1 | 17 | * Class RedisDriver 18 | * @package Cross\Cache\Driver 19 | */ 20 | class RedisDriver 21 | { 22 | /** 23 | * @var string 24 | */ 25 | private $id; 26 | 27 | /** 28 | * @var Redis|RedisCluster 29 | */ 30 | protected $link; 31 | 32 | /** 33 | * 是否集群模式 34 | * 35 | * @var bool 36 | */ 37 | protected $clusterMode = false; 38 | 39 | /** 40 | * @var array 41 | */ 42 | protected $option; 43 | 44 | /** 45 | * 连接redis 46 | *
47 | * unixsocket设置 48 | * unixsocket /tmp/redis.sock 49 | * unixsocketperm 777 50 | *51 | * 52 | * @param $option 53 | * @throws 54 | */ 55 | function __construct(array $option) 56 | { 57 | if (!extension_loaded('redis')) { 58 | throw new CoreException('Not support redis extension !'); 59 | } 60 | 61 | $option['host'] = $option['host'] ?? '127.0.0.1'; 62 | $option['port'] = $option['port'] ?? 6379; 63 | $option['timeout'] = $option['timeout'] ?? 3; 64 | $option['readTimeout'] = $option['readTimeout'] ?? $option['timeout']; 65 | $option['clusterName'] = $option['clusterName'] ?? null; 66 | 67 | //是否使用长链接 68 | if (PHP_SAPI == 'cli') { 69 | ini_set('default_socket_timeout', -1); 70 | $persistent = true; 71 | } else { 72 | $persistent = $option['persistent'] ?? false; 73 | } 74 | 75 | if (is_array($option['host'])) { 76 | $id = implode('|', $option['host']); 77 | $useUnixSocket = false; 78 | $this->clusterMode = true; 79 | } else { 80 | $this->clusterMode = false; 81 | if (strcasecmp(PHP_OS, 'linux') == 0 && !empty($option['unix_socket'])) { 82 | $id = $option['unix_socket']; 83 | $useUnixSocket = true; 84 | } else { 85 | $id = "{$option['host']}:{$option['port']}:{$option['timeout']}"; 86 | $useUnixSocket = false; 87 | } 88 | } 89 | 90 | static $connects; 91 | if (!isset($connects[$id])) { 92 | if ($this->clusterMode) { 93 | $redis = new RedisCluster($option['clusterName'], $option['host'], $option['timeout'], 94 | $option['readTimeout'], $persistent, $option['pass'] ?: null); 95 | } else { 96 | $redis = new Redis(); 97 | if ($persistent) { 98 | if ($useUnixSocket) { 99 | $redis->pconnect($option['unix_socket']); 100 | } else { 101 | $redis->pconnect($option['host'], $option['port'], 0); 102 | } 103 | } else { 104 | if ($useUnixSocket) { 105 | $redis->connect($option['unix_socket']); 106 | } else { 107 | $redis->connect($option['host'], $option['port'], $option['timeout']); 108 | } 109 | } 110 | 111 | if (!empty($option['pass'])) { 112 | $authStatus = $redis->auth($option['pass']); 113 | if (!$authStatus) { 114 | throw new CoreException('Redis auth failed !'); 115 | } 116 | } 117 | } 118 | 119 | $connects[$id] = $redis; 120 | } else { 121 | $redis = &$connects[$id]; 122 | } 123 | 124 | $this->id = $id; 125 | $this->link = $redis; 126 | $this->option = $option; 127 | } 128 | 129 | /** 130 | * 获取连接属性 131 | * 132 | * @return array 133 | */ 134 | function getLinkOption() 135 | { 136 | return $this->option; 137 | } 138 | 139 | /** 140 | * 调用redis类提供的方法 141 | * 142 | * @param $method 143 | * @param $argv 144 | * @return mixed|null 145 | * @throws CoreException 146 | */ 147 | public function __call($method, $argv) 148 | { 149 | $result = null; 150 | if (method_exists($this->link, $method)) { 151 | $this->selectCurrentDatabase(); 152 | $result = ($argv == null) 153 | ? $this->link->$method() 154 | : call_user_func_array([$this->link, $method], $argv); 155 | } 156 | 157 | return $result; 158 | } 159 | 160 | /** 161 | * 选择当前数据库 162 | * 163 | * @throws CoreException 164 | */ 165 | protected function selectCurrentDatabase() 166 | { 167 | if ($this->clusterMode) { 168 | return; 169 | } 170 | 171 | static $selected = null; 172 | $db = &$this->option['db']; 173 | $current = $this->id . ':' . $db; 174 | if ($selected !== $current) { 175 | $selectRet = $this->link->select($db); 176 | if ($selectRet) { 177 | $selected = $current; 178 | } else { 179 | throw new CoreException("Redis select DB($current) failed!"); 180 | } 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/Cache/Request/FileCache.php: -------------------------------------------------------------------------------- 1 | 17 | * 18 | * Class FileCache 19 | * @package Cross\Cache\Request 20 | */ 21 | class FileCache implements RequestCacheInterface 22 | { 23 | /** 24 | * @var string 25 | */ 26 | private $cacheKey; 27 | 28 | /** 29 | * @var string 30 | */ 31 | private $cachePath; 32 | 33 | /** 34 | * @var array 35 | */ 36 | private $config; 37 | 38 | /** 39 | * 缓存过期时间(秒) 40 | * 41 | * @var int 42 | */ 43 | private $expireTime = 3600; 44 | 45 | /** 46 | * @var FileCacheDriver 47 | */ 48 | private $driver; 49 | 50 | /** 51 | * FileCache constructor. 52 | * 53 | * @param array $config 54 | * @throws CoreException 55 | */ 56 | function __construct(array $config) 57 | { 58 | if (!isset($config['cache_path'])) { 59 | throw new CoreException('请指定缓存目录'); 60 | } 61 | 62 | if (!isset($config['key'])) { 63 | throw new CoreException('请指定缓存KEY'); 64 | } 65 | 66 | if (isset($config['expire_time'])) { 67 | $this->expireTime = &$config['expire_time']; 68 | } 69 | 70 | $this->cacheKey = &$config['key']; 71 | 72 | $this->cachePath = rtrim($config['cache_path'], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; 73 | $this->driver = new FileCacheDriver($this->cachePath); 74 | } 75 | 76 | /** 77 | * 写入缓存 78 | * 79 | * @param string $value 80 | * @throws CoreException 81 | */ 82 | function set(string $value): void 83 | { 84 | $this->driver->set($this->cacheKey, $value); 85 | } 86 | 87 | /** 88 | * 获取缓存内容 89 | * 90 | * @return mixed get cache 91 | */ 92 | function get(): string 93 | { 94 | return $this->driver->get($this->cacheKey); 95 | } 96 | 97 | /** 98 | * 是否有效 99 | * 100 | * @return bool 101 | */ 102 | function isValid(): bool 103 | { 104 | $cacheFile = $this->cachePath . $this->cacheKey; 105 | if (!file_exists($cacheFile)) { 106 | return false; 107 | } 108 | 109 | if ((time() - filemtime($cacheFile)) < $this->expireTime) { 110 | return true; 111 | } 112 | 113 | return false; 114 | } 115 | 116 | /** 117 | * 缓存配置 118 | * 119 | * @param array $config 120 | */ 121 | function setConfig(array $config = []) 122 | { 123 | $this->config = $config; 124 | } 125 | 126 | /** 127 | * 获取缓存配置 128 | * 129 | * @return mixed 130 | */ 131 | function getConfig() 132 | { 133 | return $this->config; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/Cache/Request/Memcache.php: -------------------------------------------------------------------------------- 1 | 17 | * Class RequestMemcache 18 | * @package Cross\Cache\Request 19 | */ 20 | class Memcache implements RequestCacheInterface 21 | { 22 | /** 23 | * @var array 24 | */ 25 | protected $config; 26 | 27 | /** 28 | * 缓存key 29 | * 30 | * @var string 31 | */ 32 | protected $cacheKey; 33 | 34 | /** 35 | * 有效时间 36 | * 37 | * @var int 38 | */ 39 | protected $expireTime = 3600; 40 | 41 | /** 42 | * @var int 43 | */ 44 | protected $flag = 0; 45 | 46 | /** 47 | * @var MemcacheDriver 48 | */ 49 | protected $driver; 50 | 51 | /** 52 | * Memcache constructor. 53 | * 54 | * @param array $option 55 | * @throws CoreException 56 | */ 57 | function __construct(array $option) 58 | { 59 | if (empty($option['key'])) { 60 | throw new CoreException('请指定缓存KEY'); 61 | } 62 | 63 | $this->cacheKey = &$option['key']; 64 | $this->driver = new MemcacheDriver($option); 65 | 66 | if (isset($option['flag']) && $option['flag']) { 67 | $this->flag = &$option['flag']; 68 | } 69 | 70 | if (isset($option['expire_time'])) { 71 | $this->expireTime = &$option['expire_time']; 72 | } 73 | } 74 | 75 | /** 76 | * 设置request缓存 77 | * 78 | * @param string $value 79 | */ 80 | function set(string $value): void 81 | { 82 | $this->driver->set($this->cacheKey, $value, $this->flag, $this->expireTime); 83 | } 84 | 85 | /** 86 | * 获取request缓存 87 | * 88 | * @param int $flag 89 | * @return array|string 90 | */ 91 | function get(&$flag = 0): string 92 | { 93 | return $this->driver->get($this->cacheKey, $flag); 94 | } 95 | 96 | /** 97 | * 查看key是否有效 98 | * 99 | * @return bool 100 | */ 101 | function isValid(): bool 102 | { 103 | if ($this->driver->get($this->cacheKey)) { 104 | return true; 105 | } 106 | 107 | return false; 108 | } 109 | 110 | /** 111 | * 设置配置 112 | * 113 | * @param array $config 114 | */ 115 | function setConfig(array $config = []) 116 | { 117 | $this->config = $config; 118 | } 119 | 120 | /** 121 | * 获取缓存配置 122 | * 123 | * @return mixed 124 | */ 125 | function getConfig() 126 | { 127 | return $this->config; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/Cache/Request/RedisCache.php: -------------------------------------------------------------------------------- 1 | 17 | * Class RequestRedisCache 18 | * @package Cross\Cache\Request 19 | */ 20 | class RedisCache implements RequestCacheInterface 21 | { 22 | /** 23 | * @var array 24 | */ 25 | protected $config; 26 | 27 | /** 28 | * 缓存key 29 | * 30 | * @var string 31 | */ 32 | protected $cacheKey; 33 | 34 | /** 35 | * 有效时间 36 | * 37 | * @var int 38 | */ 39 | protected $expireTime = 3600; 40 | 41 | /** 42 | * @var bool 43 | */ 44 | protected $compress = false; 45 | 46 | /** 47 | * @var RedisDriver 48 | */ 49 | protected $driver; 50 | 51 | /** 52 | * 设置缓存key和缓存有效期 53 | * 54 | * @param array $option 55 | * @throws CoreException 56 | */ 57 | function __construct(array $option) 58 | { 59 | if (empty($option['key'])) { 60 | throw new CoreException('请指定缓存KEY'); 61 | } 62 | 63 | if (isset($option['expire_time'])) { 64 | $this->expireTime = &$option['expire_time']; 65 | } 66 | 67 | if (isset($option['compress'])) { 68 | $this->compress = &$option['compress']; 69 | } 70 | 71 | $this->cacheKey = &$option['key']; 72 | $this->driver = new RedisDriver($option); 73 | } 74 | 75 | /** 76 | * 设置request请求 77 | * 78 | * @param string $value 79 | */ 80 | function set(string $value): void 81 | { 82 | if ($this->compress) { 83 | $value = gzcompress($value); 84 | } 85 | 86 | $this->driver->setex($this->cacheKey, $this->expireTime, $value); 87 | } 88 | 89 | /** 90 | * 返回request的内容 91 | * 92 | * @return bool|mixed|string 93 | */ 94 | function get(): string 95 | { 96 | $content = $this->driver->get($this->cacheKey); 97 | if ($this->compress) { 98 | $content = gzuncompress($content); 99 | } 100 | 101 | return $content; 102 | } 103 | 104 | /** 105 | * 检查缓存key是否有效 106 | * 107 | * @return bool 108 | */ 109 | function isValid(): bool 110 | { 111 | return $this->driver->ttl($this->cacheKey) > 0; 112 | } 113 | 114 | /** 115 | * 设置配置 116 | * 117 | * @param array $config 118 | */ 119 | function setConfig(array $config = []) 120 | { 121 | $this->config = $config; 122 | } 123 | 124 | /** 125 | * 获取缓存配置 126 | * 127 | * @return mixed 128 | */ 129 | function getConfig() 130 | { 131 | return $this->config; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/Cache/RequestCache.php: -------------------------------------------------------------------------------- 1 | 25 | * Class RequestCache 26 | * @package Cross\Cache 27 | */ 28 | class RequestCache 29 | { 30 | const FILE = 1; 31 | const MEMCACHE = 2; 32 | const REDIS = 3; 33 | 34 | /** 35 | * @var FileCache|Memcache|RedisCache|RequestCacheInterface 36 | */ 37 | static $instance; 38 | 39 | /** 40 | * RequestCache 41 | * 42 | * @param int|object|string $type 43 | * @param array $options 44 | * @return FileCache|Memcache|RedisCache|RequestCacheInterface 45 | * @throws CoreException 46 | */ 47 | static function factory($type, array $options) 48 | { 49 | switch ($type) { 50 | case 'file': 51 | case self::FILE: 52 | $instance = new FileCache($options); 53 | break; 54 | 55 | case 'memcache': 56 | case self::MEMCACHE: 57 | $instance = new Memcache($options); 58 | break; 59 | 60 | case 'redis': 61 | case self::REDIS: 62 | $instance = new RedisCache($options); 63 | break; 64 | 65 | default: 66 | if (is_string($type)) { 67 | try { 68 | $rf = new ReflectionClass($type); 69 | if ($rf->implementsInterface('Cross\I\RequestCacheInterface')) { 70 | $instance = $rf->newInstance(); 71 | } else { 72 | throw new CoreException('Must implement RequestCacheInterface'); 73 | } 74 | } catch (Exception $e) { 75 | throw new CoreException('Reflection ' . $e->getMessage()); 76 | } 77 | } elseif (is_object($type)) { 78 | if ($type instanceof RequestCacheInterface) { 79 | $instance = $type; 80 | } else { 81 | throw new CoreException('Must implement RequestCacheInterface'); 82 | } 83 | } else { 84 | throw new CoreException('Unsupported cache type!'); 85 | } 86 | } 87 | 88 | $instance->setConfig($options); 89 | return $instance; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Core/Annotate.php: -------------------------------------------------------------------------------- 1 | 15 | * Class Annotate 16 | * @package Cross\Core 17 | */ 18 | class Annotate 19 | { 20 | /** 21 | * @var Delegate 22 | */ 23 | private $delegate; 24 | 25 | /** 26 | * 注释参数前缀 27 | * 28 | * @var string 29 | */ 30 | private $prefix = 'cp_'; 31 | 32 | /** 33 | * @var Annotate 34 | */ 35 | private static $instance; 36 | 37 | /** 38 | * 注册一个wrapper 39 | * 40 | * @param Delegate $delegate 41 | */ 42 | private function __construct(Delegate &$delegate) 43 | { 44 | $this->delegate = $delegate; 45 | stream_register_wrapper('annotate', 'Cross\Lib\StringToPHPStream'); 46 | } 47 | 48 | /** 49 | * 生成解析注释配置单例对象 50 | * 51 | * @param Delegate $delegate 52 | * @return Annotate 53 | */ 54 | public static function getInstance(Delegate &$delegate): self 55 | { 56 | if (!self::$instance) { 57 | self::$instance = new Annotate($delegate); 58 | } 59 | 60 | return self::$instance; 61 | } 62 | 63 | /** 64 | * 设置前缀 65 | * 66 | * @param string $prefix 67 | * @return $this 68 | */ 69 | function setPrefix(string $prefix): self 70 | { 71 | $this->prefix = $prefix; 72 | return $this; 73 | } 74 | 75 | /** 76 | * 注释配置转换为数组 77 | * 78 | * @param string $annotate 79 | * @return array 80 | */ 81 | public function parse(string $annotate = ''): array 82 | { 83 | if (empty($annotate)) { 84 | return []; 85 | } 86 | 87 | $flag = preg_match_all("/@{$this->prefix}(.*?)\s+(.*)/", $annotate, $content); 88 | if (!$flag || empty($content[1])) { 89 | return []; 90 | } 91 | 92 | $configs = []; 93 | $values = &$content[2]; 94 | array_walk($content[1], function ($k, $index) use ($values, &$configs) { 95 | $v = &$values[$index]; 96 | if (null !== $v) { 97 | $v = trim($v); 98 | } 99 | 100 | if (isset($configs[$k]) && !is_array($configs[$k])) { 101 | $configs[$k] = [$configs[$k]]; 102 | $configs[$k][] = $v; 103 | } elseif (isset($configs[$k])) { 104 | $configs[$k][] = $v; 105 | } else { 106 | $configs[$k] = $v; 107 | } 108 | }); 109 | 110 | return $this->parseAnnotate($configs); 111 | } 112 | 113 | /** 114 | * 把PHP代码绑定到匿名函数中 115 | * 116 | * @param string $params 117 | * @return Closure 118 | */ 119 | public function bindToClosure(string $params): Closure 120 | { 121 | return function ($self) use ($params) { 122 | return include("annotate://{$params}"); 123 | }; 124 | } 125 | 126 | /** 127 | * php字符串代码通过wrapper转换为php代码 128 | * 129 | * @param string $params 130 | * @return mixed 131 | */ 132 | public function toCode(string $params) 133 | { 134 | return include("annotate://{$params}"); 135 | } 136 | 137 | /** 138 | * 配置参数值解析 139 | *
140 | * 如: a, b=file, c 会被解析为 141 | * array( 142 | * 'a' => '', 143 | * 'b' => file, 144 | * 'c' => '', 145 | * ) 146 | *147 | * 148 | * @param string $params 149 | * @return array 150 | */ 151 | public function toArray(string $params): array 152 | { 153 | $result = []; 154 | $conf = array_map('trim', explode(',', $params)); 155 | foreach ($conf as $c) { 156 | if (false !== strpos($c, '=')) { 157 | $c = explode('=', $c); 158 | $result[trim($c[0])] = isset($c[1]) ? trim($c[1]) : ''; 159 | } else { 160 | $result[$c] = ''; 161 | } 162 | } 163 | unset($conf); 164 | return $result; 165 | } 166 | 167 | /** 168 | * 注释配置解析 169 | * 170 | * @param array $annotateConfigs 171 | * @return array 172 | */ 173 | private function parseAnnotate(array $annotateConfigs): array 174 | { 175 | $result = []; 176 | foreach ($annotateConfigs as $conf => $params) { 177 | switch ($conf) { 178 | case 'params': 179 | $result['params'] = $this->toArray($params); 180 | break; 181 | 182 | case 'cache': 183 | case 'response': 184 | case 'basicAuth': 185 | $result[$conf] = $this->toCode($params); 186 | break; 187 | 188 | case 'after': 189 | case 'before': 190 | $result[$conf] = $this->bindToClosure($params); 191 | break; 192 | 193 | default: 194 | $closureContainer = $this->delegate->getClosureContainer(); 195 | $hasClosure = $closureContainer->has('parseAnnotate'); 196 | if ($hasClosure) { 197 | $closureContainer->run('parseAnnotate', array($conf, &$params, $this)); 198 | } 199 | 200 | $result[$conf] = $params; 201 | } 202 | } 203 | 204 | return $result; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/Core/Config.php: -------------------------------------------------------------------------------- 1 | 15 | * Class Config 16 | * @package Cross\Core 17 | */ 18 | class Config 19 | { 20 | /** 21 | * @var string 22 | */ 23 | private $file; 24 | 25 | /** 26 | * @var array 27 | */ 28 | private $configData; 29 | 30 | /** 31 | * @var self 32 | */ 33 | private static $instance; 34 | 35 | /** 36 | * @var CrossArray 37 | */ 38 | private $ca; 39 | 40 | /** 41 | * 查询缓存 42 | * 43 | * @var array 44 | */ 45 | private static $cache; 46 | 47 | /** 48 | * 读取配置 49 | * 50 | * @param string $file 配置文件绝对路径 51 | * @throws CoreException 52 | */ 53 | private function __construct(string $file) 54 | { 55 | $this->file = $file; 56 | $this->configData = Loader::read($file); 57 | 58 | $localFile = dirname($file) . DIRECTORY_SEPARATOR . '.' . basename($file); 59 | if (file_exists($localFile)) { 60 | $this->configData = array_merge($this->configData, Loader::read($localFile)); 61 | } 62 | 63 | $this->ca = CrossArray::init($this->configData, $this->file); 64 | } 65 | 66 | /** 67 | * 实例化配置类 68 | * 69 | * @param string $file 70 | * @return Config 71 | * @throws CoreException 72 | */ 73 | static function load(string $file): self 74 | { 75 | if (!isset(self::$instance[$file])) { 76 | self::$instance[$file] = new self($file); 77 | } 78 | 79 | return self::$instance[$file]; 80 | } 81 | 82 | /** 83 | * 合并附加数组到源数组 84 | * 85 | * @param array $appendConfig 86 | * @param bool $cover 是否覆盖已有值 87 | * @return $this 88 | */ 89 | function combine(array $appendConfig = [], bool $cover = true): self 90 | { 91 | if (!empty($appendConfig)) { 92 | foreach ($appendConfig as $key => $value) { 93 | if ($cover) { 94 | $configValue = &$this->configData[$key]; 95 | if (is_array($value) && is_array($configValue)) { 96 | $this->configData[$key] = array_merge($configValue, $value); 97 | } else { 98 | $this->configData[$key] = $value; 99 | } 100 | } else { 101 | if (isset($this->configData[$key])) { 102 | $configValue = &$this->configData[$key]; 103 | if (is_array($value) && is_array($configValue)) { 104 | $this->configData[$key] = array_merge($value, $configValue); 105 | } 106 | } elseif (!isset($this->configData[$key])) { 107 | $this->configData[$key] = $value; 108 | } 109 | } 110 | 111 | $this->clearIndexCache($key); 112 | } 113 | } 114 | 115 | return $this; 116 | } 117 | 118 | /** 119 | * 获取指定配置 120 | * 121 | * @param string $index 122 | * @param string|array $options 123 | * @return string|array 124 | * @see CrossArray::get() 125 | */ 126 | function get(string $index, $options = '') 127 | { 128 | $key = $this->getIndexCacheKey($index); 129 | if (is_array($options)) { 130 | $opk = implode('.', $options); 131 | } elseif ($options) { 132 | $opk = $options; 133 | } else { 134 | $opk = '-###-'; 135 | } 136 | 137 | if (!isset(self::$cache[$key][$opk])) { 138 | self::$cache[$key][$opk] = $this->ca->get($index, $options); 139 | } 140 | 141 | return self::$cache[$key][$opk]; 142 | } 143 | 144 | /** 145 | * 路径查找 146 | * 147 | * @param string $path 148 | * @return mixed 149 | */ 150 | function query(string $path) 151 | { 152 | $val = null; 153 | $data = $this->configData; 154 | if (isset($data[$path])) { 155 | return $data[$path]; 156 | } 157 | 158 | $keys = explode('.', $path); 159 | while ($i = array_shift($keys)) { 160 | if (!isset($data[$i])) { 161 | $val = null; 162 | break; 163 | } 164 | 165 | $data = $val = $data[$i]; 166 | } 167 | 168 | return $val; 169 | } 170 | 171 | /** 172 | * 更新指定配置 173 | * 174 | * @param string $index 175 | * @param array|string $values 176 | * @see CrossArray::get() 177 | */ 178 | function set(string $index, $values = ''): void 179 | { 180 | $this->ca->set($index, $values); 181 | $this->clearIndexCache($index); 182 | } 183 | 184 | /** 185 | * 路径更新 186 | * 187 | * @param string $path 188 | * @param $value 189 | */ 190 | function update(string $path, $value) 191 | { 192 | $data = &$this->configData; 193 | $keys = explode('.', $path); 194 | while ($i = array_shift($keys)) { 195 | if (!isset($data[$i]) || !is_array($data[$i])) { 196 | $data[$i] = []; 197 | } 198 | 199 | $data = &$data[$i]; 200 | } 201 | 202 | $data = $value; 203 | } 204 | 205 | /** 206 | * 返回全部配置数据 207 | * 208 | * @param bool $obj 是否返回对象 209 | * @return array|object 210 | * @see CrossArray::getAll() 211 | */ 212 | function getAll($obj = false) 213 | { 214 | if ($obj) { 215 | return CrossArray::arrayToObject($this->configData); 216 | } 217 | 218 | return $this->configData; 219 | } 220 | 221 | /** 222 | * 获取数组索引缓存key 223 | * 224 | * @param string $index 225 | * @return string 226 | */ 227 | protected function getIndexCacheKey(string $index): string 228 | { 229 | return $this->file . '.' . $index; 230 | } 231 | 232 | /** 233 | * 清除缓存 234 | * 235 | * @param string $index 236 | */ 237 | protected function clearIndexCache(string $index): void 238 | { 239 | $key = $this->getIndexCacheKey($index); 240 | unset(self::$cache[$key]); 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/Core/CrossArray.php: -------------------------------------------------------------------------------- 1 | 13 | * Class CrossArray 14 | * @package Cross\Core 15 | */ 16 | class CrossArray 17 | { 18 | /** 19 | * @var array 数据 20 | */ 21 | protected $data; 22 | 23 | /** 24 | * @var self 25 | */ 26 | protected static $instance; 27 | 28 | /** 29 | * CrossArray 30 | * 31 | * @param array $data 32 | */ 33 | private function __construct(array &$data) 34 | { 35 | $this->data = &$data; 36 | } 37 | 38 | /** 39 | * @param array $data 40 | * @param string|null $cacheKey 41 | * @return CrossArray 42 | */ 43 | static function init(array &$data, string $cacheKey = null): self 44 | { 45 | if (null === $cacheKey) { 46 | $cacheKey = md5(json_encode($data)); 47 | } 48 | 49 | if (!isset(self::$instance[$cacheKey])) { 50 | self::$instance[$cacheKey] = new self($data); 51 | } 52 | 53 | return self::$instance[$cacheKey]; 54 | } 55 | 56 | /** 57 | * 获取配置参数 58 | * 59 | * @param string $config 60 | * @param string|array $name 61 | * @return bool|string|array 62 | */ 63 | function get(string $config, $name = '') 64 | { 65 | if (isset($this->data[$config])) { 66 | if ($name) { 67 | if (is_array($name)) { 68 | $result = []; 69 | foreach ($name as $n) { 70 | if (isset($this->data[$config][$n])) { 71 | $result[$n] = $this->data[$config][$n]; 72 | } 73 | } 74 | return $result; 75 | } elseif (isset($this->data[$config][$name])) { 76 | return $this->data[$config][$name]; 77 | } 78 | 79 | return false; 80 | } 81 | 82 | return $this->data[$config]; 83 | } 84 | return false; 85 | } 86 | 87 | /** 88 | * 更新成员或赋值 89 | * 90 | * @param string $index 91 | * @param string|array $values 92 | */ 93 | function set(string $index, $values = ''): void 94 | { 95 | if (is_array($values)) { 96 | if (isset($this->data[$index])) { 97 | $this->data[$index] = array_merge($this->data[$index], $values); 98 | } else { 99 | $this->data[$index] = $values; 100 | } 101 | } else { 102 | $this->data[$index] = $values; 103 | } 104 | } 105 | 106 | /** 107 | * 返回全部数据 108 | * 109 | * @param bool $obj 是否返回对象 110 | * @return array|object 111 | */ 112 | function getAll(bool $obj = false) 113 | { 114 | if ($obj) { 115 | return self::arrayToObject($this->data); 116 | } 117 | 118 | return $this->data; 119 | } 120 | 121 | /** 122 | * 数组转对象 123 | * 124 | * @param $data 125 | * @return object|string 126 | */ 127 | static function arrayToObject($data) 128 | { 129 | if (is_array($data)) { 130 | return (object)array_map('self::arrayToObject', $data); 131 | } else { 132 | return $data; 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/Core/HttpAuth.php: -------------------------------------------------------------------------------- 1 | 22 | * Class HttpAuth 23 | * @package Cross\Core 24 | */ 25 | class HttpAuth 26 | { 27 | /** 28 | * @var HttpAuthInterface|object 29 | */ 30 | static $authHandler; 31 | 32 | /** 33 | * 创建用于会话管理的对象 34 | * 35 | * @param string|object $type 36 | *
37 | * type 默认为字符串(COOKIE|SESSION|REDIS|包含命名空间的类的路径) 38 | * 也可以是一个实现了HttpAuthInterface接口的对象 39 | *40 | * @param string $authKey 加解密密钥 41 | * @return HttpAuthInterface|object 42 | * @throws CoreException 43 | */ 44 | public static function factory($type = 'cookie', string $authKey = ''): object 45 | { 46 | if (!self::$authHandler) { 47 | if (is_string($type)) { 48 | if (strcasecmp($type, 'cookie') == 0) { 49 | self::$authHandler = new CookieAuth($authKey); 50 | } elseif (strcasecmp($type, 'session') == 0) { 51 | self::$authHandler = new SessionAuth(); 52 | } elseif (strcasecmp($type, 'redis') == 0) { 53 | self::$authHandler = new RedisAuth(); 54 | } else { 55 | try { 56 | $object = new ReflectionClass($type); 57 | if ($object->implementsInterface('Cross\I\HttpAuthInterface')) { 58 | self::$authHandler = $object->newInstance(); 59 | } else { 60 | throw new CoreException('The auth class must implement the HttpAuthInterface interface.'); 61 | } 62 | } catch (Exception $e) { 63 | throw new CoreException('Reflection ' . $e->getMessage()); 64 | } 65 | } 66 | } elseif (is_object($type)) { 67 | if ($type instanceof HttpAuthInterface) { 68 | self::$authHandler = $type; 69 | } else { 70 | throw new CoreException('The auth class must implement the HttpAuthInterface interface.'); 71 | } 72 | } else { 73 | throw new CoreException('Unrecognized auth classes!'); 74 | } 75 | } 76 | return self::$authHandler; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Core/Loader.php: -------------------------------------------------------------------------------- 1 | 15 | * Class Loader 16 | * @package Cross\Core 17 | */ 18 | class Loader 19 | { 20 | /** 21 | * @var self 22 | */ 23 | private static $instance; 24 | 25 | /** 26 | * 已注册的命名空间 27 | * 28 | * @var array 29 | */ 30 | private static $namespace = []; 31 | 32 | /** 33 | * 已加载类的文件列表 34 | * 35 | * @var array 36 | */ 37 | private static $loaded = []; 38 | 39 | /** 40 | * 初始化Loader 41 | */ 42 | private function __construct() 43 | { 44 | spl_autoload_register([$this, 'loadClass']); 45 | spl_autoload_register([$this, 'loadPSRClass']); 46 | } 47 | 48 | /** 49 | * 单例模式 50 | * 51 | * @return Loader 52 | */ 53 | static function init(): self 54 | { 55 | if (!self::$instance) { 56 | self::$instance = new self(); 57 | } 58 | 59 | return self::$instance; 60 | } 61 | 62 | /** 63 | * 载入文件 64 | * 65 | * @param array|string $file 66 | * @return mixed 67 | * @throws CoreException 68 | */ 69 | static function import($file) 70 | { 71 | return self::requireFile(PROJECT_REAL_PATH . $file, true); 72 | } 73 | 74 | /** 75 | * 读取指定的单一文件 76 | * 77 | * @param string $file 78 | * @param bool $getFileContent 是否读取文件文本内容 79 | * @return mixed 80 | * @throws CoreException 81 | */ 82 | static function read(string $file, bool $getFileContent = false) 83 | { 84 | if (!file_exists($file)) { 85 | throw new CoreException("{$file} 文件不存在"); 86 | } 87 | 88 | static $cache = null; 89 | $flag = (int)$getFileContent; 90 | if (isset($cache[$file][$flag])) { 91 | return $cache[$file][$flag]; 92 | } 93 | 94 | if (is_readable($file)) { 95 | if ($getFileContent) { 96 | $fileContent = file_get_contents($file); 97 | $cache[$file][$flag] = $fileContent; 98 | return $fileContent; 99 | } 100 | 101 | switch (Helper::getExt($file)) { 102 | case 'php' : 103 | $data = require $file; 104 | $cache[$file][$flag] = $data; 105 | break; 106 | 107 | case 'json' : 108 | $data = json_decode(file_get_contents($file), true); 109 | $cache[$file][$flag] = $data; 110 | break; 111 | 112 | case 'ini': 113 | $data = parse_ini_file($file, true); 114 | $cache[$file][$flag] = $data; 115 | break; 116 | 117 | default : 118 | throw new CoreException('不支持的解析格式'); 119 | } 120 | 121 | return $data; 122 | } else { 123 | throw new CoreException("读取文件失败:{$file}"); 124 | } 125 | } 126 | 127 | /** 128 | * 获取已注册的命名空间 129 | * 130 | * @return array 131 | */ 132 | static function getNamespaceMap(): array 133 | { 134 | return self::$namespace; 135 | } 136 | 137 | /** 138 | * 注册命名空间 139 | * 140 | * @param string $prefix 名称 141 | * @param string $baseDir 源文件绝对路径 142 | * @param bool $prepend 143 | */ 144 | static function registerNamespace(string $prefix, string $baseDir, bool $prepend = false): void 145 | { 146 | $prefix = trim($prefix, '\\') . '\\'; 147 | $baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR) . '/'; 148 | if (isset(self::$namespace[$prefix]) === false) { 149 | self::$namespace[$prefix] = []; 150 | } 151 | 152 | if ($prepend) { 153 | array_unshift(self::$namespace[$prefix], $baseDir); 154 | } else { 155 | array_push(self::$namespace[$prefix], $baseDir); 156 | } 157 | } 158 | 159 | /** 160 | * 自动加载 161 | * 162 | * @param string $className 163 | * @return bool|string 164 | * @throws CoreException 165 | */ 166 | private function loadClass(string $className) 167 | { 168 | $prefix = ''; 169 | $pos = strpos($className, '\\'); 170 | if (false !== $pos) { 171 | $prefix = substr($className, 0, $pos); 172 | $className = str_replace('\\', DIRECTORY_SEPARATOR, $className); 173 | } 174 | 175 | $checkFileExists = true; 176 | if ('' !== $prefix && 0 === strcasecmp($prefix, 'cross')) { 177 | $checkFileExists = false; 178 | $classFile = CP_PATH . substr($className, $pos + 1) . '.php'; 179 | } else { 180 | $classFile = PROJECT_REAL_PATH . $className . '.php'; 181 | } 182 | 183 | $this->requireFile($classFile, false, $checkFileExists); 184 | return $classFile; 185 | } 186 | 187 | /** 188 | * PSR-4 189 | * 190 | * @param string $class 191 | * @return bool|string 192 | * @throws CoreException 193 | */ 194 | private function loadPSRClass(string $class) 195 | { 196 | $prefix = $class; 197 | while (false !== $pos = strrpos($prefix, '\\')) { 198 | $prefix = substr($class, 0, $pos + 1); 199 | $relativeClass = substr($class, $pos + 1); 200 | 201 | $mappedFile = $this->loadMappedFile($prefix, $relativeClass); 202 | if ($mappedFile) { 203 | return $mappedFile; 204 | } 205 | $prefix = rtrim($prefix, '\\'); 206 | } 207 | 208 | return false; 209 | } 210 | 211 | /** 212 | * 匹配已注册的命名空间,require文件 213 | * 214 | * @param string $prefix 215 | * @param string $relativeClass 216 | * @return bool|string 217 | * @throws CoreException 218 | */ 219 | private function loadMappedFile(string $prefix, string $relativeClass) 220 | { 221 | if (isset(self::$namespace[$prefix]) === false) { 222 | return false; 223 | } 224 | 225 | foreach (self::$namespace[$prefix] as $baseDir) { 226 | $file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php'; 227 | if ($this->requireFile($file)) { 228 | return $file; 229 | } 230 | } 231 | 232 | return false; 233 | } 234 | 235 | /** 236 | * require文件 237 | * 238 | * @param string $file 239 | * @param bool $throwException 240 | * @param bool $checkFileExists 241 | * @return bool 242 | * @throws CoreException 243 | */ 244 | private static function requireFile(string $file, bool $throwException = false, bool $checkFileExists = true): bool 245 | { 246 | if (isset(self::$loaded[$file])) { 247 | return true; 248 | } elseif ($checkFileExists === false) { 249 | require $file; 250 | self::$loaded[$file] = true; 251 | return true; 252 | } elseif (is_file($file)) { 253 | require $file; 254 | self::$loaded[$file] = true; 255 | return true; 256 | } elseif ($throwException) { 257 | throw new CoreException("未找到要载入的文件:{$file}"); 258 | } else { 259 | return false; 260 | } 261 | } 262 | 263 | } 264 | -------------------------------------------------------------------------------- /src/Core/Rest.php: -------------------------------------------------------------------------------- 1 | 22 | * Class Rest 23 | * @package Cross\Core 24 | */ 25 | class Rest 26 | { 27 | /** 28 | * @var Request 29 | */ 30 | protected $request; 31 | 32 | /** 33 | * @var Delegate 34 | */ 35 | protected $delegate; 36 | 37 | /** 38 | * @var string 39 | */ 40 | protected $requestType; 41 | 42 | /** 43 | * @var string 44 | */ 45 | protected $requestString; 46 | 47 | /** 48 | * 匹配失败时是否兼容MVC模式 49 | * 50 | * @var bool 51 | */ 52 | protected $compatibleModel = false; 53 | 54 | /** 55 | * @var Rest 56 | */ 57 | private static $instance; 58 | 59 | /** 60 | * 初始化request 61 | * 62 | * @param Delegate $delegate 63 | */ 64 | private function __construct(Delegate &$delegate) 65 | { 66 | $this->delegate = $delegate; 67 | $this->request = $delegate->getRequest(); 68 | $this->requestType = strtoupper($this->request->getRequestType()); 69 | $this->requestString = $delegate->getRouter()->getUriRequest('/', $useless, false); 70 | } 71 | 72 | /** 73 | * 创建rest实例 74 | * 75 | * @param Delegate $delegate 76 | * @return Rest 77 | */ 78 | static function getInstance(Delegate &$delegate): self 79 | { 80 | if (!self::$instance) { 81 | self::$instance = new Rest($delegate); 82 | } 83 | 84 | return self::$instance; 85 | } 86 | 87 | /** 88 | * GET 89 | * 90 | * @param string $customRouter 91 | * @param mixed $handle 92 | * @throws CoreException 93 | */ 94 | function get(string $customRouter, $handle): void 95 | { 96 | $this->addCustomRouter('GET', $customRouter, $handle); 97 | } 98 | 99 | /** 100 | * POST 101 | * 102 | * @param string $customRouter 103 | * @param mixed $handle 104 | * @throws CoreException 105 | */ 106 | function post(string $customRouter, $handle): void 107 | { 108 | $this->addCustomRouter('POST', $customRouter, $handle); 109 | } 110 | 111 | /** 112 | * PUT 113 | * 114 | * @param string $customRouter 115 | * @param mixed $handle 116 | * @throws CoreException 117 | */ 118 | function put(string $customRouter, $handle): void 119 | { 120 | $this->addCustomRouter('PUT', $customRouter, $handle); 121 | } 122 | 123 | /** 124 | * PATCH 125 | * 126 | * @param string $customRouter 127 | * @param mixed $handle 128 | * @throws CoreException 129 | */ 130 | function patch(string $customRouter, $handle): void 131 | { 132 | $this->addCustomRouter('PATCH', $customRouter, $handle); 133 | } 134 | 135 | /** 136 | * OPTIONS 137 | * 138 | * @param string $customRouter 139 | * @param mixed $handle 140 | * @throws CoreException 141 | */ 142 | function options(string $customRouter, $handle): void 143 | { 144 | $this->addCustomRouter('OPTIONS', $customRouter, $handle); 145 | } 146 | 147 | /** 148 | * DELETE 149 | * 150 | * @param string $customRouter 151 | * @param mixed $handle 152 | * @throws CoreException 153 | */ 154 | function delete(string $customRouter, $handle): void 155 | { 156 | $this->addCustomRouter('DELETE', $customRouter, $handle); 157 | } 158 | 159 | /** 160 | * HEAD 161 | * 162 | * @param string $customRouter 163 | * @param mixed $handle 164 | * @throws CoreException 165 | */ 166 | function head(string $customRouter, $handle): void 167 | { 168 | $this->addCustomRouter('HEAD', $customRouter, $handle); 169 | } 170 | 171 | /** 172 | * Any 173 | * 174 | * @param string $customRouter 175 | * @param mixed $handle 176 | * @throws CoreException 177 | */ 178 | function any(string $customRouter, $handle): void 179 | { 180 | $this->addCustomRouter($this->requestType, $customRouter, $handle); 181 | } 182 | 183 | /** 184 | * on 185 | * 186 | * @param string $name 187 | * @param Closure $f 188 | * @return Delegate 189 | * @see Delegate::on() 190 | */ 191 | function on(string $name, Closure $f): Delegate 192 | { 193 | return $this->delegate->on($name, $f); 194 | } 195 | 196 | /** 197 | * 匹配失败后是否兼容MVC模式 198 | * 199 | * @param bool $compatible 200 | */ 201 | function compatibleModel(bool $compatible = true): void 202 | { 203 | $this->compatibleModel = $compatible; 204 | } 205 | 206 | /** 207 | * 参数正则验证规则 208 | * 209 | * @param array $rules 210 | */ 211 | function rules(array $rules): void 212 | { 213 | $this->delegate->getRequestMapping()->setRules($rules); 214 | } 215 | 216 | /** 217 | * 处理请求 218 | * 219 | * @throws CoreException|FrontException 220 | */ 221 | function run(): void 222 | { 223 | $params = []; 224 | $match = $this->delegate->getRequestMapping()->match($this->requestString, $handle, $params); 225 | if ($match && $handle instanceof Closure) { 226 | $this->response($handle, $params); 227 | } elseif ($match && is_string($handle)) { 228 | $this->delegate->get($handle, $params); 229 | } elseif ($this->compatibleModel) { 230 | $this->delegate->run(); 231 | } else { 232 | $closureContainer = $this->delegate->getClosureContainer(); 233 | if ($closureContainer->has('mismatching')) { 234 | $closureContainer->run('mismatching'); 235 | } else { 236 | throw new CoreException('Not match uri'); 237 | } 238 | } 239 | } 240 | 241 | /** 242 | * 输出结果 243 | * 244 | * @param Closure $handle 245 | * @param array $params 246 | * @throws CoreException 247 | */ 248 | private function response(Closure $handle, array $params = []): void 249 | { 250 | try { 251 | $ref = new ReflectionFunction($handle); 252 | $parameters = $ref->getParameters(); 253 | 254 | $closureParams = []; 255 | if (!empty($parameters)) { 256 | foreach ($parameters as $p) { 257 | if (!isset($params[$p->name]) && !$p->isOptional()) { 258 | throw new CoreException("Callback closure need param: {$p->name}"); 259 | } 260 | 261 | $closureParams[$p->name] = new DataFilter($params[$p->name] ?? $p->getDefaultValue()); 262 | } 263 | } 264 | 265 | $content = call_user_func_array($handle, $closureParams); 266 | if (null != $content) { 267 | $this->delegate->getResponse()->setRawContent($content)->send(); 268 | } 269 | } catch (Exception $e) { 270 | throw new CoreException($e->getMessage()); 271 | } 272 | } 273 | 274 | /** 275 | * 解析自定义路由并保存参数key 276 | * 277 | * @param string $requestType 278 | * @param string $customRouter 279 | * @param mixed $handle 280 | * @throws CoreException 281 | */ 282 | private function addCustomRouter(string $requestType, string $customRouter, $handle): void 283 | { 284 | $this->delegate->getRequestMapping()->addRequestRouter($requestType, $customRouter, $handle); 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /src/DB/Connector/BaseConnector.php: -------------------------------------------------------------------------------- 1 | 16 | * Class BaseConnector 17 | * @package Cross\DB\Connector 18 | */ 19 | abstract class BaseConnector implements PDOConnector 20 | { 21 | /** 22 | * @var PDO 23 | */ 24 | protected $pdo; 25 | 26 | /** 27 | * @var string 28 | */ 29 | protected $sequence; 30 | 31 | /** 32 | * 合并用户输入的options 33 | * 34 | * @param array $defaultOptions 35 | * @param array $options 36 | * @return array 37 | */ 38 | protected static function getOptions(array $defaultOptions, array $options): array 39 | { 40 | if (!empty($options)) { 41 | foreach ($options as $optionKey => $optionVal) { 42 | $defaultOptions[$optionKey] = $optionVal; 43 | } 44 | } 45 | 46 | return $defaultOptions; 47 | } 48 | 49 | /** 50 | * 设置序号 51 | * 52 | * @param string $sequence 53 | */ 54 | public function setSequence(string $sequence): void 55 | { 56 | $this->sequence = $sequence; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/DB/Connector/MySQLConnector.php: -------------------------------------------------------------------------------- 1 | 17 | * Class MySQLConnector 18 | * @package Cross\DB\Connector 19 | */ 20 | class MySQLConnector extends BaseConnector 21 | { 22 | /** 23 | * 数据库连接实例 24 | * 25 | * @var object 26 | */ 27 | private static $instance; 28 | 29 | /** 30 | * 默认连接参数 31 | *
19 | * example: 20 | * echo HTML::div('im a div'); 21 | * echo HTML::a(['@content'=>'crossphp', 'href'=>'http://www.crossphp.com']); 22 | * echo HTML::div(['@content' => 'im a div', 'style'=>'border:1px solid #dddddd;padding:20px;'], 23 | * HTML::a(['@content'=>'crossphp', 'href'=>'http://www.crossphp.com']) 24 | * ); 25 | * echo HTML::form(['method'=>'get'], 26 | * HTML::div( 27 | * HTML::label('User Name:', HTML::input(['type'=>'text'])), 28 | * HTML::label('Password :', HTML::input(['type'=>'password'])), 29 | * HTML::label(' ', HTML::input(['type'=>'submit', 'value'=>'submit'])) 30 | * ) 31 | * ); 32 | * 33 | * 支持标签列表: 34 | * https://developer.mozilla.org/en-US/docs/Web/HTML/Element 35 | *36 | * 37 | * @method static base($attributes, ...$ctx = null) 38 | * @method static head($attributes, ...$ctx = null) 39 | * @method static link($attributes, ...$ctx = null) 40 | * @method static meta($attributes, ...$ctx = null) 41 | * @method static style($attributes, ...$ctx = null) 42 | * @method static title($attributes, ...$ctx = null) 43 | * @method static body($attributes, ...$ctx = null) 44 | * @method static address($attributes, ...$ctx = null) 45 | * @method static article($attributes, ...$ctx = null) 46 | * @method static aside($attributes, ...$ctx = null) 47 | * @method static footer($attributes, ...$ctx = null) 48 | * @method static header($attributes, ...$ctx = null) 49 | * @method static h1($attributes, ...$ctx = null) 50 | * @method static h2($attributes, ...$ctx = null) 51 | * @method static h3($attributes, ...$ctx = null) 52 | * @method static h4($attributes, ...$ctx = null) 53 | * @method static h5($attributes, ...$ctx = null) 54 | * @method static h6($attributes, ...$ctx = null) 55 | * @method static hgroup($attributes, ...$ctx = null) 56 | * @method static main($attributes, ...$ctx = null) 57 | * @method static nav($attributes, ...$ctx = null) 58 | * @method static section($attributes, ...$ctx = null) 59 | * @method static blockquote($attributes, ...$ctx = null) 60 | * @method static dd($attributes, ...$ctx = null) 61 | * @method static div($attributes, ...$ctx = null) 62 | * @method static dl($attributes, ...$ctx = null) 63 | * @method static dt($attributes, ...$ctx = null) 64 | * @method static figcaption($attributes, ...$ctx = null) 65 | * @method static figure($attributes, ...$ctx = null) 66 | * @method static hr($attributes, ...$ctx = null) 67 | * @method static li($attributes, ...$ctx = null) 68 | * @method static ol($attributes, ...$ctx = null) 69 | * @method static p($attributes, ...$ctx = null) 70 | * @method static pre($attributes, ...$ctx = null) 71 | * @method static ul($attributes, ...$ctx = null) 72 | * @method static a($attributes, ...$ctx = null) 73 | * @method static abbr($attributes, ...$ctx = null) 74 | * @method static b($attributes, ...$ctx = null) 75 | * @method static bdi($attributes, ...$ctx = null) 76 | * @method static bdo($attributes, ...$ctx = null) 77 | * @method static br($attributes, ...$ctx = null) 78 | * @method static cite($attributes, ...$ctx = null) 79 | * @method static code($attributes, ...$ctx = null) 80 | * @method static data($attributes, ...$ctx = null) 81 | * @method static dfn($attributes, ...$ctx = null) 82 | * @method static em($attributes, ...$ctx = null) 83 | * @method static i($attributes, ...$ctx = null) 84 | * @method static kbd($attributes, ...$ctx = null) 85 | * @method static mark($attributes, ...$ctx = null) 86 | * @method static q($attributes, ...$ctx = null) 87 | * @method static rb($attributes, ...$ctx = null) 88 | * @method static rp($attributes, ...$ctx = null) 89 | * @method static rt($attributes, ...$ctx = null) 90 | * @method static rtc($attributes, ...$ctx = null) 91 | * @method static ruby($attributes, ...$ctx = null) 92 | * @method static s($attributes, ...$ctx = null) 93 | * @method static samp($attributes, ...$ctx = null) 94 | * @method static small($attributes, ...$ctx = null) 95 | * @method static span($attributes, ...$ctx = null) 96 | * @method static strong($attributes, ...$ctx = null) 97 | * @method static sub($attributes, ...$ctx = null) 98 | * @method static sup($attributes, ...$ctx = null) 99 | * @method static time($attributes, ...$ctx = null) 100 | * @method static u($attributes, ...$ctx = null) 101 | * @method static var($attributes, ...$ctx = null) 102 | * @method static wbr($attributes, ...$ctx = null) 103 | * @method static area($attributes, ...$ctx = null) 104 | * @method static audio($attributes, ...$ctx = null) 105 | * @method static img($attributes, ...$ctx = null) 106 | * @method static map($attributes, ...$ctx = null) 107 | * @method static track($attributes, ...$ctx = null) 108 | * @method static video($attributes, ...$ctx = null) 109 | * @method static embed($attributes, ...$ctx = null) 110 | * @method static iframe($attributes, ...$ctx = null) 111 | * @method static object($attributes, ...$ctx = null) 112 | * @method static param($attributes, ...$ctx = null) 113 | * @method static picture($attributes, ...$ctx = null) 114 | * @method static source($attributes, ...$ctx = null) 115 | * @method static canvas($attributes, ...$ctx = null) 116 | * @method static noscript($attributes, ...$ctx = null) 117 | * @method static script($attributes, ...$ctx = null) 118 | * @method static del($attributes, ...$ctx = null) 119 | * @method static ins($attributes, ...$ctx = null) 120 | * @method static caption($attributes, ...$ctx = null) 121 | * @method static col($attributes, ...$ctx = null) 122 | * @method static colgroup($attributes, ...$ctx = null) 123 | * @method static table($attributes, ...$ctx = null) 124 | * @method static tbody($attributes, ...$ctx = null) 125 | * @method static td($attributes, ...$ctx = null) 126 | * @method static tfoot($attributes, ...$ctx = null) 127 | * @method static th($attributes, ...$ctx = null) 128 | * @method static thead($attributes, ...$ctx = null) 129 | * @method static tr($attributes, ...$ctx = null) 130 | * @method static button($attributes, ...$ctx = null) 131 | * @method static datalist($attributes, ...$ctx = null) 132 | * @method static fieldset($attributes, ...$ctx = null) 133 | * @method static form($attributes, ...$ctx = null) 134 | * @method static input($attributes, ...$ctx = null) 135 | * @method static label($attributes, ...$ctx = null) 136 | * @method static legend($attributes, ...$ctx = null) 137 | * @method static meter($attributes, ...$ctx = null) 138 | * @method static optgroup($attributes, ...$ctx = null) 139 | * @method static option($attributes, ...$ctx = null) 140 | * @method static output($attributes, ...$ctx = null) 141 | * @method static progress($attributes, ...$ctx = null) 142 | * @method static select($attributes, ...$ctx = null) 143 | * @method static textarea($attributes, ...$ctx = null) 144 | * @method static details($attributes, ...$ctx = null) 145 | * @method static dialog($attributes, ...$ctx = null) 146 | * @method static menu($attributes, ...$ctx = null) 147 | * @method static summary($attributes, ...$ctx = null) 148 | * @method static slot($attributes, ...$ctx = null) 149 | * @method static template($attributes, ...$ctx = null) 150 | */ 151 | class HTML 152 | { 153 | /** 154 | * HTML处理类入口 155 | * 156 | * @param string $name 157 | * @param mixed $arguments 158 | * @return CallTree 159 | */ 160 | static function __callStatic(string $name, $arguments): CallTree 161 | { 162 | $callTree = CallTree::getInstance(); 163 | $callTree->saveNode($name, $arguments); 164 | return $callTree; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/Lib/StringToPHPStream.php: -------------------------------------------------------------------------------- 1 | 4 | * StringToPHPStream.php 5 | */ 6 | 7 | namespace Cross\Lib; 8 | 9 | /** 10 | * 字符串php代码通过wrapper转换为可以执行的php代码 11 | *
12 | * 使用方式 stream_register_wrapper("自定义名字", "stringToPHPStream") 13 | * $var = include ("自定义名字://字符串代码") 14 | *15 | * 16 | * @author wonli
59 | * 默认为项目目录下的config/db.config.php 60 | * 可以在app目录下init.php文件中通过'sys' => 'db_config'指定 61 | *62 | * 63 | * @var string 64 | */ 65 | protected $dbConfigFile; 66 | 67 | /** 68 | * 解析要连接model的参数 69 | * 70 | * @param string $params 指定要连接的数据库和配置项的key, 如mysql['db']这里的params应该为mysql:db 71 | * @throws CoreException 72 | */ 73 | function __construct(string $params = '') 74 | { 75 | parent::__construct(); 76 | 77 | $config = $this->parseModelParams($params); 78 | $this->linkName = &$config['model_name']; 79 | $this->linkType = &$config['model_type']; 80 | $this->linkConfig = &$config['model_config']; 81 | } 82 | 83 | /** 84 | * 创建model实例,参数格式和构造函数一致 85 | * 86 | * @param string $params 87 | * @param array $config 88 | * @return RedisDriver|CouchDriver|MongoDriver|PDOSqlDriver|mixed 89 | * @throws CoreException 90 | * @throws DBConnectException 91 | */ 92 | function getModel(string $params = '', &$config = []): object 93 | { 94 | static $cache = []; 95 | if (!isset($cache[$params])) { 96 | $config = $this->parseModelParams($params); 97 | $model = DBFactory::make($config['model_type'], $config['model_config'], array($this->getConfig())); 98 | $cache[$params] = array('config' => $config, 'model' => $model); 99 | } else { 100 | $model = $cache[$params]['model']; 101 | $config = $cache[$params]['config']; 102 | } 103 | 104 | return $model; 105 | } 106 | 107 | /** 108 | * 当前link的model名称 109 | * 110 | * @return string 111 | */ 112 | function getLinkName(): string 113 | { 114 | return $this->linkName; 115 | } 116 | 117 | /** 118 | * 当前link的model类型 119 | * 120 | * @return string 121 | */ 122 | function getLinkType(): string 123 | { 124 | return $this->linkType; 125 | } 126 | 127 | /** 128 | * 当前link的model详细配置信息 129 | * 130 | * @return array 131 | */ 132 | function getLinkConfig(): array 133 | { 134 | return $this->linkConfig; 135 | } 136 | 137 | /** 138 | * 获取带配置前缀的表名 139 | * 140 | * @param string $table 141 | * @return string 142 | */ 143 | function getPrefix(string $table = ''): string 144 | { 145 | return $this->link->getPrefix() . $table; 146 | } 147 | 148 | /** 149 | * 读取并解析数据库配置 150 | * 151 | * @return Config 152 | * @throws CoreException 153 | */ 154 | protected function databaseConfig(): Config 155 | { 156 | static $databaseConfig = null; 157 | if (null === $databaseConfig) { 158 | $databaseConfig = parent::loadConfig($this->getModuleConfigFile()); 159 | } 160 | 161 | return $databaseConfig; 162 | } 163 | 164 | /** 165 | * 设置配置文件名 166 | * 167 | * @param string $linkConfigFile 168 | */ 169 | protected function setDatabaseConfigFile(string $linkConfigFile): void 170 | { 171 | $this->dbConfigFile = $linkConfigFile; 172 | } 173 | 174 | /** 175 | * 解析指定model的类型和参数 176 | * 177 | * @param string $params 178 | * @return array 179 | * @throws CoreException 180 | */ 181 | protected function parseModelParams(string $params = ''): array 182 | { 183 | $dbConfigParams = ''; 184 | if ($params) { 185 | $dbConfigParams = $params; 186 | } else { 187 | static $defaultDbConfig = ''; 188 | if ($defaultDbConfig === '') { 189 | $defaultDbConfig = $this->getConfig()->get('sys', 'default_db'); 190 | } 191 | 192 | if ($defaultDbConfig) { 193 | $dbConfigParams = $defaultDbConfig; 194 | } 195 | } 196 | 197 | if ($dbConfigParams) { 198 | if (strpos($dbConfigParams, ':') === false) { 199 | throw new CoreException("数据库参数配置格式不正确: {$dbConfigParams}"); 200 | } 201 | 202 | list($modelType, $modelName) = explode(':', $dbConfigParams); 203 | } else { 204 | $modelName = 'db'; 205 | $modelType = 'mysql'; 206 | } 207 | 208 | static $cache; 209 | if (!isset($cache[$modelType][$modelName])) { 210 | $databaseConfig = $this->databaseConfig(); 211 | $modelConfig = $databaseConfig->get($modelType, $modelName); 212 | if (empty($modelConfig)) { 213 | throw new CoreException("未配置的Model: {$modelType}:{$modelName}"); 214 | } 215 | 216 | $cache[$modelType][$modelName] = [ 217 | 'model_name' => $modelName, 218 | 'model_type' => $modelType, 219 | 'model_config' => $modelConfig, 220 | ]; 221 | } 222 | 223 | return $cache[$modelType][$modelName]; 224 | } 225 | 226 | /** 227 | * 获取默认model的实例 228 | * 229 | * @return RedisDriver|CouchDriver|MongoDriver|PDOSqlDriver|mixed 230 | * @throws CoreException 231 | * @throws DBConnectException 232 | */ 233 | private function getLink(): object 234 | { 235 | return DBFactory::make($this->linkType, $this->linkConfig, [$this->getConfig()]); 236 | } 237 | 238 | /** 239 | * 获取连接配置文件名 240 | * 241 | * @return mixed 242 | */ 243 | private function getModuleConfigFile(): string 244 | { 245 | if (!$this->dbConfigFile) { 246 | $dbConfigFile = $this->getConfig()->get('sys', 'db_config'); 247 | if (!$dbConfigFile) { 248 | $dbConfigFile = 'db.config.php'; 249 | } 250 | 251 | $this->setDatabaseConfigFile($dbConfigFile); 252 | } 253 | 254 | return $this->dbConfigFile; 255 | } 256 | 257 | /** 258 | * 访问link属性时才实例化model 259 | * 260 | * @param mixed $property 261 | * @return RedisDriver|Config|CouchDriver|MongoDriver|PDOSqlDriver|Request|Response|View|mixed|null 262 | * @throws CoreException 263 | * @throws DBConnectException 264 | */ 265 | function __get($property) 266 | { 267 | switch ($property) { 268 | case 'link' : 269 | return $this->link = $this->getLink(); 270 | 271 | default : 272 | return parent::__get($property); 273 | } 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /src/Runtime/ClosureContainer.php: -------------------------------------------------------------------------------- 1 | 16 | * Class ClosureContainer 17 | * @package Cross\Core 18 | */ 19 | class ClosureContainer 20 | { 21 | /** 22 | * @var array 23 | */ 24 | protected $actions = []; 25 | 26 | /** 27 | * @var static 28 | */ 29 | private static $instance; 30 | 31 | /** 32 | * ClosureContainer constructor. 33 | */ 34 | private function __construct() 35 | { 36 | 37 | } 38 | 39 | /** 40 | * @return static 41 | */ 42 | static function getInstance(): self 43 | { 44 | if (null === self::$instance) { 45 | self::$instance = new self(); 46 | } 47 | 48 | return self::$instance; 49 | } 50 | 51 | /** 52 | * 注册一个匿名方法 53 | * 54 | * @param string $name 55 | * @param Closure $f 56 | */ 57 | function add(string $name, Closure $f): void 58 | { 59 | $this->actions[$name] = $f; 60 | } 61 | 62 | /** 63 | * 执行指定的匿名方法 64 | * 65 | * @param string $name 66 | * @param array $params 67 | * @return mixed 68 | */ 69 | function run(string $name, array $params = []) 70 | { 71 | if (isset($this->actions[$name])) { 72 | if (!is_array($params)) { 73 | $params = [$params]; 74 | } 75 | return call_user_func_array($this->actions[$name], $params); 76 | } 77 | 78 | return false; 79 | } 80 | 81 | /** 82 | * 执行指定的匿名方法并缓存执行结果 83 | * 84 | * @param string $name 85 | * @param array $params 86 | * @return mixed 87 | */ 88 | function runOnce(string $name, array $params = []) 89 | { 90 | static $cache = []; 91 | if (isset($cache[$name])) { 92 | return $cache[$name]; 93 | } elseif (isset($this->actions[$name])) { 94 | if (!is_array($params)) { 95 | $params = [$params]; 96 | } 97 | 98 | $cache[$name] = call_user_func_array($this->actions[$name], $params); 99 | return $cache[$name]; 100 | } 101 | 102 | return false; 103 | } 104 | 105 | /** 106 | * 检查指定的匿名方法是否已经注册 107 | * 108 | * @param string $name 109 | * @param Closure|null $closure 110 | * @return bool 111 | */ 112 | function has(string $name, &$closure = null): bool 113 | { 114 | if (isset($this->actions[$name])) { 115 | $closure = $this->actions[$name]; 116 | return true; 117 | } 118 | 119 | return false; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Runtime/RequestMapping.php: -------------------------------------------------------------------------------- 1 | matchString = $request; 70 | $routers = $this->mapping[Request::getInstance()->getRequestType()] ?? []; 71 | if (empty($routers)) { 72 | return $match; 73 | } 74 | 75 | if (!empty($routers['high']) && isset($routers['high'][$request])) { 76 | $handle = $routers['high'][$request]; 77 | if (is_array($handle)) { 78 | $handle = $handle['handler']; 79 | } 80 | 81 | $match = true; 82 | } 83 | 84 | if (!$match && !empty($routers['current'])) { 85 | $match = $this->matchProcess($routers['current'], $handle, $params); 86 | } 87 | 88 | if (!$match && !empty($routers['global'])) { 89 | $match = $this->matchProcess($routers['global'], $handle, $params); 90 | } 91 | 92 | return $match; 93 | } 94 | 95 | /** 96 | * 设置验证规则 97 | * 98 | * @param array $rules 99 | */ 100 | function setRules(array $rules): void 101 | { 102 | $this->rules = $rules; 103 | } 104 | 105 | /** 106 | * 添加验证规则 107 | * 108 | * @param string $params 109 | * @param string $pattern 110 | * @return static 111 | */ 112 | function addRule($params, $pattern): self 113 | { 114 | $this->rules[$params] = $pattern; 115 | return $this; 116 | } 117 | 118 | /** 119 | * 添加路由(按当前请求类型分组,等同于Any) 120 | * 121 | * @param mixed $router 122 | * @param mixed $handler 123 | * @return bool 124 | * @throws CoreException 125 | */ 126 | static function addRouter($router, $handler = null): bool 127 | { 128 | return self::getInstance()->addToMapping(Request::getInstance()->getRequestType(), $router, $handler); 129 | } 130 | 131 | /** 132 | * 添加HTTP路由 133 | * 134 | * @param string $requestType 135 | * @param string $router 136 | * @param mixed $handler 137 | * @return bool 138 | * @throws CoreException 139 | */ 140 | function addRequestRouter(string $requestType, string $router, $handler = null): bool 141 | { 142 | return $this->addToMapping($requestType, $router, $handler); 143 | } 144 | 145 | /** 146 | * 循环匹配(参数多的优先) 147 | * 148 | * @param array $routers 149 | * @param mixed $handle 150 | * @param array $params 151 | * @return bool 152 | */ 153 | private function matchProcess(array $routers, &$handle, array &$params): bool 154 | { 155 | foreach ($routers as $r => &$rr) { 156 | if (isset($r[1]) && isset($this->matchString[1]) && 0 === strcasecmp($r[1], $this->matchString[1])) { 157 | $rr['score'] += 10000; 158 | } 159 | } 160 | 161 | uasort($routers, function ($a, $b) { 162 | return $a['score'] < $b['score']; 163 | }); 164 | 165 | foreach ($routers as $router => $setting) { 166 | $params = []; 167 | if (true === $this->matchCustomRouter($router, $setting['params'], $params)) { 168 | $handle = $setting['handler']; 169 | return true; 170 | } 171 | } 172 | 173 | return false; 174 | } 175 | 176 | /** 177 | * 匹配uri和自定义路由 178 | * 179 | * @param string $customRouter 180 | * @param array $paramsKeys 181 | * @param array $params 182 | * @return bool 183 | */ 184 | private function matchCustomRouter(string $customRouter, array $paramsKeys = [], array &$params = []): bool 185 | { 186 | $matchString = $this->matchString; 187 | $customRouterParamsToken = preg_replace("#\{:(.*?)\}#", '{PARAMS}', $customRouter); 188 | while (strlen($customRouterParamsToken) > 0) { 189 | $definedParamsPos = strpos($customRouterParamsToken, '{PARAMS}'); 190 | if ($definedParamsPos) { 191 | $compareRet = substr_compare($customRouterParamsToken, $matchString, 0, $definedParamsPos); 192 | } else { 193 | $compareRet = strcasecmp($customRouterParamsToken, $matchString); 194 | } 195 | 196 | if ($compareRet !== 0) { 197 | return false; 198 | } 199 | 200 | //分段解析 201 | $customRouterParamsToken = substr($customRouterParamsToken, $definedParamsPos + 8); 202 | $matchString = substr($matchString, $definedParamsPos); 203 | 204 | if ($customRouterParamsToken) { 205 | //下一个标识符的位置 206 | $nextDefinedDotPos = strpos($matchString, $customRouterParamsToken[0]); 207 | $paramValue = substr($matchString, 0, $nextDefinedDotPos); 208 | $matchString = substr($matchString, $nextDefinedDotPos); 209 | } else { 210 | $paramValue = $matchString; 211 | } 212 | 213 | $keyName = array_shift($paramsKeys); 214 | if ($keyName && isset($this->rules[$keyName]) && !preg_match($this->rules[$keyName], $paramValue)) { 215 | return false; 216 | } 217 | 218 | if ($keyName) { 219 | $params[$keyName] = $paramValue; 220 | } 221 | } 222 | 223 | return true; 224 | } 225 | 226 | /** 227 | * 构造mapping 228 | * 229 | * @param string $groupKey 分组名称 230 | * @param string $customRouter 231 | * @param null $handler 232 | * @return bool 233 | * @throws CoreException 234 | */ 235 | private function addToMapping(string $groupKey, string $customRouter, $handler = null): bool 236 | { 237 | $customRouter = trim($customRouter); 238 | $isLowLevelRouter = preg_match_all("#(.*?)(?:(?:\[(.))|)\{:(.*?)\}(?:\]|)|(?:.+)#", $customRouter, $matches); 239 | if ($isLowLevelRouter) { 240 | $level = 'current'; 241 | $prefixStringLength = strlen($matches[1][0]); 242 | if ($prefixStringLength == 0) { 243 | $level = 'high'; 244 | } elseif ($prefixStringLength == 1) { 245 | $level = 'global'; 246 | } 247 | 248 | $hasOptional = false; 249 | $optional = $matches[2]; 250 | foreach ($optional as $n => $op) { 251 | if (!empty($op)) { 252 | $hasOptional = true; 253 | } 254 | 255 | if ($hasOptional && empty($op)) { 256 | throw new CoreException('Request mapping syntax error!'); 257 | } 258 | } 259 | 260 | if ($hasOptional) { 261 | $ori = $matches[1][0]; 262 | $oriLevel = 'high'; 263 | $oriRouterHandel = $handler; 264 | 265 | //处理可选参数 266 | $j = 0; 267 | $paramsKey = []; 268 | $optionalRouters = $ori; 269 | foreach ($matches[3] as $n => $optionalParamsName) { 270 | if ($n > 0 && !empty($matches[1][$n])) { 271 | $j++; 272 | $optionalRouters .= $matches[1][$n]; 273 | $this->mapping[$groupKey][$level][$optionalRouters] = [ 274 | 'handler' => $handler, 275 | 'score' => count($paramsKey) * 100 + $j, 276 | 'params' => $paramsKey, 277 | ]; 278 | } 279 | 280 | $j++; 281 | $paramsKey[] = $matches[3][$n]; 282 | $optionalRouters .= sprintf('%s{:%s}', $optional[$n], $optionalParamsName); 283 | $this->mapping[$groupKey][$level][$optionalRouters] = [ 284 | 'handler' => $handler, 285 | 'score' => count($paramsKey) * 100 + $j, 286 | 'params' => $paramsKey, 287 | ]; 288 | } 289 | } else { 290 | $ori = $customRouter; 291 | $oriLevel = $level; 292 | $oriRouterHandel = [ 293 | 'handler' => $handler, 294 | 'score' => count($matches[3]) * 100, 295 | 'params' => array_filter($matches[3]), 296 | ]; 297 | } 298 | 299 | $this->mapping[$groupKey][$oriLevel][$ori] = $oriRouterHandel; 300 | } else { 301 | $this->mapping[$groupKey]['high'][$customRouter] = $handler; 302 | } 303 | 304 | return true; 305 | } 306 | 307 | } -------------------------------------------------------------------------------- /src/Runtime/Rules.php: -------------------------------------------------------------------------------- 1 | rules = []; 31 | } 32 | 33 | /** 34 | * 匹配 35 | * 36 | * @param string $name 37 | * @param mixed $content 38 | * @return mixed 39 | * @throws CoreException 40 | */ 41 | static function match(string $name, $content) 42 | { 43 | $handler = self::instance()->has($name); 44 | if (null === $handler) { 45 | throw new CoreException('未定义的Rule'); 46 | } 47 | 48 | if ($handler instanceof Closure) { 49 | return $handler($content); 50 | } 51 | 52 | $isMatch = preg_match(preg_quote($handler), $content); 53 | if (!$isMatch) { 54 | throw new CoreException('验证Rule失败'); 55 | } 56 | 57 | return $content; 58 | } 59 | 60 | /** 61 | * rule是否存在 62 | * 63 | * @param string $name 64 | * @return mixed 65 | */ 66 | static function has(string $name) 67 | { 68 | return self::instance()->rules[$name] ?? null; 69 | } 70 | 71 | /** 72 | * 获取所有规则 73 | * 74 | * @return array 75 | */ 76 | static function getRules(): array 77 | { 78 | return self::instance()->rules; 79 | } 80 | 81 | /** 82 | * 添加规则 83 | * 84 | * @param string $name 85 | * @param mixed $rule 86 | * @return string 87 | */ 88 | static function addRule(string $name, $rule) 89 | { 90 | self::instance()->rules[$name] = $rule; 91 | return $rule; 92 | } 93 | 94 | /** 95 | * @return Rules 96 | */ 97 | protected static function instance() 98 | { 99 | if (null === self::$instance) { 100 | self::$instance = new self(); 101 | } 102 | 103 | return self::$instance; 104 | } 105 | } -------------------------------------------------------------------------------- /tests/MainTest.php: -------------------------------------------------------------------------------- 1 | 17 | * Class MainTest 18 | */ 19 | class MainTest extends TestCase 20 | { 21 | /** 22 | * 是否能正确加载app 23 | * 24 | * @throws CoreException 25 | * @throws FrontException 26 | */ 27 | function testLoadApp() 28 | { 29 | $app = Delegate::loadApp('test'); 30 | $this->assertInstanceOf('Cross\\Core\\Delegate', $app, 'loadApp error'); 31 | } 32 | 33 | /** 34 | * 读取app配置文件 35 | * 36 | * @throws CoreException 37 | * @throws FrontException 38 | */ 39 | function testReadAppConfig() 40 | { 41 | $result = $this->getAppResponse('Main:getAppConfig'); 42 | $ori_file = Loader::read(PROJECT_REAL_PATH . 'app/test/init.php'); 43 | $this->assertJsonStringEqualsJsonString($result, json_encode($ori_file['router'], true), 'read app/init.php error...'); 44 | } 45 | 46 | /** 47 | * 设置appConfig 48 | * 49 | * @throws CoreException 50 | * @throws FrontException 51 | */ 52 | function testSetAppConfig() 53 | { 54 | $params = array('a' => array(1, 2, 3, 'name' => array('a', 'b', 'c'))); 55 | $result = $this->getAppResponse('Main:setAppConfig', $params); 56 | 57 | $this->assertEquals($result, json_encode($params), 'set app config error...'); 58 | } 59 | 60 | /** 61 | * 测试注释配置 62 | * 使用get调用时, 注释配置依然生效 63 | * 64 | * @throws CoreException 65 | * @throws FrontException 66 | */ 67 | function testAnnotate() 68 | { 69 | $params = array('a' => 'ima', 'x' => 'imx'); 70 | $result = $this->getAppResponse('Main:annotate', $params); 71 | 72 | $this->assertEquals($result, array('a' => 'ima', 'b' => 2, 'c' => 3, 'x' => 'imx'), 'parse annotate error...'); 73 | } 74 | 75 | /** 76 | * 测试生成连接 77 | * 78 | * @throws CoreException 79 | * @throws FrontException 80 | */ 81 | function testMakeLink() 82 | { 83 | $dot = '/'; 84 | $ext = ''; 85 | $params = array('p1' => 1, 'p2' => 2, 'p3' => 3); 86 | $params['dot'] = $dot; 87 | $params['ext'] = $ext; 88 | $params['index'] = 'index.php'; 89 | 90 | for ($link_type = 1; $link_type <= 5; $link_type++) { 91 | $params['type'] = $link_type; 92 | $result = $this->getAppResponse('Main:makeLink', $params); 93 | 94 | switch ($link_type) { 95 | case 1: 96 | $this->assertEquals("/index.php/Main{$dot}getUrlSecurityParams{$dot}1{$dot}2{$dot}3{$ext}", $result, 'url->type=>1 make link error'); 97 | break; 98 | case 2: 99 | $this->assertEquals("/index.php/Main{$dot}getUrlSecurityParams{$dot}p1{$dot}1{$dot}p2{$dot}2{$dot}p3{$dot}3{$ext}", $result, 'url->type=>4 make link error'); 100 | break; 101 | default: 102 | $this->assertEquals("/index.php/Main{$dot}getUrlSecurityParams{$ext}?p1=1&p2=2&p3=3", $result, 'url->type=>2 make link error'); 103 | break; 104 | } 105 | } 106 | } 107 | 108 | /** 109 | * 生成加密连接测试 110 | * 111 | * @throws CoreException 112 | * @throws FrontException 113 | */ 114 | function testMakeEncryptLink() 115 | { 116 | $dot = '/'; 117 | $ext = ''; 118 | $params = array('p1' => 1, 'p2' => 2, 'p3' => 3); 119 | $params['dot'] = $dot; 120 | $params['ext'] = $ext; 121 | $params['index'] = 'index.php'; 122 | 123 | for ($link_type = 1; $link_type <= 5; $link_type++) { 124 | $params['type'] = $link_type; 125 | $result = $this->getAppResponse('Main:makeEncryptLink', $params); 126 | 127 | switch ($link_type) { 128 | case 1: 129 | $this->assertEquals("/index.php/Main{$dot}getUrlSecurityParams{$dot}5c38a0417051803{$ext}", $result, 'url->type=>1 make link error'); 130 | break; 131 | case 2: 132 | $this->assertEquals("/index.php/Main{$dot}getUrlSecurityParams{$dot}692ad450918061f435418041815561a03{$ext}", $result, 'url->type=>4 make link error'); 133 | break; 134 | default: 135 | $this->assertEquals("/index.php/Main{$dot}getUrlSecurityParams{$ext}?cd4b145090a061643540a041115560803", $result, 'url->type=>2 make link error'); 136 | break; 137 | } 138 | } 139 | } 140 | 141 | /** 142 | * url加密 参数解密测试 143 | * 144 | * @throws CoreException 145 | * @throws FrontException 146 | */ 147 | function testMakeEncryptLinkAndDecryptParams() 148 | { 149 | $dot = '/'; 150 | $ext = ''; 151 | $params = array('p1' => '1', 'p2' => '2', 'p3' => '3'); 152 | 153 | for ($link_type = 1; $link_type <= 3; $link_type++) { 154 | $result = $this->getAppResponse('Main:makeEncryptLinkAndDecryptParams', $params + array( 155 | 'dot' => $dot, 'ext' => $ext, 'link_type' => $link_type 156 | )); 157 | 158 | $this->assertEquals(json_encode($params), json_encode($result), "url type {$link_type} encrypt link failure!"); 159 | } 160 | } 161 | 162 | /** 163 | * SQL条件语句生成 164 | * 165 | * @throws CoreException 166 | */ 167 | function testSqlCondition() 168 | { 169 | $SQL = new SQLAssembler(); 170 | 171 | $p1 = array(); 172 | $r1 = $SQL->parseWhere(array('a' => 1, 'b' => 2), $p1); 173 | $this->assertEquals($r1, 'a = ? AND b = ?', 'condition 1 failure'); 174 | $this->assertEquals($p1, array(1, 2), 'condition 1 params failure'); 175 | 176 | $p2 = array(); 177 | $r2 = $SQL->parseWhere(array('a' => 1, 'b' => array('>=', 2)), $p2); 178 | $this->assertEquals($r2, 'a = ? AND b >= ?', 'condition 2 failure'); 179 | $this->assertEquals($p2, array(1, 2), 'condition 2 params failure'); 180 | 181 | $p3 = array(); 182 | $r3 = $SQL->parseWhere(array('a' => 1, '(b > ? OR b < ?)' => array(1, 2)), $p3); 183 | $this->assertEquals($r3, 'a = ? AND (b > ? OR b < ?)', 'condition 3 failure'); 184 | $this->assertEquals($p3, array(1, 1, 2), 'condition 3 params failure'); 185 | 186 | $p4 = array(); 187 | $r4 = $SQL->parseWhere(array( 188 | 'a' => array('AND', array( 189 | array('>=', 1), 190 | array('<=', 10), 191 | )) 192 | ), $p4); 193 | $this->assertEquals($r4, '(a >= ? AND a <= ?)', 'condition 4 failure'); 194 | $this->assertEquals($p4, array(1, 10), 'condition 4 params failure'); 195 | 196 | $p5 = array(); 197 | $r5 = $SQL->parseWhere(array( 198 | 'a' => array('between', array(1, 10)) 199 | ), $p5); 200 | $this->assertEquals($r5, 'a BETWEEN ? AND ?', 'condition 5 failure'); 201 | $this->assertEquals($p5, array(1, 10), 'condition 5 failure'); 202 | 203 | $p6 = array(); 204 | $r6 = $SQL->parseWhere(array( 205 | 'a' => array('or', array(1, 10)) 206 | ), $p6); 207 | $this->assertEquals($r6, '(a = ? OR a = ?)', 'condition 6 failure'); 208 | $this->assertEquals($p6, array(1, 10), 'condition 6 failure'); 209 | 210 | $p7 = array(); 211 | $r7 = $SQL->parseWhere(array( 212 | 'a' => array('or', array(1, 10)), 213 | 'b' => array('and', array( 214 | array('>=', 1), 215 | array('<=', 2) 216 | )), 217 | 'c' => array('between', array(1, 2)) 218 | ), $p7); 219 | $this->assertEquals($r7, '(a = ? OR a = ?) AND (b >= ? AND b <= ?) AND c BETWEEN ? AND ?', 'condition 7 failure'); 220 | $this->assertEquals($p7, array(1, 10, 1, 2, 1, 2), 'condition 7 failure'); 221 | 222 | $p8 = array(); 223 | $r8 = $SQL->parseWhere(array( 224 | '(a = ? OR a = ?) AND (b >= ? AND b <= ?) AND c BETWEEN ? AND ?', array(1, 10, 1, 2, 1, 2) 225 | ), $p8); 226 | $this->assertEquals($r8, '(a = ? OR a = ?) AND (b >= ? AND b <= ?) AND c BETWEEN ? AND ?', 'condition 8 failure'); 227 | $this->assertEquals($p8, array(1, 10, 1, 2, 1, 2), 'condition 8 failure'); 228 | } 229 | 230 | /** 231 | * 调用app指定controller 232 | * 233 | * @param $controller 234 | * @param array $params 235 | * @return array|mixed|string 236 | * @throws CoreException 237 | * @throws FrontException 238 | */ 239 | protected function getAppResponse($controller, $params = array()) 240 | { 241 | return Delegate::loadApp('test')->get($controller, $params, true); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | 进入命令行模式在框架根目录下使用`composer`安装phpunit 2 | 3 | composer install 4 | 5 | 待安装完成后, 进入`vendor/bin`目录, 执行以下命令 6 | 7 | phpunit ../../tests/MainTest.php 8 | -------------------------------------------------------------------------------- /tests/project/app/test/controllers/Main.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace app\test\controllers; 7 | 8 | use Cross\Exception\CoreException; 9 | use Cross\MVC\Controller; 10 | 11 | class Main extends Controller 12 | { 13 | /** 14 | * 取app配置文件 15 | */ 16 | function getAppConfig() 17 | { 18 | $config = $this->config->get('router'); 19 | return json_encode($config); 20 | } 21 | 22 | /** 23 | * 设置app配置文件项 24 | */ 25 | function setAppConfig() 26 | { 27 | $a = $this->params['a']; 28 | $this->config->set('a', $a); 29 | 30 | return json_encode(array('a' => $this->config->get('a'))); 31 | } 32 | 33 | /** 34 | * 测试注释配置 35 | * 36 | * @cp_params a=1, b=2, c=3 37 | */ 38 | function annotate() 39 | { 40 | return $this->params; 41 | } 42 | 43 | /** 44 | * 生成连接 45 | * 46 | * @return string 47 | * @throws CoreException 48 | */ 49 | function makeLink() 50 | { 51 | $params = $this->params; 52 | $type = $this->params['type']; 53 | $dot = $this->params['dot']; 54 | $ext = $this->params['ext']; 55 | $index = $this->params['index']; 56 | 57 | $this->view->cleanLinkCache(); 58 | $this->config->set('url', array( 59 | 'ext' => $ext, 60 | 'dot' => $dot, 61 | 'type' => $type, 62 | 'index' => $index, 63 | )); 64 | 65 | $this->view->setLinkBase(''); 66 | return $this->view->url('Main:getUrlSecurityParams', array( 67 | 'p1' => $params['p1'], 68 | 'p2' => $params['p2'], 69 | 'p3' => $params['p3'] 70 | )); 71 | } 72 | 73 | /** 74 | * 生成加密链接 75 | * 76 | * @return string 77 | * @throws CoreException 78 | */ 79 | function makeEncryptLink() 80 | { 81 | $params = $this->params; 82 | $type = $this->params['type']; 83 | $dot = $this->params['dot']; 84 | $ext = $this->params['ext']; 85 | $index = $this->params['index']; 86 | 87 | $this->view->cleanLinkCache(); 88 | $this->config->set('url', array( 89 | 'ext' => $ext, 90 | 'dot' => $dot, 91 | 'type' => $type, 92 | 'index' => $index, 93 | )); 94 | 95 | $this->view->setLinkBase(''); 96 | return $this->view->sUrl('Main:getUrlSecurityParams', [ 97 | 'p1' => $params['p1'], 98 | 'p2' => $params['p2'], 99 | 'p3' => $params['p3'] 100 | ]); 101 | } 102 | 103 | /** 104 | * url加密 参数解密 105 | * 106 | * @cp_params p1, p2, p3 107 | * @throws CoreException 108 | */ 109 | function makeEncryptLinkAndDecryptParams() 110 | { 111 | $params = $this->params; 112 | $linkType = $this->params['link_type']; 113 | $dot = $this->params['dot']; 114 | $ext = $this->params['ext']; 115 | 116 | $this->view->cleanLinkCache(); 117 | $this->config->set('url', array( 118 | 'rewrite' => false, 119 | 'ext' => $ext, 120 | 'dot' => $dot, 121 | 'type' => $linkType 122 | )); 123 | 124 | $controller = 'Main'; 125 | $action = 'getUrlSecurityParams'; 126 | 127 | $this->view->setLinkBase(''); 128 | $url = $this->view->sUrl(sprintf('%s:%s', $controller, $action), array( 129 | 'p1' => $params['p1'], 130 | 'p2' => $params['p2'], 131 | 'p3' => $params['p3'] 132 | )); 133 | 134 | $customRouterParams[] = $controller; 135 | $customRouterParams[] = $action; 136 | 137 | if ($linkType > 2) { 138 | list(, $paramsString) = explode('?', $url); 139 | } else { 140 | $urlArray = explode($dot, $url); 141 | $paramsString = end($urlArray); 142 | } 143 | 144 | return $this->sParams(true, $paramsString); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /tests/project/app/test/init.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'auth' => 'COOKIE', 5 | 'default_tpl_dir' => 'default', 6 | 'display' => 'JSON' 7 | ], 8 | 'encrypt' => [ 9 | 'uri' => 'crossphp', 10 | 'auth' => '' 11 | ], 12 | 'url' => [ 13 | '*' => 'Main:index', 14 | 'type' => 1, 15 | 'rewrite' => false, 16 | 'dot' => '/', 17 | 'ext' => '', 18 | ], 19 | 'router' => [ 20 | 'hi' => 'main:index', 21 | ] 22 | ]; 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/project/app/test/templates/default/default.layer.php: -------------------------------------------------------------------------------- 1 | = empty($content) ? '' : $content; 2 | -------------------------------------------------------------------------------- /tests/project/app/test/views/MainView.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | namespace app\test\views; 6 | 7 | use Cross\MVC\View; 8 | 9 | class MainView extends View 10 | { 11 | function __call($func, $data = null) { 12 | 13 | } 14 | } 15 | --------------------------------------------------------------------------------