From bb37bc7f91c160d48f09c394fbe659d8d5b6864d Mon Sep 17 00:00:00 2001 From: Jeremy Lindblom Date: Tue, 27 Apr 2021 11:19:23 -0700 Subject: [PATCH] Updated mu to be only 3 lines long --- .gitignore | 1 + README.md | 80 ++++++++++++++++++----------------- composer.json | 8 +++- examples/hello1.php | 11 +++++ examples/hello2.php | 11 +++++ examples/hello3.php | 24 +++++++++++ examples/hello4.php | 15 +++++++ examples/templates/hello4.php | 8 ++++ examples/verbs.php | 26 ++++++++++++ mu.php | 7 ++- test.php | 43 ++++++------------- 11 files changed, 160 insertions(+), 74 deletions(-) create mode 100644 examples/hello1.php create mode 100644 examples/hello2.php create mode 100644 examples/hello3.php create mode 100644 examples/hello4.php create mode 100644 examples/templates/hello4.php create mode 100644 examples/verbs.php 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 -[![Code Climate](https://codeclimate.com/github/jeremeamia/mu/badges/gpa.svg)](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 + + +

, !

+ + 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]");