Originally, my Slim Framework app had the classic structure
(index.php)
<?php
$app = new \Slim\Slim();
$app->get('/hello/:name', function ($name) {
echo "Hello, $name";
});
$app->run();
But as I added more routes and groups of routes, I moved to a controller based approach:
index.php
<?php
$app = new \Slim\Slim();
$app->get('/hello/:name', 'HelloController::hello');
$app->run();
HelloController.php
<?php
class HelloController {
public static function hello($name) {
echo "Hello, $name";
}
}
This works, and it had been helpful to organize my app structure, while at the same time lets me build unit tests for each controler method.
However, I'm not sure this is the right way. I feel like I'm mocking Silex's mount
method on a sui generis basis, and that can't be good. Using the $app context inside each Controller method requires me to use \Slim\Slim::getInstance(), which seems less efficient than just using $app like a closure can.
So... is there a solution allowing for both efficiency and order, or does efficiency come at the cost of route/closure nightmare?
I guess I can share what I did with you guys. I noticed that every route method in Slim\Slim at some point called the method mapRoute:
Slim.php
protected function mapRoute($args)
{
$pattern = array_shift($args);
$callable = array_pop($args);
$route = new \Slim\Route($pattern, $callable, $this->settings['routes.case_sensitive']);
$this->router->map($route);
if (count($args) > 0) {
$route->setMiddleware($args);
}
return $route;
}
In turn, the Slim\Route constructor called setCallable
Route.php
public function setCallable($callable)
{
$matches = [];
if (is_string($callable) && preg_match('!^([^\:]+)\:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!', $callable, $matches)) {
$class = $matches[1];
$method = $matches[2];
//\Util\Helpers::prdie('Matches are', $matches);
$callable = function () use ($class, $method) {
static $obj = null;
if ($obj === null) {
$obj = new $class;
}
return call_user_func_array([$obj, $method], func_get_args());
};
}
if (!is_callable($callable)) {
throw new \InvalidArgumentException('Route callable must be callable');
}
$this->callable = $callable;
}
And this means that routes declared with a callable in the form Controller:method (note the single colon) were interpreted as non-static methods and therefore where instanced in the callable closure. So I extended Slim\Slim and Slim\Route. In the first one I overrode mapRoute
\Util\MySlim
protected function mapRoute($args)
{
$pattern = array_shift($args);
$callable = array_pop($args);
$route = new \Util\MyRoute($this, $pattern, $callable, $this->settings['routes.case_sensitive']);
$this->router->map($route);
if (count($args) > 0) {
$route->setMiddleware($args);
}
return $route;
}
You see, when instancing \Util\MyRoute I pass the $app too, because in MyRoute there's such a protected property and the getCallable method uses it to instance the controller, thus allowing for non static methods to be used having the $app property already in them.
\Util\MyRoute.php
public function setCallable($callable)
{
$matches = [];
$app = $this->app;
if (is_string($callable) && preg_match('!^([^\:]+)\:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!', $callable, $matches)) {
$class = $matches[1];
$method = $matches[2];
$callable = function () use ($app, $class, $method) {
static $obj = null;
if ($obj === null) {
$obj = new $class($app);
}
return call_user_func_array([$obj, $method], func_get_args());
};
}
if (!is_callable($callable)) {
throw new \InvalidArgumentException('Route callable must be callable');
}
$this->callable = $callable;
}
So there it is. Using this two classes I can have $app injected into whatever Controller I declare on the route, as long as I use a single colon to separate controller from method. Using paamayim nekudotayim will call the method as static and therefore will throw an error if I try to access $this->app inside it.
I ran tests using blackfire.io and... the performance gain is negligible.
Pros:
- this saves me the pain of calling $app = \Slim\Slim::getInstance() on every static method call accounting for about 100 lines of text overall.
- it opens the way for further optimization by making every controller inherit from an abstract controller class, which in turn wraps the app methods into convenience methods.
- it made me understand Slim's request and response lifecycle a little better.
Cons:
- performance gains are negligible
- you have to convert all your routes to use a single colon instead of paamayin, and all your controller methods from static to dynamic.
- inheritance from Slim base classes might break when they roll out v 3.0.0
来源:https://stackoverflow.com/questions/28776707/slim-framework-routes-and-controllers