Skip to content

Commit

Permalink
Merge branch 'lupo-lupov-ConfigurableFastRouteCaching' into 3.x
Browse files Browse the repository at this point in the history
Closes #1749
  • Loading branch information
akrabat committed May 8, 2016
2 parents 85e0153 + 0f57290 commit 0141e08
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 10 deletions.
3 changes: 2 additions & 1 deletion Slim/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class Container extends PimpleContainer implements ContainerInterface
'determineRouteBeforeAppMiddleware' => false,
'displayErrorDetails' => false,
'addContentLengthHeader' => true,
'routerCacheFile' => false,
];

/**
Expand Down Expand Up @@ -96,7 +97,7 @@ private function registerDefaultServices($userSettings)
$this['settings'] = function () use ($userSettings, $defaultSettings) {
return new Collection(array_merge($defaultSettings, $userSettings));
};

$defaultProvider = new DefaultServicesProvider();
$defaultProvider->register($this);
}
Expand Down
11 changes: 9 additions & 2 deletions Slim/DefaultServicesProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,17 @@ public function register($container)
* This service MUST return a SHARED instance
* of \Slim\Interfaces\RouterInterface.
*
* @param Container $container
*
* @return RouterInterface
*/
$container['router'] = function () {
return new Router;
$container['router'] = function ($container) {
$routerCacheFile = false;
if (isset($container->get('settings')['routerCacheFile'])) {
$routerCacheFile = $container->get('settings')['routerCacheFile'];
}

return (new Router)->setCacheFile($routerCacheFile);
};
}

Expand Down
55 changes: 50 additions & 5 deletions Slim/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ class Router implements RouterInterface
*/
protected $basePath = '';

/**
* Path to fast route cache file. Set to false to disable route caching
*
* @var string|False
*/
protected $cacheFile = false;

/**
* Routes
*
Expand Down Expand Up @@ -97,6 +104,29 @@ public function setBasePath($basePath)
return $this;
}

/**
* Set path to fast route cache file. If this is false then route caching is disabled.
*
* @param string|false $cacheFile
*
* @return self
*/
public function setCacheFile($cacheFile)
{
if (!is_string($cacheFile) && $cacheFile !== false) {
throw new InvalidArgumentException('Router cacheFile must be a string or false');
}

$this->cacheFile = $cacheFile;

if ($cacheFile !== false && !is_writable(dirname($cacheFile))) {
throw new RuntimeException('Router cacheFile directory must be writable');
}


return $this;
}

/**
* Add route
*
Expand Down Expand Up @@ -142,7 +172,7 @@ public function map($methods, $pattern, $handler)
public function dispatch(ServerRequestInterface $request)
{
$uri = '/' . ltrim($request->getUri()->getPath(), '/');

return $this->createDispatcher()->dispatch(
$request->getMethod(),
$uri
Expand All @@ -154,13 +184,28 @@ public function dispatch(ServerRequestInterface $request)
*/
protected function createDispatcher()
{
return $this->dispatcher ?: \FastRoute\simpleDispatcher(function (RouteCollector $r) {
if ($this->dispatcher) {
return $this->dispatcher;
}

$routeDefinitionCallback = function (RouteCollector $r) {
foreach ($this->getRoutes() as $route) {
$r->addRoute($route->getMethods(), $route->getPattern(), $route->getIdentifier());
}
}, [
'routeParser' => $this->routeParser
]);
};

if ($this->cacheFile) {
$this->dispatcher = \FastRoute\cachedDispatcher($routeDefinitionCallback, [
'routeParser' => $this->routeParser,
'cacheFile' => $this->cacheFile,
]);
} else {
$this->dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback, [
'routeParser' => $this->routeParser,
]);
}

return $this->dispatcher;
}

