naka1205 / phpkoa
php koa
v1.8
2019-02-25 02:32 UTC
Requires
- php: >=5.4
- naka1205/phpkoa_static: ^1.2
- naka1205/phpsocket: ^1.9
README
PHP异步编程:
基于 PHP
实(chao)现(xi) NODEJS
web框架 KOA
说明
偶然间在 GITHUB
上看到有赞官方仓库的 手把手教你实现co与Koa 。由于此前用过 KOA
,对于 KOA
的洋葱模型叹为观止。不由得心血来潮的看完了整个文档,接着 CTRL+C
、CTRL+V
让代码跑了起来。
文档中是基于 swoole
扩展进行开发,而 swoole
对 WINDOWS
并不友好,向来习惯在 WINDOWS
下开发的我一鼓作气,将Workerman 改写并兼容了此项目。
体验
- PHPKoa Demo 是使用
PHPKoa
开发HTTP SERVER
的一个简单示例! - PHP Krpano PHP 全景图片生成!
- PKBook 静态博客发布程序!
- H5Make H5编辑器!
建议
欢迎各位大神提交issues
安装
composer require naka1205/phpkoa
使用
Hello World
<?php require __DIR__ . '/vendor/autoload.php'; use Naka507\Koa\Application; use Naka507\Koa\Context; $app = new Application(); $app->υse(function(Context $ctx) { $ctx->status = 200; $ctx->body = "<h1>Hello World</h1>"; }); $app->listen(3000,function(){ echo "PHPKoa is listening in 3000\n"; });
Error
<?php require __DIR__ . '/vendor/autoload.php'; use Naka507\Koa\Application; use Naka507\Koa\Context; use Naka507\Koa\Error; use Naka507\Koa\Timeout; use Naka507\Koa\Router; use Naka507\Koa\NotFound; $app = new Application(); $app->υse(new Error()); $app->υse(new NotFound()); $app->υse(new Timeout(3)); //设置3秒超时 $router = new Router(); //正常访问 $router->get('/hello', function(Context $ctx, $next) { $ctx->status = 200; $ctx->body = "<h1>Hello World</h1>"; }); //访问超时 $router->get('/timeout', function(Context $ctx, $next) { yield async_sleep(5); }); //访问出错 $router->get('/error', function(Context $ctx, $next) { $ctx->thrοw(500, "Internal Error"); yield; }); $app->υse($router->routes()); $app->listen(3000);
Router
<?php require __DIR__ . '/vendor/autoload.php'; define('DS', DIRECTORY_SEPARATOR); use Naka507\Koa\Application; use Naka507\Koa\Context; use Naka507\Koa\Error; use Naka507\Koa\Timeout; use Naka507\Koa\Router; $app = new Application(); $app->υse(new Error()); $app->υse(new Timeout(5)); $router = new Router(); $router->get('/demo1', function(Context $ctx, $next) { $ctx->body = "demo1"; }); $router->get('/demo2', function(Context $ctx, $next) { $ctx->body = "demo2"; }); $router->get('/demo3/(\d+)', function(Context $ctx, $next, $vars) { $ctx->status = 200; $ctx->body = "demo3={$vars[0]}"; }); $router->get('/demo4', function(Context $ctx, $next) { $ctx->redirect("/demo2"); }); //RESTful API $router->post('/demo3/(\d+)', function(Context $ctx, $next, $vars) { //设置 session $ctx->setSession('demo3',$vars[0]); //设置 cookie $ctx->setCookie('demo3',$vars[0]); $ctx->status = 200; $ctx->body = "post:demo3={$vars[0]}"; }); $router->put('/demo3/(\d+)', function(Context $ctx, $next, $vars) { //获取单个 cookie $cookie_demo3 = $ctx->getCookie('demo3'); //或者 $cookies = $ctx->cookies['demo3']; //获取单个 session $session_demo3 = $ctx->getSession('demo3'); //或者 $session = $ctx->session['demo3']; $ctx->status = 200; $ctx->body = "put:demo3={$vars[0]}"; }); $router->delete('/demo3/(\d+)', function(Context $ctx, $next, $vars) { //清除所有 cookie $ctx->clearCookie(); //清除所有 session $ctx->clearSession(); $ctx->status = 200; $ctx->body = "delete:demo3={$vars[0]}"; }); //文件上传 $router->post('/files/(\d+)', function(Context $ctx, $next, $vars) { $upload_path = __DIR__ . DS . "uploads" . DS; if ( !is_dir($upload_path) ) { mkdir ($upload_path , 0777, true); } $files = []; foreach ( $ctx->request->files as $key => $value) { if ( !$value['file_name'] || !$value['file_data'] ) { continue; } $file_path = $upload_path . $value['file_name']; file_put_contents($file_path, $value['file_data']); $value['file_path'] = $file_path; $files[] = $value; } $ctx->status = 200; $ctx->body = json_encode($files); }); $app->υse($router->routes()); $app->listen(3000);
<?php //此处已省略 ... //使用第三方 HTTP 客户端类库,方便测试 use GuzzleHttp\Client; $router = new Router(); //路由分组 //http://127.0.0.1:5000/curl/get //http://127.0.0.1:5000/curl/post //http://127.0.0.1:5000/curl/put //http://127.0.0.1:5000/curl/delete $router->mount('/curl', function() use ($router) { $client = new Client(); $router->get('/get', function( Context $ctx, $next ) use ($client) { $r = (yield $client->request('GET', 'http://127.0.0.1:3000/demo3/1')); $ctx->status = $r->getStatusCode(); $ctx->body = $r->getBody(); }); $router->get('/post', function(Context $ctx, $next ) use ($client){ $r = (yield $client->request('POST', 'http://127.0.0.1:3000/demo3/2')); $ctx->status = $r->getStatusCode(); $ctx->body = $r->getBody(); }); $router->get('/put', function( Context $ctx, $next ) use ($client){ $r = (yield $client->request('PUT', 'http://127.0.0.1:3000/demo3/3')); $ctx->status = $r->getStatusCode(); $ctx->body = $r->getBody(); }); $router->get('/delete', function( Context $ctx, $next ) use ($client){ $r = (yield $client->request('DELETE', 'http://127.0.0.1:3000/demo3/4')); $ctx->status = $r->getStatusCode(); $ctx->body = $r->getBody(); }); }); //http://127.0.0.1:5000/files $router->get('/files', function(Context $ctx, $next ) { $client = new Client(); $r = ( yield $client->request('POST', 'http://127.0.0.1:3000/files/2', [ 'multipart' => [ [ 'name' => 'file_name', 'contents' => fopen( __DIR__ . '/file.txt', 'r') ], [ 'name' => 'other_file', 'contents' => 'hello', 'filename' => 'filename.txt', 'headers' => [ 'X-Foo' => 'this is an extra header to include' ] ] ] ])); $ctx->status = $r->getStatusCode(); $ctx->body = $r->getBody(); }); // $router->get('/curl/(\w+)', function(Context $ctx, $next, $vars) { // $method = strtoupper($vars[0]); // $client = new Client(); // $r = (yield $client->request($method, 'http://127.0.0.1:3000/demo3/123')); // $ctx->status = $r->getStatusCode(); // $ctx->body = $r->getBody(); // }); $app->υse($router->routes()); $app->listen(5000);
Template
<body> <h1>{title}</h1> <p>{time}</p> </body>
<?php require __DIR__ . '/vendor/autoload.php'; use Naka507\Koa\Application; use Naka507\Koa\Context; use Naka507\Koa\Error; use Naka507\Koa\Timeout; use Naka507\Koa\Router; $app = new Application(); $app->υse(new Error()); $app->υse(new Timeout(5)); $router = new Router(); $router->get('/hello', function(Context $ctx) { $ctx->status = 200; $ctx->state["title"] = "HELLO WORLD"; $ctx->state["time"] = date("Y-m-d H:i:s", time());; yield $ctx->render(__DIR__ . "/hello.html"); }); $app->υse($router->routes()); $app->listen(3000);
<body> <p>{title}</p> <table border=1> <tr><td>Name</td><td>Age</td></tr> <!-- BEGIN INFO --> <tr> <td> {name} </td> <td> {age} </td> </tr> <!-- END INFO --> </table> </body>
<?php //此处已省略 ... //一维数组 $router->get('/info', function(Context $ctx) { $info = array("name" => "小明", "age" => 15); $ctx->status = 200; $ctx->state["title"] = "这是一个学生信息"; $ctx->state["info"] = $info; yield $ctx->render(__DIR__ . "/info.html"); });
<body> <p>{title}</p> <table border=1> <tr><td>Name</td><td>Age</td></tr> <!-- BEGIN TABLE --> <tr> <td> {name} </td> <td> {age} </td> </tr> <!-- END TABLE --> </table> </body>
<?php //此处已省略 ... //二维数组 $router->get('/table', function(Context $ctx) { $table = array( array("name" => "小明", "age" => 15), array("name" => "小花", "age" => 13), array("name" => "小刚", "age" => 17) ); $ctx->status = 200; $ctx->state["title"] = "这是一个学生名单"; $ctx->state["table"] = $table; yield $ctx->render(__DIR__ . "/table.html"); });
中间件
静态文件处理 中间件 PHPKoa Static
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>PHPkoa Static</title> <link rel="stylesheet" href="/css/default.css"> </head> <body> <img src="/images/20264902.jpg" /> </body> </html>
<?php require __DIR__ . '/vendor/autoload.php'; defined('DS') or define('DS', DIRECTORY_SEPARATOR); use Naka507\Koa\Application; use Naka507\Koa\Context; use Naka507\Koa\Error; use Naka507\Koa\Timeout; use Naka507\Koa\NotFound; use Naka507\Koa\Router; //静态文件处理 中间件 use Naka507\Koa\StaticFiles; $app = new Application(); $app->υse(new Error()); $app->υse(new Timeout(5)); $app->υse(new NotFound()); $app->υse(new StaticFiles(__DIR__ . DS . "static" )); $router = new Router(); $router->get('/index', function(Context $ctx, $next) { $ctx->status = 200; yield $ctx->render(__DIR__ . "/index.html"); }); $app->υse($router->routes()); $app->listen(3000);
使用 第三方ORM
composer require topthink/think-orm
创建 MYSQL 数据表
CREATE TABLE `too_user` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户ID', `account` varchar(64) NOT NULL DEFAULT '' COMMENT '账号', `password` char(32) NOT NULL DEFAULT '' COMMENT '登录密码', `nickname` varchar(32) NOT NULL DEFAULT '' COMMENT '用户昵称', `status` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '状态', `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
自定义数据模型
<?php namespace Models; use think\Model; class User extends Model { }
自定义 JSON响应 中间件 中间件会根据是否存在数据 响应不同的数据 并进行JSON格式化
<?php namespace Middlewares; use Naka507\Koa\Middleware; use Naka507\Koa\Context; class BodyJson implements Middleware { public function __construct(){ } public function __invoke(Context $ctx, $next){ yield $next; $pos = strpos($ctx->accept,'json'); if ( $pos !== false ) { $ctx->type = 'application/json'; $result = [ "code" => 0, "msg" => '操作失败']; $data = $ctx->body; if ( $data ) { $result['code'] = 200; $result['msg'] = '操作成功'; $result['data'] = $data; } $ctx->body = json_encode( $result ); } } }
动态模板 使用JQ 进行AJAX 请求
<body> <h1>/api/user/{id}</h1> <p id="user"></p> </body> <script type="text/javascript"> var id = {id}; $.ajax({ type: "GET", url: "/api/user/" + id, dataType: "json", success: function(res){ if( res.code == 200 ){ $("#user").html(JSON.stringify(res.data)) }else{ $("#user").html(res.msg) } } }); </script>