diff --git a/.gitignore b/.gitignore
index 2765898..b94c97f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ vendor
composer.phar
composer.lock
.DS_Store
+*.log
diff --git a/README.md b/README.md
index 33e5f27..34b7f80 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,6 @@
# The µ PHP Microframework
-[](https://codeclimate.com/github/jeremeamia/mu)
-
-A _"real"_ microframework that fits in **just 4 lines of code**.
+A _"real"_ microframework that fits in **just 3 lines of code**.
The "microframeworks" out there weren't _micro_ enough for me, so I brushed up on
some of my code golfing skills to create **µ**.
@@ -11,16 +9,16 @@ some of my code golfing skills to create **µ**.
## Features
-These 4 LOC come jam-packed with features!
+These 3 LOC come jam-packed with features!
### Easy, regex-based routing system
Follows the well-established route-to-callable microframework pattern.
```php
-echo (new µ)
- ->get('/hello', function ($app) {
- return "
Hello, world!
";
+(new µ)
+ ->get('/hello', function () {
+ echo "Hello, world!
";
})
->run();
```
@@ -28,9 +26,9 @@ echo (new µ)
Allows you to access parameters from the URL.
```php
-echo (new µ)
+(new µ)
->get('/hello/(?\w+)', function ($app, $params) {
- return "Hello, {$params['name']}!
";
+ echo "Hello, {$params['name']}!
";
})
->run();
```
@@ -38,7 +36,7 @@ echo (new µ)
Supports all your favorite HTTP verbs.
```php
-echo (new µ)
+(new µ)
->delete('/user/(?\d+)', $fn)
->get('/user/(?\d+)', $fn)
->head('/user/(?\d+)', $fn)
@@ -48,25 +46,16 @@ echo (new µ)
->run();
```
-Supports wildcard verbs too, because sometimes you are just making a web page
-and you really don't care about esoteric HTTP practices.
+### Simple dependency/config container
```php
-echo (new µ)
- ->any('/', $fn)
- ->run();
-```
-
-### Simple, but powerful, dependency injection container
-
-```php
-use Monolog\Logger;
use Monolog\Handler\StreamHandler;
+use Monolog\Logger;
-echo (new µ)
+(new µ)
->cfg('log.channel', 'your-app')
- ->cfg('log.handler', function ($app) {
- return new StreamHandler('path/to/your.log', Logger::WARNING);
+ ->cfg('log.handler', function () {
+ return new StreamHandler('path/to/your.log', Logger::DEBUG);
})
->cfg('log', function ($app) {
$log = new Logger($app->cfg('log.channel'));
@@ -74,12 +63,15 @@ echo (new µ)
return $log;
})
->get('/hello/(?\w+)', function ($app, $params) {
- $app->cfg('log')->addDebug("Said hello to {$params['name']}.");
- return "Hello, {$params['name']}!
";
+ $app->cfg('log')->debug("Said hello to {$params['name']}");
+ echo "Hello, {$params['name']}!
";
})
->run();
```
+If a callable is provided (like with `log.handler` above), then it is treated as a factory and is only called once to
+produce a singleton value for efficient, multiple accesses.
+
### A truly _elegant_ and fluent interface
_See previous example (I'm lazy)._
@@ -102,10 +94,10 @@ Templates are just PHP files—no mustaches and no frills.
```php
// index.php
-echo (new µ)
+(new µ)
->cfg('views', __DIR__ . '/templates')
- ->any('/hello/(?\w+)', function ($app, $params) {
- return $app->view('hello', [
+ ->get('/hello/(?\w+)', function ($app, $params) {
+ echo $app->view('hello', [
'greeting' => 'howdy',
'name' => $params['name'],
]);
@@ -113,7 +105,7 @@ echo (new µ)
->run();
```
-No twigs, plates, or blades to cut you or poke you.
+No Twigs, Plates, or Blades to cut you or poke you. That might feel a little _dull_, but it's simple.
## Design constraints
@@ -121,21 +113,31 @@ No twigs, plates, or blades to cut you or poke you.
* Must attempt to incorporate usage patterns (e.g., chainable methods, closures
as controllers) that resemble other contemporary microframeworks.
* Must work with `error_reporting` set to `-1` (all errors reported).
-* Must not exceed 4 lines of code (LOC), where each line is <= 120 characters.
+* Must not exceed 3 lines of code (LOC), where each line is <= 120 characters.
* Must not have dependencies on other packages.
-* May break traditional coding conventions for the sake of brevity.
-* Must be hand-written.
+* May break traditional coding conventions/styles for the sake of brevity.
+* Must be hand-written, not minified/obfuscated by any tools.
## It works, but it's really just a joke.
Don't use this in production, or really anywhere. It's just for fun. :smile:
-If you want to use a production-quality _microframework_, try one of these:
+If you want to use a production-quality _microframework_, try [Slim](http://www.slimframework.com/).
-* [Slim](http://www.slimframework.com/)
-* [Lumen](http://lumen.laravel.com/)
-* Zend [Diactoros](https://docs.zendframework.com/zend-diactoros/) or [Expressive](https://docs.zendframework.com/zend-expressive/)
+## Examples
-## Is there a PHP 7 Version?
+The code examples in this README are also shipped as working examples in the `/examples` directory.
-Not yet. Waiting for 7.4 with shorthand closures before I attempt anything.
+To run an example, use the built-in PHP server.
+```bash
+# For the hello1 example:
+php -S localhost:8000 examples/hello1.php
+```
+Then access `http://localhost:8000` in your browser or via cURL.
+
+## Tests
+
+A very basic test suite is included, and can be run via:
+```bash
+php test.php
+```
diff --git a/composer.json b/composer.json
index caf30c2..1b09a49 100644
--- a/composer.json
+++ b/composer.json
@@ -6,5 +6,11 @@
"type": "project",
"autoload": {
"files": ["mu.php"]
+ },
+ "dependencies": {
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "monolog/monolog": "^2.2"
}
-}
\ No newline at end of file
+}
diff --git a/examples/hello1.php b/examples/hello1.php
new file mode 100644
index 0000000..1af1448
--- /dev/null
+++ b/examples/hello1.php
@@ -0,0 +1,11 @@
+get('/hello', function () {
+ echo "Hello, world!
";
+ })
+ ->run();
diff --git a/examples/hello2.php b/examples/hello2.php
new file mode 100644
index 0000000..b3fbac6
--- /dev/null
+++ b/examples/hello2.php
@@ -0,0 +1,11 @@
+get('/hello/(?\w+)', function ($app, $params) {
+ echo "Hello, {$params['name']}!
";
+ })
+ ->run();
diff --git a/examples/hello3.php b/examples/hello3.php
new file mode 100644
index 0000000..23e8b22
--- /dev/null
+++ b/examples/hello3.php
@@ -0,0 +1,24 @@
+cfg('log.channel', 'hello3')
+ ->cfg('log.handler', function () {
+ return new StreamHandler(__DIR__ . '/hello3.log', Logger::DEBUG);
+ })
+ ->cfg('log', function ($app) {
+ $log = new Logger($app->cfg('log.channel'));
+ $log->pushHandler($app->cfg('log.handler'));
+ return $log;
+ })
+ ->get('/hello/(?\w+)', function ($app, $params) {
+ $app->cfg('log')->debug("Said hello to {$params['name']}");
+ echo "Hello, {$params['name']}!
";
+ })
+ ->run();
diff --git a/examples/hello4.php b/examples/hello4.php
new file mode 100644
index 0000000..faf075e
--- /dev/null
+++ b/examples/hello4.php
@@ -0,0 +1,15 @@
+cfg('views', __DIR__ . '/templates')
+ ->get('/hello/(?\w+)', function ($app, $params) {
+ echo $app->view('hello4', [
+ 'greeting' => 'howdy',
+ 'name' => $params['name'],
+ ]);
+ })
+ ->run();
diff --git a/examples/templates/hello4.php b/examples/templates/hello4.php
new file mode 100644
index 0000000..f37854f
--- /dev/null
+++ b/examples/templates/hello4.php
@@ -0,0 +1,8 @@
+
+
+ World Greeter
+
+
+= ucfirst($greeting) ?>, = $name ?>!
+
+
diff --git a/examples/verbs.php b/examples/verbs.php
new file mode 100644
index 0000000..3e4503a
--- /dev/null
+++ b/examples/verbs.php
@@ -0,0 +1,26 @@
+
+ Method: {$_SERVER['REQUEST_METHOD']}
+ URI: {$_SERVER['REQUEST_URI']}
+
+ HTML;
+ }
+};
+
+(new µ)
+ ->delete('/user/(?\d+)', $fn)
+ ->get('/user/(?\d+)', $fn)
+ ->head('/user/(?\d+)', $fn)
+ ->patch('/user/(?\d+)', $fn)
+ ->post('/users', $fn)
+ ->put('/user/(?\d+)', $fn)
+ ->run();
diff --git a/mu.php b/mu.php
index e13991b..cd75d72 100644
--- a/mu.php
+++ b/mu.php
@@ -1,4 +1,3 @@
-$k;if($v===null)return$c=is_callable($c)?$c($this):$c;$c=$v;return
-$this;}function __call($m,$a){$this->{($m=='any'?'':$m).$a[0]}=$a[1];return$this;}function run(){foreach($this as$x=>$f)
-if(preg_match("@$x@i","$_SERVER[REQUEST_METHOD]$_SERVER[REQUEST_URI]",$p))return$f($this,$p);}function view($f,$d=[]){
-ob_start();extract($d);require"$this->views/$f.php";return ob_get_clean();}}
\ No newline at end of file
+{$m.$a[0]};$c=$a[1]??(is_callable($c)?$c($this):$c);return isset($a[1])?
+$this:$c;}function run(){foreach($this as$x=>$f)preg_match("@$x@i","$_SERVER[REQUEST_METHOD]$_SERVER[REQUEST_URI]",$p)&&
+$f($this,$p);}function view($f,$d=[]){ob_start();extract($d);require"$this->cfgviews/$f.php";return ob_get_clean();}}
diff --git a/test.php b/test.php
index 27036c3..11d359f 100644
--- a/test.php
+++ b/test.php
@@ -8,17 +8,13 @@ function ø($m,$r){$_SERVER['REQUEST_METHOD']=$m;$_SERVER['REQUEST_URI']=$r;}
Ω('Instances of µ can be created.');
$µ = new µ;
-assert('$µ instanceof µ');
+assert($µ instanceof µ);
Ω('Method calls on the µ are chainable.');
$µ´ = $µ->cfg('greeting', 'howdy');
-assert('$µ´ === $µ');
-Ω('Values can be stored in the DIC.');
-assert('$µ->greeting === "howdy"');
-
-Ω('Values can be retrieved from the DIC.');
-$ñ = $µ->cfg('greeting');
-assert('$ñ === "howdy"');
+assert($µ´ === $µ);
+Ω('Values can be stored/retrieved in the DIC.');
+assert($µ->cfg('greeting') === "howdy");
Ω('Closure values in the DIC have access to the µ and are resolved when retrieved.');
$called = 0;
@@ -27,44 +23,31 @@ function ø($m,$r){$_SERVER['REQUEST_METHOD']=$m;$_SERVER['REQUEST_URI']=$r;}
return strtoupper($app->cfg('greeting'));
});
$ñ = $µ->cfg('greeting.upper');
-assert('$called == 1') and assert('$ñ === "HOWDY"');
+assert($called == 1) and assert($ñ === "HOWDY");
Ω('Resolved closure values in the DIC are memoized.');
$ñ = $µ->cfg('greeting.upper');
-assert('$called == 1') and assert('$ñ === "HOWDY"');
+assert($called == 1) and assert($ñ === "HOWDY");
Ω('Router allows for regex expressing named params, including optional ones.');
$ç = false;
$µ = (new µ)->get('/foo/(?\w+)(?:/(?\w+))?', function ($µ, $π) use (&$ç) {
- assert('$µ instanceof µ');
- assert('$π["bar"] === "one"');
- assert('!isset($π["baz"])');
+ assert($µ instanceof µ);
+ assert($π["bar"] === "one");
+ assert(!isset($π["baz"]));
$ç = true;
});
ø('GET', '/foo/one');
$µ->run();
-assert('$ç === true');
+assert($ç === true);
-Ω('NULL is returned when the µ is run if no routes match.');
+Ω('Nothing happens when µ is run if no routes match.');
$ç = false;
$µ = (new µ)->post('/foo/(?\w+)', function () use (&$ç) {
$ç = true;
});
ø('GET', '/foo/one');
-$ñ = $µ->run();
-assert('$ç === false');
-assert('$ñ === null');
-
-Ω('The "any" method can be used to register a route that accepts any HTTP verb.');
-$ç = false;
-$µ = (new µ)->any('/foo/(?\w+)', function ($µ, $π) use (&$ç) {
- $ç = true;
- return $π['bar'];
-});
-ø('PUT', '/foo/one');
-$ñ = $µ->run();
-assert('$ç === true');
-assert('$ñ === "one"');
+assert($ç === false);
Ω('Templating (view) system replaces variables with provided values.');
$dir = sys_get_temp_dir().'/mu-view-test';
@@ -74,4 +57,4 @@ function ø($m,$r){$_SERVER['REQUEST_METHOD']=$m;$_SERVER['REQUEST_URI']=$r;}
$ñ = (new µ)->cfg('views', $dir)->view('tpl', ['foo' => 'bar']);
unlink($file);
rmdir($dir);
-assert('$ñ === "[bar]"');
+assert($ñ === "[bar]");