/**
Expand Down
5 changes: 5 additions & 0 deletions tests/ContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,9 @@ public function testMagicGetMethod()
$this->container->get('settings')['httpVersion'] = '1.2';
$this->assertSame('1.2', $this->container->__get('settings')['httpVersion']);
}

public function testRouteCacheDisabledByDefault()
{
$this->assertFalse($this->container->get('settings')['routerCacheFile']);
}
}
98 changes: 96 additions & 2 deletions tests/RouterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public function testRelativePathFor()
$this->router->relativePathFor('foo', ['first' => 'josh', 'last' => 'lockhart'])
);
}

public function testPathForWithNoBasePath()
{
$this->router->setBasePath('');
Expand All @@ -104,7 +104,7 @@ public function testPathForWithNoBasePath()
$this->router->pathFor('foo', ['first' => 'josh', 'last' => 'lockhart'])
);
}

public function testPathForWithBasePath()
{
$methods = ['GET'];
Expand Down Expand Up @@ -306,4 +306,98 @@ public function testPathForWithModifiedRoutePattern()
$this->router->relativePathFor('foo', ['voornaam' => 'josh', 'achternaam' => 'lockhart'])
);
}

/**
* Test cacheFile may be set to false
*/
public function testSettingCacheFileToFalse()
{
$this->router->setCacheFile(false);

$class = new \ReflectionClass($this->router);
$property = $class->getProperty('cacheFile');
$property->setAccessible(true);

$this->assertFalse($property->getValue($this->router));
}

/**
* Test cacheFile should be a string or false
*/
public function testSettingInvalidCacheFileValue()
{
$this->setExpectedException(
'\InvalidArgumentException',
'Router cacheFile must be a string'
);
$this->router->setCacheFile(['invalid']);
}

/**
* Test if cacheFile is not a writable directory
*/
public function testSettingInvalidCacheFileNotExisting()
{
$this->setExpectedException(
'\RuntimeException',
'Router cacheFile directory must be writable'
);

$this->router->setCacheFile(
dirname(__FILE__) . uniqid(microtime(true)) . '/' . uniqid(microtime(true))
);
}

/**
* Test cached routes file is created & that it holds our routes.
*/
public function testRouteCacheFileCanBeDispatched()
{
$methods = ['GET'];
$pattern = '/hello/{first}/{last}';
$callable = function ($request, $response, $args) {
echo sprintf('Hello %s %s', $args['first'], $args['last']);
};
$route = $this->router->map($methods, $pattern, $callable)->setName('foo');

$cacheFile = dirname(__FILE__) . '/' . uniqid(microtime(true));
$this->router->setCacheFile($cacheFile);
$class = new \ReflectionClass($this->router);
$method = $class->getMethod('createDispatcher');
$method->setAccessible(true);

$dispatcher = $method->invoke($this->router);
$this->assertInstanceOf('\FastRoute\Dispatcher', $dispatcher);
$this->assertFileExists($cacheFile, 'cache file was not created');

// instantiate a new router & load the cached routes file & see if
// we can dispatch to the route we cached.
$router2 = new Router();
$router2->setCacheFile($cacheFile);

$class = new \ReflectionClass($router2);
$method = $class->getMethod('createDispatcher');
$method->setAccessible(true);

$dispatcher2 = $method->invoke($this->router);
$result = $dispatcher2->dispatch('GET', '/hello/josh/lockhart');
$this->assertSame(\FastRoute\Dispatcher::FOUND, $result[0]);

unlink($cacheFile);
}

/**
* Calling createDispatcher as second time should give you back the same
* dispatcher as when you called it the first time.
*/
public function testCreateDispatcherReturnsSameDispatcherASecondTime()
{
$class = new \ReflectionClass($this->router);
$method = $class->getMethod('createDispatcher');
$method->setAccessible(true);

$dispatcher = $method->invoke($this->router);
$dispatcher2 = $method->invoke($this->router);
$this->assertSame($dispatcher2, $dispatcher);
}
}

0 comments on commit 0141e08

Please sign in to comment.