Skip to content
This repository has been archived by the owner on Jul 4, 2018. It is now read-only.

Commit

Permalink
feature #1345 Allowing callables for mount (ragboyjr)
Browse files Browse the repository at this point in the history
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: RJ Garcia <rj@bighead.net>

Commits
-------

847b7ee Allowing callables for mount
  • Loading branch information
fabpot committed May 16, 2016
2 parents ff0d791 + 847b7ee commit 4643214
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 13 deletions.
13 changes: 12 additions & 1 deletion doc/organizing_controllers.rst
Expand Up @@ -25,14 +25,25 @@ 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
of ``ControllerCollection`` when used.

``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::

Expand Down
8 changes: 4 additions & 4 deletions src/Silex/Application.php
Expand Up @@ -437,8 +437,8 @@ public function sendFile($file, $status = 200, array $headers = array(), $conten
/**
* Mounts controllers under the given route prefix.
*
* @param string $prefix The route prefix
* @param ControllerCollection|ControllerProviderInterface $controllers A ControllerCollection or a ControllerProviderInterface instance
* @param string $prefix The route prefix
* @param ControllerCollection|callable|ControllerProviderInterface $controllers A ControllerCollection, a callable, or a ControllerProviderInterface instance
*
* @return Application
*
Expand All @@ -454,8 +454,8 @@ public function mount($prefix, $controllers)
}

$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);
Expand Down
20 changes: 16 additions & 4 deletions src/Silex/ControllerCollection.php
Expand Up @@ -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')));
};
Expand All @@ -57,11 +59,21 @@ public function __construct(Route $defaultRoute, $routesFactory = null)
/**
* Mounts controllers under the given route prefix.
*
* @param string $prefix The route prefix
* @param ControllerCollection $controllers A ControllerCollection instance
* @param string $prefix The route prefix
* @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;
Expand Down
7 changes: 4 additions & 3 deletions src/Silex/Provider/RoutingServiceProvider.php
Expand Up @@ -66,9 +66,10 @@ public function register(Container $app)
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) {
Expand Down
14 changes: 13 additions & 1 deletion tests/Silex/Tests/ApplicationTest.php
Expand Up @@ -464,7 +464,7 @@ public function testMountPreservesOrder()

/**
* @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()
{
Expand All @@ -482,6 +482,18 @@ public function testMountWrongConnectReturnValueException()
$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();
Expand Down
51 changes: 51 additions & 0 deletions tests/Silex/Tests/ControllerCollectionTest.php
Expand Up @@ -127,6 +127,57 @@ public function testUniqueGeneratedRouteNamesAmongNestedMounts()
$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());
Expand Down
12 changes: 12 additions & 0 deletions tests/Silex/Tests/Provider/RoutingServiceProviderTest.php
Expand Up @@ -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;

Expand Down Expand Up @@ -106,4 +108,14 @@ public function testUrlGenerationWithHttps()

$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);
});
}
}

0 comments on commit 4643214

Please sign in to comment.