├── .gitignore ├── LICENSE ├── README.md ├── app ├── admin │ ├── controller │ │ └── IndexController.php │ └── view │ │ └── index │ │ └── index.php ├── common │ └── BaseController.php ├── home │ ├── controller │ │ └── IndexController.php │ ├── language │ │ └── zh_cn.php │ ├── model │ │ └── TestModel.php │ └── view │ │ └── index │ │ └── index.php └── middleware │ ├── AuthMiddleware.php │ └── LogMiddleware.php ├── composer.json ├── config ├── cache.php ├── common.php ├── database.php ├── middleware.php ├── pagination.php └── route.php ├── extend └── .gitkeep ├── function └── custom_function.php ├── public ├── .htaccess ├── index.php ├── nginx.htaccess ├── static │ ├── css │ │ └── .gitkeep │ ├── font │ │ └── .gitkeep │ ├── images │ │ └── .gitkeep │ └── js │ │ └── .gitkeep ├── upload │ └── .gitkeep └── web.config ├── startmvc ├── autoload.php ├── boot.php ├── core │ ├── App.php │ ├── Cache.php │ ├── Config.php │ ├── Container.php │ ├── Controller.php │ ├── Cookie.php │ ├── Csrf.php │ ├── Db.php │ ├── Event.php │ ├── Exception.php │ ├── Http.php │ ├── Loader.php │ ├── Logger.php │ ├── Middleware.php │ ├── Model.php │ ├── Pagination.php │ ├── Request.php │ ├── Response.php │ ├── Router.php │ ├── Session.php │ ├── Upload.php │ ├── Validator.php │ ├── View.php │ ├── cache │ │ ├── File.php │ │ └── Redis.php │ ├── db │ │ ├── DbCache.php │ │ ├── DbCore.php │ │ └── DbInterface.php │ └── tpl │ │ ├── debug.php │ │ ├── error.php │ │ ├── jump.php │ │ └── trace.php └── function.php └── vendor ├── autoload.php └── composer ├── ClassLoader.php ├── InstalledVersions.php ├── LICENSE ├── autoload_classmap.php ├── autoload_namespaces.php ├── autoload_psr4.php ├── autoload_real.php ├── autoload_static.php ├── installed.json ├── installed.php └── platform_check.php /.gitignore: -------------------------------------------------------------------------------- 1 | /runtime/ 2 | *.log 3 | /app/test/ 4 | /composer.json 5 | /vendor 6 | /app/home/model/TestModel.php -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![输入图片说明](https://img.kancloud.cn/a0/3f/a03fbd0f272ec563a545ab568995c1eb_557x223.png "在这里输入图片标题") 2 | 3 | **StartMVC** 是一款超轻量php7框架,面向对象开发,`小巧、优雅、高效`,遵循 Apache2 开源协议发布的,支持 Composer 和 RESTful 的 PHP 开源框架。 4 | 5 | StartMVC 能够帮助开发者以最小的学习成本快速构建 Web 应用,在满足开发者最基础的分层开发、数据库和缓存访问等少量功能基础上,做到尽可能精简,以帮助您的应用基于框架高效运行。 6 | 7 | ### 优势 8 | 9 | - 轻量极致,可以高效地运行,打包后只有50k 10 | - 完全支持Composer,代码遵循ps2,psr4规范,方便扩展第三方类库。 11 | - 官方免费提供常用的扩展类库下载,拿来即用。 12 | - 数据库采用PDO操作,支持多种数据库。 13 | - 支持多应用、扩展机制、路由分发、自动加载、RESTFul Api、缓存、MVC结构和依赖注入。。 14 | - 采用原生 php语法作为视图引擎,您不必再去学习模板语法!系统运行效率极大提高! 15 | - 松耦合,执行效率更高。 16 | - 系统结构简洁,代码优雅规范。 17 | - 学习成本低,只要有一点php基码就可以迅速上手。 18 | 19 | ### 安装 20 | 解压后上传到服务器项目目录下,就可以直接使用了。StartMVC支持主程序和WEB站点根目录分离。默认的站点根目录是public,请将域名绑定(指向)到public目录,访客无法访问到除public目录之外的文件,有更高的安全性。 21 | 22 | ### composer安装 23 | composer create-project shaobingme/startmvc 24 | 25 | ### 官网 26 | http://www.startmvc.com 27 | QQ群:231304282 (加群口令startmvc) 28 | -------------------------------------------------------------------------------- /app/admin/controller/IndexController.php: -------------------------------------------------------------------------------- 1 | assign('admin',$admin); 10 | $this->display(); 11 | } 12 | } -------------------------------------------------------------------------------- /app/admin/view/index/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {$admin} 6 | 7 | 8 | 9 | 10 | 11 | 12 | {$admin} 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/common/BaseController.php: -------------------------------------------------------------------------------- 1 | _initialize(); 20 | } 21 | 22 | protected function _initialize() 23 | { 24 | // 子类可以重写此方法 25 | } 26 | } -------------------------------------------------------------------------------- /app/home/controller/IndexController.php: -------------------------------------------------------------------------------- 1 | table('article')->get(); 16 | //dump($result); 17 | 18 | $this->assign($data); 19 | $this->display(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/home/language/zh_cn.php: -------------------------------------------------------------------------------- 1 | 'startmvc轻量php框架', 6 | ]; -------------------------------------------------------------------------------- /app/home/model/TestModel.php: -------------------------------------------------------------------------------- 1 | find('*',3); 11 | //return Model::find('*',3); 12 | //return $this->model('test')->find('*',3); 13 | //return self::find('*',2); 14 | return parent::find('*',3); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/home/view/index/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {$title} 6 | 24 | 25 | 26 |
27 |

28 |

{$title}

29 | 36 |
37 | 38 | -------------------------------------------------------------------------------- /app/middleware/AuthMiddleware.php: -------------------------------------------------------------------------------- 1 | authenticated = false; 27 | echo "用户未登录,但允许继续访问。
"; 28 | } else { 29 | $request->authenticated = true; 30 | echo "用户已登录。
"; 31 | } 32 | 33 | // 调用下一个中间件或控制器 34 | $response = $next($request); 35 | 36 | // 在响应返回前可以进行一些后处理 37 | // 例如:添加响应头、修改响应内容等 38 | 39 | return $response; 40 | } 41 | } -------------------------------------------------------------------------------- /app/middleware/LogMiddleware.php: -------------------------------------------------------------------------------- 1 | $_SERVER['REQUEST_METHOD'], 27 | 'uri' => $_SERVER['REQUEST_URI'], 28 | 'ip' => $_SERVER['REMOTE_ADDR'], 29 | 'time' => date('Y-m-d H:i:s'), 30 | ]; 31 | 32 | // 输出请求信息(实际应用中应写入日志文件) 33 | echo ""; 34 | 35 | // 调用下一个中间件 36 | $response = $next($request); 37 | 38 | // 计算执行时间 39 | $endTime = microtime(true); 40 | $executionTime = round(($endTime - $startTime) * 1000, 2); 41 | 42 | // 输出执行时间信息 43 | echo ""; 44 | 45 | return $response; 46 | } 47 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shaobingme/startmvc", 3 | "description": "A light-weight PHP MVC framework.", 4 | "type": "project", 5 | "keywords": [ 6 | "startmvc", 7 | "php框架", 8 | "framework", 9 | "mvc" 10 | ], 11 | "homepage": "http://www.startmvc.com/", 12 | "license": "Apache-2.0", 13 | "authors": [ 14 | { 15 | "name": "shaobing", 16 | "email": "startmvc@126.com" 17 | } 18 | ], 19 | "require": { 20 | "php": ">=7.2.0" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "app\\": "app/", 25 | "extend\\": "extend/", 26 | "startmvc\\": "startmvc/" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /config/cache.php: -------------------------------------------------------------------------------- 1 | 'file', //默认驱动支持file,redis缓存 12 | 'file'=> [ 13 | 'cacheDir'=>'cache/', 14 | 'cacheTime'=>3600 15 | ], 16 | 'redis' => [ 17 | 'host' => '127.0.0.1', 18 | 'port' => 6379, 19 | 'password' => '', 20 | 'database' => 0, 21 | 'cacheTime'=>3600 22 | ], 23 | ]; -------------------------------------------------------------------------------- /config/common.php: -------------------------------------------------------------------------------- 1 | true, //Debug模式,开发过程中开启,生产环境中请关闭 12 | 'trace' => true, //是否开启调试追踪,生产环境中请关闭 13 | 'timezone' => 'Asia/Shanghai', //系统时区 14 | 'url_suffix' => '.html', //URL后缀 15 | 'default_module' => 'home', //默认模块 16 | 'default_controller' => 'Index', //默认控制器 17 | 'default_action' => 'index', //默认方法 18 | 'urlrewrite' => true, //是否Url重写,隐藏index.php,需要服务器支持和对应的规则 19 | 'session_prefix' => '', //Session前缀 20 | 'cookie_prefix' => '', //Cookie前缀 21 | 'locale' => 'zh_cn', //指定默认语言,小写 22 | 'db_auto_connect' => true, //是否开启数据库自动连接 23 | 'theme' => '', //指定模板子目录,方便多风格使用,为空时模板文件在view下 24 | ]; -------------------------------------------------------------------------------- /config/database.php: -------------------------------------------------------------------------------- 1 | 'mysql',//指定数据库类型 5 | 'connections' => [ 6 | 'mysql' => [ 7 | 'driver' => 'mysql',//数据库类型 8 | 'host' => 'localhost',//数据库服务器地址 9 | 'database' => 'startmvc',//数据库名称 10 | 'username' => 'root',//数据库用户名 11 | 'password' => '123456',//数据库密码 12 | 'charset' => 'utf8',//数据库字符集 13 | 'port' => 3306, //数据库端口 14 | 'collation' => 'utf8_general_ci',//数据表编码 15 | 'prefix' => 'sm_',//数据表前缀 16 | 'cachetime' => 3600,//缓存时间(秒) 17 | 'cachedir' => ROOT_PATH . 'runtime'.DS.'db'.DS,//缓存目录(可选) 18 | 'options' => [ ]//连接选项(像SSL证书等可选) 19 | ], 20 | 'sqlite' => [ 21 | 'driver' => 'sqlite',//数据库类型 22 | 'database' => BASE_PATH.'data/database/test.db',//数据库文件路径 23 | 'prefix' => 'sm_',//数据表前缀 24 | 'cachetime' => 3600,//缓存时间(秒) 25 | 'cachedir' => ROOT_PATH . 'runtime'.DS.'db'.DS,//缓存目录(可选) 26 | 'options' => [ ]//连接选项(像SSL证书等可选) 27 | ], 28 | 'pgsql' => [ 29 | 'driver' => 'pgsql',//数据库类型 30 | 'host' => 'localhost',//数据库服务器地址 31 | 'database' => 'startmvc',//数据库名称 32 | 'username' => 'root',//数据库用户名 33 | 'password' => '',//数据库密码 34 | 'charset' => 'utf8',//数据库字符集 35 | 'port' => 3306, //数据库端口 36 | 'collation' => 'utf8_general_ci',//数据表编码 37 | 'prefix' => 'sm_',//数据表前缀 38 | 'cachetime' => 3600,//缓存时间(秒) 39 | 'cachedir' => ROOT_PATH . 'runtime'.DS.'db'.DS,//缓存目录(可选) 40 | 'options' => [ ]//连接选项(像SSL证书等可选) 41 | ], 42 | 'oracle' => [ 43 | 'driver' => 'oracle',//数据库类型 44 | 'host' => 'localhost:8000',//数据库服务器地址 45 | 'database' => 'startmvc',//数据库名称 46 | 'username' => 'root',//数据库用户名 47 | 'password' => '',//数据库密码 48 | 'charset' => 'utf8',//数据库字符集 49 | 'port' => 3306, //数据库端口 50 | 'collation' => 'utf8_general_ci',//数据表编码 51 | 'prefix' => 'sm_',//数据表前缀 52 | 'cachetime' => 3600,//缓存时间(秒) 53 | 'cachedir' => ROOT_PATH . 'runtime'.DS.'db'.DS,//缓存目录(可选) 54 | 'options' => [ ]//连接选项(像SSL证书等可选) 55 | ], 56 | 57 | ], 58 | ]; -------------------------------------------------------------------------------- /config/middleware.php: -------------------------------------------------------------------------------- 1 | [ 6 | // 'csrf' => 'app\\middleware\\CsrfMiddleware', 7 | // 'auth' => 'app\\middleware\\AuthMiddleware', 8 | // 'throttle' => 'app\\middleware\\ThrottleMiddleware', 9 | // ], 10 | 11 | // // 全局中间件(每个请求都会执行) 12 | // 'global' => [ 13 | // 'app\\middleware\\CsrfMiddleware', 14 | // ], 15 | 16 | // // 路由中间件(可以应用到特定路由) 17 | // 'route' => [ 18 | // 'auth' => 'app\\middleware\\AuthMiddleware', 19 | // 'throttle' => 'app\\middleware\\ThrottleMiddleware', 20 | // ] 21 | // ]; 22 | -------------------------------------------------------------------------------- /config/pagination.php: -------------------------------------------------------------------------------- 1 | '%header% %first% %prev% %link% %next% %last%',//分页样式 14 | 'header' => '总共 %count% 页 %page% / %pageCount%',//分页头部 15 | 'first' => '首页',//首页 16 | 'last' => '末页',//末页 17 | 'prev' => '上一页',//上一页 18 | 'next' => '下一页',//下一页 19 | 'currentClass' => 'is-current',//当前页码类 20 | ]; -------------------------------------------------------------------------------- /config/route.php: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine on 3 | RewriteCond %{REQUEST_FILENAME} !-d 4 | RewriteCond %{REQUEST_FILENAME} !-f 5 | RewriteRule ^(.*)$ index.php [QSA,PT,L] 6 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /startmvc/autoload.php: -------------------------------------------------------------------------------- 1 | run(); -------------------------------------------------------------------------------- /startmvc/core/App.php: -------------------------------------------------------------------------------- 1 | registerMiddleware(); 22 | } 23 | public function run() 24 | { 25 | // 记录开始时间和内存 26 | $beginTime = microtime(true); 27 | $beginMem = memory_get_usage(); 28 | 29 | try { 30 | Exception::init(); 31 | $this->loadFunction(); 32 | 33 | // 创建请求对象 34 | $request = new Request(); 35 | 36 | // 通过中间件管道处理请求 37 | $response = Middleware::run($this, function() { 38 | return $this->handleRequest(); 39 | }); 40 | 41 | // 记录结束时间和内存 42 | $endTime = microtime(true); 43 | $endMem = memory_get_usage(); 44 | 45 | // 计算运行时间和内存使用 46 | self::$trace = [ 47 | 'beginTime' => $beginTime, 48 | 'endTime' => $endTime, 49 | 'runtime' => number_format(($endTime - $beginTime) * 1000, 2) . 'ms', 50 | 'memory' => number_format(($endMem - $beginMem) / 1024, 2) . 'KB', 51 | 'files' => get_included_files(), // 添加加载的文件列表 52 | 'uri' => $_SERVER['REQUEST_URI'], 53 | 'request_method' => $_SERVER['REQUEST_METHOD'] 54 | ]; 55 | 56 | // 输出响应内容 57 | if (is_string($response)) { 58 | echo $response; 59 | } elseif (is_array($response)) { 60 | header('Content-Type: application/json'); 61 | echo json_encode($response); 62 | } 63 | 64 | // 在页面最后输出追踪信息 65 | if (config('trace')) { 66 | echo "\n\n"; 67 | include __DIR__ . '/tpl/trace.php'; 68 | echo "\n\n"; 69 | } 70 | 71 | } catch (\Exception $e) { 72 | throw $e; 73 | } 74 | } 75 | 76 | /** 77 | * 加载自定义函数 78 | */ 79 | private static function loadFunction($dirPath = ROOT_PATH.'function'.DS.'*.php') 80 | { 81 | $files=glob($dirPath); 82 | if (is_array($files)) { 83 | foreach ($files as $v) { 84 | if(is_file($v)) require_once($v); 85 | } 86 | } 87 | } 88 | 89 | /** 90 | * 配置控制器的路径 91 | */ 92 | private static function startApp($module, $controller, $action, $argv) 93 | { 94 | // 先定义常量,因为 View 类的构造函数需要用到 95 | if (!defined('MODULE')) define('MODULE', $module); 96 | if (!defined('CONTROLLER')) define('CONTROLLER', $controller); 97 | if (!defined('ACTION')) define('ACTION', $action); 98 | 99 | $controller = APP_NAMESPACE . "\\{$module}\\controller\\{$controller}Controller"; 100 | if (!class_exists($controller)) { 101 | throw new \Exception($controller.'控制器不存在'); 102 | } 103 | $action .= 'Action'; 104 | return Loader::make($controller, $action, $argv); 105 | } 106 | /** 107 | * 自定义错误处理触发错误 108 | */ 109 | public static function errorHandler($level,$message, $file, $line) 110 | { 111 | if (error_reporting() !== 0) { 112 | $errorMessage = "错误提示:{$message},文件:{$file},行号:{$line}"; 113 | throw new \Exception($errorMessage, $level); 114 | } 115 | } 116 | /** 117 | * 异常错误处理 118 | */ 119 | public static function exceptionHandler($exception) 120 | { 121 | // Code is 404 (not found) or 500 (general error) 122 | $code = $exception->getCode(); 123 | if ($code != 404) { 124 | $code = 500; 125 | } 126 | http_response_code($code); 127 | if (config('debug')) { 128 | include 'tpl/debug.php'; 129 | //var_dump($exception); 130 | } else { 131 | //$log = new Log(); 132 | //$log->debug($exception->getMessage() . '\n' . $exception->getFile() . '\n' . $exception->getLine()); 133 | return $code; 134 | } 135 | } 136 | 137 | /** 138 | * 注册默认中间件 139 | */ 140 | protected function registerMiddleware() 141 | { 142 | // 从配置文件加载中间件 143 | $middleware = config('middleware') ?? []; 144 | 145 | // 注册中间件别名 146 | $aliases = $middleware['aliases'] ?? []; 147 | foreach ($aliases as $alias => $class) { 148 | Middleware::alias($alias, $class); 149 | } 150 | 151 | // 注册全局中间件 152 | $global = $middleware['global'] ?? []; 153 | foreach ($global as $middlewareClass) { 154 | Middleware::register($middlewareClass); 155 | } 156 | } 157 | 158 | /** 159 | * 处理请求 160 | */ 161 | private function handleRequest() 162 | { 163 | try { 164 | // 获取当前URI 165 | $uri = $_SERVER['REQUEST_URI']; 166 | 167 | // 移除查询字符串 168 | if (strpos($uri, '?') !== false) { 169 | $uri = substr($uri, 0, strpos($uri, '?')); 170 | } 171 | 172 | // 移除前后的斜杠 173 | $uri = trim($uri, '/'); 174 | 175 | // 过滤入口文件名(如index.php) 176 | $scriptName = basename($_SERVER['SCRIPT_NAME']); 177 | if (strpos($uri, $scriptName) === 0) { 178 | $uri = substr($uri, strlen($scriptName)); 179 | $uri = trim($uri, '/'); 180 | } 181 | 182 | // 如果URI为空,使用默认路由 183 | if (empty($uri)) { 184 | $module = 'home'; // 默认模块 185 | $controller = 'Index'; // 默认控制器 186 | $action = 'index'; // 默认方法 187 | $params = []; 188 | } else { 189 | // 解析URI 190 | $parts = explode('/', $uri); 191 | $module = isset($parts[0]) ? $parts[0] : 'home'; 192 | $controller = isset($parts[1]) ? ucfirst($parts[1]) : 'Index'; 193 | $action = isset($parts[2]) ? $parts[2] : 'index'; 194 | $params = array_slice($parts, 3); 195 | } 196 | 197 | // 校验模块是否存在 198 | if (!is_dir(APP_PATH . $module)) { 199 | // 模块不存在,使用默认模块 200 | $params = array_merge([$controller, $action], $params); 201 | $module = 'home'; 202 | $controller = 'Index'; 203 | $action = 'index'; 204 | } 205 | 206 | // 使用原有的startApp方法 207 | return self::startApp($module, $controller, $action, $params); 208 | 209 | } catch (\Exception $e) { 210 | throw $e; 211 | } 212 | } 213 | 214 | /** 215 | * 显示追踪信息 216 | */ 217 | protected static function showTrace() 218 | { 219 | // 确保输出在页面最后 220 | register_shutdown_function(function() { 221 | // 包含trace模板 222 | include __DIR__ . '/tpl/trace.php'; 223 | }); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /startmvc/core/Cache.php: -------------------------------------------------------------------------------- 1 | drive = new $className($params); 39 | } 40 | 41 | /** 42 | * 设置缓存 43 | * @param string $key 缓存键名 44 | * @param mixed $val 缓存数据 45 | * @return $this 46 | */ 47 | public function set(string $key, $val) { 48 | $this->drive->set($key, $val); 49 | return $this; 50 | } 51 | 52 | /** 53 | * 检查缓存是否存在 54 | * @param string $key 缓存键名 55 | * @return bool 56 | */ 57 | public function has(string $key) { 58 | return $this->drive->has($key); 59 | } 60 | 61 | /** 62 | * 获取缓存 63 | * @param string $key 缓存键名 64 | * @return mixed 65 | */ 66 | public function get(string $key) { 67 | return $this->drive->get($key); 68 | } 69 | 70 | /** 71 | * 删除缓存 72 | * @param string $key 缓存键名 73 | * @return $this 74 | */ 75 | public function delete(string $key) { 76 | $this->drive->delete($key); 77 | return $this; 78 | } 79 | 80 | /** 81 | * 清空所有缓存 82 | * @return $this 83 | */ 84 | public function clear() { 85 | $this->drive->clear(); 86 | return $this; 87 | } 88 | 89 | /** 90 | * 创建缓存实例的静态方法 91 | * @param string $driver 驱动名称 92 | * @param array $params 驱动参数 93 | * @return Cache 94 | */ 95 | public static function store(string $driver = null, array $params = []) 96 | { 97 | return new self($driver, $params); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /startmvc/core/Config.php: -------------------------------------------------------------------------------- 1 | bindings[$abstract] = compact('concrete', 'shared'); 26 | } 27 | 28 | /** 29 | * 注册一个共享绑定(单例) 30 | * @param string $abstract 抽象类型 31 | * @param mixed $concrete 具体实现 32 | * @return void 33 | */ 34 | public function singleton($abstract, $concrete = null) 35 | { 36 | $this->bind($abstract, $concrete, true); 37 | } 38 | 39 | /** 40 | * 解析一个类型的实例 41 | * @param string $abstract 要解析的类型 42 | * @param array $parameters 构造函数参数 43 | * @return mixed 解析出的实例 44 | * @throws BindingResolutionException 45 | */ 46 | public function make($abstract, array $parameters = []) 47 | { 48 | if (isset(static::$instances[$abstract])) { 49 | return static::$instances[$abstract]; 50 | } 51 | 52 | $concrete = $this->getConcrete($abstract); 53 | $object = $this->build($concrete, $parameters); 54 | 55 | if ($this->isShared($abstract)) { 56 | static::$instances[$abstract] = $object; 57 | } 58 | 59 | return $object; 60 | } 61 | 62 | /** 63 | * 增加自动解析构造函数参数的能力 64 | * @param string $concrete 具体类名 65 | * @param array $parameters 手动提供的参数 66 | * @return object 实例化对象 67 | */ 68 | protected function build($concrete, array $parameters = []) 69 | { 70 | // 如果是闭包,直接执行 71 | if ($concrete instanceof \Closure) { 72 | return $concrete($this, $parameters); 73 | } 74 | 75 | // 获取反射类 76 | $reflector = new \ReflectionClass($concrete); 77 | 78 | // 检查是否可实例化 79 | if (!$reflector->isInstantiable()) { 80 | throw new \Exception("类 {$concrete} 不可实例化"); 81 | } 82 | 83 | // 获取构造函数 84 | $constructor = $reflector->getConstructor(); 85 | 86 | // 如果没有构造函数,直接实例化 87 | if (is_null($constructor)) { 88 | return new $concrete; 89 | } 90 | 91 | // 获取构造函数参数 92 | $dependencies = $constructor->getParameters(); 93 | 94 | // 解析构造函数的依赖 95 | $instances = $this->resolveDependencies($dependencies, $parameters); 96 | 97 | // 创建实例 98 | return $reflector->newInstanceArgs($instances); 99 | } 100 | } -------------------------------------------------------------------------------- /startmvc/core/Controller.php: -------------------------------------------------------------------------------- 1 | conf = include CONFIG_PATH . 'common.php'; 25 | $this->view = new View(); 26 | } 27 | /** 28 | * 模型定义 29 | */ 30 | protected function model($model, $module = MODULE) 31 | { 32 | $model = APP_NAMESPACE.'\\' . $module . '\\'. 'model\\' . $model . 'Model'; 33 | return Loader::getInstance($model); 34 | } 35 | /** 36 | * url的方法 37 | */ 38 | protected function url($url) 39 | { 40 | $url = $url . $this->conf['url_suffix']; 41 | if ($this->conf['urlrewrite']) { 42 | $url = '/' . $url; 43 | } else { 44 | $url = '/index.php/' . $url; 45 | } 46 | return str_replace('%2F', '/', urlencode($url)); 47 | } 48 | 49 | /** 50 | * 为模板对象赋值 51 | */ 52 | protected function assign($name=[], $data='') 53 | { 54 | $this->view->assign($name, $data); 55 | return $this; // 支持链式调用 56 | } 57 | 58 | /** 59 | * 调用视图 60 | */ 61 | 62 | protected function display($tplfile='',$data=[]) 63 | { 64 | // 直接调用视图的display方法,不返回内容 65 | $this->view->display($tplfile,$data); 66 | } 67 | 68 | /** 69 | * 获取渲染内容但不输出 70 | */ 71 | protected function fetch($tplfile='',$data=[]) 72 | { 73 | return $this->view->fetch($tplfile,$data); 74 | } 75 | 76 | /** 77 | * 调用内容 78 | */ 79 | public function content($content) 80 | { 81 | header('Content-Type:text/plain; charset=utf-8'); 82 | echo $content; 83 | } 84 | protected function success($msg='',$url='',$data=[],$ajax=false) 85 | { 86 | $this->response(1,$msg,$url,$data,$ajax); 87 | } 88 | protected function error($msg='',$url='',$data=[],$ajax=false) 89 | { 90 | $this->response(0,$msg,$url,$data,$ajax); 91 | } 92 | protected function response($code='',$msg='',$url='',$data=[],$ajax=false) 93 | { 94 | if($ajax || Request::isAjax()){ 95 | $data=[ 96 | 'code'=>$code,//1-成功 0-失败 97 | 'msg'=>$msg, 98 | 'url'=>$url, 99 | 'data'=>$data, 100 | ]; 101 | $this->json($data); 102 | }else{ 103 | include __DIR__.DS.'tpl/jump.php'; 104 | exit(); 105 | } 106 | 107 | } 108 | 109 | /** 110 | * json方法 111 | */ 112 | protected function json($data) 113 | { 114 | header('Content-Type:application/json; charset=utf-8'); 115 | //echo json_encode($data, JSON_UNESCAPED_UNICODE); 116 | exit(json_encode($data, JSON_UNESCAPED_UNICODE)); 117 | } 118 | 119 | 120 | /** 121 | * 跳转 122 | */ 123 | protected function redirect($url='') 124 | { 125 | $url=$url?:'/'; 126 | header('location:' . $url); 127 | exit(); 128 | } 129 | /** 130 | * 404方法 131 | */ 132 | protected function notFound() 133 | { 134 | header("HTTP/1.1 404 Not Found"); 135 | header("Status: 404 Not Found"); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /startmvc/core/Cookie.php: -------------------------------------------------------------------------------- 1 | $value) { 108 | if (strpos($key, $prefix) === 0) { 109 | $newKey = substr($key, $prefixLength); 110 | $result[$newKey] = $value; 111 | } 112 | } 113 | 114 | return $result; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /startmvc/core/Csrf.php: -------------------------------------------------------------------------------- 1 | table($table); 27 | } 28 | 29 | /** 30 | * 获取数据库实例 31 | * @return DbCore 32 | */ 33 | protected static function getInstance() 34 | { 35 | if (static::$instance === null) { 36 | $config = include CONFIG_PATH . '/database.php'; 37 | if (isset($config['driver']) && $config['driver'] !== '') { 38 | static::$instance = DbCore::getInstance($config['connections'][$config['driver']]); 39 | } else { 40 | throw new \Exception('数据库配置不正确,请检查配置文件'); 41 | } 42 | } 43 | return static::$instance; 44 | } 45 | 46 | /** 47 | * 调用DbCore的其他方法 48 | * @param string $method 方法名 49 | * @param array $args 参数 50 | * @return mixed 51 | */ 52 | public static function __callStatic($method, $args) 53 | { 54 | return call_user_func_array([static::getInstance(), $method], $args); 55 | } 56 | } -------------------------------------------------------------------------------- /startmvc/core/Event.php: -------------------------------------------------------------------------------- 1 | $callback) { 48 | $responses[] = call_user_func($callback, $payload); 49 | } 50 | } 51 | 52 | return $responses; 53 | } 54 | 55 | /** 56 | * 移除事件监听器 57 | * @param string $event 事件名称 58 | * @return void 59 | */ 60 | public static function forget($event) 61 | { 62 | unset(self::$listeners[$event]); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /startmvc/core/Exception.php: -------------------------------------------------------------------------------- 1 | getMessage(), 70 | $exception->getFile(), 71 | $exception->getLine(), 72 | $exception->getTraceAsString() 73 | ); 74 | 75 | $logFile = $logPath . DIRECTORY_SEPARATOR . date('Y-m-d') . '_error.log'; 76 | 77 | // 使用错误抑制符,避免因写入失败导致的额外异常 78 | @error_log($message, 3, $logFile); 79 | } 80 | 81 | /** 82 | * 处理异常 83 | * @param \Throwable $exception 84 | */ 85 | public static function handleException(\Throwable $exception) 86 | { 87 | try { 88 | self::logException($exception); 89 | } catch (\Exception $e) { 90 | // 日志记录失败时的处理 91 | } 92 | 93 | // 设置HTTP状态码 94 | http_response_code(500); 95 | 96 | // 获取调试模式设置 97 | $debug = config('debug', true); // 默认为true,确保在配置不存在时也能看到错误 98 | 99 | // AJAX请求处理 100 | if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && 101 | strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') { 102 | header('Content-Type: application/json'); 103 | echo json_encode([ 104 | 'error' => $exception->getMessage(), 105 | 'trace' => $exception->getTraceAsString() 106 | ]); 107 | exit; 108 | } 109 | 110 | // 传递异常对象到错误模板 111 | $e = $exception; // 为错误模板提供异常对象 112 | 113 | // 包含错误模板 114 | $errorTemplate = CORE_PATH . 'tpl/error.php'; 115 | if (file_exists($errorTemplate)) { 116 | include $errorTemplate; 117 | } else { 118 | echo '

系统错误

'; 119 | echo '

' . htmlspecialchars($exception->getMessage()) . '

'; 120 | echo '
' . htmlspecialchars($exception->getTraceAsString()) . '
'; 121 | } 122 | exit; 123 | } 124 | 125 | /** 126 | * 处理程序结束时的错误 127 | */ 128 | public static function handleShutdown() 129 | { 130 | $error = error_get_last(); 131 | 132 | if ($error !== null && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) { 133 | self::handleError($error['type'], $error['message'], $error['file'], $error['line']); 134 | } 135 | } 136 | 137 | /** 138 | * 显示友好的错误页面给用户(在生产环境中使用) 139 | */ 140 | private static function errorPage($output) 141 | { 142 | // 将错误信息作为 GET 参数传递到错误页面 143 | //$errorPageURL = '/error-page.php?error=' . urlencode($errorMessage); 144 | //header("Location: $errorPageURL"); 145 | if(config('debug')){ 146 | include 'tpl/error.php'; 147 | } 148 | exit; 149 | } 150 | } 151 | 152 | // 创建 CustomErrorHandler 实例,自动注册错误处理和异常处理方法 153 | //$customErrorHandler = new CustomErrorHandler(); -------------------------------------------------------------------------------- /startmvc/core/Http.php: -------------------------------------------------------------------------------- 1 | newInstanceArgs($paramArr); 19 | } 20 | 21 | public static function make($controller, $action, $argv) 22 | { 23 | try { 24 | $class = new \ReflectionClass($controller); 25 | $instance = $class->newInstanceArgs(); 26 | 27 | if (!method_exists($instance, $action)) { 28 | throw new \Exception("方法{$action}不存在"); 29 | } 30 | 31 | return call_user_func_array([$instance, $action], $argv); 32 | } catch (\ReflectionException $e) { 33 | throw new \Exception("控制器实例化失败:" . $e->getMessage()); 34 | } 35 | } 36 | 37 | protected static function getMethodParams($className, $methodsName = '__construct') 38 | { 39 | $class = new \ReflectionClass($className); 40 | $paramArr = []; 41 | if ($class->hasMethod($methodsName)) { 42 | $method = $class->getMethod($methodsName); 43 | $params = $method->getParameters(); 44 | if (count($params) > 0) { 45 | foreach ($params as $key => $param) { 46 | // 使用 getType() 代替 getClass() 47 | $type = $param->getType(); 48 | if ($type && !$type->isBuiltin() && $type instanceof \ReflectionNamedType) { 49 | $paramClassName = $type->getName(); 50 | $args = self::getMethodParams($paramClassName); 51 | $paramArr[] = (new \ReflectionClass($paramClassName))->newInstanceArgs($args); 52 | } 53 | } 54 | } 55 | } 56 | return $paramArr; 57 | } 58 | 59 | protected static function filter($doc) 60 | { 61 | if ($doc) { 62 | preg_match_all('/filter\[[\S\s]+\]/U', $doc, $matches); 63 | foreach ($matches[0] as $filter) { 64 | $filterClass = preg_replace('/filter\[([\S\s]+)\(([\S\s]*)\)\]/', '${1}', $filter); 65 | $filterClass = '\\Filter\\' . $filterClass; 66 | $filterParamArr = preg_replace('/filter\[([\S\s]+)\(([\S\s]*)\)\]/', '${2}', $filter); 67 | $filterParamArr = explode(',', $filterParamArr); 68 | for ($i = 0; $i < count($filterParamArr); $i++) { 69 | $filterParamArr[$i] = trim($filterParamArr[$i]); 70 | } 71 | $instance = self::getInstance($filterClass); 72 | if (method_exists($instance, 'handle')) { 73 | $instance->handle(...$filterParamArr); 74 | } 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /startmvc/core/Logger.php: -------------------------------------------------------------------------------- 1 | path = $path ?: ROOT_PATH . 'runtime/logs'; 25 | 26 | // 确保目录存在 27 | if (!is_dir($this->path)) { 28 | mkdir($this->path, 0777, true); 29 | } 30 | } 31 | 32 | /** 33 | * 写入日志 34 | * @param string $level 日志级别 35 | * @param string $message 日志消息 36 | * @param array $context 上下文数据 37 | * @return bool 38 | */ 39 | public function log($level, $message, array $context = []) 40 | { 41 | if (!in_array($level, $this->levels)) { 42 | throw new \InvalidArgumentException("无效的日志级别 [$level]"); 43 | } 44 | 45 | // 格式化消息 46 | $message = $this->formatMessage($level, $message, $context); 47 | 48 | // 写入文件 49 | $file = $this->path . '/' . date('Y-m-d') . '.log'; 50 | return file_put_contents($file, $message . PHP_EOL, FILE_APPEND | LOCK_EX); 51 | } 52 | 53 | /** 54 | * 格式化日志消息 55 | * @param string $level 日志级别 56 | * @param string $message 日志消息 57 | * @param array $context 上下文数据 58 | * @return string 59 | */ 60 | protected function formatMessage($level, $message, array $context = []) 61 | { 62 | // 替换上下文变量 63 | $replace = []; 64 | foreach ($context as $key => $val) { 65 | if (is_string($val) || is_numeric($val)) { 66 | $replace['{' . $key . '}'] = $val; 67 | } 68 | } 69 | 70 | $message = strtr($message, $replace); 71 | 72 | return '[' . date('Y-m-d H:i:s') . '] ' . strtoupper($level) . ': ' . $message; 73 | } 74 | 75 | /** 76 | * 记录调试信息 77 | * @param string $message 日志消息 78 | * @param array $context 上下文数据 79 | * @return bool 80 | */ 81 | public function debug($message, array $context = []) 82 | { 83 | return $this->log('debug', $message, $context); 84 | } 85 | 86 | /** 87 | * 记录错误信息 88 | * @param string $message 日志消息 89 | * @param array $context 上下文数据 90 | * @return bool 91 | */ 92 | public function error($message, array $context = []) 93 | { 94 | return $this->log('error', $message, $context); 95 | } 96 | 97 | // 其他级别的快捷方法... 98 | } -------------------------------------------------------------------------------- /startmvc/core/Middleware.php: -------------------------------------------------------------------------------- 1 | handle($request, $next); 91 | }; 92 | 93 | return $firstSlice($request); 94 | } 95 | 96 | /** 97 | * 执行所有全局中间件 98 | * @param object $request 请求对象 99 | * @param \Closure $destination 最终目标处理函数 100 | * @return mixed 101 | */ 102 | public static function run($request, \Closure $destination) 103 | { 104 | return self::pipeline(self::$middleware, $request, $destination); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /startmvc/core/Model.php: -------------------------------------------------------------------------------- 1 | dbConf = include CONFIG_PATH . '/database.php'; 48 | } 49 | 50 | /** 51 | * 设置表名 52 | * 53 | * @param string $table 表名 54 | * @return $this 55 | */ 56 | public function table($table) 57 | { 58 | $this->table = $table; 59 | return $this; 60 | } 61 | 62 | /** 63 | * 设置模型数据 64 | * 65 | * @param array $data 数据 66 | * @return $this 67 | */ 68 | public function data($data) 69 | { 70 | $this->data = array_merge($this->data, $data); 71 | return $this; 72 | } 73 | 74 | /** 75 | * 插入数据 76 | * 77 | * @param array $data 数据 78 | * @return int|bool 插入ID或结果 79 | */ 80 | public function insert($data = []) 81 | { 82 | if (!empty($data)) { 83 | $this->data = $data; 84 | } 85 | 86 | return Db::table($this->table)->insert($this->data); 87 | } 88 | 89 | /** 90 | * 更新数据 91 | * 92 | * @param array $data 要更新的数据 93 | * @param mixed $where 条件(数组、字符串或整数id) 94 | * @return int|bool 影响行数或结果 95 | */ 96 | public function update($data, $where = []) 97 | { 98 | if (!empty($data)) { 99 | $this->data = $data; 100 | } 101 | 102 | $query = Db::table($this->table); 103 | 104 | if (!empty($where)) { 105 | if (is_numeric($where)) { 106 | // 如果是纯数字,认为是按主键查询 107 | $query->where($this->pk, $where); 108 | } elseif (is_array($where)) { 109 | // 如果是数组,则按条件数组处理 110 | $query->where($where); 111 | } elseif (is_string($where)) { 112 | // 如果是字符串,判断是否为条件表达式 113 | if (preg_match('/[=<>!]/', $where)) { 114 | // 包含运算符,视为条件表达式 115 | $query->where($where); 116 | } else { 117 | // 不包含运算符,视为主键值 118 | $query->where($this->pk, $where); 119 | } 120 | } 121 | } 122 | 123 | return $query->update($this->data); 124 | } 125 | 126 | /** 127 | * 保存数据(自动判断插入或更新) 128 | * 129 | * @param array $data 数据 130 | * @return int|bool 结果 131 | */ 132 | public function save($data = []) 133 | { 134 | if (!empty($data)) { 135 | $this->data = $data; 136 | } 137 | 138 | if (isset($this->data[$this->pk]) && !empty($this->data[$this->pk])) { 139 | // 有主键,执行更新 140 | $id = $this->data[$this->pk]; 141 | $updateData = $this->data; 142 | return $this->update($updateData, $id); 143 | } else { 144 | // 无主键,执行插入 145 | return $this->insert(); 146 | } 147 | } 148 | 149 | /** 150 | * 删除数据 151 | * 152 | * @param mixed $where 条件(数组、字符串或整数id) 153 | * @return int|bool 影响行数或结果 154 | */ 155 | public function delete($where = null) 156 | { 157 | $query = Db::table($this->table); 158 | 159 | if ($where !== null) { 160 | if (is_numeric($where)) { 161 | // 数字条件转为主键条件 162 | $query->where($this->pk, $where); 163 | } else { 164 | $query->where($where); 165 | } 166 | } 167 | 168 | return $query->delete(); 169 | } 170 | 171 | /** 172 | * 魔术方法:调用不存在的方法时自动调用db对象的方法 173 | * 174 | * @param string $method 方法名 175 | * @param array $args 参数 176 | * @return mixed 返回结果 177 | */ 178 | public function __call($method, $args) 179 | { 180 | $query = Db::table($this->table); 181 | 182 | if (method_exists($query, $method)) { 183 | return call_user_func_array([$query, $method], $args); 184 | } 185 | 186 | throw new \Exception("方法 {$method} 不存在"); 187 | } 188 | 189 | /** 190 | * 查找单条记录 191 | * 192 | * @param mixed $where 查询条件(主键值、条件数组或字符串条件表达式) 193 | * @param string|array $fields 查询字段,默认为* 194 | * @return array|null 返回符合条件的单条记录 195 | */ 196 | public function find($where, $fields = '*') 197 | { 198 | $query = Db::table($this->table); 199 | 200 | // 设置查询字段 201 | $query->select($fields); 202 | 203 | // 处理查询条件 204 | if (is_numeric($where)) { 205 | // 如果是纯数字,认为是按主键查询 206 | $query->where($this->pk, $where); 207 | } elseif (is_array($where)) { 208 | // 如果是数组,则按条件数组处理 209 | $query->where($where); 210 | } elseif (is_string($where)) { 211 | // 如果是字符串,判断是否为条件表达式 212 | if (preg_match('/[=<>!]/', $where)) { 213 | // 包含运算符,视为条件表达式 214 | $query->where($where); 215 | } else { 216 | // 不包含运算符,视为主键值 217 | $query->where($this->pk, $where); 218 | } 219 | } 220 | 221 | // 限制只返回一条记录 222 | $query->limit(1); 223 | 224 | // 执行查询 225 | $result = $query->get(); 226 | 227 | // 返回单条记录或null 228 | return !empty($result) ? $result[0] : null; 229 | } 230 | 231 | 232 | /** 233 | * 查找多条记录 234 | * 235 | * @param mixed $where 查询条件(条件数组或字符串条件表达式) 236 | * @param string|array $fields 查询字段,默认为* 237 | * @param string|array $order 排序方式 238 | * @param int|string $limit 查询限制 239 | * @return array 返回符合条件的记录集 240 | */ 241 | public function findAll($where = [], $fields = '*', $order = '', $limit = '') 242 | { 243 | $query = Db::table($this->table); 244 | 245 | // 设置查询字段 246 | $query->select($fields); 247 | 248 | // 处理查询条件 249 | if (!empty($where)) { 250 | if (is_array($where)) { 251 | $query->where($where); 252 | } elseif (is_string($where)) { 253 | // 字符串条件 254 | $query->where($where); 255 | } 256 | } 257 | 258 | // 设置排序 259 | if (!empty($order)) { 260 | if (is_array($order)) { 261 | foreach ($order as $field => $sort) { 262 | if (is_numeric($field)) { 263 | $query->order($sort); 264 | } else { 265 | $query->order($field, $sort); 266 | } 267 | } 268 | } else { 269 | $query->order($order); 270 | } 271 | } 272 | 273 | // 设置查询限制 274 | if (!empty($limit)) { 275 | if (is_numeric($limit)) { 276 | $query->limit($limit); 277 | } elseif (is_string($limit) && strpos($limit, ',') !== false) { 278 | list($offset, $rows) = explode(',', $limit); 279 | $query->limit($rows, $offset); 280 | } 281 | } 282 | 283 | // 执行查询 284 | return $query->get(); 285 | } 286 | 287 | /** 288 | * 静态方法:实例化模型 289 | * 290 | * @param string $table 表名 291 | * @return static 模型实例 292 | */ 293 | public static function model($table = null) 294 | { 295 | $model = new static(); 296 | 297 | if ($table !== null) { 298 | $model->table($table); 299 | } 300 | 301 | return $model; 302 | } 303 | 304 | /** 305 | * 分页查询方法 306 | * 307 | * @param int $pageSize 每页记录数 308 | * @param int $currentPage 当前页码 309 | * @param mixed $where 查询条件 310 | * @param string $order 排序方式 311 | * @return array 包含数据和分页信息的数组 312 | */ 313 | public function paginate($pageSize = 10, $currentPage = 1, $where = [], $order = '') 314 | { 315 | // 查询总记录数 316 | $total = $this->count($where); 317 | 318 | // 计算总页数 319 | $totalPages = ceil($total / $pageSize); 320 | 321 | // 确保当前页码有效 322 | $currentPage = max(1, min($totalPages, $currentPage)); 323 | 324 | // 查询当前页数据 325 | $query = Db::table($this->table); 326 | 327 | // 处理查询条件 328 | if (!empty($where)) { 329 | if (is_array($where)) { 330 | $query->where($where); 331 | } elseif (is_string($where)) { 332 | $query->where($where); 333 | } 334 | } 335 | 336 | // 设置排序 337 | if (!empty($order)) { 338 | $query->order($order); 339 | } 340 | 341 | // 设置分页 342 | $query->page($pageSize, $currentPage); 343 | 344 | // 执行查询 345 | $data = $query->get(); 346 | 347 | // 返回分页数据 348 | return [ 349 | 'data' => $data, 350 | 'pagination' => [ 351 | 'total' => $total, 352 | 'per_page' => $pageSize, 353 | 'current_page' => $currentPage, 354 | 'total_pages' => $totalPages, 355 | 'has_more' => $currentPage < $totalPages 356 | ] 357 | ]; 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /startmvc/core/Pagination.php: -------------------------------------------------------------------------------- 1 | theme =isset($config['theme'])?$config['theme']:'%header% %first% %prev% %link% %next% %last%'; 17 | $this->header =isset($config['header'])?$config['header']:'共 %count% 条记录 第 %page% / %pageCount% 页'; 18 | $this->first =isset($config['first'])?$config['first']:'首页'; 19 | $this->last =isset($config['last'])?$config['last']:'末页'; 20 | $this->prev =isset($config['prev'])?$config['prev']:'上一页'; 21 | $this->next =isset($config['next'])?$config['next']:'下一页'; 22 | $this->currentClass =isset($config['currentClass'])?$config['currentClass']:'current'; 23 | } 24 | function Show($count, $pageSize, $page, $url, $pageShowCount = 10){ 25 | $pageCount = ceil($count / $pageSize); 26 | $header = '' . str_replace(['%count%', '%page%', '%pageCount%'], [$count, $page, $pageCount], $this->header) . ''; 27 | $first = '' . $this->first . ''; 28 | $last = '' . $this->last . ''; 29 | $prev = ' 1 ? ' href="' . $this->url($url, $page - 1) . '"' : '') . '>' . $this->prev . ''; 30 | $next = '' . $this->next . ''; 31 | 32 | $link = ''; 33 | $start = $page - $pageShowCount / 2; 34 | $start = $start < 1 ? 1 : $start; 35 | $end = $start + $pageShowCount - 1; 36 | if($end > $pageCount){ 37 | $end = $pageCount; 38 | $start = $end - $pageShowCount + 1; 39 | $start = $start < 1 ? 1 : $start; 40 | } 41 | for($p = $start; $p <= $end; $p++){ 42 | $link .= 'currentClass . '"'; 45 | else 46 | $link .= ' href="' . $this->url($url, $p) . '"'; 47 | $link .= '>' . $p . ''; 48 | } 49 | return str_replace([ 50 | '%header%', '%first%', '%prev%', '%link%', '%next%', '%last%' 51 | ], [ 52 | $header, $first, $prev, $link, $next, $last 53 | ], $this->theme); 54 | } 55 | function url($url, $page){ 56 | return str_replace('{page}', $page, urldecode($url)); 57 | } 58 | } -------------------------------------------------------------------------------- /startmvc/core/Request.php: -------------------------------------------------------------------------------- 1 | all(); 33 | return $key ? ($data[$key] ?? $default) : $data; 34 | } 35 | 36 | /** 37 | * 获取请求头 38 | * @param string $key 键名 39 | * @param mixed $default 默认值 40 | * @return mixed 41 | */ 42 | public function header($key = null, $default = null) 43 | { 44 | $headers = function_exists('getallheaders') ? getallheaders() : self::headers(); 45 | if ($key) { 46 | $key = strtolower($key); 47 | foreach ($headers as $headerKey => $value) { 48 | if (strtolower($headerKey) === $key) { 49 | return $value; 50 | } 51 | } 52 | return $default; 53 | } 54 | return $headers; 55 | } 56 | 57 | /** 58 | * 判断是否为AJAX请求 59 | * @return bool 60 | */ 61 | public function isAjax() 62 | { 63 | return $this->header('X-Requested-With') === 'XMLHttpRequest'; 64 | } 65 | 66 | /** 67 | * 获取GET参数 68 | * @param string $key 键名 69 | * @param array $options 处理选项 70 | * @return mixed 71 | */ 72 | public static function get($key, $options = []) 73 | { 74 | $val = isset($_GET[$key]) ? $_GET[$key] : null; 75 | return Http::handling($val, $options); 76 | } 77 | 78 | /** 79 | * 获取POST参数 80 | * @param string $key 键名(为空则返回所有POST数据) 81 | * @param array $options 处理选项 82 | * @return mixed 83 | */ 84 | public static function post($key = '', $options = []) 85 | { 86 | $val = isset($_POST[$key]) ? $_POST[$key] : ($_POST ?: null); 87 | return Http::handling($val, $options); 88 | } 89 | 90 | /** 91 | * 获取原始POST输入 92 | * @return string 93 | */ 94 | public static function postInput() 95 | { 96 | return file_get_contents('php://input'); 97 | } 98 | 99 | /** 100 | * 获取JSON格式的POST数据 101 | * @param bool $assoc 是否转换为关联数组 102 | * @return mixed 103 | */ 104 | public static function getJson($assoc = true) 105 | { 106 | return json_decode(self::postInput(), $assoc); 107 | } 108 | 109 | /** 110 | * 获取所有请求头 111 | * @return array 112 | */ 113 | public static function headers() 114 | { 115 | $headers = []; 116 | foreach ($_SERVER as $key => $value) { 117 | if ('HTTP_' == substr($key, 0, 5)) { 118 | $headers[ucfirst(strtolower(str_replace('_', '-', substr($key, 5))))] = $value; 119 | } 120 | } 121 | return $headers; 122 | } 123 | 124 | /** 125 | * 获取请求方法 126 | * @return string 127 | */ 128 | public static function method() 129 | { 130 | return strtoupper($_SERVER['REQUEST_METHOD']); 131 | } 132 | 133 | /** 134 | * 判断是否为GET请求 135 | * @return bool 136 | */ 137 | public static function isGet() 138 | { 139 | return self::method() === 'GET'; 140 | } 141 | 142 | /** 143 | * 判断是否为POST请求 144 | * @return bool 145 | */ 146 | public static function isPost() 147 | { 148 | return self::method() === 'POST'; 149 | } 150 | 151 | /** 152 | * 判断是否为HTTPS请求 153 | * @return bool 154 | */ 155 | public static function isHttps() 156 | { 157 | return isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] === 'on' || $_SERVER['HTTPS'] == 1) 158 | || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https'; 159 | } 160 | 161 | /** 162 | * 获取客户端IP地址 163 | * @return string 164 | */ 165 | public static function ip() 166 | { 167 | $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; 168 | 169 | if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && preg_match_all('#\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}#s', $_SERVER['HTTP_X_FORWARDED_FOR'], $match)) { 170 | foreach ($match[0] as $xip) { 171 | if (!preg_match('#^(10|172\.16|192\.168)\.#', $xip)) { 172 | $ip = $xip; 173 | break; 174 | } 175 | } 176 | } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) { 177 | $ip = $_SERVER['HTTP_CLIENT_IP']; 178 | } 179 | 180 | return $ip; 181 | } 182 | } -------------------------------------------------------------------------------- /startmvc/core/Response.php: -------------------------------------------------------------------------------- 1 | statusCode = $code; 40 | return $this; 41 | } 42 | 43 | /** 44 | * 设置响应头 45 | * @param string $key 头名 46 | * @param string $value 头值 47 | * @return $this 48 | */ 49 | public function setHeader($key, $value) 50 | { 51 | $this->headers[$key] = $value; 52 | return $this; 53 | } 54 | 55 | /** 56 | * 设置内容 57 | * @param string $content 响应内容 58 | * @return $this 59 | */ 60 | public function setContent($content) 61 | { 62 | $this->content = $content; 63 | return $this; 64 | } 65 | 66 | /** 67 | * 发送响应 68 | * @return void 69 | */ 70 | public function send() 71 | { 72 | // 设置状态码 73 | http_response_code($this->statusCode); 74 | 75 | // 设置响应头 76 | foreach ($this->headers as $key => $value) { 77 | header("$key: $value"); 78 | } 79 | 80 | // 输出内容 81 | echo $this->content; 82 | } 83 | 84 | /** 85 | * 返回JSON响应 86 | * @param mixed $data 数据 87 | * @param int $status 状态码 88 | * @return $this 89 | */ 90 | public function json($data, $status = 200) 91 | { 92 | $this->setHeader('Content-Type', 'application/json'); 93 | $this->setStatusCode($status); 94 | $this->setContent(json_encode($data)); 95 | return $this; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /startmvc/core/Router.php: -------------------------------------------------------------------------------- 1 | '(\d+)', 73 | ':slug' => '([a-z0-9-]+)', 74 | ':any' => '(.+)', 75 | ':num' => '([0-9]+)', 76 | ':alpha' => '([a-zA-Z]+)', 77 | ':alphanum' => '([a-zA-Z0-9]+)' 78 | ]; 79 | 80 | /** 81 | * 简单模式替换规则 82 | * @var array 83 | */ 84 | protected static $simplePatterns = [ 85 | '(:any)' => '(.+)', 86 | '(:num)' => '([0-9]+)', 87 | '(:alpha)' => '([a-zA-Z]+)', 88 | '(:alphanum)' => '([a-zA-Z0-9]+)' 89 | ]; 90 | 91 | /** 92 | * 添加GET路由 93 | * @param string $uri 路由URI 94 | * @param mixed $action 控制器方法或回调函数 95 | * @param array $middleware 中间件数组 96 | * @return void 97 | */ 98 | public static function get($uri, $action, $middleware = []) 99 | { 100 | self::addRoute('GET', $uri, $action, $middleware); 101 | } 102 | 103 | /** 104 | * 添加POST路由 105 | * @param string $uri 路由URI 106 | * @param mixed $action 控制器方法或回调函数 107 | * @param array $middleware 中间件数组 108 | * @return void 109 | */ 110 | public static function post($uri, $action, $middleware = []) 111 | { 112 | self::addRoute('POST', $uri, $action, $middleware); 113 | } 114 | 115 | /** 116 | * 添加PUT路由 117 | * @param string $uri 路由URI 118 | * @param mixed $action 控制器方法或回调函数 119 | * @param array $middleware 中间件数组 120 | * @return void 121 | */ 122 | public static function put($uri, $action, $middleware = []) 123 | { 124 | self::addRoute('PUT', $uri, $action, $middleware); 125 | } 126 | 127 | /** 128 | * 添加DELETE路由 129 | * @param string $uri 路由URI 130 | * @param mixed $action 控制器方法或回调函数 131 | * @param array $middleware 中间件数组 132 | * @return void 133 | */ 134 | public static function delete($uri, $action, $middleware = []) 135 | { 136 | self::addRoute('DELETE', $uri, $action, $middleware); 137 | } 138 | 139 | /** 140 | * 添加支持任意HTTP方法的路由 141 | * @param string $uri 路由URI 142 | * @param mixed $action 控制器方法或回调函数 143 | * @param array $middleware 中间件数组 144 | * @return void 145 | */ 146 | public static function any($uri, $action, $middleware = []) 147 | { 148 | $methods = ['GET', 'POST', 'PUT', 'DELETE']; 149 | foreach ($methods as $method) { 150 | self::addRoute($method, $uri, $action, $middleware); 151 | } 152 | } 153 | 154 | /** 155 | * 创建路由组 156 | * @param array|string $attributes 路由组属性或前缀 157 | * @param callable $callback 路由定义回调 158 | * @return void 159 | */ 160 | public static function group($attributes, callable $callback) 161 | { 162 | // 保存当前组状态 163 | $previousPrefix = self::$prefix; 164 | $previousMiddleware = self::$middleware; 165 | 166 | // 设置新组属性 167 | if (is_string($attributes)) { 168 | self::$prefix .= $attributes; 169 | } else { 170 | if (isset($attributes['prefix'])) { 171 | self::$prefix .= '/' . trim($attributes['prefix'], '/'); 172 | } 173 | 174 | if (isset($attributes['middleware'])) { 175 | $middleware = (array) $attributes['middleware']; 176 | self::$middleware = array_merge(self::$middleware, $middleware); 177 | } 178 | } 179 | 180 | // 执行回调 181 | $callback(); 182 | 183 | // 恢复先前状态 184 | self::$prefix = $previousPrefix; 185 | self::$middleware = $previousMiddleware; 186 | } 187 | 188 | /** 189 | * 添加RESTful资源路由 190 | * @param string $name 资源名称 191 | * @param string $controller 控制器类 192 | * @return void 193 | */ 194 | public static function resource($name, $controller) 195 | { 196 | $name = trim($name, '/'); 197 | self::get("/$name", "$controller@index"); 198 | self::get("/$name/create", "$controller@create"); 199 | self::post("/$name", "$controller@store"); 200 | self::get("/$name/:id", "$controller@show"); 201 | self::get("/$name/:id/edit", "$controller@edit"); 202 | self::put("/$name/:id", "$controller@update"); 203 | self::delete("/$name/:id", "$controller@destroy"); 204 | } 205 | 206 | /** 207 | * 添加路由规则 208 | * @param string $method HTTP方法 209 | * @param string $uri 路由URI 210 | * @param mixed $action 控制器方法或回调函数 211 | * @param array $middleware 中间件数组 212 | * @return void 213 | */ 214 | protected static function addRoute($method, $uri, $action, $middleware = []) 215 | { 216 | // 处理前缀 217 | $uri = self::$prefix . '/' . trim($uri, '/'); 218 | $uri = trim($uri, '/'); 219 | if (empty($uri)) { 220 | $uri = '/'; 221 | } 222 | 223 | // 存储路由 224 | self::$routes[$method][$uri] = [ 225 | 'action' => $action, 226 | 'middleware' => $middleware 227 | ]; 228 | } 229 | 230 | /** 231 | * 根据URI和方法匹配路由 232 | * @param string $uri 请求URI 233 | * @param string $method HTTP方法 234 | * @return array|null 匹配的路由和参数 235 | */ 236 | public static function match($uri, $method) 237 | { 238 | $uri = trim($uri, '/'); 239 | if (empty($uri)) { 240 | $uri = '/'; 241 | } 242 | 243 | // 检查精确匹配 244 | if (isset(self::$routes[$method][$uri])) { 245 | return [self::$routes[$method][$uri], []]; 246 | } 247 | 248 | // 检查模式匹配 249 | foreach (self::$routes[$method] ?? [] as $route => $data) { 250 | $pattern = self::compileRoute($route); 251 | if (preg_match('#^' . $pattern . '$#', $uri, $matches)) { 252 | array_shift($matches); // 移除完整匹配 253 | return [$data, $matches]; 254 | } 255 | } 256 | 257 | return null; 258 | } 259 | 260 | /** 261 | * 将路由转换为正则表达式 262 | * @param string $route 路由URI 263 | * @return string 编译后的正则表达式 264 | */ 265 | protected static function compileRoute($route) 266 | { 267 | if (strpos($route, ':') !== false) { 268 | foreach (self::$patterns as $key => $pattern) { 269 | $route = str_replace($key, $pattern, $route); 270 | } 271 | } 272 | 273 | return str_replace('/', '\/', $route); 274 | } 275 | 276 | /** 277 | * 从配置文件加载路由定义 278 | * @param array $routes 路由配置数组 279 | * @return void 280 | */ 281 | public static function loadFromConfig(array $routes) 282 | { 283 | foreach ($routes as $route) { 284 | if (is_array($route) && count($route) >= 2) { 285 | $pattern = $route[0]; 286 | $action = $route[1]; 287 | 288 | // 检查是否为正则表达式格式 /pattern/ 289 | if (is_string($pattern) && strlen($pattern) > 2 && $pattern[0] === '/' && $pattern[strlen($pattern) - 1] === '/') { 290 | // 正则表达式路由 291 | self::regexRoute('GET', $pattern, $action); 292 | } else { 293 | // 简单模式路由 294 | self::simpleRoute('GET', $pattern, $action); 295 | } 296 | } 297 | } 298 | } 299 | 300 | /** 301 | * 添加简单模式路由 302 | * @param string $method HTTP方法 303 | * @param string $pattern 路由模式 304 | * @param string $action 控制器路径 305 | * @return void 306 | */ 307 | public static function simpleRoute($method, $pattern, $action) 308 | { 309 | // 转换简单模式为正则表达式 310 | $regex = $pattern; 311 | foreach (self::$simplePatterns as $key => $replacement) { 312 | $regex = str_replace($key, $replacement, $regex); 313 | } 314 | 315 | // 如果不是正则表达式,将其转换为精确匹配的正则 316 | if ($regex === $pattern) { 317 | $regex = '/^' . preg_quote($pattern, '/') . '$/'; 318 | } else { 319 | $regex = '/^' . str_replace('/', '\/', $regex) . '$/'; 320 | } 321 | 322 | self::$routes['config'][] = [ 323 | 'type' => 'simple', 324 | 'method' => $method, 325 | 'pattern' => $pattern, 326 | 'regex' => $regex, 327 | 'action' => $action 328 | ]; 329 | } 330 | 331 | /** 332 | * 添加正则表达式路由 333 | * @param string $method HTTP方法 334 | * @param string $regex 正则表达式 335 | * @param string $action 控制器路径 336 | * @return void 337 | */ 338 | public static function regexRoute($method, $regex, $action) 339 | { 340 | self::$routes['config'][] = [ 341 | 'type' => 'regex', 342 | 'method' => $method, 343 | 'regex' => $regex, 344 | 'action' => $action 345 | ]; 346 | } 347 | 348 | /** 349 | * 解析路由并执行匹配的操作 350 | * @return mixed 351 | * @throws \Exception 未找到路由时抛出异常 352 | */ 353 | public static function dispatch() 354 | { 355 | $uri = $_SERVER['REQUEST_URI']; 356 | 357 | // 移除查询字符串 358 | if (strpos($uri, '?') !== false) { 359 | $uri = substr($uri, 0, strpos($uri, '?')); 360 | } 361 | 362 | $method = $_SERVER['REQUEST_METHOD']; 363 | 364 | // 处理PUT、DELETE请求 365 | if ($method === 'POST' && isset($_POST['_method'])) { 366 | $method = strtoupper($_POST['_method']); 367 | } 368 | 369 | // 先尝试匹配新的路由格式 370 | $result = self::match($uri, $method); 371 | if ($result !== null) { 372 | list($route, $params) = $result; 373 | 374 | // 获取路由中间件 375 | $routeMiddleware = $route['middleware'] ?? []; 376 | 377 | // 创建请求对象 378 | $request = new Request(); 379 | 380 | // 应用路由特定的中间件 381 | $response = Middleware::pipeline($routeMiddleware, $request, function($request) use ($route, $params) { 382 | // 执行控制器方法或回调 383 | $action = $route['action']; 384 | 385 | if (is_callable($action)) { 386 | return call_user_func_array($action, $params); 387 | } 388 | 389 | // 解析控制器和方法 390 | if (is_string($action)) { 391 | list($controller, $method) = explode('@', $action); 392 | $controller = 'app\\controllers\\' . $controller; 393 | $instance = new $controller(); 394 | return call_user_func_array([$instance, $method], $params); 395 | } 396 | }); 397 | 398 | return $response; 399 | } 400 | 401 | // 尝试匹配配置文件中的路由 402 | $configResult = self::matchConfigRoutes($uri); 403 | if ($configResult !== null) { 404 | list($target, $params) = $configResult; 405 | 406 | // 处理控制器路径 407 | $parts = explode('/', $target); 408 | if (count($parts) >= 2) { 409 | $methodName = array_pop($parts); 410 | $controllerName = array_pop($parts); 411 | $namespace = !empty($parts) ? implode('\\', $parts) : 'app\\controllers'; 412 | 413 | $controllerClass = $namespace . '\\' . ucfirst($controllerName) . 'Controller'; 414 | $controller = new $controllerClass(); 415 | return call_user_func_array([$controller, $methodName], $params); 416 | } else { 417 | throw new \Exception("Invalid route target: $target", 500); 418 | } 419 | } 420 | 421 | throw new \Exception("Route not found: $uri [$method]", 404); 422 | } 423 | 424 | /** 425 | * 匹配配置文件中定义的路由 426 | * @param string $uri 请求URI 427 | * @return array|null 匹配的目标和参数 428 | */ 429 | protected static function matchConfigRoutes($uri) 430 | { 431 | $uri = trim($uri, '/'); 432 | 433 | foreach (self::$routes['config'] ?? [] as $route) { 434 | $pattern = $route['regex']; 435 | $target = $route['action']; 436 | 437 | // 移除分隔符 438 | if ($route['type'] === 'regex') { 439 | $pattern = substr($pattern, 1, -1); 440 | } 441 | 442 | if (preg_match($pattern, $uri, $matches)) { 443 | array_shift($matches); // 移除完整匹配 444 | 445 | // 替换目标中的 $1, $2 等为实际参数 446 | $replacedTarget = preg_replace_callback('/\$(\d+)/', function($m) use ($matches) { 447 | $index = intval($m[1]) - 1; 448 | return isset($matches[$index]) ? $matches[$index] : ''; 449 | }, $target); 450 | 451 | return [$replacedTarget, $matches]; 452 | } 453 | } 454 | 455 | return null; 456 | } 457 | 458 | /** 459 | * 解析路由规则 460 | * @param string $uri 请求URI 461 | * @return array 解析结果 462 | */ 463 | public static function parse($uri) 464 | { 465 | // 移除查询字符串 466 | if (strpos($uri, '?') !== false) { 467 | $uri = substr($uri, 0, strpos($uri, '?')); 468 | } 469 | 470 | // 移除前后的斜杠 471 | $uri = trim($uri, '/'); 472 | 473 | // 如果URI为空,设置为首页 474 | if (empty($uri)) { 475 | return ['home', 'index', 'index']; 476 | } 477 | 478 | // 加载路由配置 479 | $routes = config('route') ?: []; 480 | 481 | // 遍历配置的路由规则 482 | foreach ($routes as $route) { 483 | if (is_array($route) && count($route) >= 2) { 484 | $pattern = $route[0]; 485 | $target = $route[1]; 486 | 487 | // 处理正则表达式路由 488 | if (is_string($pattern) && strlen($pattern) > 2 && $pattern[0] === '/' && $pattern[strlen($pattern) - 1] === '/') { 489 | if (preg_match($pattern, $uri, $matches)) { 490 | // 替换目标中的 $1, $2 等为实际参数 491 | $target = preg_replace_callback('/\$(\d+)/', function($m) use ($matches) { 492 | $index = intval($m[1]); 493 | return isset($matches[$index]) ? $matches[$index] : ''; 494 | }, $target); 495 | 496 | $parts = explode('/', $target); 497 | return [ 498 | isset($parts[0]) ? $parts[0] : 'home', 499 | isset($parts[1]) ? $parts[1] : 'index', 500 | isset($parts[2]) ? $parts[2] : 'index', 501 | array_slice($parts, 3) 502 | ]; 503 | } 504 | } 505 | // 处理简单模式路由 506 | else { 507 | $regex = $pattern; 508 | foreach (self::$simplePatterns as $key => $replacement) { 509 | $regex = str_replace($key, $replacement, $regex); 510 | } 511 | 512 | if (preg_match('#^' . $regex . '$#', $uri, $matches)) { 513 | array_shift($matches); // 移除完整匹配 514 | $parts = explode('/', $target); 515 | 516 | // 替换目标中的参数 517 | foreach ($matches as $i => $match) { 518 | $target = str_replace('$' . ($i + 1), $match, $target); 519 | } 520 | 521 | $parts = explode('/', $target); 522 | return [ 523 | isset($parts[0]) ? $parts[0] : 'home', 524 | isset($parts[1]) ? $parts[1] : 'index', 525 | isset($parts[2]) ? $parts[2] : 'index', 526 | array_slice($parts, 3) 527 | ]; 528 | } 529 | } 530 | } 531 | } 532 | 533 | // 如果没有匹配的路由规则,使用默认的解析方式 534 | $parts = explode('/', $uri); 535 | return [ 536 | isset($parts[0]) ? $parts[0] : 'home', 537 | isset($parts[1]) ? $parts[1] : 'index', 538 | isset($parts[2]) ? $parts[2] : 'index', 539 | array_slice($parts, 3) 540 | ]; 541 | } 542 | } -------------------------------------------------------------------------------- /startmvc/core/Session.php: -------------------------------------------------------------------------------- 1 | $value) { 105 | if (strpos($key, $prefix) === 0) { 106 | $newKey = substr($key, $prefixLength); 107 | $result[$newKey] = $value; 108 | } 109 | } 110 | 111 | return $result; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /startmvc/core/Upload.php: -------------------------------------------------------------------------------- 1 | $value) { 25 | if (property_exists($this, $key)) { 26 | $this->$key = $value; 27 | } 28 | } 29 | } 30 | 31 | function upload() { 32 | $results = []; 33 | foreach ($_FILES as $file) { 34 | if (is_array($file['name'])) { 35 | foreach ($file['name'] as $key => $value) { 36 | $fileInfo = []; 37 | foreach ($file as $k => $v) { 38 | $fileInfo[$k] = $v[$key]; 39 | } 40 | $results[] = $this->file($fileInfo); 41 | } 42 | } else { 43 | $results[] = $this->file($file); 44 | } 45 | } 46 | return $results; 47 | } 48 | 49 | private function file($file) { 50 | if ($file['error'] !== UPLOAD_ERR_OK) { 51 | return ['result' => false, 'error' => '文件上传错误: ' . $file['error']]; 52 | } 53 | 54 | $fileExt = pathinfo($file['name'], PATHINFO_EXTENSION); 55 | if (!in_array($fileExt, $this->exts)) { 56 | return ['result' => false, 'error' => '无效的文件扩展名']; 57 | } 58 | 59 | $saveDir = rtrim($this->savePath, '/') . '/'; 60 | $saveUrl = rtrim($this->urlPath, '/') . '/'; 61 | 62 | if ($this->autoSub) { 63 | $subDir = date('Y/m/d'); 64 | $saveDir .= $subDir; 65 | $saveUrl .= $subDir; 66 | } 67 | if (!is_dir($saveDir)&&!mkdir($saveDir, 0755, true)) { 68 | return ['result' => false, 'error' => '创建目录失败']; 69 | } 70 | 71 | //$filename = $this->autoName ? uniqid() . '.' . $fileExt : $file['name']; 72 | $filename = $this->fileName !== '' ? $this->fileName.'.'. $fileExt : ($this->autoName ? uniqid() . '.' . $fileExt : $file['name']); 73 | $filePath = $saveDir . '/' . $filename; 74 | $urlPath = $saveUrl . '/' . $filename; 75 | 76 | if (!$this->replace && file_exists($filePath)) { 77 | return ['result' => false, 'error' => '文件已经存在']; 78 | } 79 | 80 | if (!move_uploaded_file($file['tmp_name'], $filePath)) { 81 | return ['result' => false, 'error' => '移动上传文件失败']; 82 | } 83 | 84 | return ['result' => true, 'url' => $urlPath,'filename'=>$filename]; 85 | } 86 | } -------------------------------------------------------------------------------- /startmvc/core/Validator.php: -------------------------------------------------------------------------------- 1 | '%s不能为空', 39 | 'len' => '%s长度必须为%s个字符', 40 | 'minlen' => '%s最小长度为%s个字符', 41 | 'maxlen' => '%s最大长度为%s个字符', 42 | 'width' => '%s长度必须为%s个字符', 43 | 'minwidth' => '%s最小长度为%s个字符', 44 | 'maxwidth' => '%s最大长度为%s个字符', 45 | 'gt' => '%s必须大于%s', 46 | 'lt' => '%s必须小于%s。', 47 | 'gte' => '%s必须大于等于%s', 48 | 'lte' => '%s必须小于等于%s', 49 | 'eq' => '%s必须是%s', 50 | 'neq' => '%s不能是%s', 51 | 'in' => '%s只能是%s', 52 | 'nin' => '%s不能是%s', 53 | 'same' => '%s和%s必须一致', 54 | 'is_mobile' => '手机号错误', 55 | 'is_email' => '邮箱地址错误', 56 | 'is_idcard' => '身份证号错误', 57 | 'is_ip' => 'IP地址错误', 58 | 'is_url' => 'URL地址错误', 59 | 'is_array' => '%s必须是数组', 60 | 'is_float' => '%s必须是浮点数', 61 | 'is_int' => '%s必须是整数', 62 | 'is_numeric' => '%s必须是数字', 63 | 'is_string' => '%s必须是字符', 64 | 'is_natural' => '%s必须是自然数', 65 | 'is_natural_no_zero' => '%s必须是非零自然数', 66 | 'is_hanzi' => '%s必须是中文', 67 | 'is_mongoid' => '%s不是有效的MongoID', 68 | 'is_alpha' => '%s必须是字母', 69 | 'is_alpha_num' => '%s必须是字母或者数字', 70 | 'is_alpha_num_dash' => '%s必须是字母、数字或者下划线', 71 | ]; 72 | /** 73 | * 系统默认错误模板 74 | * @var array 75 | */ 76 | private $_defaultTpl = [ 77 | 'default' => '%s格式错误', 78 | 'call_error' => '%s cannot be callable', 79 | ]; 80 | /** 81 | * 自定义函数 82 | * @var array 83 | */ 84 | private $_tagMap = []; 85 | /** 86 | * Constructor 87 | */ 88 | public function __construct() 89 | { 90 | if (function_exists('mb_internal_encoding')) { 91 | mb_internal_encoding('UTF-8'); 92 | } 93 | } 94 | /** 95 | * 是否默认去除两端空格 96 | * @param bool $autoTrim 97 | */ 98 | public function setFieldAutoTrim($autoTrim = true) 99 | { 100 | $this->fieldAutoTrim = $autoTrim; 101 | } 102 | /** 103 | * 设置自定义函数 104 | * @param $tag 105 | * @param callable $func 106 | */ 107 | public function setTagMap($tag, callable $func) 108 | { 109 | $this->_tagMap[$tag] = $func; 110 | } 111 | /** 112 | * 设置验证规则 113 | * @param array $rules 114 | * @return $this 115 | */ 116 | public function setRules($rules = []) 117 | { 118 | if (empty($rules) || !is_array($rules)) { 119 | return $this; 120 | } 121 | $this->_rules = $rules; 122 | return $this; 123 | } 124 | /** 125 | * 验证数据 126 | * @param array $data 127 | * @return bool 128 | * @throws \Exception 129 | */ 130 | public function validate(array $data = []) 131 | { 132 | if (count($this->_rules) == 0) { 133 | return true; 134 | } 135 | $result = $this->_perform($data, $this->_rules); 136 | if (false === $result) { 137 | return false; 138 | } else { 139 | $this->_finalData = $result; 140 | } 141 | if (count($this->_errorList) == 0) { 142 | return true; 143 | } 144 | return false; 145 | } 146 | /** 147 | * 递归执行规则 148 | * @param $data 149 | * @param $rules 150 | * @return bool 151 | */ 152 | private function _perform($data, $rules) 153 | { 154 | foreach ($rules as $field => $rule) { 155 | if (empty($field)) { 156 | continue; 157 | } 158 | if (is_array($rule)) { 159 | $data[$field] = $this->_perform($data[$field] ?? null, $rule); 160 | } else { 161 | $rule = $this->_parseOneRule($rule); 162 | $result = $this->_executeOneRule($data, $field, $rule['rules'], $rule['label'], $rule['msg']); 163 | if (false === $result) { 164 | return false; 165 | } 166 | if (!is_bool($result)) { 167 | $data[$field] = $result; 168 | } 169 | } 170 | } 171 | return $data; 172 | } 173 | /** 174 | * 解析单条规则 175 | * @param $rule 176 | * @return array 177 | */ 178 | private function _parseOneRule($rule): array 179 | { 180 | $label = $msg = ''; 181 | if (false !== strpos($rule, '`')) { 182 | if (preg_match('/``(.*)``/', $rule, $matches)) { 183 | $msg = $matches[1]; 184 | $rule = preg_replace('/`(.*)`/', '', $rule); 185 | } elseif (preg_match('/`(.*)`/', $rule, $matches)) { 186 | $label = $matches[1]; 187 | $rule = preg_replace('/`(.*)`/', '', $rule); 188 | } 189 | } 190 | return ['rules' => empty($rule) ? [] : explode('|', trim($rule)), 'label' => $label, 'msg' => $msg]; 191 | } 192 | /** 193 | * 执行一条验证规则 194 | * @param $data 195 | * @param $field 196 | * @param array $rules 197 | * @param string $label 198 | * @param string $msg 199 | * @return bool 200 | */ 201 | private function _executeOneRule($data, $field, $rules = [], $label = '', $msg = '') 202 | { 203 | if (empty($rules)) { 204 | return true; 205 | } 206 | //Auto Trim 207 | if ($this->fieldAutoTrim && isset($data[$field]) && (gettype($data[$field]) == 'string')) { 208 | $data[$field] = trim($data[$field]); 209 | } 210 | if (in_array('required', $rules)) { 211 | if (!isset($data[$field]) || !$this->required($data[$field])) { 212 | if (empty($msg)) { 213 | $msg = sprintf($this->_getErrorTpl('required'), $label ?: $field); 214 | } 215 | $this->_setError($field, $msg); 216 | return false; 217 | } 218 | $rules = array_diff($rules, ['required']); 219 | } else { 220 | if (in_array('is_array', $rules)) { 221 | if (!isset($data[$field]) || !is_array($data[$field])) { 222 | $data[$field] = []; 223 | } 224 | $rules = array_diff($rules, ['is_array']); 225 | } elseif (!isset($data[$field]) || !strlen(strval($data[$field]))) { 226 | return true; 227 | } 228 | } 229 | foreach ($rules as $rule) { 230 | if (empty($rule)) { 231 | continue; 232 | } 233 | //判断是否有参数 比如 max_length:8 234 | $param = []; 235 | $rawParam = ''; 236 | if (strpos($rule, ':') !== false) { 237 | $match = explode(':', $rule); 238 | $rule = $match[0]; 239 | $rawParam = $match[1]; 240 | $param = explode(',', $match[1]); 241 | } 242 | //处理参数传递顺序 243 | if (false !== ($place = array_search('@@', $param))) { 244 | $param[$place] = $data[$field]; 245 | } else { 246 | array_unshift($param, $data[$field]); 247 | } 248 | //same只能是同一层级的对比 249 | if ($rule == 'same') { 250 | $param[1] = $data[$param[1]] ?? null; 251 | } 252 | //下划线转驼峰 253 | $methodSnakeMapper = $this->_getMethodSnakeMapper($rule); 254 | if (method_exists(__CLASS__, $methodSnakeMapper)) { 255 | $result = call_user_func_array([__CLASS__, $methodSnakeMapper], $param); 256 | } elseif (function_exists($methodSnakeMapper)) { 257 | $result = call_user_func_array($methodSnakeMapper, $param); 258 | } elseif (method_exists(__CLASS__, $rule)) { 259 | $result = call_user_func_array([__CLASS__, $rule], $param); 260 | } elseif (function_exists($rule)) { 261 | $result = call_user_func_array($rule, $param); 262 | } elseif (isset($this->_tagMap[$rule])) { 263 | $result = call_user_func_array($this->_tagMap[$rule], $param); 264 | } else { 265 | $result = false; 266 | $msg = sprintf($this->_defaultTpl['call_error'], $rule); 267 | } 268 | if ($result === false) { 269 | if (empty($msg)) { 270 | $tpl = $this->_getErrorTpl($rule); 271 | if (false === $tpl) { 272 | $msg = sprintf($this->_defaultTpl['default'], $label ?: $field); 273 | } else { 274 | $msg = sprintf($tpl, $label ?: $field, $rawParam); 275 | } 276 | } 277 | $this->_setError($field, $msg); 278 | //continue; 279 | //break when an error accured 280 | return false; 281 | } 282 | //filter data 283 | if (!is_bool($result)) { 284 | $data[$field] = $result; 285 | } 286 | } 287 | return $data[$field]; 288 | } 289 | /** 290 | * 下划线转驼峰函数名 291 | * @param $method 292 | * @return string 293 | */ 294 | private function _getMethodSnakeMapper($method) 295 | { 296 | $method = array_reduce(explode('_', $method), function ($str, $item) { 297 | $str .= ucfirst($item); 298 | return $str; 299 | }); 300 | return lcfirst($method); 301 | } 302 | /** 303 | * 获取错误模板 304 | * @param string $tag 305 | * @return bool|mixed 306 | */ 307 | private function _getErrorTpl($tag = '') 308 | { 309 | return ($tag == '' || !isset($this->_errorTpl[$tag])) ? false : $this->_errorTpl[$tag]; 310 | } 311 | /** 312 | * 设置错误 313 | * @param string $field 314 | * @param string $message 315 | */ 316 | private function _setError($field = '', $message = '') 317 | { 318 | if (!isset($this->_errorList[$field])) { 319 | $this->_errorList[$field] = $message; 320 | } 321 | } 322 | /** 323 | * 获取所有数据 含未验证字段 324 | * @return array|mixed|string 325 | */ 326 | public function getAllData() 327 | { 328 | return $this->_finalData; 329 | } 330 | /** 331 | * 获取所有数据 不含未验证字段 332 | * @return mixed 333 | */ 334 | public function getData() 335 | { 336 | return $this->_getDataByRules($this->_rules, $this->_finalData); 337 | } 338 | /** 339 | * 递归获取数据 340 | * @param $rules 341 | * @param $data 342 | * @return array 343 | */ 344 | private function _getDataByRules($rules, $data) 345 | { 346 | $result = []; 347 | foreach ($rules as $field => $rule) { 348 | if (is_array($rule)) { 349 | $result[$field] = $this->_getDataByRules($rule, $data[$field] ?? null); 350 | } else { 351 | if (array_key_exists($field, $data)) { 352 | $result[$field] = $data[$field]; 353 | } 354 | } 355 | } 356 | return $result; 357 | } 358 | /** 359 | * 获取指定key的数据 支持多级key 如user.hobby.name 360 | * @param string $field 361 | * @return array|mixed|string 362 | */ 363 | public function getDataByField($field) 364 | { 365 | if (false === strpos($field, '.')) { 366 | return empty($field) ? '' : (array_key_exists($field, $this->_finalData) ? $this->_finalData[$field] : ''); 367 | } 368 | $fields = explode('.', $field); 369 | $data = $this->_finalData; 370 | foreach ($fields as $field) { 371 | if (array_key_exists($field, $data)) { 372 | $data = $data[$field]; 373 | } else { 374 | return ''; 375 | } 376 | } 377 | return $data; 378 | } 379 | /** 380 | * 返回数组形式的错误 381 | * @return array 382 | */ 383 | public function getError(): array 384 | { 385 | return $this->_errorList; 386 | } 387 | /** 388 | * 获取字符形式的错误 389 | * @param string $newline eg:
、\n 390 | * @return string 391 | */ 392 | public function getErrorString($newline = "\n"): string 393 | { 394 | return join($newline, $this->_errorList); 395 | } 396 | /** 397 | * Required 398 | * @param $var 399 | * @return bool 400 | */ 401 | public function required($var): bool 402 | { 403 | return !(empty($var) && !is_numeric($var)); 404 | } 405 | /** 406 | * 和另外一个字段值相同 407 | * @param $var 408 | * @param $compare_var 409 | * @return bool 410 | */ 411 | public function same($var, $compare_var): bool 412 | { 413 | return ($var === $compare_var) ? true : false; 414 | } 415 | /** 416 | * 字符长度必须等于 417 | * @param $var 418 | * @param $len 419 | * @return bool 420 | */ 421 | public static function len($var, $len): bool 422 | { 423 | $len = intval($len); 424 | return (mb_strlen($var) != $len) ? false : true; 425 | } 426 | /** 427 | * 字符最小长度 一个中文算1个字符 428 | * @param $var 429 | * @param $len 430 | * @return bool 431 | */ 432 | public static function minlen($var, $len): bool 433 | { 434 | $len = intval($len); 435 | return (mb_strlen($var) < $len) ? false : true; 436 | } 437 | /** 438 | * 字符最大长度 一个中文算1个字符 439 | * @param $var 440 | * @param $len 441 | * @return bool 442 | */ 443 | public static function maxlen($var, $len): bool 444 | { 445 | $len = intval($len); 446 | return (mb_strlen($var) > $len) ? false : true; 447 | } 448 | /** 449 | * 字符宽度必须等于 一个中文算2个字符 450 | * @param $var 451 | * @param $len 452 | * @return bool 453 | */ 454 | public static function width($var, $len): bool 455 | { 456 | $len = intval($len); 457 | return (mb_strwidth($var) != $len) ? false : true; 458 | } 459 | /** 460 | * 字符最小宽度 一个中文算2个字符 461 | * @param $var 462 | * @param $len 463 | * @return bool 464 | */ 465 | public static function minwidth($var, $len): bool 466 | { 467 | $len = intval($len); 468 | return (mb_strwidth($var) < $len) ? false : true; 469 | } 470 | /** 471 | * 字符最大宽度 一个中文算2个字符 472 | * @param $var 473 | * @param $len 474 | * @return bool 475 | */ 476 | public static function maxwidth($var, $len): bool 477 | { 478 | $len = intval($len); 479 | return (mb_strwidth($var) > $len) ? false : true; 480 | } 481 | /** 482 | * 是否手机号 483 | * @param $var 484 | * @return bool 485 | */ 486 | public static function isMobile($var): bool 487 | { 488 | return !!preg_match("/^1[3-9][0-9]{9}$/", $var); 489 | } 490 | /** 491 | * 是否邮箱 492 | * @access public 493 | * @param string 494 | * @return bool 495 | */ 496 | public static function isEmail($var): bool 497 | { 498 | return !!filter_var($var, FILTER_VALIDATE_EMAIL); 499 | } 500 | /** 501 | * 是否IP地址 502 | * @param string $var 503 | * @param string $type 504 | * @return boolean 505 | */ 506 | public static function isIp($var, $type = 'ipv4'): bool 507 | { 508 | $type = strtolower($type); 509 | switch ($type) { 510 | case 'ipv6': 511 | $flag = FILTER_FLAG_IPV6; 512 | break; 513 | default: 514 | $flag = FILTER_FLAG_IPV4; 515 | break; 516 | } 517 | return !!filter_var($var, FILTER_VALIDATE_IP, $flag); 518 | } 519 | /** 520 | * 是否有效的URL地址 521 | * @param $var 522 | * @return bool 523 | */ 524 | public static function isUrl($var): bool 525 | { 526 | //return !!filter_var($var, FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED); 527 | return !!filter_var($var, FILTER_VALIDATE_URL); 528 | } 529 | /** 530 | * 是否身份证 531 | * @param $var 532 | * @return bool 533 | */ 534 | public static function isIdcard($var): bool 535 | { 536 | if (preg_match('/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$)/', $var)) { 537 | return true; 538 | } 539 | return false; 540 | } 541 | /** 542 | * 自然数 (0,1,2,3, etc.) 543 | * @access public 544 | * @param string 545 | * @return bool 546 | */ 547 | public static function isNatural($var): bool 548 | { 549 | return (bool)preg_match('/^[0-9]+$/', $var); 550 | } 551 | /** 552 | * 自然数除了0 (1,2,3, etc.) 553 | * @access public 554 | * @param string 555 | * @return bool 556 | */ 557 | public static function isNaturalNoZero($var): bool 558 | { 559 | if (!preg_match('/^[0-9]+$/', $var)) { 560 | return false; 561 | } 562 | if ($var == 0) { 563 | return false; 564 | } 565 | return true; 566 | } 567 | /** 568 | * 判断是否中文 569 | * @param $var 570 | * @return bool 571 | */ 572 | public static function isHanzi($var): bool 573 | { 574 | if (preg_match('/^\p{Han}+$/u', $var)) { 575 | return true; 576 | } 577 | return false; 578 | } 579 | /** 580 | * 是否有效的mongoid 581 | * @param $var 582 | * @return bool 583 | */ 584 | public static function isMongoid($var): bool 585 | { 586 | return preg_match('/^[0-9a-fA-F]+$/', $var) && (strlen($var) == 24); 587 | } 588 | /** 589 | * 字母 590 | * @param $var 591 | * @return bool 592 | */ 593 | public static function isAlpha($var): bool 594 | { 595 | return (!preg_match("/^([a-z])+$/i", $var)) ? false : true; 596 | } 597 | /** 598 | * 字母数字 599 | * @param $var 600 | * @return bool 601 | */ 602 | public static function isAlphaNum($var): bool 603 | { 604 | return (!preg_match("/^([a-z0-9])+$/i", $var)) ? false : true; 605 | } 606 | /** 607 | * 字母、数字、下划线 608 | * @param $var 609 | * @return bool 610 | */ 611 | public static function isAlphaNumDash($var): bool 612 | { 613 | return (!preg_match("/^([a-z0-9_])+$/i", $var)) ? false : true; 614 | } 615 | /** 616 | * 大于 617 | * @param $var 618 | * @param $min 619 | * @return bool 620 | */ 621 | public static function gt($var, $min): bool 622 | { 623 | if (!is_numeric($var)) { 624 | return false; 625 | } 626 | return $var > $min; 627 | } 628 | /** 629 | * 小于 630 | * @param $var 631 | * @param $max 632 | * @return bool 633 | */ 634 | public static function lt($var, $max): bool 635 | { 636 | if (!is_numeric($var)) { 637 | return false; 638 | } 639 | return $var < $max; 640 | } 641 | /** 642 | * 大于等于 643 | * @param $var 644 | * @param $min 645 | * @return bool 646 | */ 647 | public static function gte($var, $min): bool 648 | { 649 | if (!is_numeric($var)) { 650 | return false; 651 | } 652 | return $var >= $min; 653 | } 654 | /** 655 | * 小于等于 656 | * @param $var 657 | * @param $max 658 | * @return bool 659 | */ 660 | public static function lte($var, $max): bool 661 | { 662 | if (!is_numeric($var)) { 663 | return false; 664 | } 665 | return $var <= $max; 666 | } 667 | /** 668 | * 等于 669 | * @param $var 670 | * @param $obj 671 | * @return bool 672 | */ 673 | public static function eq($var, $obj): bool 674 | { 675 | if (!is_numeric($var) && empty($var)) { 676 | return false; 677 | } 678 | return $var == $obj; 679 | } 680 | /** 681 | * 不等于 682 | * @param $var 683 | * @param $obj 684 | * @return bool 685 | */ 686 | public static function neq($var, $obj): bool 687 | { 688 | if (!is_numeric($var) && empty($var)) { 689 | return false; 690 | } 691 | return $var != $obj; 692 | } 693 | /** 694 | * 必须在集合中 695 | * @param $var 696 | * @param array ...$set 697 | * @return bool 698 | */ 699 | public static function in($var, ...$set): bool 700 | { 701 | if (in_array($var, $set)) { 702 | return true; 703 | } 704 | return false; 705 | } 706 | /** 707 | * 不在集合中 708 | * @param $var 709 | * @param array ...$set 710 | * @return bool 711 | */ 712 | public static function nin($var, ...$set): bool 713 | { 714 | if (!in_array($var, $set)) { 715 | return true; 716 | } 717 | return false; 718 | } 719 | /** 720 | * 过滤三字节以上的字符 721 | * @param $var 722 | * @return string 723 | */ 724 | public static function filterUtf8($var): string 725 | { 726 | /*utf8 编码表: 727 | * Unicode符号范围 | UTF-8编码方式 728 | * u0000 0000 - u0000 007F | 0xxxxxxx 729 | * u0000 0080 - u0000 07FF | 110xxxxx 10xxxxxx 730 | * u0000 0800 - u0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx 731 | * 732 | */ 733 | $ret = ''; 734 | $var = str_split(bin2hex($var), 2); 735 | $mo = 1 << 7; 736 | $mo2 = $mo | (1 << 6); 737 | $mo3 = $mo2 | (1 << 5); //三个字节 738 | $mo4 = $mo3 | (1 << 4); //四个字节 739 | $mo5 = $mo4 | (1 << 3); //五个字节 740 | $mo6 = $mo5 | (1 << 2); //六个字节 741 | for ($i = 0; $i < count($var); $i++) { 742 | if ((hexdec($var[$i]) & ($mo)) == 0) { 743 | $ret .= chr(hexdec($var[$i])); 744 | continue; 745 | } 746 | //4字节 及其以上舍去 747 | if ((hexdec($var[$i]) & ($mo6)) == $mo6) { 748 | $i = $i + 5; 749 | continue; 750 | } 751 | if ((hexdec($var[$i]) & ($mo5)) == $mo5) { 752 | $i = $i + 4; 753 | continue; 754 | } 755 | if ((hexdec($var[$i]) & ($mo4)) == $mo4) { 756 | $i = $i + 3; 757 | continue; 758 | } 759 | if ((hexdec($var[$i]) & ($mo3)) == $mo3) { 760 | $i = $i + 2; 761 | if (((hexdec($var[$i]) & ($mo)) == $mo) && ((hexdec($var[$i - 1]) & ($mo)) == $mo)) { 762 | $r = chr(hexdec($var[$i - 2])) . 763 | chr(hexdec($var[$i - 1])) . 764 | chr(hexdec($var[$i])); 765 | $ret .= $r; 766 | } 767 | continue; 768 | } 769 | if ((hexdec($var[$i]) & ($mo2)) == $mo2) { 770 | $i = $i + 1; 771 | if ((hexdec($var[$i]) & ($mo)) == $mo) { 772 | $ret .= chr(hexdec($var[$i - 1])) . chr(hexdec($var[$i])); 773 | } 774 | continue; 775 | } 776 | } 777 | return $ret; 778 | } 779 | } -------------------------------------------------------------------------------- /startmvc/core/View.php: -------------------------------------------------------------------------------- 1 | '', 30 | 31 | // array: {$array.key} 32 | '/{\$([0-9a-z_]{1,})\.([0-9a-z_]{1,})}/i' => '', 33 | 34 | // two-demensional array 35 | '/{\$([0-9a-z_]{1,})\.([0-9a-z_]{1,})\.([0-9a-z_]{1,})}/i' => '', 36 | 37 | // for loop 38 | '/{for ([^\}]+)}/i' => '', 39 | '/{\/for}/i' => '', 40 | 41 | // foreach ( $array as $key => $value ) 42 | '/{loop\s+\$([^\}]{1,})\s+\$([^\}]{1,})\s+\$([^\}]{1,})\s*}/i' => ' $${3} ) { ?>', 43 | '/{\/loop}/i' => '', 44 | 45 | // foreach ( $array as $value ) 46 | '/{loop\s+\$(.*?)\s+\$([0-9a-z_]{1,})\s*}/i' => '', 47 | 48 | // foreach ( $array as $key => $value ) 49 | '/{foreach\s+(.*?)}/i' => '', 50 | //end foreach 51 | '/{\/foreach}/i' => '', 52 | 53 | // php: excute the php expression 54 | // echo: print the php expression 55 | '/{php\s+(.*?)}/i' => '', 56 | '/{echo\s+(.*?)}/i' => '', 57 | 58 | // if else tag 59 | '/{if\s+(.*?)}/i' => '', 60 | '/{else}/i' => '', 61 | '/{elseif\s+(.*?)}/i' => '', 62 | '/{\/if}/i' => '', 63 | 64 | //lang 65 | '/\{lang\(\'([^\']+)\'\)\}/'=>'', 66 | 67 | // require|include tag 68 | '/{include\s+([^}]+)\}/i'=> 'getInclude(\'${1}\')?>', 69 | 70 | // comment tag (不会被解析) 71 | '/{\/\*(.*?)\*\/}/s' => '', 72 | 73 | // 三元运算 74 | '/{\$([^\}|\.]{1,})\?(.*?):(.*?)}/i' => '', 75 | 76 | // 输出带HTML标签的内容 77 | '/{html\s+\$(.*?)}/i' => '', 78 | 79 | // 日期格式化 80 | '/{date\s+\$(.*?)\s+(.*?)}/i' => '', 81 | ]; 82 | 83 | function __construct(){ 84 | // 使用常量或默认值 85 | $module = defined('MODULE') ? MODULE : 'home'; 86 | $controller = defined('CONTROLLER') ? CONTROLLER : 'Index'; 87 | $action = defined('ACTION') ? ACTION : 'index'; 88 | 89 | $theme=config('theme')?config('theme').DS:''; 90 | $this->tpl_template_dir = APP_PATH .MODULE . DS. 'view'.DS.$theme; 91 | $this->tpl_compile_dir = TEMP_PATH.MODULE.DS; 92 | $this->left_delimiter_quote = preg_quote($this->tpl_left_delimiter); 93 | $this->right_delimiter_quote = preg_quote($this->tpl_right_delimiter); 94 | 95 | // 读取配置的缓存时间 96 | $this->tpl_cache_time = intval(config('tpl_cache_time', 0)); 97 | } 98 | 99 | //模板赋值 100 | public function assign($name, $value='') { 101 | if (is_array($name)) { 102 | foreach ($name as $k => $v) { 103 | if ($k != '') { 104 | $this->vars[$k] = $v; 105 | } 106 | } 107 | } else { 108 | $this->vars[$name] = $value; 109 | } 110 | return $this; // 支持链式调用 111 | } 112 | 113 | //视图渲染 支持多级目录 114 | public function display($name='', $data=[]) 115 | { 116 | if ($name == '') { 117 | $name = strtolower(CONTROLLER . DS . ACTION); 118 | } 119 | 120 | $tplFile = $this->tpl_template_dir . $name .'.php'; 121 | $cacheFile = $this->tpl_compile_dir . $name .'.php'; 122 | // 模板文件不存在直接返回 123 | if (!file_exists($tplFile)) { 124 | throw new \Exception($tplFile.' 模板文件不存在'); 125 | } 126 | 127 | if (!empty($data)) { 128 | $this->vars = array_merge_recursive($this->vars, $data); 129 | } 130 | // 将变量导入到当前 131 | extract($this->vars); 132 | // 获取渲染后的内容 133 | ob_start(); 134 | $this->_compile($tplFile, $cacheFile); 135 | include $cacheFile; 136 | $content = ob_get_clean(); 137 | 138 | // 直接输出内容,不要处理trace 139 | echo $content; 140 | 141 | return $this; // 支持链式调用 142 | } 143 | 144 | // 返回渲染后的内容,而不是直接输出 145 | public function fetch($name='', $data=[]) 146 | { 147 | if ($name == '') { 148 | $name = strtolower(CONTROLLER . DS . ACTION); 149 | } 150 | 151 | $tplFile = $this->tpl_template_dir . $name .'.php'; 152 | $cacheFile = $this->tpl_compile_dir . $name .'.php'; 153 | // 模板文件不存在直接返回 154 | if (!file_exists($tplFile)) { 155 | throw new \Exception($tplFile.' 模板文件不存在'); 156 | } 157 | 158 | if (!empty($data)) { 159 | $this->vars = array_merge_recursive($this->vars, $data); 160 | } 161 | // 将变量导入到当前 162 | extract($this->vars); 163 | // 获取渲染后的内容 164 | ob_start(); 165 | $this->_compile($tplFile, $cacheFile); 166 | include $cacheFile; 167 | return ob_get_clean(); 168 | } 169 | 170 | /** 171 | * compile template 172 | */ 173 | private function _compile($tplFile, $cacheFile) 174 | { 175 | $tplCacheDir = dirname($cacheFile); 176 | 177 | // 检查缓存是否有效 178 | if (file_exists($cacheFile)) { 179 | $cacheModified = filemtime($cacheFile); 180 | $tplModified = filemtime($tplFile); 181 | 182 | // 如果缓存未过期且模板未修改,直接使用缓存 183 | if ($this->tpl_cache_time > 0 && 184 | (time() - $cacheModified < $this->tpl_cache_time) && 185 | $tplModified <= $cacheModified) { 186 | return; 187 | } 188 | } 189 | 190 | // 编译模板 191 | $content = @file_get_contents($tplFile); 192 | if ($content === false) { 193 | throw new \Exception("无法加载模板文件 {$tplFile}"); 194 | } 195 | 196 | // 增加编译前的钩子,可以自定义修改模板内容 197 | if (method_exists($this, 'beforeCompile')) { 198 | $content = $this->beforeCompile($content); 199 | } 200 | 201 | // 执行模板标签替换 202 | $content = preg_replace(array_keys(self::$rules), self::$rules, $content); 203 | 204 | // 增加编译后的钩子 205 | if (method_exists($this, 'afterCompile')) { 206 | $content = $this->afterCompile($content); 207 | } 208 | 209 | // 确保缓存目录存在 210 | if (!is_dir($tplCacheDir)) { 211 | mkdir($tplCacheDir, 0777, true); 212 | } 213 | 214 | // 添加编译时间戳注释 215 | $content = "\n" . $content; 216 | 217 | file_put_contents($cacheFile, $content, LOCK_EX); 218 | } 219 | 220 | // 获取被包含模板的路径 221 | public function getInclude($name = null) { 222 | if (empty($name)) { 223 | return ''; 224 | } 225 | 226 | $tplFile = $this->tpl_template_dir . $name . '.php'; 227 | 228 | if (file_exists($tplFile)) { 229 | // 读取包含文件内容 230 | $content = file_get_contents($tplFile); 231 | 232 | // 递归编译包含文件中的包含标签 233 | $content = preg_replace_callback( 234 | '/{include\s+([^}]+)}/i', 235 | function($matches) { 236 | return $this->getInclude($matches[1]); 237 | }, 238 | $content 239 | ); 240 | 241 | // 编译其他模板标签 242 | $content = preg_replace(array_keys(self::$rules), self::$rules, $content); 243 | 244 | return "\n" . $content; 245 | } 246 | 247 | return ''; 248 | } 249 | 250 | // 清除模板缓存 251 | public function clearCache($name = null) { 252 | if ($name === null) { 253 | // 清除所有缓存 254 | $this->_clearDir($this->tpl_compile_dir); 255 | } else { 256 | // 清除指定模板缓存 257 | $cacheFile = $this->tpl_compile_dir . $name . '.php'; 258 | if (file_exists($cacheFile)) { 259 | @unlink($cacheFile); 260 | } 261 | } 262 | return $this; 263 | } 264 | 265 | // 清空目录 266 | private function _clearDir($dir) { 267 | if (!is_dir($dir)) return; 268 | 269 | $handle = opendir($dir); 270 | while (false !== ($file = readdir($handle))) { 271 | if ($file != '.' && $file != '..') { 272 | $path = $dir . $file; 273 | if (is_dir($path)) { 274 | $this->_clearDir($path . DS); 275 | @rmdir($path); 276 | } else { 277 | @unlink($path); 278 | } 279 | } 280 | } 281 | closedir($handle); 282 | } 283 | } -------------------------------------------------------------------------------- /startmvc/core/cache/File.php: -------------------------------------------------------------------------------- 1 | cacheDir = ROOT_PATH . '/runtime/' . $params['cacheDir']; 32 | $this->cacheTime = $params['cacheTime']; 33 | 34 | if (!file_exists($this->cacheDir)) { 35 | mkdir($this->cacheDir, 0777, true); 36 | } 37 | } 38 | 39 | /** 40 | * 获取缓存文件路径 41 | * @param string $key 缓存键名 42 | * @return string 缓存文件路径 43 | */ 44 | private function getPath($key) { 45 | return $this->cacheDir . md5($key) . '.cache'; 46 | } 47 | 48 | /** 49 | * 设置缓存 50 | * @param string $key 缓存键名 51 | * @param mixed $data 缓存数据 52 | * @return void 53 | */ 54 | public function set($key, $data) { 55 | $cacheFile = $this->getPath($key); 56 | $cacheData = [ 57 | 'data' => $data, 58 | 'expire' => time() + $this->cacheTime 59 | ]; 60 | file_put_contents($cacheFile, serialize($cacheData)); 61 | } 62 | 63 | /** 64 | * 获取缓存 65 | * @param string $key 缓存键名 66 | * @return mixed 缓存数据,不存在或已过期返回null 67 | */ 68 | public function get($key) { 69 | $cacheFile = $this->getPath($key); 70 | 71 | if (!file_exists($cacheFile)) { 72 | return null; 73 | } 74 | 75 | $cacheData = unserialize(file_get_contents($cacheFile)); 76 | 77 | // 检查是否过期 78 | if (time() > $cacheData['expire']) { 79 | $this->delete($key); 80 | return null; 81 | } 82 | 83 | return $cacheData['data']; 84 | } 85 | 86 | /** 87 | * 检查缓存是否存在且有效 88 | * @param string $key 缓存键名 89 | * @return bool 90 | */ 91 | public function has(string $key): bool { 92 | $cacheFile = $this->getPath($key); 93 | 94 | if (!file_exists($cacheFile)) { 95 | return false; 96 | } 97 | 98 | $cacheData = unserialize(file_get_contents($cacheFile)); 99 | return time() <= $cacheData['expire']; 100 | } 101 | 102 | /** 103 | * 删除缓存 104 | * @param string $key 缓存键名 105 | * @return void 106 | */ 107 | public function delete($key) { 108 | $cacheFile = $this->getPath($key); 109 | if (file_exists($cacheFile)) { 110 | unlink($cacheFile); 111 | } 112 | } 113 | 114 | /** 115 | * 清空所有缓存 116 | * @return void 117 | */ 118 | public function clear() { 119 | $files = glob($this->cacheDir . '*.cache'); 120 | foreach ($files as $file) { 121 | unlink($file); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /startmvc/core/cache/Redis.php: -------------------------------------------------------------------------------- 1 | redis = new \Redis(); 38 | 39 | // 连接Redis服务器 40 | $connect = $this->redis->connect($params['host'], $params['port']); 41 | if (!$connect) { 42 | throw new \Exception('Redis连接失败'); 43 | } 44 | 45 | // 设置认证和数据库 46 | if (!empty($params['password'])) { 47 | $this->redis->auth($params['password']); 48 | } 49 | 50 | $this->redis->select((int)$params['database']); 51 | $this->cacheTime = $params['cacheTime']; 52 | } 53 | 54 | /** 55 | * 获取带前缀的缓存键名 56 | * @param string $key 原始键名 57 | * @return string 带前缀的键名 58 | */ 59 | private function getKey($key) { 60 | return 'cache:' . md5($key); 61 | } 62 | 63 | /** 64 | * 设置缓存 65 | * @param string $key 缓存键名 66 | * @param mixed $data 缓存数据 67 | * @return bool 是否成功 68 | */ 69 | public function set($key, $data) { 70 | $cacheKey = $this->getKey($key); 71 | $cacheData = [ 72 | 'data' => $data, 73 | 'expire' => time() + $this->cacheTime 74 | ]; 75 | 76 | return $this->redis->set($cacheKey, serialize($cacheData), $this->cacheTime); 77 | } 78 | 79 | /** 80 | * 获取缓存 81 | * @param string $key 缓存键名 82 | * @return mixed 缓存数据,不存在或已过期返回null 83 | */ 84 | public function get($key) { 85 | $cacheKey = $this->getKey($key); 86 | $cacheData = $this->redis->get($cacheKey); 87 | 88 | if ($cacheData === false) { 89 | return null; 90 | } 91 | 92 | $cacheData = unserialize($cacheData); 93 | 94 | // 检查是否过期(双重检查,Redis自身会过期,这里是额外保障) 95 | if (time() > $cacheData['expire']) { 96 | $this->redis->del($cacheKey); 97 | return null; 98 | } 99 | 100 | return $cacheData['data']; 101 | } 102 | 103 | /** 104 | * 检查缓存是否存在且有效 105 | * @param string $key 缓存键名 106 | * @return bool 107 | */ 108 | public function has(string $key): bool { 109 | return $this->get($key) !== null; 110 | } 111 | 112 | /** 113 | * 删除缓存 114 | * @param string $key 缓存键名 115 | * @return bool 是否成功 116 | */ 117 | public function delete($key) { 118 | $cacheKey = $this->getKey($key); 119 | return $this->redis->del($cacheKey) > 0; 120 | } 121 | 122 | /** 123 | * 清空所有缓存 124 | * @param bool $onlyCache 是否只清除缓存前缀的键 125 | * @return bool 是否成功 126 | */ 127 | public function clear($onlyCache = true) { 128 | if ($onlyCache) { 129 | // 只清除缓存前缀的键 130 | $keys = $this->redis->keys('cache:*'); 131 | if (!empty($keys)) { 132 | return $this->redis->del($keys) > 0; 133 | } 134 | return true; 135 | } 136 | 137 | // 清除整个数据库(谨慎使用) 138 | return $this->redis->flushDB(); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /startmvc/core/db/DbCache.php: -------------------------------------------------------------------------------- 1 | cacheDir = $dir; 50 | $this->cache = $time; 51 | $this->finish = time() + $time; 52 | } 53 | 54 | /** 55 | * 获取缓存数据 56 | * 57 | * @param $sql SQL查询语句 58 | * @param bool $array 是否返回数组 59 | * 60 | * @return bool|void 61 | */ 62 | public function getCache($sql, $array = false) 63 | { 64 | if (is_null($this->cache)) { 65 | return false; 66 | } 67 | 68 | $cacheFile = $this->cacheDir . $this->fileName($sql) . '.cache'; 69 | if (file_exists($cacheFile)) { 70 | $cache = json_decode(file_get_contents($cacheFile), $array); 71 | 72 | if (($array ? $cache['finish'] : $cache->finish) < time()) { 73 | unlink($cacheFile); 74 | return; 75 | } 76 | 77 | return ($array ? $cache['data'] : $cache->data); 78 | } 79 | 80 | return false; 81 | } 82 | 83 | /** 84 | * 设置缓存数据 85 | * 86 | * @param $sql SQL查询语句 87 | * @param $result 查询结果 88 | * 89 | * @return bool|void 90 | */ 91 | public function setCache($sql, $result) 92 | { 93 | if (is_null($this->cache)) { 94 | return false; 95 | } 96 | 97 | $cacheFile = $this->cacheDir . $this->fileName($sql) . '.cache'; 98 | $cacheFile = fopen($cacheFile, 'w'); 99 | 100 | if ($cacheFile) { 101 | fputs($cacheFile, json_encode(['data' => $result, 'finish' => $this->finish])); 102 | } 103 | 104 | return; 105 | } 106 | 107 | /** 108 | * 根据SQL生成缓存文件名 109 | * 110 | * @param $name SQL查询语句 111 | * 112 | * @return string 113 | */ 114 | protected function fileName($name) 115 | { 116 | return md5($name); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /startmvc/core/db/DbInterface.php: -------------------------------------------------------------------------------- 1 | 2 | .exception{margin:150px auto 0 auto;padding:2rem 2rem 1rem 2rem;width:800px;background-color:#fff;border-top:5px solid #669933;word-break:break-word;box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15)} 3 | .exception h1{padding:0;margin:0 0 4px 0;font-size:1.5rem;font-weight:normal;color:#666} 4 | .e-text{margin-bottom:1.5rem;font-size:1.2rem;line-height:1.25;font-weight:500;color:#332F51} 5 | .e-list{padding:1.5rem 0 0 0;border-top:1px solid #ddd;line-height:2} 6 | .e-list dt{float:left;margin-right:1rem;color:#666} 7 | 8 | 9 |
10 |

DEBUG

11 |

getMessage(); ?>

12 |
13 |
相关文件
14 |
getFile()))?>
15 | 16 |
错误位置
17 |
getLine() ?> 行
18 |
19 |
StartMVC框架
20 |
-------------------------------------------------------------------------------- /startmvc/core/tpl/error.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 系统错误 5 | 6 | 37 | 38 | 39 |
40 |

系统错误

41 |
42 | 43 |

错误信息:getMessage()); ?>

