Skip to content

Instantly share code, notes, and snippets.

@BinaryKitten
Forked from EmanueleMinotto/gist:4648707
Created February 15, 2013 10:11
Show Gist options
  • Save BinaryKitten/4959511 to your computer and use it in GitHub Desktop.
Save BinaryKitten/4959511 to your computer and use it in GitHub Desktop.

Microframework

This is a PHP microframework based on anonymous functions.

Features

  • requested URLs matched using regular expressions
  • request methods (matches using regular expressions too)
  • differenced FIFO queues for each $priority
  • command line usage
  • backward compatibility
  • integrated Dependency Injection system
  • settings system

CGI Usage

Hello World

<?php
$mf = require_once('microframework.php');

$mf('/', function () {
    echo "Hello World!";
});
?>

second argument must be callable, so you can use

<?php
$mf('/', function () { /* ... */ });
$mf('/', 'function');
$mf('/', array($Object, 'method'));
$mf('/', array('Class', 'staticMethod'));
$mf('/', $Object); // Used with __invoke
?>

HTTP request methods are the third parameter and are controlled using a regular expression.

<?php
$mf('/', function () {
    echo "Hello " . $_POST['name'] . "!";
}, 'POST');
?>
<?php
$mf('/', function () {
    echo "Hello " . $_SERVER['REQUEST_METHOD'] . "!";
}, 'GET|POST');
?>

Routes are defined using a regular expression too.

<?php
$mf('/hello/world', function () {
    echo "Hello ";
});
$mf('/hello(.*)', function () {
    echo "World!";
});
// Output: Hello World!
?>

CLI Usage

Hello World

<?php
$mf = require_once('microframework.php');

// Usage: $ php test.php

$mf(function () {
    echo "Hello World!";
});
?>

Like CGI usage, first argument must be callable. Function arguments are $argv arguments

<?php
// Usage: $ php test.php foo bar

$mf(function ($a, $b) {
    echo $b . ' ' . $a;
});

// Output: bar foo
?>

Priority

Priority is used set callbacks order, each function is called using highest priority 0 but you can set it passing a not negative integer or a not negative float number.

<?php
// $mf('/', function () { echo 'A'; });
$mf('/', function () { echo 'A'; }, 'GET', 0);
$mf('/', function () { echo 'B'; }, 'GET', 1);

// Output: AB
?>
<?php
$mf('/', function () { echo 'A'; }, 'GET', 1);
$mf('/', function () { echo 'B'; }, 'GET', 0);

// Output: BA
?>

In command line usage priority is the second parameter.

To break callbacks chain use the exit() function.

Backward compatibility

<?php
// Source code on 2012-01-01
$mf('/([a-zA-Z]+)', function ($name) {
  echo "Hi " . $name;
}, 'GET|POST');
?>

If after some time this microframework will change adding the possibility to set the method as an array

<?php
// Source code of 2013-01-01 (current)
$mf('/([a-zA-Z]+)', function ($name) {
    echo "Hello " . $name;
}, array('GET', 'POST'));
?>

old things can be saved changing variable names

<?php
// Source code of 2013-01-01 (current)
$mf('/([a-zA-Z]+)', function ($name) {
    echo "Hello " . $name;
}, array('GET', 'POST'));

// Source code of 2012-01-01
$mf_old('/([a-zA-Z]+)', function ($name) {
    echo "Hi " . $name;
}, 'GET|POST');
?>

This is used for a fast replacement of old $mf functions in your codebase by replacing the variable, when you've done and started relaxing you can update your old source.

<?php
// Source code of 2013-01-01 (current)
$mf('/([a-zA-Z]+)', function ($name) {
    echo "Hello " . $name;
}, array('GET', 'POST'));

// Source code of 2012-01-01
// updated on 2013-01-02 to current version
$mf('/([a-zA-Z]+)', function ($name) {
    echo "Hi " . $name;
}, array('GET', 'POST'));
?>

Backward compatibility is not guaranteed forever, of course.

404

The 404 error is defined as a regular expression too and is matched if no one of currently defined regular expressions match current URL. To retrieve the 404 regular expression call the function without arguments (available only if not in CLI mode).

<?php
$mf('/', function () {
    echo "Hello World!";
});

$mf($mf(), function () {
    echo "Error 404: Page not Found";
});
?>

Dependency Injection

Some days ago Faryshta Mextly told me that this isn't a microframework, it's only a routing system, and he was right, so I started thinking which services should a framework have and final solution was a dependency injection system that could be used for every other service.

It works like the routing system, except for the first parameter that must not start with /. Second parameter should be an anonymous function, but can be a callable.

<?php
class Bar
{
    public $foo = 5;
}

$mf('bar', function () {
    return new Bar;
});

$mf('/', function () use ($mf) {
    var_dump($mf('bar') -> foo);
});

// Output: 5
?>

Settings

Settings work like dependency injection, but you can't pass anything callable.

<?php
$mf('pi', M_PI);

$mf('/', function () use ($mf) {
    var_dump($mf('pi'));
});

// Output: 3.1415926535898
?>

Tip

