├── .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 | 
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 |
30 | - 更多功能了解,请查看Startmvc
31 | {if SM_VERSION}
32 | - 当前版本:v=SM_VERSION?> (=SM_UPDATE?>)
33 | {/if}
34 | {lang('startmvc')}
35 |
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 |
= $exception->getMessage(); ?>
12 |
13 | - 相关文件
14 | - = trim(realpath($exception->getFile()))?>
15 |
16 | - 错误位置
17 | - = $exception->getLine() ?> 行
18 |
19 |
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 |
--------------------------------------------------------------------------------