承接 bfitech/zapcore 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

bfitech/zapcore

Composer 安装命令:

composer require bfitech/zapcore

包简介

A very simple router and other utils.

README 文档

README

A very simple PHP router and other utilities.

Latest Stable Version Latest Unstable Version Build Status Code Coverage GitHub license

0. Reason

but why?

Yeah, why another framework?

These are a few (hopefully good enough) reasons:

  1. web service-oriented

    zapcore and in general zap* packages are geared towards HTTP RESTful APIs with very little emphasis on traditional HTML document serving. If you are building the back end of a single page web application, you'll feel immediately at home.

  2. performance

    zapcore performance is guaranteed to be much faster than any popular batteries-included monolithic frameworks, and is at least on par with other microframeworks. TODO: Benchmark result.

  3. idiosyncracy

    This is just a fancy way of saying, "Because that's the way we like it."

1. Installation

Install it from Packagist:

$ composer -vvv require bfitech/zapcore

2. Hello, World

Here's a bare-minimum index.php file:

<?php

require __DIR__ . '/vendor/autoload.php';
use BFITech\ZapCore\Router;
(new Router())->route('/', function($args){
	echo "Hello, World!";
});

Run it with PHP builtin web server and see it from your default browser:

$ php -S 0.0.0.0:9999 &
$ x-www-browser http://localhost:9999

3. Usage

3.0 Routing

Routing in zapcore is the responsibility of the method Router::route. Here's a simple route with /hello path, a regular function as the callback to handle the request data, applied to PUT request method.

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');

which will produce:

$ curl -XPUT -d Johnny localhost:9999/hello
Hello, Johnny.

We can use multiple methods for the same path:

$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']);

Instead of letting globals floating around, we can use closure and inherited variable for the callback:

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']);

Callback can be a method instead of function:

$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']);

And finally, you can subclass 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']);

When request URI and request method do not match any route, a default 404 error page will be sent unless you configure shutdown to false (see below).

$ curl -si http://localhost:9999/hello | head -n1
HTTP/1.1 404 Not Found

3.1 Dynamic Path

Apart from static path of the form /path/to/some/where, there are also two types of dynamic path built with enclosing pairs of symbols '<>' and '{}' that will capture matching strings from request URI and store them under $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']);

which will produce:

$ 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 Request Headers

All request headers are available under $args['header']. These include custom headers:

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']);

which will produce:

$ curl -H "My-Token: somerandomstring" localhost:9999/token
Your token is 'somerandomstring'.

NOTE: Custom request header keys will always be received in lower case, with all '-' changed into '_'.

3.3 Response Headers

You can send all kinds of response headers easily with the static method Header::header from the parent class:

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']);

which will produce:

$ curl -si 'localhost:9999/response?name=Johnny' | grep -i name
X-Name: Johnny

For a more proper sequence of response headers, you can use Header::start_header static method:

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']);

which will produce:

$ 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 Special Responses

There are wrappers specifically-tailored for error pages, redirect and static file serving:

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']);

which will produce:

$ 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 Advanced

Router::config is a special method to finetune the router behavior, e.g.:

$core = (new Router())
    ->config('shutdown', false)
    ->config('logger', new Logger());

Available configuration items are:

  • home and host

    Router attempts to infer your application root path from $_SERVER['SCRIPT_NAME'] which is mostly accurate when you deploy your application via Apache mod_php with mod_rewrite enabled. This most likely fails when $_SERVER['SCRIPT_NAME'] is no longer reliable, e.g. when you deploy your application under Apache Alias or Nginx location directives; or when you make it world-visible after a reverse-proxying. This is where home and host manual setup comes to the rescue.

    # 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 allows more than one Router instances in a single file. However, each instance executes a series of methods on shutdown if there is no matched route to ensure the routing doesn't end up in a blank page. In a multiple router situation, set shutdown config to false except for the last Router instance.

    $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.
  • logger

    All zap* packages use the same logging service provided by Logger class. By default, each Router instance has its own Logger instance, but you can share instance between Routers to avoid multiple log files.

    $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

See CONTRIBUTING.md.

5. Documentation

If you have Doxygen installed, detailed generated documentation is available with:

$ doxygen
$ x-www-browser docs/html/index.html

bfitech/zapcore 适用场景与选型建议

bfitech/zapcore 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 2.29k 次下载、GitHub Stars 达 0, 最近一次更新时间为 2017 年 03 月 18 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 bfitech/zapcore 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 bfitech/zapcore 我们能提供哪些服务?
定制开发 / 二次开发

基于 bfitech/zapcore 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 2.29k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 1
  • 依赖项目数: 2
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 1
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2017-03-18