Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Using a service as a router resource
- Loading branch information
1 parent
dbb099d
commit 79e210f
Showing
6 changed files
with
261 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
src/Symfony/Component/Routing/Loader/DependencyInjection/ServiceRouterLoader.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Routing\Loader\DependencyInjection; | ||
|
||
use Symfony\Component\Config\Loader\Loader; | ||
use Symfony\Component\DependencyInjection\ContainerInterface; | ||
use Symfony\Component\Routing\Loader\ObjectRouteLoader; | ||
|
||
/** | ||
* A route loader that executes a service to load the routes. | ||
* | ||
* This depends on the DependencyInjection component. | ||
* | ||
* @author Ryan Weaver <ryan@knpuniversity.com> | ||
*/ | ||
class ServiceRouterLoader extends ObjectRouteLoader | ||
{ | ||
/** | ||
* @var ContainerInterface | ||
*/ | ||
private $container; | ||
|
||
public function __construct(ContainerInterface $container) | ||
{ | ||
$this->container = $container; | ||
} | ||
|
||
protected function getServiceObject($id) | ||
{ | ||
return $this->container->get($id); | ||
} | ||
} |
95 changes: 95 additions & 0 deletions
95
src/Symfony/Component/Routing/Loader/ObjectRouteLoader.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Routing\Loader; | ||
|
||
use Symfony\Component\Config\Loader\Loader; | ||
use Symfony\Component\Config\Resource\FileResource; | ||
use Symfony\Component\Routing\RouteCollection; | ||
|
||
/** | ||
* A route loader that calls a method on an object to load the routes. | ||
* | ||
* @author Ryan Weaver <ryan@knpuniversity.com> | ||
*/ | ||
abstract class ObjectRouteLoader extends Loader | ||
{ | ||
/** | ||
* Returns the object that the method will be called on to load routes. | ||
* | ||
* For example, if your application uses a service container, | ||
* the $id may be a service id. | ||
* | ||
* @param string $id | ||
* | ||
* @return object | ||
*/ | ||
abstract protected function getServiceObject($id); | ||
|
||
/** | ||
* Calls the service that will load the routes. | ||
* | ||
* @param mixed $resource Some value that will resolve to a callable | ||
* @param string|null $type The resource type | ||
* | ||
* @return RouteCollection | ||
*/ | ||
public function load($resource, $type = null) | ||
{ | ||
$parts = explode(':', $resource); | ||
if (count($parts) != 2) { | ||
throw new \InvalidArgumentException(sprintf('Invalid resource "%s" passed to the "service" route loader: use the format "service_name:methodName"', $resource)); | ||
} | ||
|
||
$serviceString = $parts[0]; | ||
$method = $parts[1]; | ||
|
||
$loaderObject = $this->getServiceObject($serviceString); | ||
|
||
if (!is_object($loaderObject)) { | ||
throw new \LogicException(sprintf('%s:getServiceObject() must return an object: %s returned', get_class($this), gettype($loaderObject))); | ||
} | ||
|
||
if (!method_exists($loaderObject, $method)) { | ||
throw new \BadMethodCallException(sprintf('Method "%s" not found on "%s" when importing routing resource "%s"', $method, get_class($loaderObject), $resource)); | ||
} | ||
|
||
$routeCollection = call_user_func(array($loaderObject, $method), $this); | ||
|
||
if (!$routeCollection instanceof RouteCollection) { | ||
$type = is_object($routeCollection) ? get_class($routeCollection) : gettype($routeCollection); | ||
|
||
throw new \LogicException(sprintf('The %s::%s method must return a RouteCollection: %s returned', get_class($loaderObject), $method, $type)); | ||
} | ||
|
||
// make the service file tracked so that if it changes, the cache rebuilds | ||
$this->addClassResource(new \ReflectionClass($loaderObject), $routeCollection); | ||
|
||
return $routeCollection; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
* | ||
* @api | ||
*/ | ||
public function supports($resource, $type = null) | ||
{ | ||
return 'service' === $type; | ||
} | ||
|
||
private function addClassResource(\ReflectionClass $class, RouteCollection $collection) | ||
{ | ||
do { | ||
$collection->addResource(new FileResource($class->getFileName())); | ||
} while ($class = $class->getParentClass()); | ||
} | ||
} |
116 changes: 116 additions & 0 deletions
116
src/Symfony/Component/Routing/Tests/Loader/ObjectRouteLoaderTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Routing\Tests\Loader; | ||
|
||
use Symfony\Component\Routing\Loader\ObjectRouteLoader; | ||
use Symfony\Component\Routing\Route; | ||
use Symfony\Component\Routing\RouteCollection; | ||
|
||
class ObjectRouteLoaderTest extends \PHPUnit_Framework_TestCase | ||
{ | ||
public function testLoadCallsServiceAndReturnsCollection() | ||
{ | ||
$loader = new ObjectRouteLoaderForTest(); | ||
|
||
// create a basic collection that will be returned | ||
$collection = new RouteCollection(); | ||
$collection->add('foo', new Route('/foo')); | ||
|
||
// create some callable object | ||
$service = $this->getMockBuilder('stdClass') | ||
->setMethods(array('loadRoutes')) | ||
->getMock(); | ||
$service->expects($this->once()) | ||
->method('loadRoutes') | ||
->with($loader) | ||
->will($this->returnValue($collection)); | ||
|
||
$loader->loaderMap = array( | ||
'my_route_provider_service' => $service, | ||
); | ||
|
||
$actualRoutes = $loader->load( | ||
'my_route_provider_service:loadRoutes', | ||
'service' | ||
); | ||
|
||
$this->assertSame($collection, $actualRoutes); | ||
// the service file should be listed as a resource | ||
$this->assertNotEmpty($actualRoutes->getResources()); | ||
} | ||
|
||
/** | ||
* @expectedException \InvalidArgumentException | ||
* @dataProvider getBadResourceStrings | ||
*/ | ||
public function testExceptionWithoutSyntax($resourceString) | ||
{ | ||
$loader = new ObjectRouteLoaderForTest(); | ||
$loader->load($resourceString); | ||
} | ||
|
||
public function getBadResourceStrings() | ||
{ | ||
return array( | ||
array('Foo'), | ||
array('Bar::baz'), | ||
array('Foo:Bar:baz'), | ||
); | ||
} | ||
|
||
/** | ||
* @expectedException \LogicException | ||
*/ | ||
public function testExceptionOnNoObjectReturned() | ||
{ | ||
$loader = new ObjectRouteLoaderForTest(); | ||
$loader->loaderMap = array('my_service' => 'NOT_AN_OBJECT'); | ||
$loader->load('my_service:method'); | ||
} | ||
|
||
/** | ||
* @expectedException \BadMethodCallException | ||
*/ | ||
public function testExceptionOnBadMethod() | ||
{ | ||
$loader = new ObjectRouteLoaderForTest(); | ||
$loader->loaderMap = array('my_service' => new \stdClass()); | ||
$loader->load('my_service:method'); | ||
} | ||
|
||
/** | ||
* @expectedException \LogicException | ||
*/ | ||
public function testExceptionOnMethodNotReturningCollection() | ||
{ | ||
$service = $this->getMockBuilder('stdClass') | ||
->setMethods(array('loadRoutes')) | ||
->getMock(); | ||
$service->expects($this->once()) | ||
->method('loadRoutes') | ||
->will($this->returnValue('NOT_A_COLLECTION')); | ||
|
||
$loader = new ObjectRouteLoaderForTest(); | ||
$loader->loaderMap = array('my_service' => $service); | ||
$loader->load('my_service:loadRoutes'); | ||
} | ||
} | ||
|
||
class ObjectRouteLoaderForTest extends ObjectRouteLoader | ||
{ | ||
public $loaderMap = array(); | ||
|
||
protected function getServiceObject($id) | ||
{ | ||
return isset($this->loaderMap[$id]) ? $this->loaderMap[$id] : null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters