Teeny is a micro-route system that is really micro, supports PHP 5.3 to PHP 8, is extremely simple and ready to use.
For create your project use:
composer create-project inphinit/teeny <project name>
Replace <project name>
by your project name, for exemple, if want create your project with "blog" name (folder name), use:
composer create-project inphinit/teeny blog
Download without composer
If is not using composer
try direct download from https://github.com/inphinit/teeny/releases
Copy release or create project in Apache or Nginx folder and configure Vhost in Apache or execute direct from folder
For use with composer-auto configure index.php
like this:
<?php
require_once 'vendor/teeny.php';
$app = new \Inphinit\Teeny;
...
return $app->exec();
For use without composer-autoload
<?php
require_once 'vendor/teeny.php';
require_once 'vendor/autoload.php';
$app = new \Inphinit\Teeny;
...
return $app->exec();
Stand-alone server
For use without Apache or Nginx you can execute this command in folder:
php -S localhost:8080 index.php
Handling Http errors (like ErrorDocument)
For handling errors for not defined routes (404 Not Found) and when try access a route with invalid (not defined) method uses $app->handlerCodes(array $codes, mixed $callback)
, example (in routes.js), example:
$app->handlerCodes([ 403, 404, 405 ], function ($code) {
echo 'Custom page error ', $code;
});
Route patterns
You can create your own patterns to use with the routes in Teeny, but there are also ready-to-use patterns:
Type | Example | Description |
---|---|---|
alnum |
$app->action('GET', '/baz/<video:alnum>', ...); |
Only accepts parameters with alpha-numeric format and $params returns array( video => ...)
|
alpha |
$app->action('GET', '/foo/bar/<name:alpha>', ...); |
Only accepts parameters with alpha format and $params returns array( name => ...)
|
decimal |
$app->action('GET', '/baz/<price:decimal>', ...); |
Only accepts parameters with decimal format and $params returns array( price => ...)
|
num |
$app->action('GET', '/foo/<id:num>', ...); |
Only accepts parameters with integer format and $params returns array( id => ...)
|
noslash |
$app->action('GET', '/foo/<noslash:noslash>', ...); |
Accpets any characters expcet slashs (/ ) |
nospace |
$app->action('GET', '/foo/<nospace:nospace>', ...); |
Accpets any characters expcet spaces, like white-spaces (%20 ), tabs (%0A ) and others (see about \S in regex) |
uuid |
$app->action('GET', '/bar/<barcode:alnum>', ...); |
Only accepts parameters with uuid format and $params returns array( barcode => ...)
|
version |
$app->action('GET', '/baz/<api:version>', ...); |
Only accepts parameters with semversion (v2) format and $params returns array( api => ...)
|
For use a pattern in routes, set like this:
$app->action('GET', '/user/<name:alnum>', function ($request, $response, $params) {
return "Hello {$params['name']}";
});
$app->action('GET', '/api/<foobar:version>', function ($request, $response, $params) {
return "Version: {$params['foobar']}";
});
$app->action('GET', '/product/<id:num>', function ($request, $response, $params) {
return "Product ID: {$params['id']}";
});
...
return $app->exec();
Top comments (11)
I am a little disappointed that you used the naive implementation of sequentially matching regular expressions for routes with parameters.
I am very grateful for your feedback. Any suggestions are welcome, if you have an idea to improve this I am willing to implement it.
Sure. Not too long ago, I was looking into route matching solutions for PHP and found nikic/FastRoute. The basic idea behind FastRoute is that you do not match a path against each pattern one by one, but instead you combine all patterns and match in one shot. By bundling the patterns, you provide more information to the regex engine, which, in theory, can lead to better performance.
Then the question becomes, how do combine the patterns? In FastRoute, a path is matched against chunks of patterns with 10 to 30 patterns per chunk. It is a good solution, but it is ultimately a workaround for the limitations of the underlying PCRE engine. This blog post explains the implementation in greater detail.
So next, I was looking for a regex engine with first class support for testing against multiple patterns. I found Hyperscan which is a "high-performance multiple regex matching library" developed by Intel. It is a C++ library with a C API, so one could write a PHP extension that exposes PHP bindings for Hyperscan.
You could of course implement your own solution with a radix tree or another algorithm instead of using a regex engine.
I understand the suggestion, seems to be interesting, at the same time, how much performance it would improve?
I feel that this solution would make the debugging complex and wouldn't meaningfully improve the performance.
Thanks for comment! Apparently you are correct, at least in the initial tests I did, using ApacheBench. Perhaps the complexity of taking advantage of a configuration was more costly than a simple sequential implementation. Tested with Apache2.4 + PHP7.4 + 16GB RAM + Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz + M.2 (SSD):
FastRoute:
ab -n 1000 -c 10 http://localhost/fastroute/articles/12345/foobar
results: Requests per second: 1473.39Teeny:
ab -n 1000 -c 10 http://localhost/teeny/articles/12345/foobar
results: Requests per second: 2506.86More requests per second is better.
The performance impact depends heavily on your application. If you only have a couple of routes, then it probably doesn't matter which router you use. I did some testing with around 300 routes. The difference between a sequentially scanning router like Symfony Routing and an batch scanning router like FastRoute is noticeable.
Here is the setup that I used for benchmarking: github.com/hbgl/php-routing-bench
I did a few tests, but from what I noticed (so far), while the average is 20 regex (routes) grouped, it really is more efficient, however when it passes that or results inverts, the grouped regex is usually twice the time compared to separate regexs. So in terms of cost benefit for now it is better to keep it as it is, because grouping will only improve the performance of things that are already technically fast (where there are few routes) and where there are many routes it will get worse (this is due to the tests I managed to do) . It doesn't mean that I may not be able to reach a more efficient result in the future, I will work for that. Anyway, one thing I noticed that can be improved is to separate the routes "without regex" from those "with regex". Grateful for the links and suggestions.
Update: dev.to/brcontainer/improving-the-r...
As of this PR, Symfony is way faster than FastRoute when using a compiled version of the UrlMatcher. As a reminder, compiling the UrlMatcher is the default and recommended way of using the Symfony Routing component, because it gives a true performance boost in both loading routes (because matcher is compiled and you don't have to recreate the entire route collection) and matching them (because the compiled UrlMatcher is optimized for runtime).
I suggest to make another benchmark with Symfony Routing instead of FastRoute, and use a compiled version of the UrlMatcher 🙂
Thanks for commenting. I will test. Two days ago I made a change to the route system that greatly increased performance github.com/inphinit/teeny/blob/mas..., using
$slice = array_slice($this->paramRoutes, $indexRoutes, $limit);
to get 20 "regex routes" and testing them at the same time (using a singlepreg_match
), all combined with the(?J)
to allow groups with the same name and then separate the "callbacks" from those routes and set group names to identify the routes to lessen the work on the "PHP side"I still promise that I will test the "Symfony Routing component". Thanks!
Thanks for pointing it out, Alex. Symfony's compiler is pretty interesting. It compiles all dynamic routes into a regex that resembles a trie.
gist.github.com/hbgl/cfa637dcd9aa3...
To be fair, FastRoute also has another dispatcher implementation that uses an almost identical algorithm.
Really nice! Or should I say cute, given the size?