-
Notifications
You must be signed in to change notification settings - Fork 69
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #114 from symfony-cmf/dawehner-paged-route-collection
paged route collection
- Loading branch information
Showing
5 changed files
with
350 additions
and
0 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
<?php | ||
|
||
/** | ||
* This file is part of the Symfony CMF package. | ||
* | ||
* (c) 2011-2014 Symfony CMF | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Cmf\Component\Routing; | ||
|
||
/** | ||
* Provides a route collection which avoids having all routes in memory. | ||
* | ||
* Internally, this does load multiple routes over time using a | ||
* PagedRouteProviderInterface $route_provider. | ||
*/ | ||
class PagedRouteCollection implements \Iterator, \Countable | ||
{ | ||
/** | ||
* @var PagedRouteProviderInterface | ||
*/ | ||
protected $provider; | ||
|
||
/** | ||
* Stores the amount of routes which are loaded in parallel and kept in | ||
* memory. | ||
* | ||
* @var int | ||
*/ | ||
protected $routesBatchSize; | ||
|
||
/** | ||
* Contains the current item the iterator points to. | ||
* | ||
* @var int | ||
*/ | ||
protected $current = -1; | ||
|
||
/** | ||
* Stores the current loaded routes. | ||
* | ||
* @var \Symfony\Component\Routing\Route[] | ||
*/ | ||
protected $currentRoutes; | ||
|
||
public function __construct(PagedRouteProviderInterface $pagedRouteProvider, $routesBatchSize = 50) | ||
{ | ||
$this->provider = $pagedRouteProvider; | ||
$this->routesBatchSize = $routesBatchSize; | ||
} | ||
|
||
/** | ||
* Loads the next routes into the elements array. | ||
* | ||
* @param int $offset The offset used in the db query. | ||
*/ | ||
protected function loadNextElements($offset) | ||
{ | ||
// If the last batch was smaller than the batch size, this means there | ||
// are no more routes available. | ||
if (isset($this->currentRoutes) && count($this->currentRoutes) < $this->routesBatchSize) { | ||
$this->currentRoutes = array(); | ||
} else { | ||
$this->currentRoutes = $this->provider->getRoutesPaged($offset, $this->routesBatchSize); | ||
} | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function current() | ||
{ | ||
return current($this->currentRoutes); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function next() | ||
{ | ||
$result = next($this->currentRoutes); | ||
if (false === $result) { | ||
$this->loadNextElements($this->current + 1); | ||
} | ||
$this->current++; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function key() | ||
{ | ||
return key($this->currentRoutes); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function valid() | ||
{ | ||
return key($this->currentRoutes); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function rewind() | ||
{ | ||
$this->current = 0; | ||
$this->currentRoutes = NULL; | ||
$this->loadNextElements($this->current); | ||
} | ||
|
||
/** | ||
* Gets the number of Routes in this collection. | ||
* | ||
* @return int The number of routes | ||
*/ | ||
public function count() | ||
{ | ||
return $this->provider->getRoutesCount(); | ||
} | ||
} |
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,42 @@ | ||
<?php | ||
|
||
/** | ||
* This file is part of the Symfony CMF package. | ||
* | ||
* (c) 2011-2014 Symfony CMF | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Cmf\Component\Routing; | ||
|
||
/** | ||
* Interface for a provider which allows to retrieve a limited amount of routes. | ||
*/ | ||
interface PagedRouteProviderInterface extends RouteProviderInterface | ||
{ | ||
/** | ||
* Find an amount of routes with an offset and possible a limit. | ||
* | ||
* In case you want to iterate over all routes, you want to avoid to load | ||
* all routes at once. | ||
* | ||
* @param int $offset | ||
* The sequence will start with that offset in the list of all routes. | ||
* @param int $length [optional] | ||
* The sequence will have that many routes in it. If no length is | ||
* specified all routes are returned. | ||
* | ||
* @return \Symfony\Component\Routing\Route[] | ||
* Routes keyed by the route name. | ||
*/ | ||
public function getRoutesPaged($offset, $length = null); | ||
|
||
/** | ||
* Determines the total amount of routes. | ||
* | ||
* @return int | ||
*/ | ||
public function getRoutesCount(); | ||
} |
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,42 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony CMF package. | ||
* | ||
* (c) 2011-2014 Symfony CMF | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Cmf\Component\Routing; | ||
|
||
use Symfony\Cmf\Component\Routing\Test\CmfUnitTestCase; | ||
use Symfony\Component\Routing\Route; | ||
|
||
/** | ||
* Tests the lazy route collection. | ||
* | ||
* @group cmf/routing | ||
*/ | ||
class LazyRouteCollectionTest extends CmfUnitTestCase | ||
{ | ||
/** | ||
* Tests the iterator without a paged route provider. | ||
*/ | ||
public function testGetIterator() | ||
{ | ||
$routeProvider = $this->getMock('Symfony\Cmf\Component\Routing\RouteProviderInterface'); | ||
$testRoutes = array( | ||
'route_1' => new Route('/route-1'), | ||
'route_2"' => new Route('/route-2'), | ||
); | ||
$routeProvider->expects($this->exactly(2)) | ||
->method('getRoutesByNames') | ||
->with(null) | ||
->will($this->returnValue($testRoutes)); | ||
$lazyRouteCollection = new LazyRouteCollection($routeProvider); | ||
$this->assertEquals($testRoutes, iterator_to_array($lazyRouteCollection->getIterator())); | ||
$this->assertEquals($testRoutes, $lazyRouteCollection->all()); | ||
} | ||
} |
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,132 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony CMF package. | ||
* | ||
* (c) 2011-2014 Symfony CMF | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Cmf\Component\Routing; | ||
|
||
use Symfony\Cmf\Component\Routing\Test\CmfUnitTestCase; | ||
use Symfony\Component\Routing\Route; | ||
|
||
/** | ||
* Tests the page route collection. | ||
* | ||
* @group cmf/routing | ||
*/ | ||
class PagedRouteCollectionTest extends CmfUnitTestCase | ||
{ | ||
/** | ||
* Contains a mocked route provider. | ||
* | ||
* @var \Symfony\Cmf\Component\Routing\PagedRouteProviderInterface|\PHPUnit_Framework_MockObject_MockObject | ||
*/ | ||
protected $routeProvider; | ||
|
||
protected function setUp() | ||
{ | ||
$this->routeProvider = $this->getMock('Symfony\Cmf\Component\Routing\PagedRouteProviderInterface'); | ||
} | ||
|
||
/** | ||
* Tests iterating a small amount of routes. | ||
* | ||
* @dataProvider providerIterator | ||
*/ | ||
public function testIterator($amountRoutes, $routesLoadedInParallel, $expectedCalls = array()) | ||
{ | ||
$routes = array(); | ||
for ($i = 0; $i < $amountRoutes; $i++) { | ||
$routes['test_' . $i] = new Route("/example-$i"); | ||
} | ||
$names = array_keys($routes); | ||
|
||
foreach ($expectedCalls as $i => $range) | ||
{ | ||
$this->routeProvider->expects($this->at($i)) | ||
->method('getRoutesPaged') | ||
->with($range[0], $range[1]) | ||
->will($this->returnValue(array_slice($routes, $range[0], $range[1]))); | ||
} | ||
|
||
$route_collection = new PagedRouteCollection($this->routeProvider, $routesLoadedInParallel); | ||
|
||
$counter = 0; | ||
foreach ($route_collection as $route_name => $route) { | ||
// Ensure the route did not changed. | ||
$this->assertEquals($routes[$route_name], $route); | ||
// Ensure that the order did not changed. | ||
$this->assertEquals($route_name, $names[$counter]); | ||
$counter++; | ||
} | ||
} | ||
|
||
/** | ||
* Provides test data for testIterator(). | ||
*/ | ||
public function providerIterator() | ||
{ | ||
$data = array(); | ||
// Non total routes. | ||
$data[] = array(0, 20, array(array(0, 20))); | ||
// Less total routes than loaded in parallel. | ||
$data[] = array(10, 20, array(array(0, 20))); | ||
// Exact the same amount of routes then loaded in parallel. | ||
$data[] = array(20, 20, array(array(0, 20), array(20, 20))); | ||
// Less than twice the amount. | ||
$data[] = array(39, 20, array(array(0, 20), array(20, 20))); | ||
// More total routes than loaded in parallel. | ||
$data[] = array(40, 20, array(array(0, 20), array(20, 20), array(40, 20))); | ||
$data[] = array(41, 20, array(array(0, 20), array(20, 20), array(40, 20))); | ||
// why not. | ||
$data[] = array(42, 23, array(array(0, 23), array(23, 23))); | ||
return $data; | ||
} | ||
|
||
/** | ||
* Tests the count() method. | ||
*/ | ||
public function testCount() | ||
{ | ||
$this->routeProvider->expects($this->once()) | ||
->method('getRoutesCount') | ||
->will($this->returnValue(12)); | ||
$routeCollection = new PagedRouteCollection($this->routeProvider); | ||
$this->assertEquals(12, $routeCollection->count()); | ||
} | ||
|
||
/** | ||
* Tests the rewind method once the iterator is at the end. | ||
*/ | ||
public function testIteratingAndRewind() | ||
{ | ||
$routes = array(); | ||
for ($i = 0; $i < 30; $i++) { | ||
$routes['test_' . $i] = new Route("/example-$i"); | ||
} | ||
$this->routeProvider->expects($this->any()) | ||
->method('getRoutesPaged') | ||
->will($this->returnValueMap(array( | ||
array(0, 10, array_slice($routes, 0, 10)), | ||
array(10, 10, array_slice($routes, 9, 10)), | ||
array(20, 10, array()), | ||
))); | ||
|
||
$routeCollection = new PagedRouteCollection($this->routeProvider, 10); | ||
|
||
// Force the iterating process. | ||
$routeCollection->rewind(); | ||
for ($i = 0; $i < 29; $i++) { | ||
$routeCollection->next(); | ||
} | ||
$routeCollection->rewind(); | ||
|
||
$this->assertEquals('test_0', $routeCollection->key()); | ||
$this->assertEquals($routes['test_0'], $routeCollection->current()); | ||
} | ||
} |