Skip to content

Commit c367788

Browse files
committed
load route annotations from a directory
1 parent 95dbe97 commit c367788

File tree

8 files changed

+308
-32
lines changed

8 files changed

+308
-32
lines changed

Resources/config/routing.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
<tag name="routing.loader" />
2727
</service>
2828

29+
<service id="fos_rest.routing.loader.directory" class="FOS\RestBundle\Routing\Loader\DirectoryRouteLoader">
30+
<argument type="service" id="fos_rest.routing.loader.processor" />
31+
<tag name="routing.loader" />
32+
</service>
33+
2934
<service id="fos_rest.routing.loader.processor" class="%fos_rest.routing.loader.processor.class%" />
3035

3136
<service id="fos_rest.routing.loader.yaml_collection" class="%fos_rest.routing.loader.yaml_collection.class%">

Routing/Loader/ClassUtils.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSRestBundle package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace FOS\RestBundle\Routing\Loader;
13+
14+
/**
15+
* @internal
16+
*/
17+
class ClassUtils
18+
{
19+
/**
20+
* Returns the full class name for the first class in the file.
21+
*
22+
* @param string $file A PHP file path
23+
*
24+
* @return string|false Full class name if found, false otherwise
25+
*/
26+
public static function findClassInFile($file)
27+
{
28+
$class = false;
29+
$namespace = false;
30+
$tokens = token_get_all(file_get_contents($file));
31+
for ($i = 0, $count = count($tokens); $i < $count; ++$i) {
32+
$token = $tokens[$i];
33+
34+
if (!is_array($token)) {
35+
continue;
36+
}
37+
38+
if (true === $class && T_STRING === $token[0]) {
39+
return $namespace.'\\'.$token[1];
40+
}
41+
42+
if (true === $namespace && T_STRING === $token[0]) {
43+
$namespace = '';
44+
do {
45+
$namespace .= $token[1];
46+
$token = $tokens[++$i];
47+
} while ($i < $count && is_array($token) && in_array($token[0], array(T_NS_SEPARATOR, T_STRING)));
48+
}
49+
50+
if (T_CLASS === $token[0]) {
51+
$class = true;
52+
}
53+
54+
if (T_NAMESPACE === $token[0]) {
55+
$namespace = true;
56+
}
57+
}
58+
59+
return false;
60+
}
61+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSRestBundle package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace FOS\RestBundle\Routing\Loader;
13+
14+
use Symfony\Component\Config\Loader\Loader;
15+
use Symfony\Component\Routing\RouteCollection;
16+
17+
/**
18+
* Parse annotated controller classes from all files of a directory.
19+
*
20+
* @author Christian Flothmann <[email protected]>
21+
*/
22+
class DirectoryRouteLoader extends Loader
23+
{
24+
private $processor;
25+
26+
public function __construct(RestRouteProcessor $processor)
27+
{
28+
$this->processor = $processor;
29+
}
30+
31+
/**
32+
* {@inheritdoc}
33+
*/
34+
public function load($resource, $type = null)
35+
{
36+
if (!is_dir($resource)) {
37+
throw new \InvalidArgumentException(sprintf('Given resource of type "%s" is no directory.', $resource));
38+
}
39+
40+
$collection = new RouteCollection();
41+
42+
$directoryIterator = new \DirectoryIterator($resource);
43+
44+
foreach ($directoryIterator as $file) {
45+
if ($file->getFilename() === '.' || $file->getFilename() === '..') {
46+
continue;
47+
}
48+
49+
$imported = $this->processor->importResource($this, ClassUtils::findClassInFile($file->getPathname()), array(), null, null, 'rest');
50+
$collection->addCollection($imported);
51+
}
52+
53+
return $collection;
54+
}
55+
56+
/**
57+
* {@inheritdoc}
58+
*/
59+
public function supports($resource, $type = null)
60+
{
61+
return 'rest' === $type && is_string($resource) && is_dir($resource);
62+
}
63+
}

Routing/Loader/RestRouteLoader.php

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ private function getControllerLocator($controller)
105105

106106
if (0 === strpos($controller, '@')) {
107107
$file = $this->locator->locate($controller);
108-
$controllerClass = $this->findClass($file);
108+
$controllerClass = ClassUtils::findClassInFile($file);
109109

110110
if (false === $controllerClass) {
111111
throw new \InvalidArgumentException(sprintf('Can\'t find class for controller "%s"', $controller));
@@ -162,37 +162,8 @@ private function getControllerLocator($controller)
162162
*/
163163
protected function findClass($file)
164164
{
165-
$class = false;
166-
$namespace = false;
167-
$tokens = token_get_all(file_get_contents($file));
168-
for ($i = 0, $count = count($tokens); $i < $count; ++$i) {
169-
$token = $tokens[$i];
170-
171-
if (!is_array($token)) {
172-
continue;
173-
}
174-
175-
if (true === $class && T_STRING === $token[0]) {
176-
return $namespace.'\\'.$token[1];
177-
}
178-
179-
if (true === $namespace && T_STRING === $token[0]) {
180-
$namespace = '';
181-
do {
182-
$namespace .= $token[1];
183-
$token = $tokens[++$i];
184-
} while ($i < $count && is_array($token) && in_array($token[0], array(T_NS_SEPARATOR, T_STRING)));
185-
}
186-
187-
if (T_CLASS === $token[0]) {
188-
$class = true;
189-
}
190-
191-
if (T_NAMESPACE === $token[0]) {
192-
$namespace = true;
193-
}
194-
}
165+
@trigger_error('The '.__METHOD__.' method is deprecated since version 1.8 and will be removed in 2.0. Use '.__NAMESPACE__.'\ClassUtils::findClassInFile() instead.', E_USER_DEPRECATED);
195166

196-
return false;
167+
return ClassUtils::findClassInFile($file);
197168
}
198169
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSRestBundle package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace FOS\RestBundle\Tests\Fixtures\Controller\Directory;
13+
14+
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
15+
16+
class UserTopicCommentsController extends Controller
17+
{
18+
public function getCommentsAction($slug, $title)
19+
{
20+
}
21+
22+
// [GET] /users/{slug}/topics/{title}/comments
23+
24+
public function putCommentAction($slug, $title, $id)
25+
{
26+
}
27+
28+
// [PUT] /users/{slug}/topics/{title}/comments/{id}
29+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSRestBundle package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace FOS\RestBundle\Tests\Fixtures\Controller\Directory;
13+
14+
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
15+
16+
class UserTopicsController extends Controller
17+
{
18+
public function getTopicsAction($slug)
19+
{
20+
}
21+
22+
// [GET] /users/{slug}/topics
23+
24+
public function getTopicAction($slug, $title)
25+
{
26+
}
27+
28+
// [GET] /users/{slug}/topics/{title}
29+
30+
public function putTopicAction($slug, $title)
31+
{
32+
}
33+
34+
// [PUT] /users/{slug}/topics/{title}
35+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSRestBundle package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace FOS\RestBundle\Tests\Fixtures\Controller\Directory;
13+
14+
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
15+
16+
class UsersController extends Controller
17+
{
18+
public function getUsersAction()
19+
{
20+
}
21+
22+
// [GET] /users
23+
24+
public function getUserAction($slug)
25+
{
26+
}
27+
28+
// [GET] /users/{slug}
29+
30+
public function postUsersAction()
31+
{
32+
}
33+
34+
// [POST] /users
35+
36+
public function putUserAction($slug)
37+
{
38+
}
39+
40+
// [PUT] /users/{slug}
41+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSRestBundle package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace FOS\RestBundle\Tests\Routing\Loader;
13+
14+
use FOS\RestBundle\Routing\Loader\DirectoryRouteLoader;
15+
use FOS\RestBundle\Routing\Loader\RestRouteProcessor;
16+
use Symfony\Component\Config\Loader\LoaderResolver;
17+
18+
class DirectoryRouteLoaderTest extends LoaderTest
19+
{
20+
public function testLoad()
21+
{
22+
$collection = $this->loadFromDirectory(__DIR__.'/../../Fixtures/Controller/Directory');
23+
24+
$this->assertCount(9, $collection);
25+
26+
$this->assertInstanceOf('Symfony\Component\Routing\Route', $collection->get('get_users'));
27+
$this->assertInstanceOf('Symfony\Component\Routing\Route', $collection->get('get_user'));
28+
$this->assertInstanceOf('Symfony\Component\Routing\Route', $collection->get('post_users'));
29+
$this->assertInstanceOf('Symfony\Component\Routing\Route', $collection->get('put_user'));
30+
$this->assertInstanceOf('Symfony\Component\Routing\Route', $collection->get('get_comments'));
31+
$this->assertInstanceOf('Symfony\Component\Routing\Route', $collection->get('put_comment'));
32+
$this->assertInstanceOf('Symfony\Component\Routing\Route', $collection->get('get_topics'));
33+
$this->assertInstanceOf('Symfony\Component\Routing\Route', $collection->get('get_topic'));
34+
$this->assertInstanceOf('Symfony\Component\Routing\Route', $collection->get('put_topic'));
35+
}
36+
37+
/**
38+
* @dataProvider supportsDataProvider
39+
*/
40+
public function testSupports($resource, $type, $expected)
41+
{
42+
$loader = new DirectoryRouteLoader(new RestRouteProcessor());
43+
44+
if ($expected) {
45+
$this->assertTrue($loader->supports($resource, $type));
46+
} else {
47+
$this->assertFalse($loader->supports($resource, $type));
48+
}
49+
}
50+
51+
public function supportsDataProvider()
52+
{
53+
return array(
54+
'existing-directory' => array(__DIR__.'/../../Fixtures/Controller', 'rest', true),
55+
'non-existing-directory' => array(__DIR__.'/Fixtures/Controller', 'rest', false),
56+
'class-name' => array('FOS\RestBundle\Tests\Fixtures\Controller\UsersController', 'rest', false),
57+
'null-type' => array(__DIR__.'/../../Fixtures/Controller', null, false),
58+
);
59+
}
60+
61+
private function loadFromDirectory($resource)
62+
{
63+
$directoryLoader = new DirectoryRouteLoader(new RestRouteProcessor());
64+
$controllerLoader = $this->getControllerLoader();
65+
66+
// LoaderResolver sets the resolvers on the loaders passed to it
67+
new LoaderResolver(array($directoryLoader, $controllerLoader));
68+
69+
return $directoryLoader->load($resource, 'rest');
70+
}
71+
}

0 commit comments

Comments
 (0)