Commit 847b7eeb authored by RJ Garcia's avatar RJ Garcia

Allowing callables for mount

- Added the ability to pass callables into the controller collection
    mount method.
- Added a controllersFactory parameter into the controller collection
    which is a callable that is used to create a new controller for the
    mount method.
- Updated documentation to show usage of new callables in mount and
    recursive mounting.
- Added appropriate tests
Signed-off-by: default avatarRJ Garcia <rj@bighead.net>
parent 17c0eedc
......@@ -25,6 +25,16 @@ group them logically::
$app->mount('/blog', $blog);
$app->mount('/forum', $forum);
// define controllers for a admin
$app->mount('/admin', function ($api) {
// recursively mount
$api->mount('/blog', function ($user) {
$user->get('/', function () {
return 'Admin Blog home page';
});
});
});
.. note::
``$app['controllers_factory']`` is a factory that returns a new instance
......@@ -32,7 +42,8 @@ group them logically::
``mount()`` prefixes all routes with the given prefix and merges them into the
main Application. So, ``/`` will map to the main home page, ``/blog/`` to the
blog home page, and ``/forum/`` to the forum home page.
blog home page, ``/forum/`` to the forum home page, and ``/admin/blog/`` to the
admin blog home page.
.. caution::
......
......@@ -438,7 +438,7 @@ class Application extends Container implements HttpKernelInterface, TerminableIn
* Mounts controllers under the given route prefix.
*
* @param string $prefix The route prefix
* @param ControllerCollection|ControllerProviderInterface $controllers A ControllerCollection or a ControllerProviderInterface instance
* @param ControllerCollection|callable|ControllerProviderInterface $controllers A ControllerCollection, a callable, or a ControllerProviderInterface instance
*
* @return Application
*
......@@ -454,8 +454,8 @@ class Application extends Container implements HttpKernelInterface, TerminableIn
}
$controllers = $connectedControllers;
} elseif (!$controllers instanceof ControllerCollection) {
throw new \LogicException('The "mount" method takes either a "ControllerCollection" or a "ControllerProviderInterface" instance.');
} elseif (!$controllers instanceof ControllerCollection && !is_callable($controllers)) {
throw new \LogicException('The "mount" method takes either a "ControllerCollection" instance, "ControllerProviderInterface" instance, or a callable.');
}
$this['controllers']->mount($prefix, $controllers);
......
......@@ -44,11 +44,13 @@ class ControllerCollection
protected $defaultController;
protected $prefix;
protected $routesFactory;
protected $controllersFactory;
public function __construct(Route $defaultRoute, $routesFactory = null)
public function __construct(Route $defaultRoute, RouteCollection $routesFactory = null, $controllersFactory = null)
{
$this->defaultRoute = $defaultRoute;
$this->routesFactory = $routesFactory;
$this->controllersFactory = $controllersFactory;
$this->defaultController = function (Request $request) {
throw new \LogicException(sprintf('The "%s" route must have code to run when it matches.', $request->attributes->get('_route')));
};
......@@ -58,10 +60,20 @@ class ControllerCollection
* Mounts controllers under the given route prefix.
*
* @param string $prefix The route prefix
* @param ControllerCollection $controllers A ControllerCollection instance
* @param ControllerCollection|callable $controllers A ControllerCollection instance or a callable for defining routes
*
* @throws \LogicException
*/
public function mount($prefix, ControllerCollection $controllers)
public function mount($prefix, $controllers)
{
if (is_callable($controllers)) {
$collection = $this->controllersFactory ? call_user_func($this->controllersFactory) : new static(new Route(), new RouteCollection());
call_user_func($controllers, $collection);
$controllers = $collection;
} elseif (!$controllers instanceof self) {
throw new \LogicException('The "mount" method takes either a "ControllerCollection" instance or callable.');
}
$controllers->prefix = $prefix;
$this->controllers[] = $controllers;
......
......@@ -66,9 +66,10 @@ class RoutingServiceProvider implements ServiceProviderInterface, EventListenerP
return $app['controllers_factory'];
};
$app['controllers_factory'] = $app->factory(function ($app) {
return new ControllerCollection($app['route_factory'], $app['routes_factory']);
});
$controllers_factory = function () use ($app, &$controllers_factory) {
return new ControllerCollection($app['route_factory'], $app['routes_factory'], $controllers_factory);
};
$app['controllers_factory'] = $app->factory($controllers_factory);
$app['routing.listener'] = function ($app) {
$urlMatcher = new LazyRequestMatcher(function () use ($app) {
......
......@@ -464,7 +464,7 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
/**
* @expectedException \LogicException
* @expectedExceptionMessage The "mount" method takes either a "ControllerCollection" or a "ControllerProviderInterface" instance.
* @expectedExceptionMessage The "mount" method takes either a "ControllerCollection" instance, "ControllerProviderInterface" instance, or a callable.
*/
public function testMountNullException()
{
......@@ -482,6 +482,18 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$app->mount('/exception', new IncorrectControllerCollection());
}
public function testMountCallable()
{
$app = new Application();
$app->mount('/prefix', function (ControllerCollection $coll) {
$coll->get('/path');
});
$app->flush();
$this->assertEquals(1, $app['routes']->count());
}
public function testSendFile()
{
$app = new Application();
......
......@@ -127,6 +127,57 @@ class ControllerCollectionTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array('_root_a_tree_leaf', '_root_a_tree_leaf_1'), array_keys($routes->all()));
}
public function testMountCallable()
{
$controllers = new ControllerCollection(new Route());
$controllers->mount('/prefix', function (ControllerCollection $coll) {
$coll->mount('/path', function ($coll) {
$coll->get('/part');
});
});
$routes = $controllers->flush();
$this->assertEquals('/prefix/path/part', current($routes->all())->getPath());
}
public function testMountCallableProperClone()
{
$controllers = new ControllerCollection(new Route(), new RouteCollection());
$controllers->get('/');
$subControllers = null;
$controllers->mount('/prefix', function (ControllerCollection $coll) use (&$subControllers) {
$subControllers = $coll;
$coll->get('/');
});
$routes = $controllers->flush();
$subRoutes = $subControllers->flush();
$this->assertTrue($routes->count() == 2 && $subRoutes->count() == 0);
}
public function testMountControllersFactory()
{
$testControllers = new ControllerCollection(new Route());
$controllers = new ControllerCollection(new Route(), null, function () use ($testControllers) {
return $testControllers;
});
$controllers->mount('/prefix', function ($mounted) use ($testControllers) {
$this->assertSame($mounted, $testControllers);
});
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage The "mount" method takes either a "ControllerCollection" instance or callable.
*/
public function testMountCallableException()
{
$controllers = new ControllerCollection(new Route());
$controllers->mount('/prefix', '');
}
public function testAssert()
{
$controllers = new ControllerCollection(new Route());
......
......@@ -11,7 +11,9 @@
namespace Silex\Tests\Provider;
use Pimple\Container;
use Silex\Application;
use Silex\Provider\RoutingServiceProvider;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
......@@ -106,4 +108,14 @@ class RoutingServiceProviderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('https://localhost/secure', $response->getContent());
}
public function testControllersFactory()
{
$app = new Container();
$app->register(new RoutingServiceProvider());
$coll = $app['controllers_factory'];
$coll->mount('/blog', function ($blog) {
$this->assertInstanceOf('Silex\ControllerCollection', $blog);
});
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment