Skip to content

Commit

Permalink
Updated mu to be only 3 lines long
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremeamia committed Apr 27, 2021
1 parent 5dd72cc commit bb37bc7
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 74 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ vendor
composer.phar
composer.lock
.DS_Store
*.log
80 changes: 41 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 **µ**.
Expand All @@ -11,34 +9,34 @@ 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 "<p>Hello, world!</p>";
(new µ)
->get('/hello', function () {
echo "<p>Hello, world!</p>";
})
->run();
```

Allows you to access parameters from the URL.

```php
echo (new µ)
(new µ)
->get('/hello/(?<name>\w+)', function ($app, $params) {
return "<p>Hello, {$params['name']}!</p>";
echo "<p>Hello, {$params['name']}!</p>";
})
->run();
```

Supports all your favorite HTTP verbs.

```php
echo (new µ)
(new µ)
->delete('/user/(?<id>\d+)', $fn)
->get('/user/(?<id>\d+)', $fn)
->head('/user/(?<id>\d+)', $fn)
Expand All @@ -48,38 +46,32 @@ 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'));
$log->pushHandler($app->cfg('log.handler'));
return $log;
})
->get('/hello/(?<name>\w+)', function ($app, $params) {
$app->cfg('log')->addDebug("Said hello to {$params['name']}.");
return "<p>Hello, {$params['name']}!</p>";
$app->cfg('log')->debug("Said hello to {$params['name']}");
echo "<p>Hello, {$params['name']}!</p>";
})
->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)._
Expand All @@ -102,40 +94,50 @@ Templates are just PHP files—no mustaches and no frills.

```php
// index.php
echo (new µ)
(new µ)
->cfg('views', __DIR__ . '/templates')
->any('/hello/(?<name>\w+)', function ($app, $params) {
return $app->view('hello', [
->get('/hello/(?<name>\w+)', function ($app, $params) {
echo $app->view('hello', [
'greeting' => 'howdy',
'name' => $params['name'],
]);
})
->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

* Must have at least a Router, Container, and Templating System as features.
* 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
```
8 changes: 7 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,11 @@
"type": "project",
"autoload": {
"files": ["mu.php"]
},
"dependencies": {
"php": ">=7.0"
},
"require-dev": {
"monolog/monolog": "^2.2"
}
}
}
11 changes: 11 additions & 0 deletions examples/hello1.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

require __DIR__ . '/../vendor/autoload.php';

(new µ)
->get('/hello', function () {
echo "<p>Hello, world!</p>";
})
->run();
11 changes: 11 additions & 0 deletions examples/hello2.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

require __DIR__ . '/../vendor/autoload.php';

(new µ)
->get('/hello/(?<name>\w+)', function ($app, $params) {
echo "<p>Hello, {$params['name']}!</p>";
})
->run();
24 changes: 24 additions & 0 deletions examples/hello3.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

use Monolog\Handler\StreamHandler;
use Monolog\Logger;

require __DIR__ . '/../vendor/autoload.php';

(new µ)
->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/(?<name>\w+)', function ($app, $params) {
$app->cfg('log')->debug("Said hello to {$params['name']}");
echo "<p>Hello, {$params['name']}!</p>";
})
->run();
15 changes: 15 additions & 0 deletions examples/hello4.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

require __DIR__ . '/../vendor/autoload.php';

(new µ)
->cfg('views', __DIR__ . '/templates')
->get('/hello/(?<name>\w+)', function ($app, $params) {
echo $app->view('hello4', [
'greeting' => 'howdy',
'name' => $params['name'],
]);
})
->run();
8 changes: 8 additions & 0 deletions examples/templates/hello4.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<html>
<head>
<title>World Greeter</title>
</head>
<body>
<p><?= ucfirst($greeting) ?>, <?= $name ?>!</p>
</body>
</html>
26 changes: 26 additions & 0 deletions examples/verbs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

require __DIR__ . '/../vendor/autoload.php';

$fn = function () {
header('X-Test: Hello!', true, 200);
if ($_SERVER['REQUEST_METHOD'] !== 'HEAD') {
echo <<<HTML
<ul>
<li>Method: {$_SERVER['REQUEST_METHOD']}</li>
<li>URI: {$_SERVER['REQUEST_URI']}</li>
</ul>
HTML;
}
};

(new µ)
->delete('/user/(?<id>\d+)', $fn)
->get('/user/(?<id>\d+)', $fn)
->head('/user/(?<id>\d+)', $fn)
->patch('/user/(?<id>\d+)', $fn)
->post('/users', $fn)
->put('/user/(?<id>\d+)', $fn)
->run();
7 changes: 3 additions & 4 deletions mu.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
<?php class µ{function cfg($k,$v=null){$c=&$this->$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();}}
<?php class µ{function __call($m,$a){$c=&$this->{$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();}}
43 changes: 13 additions & 30 deletions test.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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/(?<bar>\w+)(?:/(?<baz>\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/(?<bar>\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/(?<bar>\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';
Expand All @@ -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]");

0 comments on commit bb37bc7

Please sign in to comment.