bfitech / zapcore
一个非常简单的路由器和其他实用工具。
Requires
- php: >=7.0 <7.5
- ext-curl: *
Requires (Dev)
- phpunit/phpunit: 6.*
README
一个非常简单的PHP路由器和其他实用工具。
0. 原因
是的,为什么还要另一个框架?
以下是一些(希望足够好)的原因
-
面向Web服务
zapcore
和一般的zap*
包针对HTTP RESTful API,对传统HTML文档服务重视较少。如果你正在构建单页Web应用的后端,你会感到立即自在。 -
性能
zapcore
的性能保证比任何流行的单体框架都要快得多,并且至少与其它微框架相当。 待办事项:基准测试结果。 -
个性
这只是说,"因为我们喜欢这样"的一种花哨说法。
1. 安装
从Packagist安装
$ composer -vvv require bfitech/zapcore
2. Hello, World
这是一个最简单的 index.php
文件
<?php require __DIR__ . '/vendor/autoload.php'; use BFITech\ZapCore\Router; (new Router())->route('/', function($args){ echo "Hello, World!"; });
用PHP内置的Web服务器运行它,然后用你默认的浏览器查看
$ php -S 0.0.0.0:9999 &
$ x-www-browser https://:9999
3. 使用方法
3.0 路由
在 zapcore
中,路由是 Router::route
方法的责任。这里有一个简单的路由,使用 /hello
路径,一个作为回调来处理请求数据的常规函数,应用于 PUT
请求方法。
function my_callback($args) { $name = $args['put']; file_put_contents('name.txt', $name); die(sprintf("Hello, %s.", $name)); } $core = new Router(); $core->route('/hello', 'my_callback', 'PUT');
这将产生
$ curl -XPUT -d Johnny localhost:9999/hello Hello, Johnny.
我们可以对同一个路径使用多种方法
$core = new Router(); function my_callback($args) { global $core; if ($core->get_request_method() == 'PUT') { $name = $args['put']; } else { if (!isset($args['post']['name'])) die("Who are you?"); $name = $args['post']['name']; } file_put_contents('name.txt', $name); die(sprintf("Hello, %s.", $name)); } $core->route('/hello', 'my_callback', ['PUT', 'POST']);
我们可以使用闭包和继承的变量来代替全局变量作为回调
function my_callback($args, $core) { if ($core->get_request_method() == 'PUT') { $name = $args['put']; } else { if (!isset($args['post']['name'])) die("Who are you?"); $name = $args['post']['name']; } file_put_contents('name.txt', $name); die(sprintf("Hello, %s.", $name)); } $core = new Router(); $core->route('/hello', function($args) use($core) { my_callback($args, $core); }, ['PUT', 'POST']);
回调可以是一个方法而不是函数
$core = new Router(); class MyName { public function my_callback($args) { global $core; if ($core->get_request_method() == 'PUT') { $name = $args['put']; } else { if (!isset($args['post']['name'])) die("Who are you?"); $name = $args['post']['name']; } file_put_contents('name.txt', $name); die(sprintf("Hello, %s.", $name)); } } $myname = new MyName(); $core->route('/hello', [$myname, 'my_callback'], ['PUT', 'POST']);
最后,你可以继承 Router
class MyName extends Router { public function my_callback($args) { if ($this->get_request_method() == 'PUT') { $name = $args['put']; } else { if (!isset($args['post']['name'])) die("Who are you?"); $name = $args['post']['name']; } file_put_contents('name.txt', $name); die(sprintf("Hello, %s.", $name)); } public function my_home($args) { if (!file_exists('name.txt')) die("Hello, stranger."); $name = file_get_contents('name.txt'); die(sprintf("You're home, %s.", $name)); } } $core = new MyName(); $core->route('/hello', [$core, 'my_callback'], ['PUT', 'POST']); $core->route('/', [$core, 'my_home']);
当请求URI和请求方法与任何路由不匹配时,除非你将 shutdown
配置为 false
(见下文),否则将发送默认的404错误页面。
$ curl -si https://:9999/hello | head -n1
HTTP/1.1 404 Not Found
3.1 动态路径
除了静态路径 /path/to/some/where
的形式外,还有两种类型的动态路径,它们由成对的符号 '<>'
和 '{}'
构成,将捕获匹配的字符串从请求URI中,并存储在 $args['params']
下。
class MyPath extends Router { public function my_short_param($args) { printf("Showing profile for user '%s'.\n", $args['params']['short']); } public function my_long_param($args) { printf("Showing version 1 of file '%s'.\n", $args['params']['long']); } public function my_compound_param($args) { extract($args['params']); printf("Showing revision %s of file '%s'.\n", $short, $long); } } $core = new MyPath(); // short parameter with '<>', no slash captured $core->route('/user/<short>/profile', [$core, 'my_short_param']); // long parameter with '{}', slashes captured $core->route('/file/{long}/v1', [$core, 'my_long_param']); // short and long parameters combined $core->route('/rev/{long}/v/<short>', [$core, 'my_compound_param']);
这将产生
$ curl localhost:9999/user/Johnny/profile Showing profile for user 'Johnny'. $ curl localhost:9999/file/in/the/cupboard/v1 Showing version 1 of file 'in/the/cupboard'. $ curl localhost:9999/rev/in/the/cupboard/v/3 Showing revision 3 of file 'in/the/cupboard'.
3.2 请求头
所有请求头都在 $args['header']
下可用。这些包括自定义头
class MyToken extends MyName { public function my_token($args) { if (!isset($args['header']['my_token'])) die("No token sent."); die(sprintf("Your token is '%s'.", $args['header']['my_token'])); } } $core = new MyToken(); $core->route('/token', [$core, 'my_token']);
这将产生
$ curl -H "My-Token: somerandomstring" localhost:9999/token Your token is 'somerandomstring'.
注意:自定义请求头键始终以小写接收,将所有的 '-' 更改为 '_'。
3.3 响应头
你可以使用父类的静态方法 Header::header
轻松发送各种响应头
class MyName extends Router { public function my_response($args) { if (!isset($args['get']['name'])) self::halt("Oh noe!"); self:header(sprintf("X-Name: %s", $args['get']['name'])); } } $core = new MyName(); $core->route('/response', [$core, 'my_response']);
这将产生
$ curl -si 'localhost:9999/response?name=Johnny' | grep -i name X-Name: Johnny
为了响应头的更合适顺序,你可以使用 Header::start_header
静态方法
class MyName extends Router { public function my_response($args) { if (isset($args['get']['name'])) self::start_header(200); else self::start_header(404); } } $core = new MyName(); $core->route('/response', [$core, 'my_response']);
这将产生
$ curl -si 'localhost:9999/response?name=Johnny' | head -n1 HTTP/1.1 200 OK $ curl -si localhost:9999/response | head -n1 HTTP/1.1 404 Not Found
3.4 特殊响应
有专门针对错误页面、重定向和静态文件服务的包装器
class MyFile extends Router { public function my_file($args) { if (!isset($args['get']['name'])) // show a 403 immediately return $this->abort(403); $name = $args['get']['name']; if ($name == 'John') // redirect to another query string return $this->redirect('?name=Johnny'); // a dummy file if (!file_exists('Johnny.txt')) file_put_contents('Johnny.txt', "Here's Johnny.\n"); // serve a static file, will call $this->abort(404) // internally if the file is not found $file_name = $name . '.txt'; $this->static_file($file_name); } } $core = new MyFile(); $core->route('/file', [$core, 'my_file']);
这将产生
$ curl -siL localhost:9999/file | grep HTTP HTTP/1.1 403 Forbidden $ curl -siL 'localhost:9999/file?name=Jack' | grep HTTP HTTP/1.1 404 Not Found $ curl -siL 'localhost:9999/file?name=John' | grep HTTP HTTP/1.1 301 Moved Permanently HTTP/1.1 200 OK $ curl -L 'localhost:9999/file?name=Johnny' Here's Johnny.
3.5 高级
Router::config
是一个特殊方法,用于微调路由器的行为,例如。
$core = (new Router()) ->config('shutdown', false) ->config('logger', new Logger());
可用的配置项有
-
home
和host
Router
尝试从$_SERVER['SCRIPT_NAME']
推断你的应用程序根路径,这在通过Apachemod_php
部署应用程序且启用了mod_rewrite
时通常很准确。当$_SERVER['SCRIPT_NAME']
不再可靠时,这很可能会失败,例如,当你在ApacheAlias
或 Nginxlocation
指令下部署应用程序;或者当你在反向代理之后使其对全球可见时。这就是home
和host
手动设置派上用场的地方。# your nginx configuration location @app { set $app_dir /var/www/myapp; fastcgi_pass unix:/var/run/php5-fpm.sock; fastcgi_index index.php; fastcgi_buffers 256 4k; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $app_dir/index.php; # an inaccurate setting of SCRIPT_NAME fastcgi_param SCRIPT_NAME index.php; } location /app { try_files $uri @app; }
# your index.php $core = (new Router()) ->config('home', '/app') ->config('host', 'https://example.org/app'); // No matter where you put your app in the filesystem, it should // only be world-visible via https://example.org/app.
-
shutdown
zapcore
允许在单个文件中存在多个Router
实例。然而,如果没有匹配的路由,每个实例在关闭时都会执行一系列方法,以确保路由不会导致空白页面。在多个路由的情况下,除了最后一个Router
实例外,将shutdown
配置设置为 false。$core1 = new Router(); $core1->config('shutdown', false); $core1->route('/page', ...); $core1->route('/post', ...); $core2 = new Router(); $core2->route('/post', ...); # this route will never be executed, # see above $core2->route('/usr', ...); $core2->route('/usr/profile', ...); $core2->route('/usr/login', ...); $core2->route('/usr/logout', ...); // $core2 is the one responsible to internally call abort(404) at // the end of script execution when there's no matching route found.
-
日志记录器
所有
zap*
包都使用Logger
类提供的相同日志服务。默认情况下,每个Router
实例都有自己的Logger
实例,但您可以在Router
之间共享实例以避免多个日志文件。$logger = new Logger(Logger::DEBUG, '/tmp/myapp.log'); $core1 = (new Router()) ->config('logger', $logger); $core2 = (new Router()) ->config('logger', $logger); // Both $core1 and $core2 write to the same log file /tmp/myapp.log.
4. 贡献
请参阅 CONTRIBUTING.md。
5. 文档
如果您已安装 Doxygen,详细生成的文档可通过以下方式获得:
$ doxygen $ x-www-browser docs/html/index.html