Skip to content

Commit 4f160e2

Browse files
committed
Merge pull request #114 from symfony-cmf/dawehner-paged-route-collection
paged route collection
2 parents 286f7aa + b4e2655 commit 4f160e2

File tree

5 files changed

+350
-0
lines changed

5 files changed

+350
-0
lines changed

LazyRouteCollection.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ public function __construct(RouteProviderInterface $provider)
2929
$this->provider = $provider;
3030
}
3131

32+
/**
33+
* {@inheritdoc}
34+
*/
35+
public function getIterator()
36+
{
37+
return new \ArrayIterator($this->all());
38+
}
39+
3240
/**
3341
* Gets the number of Routes in this collection.
3442
*

PagedRouteCollection.php

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Symfony CMF package.
5+
*
6+
* (c) 2011-2014 Symfony CMF
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 Symfony\Cmf\Component\Routing;
13+
14+
/**
15+
* Provides a route collection which avoids having all routes in memory.
16+
*
17+
* Internally, this does load multiple routes over time using a
18+
* PagedRouteProviderInterface $route_provider.
19+
*/
20+
class PagedRouteCollection implements \Iterator, \Countable
21+
{
22+
/**
23+
* @var PagedRouteProviderInterface
24+
*/
25+
protected $provider;
26+
27+
/**
28+
* Stores the amount of routes which are loaded in parallel and kept in
29+
* memory.
30+
*
31+
* @var int
32+
*/
33+
protected $routesBatchSize;
34+
35+
/**
36+
* Contains the current item the iterator points to.
37+
*
38+
* @var int
39+
*/
40+
protected $current = -1;
41+
42+
/**
43+
* Stores the current loaded routes.
44+
*
45+
* @var \Symfony\Component\Routing\Route[]
46+
*/
47+
protected $currentRoutes;
48+
49+
public function __construct(PagedRouteProviderInterface $pagedRouteProvider, $routesBatchSize = 50)
50+
{
51+
$this->provider = $pagedRouteProvider;
52+
$this->routesBatchSize = $routesBatchSize;
53+
}
54+
55+
/**
56+
* Loads the next routes into the elements array.
57+
*
58+
* @param int $offset The offset used in the db query.
59+
*/
60+
protected function loadNextElements($offset)
61+
{
62+
// If the last batch was smaller than the batch size, this means there
63+
// are no more routes available.
64+
if (isset($this->currentRoutes) && count($this->currentRoutes) < $this->routesBatchSize) {
65+
$this->currentRoutes = array();
66+
} else {
67+
$this->currentRoutes = $this->provider->getRoutesPaged($offset, $this->routesBatchSize);
68+
}
69+
}
70+
71+
/**
72+
* {@inheritdoc}
73+
*/
74+
public function current()
75+
{
76+
return current($this->currentRoutes);
77+
}
78+
79+
/**
80+
* {@inheritdoc}
81+
*/
82+
public function next()
83+
{
84+
$result = next($this->currentRoutes);
85+
if (false === $result) {
86+
$this->loadNextElements($this->current + 1);
87+
}
88+
$this->current++;
89+
}
90+
91+
/**
92+
* {@inheritdoc}
93+
*/
94+
public function key()
95+
{
96+
return key($this->currentRoutes);
97+
}
98+
99+
/**
100+
* {@inheritdoc}
101+
*/
102+
public function valid()
103+
{
104+
return key($this->currentRoutes);
105+
}
106+
107+
/**
108+
* {@inheritdoc}
109+
*/
110+
public function rewind()
111+
{
112+
$this->current = 0;
113+
$this->currentRoutes = NULL;
114+
$this->loadNextElements($this->current);
115+
}
116+
117+
/**
118+
* Gets the number of Routes in this collection.
119+
*
120+
* @return int The number of routes
121+
*/
122+
public function count()
123+
{
124+
return $this->provider->getRoutesCount();
125+
}
126+
}

