| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
A very simple PHP router and other utilities.
Yeah, why another framework?
These are a few (hopefully good enough) reasons:
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.
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.
idiosyncracy
This is just a fancy way of saying, "Because that's the way we like it."
Install it from Packagist:
$ composer -vvv require bfitech/zapcoreHere'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:9999Routing 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 FoundApart 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'.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 '_'.
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: JohnnyFor 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 FoundThere 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.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.See CONTRIBUTING.md.
If you have Doxygen installed, detailed generated documentation is available with:
$ doxygen
$ x-www-browser docs/html/index.html| Back | FazBrowse Home | New Git URL |