Browse Source

Update README.md

master
therselman 7 years ago committed by GitHub
parent
commit
ddb23d4bea
  1. 112
      README.md

112
README.md

@ -4,46 +4,118 @@ @@ -4,46 +4,118 @@
Another definition of this code release would be;
> Twister is a set of fast and light-weight components around which a framework can be written
## Skeleton App
There is also a [skeleton application](https://github.com/twister-php/skeleton) based on this code!
At the heart of the framework, sits a very flexible, simple and elegant Inversion-of-Control (IoC) Container.
In fact, there are NO global variables, NO define's, NO pipeline, NO Kernel and NO App; just the Container.
## Container
At the heart of the framework, sits a very fast, very flexible, simple and elegant Inversion-of-Control (IoC) Container.
In fact, there are **NO global variables, NO define's, NO pipeline, NO Kernel and NO App**; just the Container.
The Container controls the entire flow of code (except routing), with a custom `execute()` function (written by you);
which is actually just an anonymous callback function inside the Container, called from the Front Controller (index.php);
even the name of that function can be changed in the config file.
ie. There is NO pre-programmed flow of the program or hard-coded Kernel/App.
Most things are handled/registered with the IoC Container, objects are pre-configured and `lazy-loaded` on request/use only!
For example:
When you require something from the config files:
```php
$c->config['my param']
```
Registered with something like this:
```php
'config' => function($c) { return $c->config = require __DIR__ . '/config.php'; }
```
Note how `$c->config` is replaced, so the array will be returned directly the next time!
Get a database connection:
```php
$db = $c->db;
```
Note that even though we are getting the `$c->db` property, internally a Closure has been registered something like this:
```php
'db' => function($c) { return $c->db = new Db($c); }
```
When `$c->db` is called the next time, the same (singleton) instance is returned.
Get the request object
```php
$request = $c->request;
```
The code above actually calls a function registered like this:
```php
'request' => function($c) { return $c->request = new Request($c); }
```
These dynamic `properties` have been pre-configured in the `controller` config file, they use \_\_get, \_\_set and \_\_call.
More properties can easily be added:
```php
$c->session = new Session();
$c->isSecure = $uri->scheme === 'https';
$c->getDummy = function($c) { return $c->dummy; };
$c->setDummy = function($c, $dummy) { $c->dummy = $dummy; return $dummy; };
$dummy = $c->getDummy();
$c->setDummy($dummy);
```
Note how the container instance is automatically prepended to the parameters of all internal functions (Closures) eg. `function($c)`
## Router
Along with the Container, comes a very flexible and fast router (inside the Request class).
I consider this router to be THE fastest router I've tested, with the same functionality.
It includes the ability to filter by method (GET/POST), and optional parameters like `/user/{id}[/{name}]`
Another somewhat unique capability is the ability to pre-define the patterns associated with named parameters eg. `id`=>`\d+`
So everytime you specify `{id}`, `{date}`, `{uuid}` etc. in the routes, the pre-configured patterns are used,
or you can specify custom patterns with `{id:[0-9]+}` or `{id:uuid}` where `uuid`=>`[A-F0-9-]+` etc.
   Two design choices make the router fast:
* Everything is configured/loaded from a `config` array (which is usually cached by APC/Xcode/PHP7)
I consider this router to be THE fastest router I've tested, with the same functionality.
It includes the ability to filter by method (GET/POST), and optional parameters like `/user/{id}[/{name}]`.
Another somewhat unique capability is the ability to pre-define the patterns associated with named parameters eg. `id`=>`\d+`
So everytime you specify `{id}`, `{date}`, `{uuid}` etc. in the routes, the pre-configured patterns are used,
or you can specify custom patterns with `{id:[0-9]+}` or `{id:uuid}` where `uuid`=>`[A-F0-9-]+` etc.
Two design choices make the router fast:
* Everything is configured/loaded from a `config` array file (which is usually cached by APC/Xcode/PHP7)
* The router splits the request uri by '/', doing an `isset` array lookup for the first path segment
* The router only takes further action after the first segment (eg. /admin/, /login etc.) is resolved
* The router only takes further action after the first segment (eg. /admin/, /login etc.) is resolved. ie. The routes are grouped by 'prefix', so all /admin/\* routes are grouped together.
Although Twister is a fully functional and useable framework (based on my personal framework),
it's more a proof-of-concept for me to demonstrate my capabilities and design decisions.
## Philosophy
My main design philosophy is: Configuration over Code/Convention; where 'configuration' isn't something you do once and leave, but it becomes part of the overall/ongoing development process. As you build the Container, you extend/enhance its capabilities/functionality by added more configurable components.
I would rather write a large array with hundreds of pre-configured properties/routes/Closures/functions etc.,
and have the benefit of a pre-cached array (PHP7 includes a built in cache, or XCode/APC),
than have the overhead of hundreds of (unecessary) `->add(...)` function calls. ie. A single array in a config file with 300 pre-configured lines of routes, is faster than 300 `->add()` route function calls, due to the function call overhead, which can become significant the larger a project gets.
I just see very little benefit to writing hundreds of `->add(route)` commands when the entire route layout of your website can be loaded once.
One argument for writing `->add(...)` calls in the Container and Router is input validation,
but I would argue that you can still do it by parsing a single pre-configured array. One large pre-configured array with default Routes and Container objects could serve as the 'base' for default options. Additional Routes/DI/IoC objects could be added/modified at run-time. Also, I ONLY use PHP array based configuration files, because they are cached natively by PHP; any other configuration files (.ini/YAML/JSON) have to be interpreted/parsed at runtime or a custom cache has to be invented.
My main design philosophy for the router and Container is: Configuration over Code/Convention; where 'configuration' isn't something you do once and leave, but it becomes part of the overall/ongoing development process. As you build the Container, you extend/enhance its capabilities/functionality by added more configurable components. The same applies to the router, its single configuration file determines all the routes, parameters, callbacks etc. All together!
I would rather write a large array with hundreds of pre-configured properties/routes/Closures/functions etc.,
and have the benefit of a pre-cached array (PHP7 includes a built in cache, or XCode/APC),
than have the overhead of hundreds of (unecessary) `->add(...)` function calls. ie. A single array in a config file with 300 pre-configured lines of routes, is faster (and easier to manage) than 300 `->add()` route function calls, due to the function call overhead, which can become significant the larger a project gets, and also because the array is much less verbose!
I just see very little benefit to writing hundreds of `->add(route)` commands when the entire route layout of your website can be loaded once, and visible/configurable in a single location.
One argument for writing `->add(...)` calls in the Container and Router is input validation,
but I would argue that you can still do it by parsing a single pre-configured array. One large pre-configured array with default Routes and Container objects could serve as the 'base' for default options. Additional Routes/DI/IoC objects could be added/modified at run-time. Also, I ONLY use PHP array based configuration files, because they are cached natively by PHP; any other configuration files (.ini/YAML/JSON) have to be interpreted/parsed at runtime or a custom cache has to be invented.
There is actually another reason I prefer this method, and that is because when all the routes are visible together, it's easier to see the overall picture, and make changes to them rapidly. I've worked on large projects with hundreds of routes before, especially with annotations, but even with function calls to add routes, it becomes a nightmare to manage. Try adding 300 different routes in 100 controllers and see how much fun you are NOT having! I swear I must have lowered my life expectancy from the nightmares.
Its a very interesting discovery, when you realise that all the code example of EVERY major framework, just show you how simple things are in just a few simple lines. But as your project expands, and grows, hundreds of new routes, components, annotations, controllers and handlers; you soon find out what a management nightmare everything becomes.
So my philosophy is simple; just KISS!
## Proof-of-concept
Although Twister is a fully functional and useable framework (based on my personal framework),
it's more a proof-of-concept for me to demonstrate a few concepts and alternative design decisions.
Also, I would consider it an honor if it became the basis of YOUR own Container.
After all, if we never re-invented the wheel, we would still be driving horse-drawn carriages; or worse!
## Benchmarks:
All tests were done with a skeleton `hello world` application on the same PC.
Laravel and Symfony were NOT configured to establish a database connection, while Twister WAS!
With a database connection, Symfony dropped to 9-12 requests per second, and Laravel 12-16 rps,
Twister is running anywhere from 50x-100x faster than Symfony and Laravel
Twister is running anywhere from 50x-100x faster than Symfony and Laravel, and uses much less memory!
```
ab -t 30 http://laravel/

Loading…
Cancel
Save