forked from PHP/Router
Condense the Router to just the SegmentRouter, and format as a Composer package
This commit is contained in:
parent
9e35166eea
commit
55306b27b9
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
tests/trees
|
||||
|
||||
/vendor/
|
|
@ -1,6 +0,0 @@
|
|||
<?php
|
||||
|
||||
interface Router {
|
||||
public function add(string $method, string $route, callable $handler): Router;
|
||||
public function lookup(string $method, string $uri): array;
|
||||
}
|
254
SimpleRouter.php
254
SimpleRouter.php
|
@ -1,254 +0,0 @@
|
|||
<?php
|
||||
|
||||
class SimpleRouter
|
||||
{
|
||||
private static array $routes = [];
|
||||
private static $pathNotFound;
|
||||
private static $methodNotAllowed;
|
||||
private static string $defaultConstraint = '([\w\-]+)';
|
||||
private static string $currentPrefix = '';
|
||||
private static string $lastInsertedRoute = '';
|
||||
|
||||
/**
|
||||
* A quick static function to register a route in the router. Used by the shorthand methods as well.
|
||||
*
|
||||
* @param string $route The path to be used as the route.
|
||||
* @param callable|string $action Either a callable to be executed, or a string reference to a method.
|
||||
* @param string|array $methods The HTTP verb(s) this route accepts.
|
||||
* @return Router
|
||||
*/
|
||||
public static function add(string $route, callable|string $action, string|array $methods = 'GET')
|
||||
{
|
||||
// If a prefix exists, prepend it to the route
|
||||
if (!empty(self::$currentPrefix)) {
|
||||
$route = self::$currentPrefix.$route;
|
||||
}
|
||||
|
||||
$trimmed = self::trimRoute($route);
|
||||
|
||||
self::$routes[] = [
|
||||
'route' => $trimmed,
|
||||
'action' => $action,
|
||||
'methods' => $methods,
|
||||
'constraints' => []
|
||||
];
|
||||
|
||||
self::$lastInsertedRoute = $trimmed;
|
||||
|
||||
return new self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand function to define a GET route
|
||||
*
|
||||
* @param string $route
|
||||
* @param callable $action
|
||||
* @return Router
|
||||
*/
|
||||
public static function get(string $route, callable $action)
|
||||
{
|
||||
return self::add($route, $action, 'GET');
|
||||
}
|
||||
|
||||
/**
|
||||
* Default function to define a POST route
|
||||
*
|
||||
* @param string $route
|
||||
* @param callable $action
|
||||
* @return Router
|
||||
*/
|
||||
public static function post(string $route, callable $action)
|
||||
{
|
||||
return self::add($route, $action, 'POST');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all routes currently registered
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getAllRoutes()
|
||||
{
|
||||
return self::$routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an action to be called when a path isn't found - i.e. a 404
|
||||
*
|
||||
* @param callable $action
|
||||
* @return void
|
||||
*/
|
||||
public static function pathNotFound(callable $action)
|
||||
{
|
||||
self::$pathNotFound = $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an action to be called with a method isn't allowed on a route - i.e. a 405
|
||||
*
|
||||
* @param callable $action
|
||||
* @return void
|
||||
*/
|
||||
public static function methodNotAllowed(callable $action)
|
||||
{
|
||||
self::$methodNotAllowed = $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redefine the default constraint for route parameters. Default is '([\w\-]+)'
|
||||
*
|
||||
* @param string $constraint The RegEx you want parameters to adhere to by default. Defaults to '([\w\-]+)'
|
||||
* @return void
|
||||
*/
|
||||
public static function setDefaultConstraint(string $constraint = '([\w\-]+)')
|
||||
{
|
||||
self::$defaultConstraint = $constraint;
|
||||
}
|
||||
|
||||
private static function trimRoute(string $route): string
|
||||
{
|
||||
$route = trim(trim($route), '/');
|
||||
return "/$route";
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts a callable that defines routes, and adds a prefix to them.
|
||||
*
|
||||
* @param string $prefix The prefix you want added to the routes.
|
||||
* @param callable $routes A function that defines routes.
|
||||
* @return void
|
||||
*/
|
||||
public static function prefix(string $prefix, callable $routes)
|
||||
{
|
||||
self::$currentPrefix = $prefix;
|
||||
|
||||
$routes();
|
||||
|
||||
self::$currentPrefix = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a constraint for a route parameter. If only passing one parameter,
|
||||
* provide the parameter name as first argument and constraint as second. If
|
||||
* adding constraints for multiple parameters, pass an array of 'parameter' => 'constraint'
|
||||
* pairs.
|
||||
*
|
||||
* @param string|array $parameter
|
||||
* @param string $constraint
|
||||
* @return Router
|
||||
*/
|
||||
public static function with(string|array $parameter, string $constraint = '')
|
||||
{
|
||||
$last = self::$lastInsertedRoute;
|
||||
|
||||
if (is_array($parameter)) {
|
||||
foreach ($parameter as $param => $constraint) {
|
||||
self::$routes[$last]['constraints'][$param] = $constraint;
|
||||
}
|
||||
|
||||
return new self;
|
||||
}
|
||||
|
||||
self::$routes[$last]['constraints'][$parameter] = $constraint;
|
||||
|
||||
return new self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokenizes the given URI using our constraint rules and returns the tokenized URI
|
||||
*
|
||||
* @param string $uri
|
||||
* @return string
|
||||
*/
|
||||
private static function tokenize(string $uri, array $constraints)
|
||||
{
|
||||
$constraintKeys = array_keys($constraints);
|
||||
|
||||
preg_match_all('/(?:{([\w\-]+)})+/', $uri, $matches);
|
||||
$matches = $matches[1];
|
||||
|
||||
foreach ($matches as $match) {
|
||||
$pattern = '{'.$match.'}';
|
||||
|
||||
if (in_array($match, $constraintKeys)) {
|
||||
// Do some voodoo to allow users to use parentheses in their constraints if they want
|
||||
$constraint = '('.rtrim(ltrim(trim($constraints[$match]), '('), ')').')';
|
||||
|
||||
$uri = str_replace($pattern, $constraint, $uri);
|
||||
} else {
|
||||
$uri = str_replace($pattern, self::$defaultConstraint, $uri);
|
||||
}
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the router. Accepts a base path from which to serve the routes, and optionally whether you want to try
|
||||
* and match multiple routes.
|
||||
*
|
||||
* @param string $basePath
|
||||
* @param boolean $multimatch
|
||||
* @return void
|
||||
*/
|
||||
public static function run(string $uri, string $basePath = '', bool $multimatch = false, string $method = ''): int|array
|
||||
{
|
||||
$basePath = self::trimRoute($basePath);
|
||||
$path = urldecode(self::trimRoute($uri));
|
||||
|
||||
$pathMatchFound = false;
|
||||
$routeMatchFound = false;
|
||||
|
||||
// Begin looking through routes
|
||||
foreach (self::$routes as $route) {
|
||||
// If the basePath isn't just "root"
|
||||
if ($basePath != '/') {
|
||||
$route['route'] = self::trimRoute($basePath.$route['route']);
|
||||
}
|
||||
|
||||
// Prepare route by tokenizing.
|
||||
$tokenized = '#^'.self::tokenize($route['route'], $route['constraints']).'$#u';
|
||||
|
||||
// If the tokenized route matches the current path...
|
||||
if (preg_match($tokenized, $path, $matches)) {
|
||||
$pathMatchFound = true;
|
||||
|
||||
// Run through the route's accepted method(s)
|
||||
foreach ((array) $route['methods'] as $allowedMethod) {
|
||||
// See if the current request method matches
|
||||
if (strtolower($method) == strtolower($allowedMethod)) {
|
||||
array_shift($matches); // Remove the first match - always contains the full url
|
||||
|
||||
// If we're successful at calling the route's action, echo the result
|
||||
return [$route['action'], $matches];
|
||||
|
||||
$routeMatchFound = true;
|
||||
|
||||
// Do not check other routes.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Break the loop if the first found route is a match.
|
||||
if($routeMatchFound && !$multimatch) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// No matching route was found
|
||||
if (!$routeMatchFound) {
|
||||
// But a matching path exists
|
||||
if ($pathMatchFound) {
|
||||
return 405;
|
||||
} else {
|
||||
return 404;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function clearRoutes()
|
||||
{
|
||||
self::$routes = [];
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
<?php
|
||||
|
||||
class StaticRouter
|
||||
{
|
||||
public static function lookup(array $node, string $method, string $uri): int|array
|
||||
{
|
||||
$uriSegments = explode('/', trim($uri, '/'));
|
||||
$params = [];
|
||||
|
||||
if (isset($node[$method])) return [$node[$method], $params];
|
||||
|
||||
if (! $node2 = array_reduce($uriSegments, function ($carry, $segment) use (&$params) {
|
||||
if (isset($carry[$segment])) return $carry[$segment];
|
||||
if (isset($carry[':x'])) {
|
||||
$params[] = $segment;
|
||||
return $carry[':x'];
|
||||
}
|
||||
return null;
|
||||
}, $node)) return 404;
|
||||
|
||||
if (isset($node2[$method])) return [$node2[$method], $params];
|
||||
|
||||
return 405;
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
<?php
|
||||
|
||||
class TrieRouter implements Router
|
||||
{
|
||||
public array $root = [];
|
||||
|
||||
// Add route to the trie
|
||||
public function add(string $method, string $route, callable $handler): Router
|
||||
{
|
||||
$node = &$this->root[$method];
|
||||
// Expand the route into segments and make dynamic segments into a common placeholder
|
||||
$segments = array_map(function($segment) {
|
||||
return str_starts_with($segment, ':') ? ':x' : $segment;
|
||||
}, explode('/', trim($route, '/')));
|
||||
|
||||
foreach ($segments as $segment) {
|
||||
if (!isset($node[$segment])) {
|
||||
$node[$segment] = ['_children' => [], '_handler' => null];
|
||||
}
|
||||
$node = &$node[$segment]['_children'];
|
||||
}
|
||||
|
||||
$node['_handler'] = $handler;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Find and handle the route
|
||||
public function lookup(string $method, string $uri): array
|
||||
{
|
||||
$node = &$this->root[$method];
|
||||
$segments = explode('/', trim($uri, '/'));
|
||||
$params = [];
|
||||
|
||||
foreach ($segments as $segment) {
|
||||
if (isset($node[$segment])) {
|
||||
$node = &$node[$segment]['_children'];
|
||||
} else {
|
||||
// Handle dynamic parameters (e.g., :id)
|
||||
$dynamicSegment = $this->matchDynamicSegment($node, $segment);
|
||||
if ($dynamicSegment) {
|
||||
$params[] = $segment;
|
||||
$node = &$node[$dynamicSegment]['_children'];
|
||||
} else {
|
||||
return ['code' => 404];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a handler exists for the current node
|
||||
if (isset($node['_handler'])) return ['code' => 200, 'handler' => $node['_handler'], 'params' => $params];
|
||||
|
||||
return ['code' => 404];
|
||||
}
|
||||
|
||||
// Match dynamic route segments like ':id'
|
||||
private function matchDynamicSegment(array $node, string $segment)
|
||||
{
|
||||
foreach ($node as $key => $value) {
|
||||
if (strpos($key, ':') === 0) return $key;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function clear(): Router
|
||||
{
|
||||
$this->root = [];
|
||||
return $this;
|
||||
}
|
||||
}
|
19
composer.json
Normal file
19
composer.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "sharkk/router",
|
||||
"description": "A simple node-based router.",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Sharkk\\Router\\": "src/"
|
||||
}
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Sharkk",
|
||||
"email": "email@sharkk.net"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "stable",
|
||||
"require": {}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
|
||||
class SegmentRouter implements Router
|
||||
namespace Sharkk\Router;
|
||||
|
||||
class Router
|
||||
{
|
||||
public array $routes = [];
|
||||
|
64
tests/color.php
Normal file
64
tests/color.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
class Color {
|
||||
const RESET = "\033[0m";
|
||||
const BOLD = "\033[1m";
|
||||
const UNDERLINE = "\033[4m";
|
||||
const INVERSE = "\033[7m";
|
||||
const BLACK = "\033[30m";
|
||||
const RED = "\033[31m";
|
||||
const GREEN = "\033[32m";
|
||||
const YELLOW = "\033[33m";
|
||||
const BLUE = "\033[34m";
|
||||
const MAGENTA = "\033[35m";
|
||||
const CYAN = "\033[36m";
|
||||
const WHITE = "\033[37m";
|
||||
|
||||
private static function format(string $color, string $string): string {
|
||||
return $color . $string . self::RESET;
|
||||
}
|
||||
|
||||
public static function bold(string $string): string {
|
||||
return self::format(self::BOLD, $string);
|
||||
}
|
||||
|
||||
public static function underline(string $string): string {
|
||||
return self::format(self::UNDERLINE, $string);
|
||||
}
|
||||
|
||||
public static function inverse(string $string): string {
|
||||
return self::format(self::INVERSE, $string);
|
||||
}
|
||||
|
||||
public static function black(string $string): string {
|
||||
return self::format(self::BLACK, $string);
|
||||
}
|
||||
|
||||
public static function red(string $string): string {
|
||||
return self::format(self::RED, $string);
|
||||
}
|
||||
|
||||
public static function green(string $string): string {
|
||||
return self::format(self::GREEN, $string);
|
||||
}
|
||||
|
||||
public static function yellow(string $string): string {
|
||||
return self::format(self::YELLOW, $string);
|
||||
}
|
||||
|
||||
public static function blue(string $string): string {
|
||||
return self::format(self::BLUE, $string);
|
||||
}
|
||||
|
||||
public static function magenta(string $string): string {
|
||||
return self::format(self::MAGENTA, $string);
|
||||
}
|
||||
|
||||
public static function cyan(string $string): string {
|
||||
return self::format(self::CYAN, $string);
|
||||
}
|
||||
|
||||
public static function white(string $string): string {
|
||||
return self::format(self::WHITE, $string);
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ $midpoint = ['do', 'cause', 'effect', 'affect', 'impact', 'influence', 'change',
|
|||
for ($i = 0; $i < 1000; $i++) {
|
||||
$routes[] = makeRoute();
|
||||
// write the routes array to a file
|
||||
file_put_contents('big.txt', implode("\n", $routes));
|
||||
file_put_contents('routes/big.txt', implode("\n", $routes));
|
||||
}
|
||||
|
||||
function makeRoute(): string
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
This test file puts the SegmentRouter to the test by running a million lookups on a handful
|
||||
of routes. The routes are read from two files, blog.txt and github.txt, and added to two separate
|
||||
routers. The lookups are then run in two separate loops, one for each router.
|
||||
Each lookup is timed and the memory usage is also printed out at regular intervals.
|
||||
The requests are randomly picked from the array of routes.
|
||||
*/
|
||||
|
||||
require_once 'tools.php';
|
||||
|
||||
$r = new SegmentRouter();
|
||||
|
||||
// Blog lookups
|
||||
$blog = readAndAddRoutes('blog.txt', $r);
|
||||
writeRoutesToFile($r->routes, 'storage/segment/blog.txt');
|
||||
echoTitle("Starting blog lookups");
|
||||
runIterations(10000, $r, $blog);
|
||||
runIterations(100000, $r, $blog);
|
||||
runIterations(1000000, $r, $blog);
|
||||
unset($blog);
|
||||
|
||||
// Github lookups
|
||||
$r->clear();
|
||||
$github = readAndAddRoutes('github.txt', $r);
|
||||
writeRoutesToFile($r->routes, 'storage/segment/github.txt');
|
||||
echoTitle("Starting github lookups");
|
||||
runIterations(10000, $r, $github);
|
||||
runIterations(100000, $r, $github);
|
||||
runIterations(1000000, $r, $github);
|
||||
unset($github);
|
||||
|
||||
// Big lookups
|
||||
$r->clear();
|
||||
$big = readAndAddRoutes('big.txt', $r);
|
||||
writeRoutesToFile($r->routes, 'storage/segment/big.txt');
|
||||
echoTitle("Starting big lookups");
|
||||
runIterations(10000, $r, $big);
|
||||
runIterations(100000, $r, $big);
|
||||
runIterations(1000000, $r, $big);
|
||||
unset($big);
|
||||
|
||||
// Parameter testing
|
||||
$r->clear();
|
||||
echoTitle("Testing parameters");
|
||||
|
||||
$routes = [
|
||||
['GET', '/blog/:id', function($id) {
|
||||
echo $id."\n";
|
||||
}],
|
||||
['GET', '/blog/:id/:slug', function($id, $slug) {
|
||||
echo $id . ' - ' . $slug."\n";
|
||||
}],
|
||||
['GET', '/blog/:id/:slug/:page', function($id, $slug, $page) {
|
||||
echo $id . ' - ' . $slug . ' - ' . $page."\n";
|
||||
}],
|
||||
['GET', '/blog/:id/:slug/:page/:extra', function($id, $slug, $page, $extra) {
|
||||
echo $id . ' - ' . $slug . ' - ' . $page . ' - ' . $extra."\n";
|
||||
}],
|
||||
];
|
||||
|
||||
foreach ($routes as $route) {
|
||||
[$method, $path, $handler] = $route;
|
||||
$r->add($method, $path, $handler);
|
||||
}
|
||||
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
[$method, $uri] = $routes[array_rand($routes)];
|
||||
|
||||
// Generate some random parameters
|
||||
$uri = str_replace(':id', rand(1, 100), $uri);
|
||||
$uri = str_replace(':slug', 'slug-' . rand(1, 100), $uri);
|
||||
$uri = str_replace(':page', rand(1, 100), $uri);
|
||||
$uri = str_replace(':extra', 'extra-' . rand(1, 100), $uri);
|
||||
|
||||
$res = $r->lookup($method, $uri);
|
||||
if ($res['code'] !== 200) {
|
||||
echo "Failed to handle request for $uri - $res\n";
|
||||
exit(1);
|
||||
}
|
||||
$res['handler'](...$res['params']);
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
This test file puts the SimpleRouter to the test by running a million lookups on a handful
|
||||
of routes. The routes are read from two files, blog.txt and github.txt, and added to two separate
|
||||
routers and two separate arrays. The lookups are then run in two separate loops, one for each router.
|
||||
Each lookup is timed and the memory usage is also printed out at regular intervals.
|
||||
The requests are randomly picked from the array of routes.
|
||||
*/
|
||||
|
||||
require_once 'tools.php';
|
||||
require_once __DIR__ . '/../SimpleRouter.php';
|
||||
|
||||
// Blog test
|
||||
$blog = sReadAndAddRoutes('blog.txt');
|
||||
echoTitle("Starting blog lookups");
|
||||
sRunIterations(100000, $blog);
|
||||
sRunIterations(1000000, $blog);
|
||||
|
||||
// Github test
|
||||
SimpleRouter::clearRoutes();
|
||||
$github = sReadAndAddRoutes('github.txt');
|
||||
echoTitle("Starting github lookups");
|
||||
sRunIterations(10000, $github);
|
||||
sRunIterations(100000, $github);
|
||||
sRunIterations(1000000, $github);
|
||||
|
||||
// Big test; since simplerouter is so much slower, we'll only run the big test if the -b flag is passed
|
||||
if (in_array('-b', $argv)) {
|
||||
SimpleRouter::clearRoutes();
|
||||
$big = sReadAndAddRoutes('big.txt');
|
||||
echoTitle("Starting big lookups");
|
||||
sRunIterations(10000, $big);
|
||||
sRunIterations(100000, $big);
|
||||
sRunIterations(1000000, $big);
|
||||
}
|
||||
|
||||
function sReadAndAddRoutes(string $file): array
|
||||
{
|
||||
$array = [];
|
||||
$routes = file($file);
|
||||
foreach ($routes as $route) {
|
||||
[$method, $path] = explode(' ', $route);
|
||||
$path = trim($path);
|
||||
// convert params from :param to {param}
|
||||
$path = preg_replace('/:(\w+)/', '{$1}', $path);
|
||||
$array[] = [$method, $path];
|
||||
SimpleRouter::add($path, function() {
|
||||
return true;
|
||||
}, $method);
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
function sRunIterations(int $iterations, array $routes) {
|
||||
echo "Running $iterations iterations\n";
|
||||
$start = microtime(true);
|
||||
$interval = $iterations / 10;
|
||||
for ($i = 0; $i < $iterations; $i++) {
|
||||
// pick a random route from the array
|
||||
[$method, $uri] = $routes[array_rand($routes)];
|
||||
// replace the params with random values
|
||||
$uri = preg_replace_callback('/{(\w+)}/', function($matches) {
|
||||
return $matches[1] . '-' . rand(1, 100);
|
||||
}, $uri);
|
||||
$res = SimpleRouter::run($uri, '', false, $method);
|
||||
if ($res === 404 || $res === 405) {
|
||||
echo Color::red("Failed to handle request for $uri - $res\n");
|
||||
exit(1);
|
||||
}
|
||||
if ($i !== 0 && $i % ($interval) === 0) echoMemoryAndTime($i, $start);
|
||||
}
|
||||
echo "Time: " . Color::cyan(number_format(microtime(true) - $start, 10) . " s\n");
|
||||
// echo the average time per request
|
||||
echo "Avg/lookup: " . Color::yellow(number_format((microtime(true) - $start) / $iterations, 10) . " s\n");
|
||||
echo "\n";
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
<?php
|
||||
|
||||
require_once 'tools.php';
|
||||
require_once __DIR__ . '/../StaticRouter.php';
|
||||
|
||||
$r = new SegmentRouter();
|
||||
|
||||
// Big test
|
||||
$big = readRoutes('big.txt');
|
||||
foreach ($big as $route) {
|
||||
[$method, $path] = $route;
|
||||
$r->add($method, $path, function() {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
echoTitle("Starting big lookups");
|
||||
|
||||
$start = microtime(true);
|
||||
$reqs = 0;
|
||||
for ($i = 0; $i < 1000000; $i++) {
|
||||
$index = array_rand($big);
|
||||
[$method, $path] = $big[$index];
|
||||
$rstart = microtime(true);
|
||||
$res = StaticRouter::lookup($r->routes, $method, $path);
|
||||
if ($res === 404 || $res === 405) {
|
||||
die("404 or 405\n");
|
||||
}
|
||||
$reqs += microtime(true) - $rstart;
|
||||
}
|
||||
echo "Time: " . Color::cyan(number_format(microtime(true) - $start, 10) . " s\n");
|
||||
echo "Peak memory: " . Color::magenta(round(memory_get_peak_usage() / 1024, 1) . " kb\n");
|
||||
echo "Avg/lookup: " . Color::yellow(number_format($reqs / 1000000, 10) . " s\n");
|
||||
|
||||
function readRoutes(string $file): array
|
||||
{
|
||||
$array = [];
|
||||
$routes = file($file);
|
||||
foreach ($routes as $route) {
|
||||
[$method, $path] = explode(' ', $route);
|
||||
$path = trim($path);
|
||||
$array[] = [$method, $path];
|
||||
}
|
||||
return $array;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,9 +0,0 @@
|
|||
/
|
||||
├── GET
|
||||
├── :x
|
||||
│ └── GET
|
||||
├── tags
|
||||
│ └── GET
|
||||
└── tag
|
||||
└── :x
|
||||
└── GET
|
|
@ -1,376 +0,0 @@
|
|||
/
|
||||
├── authorizations
|
||||
│ ├── GET
|
||||
│ ├── :x
|
||||
│ │ ├── GET
|
||||
│ │ └── DELETE
|
||||
│ └── POST
|
||||
├── applications
|
||||
│ └── :x
|
||||
│ └── tokens
|
||||
│ ├── :x
|
||||
│ │ ├── GET
|
||||
│ │ └── DELETE
|
||||
│ └── DELETE
|
||||
├── events
|
||||
│ └── GET
|
||||
├── repos
|
||||
│ └── :x
|
||||
│ └── :x
|
||||
│ ├── events
|
||||
│ │ └── GET
|
||||
│ ├── notifications
|
||||
│ │ ├── GET
|
||||
│ │ └── PUT
|
||||
│ ├── stargazers
|
||||
│ │ └── GET
|
||||
│ ├── subscribers
|
||||
│ │ └── GET
|
||||
│ ├── subscription
|
||||
│ │ ├── GET
|
||||
│ │ ├── PUT
|
||||
│ │ └── DELETE
|
||||
│ ├── git
|
||||
│ │ ├── blobs
|
||||
│ │ │ ├── :x
|
||||
│ │ │ │ └── GET
|
||||
│ │ │ └── POST
|
||||
│ │ ├── commits
|
||||
│ │ │ ├── :x
|
||||
│ │ │ │ └── GET
|
||||
│ │ │ └── POST
|
||||
│ │ ├── refs
|
||||
│ │ │ ├── GET
|
||||
│ │ │ └── POST
|
||||
│ │ ├── tags
|
||||
│ │ │ ├── :x
|
||||
│ │ │ │ └── GET
|
||||
│ │ │ └── POST
|
||||
│ │ └── trees
|
||||
│ │ ├── :x
|
||||
│ │ │ └── GET
|
||||
│ │ └── POST
|
||||
│ ├── issues
|
||||
│ │ ├── GET
|
||||
│ │ ├── :x
|
||||
│ │ │ ├── GET
|
||||
│ │ │ ├── comments
|
||||
│ │ │ │ ├── GET
|
||||
│ │ │ │ └── POST
|
||||
│ │ │ ├── events
|
||||
│ │ │ │ └── GET
|
||||
│ │ │ └── labels
|
||||
│ │ │ ├── GET
|
||||
│ │ │ ├── POST
|
||||
│ │ │ ├── :x
|
||||
│ │ │ │ └── DELETE
|
||||
│ │ │ ├── PUT
|
||||
│ │ │ └── DELETE
|
||||
│ │ └── POST
|
||||
│ ├── assignees
|
||||
│ │ ├── GET
|
||||
│ │ └── :x
|
||||
│ │ └── GET
|
||||
│ ├── labels
|
||||
│ │ ├── GET
|
||||
│ │ ├── :x
|
||||
│ │ │ ├── GET
|
||||
│ │ │ └── DELETE
|
||||
│ │ └── POST
|
||||
│ ├── milestones
|
||||
│ │ ├── :x
|
||||
│ │ │ ├── labels
|
||||
│ │ │ │ └── GET
|
||||
│ │ │ ├── GET
|
||||
│ │ │ └── DELETE
|
||||
│ │ ├── GET
|
||||
│ │ └── POST
|
||||
│ ├── pulls
|
||||
│ │ ├── GET
|
||||
│ │ ├── :x
|
||||
│ │ │ ├── GET
|
||||
│ │ │ ├── commits
|
||||
│ │ │ │ └── GET
|
||||
│ │ │ ├── files
|
||||
│ │ │ │ └── GET
|
||||
│ │ │ ├── merge
|
||||
│ │ │ │ ├── GET
|
||||
│ │ │ │ └── PUT
|
||||
│ │ │ └── comments
|
||||
│ │ │ ├── GET
|
||||
│ │ │ └── PUT
|
||||
│ │ └── POST
|
||||
│ ├── GET
|
||||
│ ├── contributors
|
||||
│ │ └── GET
|
||||
│ ├── languages
|
||||
│ │ └── GET
|
||||
│ ├── teams
|
||||
│ │ └── GET
|
||||
│ ├── tags
|
||||
│ │ └── GET
|
||||
│ ├── branches
|
||||
│ │ ├── GET
|
||||
│ │ └── :x
|
||||
│ │ └── GET
|
||||
│ ├── DELETE
|
||||
│ ├── collaborators
|
||||
│ │ ├── GET
|
||||
│ │ └── :x
|
||||
│ │ ├── GET
|
||||
│ │ ├── PUT
|
||||
│ │ └── DELETE
|
||||
│ ├── comments
|
||||
│ │ ├── GET
|
||||
│ │ └── :x
|
||||
│ │ ├── GET
|
||||
│ │ └── DELETE
|
||||
│ ├── commits
|
||||
│ │ ├── :x
|
||||
│ │ │ ├── comments
|
||||
│ │ │ │ ├── GET
|
||||
│ │ │ │ └── POST
|
||||
│ │ │ └── GET
|
||||
│ │ └── GET
|
||||
│ ├── readme
|
||||
│ │ └── GET
|
||||
│ ├── keys
|
||||
│ │ ├── GET
|
||||
│ │ ├── :x
|
||||
│ │ │ ├── GET
|
||||
│ │ │ └── DELETE
|
||||
│ │ └── POST
|
||||
│ ├── downloads
|
||||
│ │ ├── GET
|
||||
│ │ └── :x
|
||||
│ │ ├── GET
|
||||
│ │ └── DELETE
|
||||
│ ├── forks
|
||||
│ │ ├── GET
|
||||
│ │ └── POST
|
||||
│ ├── hooks
|
||||
│ │ ├── GET
|
||||
│ │ ├── :x
|
||||
│ │ │ ├── GET
|
||||
│ │ │ ├── tests
|
||||
│ │ │ │ └── POST
|
||||
│ │ │ └── DELETE
|
||||
│ │ └── POST
|
||||
│ ├── merges
|
||||
│ │ └── POST
|
||||
│ ├── releases
|
||||
│ │ ├── GET
|
||||
│ │ ├── :x
|
||||
│ │ │ ├── GET
|
||||
│ │ │ ├── DELETE
|
||||
│ │ │ └── assets
|
||||
│ │ │ └── GET
|
||||
│ │ └── POST
|
||||
│ ├── stats
|
||||
│ │ ├── contributors
|
||||
│ │ │ └── GET
|
||||
│ │ ├── commit_activity
|
||||
│ │ │ └── GET
|
||||
│ │ ├── code_frequency
|
||||
│ │ │ └── GET
|
||||
│ │ ├── participation
|
||||
│ │ │ └── GET
|
||||
│ │ └── punch_card
|
||||
│ │ └── GET
|
||||
│ └── statuses
|
||||
│ └── :x
|
||||
│ ├── GET
|
||||
│ └── POST
|
||||
├── networks
|
||||
│ └── :x
|
||||
│ └── :x
|
||||
│ └── events
|
||||
│ └── GET
|
||||
├── orgs
|
||||
│ └── :x
|
||||
│ ├── events
|
||||
│ │ └── GET
|
||||
│ ├── issues
|
||||
│ │ └── GET
|
||||
│ ├── GET
|
||||
│ ├── members
|
||||
│ │ ├── GET
|
||||
│ │ └── :x
|
||||
│ │ ├── GET
|
||||
│ │ └── DELETE
|
||||
│ ├── public_members
|
||||
│ │ ├── GET
|
||||
│ │ └── :x
|
||||
│ │ ├── GET
|
||||
│ │ ├── PUT
|
||||
│ │ └── DELETE
|
||||
│ ├── teams
|
||||
│ │ ├── GET
|
||||
│ │ └── POST
|
||||
│ └── repos
|
||||
│ ├── GET
|
||||
│ └── POST
|
||||
├── users
|
||||
│ ├── :x
|
||||
│ │ ├── received_events
|
||||
│ │ │ ├── GET
|
||||
│ │ │ └── public
|
||||
│ │ │ └── GET
|
||||
│ │ ├── events
|
||||
│ │ │ ├── GET
|
||||
│ │ │ ├── public
|
||||
│ │ │ │ └── GET
|
||||
│ │ │ └── orgs
|
||||
│ │ │ └── :x
|
||||
│ │ │ └── GET
|
||||
│ │ ├── starred
|
||||
│ │ │ └── GET
|
||||
│ │ ├── subscriptions
|
||||
│ │ │ └── GET
|
||||
│ │ ├── gists
|
||||
│ │ │ └── GET
|
||||
│ │ ├── orgs
|
||||
│ │ │ └── GET
|
||||
│ │ ├── repos
|
||||
│ │ │ └── GET
|
||||
│ │ ├── GET
|
||||
│ │ ├── followers
|
||||
│ │ │ └── GET
|
||||
│ │ ├── following
|
||||
│ │ │ ├── GET
|
||||
│ │ │ └── :x
|
||||
│ │ │ └── GET
|
||||
│ │ └── keys
|
||||
│ │ └── GET
|
||||
│ └── GET
|
||||
├── feeds
|
||||
│ └── GET
|
||||
├── notifications
|
||||
│ ├── GET
|
||||
│ ├── PUT
|
||||
│ └── threads
|
||||
│ └── :x
|
||||
│ ├── GET
|
||||
│ └── subscription
|
||||
│ ├── GET
|
||||
│ ├── PUT
|
||||
│ └── DELETE
|
||||
├── user
|
||||
│ ├── starred
|
||||
│ │ ├── GET
|
||||
│ │ └── :x
|
||||
│ │ └── :x
|
||||
│ │ ├── GET
|
||||
│ │ ├── PUT
|
||||
│ │ └── DELETE
|
||||
│ ├── subscriptions
|
||||
│ │ ├── GET
|
||||
│ │ └── :x
|
||||
│ │ └── :x
|
||||
│ │ ├── GET
|
||||
│ │ ├── PUT
|
||||
│ │ └── DELETE
|
||||
│ ├── issues
|
||||
│ │ └── GET
|
||||
│ ├── orgs
|
||||
│ │ └── GET
|
||||
│ ├── teams
|
||||
│ │ └── GET
|
||||
│ ├── repos
|
||||
│ │ ├── GET
|
||||
│ │ └── POST
|
||||
│ ├── GET
|
||||
│ ├── emails
|
||||
│ │ ├── GET
|
||||
│ │ ├── POST
|
||||
│ │ └── DELETE
|
||||
│ ├── followers
|
||||
│ │ └── GET
|
||||
│ ├── following
|
||||
│ │ ├── GET
|
||||
│ │ └── :x
|
||||
│ │ ├── GET
|
||||
│ │ ├── PUT
|
||||
│ │ └── DELETE
|
||||
│ └── keys
|
||||
│ ├── GET
|
||||
│ ├── :x
|
||||
│ │ ├── GET
|
||||
│ │ └── DELETE
|
||||
│ └── POST
|
||||
├── gists
|
||||
│ ├── GET
|
||||
│ ├── :x
|
||||
│ │ ├── GET
|
||||
│ │ ├── star
|
||||
│ │ │ ├── PUT
|
||||
│ │ │ ├── DELETE
|
||||
│ │ │ └── GET
|
||||
│ │ ├── forks
|
||||
│ │ │ └── POST
|
||||
│ │ └── DELETE
|
||||
│ └── POST
|
||||
├── issues
|
||||
│ └── GET
|
||||
├── emojis
|
||||
│ └── GET
|
||||
├── gitignore
|
||||
│ └── templates
|
||||
│ ├── GET
|
||||
│ └── :x
|
||||
│ └── GET
|
||||
├── markdown
|
||||
│ ├── POST
|
||||
│ └── raw
|
||||
│ └── POST
|
||||
├── meta
|
||||
│ └── GET
|
||||
├── rate_limit
|
||||
│ └── GET
|
||||
├── teams
|
||||
│ └── :x
|
||||
│ ├── GET
|
||||
│ ├── DELETE
|
||||
│ ├── members
|
||||
│ │ ├── GET
|
||||
│ │ └── :x
|
||||
│ │ ├── GET
|
||||
│ │ ├── PUT
|
||||
│ │ └── DELETE
|
||||
│ └── repos
|
||||
│ ├── GET
|
||||
│ └── :x
|
||||
│ └── :x
|
||||
│ ├── GET
|
||||
│ ├── PUT
|
||||
│ └── DELETE
|
||||
├── repositories
|
||||
│ └── GET
|
||||
├── search
|
||||
│ ├── repositories
|
||||
│ │ └── GET
|
||||
│ ├── code
|
||||
│ │ └── GET
|
||||
│ ├── issues
|
||||
│ │ └── GET
|
||||
│ └── users
|
||||
│ └── GET
|
||||
└── legacy
|
||||
├── issues
|
||||
│ └── search
|
||||
│ └── :x
|
||||
│ └── :x
|
||||
│ └── :x
|
||||
│ └── :x
|
||||
│ └── GET
|
||||
├── repos
|
||||
│ └── search
|
||||
│ └── :x
|
||||
│ └── GET
|
||||
└── user
|
||||
├── search
|
||||
│ └── :x
|
||||
│ └── GET
|
||||
└── email
|
||||
└── :x
|
||||
└── GET
|
File diff suppressed because it is too large
Load Diff
|
@ -1,21 +0,0 @@
|
|||
/
|
||||
└── GET
|
||||
├── /
|
||||
│ ├── _children
|
||||
│ │ └── _handler
|
||||
│ └── _handler
|
||||
├── :x
|
||||
│ ├── _children
|
||||
│ │ └── _handler
|
||||
│ └── _handler
|
||||
├── tags
|
||||
│ ├── _children
|
||||
│ │ └── _handler
|
||||
│ └── _handler
|
||||
└── tag
|
||||
├── _children
|
||||
│ └── :x
|
||||
│ ├── _children
|
||||
│ │ └── _handler
|
||||
│ └── _handler
|
||||
└── _handler
|
File diff suppressed because it is too large
Load Diff
|
@ -1,84 +1,86 @@
|
|||
<?php
|
||||
|
||||
require_once 'color.php';
|
||||
require_once __DIR__ . '/../src/Router.php';
|
||||
|
||||
use Sharkk\Router\Router;
|
||||
|
||||
// if there's a flag, reset the opcache
|
||||
if (in_array('-f', $argv)) {
|
||||
opcache_reset();
|
||||
echoTitle("opcache reset");
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../Router.php';
|
||||
require_once __DIR__ . '/../SegmentRouter.php';
|
||||
require_once __DIR__ . '/../TrieRouter.php';
|
||||
$r = new Router();
|
||||
|
||||
// A simple class to return a string wrapped in ANSI color codes for terminal output
|
||||
class Color {
|
||||
const RESET = "\033[0m";
|
||||
const BOLD = "\033[1m";
|
||||
const UNDERLINE = "\033[4m";
|
||||
const INVERSE = "\033[7m";
|
||||
const BLACK = "\033[30m";
|
||||
const RED = "\033[31m";
|
||||
const GREEN = "\033[32m";
|
||||
const YELLOW = "\033[33m";
|
||||
const BLUE = "\033[34m";
|
||||
const MAGENTA = "\033[35m";
|
||||
const CYAN = "\033[36m";
|
||||
const WHITE = "\033[37m";
|
||||
// Blog lookups
|
||||
$blog = readAndAddRoutes('routes/blog.txt', $r);
|
||||
writeRoutesToFile($r->routes, 'trees/blog.txt');
|
||||
echoTitle("Starting blog lookups");
|
||||
runIterations(10000, $r, $blog);
|
||||
runIterations(100000, $r, $blog);
|
||||
runIterations(1000000, $r, $blog);
|
||||
unset($blog);
|
||||
|
||||
private static function format(string $color, string $string): string {
|
||||
return $color . $string . self::RESET;
|
||||
}
|
||||
// Github lookups
|
||||
$r->clear();
|
||||
$github = readAndAddRoutes('routes/github.txt', $r);
|
||||
writeRoutesToFile($r->routes, 'trees/github.txt');
|
||||
echoTitle("Starting github lookups");
|
||||
runIterations(10000, $r, $github);
|
||||
runIterations(100000, $r, $github);
|
||||
runIterations(1000000, $r, $github);
|
||||
unset($github);
|
||||
|
||||
public static function bold(string $string): string {
|
||||
return self::format(self::BOLD, $string);
|
||||
}
|
||||
// Big lookups
|
||||
$r->clear();
|
||||
$big = readAndAddRoutes('routes/big.txt', $r);
|
||||
writeRoutesToFile($r->routes, 'trees/big.txt');
|
||||
echoTitle("Starting big lookups");
|
||||
runIterations(10000, $r, $big);
|
||||
runIterations(100000, $r, $big);
|
||||
runIterations(1000000, $r, $big);
|
||||
unset($big);
|
||||
|
||||
public static function underline(string $string): string {
|
||||
return self::format(self::UNDERLINE, $string);
|
||||
}
|
||||
// Parameter testing
|
||||
$r->clear();
|
||||
echoTitle("Testing parameters");
|
||||
|
||||
public static function inverse(string $string): string {
|
||||
return self::format(self::INVERSE, $string);
|
||||
}
|
||||
$routes = [
|
||||
['GET', '/blog/:id', function($id) {
|
||||
echo $id."\n";
|
||||
}],
|
||||
['GET', '/blog/:id/:slug', function($id, $slug) {
|
||||
echo $id . ' - ' . $slug."\n";
|
||||
}],
|
||||
['GET', '/blog/:id/:slug/:page', function($id, $slug, $page) {
|
||||
echo $id . ' - ' . $slug . ' - ' . $page."\n";
|
||||
}],
|
||||
['GET', '/blog/:id/:slug/:page/:extra', function($id, $slug, $page, $extra) {
|
||||
echo $id . ' - ' . $slug . ' - ' . $page . ' - ' . $extra."\n";
|
||||
}],
|
||||
];
|
||||
|
||||
public static function black(string $string): string {
|
||||
return self::format(self::BLACK, $string);
|
||||
}
|
||||
|
||||
public static function red(string $string): string {
|
||||
return self::format(self::RED, $string);
|
||||
}
|
||||
|
||||
public static function green(string $string): string {
|
||||
return self::format(self::GREEN, $string);
|
||||
}
|
||||
|
||||
public static function yellow(string $string): string {
|
||||
return self::format(self::YELLOW, $string);
|
||||
}
|
||||
|
||||
public static function blue(string $string): string {
|
||||
return self::format(self::BLUE, $string);
|
||||
}
|
||||
|
||||
public static function magenta(string $string): string {
|
||||
return self::format(self::MAGENTA, $string);
|
||||
}
|
||||
|
||||
public static function cyan(string $string): string {
|
||||
return self::format(self::CYAN, $string);
|
||||
}
|
||||
|
||||
public static function white(string $string): string {
|
||||
return self::format(self::WHITE, $string);
|
||||
}
|
||||
foreach ($routes as $route) {
|
||||
[$method, $path, $handler] = $route;
|
||||
$r->add($method, $path, $handler);
|
||||
}
|
||||
|
||||
function echoMemoryAndTime(int $i, float $start) {
|
||||
echo "(".Color::green("$i lookups").") M: " .
|
||||
Color::blue(round(memory_get_usage() / 1024, 1) . " kb") .
|
||||
" - T: ". Color::blue(number_format(microtime(true) - $start, 10) ." s") .
|
||||
"\n";
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
[$method, $uri] = $routes[array_rand($routes)];
|
||||
|
||||
// Generate some random parameters
|
||||
$uri = str_replace(':id', rand(1, 100), $uri);
|
||||
$uri = str_replace(':slug', 'slug-' . rand(1, 100), $uri);
|
||||
$uri = str_replace(':page', rand(1, 100), $uri);
|
||||
$uri = str_replace(':extra', 'extra-' . rand(1, 100), $uri);
|
||||
|
||||
$res = $r->lookup($method, $uri);
|
||||
if ($res['code'] !== 200) {
|
||||
echo "Failed to handle request for $uri - $res\n";
|
||||
exit(1);
|
||||
}
|
||||
$res['handler'](...$res['params']);
|
||||
}
|
||||
|
||||
function echoTitle(string $title) {
|
||||
|
@ -207,4 +209,3 @@ function writeNode($node, $indent, $prefix, $fp) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
This test file puts the PATRICIA trie to the test by running a million lookups on a handful
|
||||
of routes. The routes are read from two files, blog.txt and github.txt, and added to two separate
|
||||
routers and two separate arrays. The lookups are then run in two separate loops, one for each router.
|
||||
Each lookup is timed and the memory usage is also printed out at regular intervals.
|
||||
The requests are randomly picked from the array of routes.
|
||||
*/
|
||||
|
||||
require_once 'tools.php';
|
||||
|
||||
$r = new TrieRouter();
|
||||
|
||||
// Blog test
|
||||
$blog = readAndAddRoutes('blog.txt', $r);
|
||||
writeRoutesToFile($r->root, 'storage/trie/blog.txt');
|
||||
echoTitle("Starting blog lookups");
|
||||
runIterations(100000, $r, $blog);
|
||||
runIterations(1000000, $r, $blog);
|
||||
|
||||
// Github test
|
||||
$r->clear();
|
||||
$github = readAndAddRoutes('github.txt', $r);
|
||||
writeRoutesToFile($r->root, 'storage/trie/github.txt');
|
||||
echoTitle("Starting github lookups");
|
||||
runIterations(10000, $r, $github);
|
||||
runIterations(100000, $r, $github);
|
||||
runIterations(1000000, $r, $github);
|
||||
|
||||
// Big test
|
||||
$r->clear();
|
||||
$big = readAndAddRoutes('big.txt', $r);
|
||||
writeRoutesToFile($r->root, 'storage/trie/big.txt');
|
||||
echoTitle("Starting big lookups");
|
||||
runIterations(10000, $r, $big);
|
||||
runIterations(100000, $r, $big);
|
||||
runIterations(1000000, $r, $big);
|
Loading…
Reference in New Issue
Block a user