I've been working on a personal from-scratch project for the purpose of learning, and so far I've been able to successfully build a DI container and a simple URI router. It works as expected right now, but from my perspective there's a lot that could potentially be improved.
So, from the tip top, my router is loosely based on Laravel's implementation, and is begun by injecting it with a hacky dependency container.
$container = new s\Container(new s\JsonFactory('../src/dependencies.json'));
$router = new Router($container);
Afterwards, I define routes in a series of formats. Namely, you can pass the URI and an "action" to a series of public functions in the Router; primarily get() and post(). As expected, each of these functions handle their own HTTP request types.
$router->get('/user/register', function() {
$view = new v\User\Register();
$view->output();
});
$router->post('/user/register', 'user/Register@register');
My container reads from a JSON file to know what dependencies to inject:
{
"classes": [
{
"id": "TestClass",
"class": "Poseidon\\TestClass",
"arguments": [
"Database"
]
}
]
}
When a class is requested from the container, the Register pulls from an associative array containing both class paths and the ids which point to each array from the JSON file.
Once the URI matches one of my routes (through a series of redundant checks) I analyze the "action" of the route:
protected function prepAction($action)
{
if (is_string($action) && preg_match('/^[\w]+(?:\/)?[\w]+\@[\w]+$/', $action)) {
// If the function is in the format 'class@method'
return explode('@', $action);
} elseif (is_array($action) || is_callable($action)) {
// If the function is a class-method array or a callable
return $action;
}
throw new \Exception("The action [$action] is invalid!");
}
At which time, the Container will resolve the dependencies of the action passed to it:
public function analyse($function)
{
$method = null;
if (is_array($function)) {
$class = $this->get($function[0]);
if ($class) {
$method = new ReflectionMethod($class, $function[1]);
}
} elseif (is_callable($function)) {
$method = new ReflectionFunction($function);
}
return $method;
}
public function resolve($function)
{
$params = $function->getParameters();
$result = [];
$indentifiable = [];
foreach ($params as $param) {
if ($param->getClass() !== null) {
$indentifiable[] = $param;
}
}
foreach ($indentifiable as $param) {
$result[$param->name] = $this->get($param->getClass()->name);
}
return $result;
}
And then I proceed to have my Router convert the Reflection to a closure:
protected function closureify($method)
{
if ($method instanceof \ReflectionFunction) {
return $method->getClosure();
} elseif ($method instanceof \ReflectionMethod) {
return $method->getClosure($this->container->get($method->class));
} elseif ($method instanceof \Closure) {
return $method;
}
throw new \Exception("Whatever [$method] is, it's not valid.");
}
And then it's simply called:
public function work()
{
// Get the route info from our routes, if it's there
$retrieved = $this->retrieve($this->currentURI);
// If it is there, go ahead and run it's action here
if (!empty($retrieved)) {
$this->run($retrieved[2], $retrieved[1]);
return;
}
throw new \Exception("No routes match the current URI. [$this->currentURI]");
}
protected function run($action, $args)
{
$method = $this->container->analyse($this->prepAction($action));
$params = $this->container->resolve($method);
$result = array_merge($params, $args);
call_user_func_array($this->closureify($method), $result);
}
That's my current way of doing things!! I need to know what I can do better and how I can do it.