├── LICENSE ├── README.md └── src ├── Boot ├── ArrayObject.php ├── Configuration.php ├── Controller.php ├── Kernel.php ├── Logger.php └── TKObject.php ├── Lib ├── Exception │ ├── ControllerInterruptException.php │ ├── DataChangedException.php │ ├── DataExistsException.php │ ├── ErrorReportException.php │ ├── ErrorReportHandler.php │ ├── NoSuchFileOrDirException.php │ ├── PermissionDeniedException.php │ ├── RuntimeException.php │ ├── SQLQueryErrorException.php │ ├── TryNoException.php │ ├── TryYesException.php │ ├── UndefinedConstantException.php │ ├── UndefinedIndexException.php │ └── UndefinedVariableException.php ├── Filesystem │ ├── Dir.php │ ├── File.php │ └── Path.php ├── Flag │ ├── Denied.php │ ├── Flag.php │ ├── No.php │ ├── Skip.php │ ├── Unfound.php │ └── Yes.php ├── IO │ ├── Cookie.php │ ├── Get.php │ ├── HttpHeader.php │ ├── Post.php │ ├── Request.php │ ├── Response.php │ └── Route.php ├── Model │ ├── Auth │ │ ├── RequestCsrf.php │ │ ├── Session.php │ │ └── User.php │ ├── Database │ │ ├── ActiveRecord.php │ │ ├── Column.php │ │ ├── DB.php │ │ ├── Expression.php │ │ ├── FunctionExpression.php │ │ ├── LogicalExpression.php │ │ ├── QueryBuild.php │ │ ├── QueryExpression.php │ │ └── TableModel.php │ └── Permission │ │ ├── Permission.php │ │ ├── Role.php │ │ └── Root.php └── View │ ├── Compile.php │ ├── Control.php │ ├── Element.php │ ├── View.php │ └── ViewData.php └── boot.php /LICENSE: -------------------------------------------------------------------------------- 1 | The ToKnot Framework is free software. It is released under the terms of the following BSD License. 2 | 3 | Copyright © 2010-2015 by Szopen Xiao (xiao@toknot.com) All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | Neither the name of Szopen Xiao nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### About 2 | ToKnot is a php framework 3 | 4 | [http://toknot.com](http://toknot.com) 5 | 6 | [![Latest Stable Version](https://poser.pugx.org/toknot/toknot/v/stable)](https://packagist.org/packages/toknot/toknot) 7 | [![Latest Unstable Version](https://poser.pugx.org/toknot/toknot/v/unstable)](https://packagist.org/packages/toknot/toknot) 8 | [![License](https://poser.pugx.org/toknot/toknot/license)](https://packagist.org/packages/toknot/toknot) 9 | ### License 10 | The PHP framework is under New BSD License (http://toknot.com/LICENSE.txt) 11 | 12 | The demos is under GNU GPL version 3 or later 13 | 14 | ### Usage and Configure 15 | On command line exec: `php vendor/toknot/initapp.php` App Init Guide build your app 16 | 17 | On command line exec: `php app/tool/index.php` show tool app help message 18 | 19 | 在命令行中执行:`php vendor/toknot/initapp.php` 运行应用初始化向导,向导程序会创建应用基本目录及文件 20 | 21 | 在命令行中执行:`php app/tool/index.php` 显示tool应用帮助信息 22 | 23 | * [Controller And Model usage](https://github.com/chopins/toknot/blob/master/vendor/toknot/doc/Controller-Model-Usage.md) 24 | * [Main config](https://github.com/chopins/toknot/blob/master/vendor/toknot/doc/main-config-usage.md) 25 | * [router config](https://github.com/chopins/toknot/blob/master/vendor/toknot/doc/route-config.md) 26 | * [table config](https://github.com/chopins/toknot/blob/master/vendor/toknot/doc/table-config.md) 27 | * [view document](https://github.com/chopins/toknot/blob/master/vendor/toknot/doc/view.md) 28 | * [相关库 document](https://github.com/chopins/toknot/blob/master/vendor/toknot/doc/tool.md) 29 | 30 | ### Server Config 31 | 将所有请求都定向到index.php入口文件,以下是nginx与apache服务器配置方法 32 | * nginx: 33 | ```conf 34 | location / { 35 | root $dir/index.php; 36 | } 37 | ``` 38 | 39 | * apache: 40 | ```conf 41 | 42 | RewriteBase / 43 | RewriteRule .* index.php 44 | RewriteEngine On 45 | 46 | ``` 47 | * PHP CLI Web Server: 48 | ``` 49 | php -S 127.0.0.1:8000 index.php 50 | ``` 51 | -------------------------------------------------------------------------------- /src/Boot/ArrayObject.php: -------------------------------------------------------------------------------- 1 | offsetExists($key)) { 18 | return $this->offsetGet($key); 19 | } 20 | return null; 21 | } 22 | 23 | public function __get($key) { 24 | return $this->value($key); 25 | } 26 | 27 | public function merge($param) { 28 | return Kernel::merge($this, $param); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/Boot/Configuration.php: -------------------------------------------------------------------------------- 1 | offsetExists($key)) { 19 | return null; 20 | } 21 | $value = parent::offsetGet($key); 22 | if (is_array($value)) { 23 | return new static($value); 24 | } 25 | return $value; 26 | } 27 | 28 | public function offsetExists($key) { 29 | $key = Kernel::classToLower($key, Kernel::UDL); 30 | return parent::offsetExists($key); 31 | } 32 | 33 | public function offsetSet($key, $value = null) { 34 | if (Kernel::getInstance()) { 35 | Kernel::getInstance()->runtimeException('can not set config at runtime'); 36 | } 37 | } 38 | 39 | public function __isset($key) { 40 | $key = Kernel::classToLower($key, Kernel::UDL); 41 | return $this->offsetExists($key); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/Boot/Controller.php: -------------------------------------------------------------------------------- 1 | kernel = Kernel::instance(); 52 | $this->config = $this->kernel->config(); 53 | $this->response = Response::instance(); 54 | $this->route = Route::instance(); 55 | $this->setViewCachePath(); 56 | } 57 | 58 | public function kernel() { 59 | return $this->kernel; 60 | } 61 | 62 | public function tk() { 63 | return $this->kernel; 64 | } 65 | 66 | public function route() { 67 | if (!$this->route) { 68 | $this->route = Route::instance(); 69 | } 70 | return $this->route; 71 | } 72 | 73 | public static function __callStatic($name, $params = []) { 74 | if (!method_exists(get_called_class(), $name)) { 75 | return call_user_func_array([Kernel::class, $name], $params); 76 | } 77 | parent::__callStatic($name, $params); 78 | } 79 | 80 | public function move($controller, $params = [], $domain = '', $scheme = '') { 81 | $this->response->move($controller, $params, $domain, $scheme); 82 | } 83 | 84 | public function redict($controller, $params = [], $domain = '', $scheme = '') { 85 | $this->response->redict($controller, $params, $domain, $scheme); 86 | } 87 | 88 | public function moveUrl($url) { 89 | $this->response->moveUrl($url); 90 | } 91 | 92 | public function redictUrl($url) { 93 | $this->response->redictUrl($url); 94 | } 95 | 96 | public function returnFile($file, $type = '') { 97 | $resp = $this->response; 98 | $resp->responseFilePath = $file; 99 | $resp->responseFileType = $type; 100 | $this->abort(); 101 | } 102 | 103 | public function returnJson(array $data = []) { 104 | $this->response->responseType = Response::RESP_TYPE_JSON; 105 | $this->response->data($data); 106 | $this->abort(); 107 | } 108 | 109 | public function returnXML(array $data = []) { 110 | $this->response->responseType = Response::RESP_TYPE_XML; 111 | $this->response->data($data); 112 | $this->abort(); 113 | } 114 | 115 | public static function pushException($exception) { 116 | $response = Response::instance(); 117 | $response->responseException($exception); 118 | } 119 | 120 | /** 121 | * 122 | * @return \Toknot\Lib\IO\Response 123 | */ 124 | public static function response() { 125 | $resp = Response::instance(); 126 | $config = Kernel::instance()->config(); 127 | if (isset($config->index) && isset($config->index->action)) { 128 | $resp->setIndexAction($config->index->action); 129 | } 130 | if (isset($config->index) && isset($config->index->controller)) { 131 | $resp->setIndexController($config->index->controller); 132 | } 133 | $resp->launch(); 134 | try { 135 | $resp->thenMiddleware()->thenBefore()->thenDoAction()->thenAfter(); 136 | } catch (ControllerInterruptException $e) { 137 | 138 | } 139 | $resp->thenEnd(); 140 | return $resp; 141 | } 142 | 143 | /** 144 | * 使用特定异常进行手动中断操作,只能在控制器内使用 145 | * 146 | * @throws ControllerInterruptException 147 | */ 148 | protected function abort() { 149 | throw new ControllerInterruptException; 150 | } 151 | 152 | public function setViewCachePath() { 153 | $suffix = isset($this->config->viewPath) ? $this->config->viewPath : 'tpl'; 154 | $dir = $this->kernel->runtime . DIRECTORY_SEPARATOR . $suffix; 155 | if (!is_dir($dir)) { 156 | mkdir($dir, 0777); 157 | } 158 | self::$viewCacheDir = $dir; 159 | } 160 | 161 | public static function documentRoot() { 162 | return Request::getDocumentRoot(); 163 | } 164 | 165 | public function getViewCachePath() { 166 | return self::$viewCacheDir; 167 | } 168 | 169 | public function getRouteId() { 170 | return Route::getRoute(); 171 | } 172 | 173 | public function arg($key) { 174 | if (PHP_SAPI === Kernel::CLI) { 175 | return Request::cli()->value($key); 176 | } else { 177 | $value = $this->request(null, $key); 178 | if (!$value && Request::method() !== Request::METHOD_LIST[0]) { 179 | return $this->request('get', $key); 180 | } 181 | return $value; 182 | } 183 | } 184 | 185 | /** 186 | * 187 | * @param string $type 188 | * @param string $key 189 | * @return \Toknot\Lib\IO\Request 190 | */ 191 | public function request($type = '', $key = '') { 192 | if (!$type) { 193 | $ins = Request::input(); 194 | } elseif ($type === 'server') { 195 | return Request::server($key); 196 | } else { 197 | $ins = Request::$type(); 198 | } 199 | if ($key) { 200 | return $ins->value($key); 201 | } 202 | return $ins; 203 | } 204 | 205 | public function cookie($key = '') { 206 | if ($key === '') { 207 | return Request::cookie(); 208 | } 209 | return Request::cookie()->value($key); 210 | } 211 | 212 | public static function exitCode($code = null) { 213 | if ($code !== null) { 214 | exit($code); 215 | } 216 | $code = Response::instance()->getResponseCode(); 217 | if (PHP_SAPI == Kernel::CLI) { 218 | exit($code); 219 | } 220 | } 221 | 222 | public function setView($viewId) { 223 | $this->viewId = $viewId; 224 | list($view, $this->viewAction) = explode(Kernel::AT, $this->viewId); 225 | $this->viewAction .= Kernel::ACTION; 226 | $view = Kernel::pathToClass(ucwords($view, Kernel::URL_SEP)); 227 | $this->view = Kernel::instance()->appViewNs() . Kernel::NS . Kernel::toUpper($view); 228 | } 229 | 230 | public function getViewId() { 231 | return $this->viewId; 232 | } 233 | 234 | public function getView() { 235 | return $this->view; 236 | } 237 | 238 | public function getViewAction() { 239 | return $this->viewAction; 240 | } 241 | 242 | final public function setViewParam($var, $value) { 243 | Response::instance()->data($var, $value); 244 | } 245 | 246 | } 247 | -------------------------------------------------------------------------------- /src/Boot/Logger.php: -------------------------------------------------------------------------------- 1 | saveFile = Kernel::instance()->logDir; 29 | } 30 | 31 | public function emergency($message, array $context = array()) { 32 | $this->log(self::EMERGENCY, $message, $context); 33 | } 34 | 35 | public function alert($message, array $context = array()) { 36 | $this->log(self::ALERT, $message, $context); 37 | } 38 | 39 | public function critical($message, array $context = array()) { 40 | $this->log(self::CRITICAL, $message, $context); 41 | } 42 | 43 | public function error($message, array $context = array()) { 44 | $this->log(self::ERROR, $message, $context); 45 | } 46 | 47 | public function warning($message, array $context = array()) { 48 | $this->log(self::WARNING, $message, $context); 49 | } 50 | 51 | public function notice($message, array $context = array()) { 52 | $this->log(self::NOTICE, $message, $context); 53 | } 54 | 55 | public function info($message, array $context = array()) { 56 | $this->log(self::INFO, $message, $context); 57 | } 58 | 59 | public function debug($message, array $context = array()) { 60 | $this->log(self::DEBUG, $message, $context); 61 | } 62 | 63 | public function log($level, $message, array $context = array()) { 64 | $date = date('Y-m-d H:i:s e'); 65 | $messageData = [$level]; 66 | $messageData[] = PHP_SAPI; 67 | $messageData[] = $date; 68 | $messageData[] = gethostname(); 69 | $messageData[] = Kernel::localIp(); 70 | $messageData[] = Kernel::requestIp(); 71 | $messageData[] = $message; 72 | $messageData[] = str_replace(PHP_EOL, Kernel::getEOLToken(), var_export($context, true)); 73 | $logstr = join(' - ', $messageData); 74 | $file = $this->saveFile . DIRECTORY_SEPARATOR . $level . Kernel::DOT . date('Ymd'); 75 | $this->save($file, $logstr); 76 | } 77 | 78 | public function save($file, $string) { 79 | if (empty(self::$logFileHandle[$file])) { 80 | self::$logFileHandle[$file] = fopen($file, 'ab'); 81 | } 82 | flock(self::$logFileHandle[$file], LOCK_EX); 83 | fwrite(self::$logFileHandle[$file], $string); 84 | flock(self::$logFileHandle[$file], LOCK_UN); 85 | } 86 | 87 | public function __destruct() { 88 | foreach (self::$logFileHandle as $fp) { 89 | @fclose($fp); 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/Boot/TKObject.php: -------------------------------------------------------------------------------- 1 | $name(); 46 | case 1: 47 | return $this->$name($params[0]); 48 | case 2: 49 | return $this->$name($params[0], $params[1]); 50 | case 3: 51 | return $this->$name($params[0], $params[1], $params[2]); 52 | case 4: 53 | return $this->$name($params[0], $params[1], $params[2], $params[3]); 54 | case 5: 55 | return $this->$name($params[0], $params[1], $params[2], $params[4], $params[5]); 56 | default: 57 | $ret = null; 58 | $argStr = self::argStr(array_keys($params)); 59 | eval("$ret = \$this->{$name}($argStr)"); 60 | return $ret; 61 | } 62 | }; 63 | } 64 | $class = spl_object_hash($this); 65 | if(empty(self::$bindInsInoke[$class])) { 66 | self::$bindInsInoke[$class] = Closure::bind(self::$bindInokeClosure, $this, get_called_class()); 67 | } 68 | $c = self::$bindInsInoke[$class]; 69 | return $c($name, $params); 70 | } 71 | 72 | public function invokeMethod($name, ...$params) { 73 | return $this->invoke($name, $params); 74 | } 75 | 76 | public static function invokeStaticMethod($name, ...$params) { 77 | return self::invokeStatic($name, $params); 78 | } 79 | 80 | public static function invokeStatic($name, array $params = []) { 81 | if (self::$bindStaticClosure === null) { 82 | self::$bindStaticClosure = function($name, $params) { 83 | $argc = count($params); 84 | switch ($argc) { 85 | case 0: 86 | return self::$name(); 87 | case 1: 88 | return self::$name($params[0]); 89 | case 2: 90 | return self::$name($params[0], $params[1]); 91 | case 3: 92 | return self::$name($params[0], $params[1], $params[2]); 93 | case 4: 94 | return self::$name($params[0], $params[1], $params[2], $params[3]); 95 | case 5: 96 | return self::$name($params[0], $params[1], $params[2], $params[4], $params[5]); 97 | default: 98 | $ret = null; 99 | $argStr = self::argStr($argc); 100 | eval("$ret = self::{$name}($argStr)"); 101 | return $ret; 102 | } 103 | }; 104 | } 105 | $class = get_called_class(); 106 | if(empty(self::$bindInsStatic[$class])) { 107 | self::$bindInsStatic[$class] = Closure::bind(self::$bindStaticClosure, null, $class); 108 | } 109 | $c = self::$bindInsStatic; 110 | return $c($name, $params); 111 | } 112 | 113 | public function __call($name, $params = []) { 114 | throw new \BadMethodCallException('Call to undefined method ' . get_called_class() . "::$name()"); 115 | } 116 | 117 | public static function __callStatic($name, $param = []) { 118 | throw new \BadMethodCallException('Call to undefined method ' . get_called_class() . "::$name()"); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/Lib/Exception/ControllerInterruptException.php: -------------------------------------------------------------------------------- 1 | file = $file; 19 | $this->line = $line; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Lib/Exception/ErrorReportHandler.php: -------------------------------------------------------------------------------- 1 | code = $errorArg[0]; 31 | $this->message = $errorArg[1]; 32 | $this->file = $errorArg[2]; 33 | $this->line = $errorArg[3]; 34 | } 35 | 36 | public function throwException() { 37 | if ($this->levelLogger()) { 38 | return true; 39 | } 40 | if ($this->checkMessage('No such file or directory')) { 41 | $this->throwExceptionInstance(NoSuchFileOrDirException::class); 42 | } elseif ($this->checkMessage('Permission denied')) { 43 | $this->throwExceptionInstance(PermissionDeniedException::class); 44 | } elseif ($this->checkMessage('Undefined index')) { 45 | return $this->whetherHideViewNotice(UndefinedIndexException::class); 46 | } elseif ($this->checkMessage('Use of undefined constant')) { 47 | return $this->whetherHideConstanceNotice(); 48 | } elseif ($this->checkMessage('Undefined variable')) { 49 | return $this->whetherHideViewNotice(UndefinedVariableException::class); 50 | } elseif ($this->checkMessage('SQLSTATE[')) { 51 | return $this->throwExceptionInstance(SQLQueryErrorException::class); 52 | } elseif ($this->checkMessage('RuntimeException')) { 53 | throw new RuntimeException($this->message, $this->code); 54 | } 55 | 56 | $this->throwExceptionInstance(ErrorReportException::class); 57 | } 58 | 59 | protected function throwExceptionInstance($exceptionClass) { 60 | throw new $exceptionClass($this->message, $this->code, $this->file, $this->line); 61 | } 62 | 63 | public function checkMessage($message) { 64 | return strpos($this->message, $message) !== false; 65 | } 66 | 67 | public function releaseStatus() { 68 | return Kernel::getRelaseStatus(); 69 | } 70 | 71 | protected function whetherHideViewNotice($exceptionClass) { 72 | $isView = strpos($this->file, Controller::$viewCacheDir) === 0; 73 | if (Controller::$hideViewError && $isView) { 74 | return true; 75 | } elseif ($isView) { 76 | return false; 77 | } 78 | $this->throwExceptionInstance($exceptionClass); 79 | } 80 | 81 | public function whetherHideConstanceNotice() { 82 | if (Kernel::$enableTokenString) { 83 | return true; 84 | } 85 | $this->throwExceptionInstance(UndefinedConstantException::class); 86 | } 87 | 88 | public function checkAllSaveLogger() { 89 | return in_array($this->releaseStatus(), [Kernel::R_RELEASE, Kernel::R_RC]); 90 | } 91 | 92 | public function checkExceptErrorSaveLogger() { 93 | return in_array($this->releaseStatus(), [Kernel::R_ALPHA, Kernel::R_BETA]) && 94 | !in_array($this->code, [E_ERROR, E_COMPILE_ERROR, E_CORE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR]); 95 | } 96 | 97 | public function levelLogger($e = '') { 98 | if ($this->checkAllSaveLogger()) { 99 | $this->saveLogger(); 100 | return true; 101 | } elseif ($this->checkExceptErrorSaveLogger()) { 102 | $this->saveLogger(); 103 | return true; 104 | } elseif($e instanceof \Exception) { 105 | return false; 106 | } 107 | return false; 108 | } 109 | 110 | public function saveLogger() { 111 | if (in_array($this->code, [E_ERROR, E_COMPILE_ERROR, E_CORE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR, E_PARSE])) { 112 | $func = 'error'; 113 | } elseif (in_array($this->code, [E_WARNING, E_USER_WARNING, E_CORE_WARNING, E_COMPILE_WARNING])) { 114 | $func = 'warning'; 115 | } else { 116 | $func = 'notice'; 117 | } 118 | Kernel::instance()->logger->$func($this->message, [$this->file, $this->line]); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/Lib/Exception/NoSuchFileOrDirException.php: -------------------------------------------------------------------------------- 1 | line = $debug[4]['line']; 18 | $this->file = $debug[4]['file']; 19 | parent::__construct($message, $code, $previous); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/Lib/Exception/SQLQueryErrorException.php: -------------------------------------------------------------------------------- 1 | getTrace(); 20 | foreach($trace as $t) { 21 | if($t['function'] === 'throwException' && $t['class'] === ErrorReportHandler::class) { 22 | $this->file = $t['file']; 23 | $this->line = $t['line']; 24 | } 25 | } 26 | parent::__construct($message, $code); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/Lib/Exception/TryNoException.php: -------------------------------------------------------------------------------- 1 | path = $path; 23 | $this->realpath = realpath($path); 24 | } 25 | 26 | public function getPath() { 27 | return $this->path; 28 | } 29 | 30 | public function name() { 31 | return basename($this->realpath); 32 | } 33 | 34 | public function getRealPath() { 35 | return $this->realpath; 36 | } 37 | 38 | public function rm($r = true) { 39 | $this->walk('unlink', $r ? 'rmdir' : null); 40 | return rmdir($this->realpath); 41 | } 42 | 43 | public function perms() { 44 | return fileperms($this->realpath); 45 | } 46 | 47 | public function glob($pattern, $flags) { 48 | return glob($this->realpath . $pattern, $flags); 49 | } 50 | 51 | public function create($mode, $r = true) { 52 | return mkdir($this->realpath, $mode, $r); 53 | } 54 | 55 | public function walk(callable $fileCallable, $dirCallable = false) { 56 | $d = \dir($this->path); 57 | while (false !== ($name = $d->read())) { 58 | if ($name == Kernel::DOT || $name == '..') { 59 | continue; 60 | } 61 | 62 | $realpath = $this->path . Kernel::PATH_SEP . $name; 63 | if ($dirCallable && is_dir($realpath)) { 64 | $n = new static($realpath); 65 | $n->walk($fileCallable, $dirCallable); 66 | is_callable($dirCallable) && $dirCallable($realpath, $name); 67 | } elseif (is_file($realpath)) { 68 | $fileCallable($realpath, $name); 69 | } 70 | } 71 | return true; 72 | } 73 | 74 | public function move($newPath) { 75 | if (rename($this->path, $newPath) === true) { 76 | return true; 77 | } else { 78 | return false; 79 | } 80 | } 81 | 82 | public function copy($newPath, $override = true) { 83 | $newRealPath = realpath($newPath); 84 | if (!$override && ($exist = file_exists($newRealPath))) { 85 | Kernel::runtimeException("$newRealPath is file that not directory", E_USER_WARNING); 86 | } 87 | if ($exist) { 88 | $tmpPath = $newRealPath . File::randName(); 89 | if (rename($newRealPath, $tmpPath) === false) { 90 | Kernel::runtimeException("move $newRealPath error", E_USER_WARNING); 91 | } 92 | } 93 | $perms = fileperms($this->realpath); 94 | if (mkdir($newRealPath, $perms, true) === false) { 95 | return false; 96 | } 97 | try { 98 | $pathLen = strlen($this->path); 99 | $this->walk(function($realpath) use($pathLen, $newRealPath) { 100 | $dest = $newRealPath . substr($realpath, $pathLen); 101 | copy($realpath, $dest); 102 | }, function($realpath)use($pathLen, $newRealPath) { 103 | $dest = $newRealPath . substr($realpath, $pathLen); 104 | mkdir($dest); 105 | }); 106 | } catch (Exception $e) { 107 | $this->rollback($newRealPath, $tmpPath, $e); 108 | } catch (Error $e) { 109 | $this->rollback($newRealPath, $tmpPath, $e); 110 | } 111 | $this->commit($exist, $tmpPath); 112 | return true; 113 | } 114 | 115 | protected function rollback($newRealPath, $tmpPath, $e) { 116 | $newDir = new Dir($newRealPath); 117 | $newDir->rm(); 118 | rename($tmpPath, $newRealPath); 119 | Kernel::runtimeException($e, E_USER_WARNING); 120 | } 121 | 122 | protected function commit($exist, $tmpPath) { 123 | if ($exist && is_dir($tmpPath)) { 124 | $tmpDir = new Dir($tmpPath); 125 | $tmpDir->rm(); 126 | } elseif ($exist) { 127 | unlink($tmpPath); 128 | } 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/Lib/Filesystem/File.php: -------------------------------------------------------------------------------- 1 | realpath = $this->getRealPath(); 22 | $this->pathInfo = pathinfo($this->realpath); 23 | } 24 | 25 | public function __destruct() { 26 | $this->realpath = null; 27 | $this->pathInfo = null; 28 | } 29 | 30 | public function getDir() { 31 | return $this->pathInfo['dirname']; 32 | } 33 | 34 | public function basename() { 35 | return $this->pathInfo['basename']; 36 | } 37 | 38 | public function ext() { 39 | return $this->pathInfo['extension']; 40 | } 41 | 42 | public function name() { 43 | return $this->pathInfo['filename']; 44 | } 45 | 46 | public function pathInfo() { 47 | return $this->pathInfo; 48 | } 49 | 50 | public function save($data) { 51 | return file_put_contents($this->realpath, $data); 52 | } 53 | 54 | public function getADate($format = 'Y-m-d H:i:s') { 55 | return date($format, $this->getATime()); 56 | } 57 | 58 | public function getCDate($format = 'Y-m-d H:i:s') { 59 | return date($format, $this->getCTime()); 60 | } 61 | 62 | public function getMDate($format = 'Y-m-d H:i:s') { 63 | return date($format, $this->getMTime()); 64 | } 65 | 66 | public function humanSize($precision = 2, $si = false) { 67 | $size = $this->size(); 68 | $base = $si ? 1000 : 1024; 69 | $unit = $si ? 'B' : 'iB'; 70 | $arr = [5 => 'P', 4 => 'T', 3 => 'G', 2 => 'M', 1 => 'K', 0 => '']; 71 | foreach ($arr as $i => $u) { 72 | $suffix = " {$u}{$unit}"; 73 | if ($i === 1) { 74 | return round($size / $base, $precision) . $suffix; 75 | } elseif ($size > pow($base, $i)) { 76 | return round($size / pow($base, $i), $precision) . $suffix; 77 | } else { 78 | return $size . $suffix; 79 | } 80 | } 81 | } 82 | 83 | public function verboseSize($si = false, $returnArr = false) { 84 | $size = $this->size(); 85 | $base = $si ? 1000 : 1024; 86 | $unit = $si ? 'B' : 'iB'; 87 | $arr = [5 => 'P', 4 => 'T', 3 => 'G', 2 => 'M', 1 => 'K', 0 => '']; 88 | $res = []; 89 | foreach ($arr as $i => $u) { 90 | if ($i === 1) { 91 | $res[] = floor($size / $base, 2); 92 | $size = $size % $base; 93 | } elseif ($size > pow($base, $i)) { 94 | $res[] = floor($size / pow($base, $i)); 95 | $size = $size % pow($base, $i); 96 | } else { 97 | $res[] = $size; 98 | } 99 | $res[] = "{$u}{$unit}"; 100 | } 101 | return $returnArr ? $res : join(' ', $res); 102 | } 103 | 104 | public function append($data) { 105 | return file_put_contents($this->realpath, $data, FILE_APPEND); 106 | } 107 | 108 | public function get() { 109 | return file_get_contents($this->realpath); 110 | } 111 | 112 | public function del() { 113 | if (unlink($this->realpath) === true) { 114 | return true; 115 | } else { 116 | return false; 117 | } 118 | } 119 | 120 | public function getJson() { 121 | return json_decode($this->get(), true); 122 | } 123 | 124 | public function copy($newFile) { 125 | return copy($this->realpath, $newFile); 126 | } 127 | 128 | public function move($newFile) { 129 | if (rename($this->realpath, $newFile) === true) { 130 | $this->__construct($newFile); 131 | return true; 132 | } else { 133 | return false; 134 | } 135 | } 136 | 137 | public function saveJson($array) { 138 | return $this->save(json_encode($array)); 139 | } 140 | 141 | public static function randPathName($path, $prefix = '', $len = 6) { 142 | return $path . Kernel::PATH_SEP . self::randName($prefix, $len); 143 | } 144 | 145 | public static function randName($prefix = '', $len = 6) { 146 | $str = str_shuffle('QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890'); 147 | return $prefix . substr($str, 0, $len); 148 | } 149 | 150 | public static function timeName($path, $prefix) { 151 | return $path . Kernel::PATH_SEP . $prefix . '.' . time(); 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /src/Lib/Filesystem/Path.php: -------------------------------------------------------------------------------- 1 | protocol = Request::protocol(); 21 | } 22 | 23 | public static function __callStatic($name, $arguments = []) { 24 | if (self::$instance === null) { 25 | self::$instance = new static; 26 | } 27 | if (method_exists(get_called_class(), $name)) { 28 | return self::$instance->invoke($name, $arguments); 29 | } 30 | parent::__callStatic($name, $arguments); 31 | } 32 | 33 | protected function h404() { 34 | header("{$this->protocol} 404 Not Found", true, 404); 35 | } 36 | 37 | protected function h301($url) { 38 | header("Location: $url", true, 301); 39 | exit; 40 | } 41 | 42 | protected function h302($url) { 43 | header("Location: $url", true, 302); 44 | exit; 45 | } 46 | 47 | protected function h500() { 48 | header("{$this->protocol} 500 Internal Server Error", true, 500); 49 | } 50 | 51 | protected function h503() { 52 | header("{$this->protocol} 503 Service Unavailable", true, 503); 53 | } 54 | 55 | protected function attachment($filename) { 56 | header('Content-Description: File Transfer'); 57 | header('Expires: 0'); 58 | header('Cache-Control: must-revalidate'); 59 | header('Pragma: public'); 60 | header("Content-Disposition: attachment; filename=\"$filename\""); 61 | } 62 | 63 | protected function contentLength($size) { 64 | header('Content-Length: ' . $size); 65 | } 66 | 67 | protected function contentType($type) { 68 | header("Content-type: $type"); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/Lib/IO/Post.php: -------------------------------------------------------------------------------- 1 | self::FLITER_REQUIRE, 'option' => self::FLITER_OPTION, 49 | 'value' => self::FLITER_UNEMPTY, 'isemail' => self::FLITER_EMAIL, 'isurl' => self::FILTER_URL, 50 | 'word' => self::FILTER_WORD, 'number' => self::FILTER_NUMBER, 51 | ]; 52 | const XML = 'xml'; 53 | const JSON = 'json'; 54 | const XHR = 'xmlhttprequest'; 55 | 56 | protected function __construct() { 57 | $this->iteratorArray = filter_input_array($this->type); 58 | if (!$this->iteratorArray) { 59 | $this->iteratorArray = []; 60 | } 61 | $this->count = count($this->iteratorArray); 62 | } 63 | 64 | public function __call($name, $args = []) { 65 | return self::invokeStatic($name, $args); 66 | } 67 | 68 | public static function requestHash() { 69 | list($iparea) = explode('.', strrev(self::ip()), 2); 70 | $user = self::agent() . self::userBrowserId() . $iparea . self::mircotime(); 71 | $rand = uniqid(sha1($user), true) . mt_rand(1000000, 10000000); 72 | return hash('sha256', $user . $rand); 73 | } 74 | 75 | public static function uri() { 76 | if (PHP_SAPI === Kernel::CLI) { 77 | return self::argv(); 78 | } 79 | return self::server('REQUEST_URI'); 80 | } 81 | 82 | public static function url() { 83 | $uri = ltrim(self::uri(), Kernel::URL_SEP); 84 | $host = self::host(); 85 | $schema = self::schema(); 86 | return "$schema://$host/$uri"; 87 | } 88 | 89 | public static function host() { 90 | return self::server('HTTP_HOST'); 91 | } 92 | 93 | public static function getDocumentRoot() { 94 | return self::server('DOCUMENT_ROOT'); 95 | } 96 | 97 | public static function domian() { 98 | return self::server('SERVER_NAME'); 99 | } 100 | 101 | public static function method() { 102 | if (self::$requestMethod) { 103 | return self::$requestMethod; 104 | } 105 | self::$requestMethod = strtoupper(self::server('REQUEST_METHOD')); 106 | if (PHP_SAPI === Kernel::CLI) { 107 | self::$requestMethod = 'CLI'; 108 | } 109 | return self::$requestMethod; 110 | } 111 | 112 | public static function protocol() { 113 | return self::server('SERVER_PROTOCOL'); 114 | } 115 | 116 | public static function schema() { 117 | if (self::server('HTTPS')) { 118 | return 'https'; 119 | } elseif (stripos(self::protocol(), 'http') !== false) { 120 | return 'http'; 121 | } 122 | return self::protocol(); 123 | } 124 | 125 | public function port() { 126 | return self::server('SERVER_PORT'); 127 | } 128 | 129 | public static function webserver() { 130 | return self::server('SERVER_SOFTWARE'); 131 | } 132 | 133 | public static function time() { 134 | return self::server('REQUEST_TIME'); 135 | } 136 | 137 | public static function mircotime() { 138 | return self::server('REQUEST_TIME_FLOAT'); 139 | } 140 | 141 | protected static function checkWebServer($name) { 142 | return stripos(self::webserver(), $name) !== false; 143 | } 144 | 145 | public static function isApache() { 146 | return self::checkWebServer('apache'); 147 | } 148 | 149 | public static function isNginx() { 150 | return self::checkWebServer('nginx'); 151 | } 152 | 153 | public function isLighttpd() { 154 | return self::checkWebServer('lighttpd'); 155 | } 156 | 157 | public function isIIS() { 158 | return self::checkWebServer('iis'); 159 | } 160 | 161 | public static function argv($idx = -1) { 162 | if (PHP_SAPI === 'cli') { 163 | return $idx < 0 ? $GLOBALS['argv'] : (count($GLOBALS['argv']) > $idx ? $GLOBALS['argv'][$idx] : ''); 164 | } 165 | return Route::instance()->getParameter($idx); 166 | } 167 | 168 | /** 169 | * 170 | * @return \Toknot\Lib\IO\Request 171 | */ 172 | public static function input($type = '') { 173 | if ($type) { 174 | return self::any($type); 175 | } 176 | $method = self::method(); 177 | return self::any($method); 178 | } 179 | 180 | /** 181 | * 182 | * @return \Toknot\Lib\IO\Request 183 | */ 184 | public static function get() { 185 | if (self::$get === null) { 186 | self::$get = new Get(); 187 | } 188 | return self::$get; 189 | } 190 | 191 | /** 192 | * 193 | * @return \Toknot\Lib\IO\Request 194 | */ 195 | public static function post() { 196 | if (self::$post === null) { 197 | self::$post = new Post; 198 | } 199 | return self::$post; 200 | } 201 | 202 | /** 203 | * 204 | * @return \Toknot\Lib\Io\Request 205 | */ 206 | public static function cookie() { 207 | if (self::$cookie === null) { 208 | self::$cookie = new Cookie; 209 | } 210 | return self::$cookie; 211 | } 212 | 213 | public static function cli() { 214 | return new ArrayObject(self::argv()); 215 | } 216 | 217 | /** 218 | * 219 | * @param string $method 220 | * @return \Toknot\Lib\IO\Request 221 | */ 222 | public static function any($method) { 223 | if ($method == self::METHOD_LIST[0]) { 224 | return self::get(); 225 | } elseif ($method == self::METHOD_LIST[1]) { 226 | return self::post(); 227 | } elseif ($method === 'CLI') { 228 | return self::cli(); 229 | } elseif ($method === 'COOKIE') { 230 | return self::cookie(); 231 | } 232 | $ins = new static; 233 | $ins->type = constant('INPUT_' . strtoupper($method)); 234 | return $ins; 235 | } 236 | 237 | public static function server($key) { 238 | if ($key === '_') { 239 | return Kernel::globals('_SERVER')['_']; 240 | } elseif ($key == 'argv') { 241 | return self::argv(); 242 | } elseif ($key == 'argc') { 243 | return Kernel::globals('_SERVER')['argc']; 244 | } 245 | return filter_input(INPUT_SERVER, $key); 246 | } 247 | 248 | public static function isxhr() { 249 | if (self::post()->_xhr == 1 || self::get()->_xhr == 1) { 250 | return true; 251 | } elseif (strtolower(self::server('HTTP_X_REQUESTED_WITH')) === self::XHR) { 252 | return true; 253 | } 254 | return false; 255 | } 256 | 257 | public static function referer() { 258 | return self::server('HTTP_REFERER'); 259 | } 260 | 261 | public static function agent() { 262 | return self::server('HTTP_USER_AGENT'); 263 | } 264 | 265 | public static function ip() { 266 | return self::server('REMOTE_ADDR'); 267 | } 268 | 269 | public static function accept() { 270 | return self::server('HTTP_ACCEPT'); 271 | } 272 | 273 | public static function wantXML() { 274 | if (self::any(self::method())->_want_xml == 1) { 275 | return true; 276 | } elseif (strpos(self::accept(), 'text/xml') !== false) { 277 | return true; 278 | } elseif (strtolower(self::server('HTTP_X_WANT_ACCEPT')) == self::XML) { 279 | return true; 280 | } 281 | return false; 282 | } 283 | 284 | public static function wantJSON() { 285 | if (self::any(self::method())->_want_json == 1) { 286 | return true; 287 | } elseif (strpos(self::accept(), 'json') !== false) { 288 | return true; 289 | } elseif (strtolower(self::server('HTTP_X_WANT_ACCEPT')) == self::JSON) { 290 | return true; 291 | } 292 | return false; 293 | } 294 | 295 | public static function browserIdHeaderFeild() { 296 | return 'X' . Kernel::BROWSER_ID; 297 | } 298 | 299 | public static function userBrowserId() { 300 | if (($id = self::server('HTTP_' . self::browserIdHeaderFeild()))) { 301 | return $id; 302 | } elseif (($id = self::cookie()->value(Kernel::BROWSER_ID))) { 303 | return $id; 304 | } 305 | return null; 306 | } 307 | 308 | public function checkParams(array $option = []) { 309 | $result = []; 310 | foreach ($option as $field => $opt) { 311 | $result[$field] = $this->check($opt, $field); 312 | } 313 | return $result; 314 | } 315 | 316 | public function number($key) { 317 | $value = $this->value($key); 318 | if (is_numeric($value)) { 319 | return $value; 320 | } 321 | return false; 322 | } 323 | 324 | public function word($key) { 325 | $value = $this->value($key); 326 | if (preg_match('/^[a-z0-9]+$/i', $value)) { 327 | return $value; 328 | } 329 | return ''; 330 | } 331 | 332 | public function isValid($key, callable $checkVaild) { 333 | $value = $this->value($key); 334 | if ($checkVaild($value)) { 335 | return $value; 336 | } 337 | return ''; 338 | } 339 | 340 | public function isemail($key) { 341 | return $this->value($key, FILTER_VALIDATE_EMAIL, FILTER_FLAG_EMAIL_UNICODE); 342 | } 343 | 344 | public function isurl($key) { 345 | return $this->value($key, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED); 346 | } 347 | 348 | public function urlencode($key) { 349 | return $this->value($key, FILTER_SANITIZE_ENCODED); 350 | } 351 | 352 | public function magic($key) { 353 | return $this->value($key, FILTER_SANITIZE_MAGIC_QUOTES); 354 | } 355 | 356 | public function htmlencode($key) { 357 | return $this->value($key, FILTER_SANITIZE_FULL_SPECIAL_CHARS); 358 | } 359 | 360 | public function value($key, $filter = FILTER_DEFAULT) { 361 | return filter_input($this->type, $key, $filter); 362 | } 363 | 364 | public function index($index = 0) { 365 | if ($index > $this->count - 1) { 366 | Kernel::runtimeException("index out of bounds ($index) ", E_USER_WARNING); 367 | } 368 | $i = 0; 369 | foreach ($this->iteratorArray as $k => $v) { 370 | if ($i == $index) { 371 | return $k; 372 | } 373 | $i++; 374 | } 375 | } 376 | 377 | public function has($key) { 378 | return filter_has_var($this->type, $key); 379 | } 380 | 381 | public function option($key) { 382 | return true; 383 | } 384 | 385 | public function __get($name) { 386 | if (!property_exists($this, $name)) { 387 | return $this->value($name); 388 | } 389 | return null; 390 | } 391 | 392 | protected function check($opt, $field) { 393 | foreach (self::FILTER_MAP as $k => $mask) { 394 | if (!$mask & $opt) { 395 | continue; 396 | } 397 | $res = $this->$k($field); 398 | if (!$res) { 399 | return false; 400 | } 401 | } 402 | return true; 403 | } 404 | 405 | public function toArray() { 406 | return filter_input_array($this->type); 407 | } 408 | 409 | public function current() { 410 | return current($this->iteratorArray); 411 | } 412 | 413 | public function key() { 414 | return key($this->iteratorArray); 415 | } 416 | 417 | public function next() { 418 | $this->iteratorPostion++; 419 | next($this->iteratorArray); 420 | } 421 | 422 | public function rewind() { 423 | reset($this->iteratorArray); 424 | $this->iteratorPostion = 0; 425 | } 426 | 427 | public function valid() { 428 | return $this->iteratorPostion < $this->count; 429 | } 430 | 431 | public function count() { 432 | return $this->count; 433 | } 434 | 435 | } 436 | -------------------------------------------------------------------------------- /src/Lib/IO/Response.php: -------------------------------------------------------------------------------- 1 | thenRes = Kernel::onYes(); 44 | $this->route = Route::instance(); 45 | $this->config = Kernel::instance()->config(); 46 | } 47 | 48 | public function __call($name, $argv = []) { 49 | if (strpos($name, Kernel::THEN) === 0) { 50 | return $this->then(Kernel::thenName($name)); 51 | } 52 | parent::__call($name, $argv); 53 | } 54 | 55 | /** 56 | * set response code number 57 | */ 58 | public function setResponseCode($code = null) { 59 | if ($code !== null) { 60 | $this->responseCode = $code; 61 | } elseif (!$this->responseCode && PHP_SAPI !== Kernel::CLI) { 62 | $this->responseCode = http_response_code(); 63 | } 64 | } 65 | 66 | /** 67 | * set default action name of controller 68 | */ 69 | public function setIndexAction($action) { 70 | $this->route->defaultAction = $action; 71 | } 72 | 73 | /** 74 | * set default controller name 75 | */ 76 | public function setIndexController($controller) { 77 | $this->route->defaultController = $controller; 78 | } 79 | 80 | public function getResponseCode() { 81 | return $this->responseCode; 82 | } 83 | 84 | public function data($params, $value = '') { 85 | if (is_array($params)) { 86 | $this->responseData = array_merge($this->responseData, $params); 87 | } else { 88 | $this->responseData[$params] = $value; 89 | } 90 | } 91 | 92 | public function charset($charset = null) { 93 | if ($charset === null) { 94 | return $this->charset; 95 | } 96 | $this->charset = $charset; 97 | } 98 | 99 | /** 100 | * 101 | * @return \Toknot\Boot\Route 102 | */ 103 | public function getRoute() { 104 | return $this->route; 105 | } 106 | 107 | public function redict($controller, $params = [], $domain = '', $scheme = '') { 108 | $url = $this->route->generateUrl($controller, $params, $domain, $scheme); 109 | $this->redictUrl($url); 110 | } 111 | 112 | public function move($controller, $params = [], $domain = '', $scheme = '') { 113 | $url = $this->route->generateUrl($controller, $params, $domain, $scheme); 114 | $this->moveUrl($url); 115 | } 116 | 117 | public function moveUrl($url) { 118 | HttpHeader::h301($url); 119 | exit(301); 120 | } 121 | 122 | public function redictUrl($url) { 123 | HttpHeader::h302($url); 124 | exit(302); 125 | } 126 | 127 | public function getStatus() { 128 | return $this->invokeStatus; 129 | } 130 | 131 | public function return404() { 132 | HttpHeader::h404(); 133 | $this->responseUnfound(); 134 | exit(404); 135 | } 136 | 137 | public function setBrowserId($id = '') { 138 | $id = $id ? $id : Request::requestHash(); 139 | setcookie(Kernel::BROWSER_ID, $id, time() + 3600 * 24 * 365, '/'); 140 | } 141 | 142 | public function then($call) { 143 | if (Kernel::isYes($this->thenRes)) { 144 | $this->thenRes = $this->$call(); 145 | } else { 146 | $this->end(); 147 | } 148 | return $this; 149 | } 150 | 151 | public function responseFile($file, $type = '') { 152 | $filename = pathinfo($file, PATHINFO_BASENAME); 153 | HttpHeader::attachment($filename); 154 | HttpHeader::contentType($type); 155 | if (!$this->webserverSendFile) { 156 | $size = filesize($file); 157 | HttpHeader::contentLength($size); 158 | readfile($file); 159 | } elseif (Request::isApache() || Request::isLighttpd()) { 160 | header("X-Sendfile: $file"); 161 | } elseif (Request::isNginx()) { 162 | header("X-Accel-Redirect: $file"); 163 | } 164 | } 165 | 166 | public function launch() { 167 | $this->route->setup(); 168 | $controller = $this->route->getController(); 169 | if (!class_exists($controller, true)) { 170 | $this->invokeStatus = self::STATUS_NOT_FOUND_CONTROLLER; 171 | $this->return404(); 172 | } 173 | $this->accessInstance = new $controller; 174 | if (isset($this->config->charset)) { 175 | $this->charset = $this->config->charset; 176 | } 177 | if (isset($this->config->xsendfile)) { 178 | $this->webserverSendFile = $this->config->xsendfile; 179 | } 180 | } 181 | 182 | protected function middleware() { 183 | return Kernel::onYes(); 184 | } 185 | 186 | protected function before() { 187 | return $this->autoAction(__FUNCTION__); 188 | } 189 | 190 | protected function after() { 191 | return $this->autoAction(__FUNCTION__); 192 | } 193 | 194 | protected function end() { 195 | if (Kernel::isUnfound($this->thenRes)) { 196 | $this->invokeStatus = self::STATUS_NOT_FOUND_ACTION; 197 | return $this->return404(); 198 | } 199 | $this->view = $this->accessInstance->getView(); 200 | if (Request::wantJSON() || $this->responseType === self::RESP_TYPE_JSON) { 201 | $this->responseJSON(); 202 | } elseif (Request::wantXML() || $this->responseType === self::RESP_TYPE_XML) { 203 | $this->responseXML(); 204 | } elseif ($this->responseType === self::RESP_TYPE_FILE) { 205 | $this->responseFile($this->responseFilePath, $this->responseFileType); 206 | } elseif ($this->responseType === self::RESP_TYPE_EXCEPTION) { 207 | $this->responseException(); 208 | } else { 209 | $this->responseHTML(); 210 | } 211 | $this->setResponseCode(); 212 | } 213 | 214 | protected function responseUnfound() { 215 | if ($this->config->view->unfound) { 216 | $fullClass = View::class . Kernel::NS . $this->config->view->unfound; 217 | $ins = new $fullClass; 218 | $ins->route = $this->route; 219 | $ins->response = $this; 220 | return $ins->put(); 221 | } 222 | } 223 | 224 | protected function responseJSON() { 225 | HttpHeader::contentType('application/json'); 226 | if ($this->view) { 227 | return $this->callView(); 228 | } 229 | echo json_encode($this->responseData); 230 | } 231 | 232 | protected function responseXML() { 233 | HttpHeader::contentType('text/xml'); 234 | if ($this->view) { 235 | return $this->callView(); 236 | } 237 | 238 | echo 'charset . '"?>'; 239 | echo ''; 240 | $this->array2Xml($this->responseData); 241 | echo ''; 242 | } 243 | 244 | protected function array2Xml($arr) { 245 | foreach ($arr as $k => $v) { 246 | if (is_array($v)) { 247 | echo "<$k>"; 248 | $this->array2Xml($v); 249 | echo ""; 250 | } else { 251 | echo "<$k>$v"; 252 | } 253 | } 254 | } 255 | 256 | public function responseException($e) { 257 | if (PHP_SAPI !== Kernel::CLI) { 258 | echo "
";
259 |         }
260 |         echo $e;
261 |         if (PHP_SAPI !== Kernel::CLI) {
262 |             echo '
'; 263 | } 264 | } 265 | 266 | protected function responseHTML() { 267 | if ($this->view) { 268 | return $this->callView(); 269 | } elseif ($this->responseData) { 270 | echo "
";
271 |             var_dump($this->responseData);
272 |             echo '
'; 273 | } 274 | } 275 | 276 | protected function callView() { 277 | $view = $this->view; 278 | try { 279 | $ins = new $view($this->accessInstance); 280 | } catch (\Error $e) { 281 | if(class_exists($view)) { 282 | throw $e; 283 | } 284 | Kernel::runtimeException("View Class Not Found ($view)", E_USER_ERROR); 285 | } 286 | $ins->setData($this->responseData); 287 | $viewAction = $this->accessInstance->getViewAction(); 288 | try { 289 | $ins->{$viewAction}(); 290 | } catch (\Exception $e) { 291 | Kernel::runtimeException("View Class Not Found ($view)", E_USER_ERROR); 292 | } 293 | $ins->output(); 294 | } 295 | 296 | /** 297 | * call controller action, call step: 298 | * has userPostAction called, otherwise check whether has userAction and has called 299 | * no one return unfound 300 | * 301 | * @return \Toknot\Lib\Flag\Flag 302 | */ 303 | protected function doAction() { 304 | if (Kernel::isNo($this->route->getRoute())) { 305 | return Kernel::onUnfound(); 306 | } 307 | $action = $this->route->getAction() . $this->route->getModifier(); 308 | $anyMethodAction = $this->route->getAction() . Kernel::ACTION; 309 | 310 | if (method_exists($this->accessInstance, $action)) { 311 | $callAction = $action; 312 | } else if (method_exists($this->accessInstance, $anyMethodAction)) { 313 | $callAction = $anyMethodAction; 314 | } else { 315 | return Kernel::onUnfound(); 316 | } 317 | if ($this->route->getParameter()) { 318 | call_user_func_array(array($this->accessInstance, $callAction), $this->route->getParameter()); 319 | } else { 320 | $this->accessInstance->$callAction(); 321 | } 322 | return Kernel::onYes(); 323 | } 324 | 325 | protected function autoAction($function) { 326 | $action = $function . $this->route->getModifier(); 327 | $defAction = $function . Kernel::ACTION; 328 | if (method_exists($this->accessInstance, $defAction)) { 329 | $res = $this->accessInstance->$defAction(); 330 | if (Kernel::isNo($res)) { 331 | return Kernel::onNo(); 332 | } 333 | } 334 | if (method_exists($this->accessInstance, $action)) { 335 | $res = $this->accessInstance->$action(); 336 | if (Kernel::isNo($res)) { 337 | return Kernel::onNo(); 338 | } 339 | } 340 | return Kernel::onYes(); 341 | } 342 | 343 | } 344 | -------------------------------------------------------------------------------- /src/Lib/IO/Route.php: -------------------------------------------------------------------------------- 1 | ', '', '', '']; 18 | const REG_MATCH_LIST = ['([a-z]+)', '([a-z]*)', '([0-9]+)', '([0-9]*)']; 19 | 20 | private $controller = null; 21 | private $route = ''; 22 | private $uri = '/'; 23 | private $actionModifier = 'GET' . Kernel::ACTION; 24 | private $requestParams = []; 25 | private $action = ''; 26 | private $suffixLen = 5; 27 | private $routeConfig = null; 28 | protected $config = null; 29 | public $defaultAction = 'index'; 30 | public $defaultController = 'Index'; 31 | public static $sep = Kernel::HZL; 32 | public static $sepAction = Kernel::AT; 33 | 34 | protected function __construct() { 35 | $this->config = Kernel::instance()->config(); 36 | $this->routeConfig = $this->config->route; 37 | if ($this->config->sepAction) { 38 | self::$sepAction = $this->config->sepAction; 39 | } 40 | if($this->config->rewriteUnavailable) { 41 | if($this->config->routeFeild) { 42 | $this->uri = Request::get()->value($this->config->routeFeild); 43 | } else { 44 | $this->uri = Request::get()->index(0); 45 | } 46 | } else { 47 | $this->uri = Request::uri(); 48 | } 49 | $this->suffixLen = strlen(Kernel::ACTION); 50 | } 51 | 52 | public function setup() { 53 | $this->setRoute(); 54 | $this->route2Controller(); 55 | } 56 | 57 | public static function __callStatic($name, $params = []) { 58 | $ins = self::instance(); 59 | return $ins->invoke($name, $params); 60 | } 61 | 62 | public function getController() { 63 | return $this->controller; 64 | } 65 | 66 | public function getModifier() { 67 | return $this->actionModifier; 68 | } 69 | 70 | public function getAction() { 71 | return $this->action; 72 | } 73 | 74 | public function getRoute() { 75 | return $this->route; 76 | } 77 | 78 | public function getParameter($idx = -1) { 79 | if ($idx < 0) { 80 | return $this->requestParams; 81 | } 82 | return $this->requestParams[$idx]; 83 | } 84 | 85 | /** 86 | * convert controller action to url 87 | * 88 | * @param string $controller the controller action name ,like: NamespaceOne\ClassOne@actionName, 89 | * the namespace without Toknot\App\Controller 90 | * @param array $params url query string 91 | * @param string $domain url domain, if not pass, return path only 92 | * @param string $scheme url scheme 93 | * 94 | */ 95 | public function generateUrl($controller, $params = [], $domain = '', $scheme = '') { 96 | $url = Kernel::URL_SEP . $this->controller2Route($controller); 97 | if ($params) { 98 | $url = $this->buildParams($url, $params); 99 | } 100 | if ($domain) { 101 | $url = $domain . $url; 102 | } 103 | if ($scheme) { 104 | $url = $scheme . '://' . $url; 105 | } 106 | return $url; 107 | } 108 | 109 | /** 110 | * bind url query string 111 | * 112 | * @param string $url the url path 113 | * @param string $param the query args 114 | */ 115 | protected function buildParams($url, $param) { 116 | $rule = $this->findRule($url); 117 | $regs = $this->findRuleReg($rule); 118 | if (!$regs) { 119 | return $url . Kernel::QUTM . http_build_query($param); 120 | } 121 | $option = $httpGets = []; 122 | foreach ($param as $i => $v) { 123 | if (is_numeric($i)) { 124 | $option[$i] = $v; 125 | } else { 126 | $httpGets[$i] = $v; 127 | } 128 | } 129 | if (empty($option)) { 130 | return $url . Kernel::QUTM . http_build_query($param); 131 | } 132 | 133 | $replace = []; 134 | $k = 0; 135 | foreach ($regs as $i => $v) { 136 | if (!isset($option[$k])) { 137 | $option[$k] = ''; 138 | } 139 | $replace[$k] = self::MATCH_LIST[$i]; 140 | $k++; 141 | } 142 | $all = array_merge($httpGets, array_diff_key($option, $replace)); 143 | $getParams = Kernel::QUTM . http_build_query($all); 144 | return str_replace($replace, $option, $option) . $getParams; 145 | } 146 | 147 | /** 148 | * convert controller action name to route path 149 | */ 150 | protected function controller2Route($controller) { 151 | $appNs = Kernel::NS . Kernel::instance()->appControllerNs() . Kernel::NS; 152 | $controller = str_replace($appNs, '', $controller); 153 | 154 | $controllerCompent = explode(self::$sepAction, $controller); 155 | $controllerClass = $controllerCompent[0]; 156 | $action = isset($controllerCompent[1]) ? $controllerCompent[1] : $this->defaultAction; 157 | 158 | $path = Kernel::classToLower($controllerClass); 159 | $actionRoute = Kernel::NOP; 160 | if ($action) { 161 | $len = strlen($action); 162 | 163 | foreach (Request::METHOD_LIST as $m) { 164 | $len = strlen($m); 165 | $offset = -($len + $this->suffixLen); 166 | if (strtoupper(substr($action, $offset, $len)) === $m) { 167 | $actionRoute = substr($action, 0, $offset); 168 | break; 169 | } 170 | } 171 | if (!$actionRoute) { 172 | $actionRoute = $action; 173 | } 174 | $actionRoute = Kernel::classToLower($actionRoute); 175 | } 176 | 177 | return ltrim(strtolower($path), Kernel::UDL) . Kernel::URL_SEP . $actionRoute; 178 | } 179 | 180 | /** 181 | * set current route according to access uri 182 | */ 183 | protected function setRoute() { 184 | if (is_array($this->uri)) { 185 | $path = isset($this->uri[1]) ? $this->uri[1] : Kernel::NOP; 186 | } else { 187 | $path = parse_url($this->uri, PHP_URL_PATH); 188 | } 189 | $path = trim($path); 190 | 191 | foreach ($this->routeConfig as $match => $m) { 192 | if ($match === $path) { 193 | $this->route = $m; 194 | return true; 195 | } elseif ($this->match($match, $path)) { 196 | $this->route = $m; 197 | return true; 198 | } 199 | } 200 | $isDir = (substr($path, -1) === Kernel::URL_SEP || $path === Kernel::NOP); 201 | if ($isDir) { 202 | if ($path === Kernel::URL_SEP || $path === Kernel::NOP) { 203 | $controller = $this->defaultController; 204 | } else { 205 | $controller = rtrim(Kernel::toUpper($path, self::$sep . Kernel::URL_SEP), Kernel::URL_SEP); 206 | } 207 | $this->route = $controller . self::$sepAction . $this->defaultAction; 208 | } else { 209 | $findIdx = strrpos($path, Kernel::URL_SEP); 210 | $lastIdx = $findIdx + 1; 211 | $controller = substr($path, 0, $lastIdx); 212 | if ($controller === Kernel::URL_SEP || $findIdx === false) { 213 | $controller = Kernel::toUpper($path, self::$sep . Kernel::URL_SEP); 214 | $action = $this->defaultAction; 215 | $this->route = $controller . self::$sepAction . $action; 216 | } else { 217 | $controller = Kernel::toUpper($controller, self::$sep . Kernel::URL_SEP); 218 | $action = substr($path, $lastIdx); 219 | $this->route = $controller . self::$sepAction . $action; 220 | } 221 | } 222 | return true; 223 | } 224 | 225 | /** 226 | * route to controller action 227 | */ 228 | protected function route2Controller() { 229 | $route = strtr(rtrim($this->route, Kernel::NS . Kernel::SP), Kernel::URL_SEP, Kernel::NS); 230 | list($controller, $action) = explode(self::$sepAction, $route); 231 | $this->action = lcfirst(Kernel::toUpper($action, Kernel::HZL)); 232 | $this->actionModifier = Request::method() . Kernel::ACTION; 233 | $this->controller = Kernel::instance()->appControllerNs() . Kernel::NS . Kernel::toUpper(trim($controller, Kernel::NS)); 234 | } 235 | 236 | protected function match($match, $path) { 237 | $match = strtolower($match); 238 | $path = strtolower($path); 239 | $findReg = $this->findRuleReg($match); 240 | if (empty($findReg)) { 241 | return false; 242 | } 243 | $pathStart = $ruleStart = 0; 244 | $m = []; 245 | foreach ($findReg as $i => $idx) { 246 | $normalStrLen = $idx - $ruleStart; 247 | $pre = substr($path, $pathStart, $normalStrLen); 248 | if ($pre != substr($match, $ruleStart, $normalStrLen)) { 249 | return false; 250 | } 251 | $matchPath = substr($path, $pathStart - $normalStrLen); 252 | $reg = '/^' . self::REG_MATCH_LIST[$i] . Kernel::URL_SEP; 253 | if (!preg_match($reg, $matchPath, $m)) { 254 | return false; 255 | } 256 | $this->requestParams[] = $m[1]; 257 | $pathStart = $idx + strlen($m[1]); 258 | $ruleStart = $idx + strlen(self::MATCH_LIST[$i]); 259 | } 260 | return true; 261 | } 262 | 263 | protected function findRuleReg($match) { 264 | $findReg = []; 265 | foreach (self::MATCH_LIST as $i => $reg) { 266 | if (($idx = strpos($match, $reg)) !== false) { 267 | $findReg = [$i => $idx]; 268 | } 269 | } 270 | return $findReg; 271 | } 272 | 273 | protected function findRule($route) { 274 | foreach ($this->routeConfig as $rule => $map) { 275 | if ($route == $map) { 276 | return $rule; 277 | } 278 | } 279 | } 280 | 281 | } 282 | -------------------------------------------------------------------------------- /src/Lib/Model/Auth/RequestCsrf.php: -------------------------------------------------------------------------------- 1 | feildName = $feildName; 29 | $this->handler = $handler; 30 | } 31 | 32 | public function hash($data, $key, $raw = false) { 33 | return Kernel::hash($data, $key, $raw, self::$useAlgo, self::$hmac); 34 | } 35 | 36 | public function setRefererWhite($referer) { 37 | $this->refererWhiteList[] = $referer; 38 | } 39 | 40 | public function forceValidReferer() { 41 | $this->validReferer = true; 42 | } 43 | 44 | public function getLastAlgo() { 45 | return self::$useAlgo; 46 | } 47 | 48 | public function getLastHmac() { 49 | return self::$hmac; 50 | } 51 | 52 | public function enableCsrf($method = null) { 53 | if ($method === null) { 54 | foreach (Request::METHOD_LIST as $m) { 55 | $this->state[$m] = true; 56 | } 57 | } else { 58 | if (is_numeric($method) && !empty(Request::METHOD_LIST[$method])) { 59 | $m = Request::METHOD_LIST[$method]; 60 | } elseif (in_array($method, Request::METHOD_LIST)) { 61 | $m = $method; 62 | } else { 63 | throw new Exception('unknow http request method'); 64 | } 65 | $this->state[$m] = true; 66 | } 67 | } 68 | 69 | public function checkCsrf($id) { 70 | $method = Request::method(); 71 | if (isset($this->state[$method])) { 72 | $value = Request::any($method)->value($this->feildName); 73 | return $this->handler->get($id) === $value; 74 | } 75 | return true; 76 | } 77 | 78 | public function getCsrfHash($id) { 79 | $value = $this->hash(Request::requestHash()); 80 | $this->handler->store($id, $value); 81 | return $value; 82 | } 83 | 84 | protected function checkWhiteReferer($referer) { 85 | if (!$this->validReferer && !$referer) { 86 | return true; 87 | } 88 | foreach ($this->refererWhiteList as $white) { 89 | if (strcasecmp($referer, $white) === 0) { 90 | return true; 91 | } 92 | } 93 | return false; 94 | } 95 | 96 | /** 97 | * 98 | * @param string $key 99 | * @return type 100 | */ 101 | public function selfVerifyHash($key = Kernel::NOP) { 102 | $t = pack('V', time()); 103 | $uri = $this->hash(Request::url(), $key . $t, true); 104 | $userMark = $t . $this->hashReqeustInfo($t, $key, $uri); 105 | $hashValue = $uri . $t . password_hash($userMark, PASSWORD_DEFAULT); 106 | return urlencode(base64_encode($hashValue)); 107 | } 108 | 109 | public function verifyReferer($refererHash, $key, $t) { 110 | $referer = Request::referer(); 111 | if ($this->checkWhiteReferer($referer)) { 112 | return true; 113 | } 114 | $refererState = ($this->hash($referer . $key, $t, true) === $refererHash); 115 | if (($referer || $this->validReferer) && !$refererState) { 116 | return false; 117 | } 118 | return true; 119 | } 120 | 121 | public function execSelfVerifyHash($hash, $key = Kernel::NOP) { 122 | $referer = Request::referer() ? Request::referer() : Kernel::NOP; 123 | if ($this->validReferer && !$referer) { 124 | return false; 125 | } 126 | $hash = base64_decode(urldecode($hash)); 127 | $hashLen = strlen($this->hash(1, 1, true)); 128 | $t = substr($hash, $hashLen, 4); 129 | $uri = substr($hash, 0, $hashLen); 130 | if (!$this->verifyReferer($uri, $key, $t)) { 131 | return false; 132 | } 133 | 134 | list(, $hashTime) = unpack('V', $t); 135 | if (!is_numeric($hashTime)) { 136 | return false; 137 | } 138 | $checkHash = substr($hash, $hashLen + 4); 139 | $offsetTime = ceil((time() - $hashTime) / 60); 140 | if ($offsetTime > $this->leftTime) { 141 | return false; 142 | } 143 | $userMask = $t . $this->hashReqeustInfo($t, $key, $uri); 144 | if (password_verify($userMask, $checkHash)) { 145 | return true; 146 | } 147 | 148 | return false; 149 | } 150 | 151 | protected function hashReqeustInfo($t, $key, $uri) { 152 | $a = $t . $uri . Request::userBrowserId() . Request::ip() . Request::agent() . self::serverEntropy(); 153 | return $this->hash($a, $key, true); 154 | } 155 | 156 | /** 157 | * multiple server must has same platfrom software that is php, mysqlnd, webserver ,os release 158 | * 159 | * @param bool $single 160 | * @return string 161 | */ 162 | public static function serverEntropy($single = true) { 163 | return Kernel::serverEntropy($single); 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/Lib/Model/Auth/Session.php: -------------------------------------------------------------------------------- 1 | kernel = Kernel::instance(); 27 | session_set_save_handler($this, true); 28 | $this->definedName = $name === null ? $this->definedName : $name; 29 | $this->opHander = $opHandler; 30 | $this->name($this->definedName); 31 | $this->start(); 32 | } 33 | 34 | public function start() { 35 | $status = session_status(); 36 | if ($status === PHP_SESSION_ACTIVE) { 37 | $this->id = session_id(); 38 | return; 39 | } elseif ($status === PHP_SESSION_NONE) { 40 | $this->manual = !session_start(); 41 | if ($this->manual) { 42 | $id = $this->generateId(); 43 | $this->id($id); 44 | } else { 45 | $this->id = session_id(); 46 | } 47 | } elseif ($status === PHP_SESSION_DISABLED) { 48 | $this->manual = true; 49 | } 50 | parent::__construct($_SESSION); 51 | } 52 | 53 | public function get($key) { 54 | return $_SESSION[$key]; 55 | } 56 | 57 | public function set($key, $value) { 58 | $_SESSION[$key] = $value; 59 | } 60 | 61 | public function offsetSet($key, $value) { 62 | $_SESSION[$key] = $value; 63 | parent::offsetSet($key, $value); 64 | } 65 | 66 | public function offsetExists($key) { 67 | return array_key_exists($key, $_SESSION); 68 | } 69 | 70 | public function offsetUnset($key) { 71 | unset($_SESSION[$key]); 72 | parent::offsetUnset($key); 73 | } 74 | 75 | public function offsetGet($key) { 76 | return $_SESSION[$key]; 77 | } 78 | 79 | public function name($name = null) { 80 | if ($name === null) { 81 | return $this->getName(); 82 | } 83 | $this->definedName = $name; 84 | return session_name($name); 85 | } 86 | 87 | public function id($id = null) { 88 | if ($id === null) { 89 | return $this->getId(); 90 | } 91 | $this->id = $id; 92 | return session_id($id); 93 | } 94 | 95 | public function close() { 96 | if ($this->manual) { 97 | $this->manualSetCookie(); 98 | } 99 | return true; 100 | } 101 | 102 | public function destroy($sessionId) { 103 | return $this->opHander->destory($sessionId); 104 | } 105 | 106 | public function gc($maxlifetime) { 107 | return $this->opHander->gc($maxlifetime); 108 | } 109 | 110 | public function open($savePath, $name) { 111 | try { 112 | return $this->opHander->open($savePath, $name); 113 | } catch (\Exception $e) { 114 | $this->kernel->echoException($e); 115 | return false; 116 | } 117 | } 118 | 119 | public function read($sessionId) { 120 | try { 121 | return $this->opHander->read($sessionId); 122 | } catch (\Exception $e) { 123 | $this->kernel->echoException($e); 124 | return false; 125 | } 126 | } 127 | 128 | public function write($sessionId, $sessionData) { 129 | try { 130 | return $this->opHander->write($sessionId, $sessionData); 131 | } catch (\Exception $e) { 132 | $this->kernel->echoException($e); 133 | return false; 134 | } 135 | } 136 | 137 | protected function getName() { 138 | if ($this->manual) { 139 | return $this->definedName; 140 | } else { 141 | return session_name(); 142 | } 143 | } 144 | 145 | protected function getId() { 146 | if ($this->manual) { 147 | return Request::cookie()->value($this->definedName); 148 | } else { 149 | return session_id(); 150 | } 151 | } 152 | 153 | protected function manualSetCookie() { 154 | setcookie($this->definedName, $this->id); 155 | } 156 | 157 | protected function generateId() { 158 | return sha1(Request::requestHash()); 159 | } 160 | 161 | public function __destruct() { 162 | $this->close(); 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /src/Lib/Model/Auth/User.php: -------------------------------------------------------------------------------- 1 | table($table); 46 | if ($cas) { 47 | $this->enableCas(); 48 | } 49 | $this->id = $id; 50 | if ($this->id) { 51 | $this->find(); 52 | } 53 | } 54 | 55 | public function isNobody() { 56 | if (!$this->id || $this->record->isIdler()) { 57 | return true; 58 | } 59 | return false; 60 | } 61 | 62 | protected function hashToken($bgdata = '') { 63 | $data = $bgdata . $this->record->username . $this->record->email . $this->record->id . $this->record->password . $this->salt; 64 | $data .= Kernel::serverEntropy(); 65 | return Kernel::hash($data, self::$hashKey, self::$useAlgo, self::$hmac); 66 | } 67 | 68 | public function generateSiginUserToken($bgdata = '') { 69 | return self::generatePassword($this->hashToken($bgdata)); 70 | } 71 | 72 | public function checkSiginUserToken($hash, $bgdata = '') { 73 | $needhash = $this->hashToken($bgdata); 74 | return self::passwordVerify($needhash, $hash); 75 | } 76 | 77 | public static function generatePassword($password) { 78 | return password_hash($password, PASSWORD_DEFAULT); 79 | } 80 | 81 | public static function passwordVerify($password, $hash) { 82 | return password_verify($password, $hash); 83 | } 84 | 85 | public function enableCas($casCol = '_cas_ver') { 86 | $this->enableCas = true; 87 | self::$casFeild = $casCol; 88 | $this->table->casVerCol = $casCol; 89 | } 90 | 91 | public function enableSalt($salt = 'salt') { 92 | $this->enableSalt = true; 93 | if ($salt) { 94 | self::$saltFeild = $salt; 95 | } 96 | } 97 | 98 | public function setSaltLength($length) { 99 | if ($length % 2 !== 0) { 100 | $length = $length + 1; 101 | } 102 | self::$saltLength = $length; 103 | } 104 | 105 | public static function generateSalt() { 106 | return Kernel::randHex(self::$saltLength); 107 | } 108 | 109 | public function save() { 110 | $exp = ''; 111 | if ($this->enableCas) { 112 | $this->newCasVer(); 113 | $exp = $this->table->query()->col(self::$casFeild)->eq($this->casVer); 114 | } 115 | $this->record->save($exp); 116 | } 117 | 118 | protected function newCasVer() { 119 | if ($this->casVer) { 120 | $this->record[self::$casFeild] = $this->casVer + 1; 121 | } else { 122 | $this->record[self::$casFeild] = 1; 123 | } 124 | } 125 | 126 | public function setPassword($password) { 127 | if ($this->enableSalt) { 128 | $salt = $this->newSalt(); 129 | $password .= hash('sha256', $salt); 130 | } 131 | $this->record[self::$passwordFeild] = self::generatePassword($password); 132 | } 133 | 134 | public function checkUserPassword($password) { 135 | if ($this->enableSalt) { 136 | $password .= hash('sha256', $this->record[self::$saltFeild]); 137 | } 138 | return self::passwordVerify($password, $this->record[self::$passwordFeild]); 139 | } 140 | 141 | public function changePassword($oldPassword, $newPassword) { 142 | if ($this->checkUserPassword($oldPassword)) { 143 | $this->setPassword($newPassword); 144 | return true; 145 | } else { 146 | return false; 147 | } 148 | } 149 | 150 | public function newSalt() { 151 | $this->record[self::$saltFeild] = self::generateSalt(); 152 | return $this->record[self::$saltFeild]; 153 | } 154 | 155 | public function __set($name, $value) { 156 | if ($name === $this->idFeild) { 157 | Kernel::runtimeException('can not set user id'); 158 | } elseif ($name === $this->saltFeild) { 159 | Kernel::runtimeException('only use ' . __CLASS__ . '::newSalt() set salt'); 160 | } else { 161 | if ($name === self::$passwordFeild) { 162 | $this->setPassword($value); 163 | } else { 164 | $this->record->$name = $value; 165 | } 166 | } 167 | } 168 | 169 | public function __get($name) { 170 | return $this->record->$name; 171 | } 172 | 173 | protected function find() { 174 | if (!$this->id) { 175 | return $this->record = $this->table->idler(); 176 | } 177 | $this->record = $this->table->findOne($this->id); 178 | if ($this->record) { 179 | $this->checkSaltAndCas(); 180 | } 181 | } 182 | 183 | protected function checkSaltAndCas() { 184 | if (isset($this->record[self::$casFeild])) { 185 | $this->enableCas = true; 186 | $this->casVer = $this->record[self::$casFeild]; 187 | } 188 | if (isset($this->record[self::$saltFeild])) { 189 | $this->enableSalt; 190 | } 191 | } 192 | 193 | protected static function db() { 194 | return DB::instance(self::$dbkey); 195 | } 196 | 197 | protected function table($table) { 198 | $this->table = self::db()->table($table); 199 | $this->idFeild = $this->table->getKey(); 200 | $this->properList = $this->table->getColumns(); 201 | } 202 | 203 | } 204 | -------------------------------------------------------------------------------- /src/Lib/Model/Database/ActiveRecord.php: -------------------------------------------------------------------------------- 1 | table = $table; 27 | $this->currentRecord = $record; 28 | $this->columns = $this->table->getColumns(); 29 | $this->initKey(); 30 | } 31 | 32 | public function __get($name) { 33 | $name = Kernel::classToLower($name, Kernel::UDL); 34 | if (in_array($name, $this->columns)) { 35 | return $this->currentRecord[$name]; 36 | } 37 | Kernel::runtimeException(get_class($this->table) . " not exits property $name", E_USER_NOTICE); 38 | } 39 | 40 | public function toArray() { 41 | return $this->currentRecord; 42 | } 43 | 44 | public function getId() { 45 | return $this->idValue; 46 | } 47 | 48 | public function idName() { 49 | return $this->idName; 50 | } 51 | 52 | public function __set($name, $value) { 53 | $name = Kernel::classToLower($name, Kernel::UDL); 54 | if (in_array($name, $this->columns)) { 55 | $this->newRecordData[$name] = $value; 56 | return; 57 | } 58 | Kernel::runtimeException(get_class($this->table) . " not exits property $name", E_USER_NOTICE); 59 | } 60 | 61 | public function columns() { 62 | return $this->columns; 63 | } 64 | 65 | public function isIdler() { 66 | return empty($this->currentRecord); 67 | } 68 | 69 | protected function initKey() { 70 | if ($this->table->getKey()) { 71 | $this->idName = $this->table->getKey(); 72 | !$this->isIdler() && $this->idValue = $this->currentRecord[$this->idName]; 73 | } elseif ($this->table->getUnique()) { 74 | $this->idName = $this->table->getUnique()[0]; 75 | !$this->isIdler() && $this->idValue = $this->currentRecord[$this->idName]; 76 | } elseif ($this->table->getMulUnique()) { 77 | $muUnique = $this->table->getMulUnique(); 78 | $unique = current($muUnique); 79 | $this->idName = $unique; 80 | if (!$this->isIdler()) { 81 | foreach ($unique as $k) { 82 | $this->idValue[$k] = $this->currentRecord[$k]; 83 | } 84 | } 85 | } else { 86 | Kernel::runtimeException(get_class($this->table) . " not exits key or unique", E_USER_NOTICE); 87 | } 88 | } 89 | 90 | public function reset() { 91 | $this->newRecordData = []; 92 | } 93 | 94 | public function save($where = '') { 95 | if (empty($this->currentRecord)) { 96 | return $this->insert(); 97 | } elseif (is_array($this->idValue)) { 98 | $query = $this->table->query(); 99 | $and = $query->onAnd(); 100 | foreach ($this->idValue as $col => $v) { 101 | $and->arg($query->col($col)->eq($v)); 102 | } 103 | if ($where) { 104 | $and->arg($where); 105 | } 106 | return $query->where($and)->limit(1)->update($this->newRecordData); 107 | } else { 108 | return $this->table->updateById($this->newRecordData, $this->idValue, $where); 109 | } 110 | } 111 | 112 | public function lastInsertId() { 113 | return $this->table->db()->lastInsertId(); 114 | } 115 | 116 | protected function insert() { 117 | $query = $this->table->query(); 118 | $insert = []; 119 | foreach ($this->table->getColumns() as $col) { 120 | if (isset($this->newRecordData[$col])) { 121 | $insert[$col] = $this->newRecordData[$col]; 122 | continue; 123 | } 124 | 125 | $cols = $query->col($col); 126 | if ($cols->isAutoIncrement()) { 127 | continue; 128 | } 129 | if (!$cols->hasDefault()) { 130 | $insert[$col] = $cols->guessInsertDefaultValue(); 131 | } 132 | } 133 | 134 | return $query->insert($insert); 135 | } 136 | 137 | public function rewind() { 138 | reset($this->currentRecord); 139 | } 140 | 141 | public function current() { 142 | return current($this->currentRecord); 143 | } 144 | 145 | public function key() { 146 | $this->iteratorKey = key($this->currentRecord); 147 | return $this->iteratorKey; 148 | } 149 | 150 | public function next() { 151 | next($this->currentRecord); 152 | } 153 | 154 | public function valid() { 155 | return isset($this->currentRecord[$this->iteratorKey]); 156 | } 157 | 158 | public function offsetExists($offset) { 159 | if (in_array($offset, $this->columns)) { 160 | return true; 161 | } 162 | return false; 163 | } 164 | 165 | public function offsetGet($offset) { 166 | return $this->__get($offset); 167 | } 168 | 169 | public function offsetSet($offset, $value) { 170 | return $this->__set($offset, $value); 171 | } 172 | 173 | public function offsetUnset($offset) { 174 | Kernel::runtimeException('can not unset a property of table ' . get_class($this->table), E_USER_NOTICE); 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /src/Lib/Model/Database/Column.php: -------------------------------------------------------------------------------- 1 | name = $name; 42 | $this->query = $query; 43 | $this->table = $table; 44 | list($this->type, $this->length, $this->scale, $this->default, $this->enumList) = $info; 45 | $this->defaultValue(); 46 | } 47 | 48 | protected function defaultValue() { 49 | if ($this->default !== null) { 50 | $this->hasDefault = true; 51 | } else { 52 | $this->hasDefault = false; 53 | } 54 | } 55 | 56 | public function isAutoIncrement() { 57 | if ($this->isKey() && $this->table->isAutoIncrement()) { 58 | return true; 59 | } 60 | } 61 | 62 | public function getType() { 63 | return $this->type; 64 | } 65 | 66 | public function getName() { 67 | return $this->name; 68 | } 69 | 70 | public function getLength() { 71 | return $this->length; 72 | } 73 | 74 | public function getScaleLenght() { 75 | return $this->scale; 76 | } 77 | 78 | public function noBind() { 79 | $this->bind = false; 80 | return $this; 81 | } 82 | 83 | public function isKey() { 84 | return $this->table->getKey() == $this->name; 85 | } 86 | 87 | public function isUnique() { 88 | return in_array($this->name, $this->table->getUnique()); 89 | } 90 | 91 | public function isIndex() { 92 | $indexs = $this->table->getIndex(); 93 | foreach ($indexs as $keyName => $index) { 94 | if (count($index) === 0 && in_array($this->name, $index)) { 95 | return $keyName; 96 | } 97 | } 98 | return false; 99 | } 100 | 101 | public function inMulIndex() { 102 | $indexs = $this->table->getIndex(); 103 | foreach ($indexs as $keyName => $index) { 104 | if (count($index) > 1 && in_array($this->name, $index)) { 105 | return $keyName; 106 | } 107 | } 108 | return false; 109 | } 110 | 111 | public function inMulUnique() { 112 | $uniques = $this->table->getMulUnique(); 113 | foreach ($uniques as $index) { 114 | if (in_array($this->name, $index)) { 115 | return true; 116 | } 117 | } 118 | return false; 119 | } 120 | 121 | public function hasDefault() { 122 | return $this->hasDefault; 123 | } 124 | 125 | public function getDefault() { 126 | return $this->default; 127 | } 128 | 129 | public function guessInsertDefaultValue() { 130 | if ($this->isKey() || $this->isUnique()) { 131 | Kernel::runtimeException("column $this->name can not guess defalut value", E_USER_WARNING); 132 | } 133 | if ($this->isString()) { 134 | return self::$guessString; 135 | } elseif ($this->isNumber()) { 136 | return self::$guessNumber; 137 | } elseif ($this->isJSON()) { 138 | return self::$guessJSON; 139 | } elseif ($this->isTime()) { 140 | return self::$guessTime === 'now' ? date('Y-m-d H:i:s') : self::$guessTime; 141 | } elseif ($this->isEnum()) { 142 | return $this->enumList[self::$guessEnum]; 143 | } 144 | Kernel::runtimeException("column $this->name can not guess defalut value", E_USER_NOTICE); 145 | } 146 | 147 | public function isString() { 148 | $typeList = ['CHAR', 'TEXT', 'BINARY', 'BLOB']; 149 | return $this->checkType($typeList); 150 | } 151 | 152 | public function isNumber() { 153 | $typeList = ['INT', 'FLOAT', 'DOUBLE', 'REAL', 'BIT', 'BOOLEAN', 'SERIAL', 'DECIMAL']; 154 | return $this->checkType($typeList); 155 | } 156 | 157 | public function isJSON() { 158 | return $this->caseType('JSON'); 159 | } 160 | 161 | public function isEnum() { 162 | return $this->caseType('ENUM') || $this->caseType('SET'); 163 | } 164 | 165 | public function isTime() { 166 | $typeList = ['DATE', 'TIME', 'YEAR']; 167 | return $this->checkType($typeList); 168 | } 169 | 170 | /** 171 | * 172 | * @return \Toknot\Lib\Model\Database\TableModel 173 | */ 174 | public function getTable() { 175 | return $this->table; 176 | } 177 | 178 | public function func($name, ...$args) { 179 | $exp = new FunctionExpression($name); 180 | $exp->arg($this); 181 | $exp->args($args); 182 | return $exp; 183 | } 184 | 185 | public function set($value) { 186 | return $this->expression(Kernel::EQ, $value, Expression::TYPE_SET_VALUE); 187 | } 188 | 189 | public function eq($value) { 190 | if (is_array($value)) { 191 | return $this->in($value); 192 | } 193 | return $this->expression(Kernel::EQ, $value); 194 | } 195 | 196 | public function lt($value) { 197 | return $this->expression(Kernel::LT, $value); 198 | } 199 | 200 | public function gt($value) { 201 | return $this->expression(Kernel::GT, $value); 202 | } 203 | 204 | public function le($value) { 205 | return $this->expression(Kernel::LE, $value); 206 | } 207 | 208 | public function ge($value) { 209 | return $this->expression(Kernel::GE, $value); 210 | } 211 | 212 | public function neq($value) { 213 | return $this->expression(Kernel::NEQ, $value); 214 | } 215 | 216 | public function lg($value) { 217 | return $this->expression(Kernel::LG, $value); 218 | } 219 | 220 | public function add($value) { 221 | return $this->expression(Kernel::M_ADD, $value); 222 | } 223 | 224 | public function sub($value) { 225 | return $this->expression(Kernel::M_SUB, $value); 226 | } 227 | 228 | public function mul($value) { 229 | return $this->expression(Kernel::M_MUL, $value); 230 | } 231 | 232 | public function div($value) { 233 | return $this->expression(Kernel::M_DIV, $value); 234 | } 235 | 236 | public function in($value) { 237 | return $this->expression(QueryBuild::IN, $value, Expression::TYPE_LIKE_FUNC); 238 | } 239 | 240 | public function notIn($value) { 241 | return $this->expression(QueryBuild::NOT_IN, $value, Expression::TYPE_LIKE_FUNC); 242 | } 243 | 244 | public function notlike($value) { 245 | return $this->expression(Kernel::SP . Kernel::L_NOT . QueryBuild::LKIE, $value); 246 | } 247 | 248 | public function like($value) { 249 | return $this->likeExpression($value, Kernel::PERCENT, Kernel::PAD_BOTH); 250 | } 251 | 252 | public function like1($value) { 253 | return $this->likeExpression($value, Kernel::UDL, Kernel::PAD_BOTH); 254 | } 255 | 256 | public function leftLike($value) { 257 | return $this->likeExpression($value, Kernel::PERCENT, Kernel::PAD_LEFT); 258 | } 259 | 260 | public function leftLike1($value) { 261 | return $this->likeExpression($value, Kernel::UDL, Kernel::PAD_LEFT); 262 | } 263 | 264 | public function rightLike($value) { 265 | return $this->likeExpression($value, Kernel::PERCENT, Kernel::PAD_RIGHT); 266 | } 267 | 268 | public function rightLike1($value) { 269 | return $this->likeExpression($value, Kernel::UDL, Kernel::PAD_RIGHT); 270 | } 271 | 272 | public function strictLike($value) { 273 | return $this->likeExpression($value); 274 | } 275 | 276 | public function getExpression() { 277 | $alias = $this->table->getAlias(); 278 | if ($alias) { 279 | $alias .= Kernel::DOT; 280 | } 281 | return $alias . Kernel::BACKTICK . $this->name . Kernel::BACKTICK; 282 | } 283 | 284 | protected function filterData($value) { 285 | if ($this->isNumber() && !is_numeric($value)) { 286 | throw new Exception("Column '$this->name' need a numeric value, other given"); 287 | } elseif ($this->isJSON() && is_array($value)) { 288 | return $this->table->quote(json_encode($value)); 289 | } elseif ($this->isTime() && is_numeric($value)) { 290 | return $this->filterTime($value); 291 | } elseif ($this->isEnum() && !in_array($value, $this->enumlist)) { 292 | throw new Exception("given enum value '$value' invaild"); 293 | } else { 294 | return $value; 295 | } 296 | } 297 | 298 | protected function filterTime($value) { 299 | if ($this->caseType('DATE')) { 300 | return date('Y-m-d', $value); 301 | } elseif ($this->caseType('DATETIME')) { 302 | return date('Y-m-d H:i:s', $value); 303 | } elseif ($this->caseType('TIMESTAMP')) { 304 | return date('Y-m-d H:i:s', $value); 305 | } elseif ($this->caseType('YEAR')) { 306 | $f = $this->length == 2 ? 'y' : 'Y'; 307 | return date($f, $value); 308 | } elseif ($this->caseType('TIME')) { 309 | return date('H:i:s', $value); 310 | } 311 | } 312 | 313 | protected function checkType($typeList) { 314 | foreach ($typeList as $t) { 315 | if (stripos($this->type, $t) !== false) { 316 | return true; 317 | } 318 | } 319 | return false; 320 | } 321 | 322 | protected function caseType($type) { 323 | return Kernel::caseEq($this->type, $type); 324 | } 325 | 326 | protected function likeExpression($value, $pad = Kernel::NOP, $num = Kernel::PAD_NO) { 327 | if ($num === Kernel::PAD_LEFT) { 328 | $value = $pad . $value; 329 | } elseif ($num === Kernel::PAD_RIGHT) { 330 | $value = $value . $pad; 331 | } elseif ($num === Kernel::PAD_BOTH) { 332 | $value = $pad . $value . $pad; 333 | } 334 | return $this->expression(QueryBuild::LKIE, $value); 335 | } 336 | 337 | public function expression($expOp, $value, $expType = 0) { 338 | $this->exp = new Expression($expOp, $expType); 339 | $this->expressionColFlag($this, $this->exp); 340 | $this->exp->left($this); 341 | $value = $this->filterData($value); 342 | if (is_scalar($value) && $this->bind) { 343 | $setValue = $this->query->bindColParameterValue($this, $value); 344 | $this->exp->bindRight(); 345 | } else { 346 | $setValue = $value; 347 | } 348 | $this->bind = true; 349 | $this->exp->right($setValue); 350 | return $this->exp; 351 | } 352 | 353 | protected function expressionColFlag($col, $exp) { 354 | if ($col->isKey()) { 355 | return $exp->hasKey(true); 356 | } elseif ($col->isUnique()) { 357 | return $exp->hasUnique(true); 358 | } elseif ($col->isIndex()) { 359 | return $exp->hasIndex(true); 360 | } 361 | return false; 362 | } 363 | 364 | } 365 | -------------------------------------------------------------------------------- /src/Lib/Model/Database/DB.php: -------------------------------------------------------------------------------- 1 | config = self::databaseConfig(); 30 | $this->queryDBName(); 31 | $this->flushDatabaseCache(); 32 | } 33 | 34 | public function last() { 35 | return self::$last; 36 | } 37 | 38 | public static function databaseConfig() { 39 | return Kernel::instance()->config()->database; 40 | } 41 | 42 | /** 43 | * 44 | * @param string $key 45 | * @return $this 46 | */ 47 | public static function instance($key = '') { 48 | $lastKey = $key ? $key : self::$last; 49 | if (isset(self::$ins[$lastKey]) && is_a(self::$ins[$lastKey], __CLASS__)) { 50 | return self::$ins[$lastKey]; 51 | } 52 | 53 | $config = self::databaseConfig(); 54 | $defaultKey = $config->default; 55 | $key = $key ? $key : $defaultKey; 56 | 57 | $dataArg = ['dsn' => 'localhost', 'username' => '', 'password' => null, 'option' => [], 'prefix' => '']; 58 | foreach ($dataArg as $arg => $v) { 59 | $$arg = isset($config->$key->$arg) ? $config->$key->$arg : $v; 60 | } 61 | $attr = []; 62 | foreach ($option as $key => $v) { 63 | $keyValue = constant('\PDO::' . strtoupper($key)); 64 | $attr[$keyValue] = $v; 65 | } 66 | 67 | self::$last = $key; 68 | self::$ins[$key] = new static($dsn, $username, $password, $attr); 69 | 70 | self::$ins[$key]->tablePrefix = $prefix; 71 | return self::$ins[$key]; 72 | } 73 | 74 | public function setConnectAutocommit() { 75 | return $this->setAttribute(self::ATTR_AUTOCOMMIT, 1); 76 | } 77 | 78 | public function isConnectAutocommit() { 79 | return $this->getAttribute(self::ATTR_AUTOCOMMIT); 80 | } 81 | 82 | public function flushDatabaseCache() { 83 | $key = self::$last ? self::$last : $this->dbname; 84 | $this->tableModelCacheFile = Kernel::instance()->databasePath . DIRECTORY_SEPARATOR . $key . Kernel::PHP_EXT; 85 | 86 | if (self::$forceFlushDatabaseCache || !file_exists($this->tableModelCacheFile)) { 87 | $this->generateTableModel(); 88 | } 89 | } 90 | 91 | public function tablePrefix() { 92 | return $this->tablePrefix; 93 | } 94 | 95 | public function getDBName() { 96 | return $this->dbname; 97 | } 98 | 99 | public function loadTableCacheFile() { 100 | if (!$this->modelCacheLoad) { 101 | $this->modelCacheLoad = true; 102 | include $this->tableModelCacheFile; 103 | } 104 | } 105 | 106 | /** 107 | * 108 | * @param string $table 109 | * @return \Toknot\Lib\Model\Database\TableModel 110 | */ 111 | public function table($table) { 112 | $this->loadTableCacheFile(); 113 | if ($this->tablePrefix && strpos($table, $this->tablePrefix) === 0) { 114 | $table = substr($table, strlen($this->tablePrefix)); 115 | } 116 | $tableClass = $this->tableModelNamespace() . Kernel::NS . Kernel::toUpper($table); 117 | if (class_exists($tableClass, false)) { 118 | return new $tableClass(); 119 | } 120 | Kernel::runtimeException("table '$table' not exists at database '$this->dbname'", E_USER_ERROR); 121 | } 122 | 123 | public function tableNumber() { 124 | return $this->query("SELECT COUNT(*) AS cnt FROM `information_schema`.`TABLES` " 125 | . "WHERE `TABLES`.`TABLE_SCHEMA`='{$this->dbname}'")->fetchColumn(); 126 | } 127 | 128 | protected function getDBTablesInfo($offset = 0) { 129 | return $this->query("SELECT * FROM `information_schema`.`COLUMNS` " 130 | . "WHERE `COLUMNS`.`TABLE_SCHEMA`='{$this->dbname}' LIMIT {$this->tableLimit} OFFSET $offset") 131 | ->fetchAll(self::FETCH_ASSOC); 132 | } 133 | 134 | protected function getTableIndex($table) { 135 | $indexList = $this->query("SHOW INDEX FROM `{$this->dbname}`.`{$table}`"); 136 | $mulList = ['mulIndex' => [], 'mulUni' => []]; 137 | $keys = ['mulIndex' => [], 'mulUni' => []]; 138 | foreach ($indexList as $index) { 139 | $keyName = $index['Key_name']; 140 | $column = $index['Column_name']; 141 | if ($keyName == 'PRIMARY') { 142 | continue; 143 | } 144 | $vName = $index['Non_unique'] ? 'mulIndex' : 'mulUni'; 145 | 146 | if (isset($mulList[$vName][$keyName])) { 147 | $mulList[$vName][$keyName][] = $column; 148 | $keys[$vName][$keyName] = $mulList[$vName][$keyName]; 149 | } else { 150 | $mulList[$vName][$keyName] = [$column]; 151 | if ($vName == 'mulIndex') { 152 | $keys[$vName][$keyName] = $mulList[$vName][$keyName]; 153 | } 154 | } 155 | } 156 | 157 | return $keys; 158 | } 159 | 160 | public function tableModelNamespace() { 161 | $defNs = Kernel::toUpper($this->dbname, Kernel::UDL . Kernel::HZL); 162 | return Kernel::TOKNOT_NS . Kernel::NS . 'TableModel' . Kernel::NS . $defNs; 163 | } 164 | 165 | protected function tableModelString() { 166 | $offset = 0; 167 | $tableColumns = []; 168 | do { 169 | $tableCols = $this->getDBTablesInfo($offset); 170 | $tableColumns = array_merge($tableColumns, $tableCols); 171 | $offset += 100; 172 | } while ($tableCols); 173 | $tableList = []; 174 | foreach ($tableColumns as $col) { 175 | $tableName = $col['TABLE_NAME']; 176 | $column = $col['COLUMN_NAME']; 177 | if (empty($tableList[$tableName])) { 178 | $tableList[$tableName] = []; 179 | $tableList[$tableName]['column'] = []; 180 | $tableList[$tableName]['columnInfo'] = []; 181 | $tableList[$tableName]['key'] = ''; 182 | $tableList[$tableName]['uni'] = []; 183 | $tableList[$tableName]['index'] = []; 184 | $tableList[$tableName]['ai'] = false; 185 | $tableList[$tableName]['mul'] = false; 186 | } 187 | 188 | $tableList[$tableName]['column'][] = $column; 189 | 190 | $values = []; 191 | if ($col['DATA_TYPE'] == 'set' || $col['DATA_TYPE'] == 'enum') { 192 | $len = strlen($col['DATA_TYPE']); 193 | $values = explode(Kernel::COMMA, str_replace(Kernel::QUOTE, Kernel::NOP, substr($col['COLUMN_TYPE'], $len + 1, -1))); 194 | } 195 | if ($col['CHARACTER_MAXIMUM_LENGTH'] !== null) { 196 | $len = $col['CHARACTER_MAXIMUM_LENGTH']; 197 | } elseif ($col['NUMERIC_PRECISION'] !== null) { 198 | $len = $col['NUMERIC_PRECISION']; 199 | } elseif ($col['DATETIME_PRECISION'] !== null) { 200 | $len = $col['DATETIME_PRECISION']; 201 | } 202 | $tableList[$tableName]['columnInfo'][$column] = [strtoupper($col['DATA_TYPE']), $len, 203 | $col['NUMERIC_SCALE'], 204 | $col['COLUMN_DEFAULT'], $values]; 205 | 206 | if ($col['COLUMN_KEY'] == 'PRI') { 207 | $tableList[$tableName]['key'] = $column; 208 | } elseif ($col['COLUMN_KEY'] == 'UNI') { 209 | $tableList[$tableName]['uni'][] = $column; 210 | } elseif ($col['COLUMN_KEY'] == 'MUL') { 211 | $tableList[$tableName]['mul'] = true; 212 | } 213 | 214 | if ($col['EXTRA'] == 'auto_increment') { 215 | $tableList[$tableName]['ai'] = true; 216 | } 217 | } 218 | 219 | $class = Kernel::PHP . '/* Auto generate by toknot at Date:' . date('Y-m-d H:i:s') . ' */' . Kernel::EOL; 220 | $class .= Kernel::DEF_NS . $this->tableModelNamespace() . Kernel::SEMI; 221 | $class .= Kernel::DEF_USE . TableModel::class . Kernel::SEMI . Kernel::EOL; 222 | foreach ($tableList as $tableName => $cols) { 223 | if ($cols['mul']) { 224 | $keys = $this->getTableIndex($tableName); 225 | } else { 226 | $keys = []; 227 | } 228 | 229 | if ($this->tablePrefix) { 230 | $tableName = substr($tableName, strlen($this->tablePrefix)); 231 | } 232 | $class .= Kernel::DEF_CLASS . Kernel::toUpper($tableName, Kernel::UDL) . Kernel::DEF_EXTENDS . 'TableModel' . Kernel::LB . Kernel::EOL; 233 | $sep = Kernel::BACKTICK . Kernel::COMMA . Kernel::SP . Kernel::BACKTICK; 234 | $class .= $this->generateTableModelProperty('selectFeild', Kernel::BACKTICK . join($sep, $cols['column']) . Kernel::BACKTICK); 235 | $class .= $this->generateTableModelProperty('tableName', $tableName); 236 | $class .= $this->generateTableModelProperty('columnList', $cols['column'], true); 237 | $class .= $this->generateTableModelProperty('keyName', $cols['key']); 238 | $class .= $this->generateTableModelProperty('ai', $cols['ai']); 239 | $class .= $this->generateTableModelProperty('unique', $cols['uni'], true); 240 | if ($keys) { 241 | $class .= $this->generateTableModelProperty('index', array_merge($keys['mulIndex']), true); 242 | $class .= $this->generateTableModelProperty('mulUnique', $keys['mulUni'], true); 243 | } 244 | $class .= $this->generateTableModelProperty('cols', $cols['columnInfo'], true); 245 | $class .= $this->generateTableConst('NAME', $tableName); 246 | $class .= $this->generateTableConst('KEY', $cols['key']); 247 | $class .= Kernel::RB . Kernel::EOL; 248 | } 249 | return $class; 250 | } 251 | 252 | public function generateTableModel() { 253 | $class = $this->tableModelString(); 254 | file_put_contents($this->tableModelCacheFile, $class); 255 | } 256 | 257 | protected function queryDBName() { 258 | $this->dbname = $this->query('SELECT database()')->fetchColumn(); 259 | } 260 | 261 | protected function generateTableConst($const, $expression) { 262 | return 'CONST ' . strtoupper($const) . Kernel::EQ . var_export($expression, true) . Kernel::SEMI . Kernel::EOL; 263 | } 264 | 265 | protected function generateTableModelProperty($varName, $expression, $trimeol = false) { 266 | $expressionStr = var_export($expression, true); 267 | if ($trimeol) { 268 | $expressionStr = str_replace(Kernel::EOL, Kernel::NOP, $expressionStr); 269 | } 270 | return Kernel::DEF_PROTECTED . Kernel::DOLAR . $varName . Kernel::EQ . $expressionStr . Kernel::SEMI . Kernel::EOL; 271 | } 272 | 273 | public function transaction(callable $queryCallabe, callable $afterRollback) { 274 | $this->beginTransaction(); 275 | try { 276 | $res = $queryCallabe(); 277 | $this->commit(); 278 | return $res; 279 | } catch (\Exception $e) { 280 | $this->rollBack(); 281 | return $afterRollback($e); 282 | } catch (\Error $e) { 283 | $this->rollBack(); 284 | return $afterRollback($e); 285 | } 286 | } 287 | 288 | public function beginWork() { 289 | return $this->query('EGIN WORK'); 290 | } 291 | 292 | public function commitWork() { 293 | return $this->query('COMMIT WORK'); 294 | } 295 | 296 | public function setAutocommit() { 297 | if (!$this->isConnectAutocommit()) { 298 | $this->sessionQueryAutocommitChanged = true; 299 | return $this->query("SET autocommit = 1"); 300 | } 301 | return true; 302 | } 303 | 304 | public function unAutocommit() { 305 | if ($this->isConnectAutocommit()) { 306 | $this->sessionQueryCommit = true; 307 | return $this->query("SET autocommit = 0"); 308 | } 309 | return true; 310 | } 311 | 312 | public function commit() { 313 | parent::commit(); 314 | if ($this->isConnectAutocommit()) { 315 | $status = $this->query('select @@session.autocommit')->fetchColumn(0); 316 | $newStatus = intval(!$status); 317 | $this->sessionQueryAutocommitChanged = false; 318 | if ($newStatus == $this->isConnectAutocommit()) { 319 | return $this->query("SET autocommit = $newStatus"); 320 | } 321 | } 322 | } 323 | 324 | } 325 | -------------------------------------------------------------------------------- /src/Lib/Model/Database/Expression.php: -------------------------------------------------------------------------------- 1 | operator = Kernel::SP . $operator . Kernel::SP; 31 | $this->expType = $expType; 32 | } 33 | 34 | public function left($v) { 35 | $this->left = $this->filterScalar($v); 36 | return $this; 37 | } 38 | 39 | public function right($v) { 40 | $this->right = $this->bindRight ? $v : $this->filterScalar($v); 41 | return $this; 42 | } 43 | 44 | public function bindRight() { 45 | $this->bindRight = true; 46 | } 47 | 48 | public function getOperator() { 49 | return $this->operator; 50 | } 51 | 52 | public function getExpression() { 53 | if (Kernel::isBitSet($this->expType, self::TYPE_LIKE_FUNC)) { 54 | $right = Kernel::LP . $this->right . Kernel::RP; 55 | } elseif (Kernel::isBitSet($this->expType, self::TYPE_SET_VALUE)) { 56 | return Kernel::SP . $this->left . $this->operator . $this->right . Kernel::SP; 57 | } else { 58 | $right = $this->right; 59 | } 60 | return Kernel::SP . Kernel::LP . $this->left . $this->operator . $right . Kernel::RP . Kernel::SP; 61 | } 62 | 63 | public function hasKey($set = null) { 64 | return $this->singleProperty('hasKey', $set); 65 | } 66 | 67 | public function hasUnique($set = null) { 68 | return $this->singleProperty('hasUnique', $set); 69 | } 70 | 71 | public function hasIndex($set = null) { 72 | return $this->singleProperty('hasIndex', $set); 73 | } 74 | 75 | protected function singleProperty($proper, $set = null) { 76 | if ($set === null) { 77 | return $this->$proper; 78 | } 79 | $this->$proper = $set; 80 | return $this->$proper; 81 | } 82 | 83 | protected function filterScalar($v) { 84 | if (!is_scalar($v)) { 85 | return $v; 86 | } 87 | return DB::instance()->quote($v); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/Lib/Model/Database/FunctionExpression.php: -------------------------------------------------------------------------------- 1 | func = $name; 22 | } 23 | 24 | public function arg($expression) { 25 | $this->arg[] = $this->filterScalar($expression); 26 | return $this; 27 | } 28 | 29 | public function args($args) { 30 | foreach ($args as $i => $arg) { 31 | $args[$i] = $this->filterScalar($arg); 32 | } 33 | $this->arg = array_merge($this->arg, $args); 34 | return $this; 35 | } 36 | 37 | public function push(...$args) { 38 | $this->args($args); 39 | return $this; 40 | } 41 | 42 | public function getExpression() { 43 | return Kernel::SP . $this->func . Kernel::LP . join(Kernel::COMMA, $this->arg) . Kernel::RP . Kernel::SP; 44 | } 45 | 46 | protected function filterScalar($v) { 47 | if (!is_scalar($v)) { 48 | return $v; 49 | } 50 | return DB::instance()->quote($v); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/Lib/Model/Database/LogicalExpression.php: -------------------------------------------------------------------------------- 1 | operator = Kernel::SP . $operator . Kernel::SP; 21 | } 22 | 23 | public function arg($expression) { 24 | $this->args[] = $expression; 25 | } 26 | 27 | public function args(array $args) { 28 | $this->args = array_merge($this->args, $args); 29 | } 30 | 31 | public function push(...$args) { 32 | $this->args = array_merge($this->args, $args); 33 | } 34 | 35 | public function getExpression() { 36 | usort($this->args, array($this, 'sortCall')); 37 | return Kernel::SP . Kernel::LP . join($this->operator, $this->args) . Kernel::RP . Kernel::SP; 38 | } 39 | 40 | public function sortCall($a, $b) { 41 | if (($res = $this->indexTypeCall($a, $b, 'hasKey')) !== null) { 42 | return $res; 43 | } elseif (($res = $this->indexTypeCall($a, $b, 'hasUnique')) !== null) { 44 | return $res; 45 | } elseif (($res = $this->indexTypeCall($a, $b, 'hasIndex')) !== null) { 46 | return $res; 47 | } 48 | return 0; 49 | } 50 | 51 | public function indexTypeCall($a, $b, $func) { 52 | if ($a->$func() && $b->$func()) { 53 | return 0; 54 | } elseif ($a->$func()) { 55 | return 1; 56 | } elseif ($b->$func()) { 57 | return -1; 58 | } 59 | return null; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/Lib/Model/Database/QueryBuild.php: -------------------------------------------------------------------------------- 1 | tableModel = $tableModel; 80 | $this->leftFeildsList = []; 81 | $this->joinTable = []; 82 | $this->group = []; 83 | $this->order = []; 84 | $this->valueData = []; 85 | $this->noBacktick = false; 86 | $this->multiTable = false; 87 | $this->forUpdate = false; 88 | $this->forSelect = false; 89 | } 90 | 91 | public function __get($name) { 92 | return $this->col($name); 93 | } 94 | 95 | public function clearInsertValue() { 96 | $this->valueData = []; 97 | } 98 | 99 | public function clearJoin() { 100 | $this->joinTable = []; 101 | } 102 | 103 | public function clearLeftFeilds() { 104 | $this->leftFeildsList = []; 105 | } 106 | 107 | public function __clone() { 108 | $this->cleanBindParameter(); 109 | } 110 | 111 | public function __destruct() { 112 | $this->cleanBindParameter(); 113 | } 114 | 115 | public function quote($value) { 116 | $this->tableModel->quote($value); 117 | } 118 | 119 | public function hasFeild($feild) { 120 | return in_array($feild, $this->tableModel->getColumns()); 121 | } 122 | 123 | public function key() { 124 | return $this->col($this->tableModel->getKey()); 125 | } 126 | 127 | public function col($name, $value = null) { 128 | if ($this->hasFeild($name)) { 129 | $col = new Column($name, $this->tableModel, $this, $this->tableModel->getCols()[$name]); 130 | if ($value !== null) { 131 | return $col->eq($value); 132 | } 133 | return $col; 134 | } else { 135 | throw new Exception("coulum $name not found in table $this->tableName"); 136 | } 137 | } 138 | 139 | public function bindParameterValue($name, $value, $isNum = false) { 140 | if (is_numeric($name)) { 141 | throw new Exception('bind param name can not numeric'); 142 | } 143 | self::$queryParameters[$name] = [$isNum, $value]; 144 | } 145 | 146 | public function bindValue($value) { 147 | self::$queryParameters[] = $value; 148 | } 149 | 150 | public function getParameterValue() { 151 | return self::$queryParameters; 152 | } 153 | 154 | public function bindColParameterValue(Column $col, $value) { 155 | $key = Kernel::COLON . $col->getName() . count(self::$queryParameters); 156 | self::$queryParameters[$key] = [$col->isNumber(), $value]; 157 | return $key; 158 | } 159 | 160 | public function cleanBindParameter() { 161 | self::$queryParameters = []; 162 | } 163 | 164 | public static function clearAllBindParameter() { 165 | self::$queryParameters = []; 166 | } 167 | 168 | /** 169 | * 170 | * @param mix $feild string of SQL expression, * , array, 171 | * instance of Column, 172 | * instance of FunctionExpression, 173 | * instance of Expression 174 | * @param \Toknot\Lib\Model\Database\TableModel $table 175 | * @return $this 176 | */ 177 | public function select($feild = Kernel::STAR, $table = null) { 178 | $table = $table === null ? $this->tableModel : $table; 179 | if ($feild === Kernel::STAR) { 180 | if ($this->multiTable) { 181 | $this->multiTableSelectFeild($table); 182 | } else { 183 | $this->leftFeildsList[] = $table->getSelectFeild(); 184 | } 185 | } elseif (is_string($feild)) { 186 | $this->leftFeildsList[] = $feild; 187 | } elseif (is_array($feild)) { 188 | foreach ($feild as $f) { 189 | if (is_string($f)) { 190 | $this->leftFeildsList[] = $table->query()->col($f); 191 | } else { 192 | $this->leftFeildsList[] = $f; 193 | } 194 | } 195 | } else { 196 | $this->leftFeildsList[] = $feild; 197 | } 198 | return $this; 199 | } 200 | 201 | public function forUpdate() { 202 | $this->forUpdate = true; 203 | return $this; 204 | } 205 | 206 | public function getSQL() { 207 | $sql = $this->buildQueryType(); 208 | 209 | if ($this->sqlType !== self::INSERT) { 210 | $sql .= $this->buildGroup(); 211 | $sql .= $this->buildOrder(); 212 | $sql .= $this->buildOffset(); 213 | } 214 | return $sql; 215 | } 216 | 217 | public function group($feild) { 218 | $this->group[$feild] = 1; 219 | return $this; 220 | } 221 | 222 | public function order($feild, $sort = self::SORT_ASC) { 223 | $this->order[$feild] = $sort; 224 | return $this; 225 | } 226 | 227 | public function offset($offset = 0) { 228 | $this->offset = $offset; 229 | return $this; 230 | } 231 | 232 | public function limit($step) { 233 | $this->step = $step; 234 | return $this; 235 | } 236 | 237 | public function onAnd(...$args) { 238 | return $this->logicalExpression(Kernel::L_AND, $args); 239 | } 240 | 241 | public function onOr(...$args) { 242 | return $this->logicalExpression(Kernel::L_OR, $args); 243 | } 244 | 245 | public function onXor(...$args) { 246 | return $this->logicalExpression(Kernel::L_XOR, $args); 247 | } 248 | 249 | public function eq($left = null, $right = null) { 250 | return $this->expression(Kernel::EQ, $left, $right); 251 | } 252 | 253 | public function lt($left = null, $right = null) { 254 | return $this->expression(Kernel::LT, $left, $right); 255 | } 256 | 257 | public function gt($left = null, $right = null) { 258 | return $this->expression(Kernel::GT, $left, $right); 259 | } 260 | 261 | public function le($left = null, $right = null) { 262 | return $this->expression(Kernel::LE, $left, $right); 263 | } 264 | 265 | public function ge($left = null, $right = null) { 266 | return $this->expression(Kernel::GE, $left, $right); 267 | } 268 | 269 | public function neq($left = null, $right = null) { 270 | return $this->expression(Kernel::NEQ, $left, $right); 271 | } 272 | 273 | public function lg($left = null, $right = null) { 274 | return $this->expression(Kernel::LG, $left, $right); 275 | } 276 | 277 | public function add($left = null, $right = null) { 278 | return $this->expression(Kernel::M_ADD, $left, $right); 279 | } 280 | 281 | public function sub($left = null, $right = null) { 282 | return $this->expression(Kernel::M_SUB, $left, $right); 283 | } 284 | 285 | public function mul($left = null, $right = null) { 286 | return $this->expression(Kernel::M_MUL, $left, $right); 287 | } 288 | 289 | public function div($left = null, $right = null) { 290 | return $this->expression(Kernel::M_DIV, $left, $right); 291 | } 292 | 293 | public function useIndex($indexs) { 294 | $this->useIndex = $this->func(self::U_INDEX, $indexs); 295 | return $this; 296 | } 297 | 298 | public function forceIndex($indexs) { 299 | $this->forceIndex = $this->func(self::F_INDEX, $indexs); 300 | return $this; 301 | } 302 | 303 | public function ignoreIndex($indexs) { 304 | $this->ignoreIndex = $this->func(self::F_INDEX, $indexs); 305 | return $this; 306 | } 307 | 308 | public function func($name, ...$args) { 309 | $exp = new FunctionExpression($name); 310 | $exp->args($args); 311 | return $exp; 312 | } 313 | 314 | public function join(TableModel $table, $type = self::C_JOIN, $on = '') { 315 | $this->multiTable = true; 316 | $this->joinTable[] = [$type, $table, $on]; 317 | return $this; 318 | } 319 | 320 | /** 321 | * 322 | * @param string|QueryExpression $condition 323 | * @param mixed $value 324 | * @return $this 325 | */ 326 | public function where($condition, $value = null) { 327 | if ($value === null && is_string($condition)) { 328 | $this->whereSQL = $condition; 329 | } elseif ($value !== null && is_string($condition)) { 330 | $col = $this->col($condition); 331 | $this->whereSQL = $col->eq($value); 332 | } elseif (is_subclass_of($condition, QueryExpression::class)) { 333 | $this->whereSQL = $condition->getExpression(); 334 | } elseif ($condition !== null) { 335 | Kernel::runtimeException('unsupport condition in QueryBuild::where()', E_USER_WARNING); 336 | } 337 | return $this; 338 | } 339 | 340 | public function range($start, $limit) { 341 | $this->offset = $start; 342 | $this->step = $limit; 343 | return $this; 344 | } 345 | 346 | public function asArray() { 347 | $this->queryResultStyle = PDO::FETCH_ASSOC; 348 | return $this; 349 | } 350 | 351 | public function asNum() { 352 | $this->queryResultStyle = PDO::FETCH_NUM; 353 | return $this; 354 | } 355 | 356 | /** 357 | * 358 | * @param int $style 359 | * @param mix $class 360 | * @param mix $args 361 | * @return $this 362 | */ 363 | public function resultStyle($style, $class = null, $args = []) { 364 | $this->queryResultStyle = $style; 365 | $this->queryResultClassObject = $class; 366 | $this->queryResultFetchArgument = $args; 367 | return $this; 368 | } 369 | 370 | public function findOne($id) { 371 | $this->sqlType = self::SELECT; 372 | $keyName = $this->tableModel->getKey(); 373 | if (!$keyName) { 374 | $unique = $this->tableModel->getUnique(); 375 | $type = is_numeric($id) ? Kernel::T_NUMBER : Kernel::T_STRING; 376 | if (!$unique) { 377 | Kernel::runtimeException('table ' . $this->tableModel->tableName() . ' not exits key', E_USER_WARNING); 378 | } 379 | foreach ($unique as $col) { 380 | if ($this->col($col)->isNumber() && $type == 'number') { 381 | $keyName = $col; 382 | } else { 383 | $keyName = $col; 384 | } 385 | } 386 | } 387 | $this->where($keyName, $id); 388 | $this->limit(1); 389 | return $this->row(); 390 | } 391 | 392 | public function count($feild = Kernel::STAR) { 393 | $this->sqlType = self::SELECT; 394 | $count = $this->func('COUNT', $feild); 395 | $this->select($count); 396 | $this->limit(1); 397 | return $this->tableModel->executeSelectOrUpdate($this)->fetchColumn(0); 398 | } 399 | 400 | public function sum($feild) { 401 | $this->sqlType = self::SELECT; 402 | $sum = $this->func('SUM', $feild); 403 | $this->select($sum); 404 | return $this->tableModel->executeSelectOrUpdate($this)->fetchColumn(0); 405 | } 406 | 407 | public function row() { 408 | $this->sqlType = self::SELECT; 409 | $sth = $this->tableModel->executeSelectOrUpdate($this); 410 | if ($this->queryResultClassObject === null) { 411 | $sth->setFetchMode($this->queryResultStyle); 412 | } elseif (is_int($this->queryResultClassObject) || is_object($this->queryResultClassObject)) { 413 | $sth->setFetchMode($this->queryResultStyle, $this->queryResultClassObject); 414 | } elseif (is_string($this->queryResultClassObject) && !is_numeric($this->queryResultClassObject)) { 415 | $sth->setFetchMode($this->queryResultStyle, $this->queryResultClassObject, $this->queryResultFetchArgument); 416 | } 417 | return $sth->fetch(); 418 | } 419 | 420 | public function column($idx) { 421 | $this->resultStyle(PDO::FETCH_COLUMN, null, $idx); 422 | return $this->all(); 423 | } 424 | 425 | public function all() { 426 | $this->sqlType = self::SELECT; 427 | $sth = $this->tableModel->executeSelectOrUpdate($this); 428 | if ($this->queryResultStyle === PDO::FETCH_COLUMN) { 429 | return $sth->fetchAll($this->queryResultStyle, $this->queryResultFetchArgument); 430 | } elseif ($this->queryResultStyle === PDO::FETCH_CLASS) { 431 | return $sth->fetchAll($this->queryResultStyle, $this->queryResultClassObject, $this->queryResultFetchArgument); 432 | } else { 433 | return $sth->fetchAll($this->queryResultStyle); 434 | } 435 | } 436 | 437 | public function update($param) { 438 | $this->sqlType = self::UPDATE; 439 | if (is_array($param)) { 440 | $this->setUpdate($param); 441 | } else { 442 | $this->leftFeildsList[] = $param; 443 | } 444 | $sth = $this->tableModel->executeSelectOrUpdate($this); 445 | return $sth->rowCount(); 446 | } 447 | 448 | public function delete() { 449 | $this->sqlType = self::DELETE; 450 | $sth = $this->tableModel->executeSelectOrUpdate($this); 451 | return $sth->rowCount(); 452 | } 453 | 454 | public function insert($firstRow, $multiValue = []) { 455 | $this->sqlType = self::INSERT; 456 | $this->setInsert($firstRow, $multiValue); 457 | $sth = $this->tableModel->executeSelectOrUpdate($this); 458 | return $sth->rowCount(); 459 | } 460 | 461 | public function addBacktick($feild) { 462 | $bt = $this->noBacktick ? Kernel::NOP : Kernel::BACKTICK; 463 | return $bt . $feild . $bt; 464 | } 465 | 466 | public function leftJoin($table, $on = '') { 467 | $this->join($table, self::L_JOIN, $on); 468 | return $this; 469 | } 470 | 471 | public function rightJoin($table, $on = '') { 472 | $this->join($table, self::R_JOIN, $on); 473 | return $this; 474 | } 475 | 476 | public function innerJoin($table, $on = '') { 477 | $this->join($table, self::I_JOIN, $on); 478 | return $this; 479 | } 480 | 481 | protected function buildOffset() { 482 | $res = Kernel::NOP; 483 | if ($this->step > 0) { 484 | $res .= self::LIMIT . $this->step; 485 | } 486 | if ($this->sqlType === self::SELECT) { 487 | $this->offset = $this->offset > 0 ? $this->offset : 0; 488 | $res .= self::OFFSET . $this->offset; 489 | } 490 | return $res; 491 | } 492 | 493 | protected function buildGroup() { 494 | if (!$this->group) { 495 | return Kernel::NOP; 496 | } 497 | return self::GROUP . Kernel::LP . $this->buildFeild(array_keys($this->group)) . Kernel::RP; 498 | } 499 | 500 | protected function buildOrder() { 501 | if (!$this->order) { 502 | return Kernel::NOP; 503 | } 504 | $orders = []; 505 | foreach ($this->order as $f => $s) { 506 | if ($f instanceof Column) { 507 | $orders[] = $f . $s; 508 | } else { 509 | $orders[] = $this->addBacktick($f) . $s; 510 | } 511 | } 512 | return self::ORDER . join(Kernel::COMMA, $orders); 513 | } 514 | 515 | protected function multiTableSelectFeild($table) { 516 | $sep = Kernel::COMMA . Kernel::SP . Kernel::BACKTICK . $table->getAlias() . Kernel::BACKTICK . Kernel::DOT; 517 | $this->leftFeildsList[] = join($sep, $table->getColumns()); 518 | } 519 | 520 | protected function buildJoin() { 521 | $sql = ''; 522 | foreach ($this->joinTable as list($type, $table, $on)) { 523 | $tableName = $table->tableName(); 524 | $sql .= $type . $tableName . self::TABLE_AS . $tableName; 525 | $sql .= self::JOIN_ON . $on; 526 | $this->multiTableSelectFeild($table); 527 | } 528 | return $sql; 529 | } 530 | 531 | protected function buildQueryType() { 532 | $tableName = $this->tableModel->tableName(); 533 | if ($this->multiTable) { 534 | $tableName .= $this->buildJoin(); 535 | } 536 | $where = Kernel::NOP; 537 | if ($this->whereSQL) { 538 | $where = self::WHERE . $this->whereSQL; 539 | } 540 | if ($this->sqlType == self::SELECT) { 541 | if (empty($this->leftFeildsList)) { 542 | $selectFeild = $this->tableModel->getSelectFeild(); 543 | } else { 544 | $selectFeild = join(Kernel::COMMA, $this->leftFeildsList); 545 | } 546 | $hitIndex = $this->forceIndex . $this->ignoreIndex . $this->useIndex; 547 | 548 | $sql = self::SELECT . $selectFeild . self::FORM . $tableName . $hitIndex . $where; 549 | if ($this->forUpdate) { 550 | $sql .= self::FOR_UPDATE; 551 | } 552 | } elseif ($this->sqlType == self::UPDATE) { 553 | $fields = implode(Kernel::COMMA, $this->leftFeildsList); 554 | $sql = self::UPDATE . $tableName . self::UPDATE_SET . $fields . $where; 555 | } elseif ($this->sqlType == self::INSERT) { 556 | $valueList = join(Kernel::COMMA, $this->valueData); 557 | $sql = self::INSERT . $tableName . $this->leftFeildsList . self::INSERT_VALUE . $valueList; 558 | } else if ($this->sqlType == self::DELETE) { 559 | $sql = self::DELETE . $tableName . $where; 560 | } 561 | return $sql; 562 | } 563 | 564 | protected function buildFeild($feilds) { 565 | $bt = $this->noBacktick ? Kernel::NOP : Kernel::BACKTICK; 566 | $sep = $bt . Kernel::COMMA . $bt; 567 | return $bt . join($sep, $feilds) . $bt; 568 | } 569 | 570 | protected function expression($operator, $left = null, $right = null) { 571 | $exp = new Expression($operator); 572 | if ($left !== null) { 573 | $exp->left($left); 574 | } 575 | if ($right !== null) { 576 | $exp->right($right); 577 | } 578 | return $exp; 579 | } 580 | 581 | protected function logicalExpression($operator, $args) { 582 | $exp = new LogicalExpression($operator); 583 | $exp->args($args); 584 | return $exp; 585 | } 586 | 587 | protected function setUpdate($param) { 588 | foreach ($param as $key => $v) { 589 | if (is_scalar($v)) { 590 | $n = $this->col($key); 591 | $this->leftFeildsList[] = $n->set($v); 592 | } else { 593 | $this->leftFeildsList[] = $v; 594 | } 595 | } 596 | } 597 | 598 | protected function setInsert($first, $data = []) { 599 | $keys = array_keys($first); 600 | if (!is_numeric(current($keys))) { 601 | reset($keys); 602 | $this->leftFeildsList = Kernel::LP . $this->buildFeild($keys) . Kernel::RP; 603 | } else { 604 | $this->leftFeildsList = Kernel::LP . $this->tableModel->getSelectFeild() . Kernel::RP; 605 | } 606 | $len = count($first); 607 | $valueList = Kernel::LP . join(Kernel::COMMA, array_fill(0, $len, Kernel::QUTM)) . Kernel::RP; 608 | $this->valueData[] = $valueList; 609 | self::$queryParameters = array_merge(self::$queryParameters, array_values($first)); 610 | 611 | foreach ($data as $v) { 612 | if (count($v) !== $len) { 613 | throw new Exception('insert multiple row must alignment of all row'); 614 | } 615 | $this->valueData[] = $valueList; 616 | self::$queryParameters = array_merge(self::$queryParameters, $v); 617 | } 618 | } 619 | 620 | } 621 | -------------------------------------------------------------------------------- /src/Lib/Model/Database/QueryExpression.php: -------------------------------------------------------------------------------- 1 | getExpression(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/Lib/Model/Database/TableModel.php: -------------------------------------------------------------------------------- 1 | db = DB::instance($dbkey); 44 | $this->query = null; 45 | } 46 | 47 | /** 48 | * 49 | * @return \Toknot\Lib\Model\Database\DB 50 | */ 51 | public function db() { 52 | return $this->db; 53 | } 54 | 55 | /** 56 | * 57 | * @return string 58 | */ 59 | public function getRawSql() { 60 | return $this->lastSql; 61 | } 62 | 63 | /** 64 | * 65 | * @return array 66 | */ 67 | public function getCols() { 68 | return $this->cols; 69 | } 70 | 71 | /** 72 | * 73 | * @return array 74 | */ 75 | public function getColumns() { 76 | return $this->columnList; 77 | } 78 | 79 | /** 80 | * 81 | * @return mixed 82 | */ 83 | public function getColumnDefault() { 84 | return $this->columnDefault; 85 | } 86 | 87 | /** 88 | * 89 | * @return bool 90 | */ 91 | public function isAutoIncrement() { 92 | return $this->ai; 93 | } 94 | 95 | /** 96 | * 97 | * @return string 98 | */ 99 | public function getSelectFeild() { 100 | return $this->selectFeild; 101 | } 102 | 103 | /** 104 | * 105 | * @return string 106 | */ 107 | public function getKey() { 108 | return $this->keyName; 109 | } 110 | 111 | /** 112 | * 113 | * @return array 114 | */ 115 | public function getUnique() { 116 | return $this->unique; 117 | } 118 | 119 | 120 | /** 121 | * 122 | * @return array 123 | */ 124 | public function getMulUnique() { 125 | return $this->mulUnique; 126 | } 127 | 128 | /** 129 | * 130 | * @return array 131 | */ 132 | public function getIndex() { 133 | return $this->index; 134 | } 135 | 136 | /** 137 | * 138 | * @return int 139 | */ 140 | public function lastInsertId() { 141 | return $this->db->lastInsertId(); 142 | } 143 | 144 | /** 145 | * 146 | * @return \PDOStatement 147 | */ 148 | public function executeSelectOrUpdate(QueryBuild $queryBuild) { 149 | $sql = $queryBuild->getSQL(); 150 | $sth = $this->db->prepare($sql); 151 | $bindParameter = $queryBuild->getParameterValue(); 152 | foreach ($bindParameter as $i => $bind) { 153 | if (is_array($bind)) { 154 | $param = $bind[1]; 155 | $isNum = $bind[0]; 156 | } else { 157 | $param = $bind; 158 | $isNum = false; 159 | } 160 | if (is_numeric($i)) { 161 | $sth->bindValue($i + 1, $param, DB::PARAM_STR); 162 | } else { 163 | $res = $sth->bindValue($i, $param, $isNum ? DB::PARAM_INT : DB::PARAM_STR); 164 | } 165 | } 166 | $res = $sth->execute(); 167 | if (!$res) { 168 | $errInfo = $sth->errorInfo(); 169 | $errData = [$errInfo[1], "SQLSTATE[$errInfo[0]] $errInfo[2]", $sql, $bindParameter]; 170 | $handle = new ErrorReportHandler($errData); 171 | return $handle->throwException(); 172 | } 173 | $queryBuild->cleanBindParameter(); 174 | return $sth; 175 | } 176 | 177 | /** 178 | * 179 | * @param string $name 180 | * @return int 181 | */ 182 | public function lastId($name = '') { 183 | return $this->db->lastInsertId($name); 184 | } 185 | 186 | /** 187 | * 188 | * @return \Toknot\Lib\Model\Database\QueryBuild 189 | */ 190 | public function query() { 191 | return new QueryBuild($this); 192 | } 193 | 194 | /** 195 | * 196 | * @return QueryBuild 197 | */ 198 | public function newQuery() { 199 | $query = new QueryBuild($this); 200 | $query->cleanBindParameter(); 201 | return $query; 202 | } 203 | 204 | public function endQuery() { 205 | QueryBuild::clearAllBindParameter(); 206 | } 207 | 208 | /** 209 | * 210 | * @return string 211 | */ 212 | public function tableName() { 213 | return $this->tableName; 214 | } 215 | 216 | public function quote($string) { 217 | $this->db->quote($string); 218 | } 219 | 220 | /** 221 | * 222 | * @return string 223 | */ 224 | public function getAlias() { 225 | return $this->alias; 226 | } 227 | 228 | /** 229 | * 230 | * @param string $alias 231 | */ 232 | public function setAlias($alias = '') { 233 | $this->alias = $alias ? $alias : $this->tableName; 234 | } 235 | 236 | /** 237 | * 238 | * @param mixed $where 239 | * @return $this 240 | */ 241 | public function where($where) { 242 | $this->where = $where; 243 | return $this; 244 | } 245 | 246 | /** 247 | * find one row 248 | * 249 | * @param mixed $id 250 | * @return array 251 | */ 252 | public function one($id) { 253 | $query = $this->query(); 254 | if (is_array($id)) { 255 | $and = $query->onAnd(); 256 | foreach ($id as $col => $v) { 257 | $and->arg($query->col($col)->eq($v)); 258 | } 259 | 260 | return $query->where($and)->limit(1)->row(); 261 | } 262 | return $query->findOne($id); 263 | } 264 | 265 | /** 266 | * one row ActiveRecord 267 | * 268 | * @param mixed $id 269 | * @return \Toknot\Lib\Model\Database\ActiveRecord 270 | */ 271 | public function findOne($id) { 272 | $list = $this->one($id); 273 | if (!$list) { 274 | return null; 275 | } 276 | return new ActiveRecord($this, $list); 277 | } 278 | 279 | /** 280 | * empty ActiveRecord 281 | * 282 | * @return \Toknot\Lib\Model\Database\ActiveRecord 283 | */ 284 | public function idler() { 285 | return new ActiveRecord($this, []); 286 | } 287 | 288 | /** 289 | * count all row number 290 | * 291 | * @param mix $where 292 | * @return int 293 | */ 294 | public function count($where = null) { 295 | $query = $this->query(); 296 | $query->where($where); 297 | return $query->count(); 298 | } 299 | 300 | /** 301 | * find all row 302 | * 303 | * @param int $limit 304 | * @param int $offset 305 | * @return array 306 | */ 307 | public function findAll($limit, $offset = 0) { 308 | $query = $this->query(); 309 | return $query->range($offset, $limit)->all(); 310 | } 311 | 312 | /** 313 | * find row by greater than id 314 | * 315 | * @param int $id 316 | * @param int $limit 317 | * @param int $offset 318 | * @return array 319 | */ 320 | public function findGTId($id, $limit, $offset = 0) { 321 | $query = $this->query(); 322 | $exp = $query->col($this->keyName)->gt($id); 323 | return $query->where($exp)->range($offset, $limit)->all(); 324 | } 325 | 326 | /** 327 | * find row by less than id 328 | * 329 | * @param int $id 330 | * @param int $limit 331 | * @param int $offset 332 | * @return array 333 | */ 334 | public function findLTId($id, $limit, $offset = 0) { 335 | $query = $this->query(); 336 | $exp = $query->col($this->keyName)->lt($id); 337 | return $query->where($exp)->range($offset, $limit)->all(); 338 | } 339 | 340 | /** 341 | * update by id when cas version not change 342 | * 343 | * @param array $param 344 | * @param int $id 345 | * @param int $casValue 346 | * @param int $newValue 347 | * @return int 348 | */ 349 | public function casUpdateById(array $param, $id, $casValue, $newValue) { 350 | $query = $this->query(); 351 | $exp1 = $query->col($this->keyName)->eq($id); 352 | $exp2 = $query->col($this->casVerCol)->eq($casValue); 353 | $exp = $query->onAnd($exp1, $exp2); 354 | $param[$this->casVerCol] = $newValue; 355 | return $query->where($exp)->range(0, 1)->update($param); 356 | } 357 | 358 | /** 359 | * update by id 360 | * 361 | * @param array $param 362 | * @param int $id 363 | * @param int $where 364 | * @return int 365 | */ 366 | public function updateById(array $param, $id, $where = '') { 367 | $query = $this->query(); 368 | $exp1 = $query->col($this->keyName)->eq($id); 369 | $filter = $exp1; 370 | if ($where) { 371 | $and = $query->onAnd(); 372 | $and->arg($exp1); 373 | $and->arg($where); 374 | $filter = $and; 375 | } 376 | return $query->where($filter)->range(0, 1)->update($param); 377 | } 378 | 379 | /** 380 | * delete row by id 381 | * 382 | * @param int $id 383 | * @return int 384 | */ 385 | public function deleteById($id) { 386 | $query = $this->query(); 387 | $exp = $query->key()->eq($id); 388 | return $query->where($exp)->range(0, 1)->delete(); 389 | } 390 | 391 | /** 392 | * update a feild value by id 393 | * 394 | * @param int $id 395 | * @param string $setCol 396 | * @param mixed $set 397 | */ 398 | public function setById($id, $setCol, $set) { 399 | $query = $this->query(); 400 | $exp1 = $query->key()->eq($id); 401 | $exp2 = $query->col($setCol)->eq($set); 402 | $query->where($exp1)->range(0, 1)->update($exp2); 403 | } 404 | 405 | /** 406 | * multitple table query,default is left join query 407 | * 408 | * @param array $table 409 | * @param int $limit 410 | * @param int $offset 411 | * @return array 412 | */ 413 | public function multiAll(array $table, $limit, $offset = 0) { 414 | $this->setAlias(); 415 | $selfQuery = $this->query(); 416 | if (is_array($table)) { 417 | foreach ($table as $type => $t) { 418 | if (is_numeric($type)) { 419 | $this->multiJoin($t, $selfQuery); 420 | } else { 421 | $this->multiJoin($t, $selfQuery, $type); 422 | } 423 | } 424 | } else { 425 | $this->multiJoin($table, $selfQuery); 426 | } 427 | return $selfQuery->range($offset, $limit)->all(); 428 | } 429 | 430 | /** 431 | * insert data 432 | * 433 | * @param array $param the first row data, if not key with query column name will use table defined feild 434 | * @param array $otherData the other row data, must align with first param 435 | * @return int 436 | */ 437 | public function insert($param, $otherData = []) { 438 | return $this->query()->insert($param, $otherData); 439 | } 440 | 441 | /** 442 | * save data, if has exists of key will update, otherwise insert data 443 | * 444 | * @param array $param save data, one value is pairs key/value map column-name/value 445 | * @param bool $autoUpate whether auto update, if true will check unique key whether seted and exec update 446 | * if false only check parimary key 447 | * @param int $casVer current cas ver 448 | * @param int $newVer update new cas 449 | * @return int 450 | */ 451 | public function save(array $param, $autoUpate = true, $casVer = 0, $newVer = 0) { 452 | $keys = array_keys($param); 453 | if (isset($param[$this->keyName]) && ($this->ai || $autoUpate)) { 454 | $idValue = Kernel::pullOut($param, $this->keyName); 455 | if ($newVer && $casVer) { 456 | $this->casUpdateById($param, $idValue, $casVer, $newVer); 457 | } else { 458 | $this->updateById($param, $idValue); 459 | } 460 | } elseif ($autoUpate && ($ainter = array_intersect($keys, $this->unique))) { 461 | $query = $this->query(); 462 | $and = $query->onAnd(); 463 | foreach ($ainter as $col => $value) { 464 | $exp = $query->col($col)->eq($value); 465 | $and->arg($exp); 466 | unset($param[$col]); 467 | } 468 | if ($casVer && $newVer) { 469 | $and->arg($this->casExpression($query, $casVer)); 470 | $param[$this->casVerCol] = $newVer; 471 | } 472 | return $query->where($and)->range(0, 1)->update($param); 473 | } else { 474 | if ($newVer) { 475 | $param[$this->casVerCol] = $newVer; 476 | } 477 | return $this->insert($param); 478 | } 479 | } 480 | 481 | /** 482 | * 483 | * @param \Toknot\Lib\Model\Database\QueryBuild $query 484 | * @param int $casVer 485 | * @return \Toknot\Lib\Model\Database\Expression 486 | */ 487 | public function casExpression(QueryBuild $query, $casVer) { 488 | return $query->col($this->casVerCol)->eq($casVer); 489 | } 490 | 491 | /** 492 | * 493 | * @param \Toknot\Lib\Model\Database\TableModel $table 494 | * @param QueryBuild $selfQuery 495 | * @param string $type 496 | */ 497 | protected function multiJoin(TableModel $table, QueryBuild $selfQuery, $type = QueryBuild::L_JOIN) { 498 | $table->setAlias(); 499 | $exp = $table->query()->key()->eq($selfQuery->key()); 500 | if ($type === QueryBuild::C_JOIN) { 501 | $selfQuery->join($table, $exp); 502 | } elseif ($type === QueryBuild::R_JOIN) { 503 | $selfQuery->rightJoin($table, $exp); 504 | } elseif ($type === QueryBuild::I_JOIN) { 505 | $selfQuery->innerJoin($table, $exp); 506 | } else { 507 | $selfQuery->leftJoin($table, $type, $exp); 508 | } 509 | } 510 | 511 | } 512 | -------------------------------------------------------------------------------- /src/Lib/Model/Permission/Permission.php: -------------------------------------------------------------------------------- 1 | self::P_S . self::UNLIMIT, 1 => self::P_S . self::VIEW, 50 | 2 => self::P_S . self::UPDATE, 3 => self::P_S . self::CREATE, 4 => self::P_S . self::DELETE, 51 | 5 => self::P_H . self::VIEW, 6 => self::P_H . self::UPDATE, 52 | 7 => self::P_H . self::CREATE, 8 => self::P_H . self::DELETE,]; 53 | 54 | abstract public static function addRole($data); 55 | 56 | public function toArray() { 57 | return ['id' => $this->id, 'token' => $this->token, 'perm' => $this->allPermission()]; 58 | } 59 | 60 | public function getId() { 61 | return $this->id; 62 | } 63 | 64 | public function getToken() { 65 | return $this->token; 66 | } 67 | 68 | public function push(&$allRole) { 69 | $allRole[$this->id] = $this; 70 | } 71 | 72 | public function allPermission() { 73 | $perm = []; 74 | foreach (self::PERM_MAP as $id => $name) { 75 | $perm[$id] = $this->$name; 76 | } 77 | return $perm; 78 | } 79 | 80 | public function setUnlimit() { 81 | $this->selfUnlimit = true; 82 | } 83 | 84 | public function add(Role $role, $action) { 85 | if ($role->isUnlmit()) { 86 | return true; 87 | } 88 | $key = ''; 89 | $proper = $this->accessProper($action, null, $key); 90 | $this->$key = Permission::set($proper, $role->getPermission($action)); 91 | return $this->$key; 92 | } 93 | 94 | public function remove(Role $role, $action) { 95 | $key = ''; 96 | $proper = $this->accessProper($action, null, $key); 97 | $this->$key = Permission::remove($proper, $role->getPermission($action)); 98 | return $this->$key; 99 | } 100 | 101 | public function permission(Role $role, $action) { 102 | if ($this->selfUnlimit) { 103 | return true; 104 | } 105 | return Permission::has($role->getPermission($action + 4), $this->accessProper($action)); 106 | } 107 | 108 | public function hasPermission(Role $role, $action) { 109 | if ($role->isUnlmit()) { 110 | return true; 111 | } 112 | return Permission::has($this->accessProper($action + 4), $role->getPermission($action)); 113 | } 114 | 115 | public function getPermission($action) { 116 | return $this->accessProper($action); 117 | } 118 | 119 | public function isUnlmit() { 120 | return $this->selfUnlimit; 121 | } 122 | 123 | public function initPermission($id, $token = '', $permission = []) { 124 | $this->id = $id; 125 | $this->token = $token; 126 | foreach ($permission as $id => $p) { 127 | if ($id == self::S_UNLIMIT) { 128 | $this->selfUnlimit = $p; 129 | } else if (!empty(self::PERM_MAP[$id])) { 130 | $this->accessProper($id, $p); 131 | } 132 | } 133 | } 134 | 135 | protected function accessProper($action, $value = null, &$key = '') { 136 | if ($action < 1) { 137 | Kernel::runtimeException('unknow action'); 138 | } 139 | $proper = self::PERM_MAP[$action]; 140 | $key = $proper; 141 | if ($value !== null) { 142 | $this->$proper = $value; 143 | return $this->$proper; 144 | } else { 145 | return $this->$proper; 146 | } 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /src/Lib/Model/Permission/Root.php: -------------------------------------------------------------------------------- 1 | 为占位符号名,可将块模板文件或模板代码插入此位置,通过view插入或在模板内使用 @insert 标签 17 | * @& 为别名符号,续在view中注册关联的标签 18 | * @insert @ 将插入指定的位置,其中@符号为结束符号 19 | * @load 将模板导入当前位置 20 | * @widget 将模板导入当前位置,但是导入的模板内部变量与外部隔离,通过可向模板传递参数 21 | * @if 与 PHP if 表达式类似 22 | * @else if/elseif 与 PHP elseif/else if 类似 23 | * @else 等效 PHP else 24 | * @end 等效 PHP 右花括号 } 25 | * @iter 等效 PHP foreach 26 | * @def 定义变量,符合PHP规则 27 | * @route 输出路由表示的 URL 地址 28 | * @$ 输出变量 29 | * @() 输出函数返回值 30 | * 31 | */ 32 | class Compile { 33 | 34 | private $tplFile = ''; 35 | private $content = ''; 36 | private $cacheFile = ''; 37 | private $varName = ''; 38 | private $localName = ''; 39 | public static $LT = '`'; 40 | public static $RT = '`'; 41 | protected $exclude = ''; 42 | private $pageHoldTag = []; 43 | private $viewTag = []; 44 | private $hasLoadTag = false; 45 | private $insertTpl = []; 46 | private $widgetCount = 0; 47 | 48 | /** 49 | * 50 | * @var \Toknot\App\View\View 51 | */ 52 | public $view = null; 53 | 54 | public function __construct($tplFile, $cacheFile) { 55 | $this->tplFile = $tplFile; 56 | $this->cacheFile = $cacheFile; 57 | $this->varName = '_tpl_' . substr($this->cacheFile, -10, 5); 58 | $this->exclude = '[^' . self::$RT . ']+'; 59 | } 60 | 61 | /** 62 | * get current view complie content 63 | */ 64 | public function getContent() { 65 | return $this->content; 66 | } 67 | 68 | /** 69 | * convert controller action to url 70 | * 71 | * @param string $route 72 | * @param array $params 73 | */ 74 | public function route2Url($route, $params = []) { 75 | return $this->view->route2Url($route, $params); 76 | } 77 | 78 | /** 79 | * load view build cache file 80 | * 81 | * @param ViewData $data view data 82 | */ 83 | public function load($data) { 84 | $name = $this->varName; 85 | $$name = $data; 86 | include $this->cacheFile; 87 | } 88 | 89 | /** 90 | * render view 91 | */ 92 | public function render() { 93 | $this->hasLoadTag = false; 94 | $this->content = file_get_contents($this->tplFile); 95 | $this->viewTag = $this->view->getTags(); 96 | $this->insertTpl = $this->view->getInsertTemplate(); 97 | $this->loadTemplate(); 98 | 99 | while ($this->linkTag()) { 100 | $this->loadTemplate(); 101 | } 102 | 103 | $this->tplPageInsert(); 104 | $this->complieControl(); 105 | $this->clean(); 106 | if ($this->cacheFile) { 107 | $this->save(); 108 | } 109 | } 110 | 111 | protected function clean() { 112 | $pageHoldTag = array_keys($this->pageHoldTag); 113 | $this->content = str_replace($pageHoldTag, '', $this->content); 114 | } 115 | 116 | /** 117 | * view tag 118 | */ 119 | protected function linkTag() { 120 | //`*TAG` 121 | $this->hasLoadTag = false; 122 | $this->replace('\&([a-z-0-9_]+)', [$this, 'replaceLinkTag']); 123 | return $this->hasLoadTag; 124 | } 125 | 126 | public function replaceLinkTag($m) { 127 | $tag = $m[1]; 128 | if (!isset($this->viewTag[$tag])) { 129 | return Kernel::NOP; 130 | } 131 | $exp = $this->viewTag[$tag]; 132 | if (strpos($exp, ':') === 0) { 133 | $this->hasLoadTag = true; 134 | } elseif (strpos($exp, 'load ') === 0) { 135 | $this->hasLoadTag = true; 136 | } 137 | return self::$LT . $exp . self::$RT; 138 | } 139 | 140 | protected function holdTag($tag) { 141 | $holdTag = "|~!--/*###$tag*/###--~|"; 142 | $this->pageHoldTag[$holdTag] = 1; 143 | return $holdTag; 144 | } 145 | 146 | protected function tplPageInsert() { 147 | $pageInsert = []; 148 | $this->replace('insert\s+(' . $this->exclude . ')@', function($m) use(&$pageInsert) { 149 | list($tag, $code) = explode(Kernel::SP, trim($m[1]), 2); 150 | if (!isset($pageInsert[$tag])) { 151 | $pageInsert[$tag] = Kernel::NOP; 152 | } 153 | $pageInsert[$tag] .= $code; 154 | return Kernel::NOP; 155 | }); 156 | foreach ($pageInsert as $tag => $code) { 157 | $this->content = str_replace($this->holdTag($tag), $code . $this->holdTag($tag), $this->content); 158 | } 159 | } 160 | 161 | public function tagBlock($m) { 162 | $tag = $m[1]; 163 | if (!isset($this->insertTpl[$tag])) { 164 | return $this->holdTag($tag); 165 | } 166 | $tpl = ''; 167 | foreach ($this->insertTpl[$tag] as $tplPath) { 168 | $tpl .= file_get_contents($tplPath); 169 | } 170 | return $tpl . $this->holdTag($tag); 171 | } 172 | 173 | public function tagLoad($m) { 174 | $path = $this->view->getTplRealpath($m[1]); 175 | return file_get_contents($path); 176 | } 177 | 178 | public function tagWidget($m) { 179 | $params = explode(Kernel::SP, $m[1]); 180 | $tpl = array_shift($params); 181 | if (empty($tpl)) { 182 | return Kernel::NOP; 183 | } 184 | $localVar = ''; 185 | $paramsArr = $funcArr = []; 186 | foreach ($params as $arg) { 187 | $arg = trim($arg); 188 | list($vn, $v) = explode(Kernel::EQ, $arg); 189 | $v = trim($v); 190 | $vn = trim($vn); 191 | if (strpos($v, '$') === 0) { 192 | $paramsArr[] = $this->replaceVar($v); 193 | $localVar .= "\${$this->varName}->{$vn} = \${$vn};"; 194 | $funcArr[] = "\${$vn}"; 195 | } else { 196 | $localVar .= "\${$this->varName}->{$vn}='$vn';"; 197 | } 198 | } 199 | 200 | $path = $this->view->getTplRealpath($tpl); 201 | $function = '_widget' . $this->widgetCount . '_' . md5($path . microtime())[1]; 202 | $this->widgetCount++; 203 | $funcStr = implode(',', $funcArr); 204 | $callParam = implode(',', $paramsArr); 205 | $vcls = ViewData::class; 206 | return $this->t("if(!isset(\${$function})){\${$function} = function($funcStr){ \${$this->varName}= new $vcls; $localVar") 207 | . file_get_contents($path) . $this->t("};} \${$function}($callParam);"); 208 | } 209 | 210 | protected function loadTemplate() { 211 | //`:TAG` 将块文件插入此位置 212 | $this->replace(':([a-z-0-9_]+)', [$this, 'tagBlock']); 213 | 214 | //`load tpl/path` 在标签位置载入文件 215 | $this->replace('load\s+(' . $this->exclude . ')', [$this, 'tagLoad']); 216 | 217 | //`widget tpl arg='value' arg1=$var` 封闭区域 218 | $this->replace('widget\s+(' . $this->exclude . ')', [$this, 'tagWidget']); 219 | } 220 | 221 | private function t($str) { 222 | return ""; 223 | } 224 | 225 | protected function complieControl() { 226 | 227 | //control structure expression must start space or line begin and until line end 228 | //`if $a>2` 229 | $this->replace('if\s+(' . $this->exclude . ')', function($m) { 230 | return $this->t('if(' . $this->replaceVar($m[1]) . ') {'); 231 | }); 232 | //`elseif $ab > 2` 233 | $this->replace('else\s*if\s+(' . $this->exclude . ')', function($m) { 234 | return $this->t('}elseif(' . $this->replaceVar($m[1]) . ') {'); 235 | }); 236 | //`else` 237 | $this->replace('else', '$1'); 238 | //`end` 239 | $this->replace('end', $this->t('}')); 240 | //`iter $a as $b=>$c` 241 | $this->replace('iter\s+(' . $this->exclude . ')', function($m) { 242 | $arrValue = $this->replaceVar($m[1]); 243 | return $this->t("foreach($arrValue) {"); 244 | }); 245 | //`def $b='c'` 246 | $this->replace('def\s+(' . $this->exclude . ')', function($m) { 247 | $exp = $this->replaceVar($m[1]); 248 | return $this->t($exp); 249 | }); 250 | //`route login@action params` 251 | $this->replace('route\s+([a-z0-9@\-\\\]+)(|\s+' . $this->exclude . ')', function($m) { 252 | $param = $m[2] ? ',' . $this->replaceVar($m[2]) : ''; 253 | return 'route2Url(' . Kernel::QUOTE . $m[1] . Kernel::QUOTE . $param . ')?>'; 254 | }); 255 | //`$aa` echo $aa 256 | $this->replace('\$(' . $this->exclude . ')', "varName}->\$1?>"); 257 | //`function()` 258 | $this->replace('([a-z_][a-z0-9_]*\()([^\)]*)\)', function($m) { 259 | return "replaceVar($m[2]) . ')?>'; 260 | }); 261 | } 262 | 263 | protected function replace($regContent, $replace) { 264 | $reg = '/' . self::$LT . $regContent . self::$RT . '/im'; 265 | if (is_callable($replace)) { 266 | $this->content = preg_replace_callback($reg, $replace, $this->content); 267 | } else { 268 | $this->content = preg_replace($reg, $replace, $this->content); 269 | } 270 | } 271 | 272 | protected function replaceVar($m) { 273 | return preg_replace('/\$([_a-z][a-z0-9_]*)/i', "\${$this->varName}->\$1", $m); 274 | } 275 | 276 | protected function save() { 277 | file_put_contents($this->cacheFile, $this->content); 278 | } 279 | 280 | } 281 | -------------------------------------------------------------------------------- /src/Lib/View/Control.php: -------------------------------------------------------------------------------- 1 | ') : ''; 15 | $dataName = ltrim($dataName, '$'); 16 | $str = '$tpl"; 17 | return $str; 18 | } 19 | 20 | public static function ifexp($tpl, $exp) { 21 | $str = "$tpl"; 22 | return $str; 23 | } 24 | 25 | public static function ifelse($explist, $elseTpl = '') { 26 | $str = ''; 27 | $i = 0; 28 | foreach ($explist as $exp => $tpl) { 29 | $elseif = $i > 0 ? '} else' : ''; 30 | $str .= "$tpl"; 31 | $i++; 32 | } 33 | if ($elseTpl) { 34 | $str .= "$elseTpl"; 35 | } 36 | return $str; 37 | } 38 | 39 | public static function hasArrVar($arrName, $keyName) { 40 | return 'isset(' . self::arrVarExp($arrName, $keyName) . ')'; 41 | } 42 | 43 | public static function notHasArrVar($arrName, $keyName) { 44 | return '!' . self::hasArrVar($arrName, $keyName); 45 | } 46 | 47 | public static function echoVar($varName) { 48 | return ''; 49 | } 50 | 51 | public static function echoArrValue($arrName, ...$keyName) { 52 | array_unshift($keyName, $arrName); 53 | return ''; 54 | } 55 | 56 | public static function arrVarExp($arrName, ...$keyName) { 57 | return "\${$arrName}['" . join('\'][\'', $keyName) . '\']'; 58 | } 59 | 60 | public static function arrKeyVarExp($arrName, $keyVarName) { 61 | return "\${$arrName}[\${$keyVarName}]"; 62 | } 63 | 64 | public static function varExp($varName) { 65 | return '$' . $varName; 66 | } 67 | 68 | public static function scopeVarName($varName) { 69 | return $varName . '_' . md5($varName . microtime() . mt_rand(10000, 99999)); 70 | } 71 | 72 | public static function setVar($varNameTpl, $valueTpl) { 73 | return ""; 74 | } 75 | 76 | public static function callFunc($function, ...$args) { 77 | return "$function(" . join(',', $args) . ")"; 78 | } 79 | 80 | public static function arrayElementExp($key, $var) { 81 | return "$key => $var,"; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/Lib/View/Element.php: -------------------------------------------------------------------------------- 1 | "; 20 | 21 | $itemName = 'tValue'; 22 | if ($local) { 23 | $itemName = Control::scopeVarName($itemName); 24 | } 25 | 26 | $dataTr = ''; 27 | foreach ($title as $k => $v) { 28 | $html .= "$v"; 29 | $dataTr .= '' . Control::arrVarExp($itemName, $k) . ''; 30 | } 31 | $html .= ''; 32 | 33 | $html .= Control::iterator($dataName, $dataTr, $itemName); 34 | $html .= ''; 35 | return $html; 36 | } 37 | 38 | public static function tag($tagName, $attr = '') { 39 | return "<{$tagName}{$attr}>"; 40 | } 41 | 42 | public static function endTag($tag) { 43 | return ""; 44 | } 45 | 46 | public static function liList($dataName, $attr = '') { 47 | if (is_array($attr)) { 48 | $attr = self::batchSetTagAttr($attr); 49 | } 50 | 51 | $html = self::tag('ul', $attr); 52 | $inVar = Control::scopeVarName('lValue'); 53 | 54 | $atag = ''; 55 | $ifa = Control::ifexp(Control::hasArrVar($inVar, 'href'), $atag); 56 | 57 | $ifa2 = Control::ifexp(Control::hasArrVar($inVar, 'href'), ''); 58 | 59 | $li = self::tag('li') . $ifa . self::echoArrValue($inVar, 'title') . $ifa2 . self::endTag('li'); 60 | 61 | $html .= Control::iterator($dataName, $li, $inVar); 62 | $html .= self::endTag('ul'); 63 | return $html; 64 | } 65 | 66 | /** 67 | * type, div-attr,input-attr,option, label 68 | * 69 | * @param string $dataName 70 | * @param array $attr 71 | */ 72 | public static function form($dataName, $attr = '') { 73 | if (is_array($attr)) { 74 | $attr = self::batchSetTagAttr($attr); 75 | } 76 | 77 | $html = self::tag('form', $attr); 78 | $inVar = Control::scopeVarName('formInputItem'); 79 | 80 | $attrVar = Control::scopeVarName('formInputItemAttr'); 81 | $attrVarKey = Control::scopeVarName('formInputItemAttrKey'); 82 | $inputOptionVar = Control::scopeVarName('inputOptionVar'); 83 | $inputOptionVarKey = Control::scopeVarName('inputOptionVarKey'); 84 | $merge = Control::scopeVarName('merge'); 85 | 86 | $expList = []; 87 | 88 | $typeExp = Control::arrVarExp($inVar, 'type'); 89 | 90 | $labelForExp = Control::ifexp(' for="' . Control::echoArrValue($inVar, 'id') . '"', Control::hasArrVar($inVar, 'id')); 91 | 92 | $labelExp = Control::ifexp("" . Control::echoArrValue($inVar, 'label') . '', Control::hasArrVar($inVar, 'label')); 93 | 94 | $singleAttr = self::echoTagAttr($attrVarKey, $attrVar); 95 | $pushAttr = '['. Control::arrayElementExp('type', Control::arrVarExp($inVar, 'type')) . Control::arrayElementExp('id', Control::arrVarExp($inVar, 'id')) . ']'; 96 | $checkAttrArr = Control::ifexp('', Control::notHasArrVar($inVar, 'input-attr')); 97 | 98 | $mergeTpl = $checkAttrArr . Control::setVar($merge, Control::callFunc('array_merge', Control::arrVarExp($inVar, 'input-attr'), $pushAttr)); 99 | 100 | $inputAttr = $mergeTpl . Control::iterator($merge, $singleAttr, $attrVar, $attrVarKey); 101 | //$inputAttr = Control::ifexp($inputAttr, Control::hasArrVar($inVar, 'input-attr')); 102 | $selected = Control::ifexp(' selected', Control::varExp($inputOptionVar) . '==' . Control::arrVarExp($inVar, 'input-attr', 'value')); 103 | $optionTpl = ''; 104 | $options = Control::iterator(Control::arrVarExp($inVar, 'option'), $optionTpl, $inputOptionVar, $inputOptionVarKey); 105 | 106 | $selectType = "$typeExp=='select'"; 107 | $selectTpl = "
$labelExp$options
"; 108 | $expList[$selectType] = $selectTpl; 109 | $textareaType = "$typeExp=='textarea'"; 110 | $textareaTpl = "
$labelExp
"; 111 | $expList[$textareaType] = $textareaTpl; 112 | 113 | $buttonType = "$typeExp=='button'"; 114 | $buttonTpl = "" . Control::echoArrValue($inVar, 'input-attr', 'value') . ""; 115 | $expList[$buttonType] = $buttonTpl; 116 | 117 | 118 | $inputTpl = "
$labelExp
"; 119 | 120 | $input = Control::ifelse($expList, $inputTpl); 121 | 122 | $html .= Control::iterator($dataName, $input, $inVar); 123 | $html .= ''; 124 | return $html; 125 | } 126 | 127 | public static function echoTagAttrArr($arrName, $keyName) { 128 | $arr = Control::hasArrVar($arrName, $keyName); 129 | $tpl = self::setTagAttr($keyName, Control::echoArrValue($arrName, $keyName)); 130 | return Control::ifexp($tpl, $arr); 131 | } 132 | 133 | public static function echoTagAttr($varName, $valueName) { 134 | return self::setTagAttr(Control::echoVar($varName), Control::echoVar($valueName)); 135 | } 136 | 137 | public static function setTagAttr($attrName, $value) { 138 | return Kernel::SP . $attrName . '="' . $value . '"'; 139 | } 140 | 141 | public static function batchSetTagAttr($arr) { 142 | $attr = Kernel::NOP; 143 | foreach ($arr as $k => $v) { 144 | $attr .= self::setTagAttr($k, $v); 145 | } 146 | return $attr; 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /src/Lib/View/View.php: -------------------------------------------------------------------------------- 1 | kernel = Kernel::instance(); 33 | $this->config = $this->kernel->config(); 34 | $this->initCacheConfig(); 35 | $this->controller = $controller; 36 | $this->tplData = new ViewData; 37 | $this->viewCacheDir = $controller->getViewCachePath(); 38 | } 39 | 40 | public function newViewData() { 41 | return new ViewData; 42 | } 43 | 44 | protected function initCacheConfig() { 45 | if (!isset($this->config->view->cache)) { 46 | return; 47 | } 48 | $cacheConfig = $this->config->view->cache; 49 | if (isset($cacheConfig->disable)) { 50 | $this->disableCache = $cacheConfig->disable; 51 | } 52 | if (isset($cacheConfig->autoupdate)) { 53 | $this->autoUpdateRender = $cacheConfig->autoupdate; 54 | } 55 | } 56 | 57 | public function getViewNS() { 58 | return $this->kernel->appViewNs(); 59 | } 60 | 61 | public function getViewPath() { 62 | return $this->kernel->getToknotClassPath($this->kernel->appViewNs(), false); 63 | } 64 | 65 | public function setViewId($viewId) { 66 | $this->viewId = $viewId; 67 | } 68 | 69 | public function setParams($param, $value) { 70 | $this->tplData->add($param, $value); 71 | } 72 | 73 | public function template($tpl) { 74 | $this->viewTpl = $tpl; 75 | } 76 | 77 | public function getTemplate() { 78 | return $this->viewTpl; 79 | } 80 | 81 | public function getTplRealpath($tpl) { 82 | return realpath($tpl); 83 | } 84 | 85 | public function insertTemplate($tpl, $tag) { 86 | if (isset($this->insert[$tag])) { 87 | $this->insert[$tag][] = $this->getTplRealpath($tpl); 88 | } else { 89 | $this->insert[$tag] = [$this->getTplRealpath($tpl)]; 90 | } 91 | } 92 | 93 | public function getInsertTemplate() { 94 | return $this->insert; 95 | } 96 | 97 | public function defineTag($tag, $exp) { 98 | $this->tagList[$tag] = $exp; 99 | } 100 | 101 | public function getTags() { 102 | return $this->tagList; 103 | } 104 | 105 | protected function gCacheTplName() { 106 | $filename = str_replace(Kernel::NS, Kernel::HZL, $this->controller->getViewId()); 107 | $filename .= Kernel::HZL . md5(get_called_class()); 108 | $filename .= Kernel::PHP_EXT; 109 | $filename = str_replace(DIRECTORY_SEPARATOR, '-', $filename); 110 | $this->viewTplCacheFile = $this->viewCacheDir . DIRECTORY_SEPARATOR . $filename; 111 | } 112 | 113 | public function output() { 114 | if (!$this->viewTpl) { 115 | return Kernel::NOP; 116 | } 117 | $this->gCacheTplName(); 118 | $complie = new Compile($this->viewTpl, $this->viewTplCacheFile); 119 | $complie->view = $this; 120 | if (($this->autoUpdateRender && $this->checkCache()) || $this->disableCache) { 121 | $complie->render(); 122 | } 123 | $complie->load($this->tplData); 124 | } 125 | 126 | /** 127 | * controller action to url 128 | * 129 | * @param string $route route 130 | * @param array $params url query params 131 | */ 132 | public function route2Url($route, $params) { 133 | return Route::instance()->generateUrl($route, $params); 134 | } 135 | 136 | public function checkCache() { 137 | return !file_exists($this->viewTplCacheFile) || 138 | filemtime($this->viewTpl) > filemtime($this->viewTplCacheFile) || $this->checkInsertTpl(); 139 | } 140 | 141 | public function checkInsertTpl() { 142 | $cacheTime = filemtime($this->viewTplCacheFile); 143 | foreach ($this->insert as $l) { 144 | foreach ($l as $tpl) { 145 | if (filemtime($tpl) > $cacheTime) { 146 | return true; 147 | } 148 | } 149 | } 150 | } 151 | 152 | final public function setData($data) { 153 | $this->tplData = Kernel::merge($this->tplData, $data); 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /src/Lib/View/ViewData.php: -------------------------------------------------------------------------------- 1 | offsetSet($name, $value); 17 | } 18 | 19 | public function add($name, $value) { 20 | $this->offsetSet($name, $value); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/boot.php: -------------------------------------------------------------------------------- 1 | boot(); 12 | } 13 | 14 | return main(); 15 | --------------------------------------------------------------------------------