PagedRouteProviderInterface.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Symfony CMF package.
5+
*
6+
* (c) 2011-2014 Symfony CMF
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 Symfony\Cmf\Component\Routing;
13+
14+
/**
15+
* Interface for a provider which allows to retrieve a limited amount of routes.
16+
*/
17+
interface PagedRouteProviderInterface extends RouteProviderInterface
18+
{
19+
/**
20+
* Find an amount of routes with an offset and possible a limit.
21+
*
22+
* In case you want to iterate over all routes, you want to avoid to load
23+
* all routes at once.
24+
*
25+
* @param int $offset
26+
* The sequence will start with that offset in the list of all routes.
27+
* @param int $length [optional]
28+
* The sequence will have that many routes in it. If no length is
29+
* specified all routes are returned.
30+
*
31+
* @return \Symfony\Component\Routing\Route[]
32+
* Routes keyed by the route name.
33+
*/
34+
public function getRoutesPaged($offset, $length = null);
35+
36+
/**
37+
* Determines the total amount of routes.
38+
*
39+
* @return int
40+
*/
41+
public function getRoutesCount();
42+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony CMF package.
5+
*
6+
* (c) 2011-2014 Symfony CMF
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 Symfony\Cmf\Component\Routing;
13+
14+
use Symfony\Cmf\Component\Routing\Test\CmfUnitTestCase;
15+
use Symfony\Component\Routing\Route;
16+
17+
/**
18+
* Tests the lazy route collection.
19+
*
20+
* @group cmf/routing
21+
*/
22+
class LazyRouteCollectionTest extends CmfUnitTestCase
23+
{
24+
/**
25+
* Tests the iterator without a paged route provider.
26+
*/
27+
public function testGetIterator()
28+
{
29+
$routeProvider = $this->getMock('Symfony\Cmf\Component\Routing\RouteProviderInterface');
30+
$testRoutes = array(
31+
'route_1' => new Route('/route-1'),
32+
'route_2"' => new Route('/route-2'),
33+
);
34+
$routeProvider->expects($this->exactly(2))
35+
->method('getRoutesByNames')
36+
->with(null)
37+
->will($this->returnValue($testRoutes));
38+
$lazyRouteCollection = new LazyRouteCollection($routeProvider);
39+
$this->assertEquals($testRoutes, iterator_to_array($lazyRouteCollection->getIterator()));
40+
$this->assertEquals($testRoutes, $lazyRouteCollection->all());
41+
}
42+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony CMF package.
5+
*
6+
* (c) 2011-2014 Symfony CMF
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 Symfony\Cmf\Component\Routing;
13+
14+
use Symfony\Cmf\Component\Routing\Test\CmfUnitTestCase;
15+
use Symfony\Component\Routing\Route;
16+
17+
/**
18+
* Tests the page route collection.
19+
*
20+
* @group cmf/routing
21+
*/
22+
class PagedRouteCollectionTest extends CmfUnitTestCase
23+
{
24+
/**
25+
* Contains a mocked route provider.
26+
*
27+
* @var \Symfony\Cmf\Component\Routing\PagedRouteProviderInterface|\PHPUnit_Framework_MockObject_MockObject
28+
*/
29+
protected $routeProvider;
30+
31+
protected function setUp()
32+
{
33+
$this->routeProvider = $this->getMock('Symfony\Cmf\Component\Routing\PagedRouteProviderInterface');
34+
}
35+
36+
/**
37+
* Tests iterating a small amount of routes.
38+
*
39+
* @dataProvider providerIterator
40+
*/
41+
public function testIterator($amountRoutes, $routesLoadedInParallel, $expectedCalls = array())
42+
{
43+
$routes = array();
44+
for ($i = 0; $i < $amountRoutes; $i++) {
45+
$routes['test_' . $i] = new Route("/example-$i");
46+
}
47+
$names = array_keys($routes);
48+
49+
foreach ($expectedCalls as $i => $range)
50+
{
51+
$this->routeProvider->expects($this->at($i))
52+
->method('getRoutesPaged')
53+
->with($range[0], $range[1])
54+
->will($this->returnValue(array_slice($routes, $range[0], $range[1])));
55+
}
56+
57+
$route_collection = new PagedRouteCollection($this->routeProvider, $routesLoadedInParallel);
58+
59+
$counter = 0;
60+
foreach ($route_collection as $route_name => $route) {
61+
// Ensure the route did not changed.
62+
$this->assertEquals($routes[$route_name], $route);
63+
// Ensure that the order did not changed.
64+
$this->assertEquals($route_name, $names[$counter]);
65+
$counter++;
66+
}
67+
}
68+
69+
/**
70+
* Provides test data for testIterator().
71+
*/
72+
public function providerIterator()
73+
{
74+
$data = array();
75+
// Non total routes.
76+
$data[] = array(0, 20, array(array(0, 20)));
77+
// Less total routes than loaded in parallel.
78+
$data[] = array(10, 20, array(array(0, 20)));
79+
// Exact the same amount of routes then loaded in parallel.
80+
$data[] = array(20, 20, array(array(0, 20), array(20, 20)));
81+
// Less than twice the amount.
82+
$data[] = array(39, 20, array(array(0, 20), array(20, 20)));
83+
// More total routes than loaded in parallel.
84+
$data[] = array(40, 20, array(array(0, 20), array(20, 20), array(40, 20)));
85+
$data[] = array(41, 20, array(array(0, 20), array(20, 20), array(40, 20)));
86+
// why not.
87+
$data[] = array(42, 23, array(array(0, 23), array(23, 23)));
88+
return $data;
89+
}
90+
91+
/**
92+
* Tests the count() method.
93+
*/
94+
public function testCount()
95+
{
96+
$this->routeProvider->expects($this->once())
97+
->method('getRoutesCount')
98+
->will($this->returnValue(12));
99+
$routeCollection = new PagedRouteCollection($this->routeProvider);
100+
$this->assertEquals(12, $routeCollection->count());
101+
}
102+
103+
/**
104+
* Tests the rewind method once the iterator is at the end.
105+
*/
106+
public function testIteratingAndRewind()
107+
{
108+
$routes = array();
109+
for ($i = 0; $i < 30; $i++) {
110+
$routes['test_' . $i] = new Route("/example-$i");
111+
}
112+
$this->routeProvider->expects($this->any())
113+
->method('getRoutesPaged')
114+
->will($this->returnValueMap(array(
115+
array(0, 10, array_slice($routes, 0, 10)),
116+
array(10, 10, array_slice($routes, 9, 10)),
117+
array(20, 10, array()),
118+
)));
119+
120+
$routeCollection = new PagedRouteCollection($this->routeProvider, 10);
121+
122+
// Force the iterating process.
123+
$routeCollection->rewind();
124+
for ($i = 0; $i < 29; $i++) {
125+
$routeCollection->next();
126+
}
127+
$routeCollection->rewind();
128+
129+
$this->assertEquals('test_0', $routeCollection->key());
130+
$this->assertEquals($routes['test_0'], $routeCollection->current());
131+
}
132+
}

0 commit comments

Comments
 (0)