Commit 46432144 authored by Fabien Potencier's avatar Fabien Potencier

feature #1345 Allowing callables for mount (ragboyjr)

This PR was merged into the 2.0.x-dev branch.

Discussion
----------

Allowing callables for mount

- Added the ability to pass callables into the controller collection
    mount method.
- Updated documentation to show usage of new callables in mount and
    recursive mounting.
- Added appropriate tests

This addresses #1262 and #1290
Signed-off-by: default avatarRJ Garcia <rj@bighead.net>

Commits
-------

847b7eeb Allowing callables for mount
parents ff0d7910 847b7eeb
...@@ -25,6 +25,16 @@ group them logically:: ...@@ -25,6 +25,16 @@ group them logically::
$app->mount('/blog', $blog); $app->mount('/blog', $blog);
$app->mount('/forum', $forum); $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:: .. note::
``$app['controllers_factory']`` is a factory that returns a new instance ``$app['controllers_factory']`` is a factory that returns a new instance
...@@ -32,7 +42,8 @@ group them logically:: ...@@ -32,7 +42,8 @@ group them logically::
``mount()`` prefixes all routes with the given prefix and merges them into the ``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 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:: .. caution::
......
...@@ -438,7 +438,7 @@ class Application extends Container implements HttpKernelInterface, TerminableIn ...@@ -438,7 +438,7 @@ class Application extends Container implements HttpKernelInterface, TerminableIn
* Mounts controllers under the given route prefix. * Mounts controllers under the given route prefix.
* *
* @param string $prefix The 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 * @return Application
* *
...@@ -454,8 +454,8 @@ class Application extends Container implements HttpKernelInterface, TerminableIn ...@@ -454,8 +454,8 @@ class Application extends Container implements HttpKernelInterface, TerminableIn
} }
$controllers = $connectedControllers; $controllers = $connectedControllers;
} elseif (!$controllers instanceof ControllerCollection) { } elseif (!$controllers instanceof ControllerCollection && !is_callable($controllers)) {
throw new \LogicException('The "mount" method takes either a "ControllerCollection" or a "ControllerProviderInterface" instance.'); throw new \LogicException('The "mount" method takes either a "ControllerCollection" instance, "ControllerProviderInterface" instance, or a callable.');
} }
$this['controllers']->mount($prefix, $controllers); $this['controllers']->mount($prefix, $controllers);
......
...@@ -44,11 +44,13 @@ class ControllerCollection ...@@ -44,11 +44,13 @@ class ControllerCollection
protected $defaultController; protected $defaultController;
protected $prefix; protected $prefix;
protected $routesFactory; 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->defaultRoute = $defaultRoute;
$this->routesFactory = $routesFactory; $this->routesFactory = $routesFactory;
$this->controllersFactory = $controllersFactory;
$this->defaultController = function (Request $request) { $this->defaultController = function (Request $request) {
throw new \LogicException(sprintf('The "%s" route must have code to run when it matches.', $request->attributes->get('_route'))); 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 ...@@ -58,10 +60,20 @@ class ControllerCollection
* Mounts controllers under the given route prefix. * Mounts controllers under the given route prefix.
* *
* @param string $prefix The 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; $controllers->prefix = $prefix;
$this->controllers[] = $controllers; $this->controllers[] = $controllers;
......
...@@ -66,9 +66,10 @@ class RoutingServiceProvider implements ServiceProviderInterface, EventListenerP ...@@ -66,9 +66,10 @@ class RoutingServiceProvider implements ServiceProviderInterface, EventListenerP
return $app['controllers_factory']; return $app['controllers_factory'];
}; };
$app['controllers_factory'] = $app->factory(function ($app) { $controllers_factory = function () use ($app, &$controllers_factory) {
return new ControllerCollection($app['route_factory'], $app['routes_factory']); return new ControllerCollection($app['route_factory'], $app['routes_factory'], $controllers_factory);
}); };
$app['controllers_factory'] = $app->factory($controllers_factory);
$app['routing.listener'] = function ($app) { $app['routing.listener'] = function ($app) {
$urlMatcher = new LazyRequestMatcher(function () use ($app) { $urlMatcher = new LazyRequestMatcher(function () use ($app) {
......
...@@ -464,7 +464,7 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase ...@@ -464,7 +464,7 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
/** /**
* @expectedException \LogicException * @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() public function testMountNullException()
{ {
...@@ -482,6 +482,18 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase ...@@ -482,6 +482,18 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
$app->mount('/exception', new IncorrectControllerCollection()); $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() public function testSendFile()
{ {
$app = new Application(); $app = new Application();
......
...@@ -127,6 +127,57 @@ class ControllerCollectionTest extends \PHPUnit_Framework_TestCase ...@@ -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())); $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() public function testAssert()
{ {
$controllers = new ControllerCollection(new Route()); $controllers = new ControllerCollection(new Route());
......
...@@ -11,7 +11,9 @@ ...@@ -11,7 +11,9 @@
namespace Silex\Tests\Provider; namespace Silex\Tests\Provider;
use Pimple\Container;
use Silex\Application; use Silex\Application;
use Silex\Provider\RoutingServiceProvider;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
...@@ -106,4 +108,14 @@ class RoutingServiceProviderTest extends \PHPUnit_Framework_TestCase ...@@ -106,4 +108,14 @@ class RoutingServiceProviderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('https://localhost/secure', $response->getContent()); $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