It's suggested to create the unused PHP main function.

<?php
if (!function_exists('main')) {
    function main() {
        static $mf = null;
        if (is_null($mf))
            $mf = require_once('microframework.php');
        return call_user_func_array($mf, func_get_args());
    }
}

main('/', function () {
    echo "Hello World!";
});
?>
<?php
return function () {
/**
* Used to store functions and allow recursive callbacks.
* @var null|callback
*/
static $deploy = null;
/**
* Defined matches.
* @var array
*/
static $ms = array();
/**
* Dependency Injection callbacks, used for settings too.
* @var array
*/
static $di = array();
// used to shorten code
$num_args = func_num_args();
$get_args = func_get_args();
// used to retrieve currently defined matches
// http://www.php.net/manual/en/regexp.reference.conditional.php
// http://stackoverflow.com/questions/14598972/catch-all-regular-expression
if ($num_args === 0 && PHP_SAPI !== 'cli') {
return '/(?!(' . implode('|', $ms) . ')$).*';
}
// functions used for Dependency Injection and settings
elseif ($num_args === 2 && preg_match("#^[^/].+#", $get_args[0])) {
return $di[$get_args[0]] = $get_args[1];
} elseif ($num_args === 1 && preg_match("#^[^/].+#", $get_args[0])) {
return is_callable($di[$get_args[0]])
? call_user_func($di[$get_args[0]])
: $di[$get_args[0]];
}
// functions have to be stored only once
if (is_null($deploy) && PHP_SAPI === 'cli') {
/**
* Command line interface for the main function.
*
* @link http://php.net/manual/en/language.types.float.php
*
* @param callback $callback Function invoked when script ends
* @param integer $priority Set `$callback` priority from 0 (high) to ~1.8e308 (low)
* @return void
*/
$deploy = function ($callback, $priority = 0) use (&$deploy) {
/**
* Checking well formed call
*/
if (!is_callable($callback)) {
throw new BadFunctionCallException(
'Argument 1 passed to function must be callable, '
. gettype($callback) . ' given'
);
} elseif (!is_numeric($priority)) {
throw new BadFunctionCallException(
'Argument 2 passed to function must be numeric, '
. gettype($priority) . ' given'
);
}
/**
* Arguments passed to the script.
* @link http://php.net/manual/en/reserved.variables.argv.php
* @var array
*/
$argv = $GLOBALS['argv'];
if ($priority > 0) {
/**
* Recursion is used to set callback priority
*/
register_shutdown_function($deploy, $callback, $priority - 1);
} else {
$argv[0] = $callback;
/**
* register_shutdown_function is used to call added functions when script ends
* @link http://it2.php.net/manual/en/function.register-shutdown-function.php
*/
call_user_func_array('register_shutdown_function', $argv);
}
};
} elseif (is_null($deploy)) {
/**
* Function used as a router.
*
* @link http://php.net/manual/en/language.types.float.php
*
* @param string $regex Regular expression used to match requested URL
* @param callback $callback Function invoked when there's a match
* @param string $method Request method(s)
* @param float $priority Set `$callback` priority from 0 (high) to ~1.8e308 (low)
* @return void
*/
$deploy = function ($regex, $callback, $method = 'GET', $priority = 0) use (&$deploy, &$ms) {
/**
* Checking well formed call
*/
if (!is_string($regex)) {
throw new BadFunctionCallException(
'Argument 1 passed to function must be string, '
. gettype($regex) . ' given'
);
} elseif (!is_callable($callback)) {
throw new BadFunctionCallException(
'Argument 2 passed to function must be callable, '
. gettype($callback) . ' given'
);
} elseif (!is_string($method)) {
throw new BadFunctionCallException(
'Argument 3 passed to function must be string, '
. gettype($method) . ' given'
);
} elseif (!is_numeric($priority)) {
throw new BadFunctionCallException(
'Argument 4 passed to function must be numeric, '
. gettype($priority) . ' given'
);
}
// match stored as unique
$ms[md5($regex)] = $regex;
if ($priority > 0) {
/**
* Recursion is used to set callback priority
*/
register_shutdown_function($deploy, $regex, $callback, $method, $priority - 1);
} elseif (preg_match('#' . $method . '#', $_SERVER['REQUEST_METHOD'])) {
if (preg_match('#^' . $regex . '$#', $_SERVER['REQUEST_URI'], $matches)) {
/**
* Named subpatterns aren't allowed
* @link http://it2.php.net/manual/en/regexp.reference.subpatterns.php
*/
while (list($key) = each($matches)) {
if (!is_int($key)) {
unset($matches[$key]);
}
}
/**
* Closure is added to `register_shutdown_function` calling
*/
if (isset($matches[0]) && $matches[0] === $_SERVER['REQUEST_URI']) {
$matches[0] = $callback;
} else {
array_unshift($matches, $callback);
}
/**
* register_shutdown_function is used to call added functions when script ends
* @link http://it2.php.net/manual/en/function.register-shutdown-function.php
*/
call_user_func_array('register_shutdown_function', $matches);
}
}
};
}
return call_user_func_array($deploy, func_get_args());
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment