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
<?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";
});
<?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);
<?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);
<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>