├── .gitignore
├── MIT-LICENSE.txt
├── README.md
├── composer.json
├── example
├── demo.php
├── demo2.php
├── demo3.php
├── demo4.php
├── demo5.php
├── demo6.php
├── demo7.php
├── file.txt
├── hello.html
├── info.html
├── public
│ ├── css
│ │ └── default.css
│ ├── favicon.ico
│ └── images
│ │ └── 20264902.jpg
├── table.html
└── template
│ └── index.html
└── src
├── All.php
├── Any.php
├── Application.php
├── Async.php
├── AsyncTask.php
├── CallCC.php
├── Context.php
├── Error.php
├── Gen.php
├── HttpException.php
├── Middleware.php
├── NotFound.php
├── Request.php
├── Response.php
├── Router.php
├── Syscall.php
├── Template.php
├── Timeout.php
├── functions.php
└── template
├── 404.html
├── 500.html
└── error.html
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 |
--------------------------------------------------------------------------------
/MIT-LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2009-2015 naka507<171453643@qq.com> and contributors (see https://github.com/naka1205/phpkoa/graphs/contributors)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | PHPKoa
2 | =================
3 | [](https://packagist.org/packages/naka1205/phpkoa)
4 | [](https://packagist.org/packages/naka1205/phpkoa)
5 | [](https://packagist.org/packages/naka1205/phpkoa)
6 |
7 | PHP异步编程:
8 | 基于 `PHP` 实(chao)现(xi) `NODEJS` web框架 [KOA](https://github.com/koajs/koa)
9 |
10 | 说明
11 | =======
12 | 偶然间在 `GITHUB` 上看到有赞官方仓库的 [手把手教你实现co与Koa](https://github.com/youzan/php-co-koa) 。由于此前用过 `KOA` ,对于 `KOA` 的洋葱模型叹为观止。不由得心血来潮的看完了整个文档,接着 `CTRL+C`、`CTRL+V` 让代码跑了起来。
13 | 文档中是基于 `swoole` 扩展进行开发,而 `swoole` 对 `WINDOWS` 并不友好,向来习惯在 `WINDOWS` 下开发的我一鼓作气,将[Workerman](https://github.com/walkor/Workerman) 改写并兼容了此项目。
14 |
15 | 体验
16 | =======
17 | 1. [PHPKoa Demo](https://github.com/naka1205/phpkoa_demo) 是使用 `PHPKoa` 开发 `HTTP SERVER` 的一个简单示例!
18 | 2. [PHP Krpano](https://github.com/naka1205/phpkrpano) PHP 全景图片生成!
19 | 3. [PKBook](https://github.com/naka1205/pkbook) 静态博客发布程序!
20 | 4. [H5Make](https://github.com/naka1205/H5Make) H5编辑器!
21 |
22 | 建议
23 | =======
24 | 欢迎各位大神提交[issues](https://github.com/naka1205/phpkoa/issues/1)
25 |
26 | 安装
27 | =======
28 | ```
29 | composer require naka1205/phpkoa
30 | ```
31 |
32 | 使用
33 | =======
34 |
35 | ### Hello World
36 | ```php
37 | υse(function(Context $ctx) {
46 | $ctx->status = 200;
47 | $ctx->body = "
Hello World
";
48 | });
49 |
50 | $app->listen(3000,function(){
51 | echo "PHPKoa is listening in 3000\n";
52 | });
53 |
54 | ```
55 | ### Error
56 | ```php
57 | υse(new Error());
70 | $app->υse(new NotFound());
71 | $app->υse(new Timeout(3)); //设置3秒超时
72 |
73 | $router = new Router();
74 |
75 | //正常访问
76 | $router->get('/hello', function(Context $ctx, $next) {
77 | $ctx->status = 200;
78 | $ctx->body = "Hello World
";
79 | });
80 |
81 | //访问超时
82 | $router->get('/timeout', function(Context $ctx, $next) {
83 | yield async_sleep(5);
84 | });
85 |
86 | //访问出错
87 | $router->get('/error', function(Context $ctx, $next) {
88 | $ctx->thrοw(500, "Internal Error");
89 | yield;
90 | });
91 |
92 | $app->υse($router->routes());
93 |
94 | $app->listen(3000);
95 |
96 | ```
97 | ### Router
98 | ```php
99 | υse(new Error());
112 | $app->υse(new Timeout(5));
113 |
114 | $router = new Router();
115 | $router->get('/demo1', function(Context $ctx, $next) {
116 | $ctx->body = "demo1";
117 | });
118 | $router->get('/demo2', function(Context $ctx, $next) {
119 | $ctx->body = "demo2";
120 | });
121 | $router->get('/demo3/(\d+)', function(Context $ctx, $next, $vars) {
122 | $ctx->status = 200;
123 | $ctx->body = "demo3={$vars[0]}";
124 | });
125 | $router->get('/demo4', function(Context $ctx, $next) {
126 | $ctx->redirect("/demo2");
127 | });
128 |
129 | //RESTful API
130 | $router->post('/demo3/(\d+)', function(Context $ctx, $next, $vars) {
131 | //设置 session
132 | $ctx->setSession('demo3',$vars[0]);
133 | //设置 cookie
134 | $ctx->setCookie('demo3',$vars[0]);
135 | $ctx->status = 200;
136 | $ctx->body = "post:demo3={$vars[0]}";
137 | });
138 | $router->put('/demo3/(\d+)', function(Context $ctx, $next, $vars) {
139 |
140 | //获取单个 cookie
141 | $cookie_demo3 = $ctx->getCookie('demo3');
142 | //或者
143 | $cookies = $ctx->cookies['demo3'];
144 |
145 | //获取单个 session
146 | $session_demo3 = $ctx->getSession('demo3');
147 | //或者
148 | $session = $ctx->session['demo3'];
149 |
150 | $ctx->status = 200;
151 | $ctx->body = "put:demo3={$vars[0]}";
152 | });
153 | $router->delete('/demo3/(\d+)', function(Context $ctx, $next, $vars) {
154 | //清除所有 cookie
155 | $ctx->clearCookie();
156 | //清除所有 session
157 | $ctx->clearSession();
158 | $ctx->status = 200;
159 | $ctx->body = "delete:demo3={$vars[0]}";
160 | });
161 |
162 | //文件上传
163 | $router->post('/files/(\d+)', function(Context $ctx, $next, $vars) {
164 | $upload_path = __DIR__ . DS . "uploads" . DS;
165 | if ( !is_dir($upload_path) ) {
166 | mkdir ($upload_path , 0777, true);
167 | }
168 | $files = [];
169 | foreach ( $ctx->request->files as $key => $value) {
170 | if ( !$value['file_name'] || !$value['file_data'] ) {
171 | continue;
172 | }
173 | $file_path = $upload_path . $value['file_name'];
174 | file_put_contents($file_path, $value['file_data']);
175 | $value['file_path'] = $file_path;
176 | $files[] = $value;
177 | }
178 |
179 | $ctx->status = 200;
180 | $ctx->body = json_encode($files);
181 | });
182 |
183 |
184 | $app->υse($router->routes());
185 |
186 | $app->listen(3000);
187 |
188 | ```
189 |
190 | ```php
191 | mount('/curl', function() use ($router) {
205 | $client = new Client();
206 | $router->get('/get', function( Context $ctx, $next ) use ($client) {
207 | $r = (yield $client->request('GET', 'http://127.0.0.1:3000/demo3/1'));
208 | $ctx->status = $r->getStatusCode();
209 | $ctx->body = $r->getBody();
210 | });
211 |
212 | $router->get('/post', function(Context $ctx, $next ) use ($client){
213 | $r = (yield $client->request('POST', 'http://127.0.0.1:3000/demo3/2'));
214 | $ctx->status = $r->getStatusCode();
215 | $ctx->body = $r->getBody();
216 | });
217 |
218 | $router->get('/put', function( Context $ctx, $next ) use ($client){
219 | $r = (yield $client->request('PUT', 'http://127.0.0.1:3000/demo3/3'));
220 | $ctx->status = $r->getStatusCode();
221 | $ctx->body = $r->getBody();
222 | });
223 |
224 | $router->get('/delete', function( Context $ctx, $next ) use ($client){
225 | $r = (yield $client->request('DELETE', 'http://127.0.0.1:3000/demo3/4'));
226 | $ctx->status = $r->getStatusCode();
227 | $ctx->body = $r->getBody();
228 | });
229 | });
230 |
231 | //http://127.0.0.1:5000/files
232 | $router->get('/files', function(Context $ctx, $next ) {
233 | $client = new Client();
234 | $r = ( yield $client->request('POST', 'http://127.0.0.1:3000/files/2', [
235 | 'multipart' => [
236 | [
237 | 'name' => 'file_name',
238 | 'contents' => fopen( __DIR__ . '/file.txt', 'r')
239 | ],
240 | [
241 | 'name' => 'other_file',
242 | 'contents' => 'hello',
243 | 'filename' => 'filename.txt',
244 | 'headers' => [
245 | 'X-Foo' => 'this is an extra header to include'
246 | ]
247 | ]
248 | ]
249 | ]));
250 |
251 | $ctx->status = $r->getStatusCode();
252 | $ctx->body = $r->getBody();
253 | });
254 |
255 | // $router->get('/curl/(\w+)', function(Context $ctx, $next, $vars) {
256 | // $method = strtoupper($vars[0]);
257 | // $client = new Client();
258 | // $r = (yield $client->request($method, 'http://127.0.0.1:3000/demo3/123'));
259 | // $ctx->status = $r->getStatusCode();
260 | // $ctx->body = $r->getBody();
261 | // });
262 |
263 | $app->υse($router->routes());
264 | $app->listen(5000);
265 | ```
266 |
267 | ### Template
268 |
269 | ```html
270 |
271 | {title}
272 | {time}
273 |
274 | ```
275 | ```php
276 | υse(new Error());
287 | $app->υse(new Timeout(5));
288 |
289 | $router = new Router();
290 | $router->get('/hello', function(Context $ctx) {
291 | $ctx->status = 200;
292 | $ctx->state["title"] = "HELLO WORLD";
293 | $ctx->state["time"] = date("Y-m-d H:i:s", time());;
294 | yield $ctx->render(__DIR__ . "/hello.html");
295 | });
296 | $app->υse($router->routes());
297 |
298 | $app->listen(3000);
299 |
300 | ```
301 | ```html
302 |
303 | {title}
304 |
305 | Name | Age |
306 |
307 |
308 |
309 | {name} |
310 | {age} |
311 |
312 |
313 |
314 |
315 |
316 | ```
317 | ```php
318 | get('/info', function(Context $ctx) {
323 | $info = array("name" => "小明", "age" => 15);
324 | $ctx->status = 200;
325 | $ctx->state["title"] = "这是一个学生信息";
326 | $ctx->state["info"] = $info;
327 | yield $ctx->render(__DIR__ . "/info.html");
328 | });
329 | ```
330 | ```html
331 |
332 | {title}
333 |
334 | Name | Age |
335 |
336 |
337 |
338 | {name} |
339 | {age} |
340 |
341 |
342 |
343 |
344 |
345 | ```
346 | ```php
347 | get('/table', function(Context $ctx) {
352 | $table = array(
353 | array("name" => "小明", "age" => 15),
354 | array("name" => "小花", "age" => 13),
355 | array("name" => "小刚", "age" => 17)
356 | );
357 | $ctx->status = 200;
358 | $ctx->state["title"] = "这是一个学生名单";
359 | $ctx->state["table"] = $table;
360 | yield $ctx->render(__DIR__ . "/table.html");
361 | });
362 | ```
363 |
364 | 中间件
365 | =======
366 | 静态文件处理 中间件 [PHPKoa Static](https://github.com/naka1205/phpkoa_static)
367 |
368 | ```html
369 |
370 |
371 |
372 |
373 | PHPkoa Static
374 |
375 |
376 |
377 |
378 |
379 |
380 | ```
381 | ```php
382 | υse(new Error());
398 | $app->υse(new Timeout(5));
399 | $app->υse(new NotFound());
400 | $app->υse(new StaticFiles(__DIR__ . DS . "static" ));
401 |
402 | $router = new Router();
403 |
404 | $router->get('/index', function(Context $ctx, $next) {
405 | $ctx->status = 200;
406 | yield $ctx->render(__DIR__ . "/index.html");
407 | });
408 |
409 | $app->υse($router->routes());
410 |
411 | $app->listen(3000);
412 | ```
413 | 使用 第三方ORM
414 | ```
415 | composer require topthink/think-orm
416 | ```
417 | 创建 MYSQL 数据表
418 | ```mysql
419 | CREATE TABLE `too_user` (
420 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户ID',
421 | `account` varchar(64) NOT NULL DEFAULT '' COMMENT '账号',
422 | `password` char(32) NOT NULL DEFAULT '' COMMENT '登录密码',
423 | `nickname` varchar(32) NOT NULL DEFAULT '' COMMENT '用户昵称',
424 | `status` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '状态',
425 | `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
426 | `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
427 | PRIMARY KEY (`id`)
428 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
429 | ```
430 | 自定义数据模型
431 | ```php
432 | accept,'json');
453 | if ( $pos !== false ) {
454 | $ctx->type = 'application/json';
455 | $result = [ "code" => 0, "msg" => '操作失败'];
456 | $data = $ctx->body;
457 | if ( $data ) {
458 | $result['code'] = 200;
459 | $result['msg'] = '操作成功';
460 | $result['data'] = $data;
461 | }
462 | $ctx->body = json_encode( $result );
463 | }
464 |
465 | }
466 | }
467 | ```
468 | 动态模板 使用JQ 进行AJAX 请求
469 | ```html
470 |
471 | /api/user/{id}
472 |
473 |
474 |
490 | ```
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "naka1205/phpkoa",
3 | "description": "php koa",
4 | "type": "library",
5 | "authors": [
6 | {
7 | "name": "naka507",
8 | "email": "171453643@qq.com"
9 | }
10 | ],
11 | "license": "MIT",
12 | "require": {
13 | "php": ">=5.4",
14 | "naka1205/phpsocket": "^1.9",
15 | "naka1205/phpkoa_static": "^1.2"
16 | },
17 | "autoload": {
18 | "psr-4": {
19 | "Naka507\\Koa\\": "src/"
20 | },
21 | "files": ["src/functions.php"]
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example/demo.php:
--------------------------------------------------------------------------------
1 | υse(function(Context $ctx) {
10 | $ctx->status = 200;
11 | $ctx->body = "Hello World
";
12 | });
13 |
14 | $app->listen(3000,function(){
15 | echo "PHPKoa is listening in 3000\n";
16 | });
--------------------------------------------------------------------------------
/example/demo2.php:
--------------------------------------------------------------------------------
1 | υse(new Error());
14 | $app->υse(new NotFound());
15 | $app->υse(new Timeout(3)); //设置3秒超时
16 |
17 | $router = new Router();
18 |
19 | $router->get('/hello', function(Context $ctx, $next) {
20 | $ctx->status = 200;
21 | $ctx->body = "Hello World
";
22 | });
23 |
24 | //访问超时
25 | $router->get('/timeout', function(Context $ctx, $next) {
26 | yield async_sleep(5);
27 | });
28 |
29 | //访问出错
30 | $router->get('/error', function(Context $ctx, $next) {
31 | $ctx->thrοw(500, "Internal Error");
32 | yield;
33 | });
34 |
35 | $app->υse($router->routes());
36 |
37 | $app->listen(3000);
--------------------------------------------------------------------------------
/example/demo3.php:
--------------------------------------------------------------------------------
1 | υse(new Error());
15 | $app->υse(new Timeout(5));
16 | $app->υse(new NotFound());
17 |
18 | $router = new Router();
19 |
20 | $router->get('/demo1', function(Context $ctx, $next) {
21 | $ctx->status = 200;
22 | $ctx->body = "demo1";
23 | });
24 | $router->get('/demo2', function(Context $ctx, $next) {
25 | $ctx->status = 200;
26 | $ctx->body = "demo2";
27 | });
28 | $router->get('/demo3/(\d+)', function(Context $ctx, $next, $vars) {
29 | $ctx->status = 200;
30 | $ctx->body = "demo3={$vars[0]}";
31 | });
32 | $router->get('/demo4', function(Context $ctx, $next) {
33 | $ctx->redirect("/demo2");
34 | });
35 |
36 | //RESTful API
37 | $router->post('/demo3/(\d+)', function(Context $ctx, $next, $vars) {
38 | $ctx->status = 200;
39 | $ctx->body = "post:demo3={$vars[0]}";
40 | });
41 | $router->put('/demo3/(\d+)', function(Context $ctx, $next, $vars) {
42 | $ctx->status = 200;
43 | $ctx->body = "put:demo3={$vars[0]}";
44 | });
45 | $router->delete('/demo3/(\d+)', function(Context $ctx, $next, $vars) {
46 | $ctx->status = 200;
47 | $ctx->body = "delete:demo3={$vars[0]}";
48 | });
49 |
50 | //文件上传
51 | $router->post('/files/(\d+)', function(Context $ctx, $next, $vars) {
52 | $upload_path = __DIR__ . DS . "uploads" . DS;
53 | if ( !is_dir($upload_path) ) {
54 | mkdir ($upload_path , 0777, true);
55 | }
56 | $files = [];
57 | foreach ( $ctx->request->files as $key => $value) {
58 | if ( !$value['file_name'] || !$value['file_data'] ) {
59 | continue;
60 | }
61 | $file_path = $upload_path . $value['file_name'];
62 | file_put_contents($file_path, $value['file_data']);
63 | $value['file_path'] = $file_path;
64 | $files[] = $value;
65 | }
66 |
67 | $ctx->status = 200;
68 | $ctx->body = json_encode($files);
69 | });
70 | $app->υse($router->routes());
71 |
72 | $app->listen(3000);
--------------------------------------------------------------------------------
/example/demo4.php:
--------------------------------------------------------------------------------
1 | υse(new Error());
12 | $app->υse(new Timeout(5));
13 |
14 | $router = new Router();
15 | $router->get('/hello', function(Context $ctx) {
16 | $ctx->status = 200;
17 | $ctx->state["title"] = "HELLO WORLD";
18 | $ctx->state["time"] = date("Y-m-d H:i:s", time());;
19 | yield $ctx->render(__DIR__ . "/hello.html");
20 | });
21 |
22 | //一维数组
23 | $router->get('/info', function(Context $ctx) {
24 | $info = array("name" => "小明", "age" => 15);
25 | $ctx->status = 200;
26 | $ctx->state["title"] = "这是一个学生信息";
27 | $ctx->state["info"] = $info;
28 | yield $ctx->render(__DIR__ . "/info.html");
29 | });
30 | //二维数组
31 | $router->get('/table', function(Context $ctx) {
32 | $table = array(
33 | array("name" => "小明", "age" => 15),
34 | array("name" => "小花", "age" => 13),
35 | array("name" => "小刚", "age" => 17)
36 | );
37 | $ctx->status = 200;
38 | $ctx->state["title"] = "这是多个学生信息";
39 | $ctx->state["table"] = $table;
40 | yield $ctx->render(__DIR__ . "/table.html");
41 | });
42 |
43 | $app->υse($router->routes());
44 |
45 | $app->listen(3000);
--------------------------------------------------------------------------------
/example/demo5.php:
--------------------------------------------------------------------------------
1 | υse(new Error());
14 | $app->υse(new Timeout(10));
15 |
16 | $router = new Router();
17 |
18 | //路由分组
19 | //http://127.0.0.1:5000/curl/get
20 | //http://127.0.0.1:5000/curl/post
21 | //http://127.0.0.1:5000/curl/put
22 | //http://127.0.0.1:5000/curl/delete
23 | $router->mount('/curl', function() use ($router) {
24 | $client = new Client();
25 | $router->get('/get', function( Context $ctx, $next ) use ($client) {
26 | $client = new Client();
27 | $r = (yield $client->request('GET', 'http://127.0.0.1:3000/demo3/1'));
28 | $ctx->status = $r->getStatusCode();
29 | $ctx->body = $r->getBody();
30 | });
31 |
32 | $router->get('/post', function(Context $ctx, $next ) use ($client){
33 | $r = (yield $client->request('POST', 'http://127.0.0.1:3000/demo3/2'));
34 | $ctx->status = $r->getStatusCode();
35 | $ctx->body = $r->getBody();
36 | });
37 |
38 | $router->get('/put', function( Context $ctx, $next ) use ($client){
39 | $r = (yield $client->request('PUT', 'http://127.0.0.1:3000/demo3/3'));
40 | $ctx->status = $r->getStatusCode();
41 | $ctx->body = $r->getBody();
42 | });
43 |
44 | $router->get('/delete', function( Context $ctx, $next ) use ($client){
45 | $r = (yield $client->request('DELETE', 'http://127.0.0.1:3000/demo3/4'));
46 | $ctx->status = $r->getStatusCode();
47 | $ctx->body = $r->getBody();
48 | });
49 | });
50 |
51 | //http://127.0.0.1:5000/files
52 | $router->get('/files', function(Context $ctx, $next ) {
53 | $client = new Client();
54 | $r = ( yield $client->request('POST', 'http://127.0.0.1:3000/files/2', [
55 | 'multipart' => [
56 | [
57 | 'name' => 'file_name',
58 | 'contents' => fopen( __DIR__ . '/file.txt', 'r')
59 | ],
60 | [
61 | 'name' => 'other_file',
62 | 'contents' => 'hello',
63 | 'filename' => 'filename.txt',
64 | 'headers' => [
65 | 'X-Foo' => 'this is an extra header to include'
66 | ]
67 | ]
68 | ]
69 | ]));
70 |
71 | $ctx->status = $r->getStatusCode();
72 | $ctx->body = $r->getBody();
73 | });
74 |
75 | // $router->get('/curl/(\w+)', function(Context $ctx, $next, $vars) {
76 | // $method = strtoupper($vars[0]);
77 | // $client = new Client();
78 | // $r = (yield $client->request($method, 'http://127.0.0.1:3000/demo3/123'));
79 | // $ctx->status = $r->getStatusCode();
80 | // $ctx->body = $r->getBody();
81 | // });
82 |
83 | $app->υse($router->routes());
84 |
85 | $app->listen(5000);
--------------------------------------------------------------------------------
/example/demo6.php:
--------------------------------------------------------------------------------
1 | υse(new Error());
16 | $app->υse(new Timeout(5));
17 | $app->υse(new NotFound());
18 |
19 | $public_path = __DIR__ . DS . "public" ;
20 | $app->υse(new StaticFiles( $public_path ));
21 |
22 | $router = new Router();
23 |
24 | $router->get('/index', function(Context $ctx, $next) {
25 | $ctx->status = 200;
26 | yield $ctx->render(__DIR__ . "/template/index.html");
27 | });
28 |
29 | //文件上传
30 | $router->post('/files', function(Context $ctx, $next) use ($public_path) {
31 | $upload_path = "uploads";
32 | $save_path = $public_path . DS . $upload_path . DS;
33 |
34 | if ( !is_dir($save_path) ) {
35 | mkdir ($save_path , 0777, true);
36 | }
37 | $files = [];
38 | foreach ( $ctx->request->files as $key => $value) {
39 | if ( !$value['file_name'] || !$value['file_data'] ) {
40 | continue;
41 | }
42 | $ext = explode(".",$value['file_name'])[1];
43 | $file_name = time() . "." . $ext;
44 | $file_path = $save_path . $file_name;
45 | file_put_contents($file_path, $value['file_data']);
46 | $value['file_path'] = "/" . $upload_path . "/" . $file_name ;
47 | unset($value['file_data']);
48 | $files[] = $value;
49 | }
50 | $ctx->status = 200;
51 | $ctx->body = json_encode($files);
52 | });
53 |
54 | $app->υse($router->routes());
55 |
56 | $app->listen(3000);
--------------------------------------------------------------------------------
/example/demo7.php:
--------------------------------------------------------------------------------
1 | υse(new Error());
16 | $app->υse(new Timeout(5));
17 | $app->υse(new NotFound());
18 |
19 | $public_path = __DIR__ . DS . "public" ;
20 | $app->υse(new StaticFiles( $public_path ));
21 |
22 | $router = new Router();
23 |
24 | //http://127.0.0.1:3000/get/cookies
25 | //http://127.0.0.1:3000/get/session
26 | $router->get('/get/(\w+)', function(Context $ctx, $next, $vars) {
27 | $name = '';
28 | if ( $vars[0] == 'session' ) {
29 | $name = $ctx->getSession('name1');
30 | }else{
31 | $name = $ctx->getCookie('name1');
32 | }
33 | $ctx->status = 200;
34 | $ctx->body = $name ? $name : '';
35 | });
36 | //http://127.0.0.1:3000/set/1234
37 | $router->get('/set/(\d+)', function(Context $ctx, $next, $vars) {
38 | $ctx->setSession('name1',$vars[0]);
39 | $ctx->setCookie('name1',$vars[0]);
40 | $ctx->status = 200;
41 | $ctx->body = $vars[0];
42 | });
43 | //http://127.0.0.1:3000/clear/cookies
44 | //http://127.0.0.1:3000/clear/session
45 | $router->get('/clear/(\w+)', function(Context $ctx, $next, $vars) {
46 | if ( $vars[0] == 'cookies' ) {
47 | $ctx->clearCookie();
48 | }else{
49 | $ctx->clearSession();
50 | }
51 | $ctx->status = 200;
52 | $ctx->body = 'OK';
53 | });
54 | //http://127.0.0.1:3000/session
55 | $router->get('/session', function(Context $ctx, $next) {
56 | $session = $ctx->session;
57 | $ctx->status = 200;
58 | $ctx->body = json_encode($session);
59 | });
60 | //http://127.0.0.1:3000/cookies
61 | $router->get('/cookies', function(Context $ctx, $next) {
62 | $cookies = $ctx->cookies;
63 | $ctx->status = 200;
64 | $ctx->body = json_encode($cookies);
65 | });
66 |
67 | $app->υse($router->routes());
68 |
69 | $app->listen(3000);
--------------------------------------------------------------------------------
/example/file.txt:
--------------------------------------------------------------------------------
1 | upload file
--------------------------------------------------------------------------------
/example/hello.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Index
8 |
9 |
10 | {title}
11 | {time}
12 |
13 |
--------------------------------------------------------------------------------
/example/info.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Table
8 |
9 |
10 | {title}
11 |
12 | Name | Age |
13 |
14 |
15 |
16 | {name} |
17 | {age} |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/example/public/css/default.css:
--------------------------------------------------------------------------------
1 | html{
2 | background: white;
3 | }
4 |
5 | img{
6 | width: 100px;
7 | height: 100px;
8 | }
--------------------------------------------------------------------------------
/example/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/naka1205/phpkoa/2aadefa2996bab1b3a6bd09f5fa645f619a22422/example/public/favicon.ico
--------------------------------------------------------------------------------
/example/public/images/20264902.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/naka1205/phpkoa/2aadefa2996bab1b3a6bd09f5fa645f619a22422/example/public/images/20264902.jpg
--------------------------------------------------------------------------------
/example/table.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Table
8 |
9 |
10 | {title}
11 |
12 | Name | Age |
13 |
14 |
15 |
16 | {name} |
17 | {age} |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/example/template/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | PHPkoa Static
8 |
9 |
10 |
11 |
12 |
13 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/All.php:
--------------------------------------------------------------------------------
1 | tasks = $tasks;
13 | $this->parent = $parent;
14 | $this->n = count($tasks);
15 | $this->results = [];
16 | $this->done = false;
17 | }
18 | public function begin(callable $continuation) {
19 | $this->continuation = $continuation;
20 | foreach ($this->tasks as $id => $task) {
21 | (new AsyncTask($task, $this->parent))->begin($this->continuation($id));
22 | };
23 | }
24 | private function continuation($id) {
25 | return function($r, $ex = null) use($id) {
26 | if ($this->done) {
27 | return;
28 | }
29 | if($ex){
30 | $this->done = true;
31 | $k = $this->continuation;
32 | $k(null,$ex);
33 | return;
34 | }
35 | $this->results[$id] = $r;
36 | if(--$this->n === 0){
37 | $this->done = true;
38 | $k = $this->continuation;
39 | $k($this->results);
40 | }
41 | };
42 | }
43 | }
--------------------------------------------------------------------------------
/src/Any.php:
--------------------------------------------------------------------------------
1 | tasks = $tasks;
14 | $this->parent = $parent;
15 | $this->done = false;
16 | }
17 |
18 | public function begin(callable $continuation)
19 | {
20 | $this->continuation = $continuation;
21 | foreach ($this->tasks as $id => $task) {
22 | (new AsyncTask($task, $this->parent))->begin($this->continuation($id));
23 | };
24 | }
25 |
26 | private function continuation($id)
27 | {
28 | return function($r, $ex = null) use($id) {
29 | if ($this->done) {
30 | return;
31 | }
32 | $this->done = true;
33 |
34 | if ($this->continuation) {
35 | $k = $this->continuation;
36 | $k($r, $ex);
37 | }
38 | };
39 | }
40 | }
--------------------------------------------------------------------------------
/src/Application.php:
--------------------------------------------------------------------------------
1 | context = new Context();
15 | $this->context->app = $this;
16 | }
17 |
18 | public function υse(callable $fn)
19 | {
20 | $this->middleware[] = $fn;
21 | return $this;
22 | }
23 |
24 | public function listen($port = 8000,callable $fn = null)
25 | {
26 | $this->fn = compose($this->middleware);
27 | $this->httpServer = new Server($port);
28 |
29 | if ( $fn != null ) {
30 | $this->httpServer->onWorkerStart = $fn;
31 | }
32 |
33 | $this->httpServer->onConnect = [$this,'onConnect'];
34 | $this->httpServer->onMessage = [$this,'onRequest'];
35 | $this->httpServer->start();
36 |
37 | }
38 |
39 | public function onConnect(){
40 |
41 | }
42 |
43 | public function onRequest( $req, $res)
44 | {
45 | $ctx = $this->createContext($req, $res);
46 | $reqHandler = $this->makeRequest($ctx);
47 | $resHandler = $this->makeResponse($ctx);
48 | spawn($reqHandler, $resHandler);
49 | }
50 |
51 | protected function makeRequest(Context $ctx)
52 | {
53 | return function() use($ctx) {
54 | yield setCtx("ctx", $ctx);
55 | $ctx->res->status(404);
56 | $fn = $this->fn;
57 | yield $fn($ctx);
58 | };
59 | }
60 |
61 | protected function makeResponse(Context $ctx)
62 | {
63 | return function($r = null, \Exception $ex = null) use($ctx) {
64 | if ($ex) {
65 | $this->error($ctx, $ex);
66 | } else {
67 | $this->respond($ctx);
68 | }
69 | };
70 | }
71 |
72 | protected function error(Context $ctx, \Exception $ex = null)
73 | {
74 | if ($ex === null) {
75 | return;
76 | }
77 |
78 | if ($ex && $ex->getCode() !== 404) {
79 |
80 | }
81 |
82 | $msg = $ex->getCode();
83 | if ($ex instanceof HttpException) {
84 | $status = $ex->status ?: 500;
85 | $ctx->res->status($status);
86 | $msg = $ex->getMessage();
87 |
88 | } else {
89 | $ctx->res->status(500);
90 | }
91 |
92 | $ctx->res->header("Content-Type", "text");
93 | $ctx->res->write($msg);
94 | $ctx->res->end();
95 | }
96 |
97 | protected function respond(Context $ctx)
98 | {
99 | if ($ctx->respond === false) return;
100 |
101 | $body = $ctx->body;
102 | $code = $ctx->status;
103 |
104 | if ($code !== null) {
105 | $ctx->res->status($code);
106 | }
107 |
108 | if ($body !== null) {
109 | $ctx->res->write($body);
110 | }
111 |
112 | $ctx->res->end();
113 | }
114 |
115 | protected function createContext( $req, $res)
116 | {
117 | $context = clone $this->context;
118 |
119 | $request = $context->request = new Request($this, $context, $req, $res);
120 | $response = $context->response = new Response($this, $context, $req, $res);
121 |
122 | $context->app = $this;
123 | $context->req = $req;
124 | $context->res = $res;
125 |
126 | $request->response = $response;
127 | $response->request = $request;
128 |
129 | $request->originalUrl = $req->server["request_uri"];
130 | $request->ip = $req->server["remote_addr"];
131 |
132 | return $context;
133 | }
134 | }
--------------------------------------------------------------------------------
/src/Async.php:
--------------------------------------------------------------------------------
1 | gen = new Gen($gen);
12 | $this->parent = $parent;
13 | }
14 |
15 | public function begin(callable $continuation)
16 | {
17 | $this->continuation = $continuation;
18 | $this->next();
19 | }
20 |
21 | public function next($result = null, $ex = null)
22 | {
23 | try {
24 | if ($ex) {
25 | $value = $this->gen->throws($ex);
26 | } else {
27 | $value = $this->gen->send($result);
28 | }
29 |
30 | if ($this->gen->valid()) {
31 | if ($value instanceof Syscall) {
32 | $value = $value($this);
33 | }
34 |
35 | if ($value instanceof \Generator) {
36 | $value = new self($value, $this);
37 | }
38 |
39 | if ($value instanceof Async) {
40 | $cc = [$this, "next"];
41 | $value->begin($cc);
42 | } else {
43 | $this->next($value, null);
44 | }
45 | } else {
46 | $cc = $this->continuation;
47 | $cc($result, null);
48 | }
49 | } catch (\Exception $ex) {
50 | if ($this->gen->valid()) {
51 | $this->next(null, $ex);
52 | } else {
53 | $cc = $this->continuation;
54 | $cc($result, $ex);
55 | }
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/src/CallCC.php:
--------------------------------------------------------------------------------
1 | fun = $fun;
10 | }
11 |
12 | public function begin(callable $continuation)
13 | {
14 | $fun = $this->fun;
15 | $fun($continuation);
16 | }
17 | }
--------------------------------------------------------------------------------
/src/Context.php:
--------------------------------------------------------------------------------
1 | funcs)){
20 | return $this->funcs[$name][1](...$arguments);
21 | }
22 | $fn = [$this->response, $name];
23 | return $fn(...$arguments);
24 | }
25 |
26 | public function __get($name)
27 | {
28 | return $this->request->$name;
29 | }
30 |
31 | public function __set($name, $value)
32 | {
33 | $this->response->$name = $value;
34 | }
35 |
36 | public function add($func, $callback){
37 | $this->funcs[$func] = array($func, $callback);
38 | }
39 |
40 | public function accept($name)
41 | {
42 | return strpos($this->request->server['http_accept'],$name) === false ? false : true;
43 | }
44 |
45 | public function thrοw($status, $message)
46 | {
47 | if ($message instanceof \Exception) {
48 | $ex = $message;
49 | throw new HttpException($status, $ex->getMessage(), $ex->getCode(), $ex->getPrevious());
50 | } else {
51 | throw new HttpException($status, $message);
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/src/Error.php:
--------------------------------------------------------------------------------
1 | getCode() ?: 0;
12 | $msg = "Internal Error";
13 |
14 | if ( $ex instanceof HttpException ) {
15 | $status = $ex->getStatus();
16 | $msg = $ex->getMessage();
17 | }
18 |
19 | $err = [ "code" => $code, "msg" => $msg ];
20 | if ( $ctx->accept("json") ) {
21 | $ctx->status = 200;
22 | $ctx->body = json_encode($err);
23 | } else {
24 | $ctx->status = $status;
25 | if ($status === 404) {
26 | $ctx->body = (yield Template::render(__DIR__ . "/template/404.html"));
27 | } else if ($status === 500) {
28 | $ctx->body = (yield Template::render(__DIR__ . "/template/500.html"));
29 | } else {
30 | $ctx->body = (yield Template::render(__DIR__ . "/template/error.html",$err));
31 | }
32 | }
33 |
34 |
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/src/Gen.php:
--------------------------------------------------------------------------------
1 | id = $id;
11 | $this->generator = $generator;
12 | }
13 |
14 | public function throws(\Exception $ex){
15 | return $this->generator->throw($ex);
16 | }
17 |
18 | public function valid(){
19 | return $this->generator->valid();
20 | }
21 |
22 | public function send($value = null){
23 |
24 | if( $this->isfirst ){
25 | $this->isfirst = false;
26 | $result = $this->generator->current();
27 | }else{
28 | $result = $this->generator->send($value);
29 | }
30 | return $result;
31 | }
32 | }
--------------------------------------------------------------------------------
/src/HttpException.php:
--------------------------------------------------------------------------------
1 | status = $status;
11 | $this->headers = $headers;
12 |
13 | parent::__construct($message, $code, $previous);
14 | }
15 |
16 | public function getStatus()
17 | {
18 | return $this->status;
19 | }
20 |
21 | public function getHeaders()
22 | {
23 | return $this->headers;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Middleware.php:
--------------------------------------------------------------------------------
1 | status !== 404 || $ctx->body){
12 | return;
13 | }
14 | $ctx->status = 404;
15 | $ctx->body = (yield Template::render(__DIR__ . "/template/404.html"));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Request.php:
--------------------------------------------------------------------------------
1 | app = $app;
16 | $this->ctx = $ctx;
17 | $this->req = $req;
18 | $this->res = $res;
19 | }
20 |
21 | public function __get($name)
22 | {
23 | switch ($name) {
24 | case "rawcontent":
25 | return $this->req->rawContent();
26 | case "post":
27 | return isset($this->req->post) ? $this->req->post : [];
28 | case "get":
29 | return isset($this->req->get) ? $this->req->get : [];
30 | case "cookie":
31 | case "cookies":
32 | return isset($this->req->cookie) ? $this->req->cookie : [];
33 | case "request":
34 | return isset($this->req->request) ? $this->req->request : [];
35 | case "header":
36 | case "headers":
37 | return isset($this->req->header) ? $this->req->header : [];
38 | case "files":
39 | return isset($this->req->files) ? $this->req->files : [];
40 | case "accept":
41 | return $this->req->server['http_accept'];
42 | case "method":
43 | return $this->req->server["request_method"];
44 | case "url":
45 | case "origin":
46 | return $this->req->server["request_uri"];
47 | case "path":
48 | return isset($this->req->server["path_info"]) ? $this->req->server["path_info"] : "";
49 | case "query":
50 | case "querystring":
51 | return isset($this->req->server["query_string"]) ? $this->req->server["query_string"] : "";
52 | case "host":
53 | case "hostname":
54 | return isset($this->req->header["host"]) ? $this->req->header["host"] : "";
55 | case "protocol":
56 | return $this->req->server["server_protocol"];
57 | default:
58 | return $this->req->$name;
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/src/Response.php:
--------------------------------------------------------------------------------
1 | app = $app;
15 | $this->ctx = $ctx;
16 | $this->req = $req;
17 | $this->res = $res;
18 | }
19 |
20 | public function __call($name, $arguments)
21 | {
22 | $fn = [$this->res, $name];
23 | return $fn(...$arguments);
24 | }
25 |
26 | public function __get($name)
27 | {
28 | return $this->res->$name;
29 | }
30 |
31 | public function __set($name, $value)
32 | {
33 | switch ($name) {
34 | case "type":
35 | return $this->res->header("Content-Type", $value);
36 | case "lastModified":
37 | return $this->res->header("Last-Modified", $value);
38 | case "etag":
39 | return $this->res->header("ETag", $value);
40 | case "length":
41 | return $this->res->header("Content-Length", $value);
42 | default:
43 | return $this->res->header($name, $value);
44 | }
45 | }
46 |
47 | public function end($html = "")
48 | {
49 | if ($this->isEnd) {
50 | return false;
51 | }
52 | $this->isEnd = true;
53 | return $this->res->end($html);
54 | }
55 |
56 | public function redirect($url, $status = 302)
57 | {
58 | $this->res->header("Location", $url);
59 | $this->res->header("Content-Type", "text/plain; charset=utf-8");
60 | $this->ctx->status = $status;
61 | $this->ctx->body = "Redirecting to $url.";
62 | }
63 |
64 | public function render($file)
65 | {
66 | $this->ctx->body = ( yield Template::render($file, $this->ctx->state) );
67 | }
68 | }
--------------------------------------------------------------------------------
/src/Router.php:
--------------------------------------------------------------------------------
1 | requestedMethod = $this->getRequestMethod();
33 |
34 | $handled = 0;
35 | $quitAfterRun = false;
36 |
37 | if (isset($this->beforeRoutes[$this->requestedMethod])) {
38 | $before = $this->handle($this->beforeRoutes[$this->requestedMethod]);
39 | if ($before['handled'] != 0 && is_callable($before['fn'])) {
40 | yield $before['fn']($ctx, $next, $before['vars']);
41 | // 状态码已写入 终止
42 | if ( $ctx->status ) {
43 | return;
44 | }
45 | }
46 | }
47 |
48 | if (isset($this->afterRoutes[$this->requestedMethod])) {
49 | $route = ( yield $this->handle($this->afterRoutes[$this->requestedMethod]) );
50 | $handled = $route['handled'];
51 | }
52 | switch ($handled) {
53 | case 0:
54 | // 状态码写入Context
55 | $ctx->status = 404;
56 | yield $next;
57 | break;
58 |
59 | default:
60 | $fn = $route['fn'];
61 | $vars = $route['vars'];
62 | // 从路由表提取处理器
63 | yield $fn($ctx, $next, $vars);
64 | break;
65 | }
66 | }
67 |
68 |
69 | public function before($methods, $pattern, $fn)
70 | {
71 | $pattern .= $this->suffix;
72 | $pattern = $this->baseRoute.'/'.trim($pattern, '/');
73 | $pattern = $this->baseRoute ? rtrim($pattern, '/') : $pattern;
74 | foreach (explode('|', $methods) as $method) {
75 | $this->beforeRoutes[$method][] = array(
76 | 'pattern' => $pattern,
77 | 'fn' => $fn,
78 | );
79 | }
80 | }
81 |
82 | public function match($methods, $pattern, $fn)
83 | {
84 | $pattern .= $this->suffix;
85 | $pattern = $this->baseRoute.'/'.trim($pattern, '/');
86 | $pattern = $this->baseRoute ? rtrim($pattern, '/') : $pattern;
87 | foreach (explode('|', $methods) as $method) {
88 | $this->afterRoutes[$method][] = array(
89 | 'pattern' => $pattern,
90 | 'fn' => $fn,
91 | );
92 | }
93 | }
94 |
95 | public function all($pattern, $fn)
96 | {
97 | $this->match('GET|POST|PUT|DELETE|OPTIONS|PATCH|HEAD', $pattern, $fn);
98 | }
99 |
100 | public function get($pattern, $fn)
101 | {
102 | $this->match('GET', $pattern, $fn);
103 | }
104 |
105 | public function post($pattern, $fn)
106 | {
107 | $this->match('POST', $pattern, $fn);
108 | }
109 |
110 | public function patch($pattern, $fn)
111 | {
112 | $this->match('PATCH', $pattern, $fn);
113 | }
114 |
115 | public function delete($pattern, $fn)
116 | {
117 | $this->match('DELETE', $pattern, $fn);
118 | }
119 |
120 | public function put($pattern, $fn)
121 | {
122 | $this->match('PUT', $pattern, $fn);
123 | }
124 |
125 | public function options($pattern, $fn)
126 | {
127 | $this->match('OPTIONS', $pattern, $fn);
128 | }
129 |
130 | public function mount($baseRoute, $fn)
131 | {
132 | $curBaseRoute = $this->baseRoute;
133 | $this->baseRoute .= $baseRoute;
134 | call_user_func($fn);
135 | $this->baseRoute = $curBaseRoute;
136 | }
137 |
138 | public function suffix($str = '')
139 | {
140 | $this->suffix = $str;
141 | }
142 |
143 | public function getRequestHeaders()
144 | {
145 | $headers = array();
146 | if (function_exists('getallheaders')) {
147 | $headers = getallheaders();
148 | if ($headers !== false) {
149 | return $headers;
150 | }
151 | }
152 | foreach ($_SERVER as $name => $value) {
153 | if ((substr($name, 0, 5) == 'HTTP_') || ($name == 'CONTENT_TYPE') || ($name == 'CONTENT_LENGTH')) {
154 | $headers[str_replace(array(' ', 'Http'), array('-', 'HTTP'), ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
155 | }
156 | }
157 | return $headers;
158 | }
159 |
160 | public function getRequestMethod()
161 | {
162 | $method = $_SERVER['REQUEST_METHOD'];
163 | if ($_SERVER['REQUEST_METHOD'] == 'HEAD') {
164 | ob_start();
165 | $method = 'GET';
166 | }elseif ($_SERVER['REQUEST_METHOD'] == 'POST') {
167 | $headers = $this->getRequestHeaders();
168 | if (isset($headers['X-HTTP-Method-Override']) && in_array($headers['X-HTTP-Method-Override'], array('PUT', 'DELETE', 'PATCH'))) {
169 | $method = $headers['X-HTTP-Method-Override'];
170 | }
171 | }
172 | return $method;
173 | }
174 |
175 | public function notFound($fn)
176 | {
177 | $this->notFoundCallback = $fn;
178 | }
179 |
180 | private function handle($routes)
181 | {
182 | $fn = null;
183 | $vars = null;
184 | $handled = 0;
185 | $uri = $this->getCurrentUri();
186 | foreach ($routes as $route) {
187 | $route['pattern'] = preg_replace('/{([A-Za-z]*?)}/', '(\w+)', $route['pattern']);
188 | if (preg_match_all('#^'.$route['pattern'].'$#', $uri, $matches, PREG_OFFSET_CAPTURE)) {
189 | $matches = array_slice($matches, 1);
190 | $params = array_map(function ($match, $index) use ($matches) {
191 | if (isset($matches[$index + 1]) && isset($matches[$index + 1][0]) && is_array($matches[$index + 1][0])) {
192 | return trim(substr($match[0][0], 0, $matches[$index + 1][0][1] - $match[0][1]), '/');
193 | }else {
194 | return isset($match[0][0]) ? trim($match[0][0], '/') : null;
195 | }
196 | }, $matches, array_keys($matches));
197 |
198 | $fn = $route['fn'];
199 | $vars = $params;
200 | ++$handled;
201 | break;
202 | }
203 | }
204 | return ['handled' => $handled,'fn' => $fn,'vars' => $vars];
205 | }
206 |
207 | protected function getCurrentUri()
208 | {
209 | $uri = substr($_SERVER['REQUEST_URI'], strlen($this->getBasePath()));
210 | if (strstr($uri, '?')) {
211 | $uri = substr($uri, 0, strpos($uri, '?'));
212 | }
213 | return '/'.trim($uri, '/');
214 | }
215 |
216 | protected function getBasePath()
217 | {
218 | if ($this->serverBasePath === null) {
219 | $this->serverBasePath = implode('/', array_slice(explode('/', $_SERVER['SCRIPT_NAME']), 0, -1)).'/';
220 | }
221 | return $this->serverBasePath;
222 | }
223 | }
--------------------------------------------------------------------------------
/src/Syscall.php:
--------------------------------------------------------------------------------
1 | fun = $fun;
10 | }
11 |
12 | public function __invoke(AsyncTask $task)
13 | {
14 | $cb = $this->fun;
15 | return $cb($task);
16 | }
17 | }
--------------------------------------------------------------------------------
/src/Template.php:
--------------------------------------------------------------------------------
1 | accurate = $accurate;
18 | $this->loadfile(".", $filename);
19 | }
20 |
21 | public function addFile($varname, $filename){
22 | if(!$this->exists($varname)) throw new \InvalidArgumentException("addFile: var $varname does not exist");
23 | $this->loadfile($varname, $filename);
24 | }
25 |
26 | public function __set($varname, $value){
27 | if(!$this->exists($varname)) throw new \RuntimeException("var $varname does not exist");
28 | $stringValue = $value;
29 | if(is_object($value)){
30 | $this->instances[$varname] = $value;
31 | if(!isset($this->properties[$varname])) $this->properties[$varname] = array();
32 | if(method_exists($value, "__toString")) $stringValue = $value->__toString();
33 | else $stringValue = "Object";
34 | }
35 | $this->setValue($varname, $stringValue);
36 | return $value;
37 | }
38 |
39 | public function __get($varname){
40 | if(isset($this->values["{".$varname."}"])) return $this->values["{".$varname."}"];
41 | elseif(isset($this->instances[$varname])) return $this->instances[$varname];
42 | throw new \RuntimeException("var $varname does not exist");
43 | }
44 |
45 | public function exists($varname){
46 | return in_array($varname, $this->vars);
47 | }
48 |
49 | private function loadfile($varname, $filename) {
50 | if (!file_exists($filename)) throw new \InvalidArgumentException("file $filename does not exist");
51 | if($this->isPHP($filename)){
52 | ob_start();
53 | require $filename;
54 | $str = ob_get_contents();
55 | ob_end_clean();
56 | $this->setValue($varname, $str);
57 | } else {
58 | $str = preg_replace("//smi", "", file_get_contents($filename));
59 | if (empty($str)) throw new \InvalidArgumentException("file $filename is empty");
60 | $this->setValue($varname, $str);
61 | $blocks = $this->identify($str, $varname);
62 | $this->createBlocks($blocks);
63 | }
64 | }
65 |
66 | private function isPHP($filename){
67 | foreach(array('.php', '.php5', '.cgi') as $php){
68 | if(0 == strcasecmp($php, substr($filename, strripos($filename, $php)))) return true;
69 | }
70 | return false;
71 | }
72 |
73 | private function identify(&$content, $varname){
74 | $blocks = array();
75 | $queued_blocks = array();
76 | $this->identifyVars($content);
77 | $lines = explode("\n", $content);
78 | if(1==sizeof($lines)){
79 | $content = str_replace('-->', "-->\n", $content);
80 | $lines = explode("\n", $content);
81 | }
82 | foreach (explode("\n", $content) as $line) {
83 | if (strpos($line, "/sm";
90 | preg_match($reg, $line, $m);
91 | if (1==preg_match($reg, $line, $m)){
92 | if (0==sizeof($queued_blocks)) $parent = $varname;
93 | else $parent = end($queued_blocks);
94 | if (!isset($blocks[$parent])){
95 | $blocks[$parent] = array();
96 | }
97 | $blocks[$parent][] = $m[1];
98 | $queued_blocks[] = $m[1];
99 | }
100 | $reg = "//sm";
101 | if (1==preg_match($reg, $line)) array_pop($queued_blocks);
102 | }
103 |
104 | private function identifyVars(&$content){
105 | $r = preg_match_all("/{(".self::$REG_NAME.")((\-\>(".self::$REG_NAME."))*)?((\|.*?)*)?}/", $content, $m);
106 | if ($r){
107 | for($i=0; $i<$r; $i++){
108 | if($m[3][$i] && (!isset($this->properties[$m[1][$i]]) || !in_array($m[3][$i], $this->properties[$m[1][$i]]))){
109 | $this->properties[$m[1][$i]][] = $m[3][$i];
110 | }
111 | if($m[7][$i] && (!isset($this->modifiers[$m[1][$i]]) || !in_array($m[7][$i], $this->modifiers[$m[1][$i].$m[3][$i]]))){
112 | $this->modifiers[$m[1][$i].$m[3][$i]][] = $m[1][$i].$m[3][$i].$m[7][$i];
113 | }
114 | if(!in_array($m[1][$i], $this->vars)){
115 | $this->vars[] = $m[1][$i];
116 | }
117 | }
118 | }
119 | }
120 |
121 | private function createBlocks(&$blocks) {
122 | $this->parents = array_merge($this->parents, $blocks);
123 | foreach($blocks as $parent => $block){
124 | foreach($block as $chield){
125 | if(in_array($chield, $this->blocks)) throw new \UnexpectedValueException("duplicated block: $chield");
126 | $this->blocks[] = $chield;
127 | $this->setBlock($parent, $chield);
128 | }
129 | }
130 | }
131 |
132 | private function setBlock($parent, $block) {
133 | $name = $block.'_value';
134 | $str = $this->getVar($parent);
135 | if($this->accurate){
136 | $str = str_replace("\r\n", "\n", $str);
137 | $reg = "/\t*\n*(\s*.*?\n?)\t*\n*((\s*.*?\n?)\t*\n?)?/sm";
138 | }
139 | else $reg = "/\s*(\s*.*?\s*)\s*((\s*.*?\s*))?\s*/sm";
140 | if(1!==preg_match($reg, $str, $m)) throw new \UnexpectedValueException("mal-formed block $block");
141 | $this->setValue($name, '');
142 | $this->setValue($block, $m[1]);
143 | $this->setValue($parent, preg_replace($reg, "{".$name."}", $str));
144 | if(isset($m[3])) $this->finally[$block] = $m[3];
145 | }
146 |
147 | protected function setValue($varname, $value) {
148 | $this->values['{'.$varname.'}'] = $value;
149 | }
150 |
151 | private function getVar($varname) {
152 | return $this->values['{'.$varname.'}'];
153 | }
154 |
155 | public function clear($varname) {
156 | $this->setValue($varname, "");
157 | }
158 |
159 | public function setParent($parent, $block){
160 | $this->parents[$parent][] = $block;
161 | }
162 |
163 | private function substModifiers($value, $exp){
164 | $statements = explode('|', $exp);
165 | for($i=1; $ivalues), $this->values, $value);
176 | foreach($this->modifiers as $var => $expressions){
177 | if(false!==strpos($s, "{".$var."|")) foreach($expressions as $exp){
178 | if(false===strpos($var, "->") && isset($this->values['{'.$var.'}'])){
179 | $s = str_replace('{'.$exp.'}', $this->substModifiers($this->values['{'.$var.'}'], $exp), $s);
180 | }
181 | }
182 | }
183 | foreach($this->instances as $var => $instance){
184 | foreach($this->properties[$var] as $properties){
185 | if(false!==strpos($s, "{".$var.$properties."}") || false!==strpos($s, "{".$var.$properties."|")){
186 | $pointer = $instance;
187 | $property = explode("->", $properties);
188 | for($i = 1; $i < sizeof($property); $i++){
189 | if(!is_null($pointer)){
190 | $obj = strtolower(str_replace('_', '', $property[$i]));
191 | if(method_exists($pointer, "get$obj")) $pointer = $pointer->{"get$obj"}();
192 | elseif(method_exists($pointer, "__get")) $pointer = $pointer->__get($property[$i]);
193 | elseif(property_exists($pointer, $obj)) $pointer = $pointer->$obj;
194 | else {
195 | $className = $property[$i-1] ? $property[$i-1] : get_class($instance);
196 | $class = is_null($pointer) ? "NULL" : get_class($pointer);
197 | throw new \BadMethodCallException("no accessor method in class ".$class." for ".$className."->".$property[$i]);
198 | }
199 | } else {
200 | $pointer = $instance->get($obj);
201 | }
202 | }
203 | if(is_object($pointer)){
204 | $pointer = method_exists($pointer, "__toString") ? $pointer->__toString() : "Object";
205 | } elseif(is_array($pointer)){
206 | $value = "";
207 | for($i=0; list($key, $val) = each($pointer); $i++){
208 | $value.= "$key => $val";
209 | if($imodifiers[$var.$properties])){
215 | foreach($this->modifiers[$var.$properties] as $exp){
216 | $s = str_replace('{'.$exp.'}', $this->substModifiers($pointer, $exp), $s);
217 | }
218 | }
219 | }
220 | }
221 | }
222 | return $s;
223 | }
224 |
225 | public function block($block, $append = true) {
226 | if(!in_array($block, $this->blocks)) throw new \InvalidArgumentException("block $block does not exist");
227 | if(isset($this->parents[$block])) foreach($this->parents[$block] as $child){
228 | if(isset($this->finally[$child]) && !in_array($child, $this->parsed)){
229 | $this->setValue($child.'_value', $this->subst($this->finally[$child]));
230 | $this->parsed[] = $block;
231 | }
232 | }
233 | if ($append) {
234 | $this->setValue($block.'_value', $this->getVar($block.'_value') . $this->subst($this->getVar($block)));
235 | } else {
236 | $this->setValue($block.'_value', $this->getVar($block.'_value'));
237 | }
238 | if(!in_array($block, $this->parsed)) $this->parsed[] = $block;
239 | if(isset($this->parents[$block])) foreach($this->parents[$block] as $child) $this->clear($child.'_value');
240 | }
241 |
242 | public function parse() {
243 | foreach(array_reverse($this->parents) as $parent => $children){
244 | foreach($children as $block){
245 | if(in_array($parent, $this->blocks) && in_array($block, $this->parsed) && !in_array($parent, $this->parsed)){
246 | $this->setValue($parent.'_value', $this->subst($this->getVar($parent)));
247 | $this->parsed[] = $parent;
248 | }
249 | }
250 | }
251 | foreach($this->finally as $block => $content){
252 | if(!in_array($block, $this->parsed)){
253 | $this->setValue($block.'_value', $this->subst($content));
254 | }
255 | }
256 | return preg_replace("/{(".self::$REG_NAME.")((\-\>(".self::$REG_NAME."))*)?((\|.*?)*)?}/", "", $this->subst($this->getVar(".")));
257 | }
258 |
259 | public static function render($file,$data=[]) {
260 | $tpl = new Template($file);
261 | try {
262 | foreach ($data as $key => $value) {
263 | if ( !is_array($value) ) {
264 | $tpl->$key = $value;
265 | continue;
266 | }
267 | if ( sizeof($value) <= 0 ) {
268 | $tpl->block(strtoupper($key). '_EMPTY');
269 | continue;
270 | }
271 | $bool = false;
272 | foreach($value as $ke => $val){
273 | if ( !is_array($val) ) {
274 | $tpl->$ke = $val;
275 | }else{
276 | $bool = true;
277 | foreach ($val as $k => $v) {
278 | $tpl->$k = $v;
279 | }
280 | $tpl->block(strtoupper($key));
281 | }
282 | }
283 | if ( !$bool ) {
284 | $tpl->block(strtoupper($key));
285 | continue;
286 | }
287 | }
288 | } catch (\Exception $e){
289 |
290 | return "Template Error!";
291 |
292 | }
293 | return $tpl->parse();
294 | }
295 | }
--------------------------------------------------------------------------------
/src/Timeout.php:
--------------------------------------------------------------------------------
1 | times = $times;
12 | if ($ex === null) {
13 | $this->exception = new HttpException(408, "Request timeout");
14 | } else {
15 | $this->exception = $ex;
16 | }
17 | }
18 |
19 | public function __invoke(Context $ctx, $next)
20 | {
21 | yield race([
22 | callcc(function($k) {
23 | Timer::add($this->times, function() use($k) {
24 | $k(null, $this->exception);
25 | },[],false);
26 | }),
27 | function() use($next) {
28 | yield $next;
29 | }
30 | ]);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/functions.php:
--------------------------------------------------------------------------------
1 | $v) {
43 | $task->$k = $v;
44 | }
45 | (new AsyncTask($task, $parent))->begin($continuation);
46 | } else {
47 | $continuation($task, null);
48 | }
49 | }
50 |
51 | function await($task, ...$args)
52 | {
53 | if ($task instanceof \Generator) {
54 | return $task;
55 | }
56 |
57 | if (is_callable($task)) {
58 | $gen = function() use($task, $args) { yield $task(...$args); };
59 | } else {
60 | $gen = function() use($task) { yield $task; };
61 | }
62 | return $gen();
63 | }
64 |
65 | function race(array $tasks)
66 | {
67 | $tasks = array_map(__NAMESPACE__ . "\\await", $tasks);
68 |
69 | return new Syscall(function(AsyncTask $parent) use($tasks) {
70 | if (empty($tasks)) {
71 | return null;
72 | } else {
73 | return new Any($tasks, $parent);
74 | }
75 | });
76 | }
77 |
78 | function all(array $tasks){
79 |
80 | $tasks = array_map(__NAMESPACE__."\\await",$tasks);
81 |
82 | return new SysCall(function(AsyncTask $parent) use ($tasks){
83 | if(empty($tasks)){
84 | return null;
85 | }else{
86 | return new All($tasks,$parent);
87 | }
88 | });
89 | }
90 |
91 | function timeout($ms)
92 | {
93 | return callcc(function($k) use($ms) {
94 |
95 | Timer::add($ms, function() use($k) {
96 | $k(null, new \Exception("timeout"));
97 | },[],false);
98 |
99 | });
100 | }
101 |
102 | function async_sleep($ms){
103 | return callcc( function($k) use($ms){
104 |
105 | Timer::add($ms, function() use($k) {
106 | $k(null);
107 | },[],false);
108 |
109 | });
110 | }
111 |
112 | function async_http_curl( $method,$host,$params = []){
113 |
114 | }
115 |
116 | function callcc(callable $fun, $timeout = 0)
117 | {
118 | if ($timeout > 0) {
119 | $fun = timeoutWrapper($fun, $timeout);
120 | }
121 | return new CallCC($fun);
122 | }
123 |
124 |
125 | function timeoutWrapper(callable $fun,$ms){
126 | return function ($k) use($fun,$ms){
127 | $k = once($k);
128 | $fun($k);
129 |
130 | Timer::add($ms, function() use($k) {
131 | $k(null, new \Exception("timeout"));
132 | },[],false);
133 |
134 | };
135 | }
136 |
137 | function getCtx($key, $default = null)
138 | {
139 | return new Syscall(function(AsyncTask $task) use($key, $default) {
140 | while($task->parent && $task = $task->parent);
141 | if (isset($task->gen->generator->$key)) {
142 | return $task->gen->generator->$key;
143 | } else {
144 | return $default;
145 | }
146 | });
147 | }
148 |
149 | function setCtx($key, $val)
150 | {
151 | return new Syscall(function(AsyncTask $task) use($key, $val) {
152 | while($task->parent && $task = $task->parent);
153 | $task->gen->generator->$key = $val;
154 | });
155 | }
156 |
157 | function array_right_reduce(array $input, callable $function, $initial = null)
158 | {
159 | return array_reduce(array_reverse($input, true), $function, $initial);
160 | }
161 |
162 | function compose(array $middleware)
163 | {
164 | return function(Context $ctx = null) use($middleware) {
165 | $ctx = $ctx ?: new Context();
166 | return array_right_reduce($middleware, function($rightNext, $leftFn) use($ctx) {
167 | return $leftFn($ctx, $rightNext);
168 | }, null);
169 | };
170 | }
--------------------------------------------------------------------------------
/src/template/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 404 Error
8 |
9 |
10 | 页面丢失了!
11 |
12 |
--------------------------------------------------------------------------------
/src/template/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 500 Error
8 |
9 |
10 | 服务器出错了!
11 |
12 |
--------------------------------------------------------------------------------
/src/template/error.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Error
8 |
9 |
10 | 页面出错了!
11 | 错误代码: {code}
12 | 错误消息: {msg}
13 |
14 |
--------------------------------------------------------------------------------