44 |

文件位置:getFile()); ?> 行号:getLine(); ?>

45 |
46 | 堆栈跟踪:
47 | getTraceAsString())); ?> 48 |
49 | 50 |

抱歉,系统遇到了一些问题。请稍后再试。

51 | 52 |
53 |
54 | 55 | -------------------------------------------------------------------------------- /startmvc/core/tpl/jump.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 页面跳转... 9 | 41 | 42 | 43 | 44 | 45 |
46 | 47 | 48 | :( 49 | 50 | :) 51 | 52 | 53 | 54 |
55 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /startmvc/function.php: -------------------------------------------------------------------------------- 1 | \n(\s+)/m", "] => ", $output); 64 | 65 | $cli = preg_match("/cli/i", PHP_SAPI) ? true : false; 66 | 67 | if ($cli === true) { 68 | $output = PHP_EOL . $label . PHP_EOL . $output . PHP_EOL; 69 | } else { 70 | $output = '
' . PHP_EOL . $label . PHP_EOL . $output . '
' . PHP_EOL; 71 | } 72 | 73 | if ($echo) { 74 | echo $output; 75 | } 76 | 77 | return $output; 78 | } 79 | 80 | /** 81 | * 配置文件函数 82 | * @param string $key 83 | * @param mixed $default 84 | * @return mixed 85 | */ 86 | function config($key = null, $default = null) 87 | { 88 | static $config = null; 89 | 90 | // 如果配置未加载,先加载配置 91 | if ($config === null) { 92 | // 加载公共配置文件 93 | $commonConfig = require ROOT_PATH . '/config/common.php'; 94 | 95 | // 如果有环境配置文件,也加载它 96 | $envConfig = []; 97 | if (file_exists(ROOT_PATH . '/config/' . ENV . '.php')) { 98 | $envConfig = require ROOT_PATH . '/config/' . ENV . '.php'; 99 | } 100 | 101 | // 合并配置 102 | $config = array_merge($commonConfig, $envConfig); 103 | } 104 | 105 | // 如果没有指定 key,返回所有配置 106 | if ($key === null) { 107 | return $config; 108 | } 109 | 110 | // 返回指定配置项,如果不存在返回默认值 111 | return isset($config[$key]) ? $config[$key] : $default; 112 | } 113 | 114 | /** 115 | * 缓存助手函数 116 | * 117 | * @param string $name 缓存名称(注意命名唯一性,防止重复) 118 | * @param mixed $value 缓存值,为null时表示获取缓存 119 | * @param int $expire 缓存时间(秒),默认3600秒 120 | * @param string $driver 缓存驱动,默认使用配置中的驱动 121 | * @return mixed 获取缓存时返回缓存值,设置缓存时返回true/false 122 | */ 123 | function cache($name, $value = null, $expire = 3600, $driver = null) 124 | { 125 | static $instance = []; 126 | 127 | // 获取缓存驱动实例 128 | $driverName = $driver ?: config('cache.drive', 'file'); 129 | if (!isset($instance[$driverName])) { 130 | $instance[$driverName] = Cache::store($driverName); 131 | } 132 | 133 | // 获取缓存 134 | if ($value === null) { 135 | return $instance[$driverName]->get($name); 136 | } 137 | 138 | // 删除缓存 139 | if ($value === false) { 140 | return $instance[$driverName]->delete($name); 141 | } 142 | 143 | // 自定义缓存参数 144 | $cacheConfig = config('cache.' . $driverName, []); 145 | if ($expire !== 3600) { 146 | $cacheConfig['cacheTime'] = $expire; 147 | } 148 | 149 | // 设置缓存 150 | return $instance[$driverName]->set($name, $value); 151 | } 152 | 153 | /** 154 | * url的方法 155 | */ 156 | function url($url){ 157 | $url = $url . config('url_suffix'); 158 | if (config('urlrewrite')) { 159 | $url = '/' . $url; 160 | } else { 161 | $url = '/index.php/' . $url; 162 | } 163 | return str_replace('%2F', '/', urlencode($url)); 164 | } 165 | 166 | /** 167 | * 获取客户端的真实IP地址 168 | */ 169 | function get_ip() { 170 | // 优先检查HTTP_X_FORWARDED_FOR,因为它可能包含多个IP,我们取第一个非未知的IP 171 | $ip = null; 172 | if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { 173 | $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); 174 | foreach ($ips as $tmp) { 175 | $ip = trim($tmp); 176 | if ($ip !== 'unknown') { 177 | break; 178 | } 179 | } 180 | } 181 | 182 | // 如果没有通过HTTP_X_FORWARDED_FOR获取到IP,尝试其他可能的服务器变量 183 | if (!$ip) { 184 | $ip = $_SERVER['REMOTE_ADDR'] ?? $_SERVER['HTTP_CLIENT_IP'] ?? $_SERVER['HTTP_CDN_SRC_IP'] ?? '0.0.0.0'; 185 | } 186 | 187 | // 验证IP地址格式,如果不是有效的IPv4或IPv6,返回默认值 188 | if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6)) { 189 | $ip = '0.0.0.0'; 190 | } 191 | 192 | return $ip; 193 | } -------------------------------------------------------------------------------- /vendor/autoload.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17 | * 18 | * $loader = new \Composer\Autoload\ClassLoader(); 19 | * 20 | * // register classes with namespaces 21 | * $loader->add('Symfony\Component', __DIR__.'/component'); 22 | * $loader->add('Symfony', __DIR__.'/framework'); 23 | * 24 | * // activate the autoloader 25 | * $loader->register(); 26 | * 27 | * // to enable searching the include path (eg. for PEAR packages) 28 | * $loader->setUseIncludePath(true); 29 | * 30 | * In this example, if you try to use a class in the Symfony\Component 31 | * namespace or one of its children (Symfony\Component\Console for instance), 32 | * the autoloader will first look for the class under the component/ 33 | * directory, and it will then fallback to the framework/ directory if not 34 | * found before giving up. 35 | * 36 | * This class is loosely based on the Symfony UniversalClassLoader. 37 | * 38 | * @author Fabien Potencier 39 | * @author Jordi Boggiano 40 | * @see https://www.php-fig.org/psr/psr-0/ 41 | * @see https://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | /** @var ?string */ 46 | private $vendorDir; 47 | 48 | // PSR-4 49 | /** 50 | * @var array[] 51 | * @psalm-var array> 52 | */ 53 | private $prefixLengthsPsr4 = array(); 54 | /** 55 | * @var array[] 56 | * @psalm-var array> 57 | */ 58 | private $prefixDirsPsr4 = array(); 59 | /** 60 | * @var array[] 61 | * @psalm-var array 62 | */ 63 | private $fallbackDirsPsr4 = array(); 64 | 65 | // PSR-0 66 | /** 67 | * @var array[] 68 | * @psalm-var array> 69 | */ 70 | private $prefixesPsr0 = array(); 71 | /** 72 | * @var array[] 73 | * @psalm-var array 74 | */ 75 | private $fallbackDirsPsr0 = array(); 76 | 77 | /** @var bool */ 78 | private $useIncludePath = false; 79 | 80 | /** 81 | * @var string[] 82 | * @psalm-var array 83 | */ 84 | private $classMap = array(); 85 | 86 | /** @var bool */ 87 | private $classMapAuthoritative = false; 88 | 89 | /** 90 | * @var bool[] 91 | * @psalm-var array 92 | */ 93 | private $missingClasses = array(); 94 | 95 | /** @var ?string */ 96 | private $apcuPrefix; 97 | 98 | /** 99 | * @var self[] 100 | */ 101 | private static $registeredLoaders = array(); 102 | 103 | /** 104 | * @param ?string $vendorDir 105 | */ 106 | public function __construct($vendorDir = null) 107 | { 108 | $this->vendorDir = $vendorDir; 109 | } 110 | 111 | /** 112 | * @return string[] 113 | */ 114 | public function getPrefixes() 115 | { 116 | if (!empty($this->prefixesPsr0)) { 117 | return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); 118 | } 119 | 120 | return array(); 121 | } 122 | 123 | /** 124 | * @return array[] 125 | * @psalm-return array> 126 | */ 127 | public function getPrefixesPsr4() 128 | { 129 | return $this->prefixDirsPsr4; 130 | } 131 | 132 | /** 133 | * @return array[] 134 | * @psalm-return array 135 | */ 136 | public function getFallbackDirs() 137 | { 138 | return $this->fallbackDirsPsr0; 139 | } 140 | 141 | /** 142 | * @return array[] 143 | * @psalm-return array 144 | */ 145 | public function getFallbackDirsPsr4() 146 | { 147 | return $this->fallbackDirsPsr4; 148 | } 149 | 150 | /** 151 | * @return string[] Array of classname => path 152 | * @psalm-return array 153 | */ 154 | public function getClassMap() 155 | { 156 | return $this->classMap; 157 | } 158 | 159 | /** 160 | * @param string[] $classMap Class to filename map 161 | * @psalm-param array $classMap 162 | * 163 | * @return void 164 | */ 165 | public function addClassMap(array $classMap) 166 | { 167 | if ($this->classMap) { 168 | $this->classMap = array_merge($this->classMap, $classMap); 169 | } else { 170 | $this->classMap = $classMap; 171 | } 172 | } 173 | 174 | /** 175 | * Registers a set of PSR-0 directories for a given prefix, either 176 | * appending or prepending to the ones previously set for this prefix. 177 | * 178 | * @param string $prefix The prefix 179 | * @param string[]|string $paths The PSR-0 root directories 180 | * @param bool $prepend Whether to prepend the directories 181 | * 182 | * @return void 183 | */ 184 | public function add($prefix, $paths, $prepend = false) 185 | { 186 | if (!$prefix) { 187 | if ($prepend) { 188 | $this->fallbackDirsPsr0 = array_merge( 189 | (array) $paths, 190 | $this->fallbackDirsPsr0 191 | ); 192 | } else { 193 | $this->fallbackDirsPsr0 = array_merge( 194 | $this->fallbackDirsPsr0, 195 | (array) $paths 196 | ); 197 | } 198 | 199 | return; 200 | } 201 | 202 | $first = $prefix[0]; 203 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 204 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 205 | 206 | return; 207 | } 208 | if ($prepend) { 209 | $this->prefixesPsr0[$first][$prefix] = array_merge( 210 | (array) $paths, 211 | $this->prefixesPsr0[$first][$prefix] 212 | ); 213 | } else { 214 | $this->prefixesPsr0[$first][$prefix] = array_merge( 215 | $this->prefixesPsr0[$first][$prefix], 216 | (array) $paths 217 | ); 218 | } 219 | } 220 | 221 | /** 222 | * Registers a set of PSR-4 directories for a given namespace, either 223 | * appending or prepending to the ones previously set for this namespace. 224 | * 225 | * @param string $prefix The prefix/namespace, with trailing '\\' 226 | * @param string[]|string $paths The PSR-4 base directories 227 | * @param bool $prepend Whether to prepend the directories 228 | * 229 | * @throws \InvalidArgumentException 230 | * 231 | * @return void 232 | */ 233 | public function addPsr4($prefix, $paths, $prepend = false) 234 | { 235 | if (!$prefix) { 236 | // Register directories for the root namespace. 237 | if ($prepend) { 238 | $this->fallbackDirsPsr4 = array_merge( 239 | (array) $paths, 240 | $this->fallbackDirsPsr4 241 | ); 242 | } else { 243 | $this->fallbackDirsPsr4 = array_merge( 244 | $this->fallbackDirsPsr4, 245 | (array) $paths 246 | ); 247 | } 248 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 249 | // Register directories for a new namespace. 250 | $length = strlen($prefix); 251 | if ('\\' !== $prefix[$length - 1]) { 252 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 253 | } 254 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 255 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 256 | } elseif ($prepend) { 257 | // Prepend directories for an already registered namespace. 258 | $this->prefixDirsPsr4[$prefix] = array_merge( 259 | (array) $paths, 260 | $this->prefixDirsPsr4[$prefix] 261 | ); 262 | } else { 263 | // Append directories for an already registered namespace. 264 | $this->prefixDirsPsr4[$prefix] = array_merge( 265 | $this->prefixDirsPsr4[$prefix], 266 | (array) $paths 267 | ); 268 | } 269 | } 270 | 271 | /** 272 | * Registers a set of PSR-0 directories for a given prefix, 273 | * replacing any others previously set for this prefix. 274 | * 275 | * @param string $prefix The prefix 276 | * @param string[]|string $paths The PSR-0 base directories 277 | * 278 | * @return void 279 | */ 280 | public function set($prefix, $paths) 281 | { 282 | if (!$prefix) { 283 | $this->fallbackDirsPsr0 = (array) $paths; 284 | } else { 285 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 286 | } 287 | } 288 | 289 | /** 290 | * Registers a set of PSR-4 directories for a given namespace, 291 | * replacing any others previously set for this namespace. 292 | * 293 | * @param string $prefix The prefix/namespace, with trailing '\\' 294 | * @param string[]|string $paths The PSR-4 base directories 295 | * 296 | * @throws \InvalidArgumentException 297 | * 298 | * @return void 299 | */ 300 | public function setPsr4($prefix, $paths) 301 | { 302 | if (!$prefix) { 303 | $this->fallbackDirsPsr4 = (array) $paths; 304 | } else { 305 | $length = strlen($prefix); 306 | if ('\\' !== $prefix[$length - 1]) { 307 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 308 | } 309 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 310 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 311 | } 312 | } 313 | 314 | /** 315 | * Turns on searching the include path for class files. 316 | * 317 | * @param bool $useIncludePath 318 | * 319 | * @return void 320 | */ 321 | public function setUseIncludePath($useIncludePath) 322 | { 323 | $this->useIncludePath = $useIncludePath; 324 | } 325 | 326 | /** 327 | * Can be used to check if the autoloader uses the include path to check 328 | * for classes. 329 | * 330 | * @return bool 331 | */ 332 | public function getUseIncludePath() 333 | { 334 | return $this->useIncludePath; 335 | } 336 | 337 | /** 338 | * Turns off searching the prefix and fallback directories for classes 339 | * that have not been registered with the class map. 340 | * 341 | * @param bool $classMapAuthoritative 342 | * 343 | * @return void 344 | */ 345 | public function setClassMapAuthoritative($classMapAuthoritative) 346 | { 347 | $this->classMapAuthoritative = $classMapAuthoritative; 348 | } 349 | 350 | /** 351 | * Should class lookup fail if not found in the current class map? 352 | * 353 | * @return bool 354 | */ 355 | public function isClassMapAuthoritative() 356 | { 357 | return $this->classMapAuthoritative; 358 | } 359 | 360 | /** 361 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 362 | * 363 | * @param string|null $apcuPrefix 364 | * 365 | * @return void 366 | */ 367 | public function setApcuPrefix($apcuPrefix) 368 | { 369 | $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; 370 | } 371 | 372 | /** 373 | * The APCu prefix in use, or null if APCu caching is not enabled. 374 | * 375 | * @return string|null 376 | */ 377 | public function getApcuPrefix() 378 | { 379 | return $this->apcuPrefix; 380 | } 381 | 382 | /** 383 | * Registers this instance as an autoloader. 384 | * 385 | * @param bool $prepend Whether to prepend the autoloader or not 386 | * 387 | * @return void 388 | */ 389 | public function register($prepend = false) 390 | { 391 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 392 | 393 | if (null === $this->vendorDir) { 394 | return; 395 | } 396 | 397 | if ($prepend) { 398 | self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; 399 | } else { 400 | unset(self::$registeredLoaders[$this->vendorDir]); 401 | self::$registeredLoaders[$this->vendorDir] = $this; 402 | } 403 | } 404 | 405 | /** 406 | * Unregisters this instance as an autoloader. 407 | * 408 | * @return void 409 | */ 410 | public function unregister() 411 | { 412 | spl_autoload_unregister(array($this, 'loadClass')); 413 | 414 | if (null !== $this->vendorDir) { 415 | unset(self::$registeredLoaders[$this->vendorDir]); 416 | } 417 | } 418 | 419 | /** 420 | * Loads the given class or interface. 421 | * 422 | * @param string $class The name of the class 423 | * @return true|null True if loaded, null otherwise 424 | */ 425 | public function loadClass($class) 426 | { 427 | if ($file = $this->findFile($class)) { 428 | includeFile($file); 429 | 430 | return true; 431 | } 432 | 433 | return null; 434 | } 435 | 436 | /** 437 | * Finds the path to the file where the class is defined. 438 | * 439 | * @param string $class The name of the class 440 | * 441 | * @return string|false The path if found, false otherwise 442 | */ 443 | public function findFile($class) 444 | { 445 | // class map lookup 446 | if (isset($this->classMap[$class])) { 447 | return $this->classMap[$class]; 448 | } 449 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 450 | return false; 451 | } 452 | if (null !== $this->apcuPrefix) { 453 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 454 | if ($hit) { 455 | return $file; 456 | } 457 | } 458 | 459 | $file = $this->findFileWithExtension($class, '.php'); 460 | 461 | // Search for Hack files if we are running on HHVM 462 | if (false === $file && defined('HHVM_VERSION')) { 463 | $file = $this->findFileWithExtension($class, '.hh'); 464 | } 465 | 466 | if (null !== $this->apcuPrefix) { 467 | apcu_add($this->apcuPrefix.$class, $file); 468 | } 469 | 470 | if (false === $file) { 471 | // Remember that this class does not exist. 472 | $this->missingClasses[$class] = true; 473 | } 474 | 475 | return $file; 476 | } 477 | 478 | /** 479 | * Returns the currently registered loaders indexed by their corresponding vendor directories. 480 | * 481 | * @return self[] 482 | */ 483 | public static function getRegisteredLoaders() 484 | { 485 | return self::$registeredLoaders; 486 | } 487 | 488 | /** 489 | * @param string $class 490 | * @param string $ext 491 | * @return string|false 492 | */ 493 | private function findFileWithExtension($class, $ext) 494 | { 495 | // PSR-4 lookup 496 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 497 | 498 | $first = $class[0]; 499 | if (isset($this->prefixLengthsPsr4[$first])) { 500 | $subPath = $class; 501 | while (false !== $lastPos = strrpos($subPath, '\\')) { 502 | $subPath = substr($subPath, 0, $lastPos); 503 | $search = $subPath . '\\'; 504 | if (isset($this->prefixDirsPsr4[$search])) { 505 | $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 506 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 507 | if (file_exists($file = $dir . $pathEnd)) { 508 | return $file; 509 | } 510 | } 511 | } 512 | } 513 | } 514 | 515 | // PSR-4 fallback dirs 516 | foreach ($this->fallbackDirsPsr4 as $dir) { 517 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 518 | return $file; 519 | } 520 | } 521 | 522 | // PSR-0 lookup 523 | if (false !== $pos = strrpos($class, '\\')) { 524 | // namespaced class name 525 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 526 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 527 | } else { 528 | // PEAR-like class name 529 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 530 | } 531 | 532 | if (isset($this->prefixesPsr0[$first])) { 533 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 534 | if (0 === strpos($class, $prefix)) { 535 | foreach ($dirs as $dir) { 536 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 537 | return $file; 538 | } 539 | } 540 | } 541 | } 542 | } 543 | 544 | // PSR-0 fallback dirs 545 | foreach ($this->fallbackDirsPsr0 as $dir) { 546 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 547 | return $file; 548 | } 549 | } 550 | 551 | // PSR-0 include paths. 552 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 553 | return $file; 554 | } 555 | 556 | return false; 557 | } 558 | } 559 | 560 | /** 561 | * Scope isolated include. 562 | * 563 | * Prevents access to $this/self from included files. 564 | * 565 | * @param string $file 566 | * @return void 567 | * @private 568 | */ 569 | function includeFile($file) 570 | { 571 | include $file; 572 | } 573 | -------------------------------------------------------------------------------- /vendor/composer/InstalledVersions.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer; 14 | 15 | use Composer\Autoload\ClassLoader; 16 | use Composer\Semver\VersionParser; 17 | 18 | /** 19 | * This class is copied in every Composer installed project and available to all 20 | * 21 | * See also https://getcomposer.org/doc/07-runtime.md#installed-versions 22 | * 23 | * To require its presence, you can require `composer-runtime-api ^2.0` 24 | * 25 | * @final 26 | */ 27 | class InstalledVersions 28 | { 29 | /** 30 | * @var mixed[]|null 31 | * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null 32 | */ 33 | private static $installed; 34 | 35 | /** 36 | * @var bool|null 37 | */ 38 | private static $canGetVendors; 39 | 40 | /** 41 | * @var array[] 42 | * @psalm-var array}> 43 | */ 44 | private static $installedByVendor = array(); 45 | 46 | /** 47 | * Returns a list of all package names which are present, either by being installed, replaced or provided 48 | * 49 | * @return string[] 50 | * @psalm-return list 51 | */ 52 | public static function getInstalledPackages() 53 | { 54 | $packages = array(); 55 | foreach (self::getInstalled() as $installed) { 56 | $packages[] = array_keys($installed['versions']); 57 | } 58 | 59 | if (1 === \count($packages)) { 60 | return $packages[0]; 61 | } 62 | 63 | return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); 64 | } 65 | 66 | /** 67 | * Returns a list of all package names with a specific type e.g. 'library' 68 | * 69 | * @param string $type 70 | * @return string[] 71 | * @psalm-return list 72 | */ 73 | public static function getInstalledPackagesByType($type) 74 | { 75 | $packagesByType = array(); 76 | 77 | foreach (self::getInstalled() as $installed) { 78 | foreach ($installed['versions'] as $name => $package) { 79 | if (isset($package['type']) && $package['type'] === $type) { 80 | $packagesByType[] = $name; 81 | } 82 | } 83 | } 84 | 85 | return $packagesByType; 86 | } 87 | 88 | /** 89 | * Checks whether the given package is installed 90 | * 91 | * This also returns true if the package name is provided or replaced by another package 92 | * 93 | * @param string $packageName 94 | * @param bool $includeDevRequirements 95 | * @return bool 96 | */ 97 | public static function isInstalled($packageName, $includeDevRequirements = true) 98 | { 99 | foreach (self::getInstalled() as $installed) { 100 | if (isset($installed['versions'][$packageName])) { 101 | return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); 102 | } 103 | } 104 | 105 | return false; 106 | } 107 | 108 | /** 109 | * Checks whether the given package satisfies a version constraint 110 | * 111 | * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: 112 | * 113 | * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') 114 | * 115 | * @param VersionParser $parser Install composer/semver to have access to this class and functionality 116 | * @param string $packageName 117 | * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package 118 | * @return bool 119 | */ 120 | public static function satisfies(VersionParser $parser, $packageName, $constraint) 121 | { 122 | $constraint = $parser->parseConstraints($constraint); 123 | $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); 124 | 125 | return $provided->matches($constraint); 126 | } 127 | 128 | /** 129 | * Returns a version constraint representing all the range(s) which are installed for a given package 130 | * 131 | * It is easier to use this via isInstalled() with the $constraint argument if you need to check 132 | * whether a given version of a package is installed, and not just whether it exists 133 | * 134 | * @param string $packageName 135 | * @return string Version constraint usable with composer/semver 136 | */ 137 | public static function getVersionRanges($packageName) 138 | { 139 | foreach (self::getInstalled() as $installed) { 140 | if (!isset($installed['versions'][$packageName])) { 141 | continue; 142 | } 143 | 144 | $ranges = array(); 145 | if (isset($installed['versions'][$packageName]['pretty_version'])) { 146 | $ranges[] = $installed['versions'][$packageName]['pretty_version']; 147 | } 148 | if (array_key_exists('aliases', $installed['versions'][$packageName])) { 149 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); 150 | } 151 | if (array_key_exists('replaced', $installed['versions'][$packageName])) { 152 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); 153 | } 154 | if (array_key_exists('provided', $installed['versions'][$packageName])) { 155 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); 156 | } 157 | 158 | return implode(' || ', $ranges); 159 | } 160 | 161 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 162 | } 163 | 164 | /** 165 | * @param string $packageName 166 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present 167 | */ 168 | public static function getVersion($packageName) 169 | { 170 | foreach (self::getInstalled() as $installed) { 171 | if (!isset($installed['versions'][$packageName])) { 172 | continue; 173 | } 174 | 175 | if (!isset($installed['versions'][$packageName]['version'])) { 176 | return null; 177 | } 178 | 179 | return $installed['versions'][$packageName]['version']; 180 | } 181 | 182 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 183 | } 184 | 185 | /** 186 | * @param string $packageName 187 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present 188 | */ 189 | public static function getPrettyVersion($packageName) 190 | { 191 | foreach (self::getInstalled() as $installed) { 192 | if (!isset($installed['versions'][$packageName])) { 193 | continue; 194 | } 195 | 196 | if (!isset($installed['versions'][$packageName]['pretty_version'])) { 197 | return null; 198 | } 199 | 200 | return $installed['versions'][$packageName]['pretty_version']; 201 | } 202 | 203 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 204 | } 205 | 206 | /** 207 | * @param string $packageName 208 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference 209 | */ 210 | public static function getReference($packageName) 211 | { 212 | foreach (self::getInstalled() as $installed) { 213 | if (!isset($installed['versions'][$packageName])) { 214 | continue; 215 | } 216 | 217 | if (!isset($installed['versions'][$packageName]['reference'])) { 218 | return null; 219 | } 220 | 221 | return $installed['versions'][$packageName]['reference']; 222 | } 223 | 224 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 225 | } 226 | 227 | /** 228 | * @param string $packageName 229 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. 230 | */ 231 | public static function getInstallPath($packageName) 232 | { 233 | foreach (self::getInstalled() as $installed) { 234 | if (!isset($installed['versions'][$packageName])) { 235 | continue; 236 | } 237 | 238 | return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; 239 | } 240 | 241 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 242 | } 243 | 244 | /** 245 | * @return array 246 | * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} 247 | */ 248 | public static function getRootPackage() 249 | { 250 | $installed = self::getInstalled(); 251 | 252 | return $installed[0]['root']; 253 | } 254 | 255 | /** 256 | * Returns the raw installed.php data for custom implementations 257 | * 258 | * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. 259 | * @return array[] 260 | * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} 261 | */ 262 | public static function getRawData() 263 | { 264 | @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); 265 | 266 | if (null === self::$installed) { 267 | // only require the installed.php file if this file is loaded from its dumped location, 268 | // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 269 | if (substr(__DIR__, -8, 1) !== 'C') { 270 | self::$installed = include __DIR__ . '/installed.php'; 271 | } else { 272 | self::$installed = array(); 273 | } 274 | } 275 | 276 | return self::$installed; 277 | } 278 | 279 | /** 280 | * Returns the raw data of all installed.php which are currently loaded for custom implementations 281 | * 282 | * @return array[] 283 | * @psalm-return list}> 284 | */ 285 | public static function getAllRawData() 286 | { 287 | return self::getInstalled(); 288 | } 289 | 290 | /** 291 | * Lets you reload the static array from another file 292 | * 293 | * This is only useful for complex integrations in which a project needs to use 294 | * this class but then also needs to execute another project's autoloader in process, 295 | * and wants to ensure both projects have access to their version of installed.php. 296 | * 297 | * A typical case would be PHPUnit, where it would need to make sure it reads all 298 | * the data it needs from this class, then call reload() with 299 | * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure 300 | * the project in which it runs can then also use this class safely, without 301 | * interference between PHPUnit's dependencies and the project's dependencies. 302 | * 303 | * @param array[] $data A vendor/composer/installed.php data set 304 | * @return void 305 | * 306 | * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data 307 | */ 308 | public static function reload($data) 309 | { 310 | self::$installed = $data; 311 | self::$installedByVendor = array(); 312 | } 313 | 314 | /** 315 | * @return array[] 316 | * @psalm-return list}> 317 | */ 318 | private static function getInstalled() 319 | { 320 | if (null === self::$canGetVendors) { 321 | self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); 322 | } 323 | 324 | $installed = array(); 325 | 326 | if (self::$canGetVendors) { 327 | foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { 328 | if (isset(self::$installedByVendor[$vendorDir])) { 329 | $installed[] = self::$installedByVendor[$vendorDir]; 330 | } elseif (is_file($vendorDir.'/composer/installed.php')) { 331 | $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; 332 | if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { 333 | self::$installed = $installed[count($installed) - 1]; 334 | } 335 | } 336 | } 337 | } 338 | 339 | if (null === self::$installed) { 340 | // only require the installed.php file if this file is loaded from its dumped location, 341 | // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 342 | if (substr(__DIR__, -8, 1) !== 'C') { 343 | self::$installed = require __DIR__ . '/installed.php'; 344 | } else { 345 | self::$installed = array(); 346 | } 347 | } 348 | $installed[] = self::$installed; 349 | 350 | return $installed; 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /vendor/composer/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) Nils Adermann, Jordi Boggiano 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /vendor/composer/autoload_classmap.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/composer/InstalledVersions.php', 10 | ); 11 | -------------------------------------------------------------------------------- /vendor/composer/autoload_namespaces.php: -------------------------------------------------------------------------------- 1 | array($baseDir . '/startmvc'), 10 | 'extend\\' => array($baseDir . '/extend'), 11 | 'app\\' => array($baseDir . '/app'), 12 | ); 13 | -------------------------------------------------------------------------------- /vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | register(true); 35 | 36 | return $loader; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | 11 | array ( 12 | 'startmvc\\' => 9, 13 | ), 14 | 'e' => 15 | array ( 16 | 'extend\\' => 7, 17 | ), 18 | 'a' => 19 | array ( 20 | 'app\\' => 4, 21 | ), 22 | ); 23 | 24 | public static $prefixDirsPsr4 = array ( 25 | 'startmvc\\' => 26 | array ( 27 | 0 => __DIR__ . '/../..' . '/startmvc', 28 | ), 29 | 'extend\\' => 30 | array ( 31 | 0 => __DIR__ . '/../..' . '/extend', 32 | ), 33 | 'app\\' => 34 | array ( 35 | 0 => __DIR__ . '/../..' . '/app', 36 | ), 37 | ); 38 | 39 | public static $classMap = array ( 40 | 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 41 | ); 42 | 43 | public static function getInitializer(ClassLoader $loader) 44 | { 45 | return \Closure::bind(function () use ($loader) { 46 | $loader->prefixLengthsPsr4 = ComposerStaticInit40c38550ecb0b597f81db8b0fdbec3d6::$prefixLengthsPsr4; 47 | $loader->prefixDirsPsr4 = ComposerStaticInit40c38550ecb0b597f81db8b0fdbec3d6::$prefixDirsPsr4; 48 | $loader->classMap = ComposerStaticInit40c38550ecb0b597f81db8b0fdbec3d6::$classMap; 49 | 50 | }, null, ClassLoader::class); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [], 3 | "dev": true, 4 | "dev-package-names": [] 5 | } 6 | -------------------------------------------------------------------------------- /vendor/composer/installed.php: -------------------------------------------------------------------------------- 1 | array( 3 | 'name' => 'shaobingme/startmvc', 4 | 'pretty_version' => 'dev-master', 5 | 'version' => 'dev-master', 6 | 'reference' => '430165126207e1a6c67a8124ad6ff055d16dd668', 7 | 'type' => 'project', 8 | 'install_path' => __DIR__ . '/../../', 9 | 'aliases' => array(), 10 | 'dev' => true, 11 | ), 12 | 'versions' => array( 13 | 'shaobingme/startmvc' => array( 14 | 'pretty_version' => 'dev-master', 15 | 'version' => 'dev-master', 16 | 'reference' => '430165126207e1a6c67a8124ad6ff055d16dd668', 17 | 'type' => 'project', 18 | 'install_path' => __DIR__ . '/../../', 19 | 'aliases' => array(), 20 | 'dev_requirement' => false, 21 | ), 22 | ), 23 | ); 24 | -------------------------------------------------------------------------------- /vendor/composer/platform_check.php: -------------------------------------------------------------------------------- 1 | = 70200)) { 8 | $issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.0". You are running ' . PHP_VERSION . '.'; 9 | } 10 | 11 | if ($issues) { 12 | if (!headers_sent()) { 13 | header('HTTP/1.1 500 Internal Server Error'); 14 | } 15 | if (!ini_get('display_errors')) { 16 | if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { 17 | fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); 18 | } elseif (!headers_sent()) { 19 | echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; 20 | } 21 | } 22 | trigger_error( 23 | 'Composer detected issues in your platform: ' . implode(' ', $issues), 24 | E_USER_ERROR 25 | ); 26 | } 27 | --------------------------------------------------------------------------------