Skip to content

Commit

Permalink
Merge pull request #114 from symfony-cmf/dawehner-paged-route-collection
Browse files Browse the repository at this point in the history
paged route collection
  • Loading branch information
lsmith77 committed Sep 4, 2014
2 parents 286f7aa + b4e2655 commit 4f160e2
Show file tree
Hide file tree
Showing 5 changed files with 350 additions and 0 deletions.
8 changes: 8 additions & 0 deletions LazyRouteCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ public function __construct(RouteProviderInterface $provider)
$this->provider = $provider;
}

/**
* {@inheritdoc}
*/
public function getIterator()
{
return new \ArrayIterator($this->all());
}

/**
* Gets the number of Routes in this collection.
*
Expand Down
126 changes: 126 additions & 0 deletions PagedRouteCollection.php
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();
}
}
42 changes: 42 additions & 0 deletions PagedRouteProviderInterface.php
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();
}
42 changes: 42 additions & 0 deletions Tests/Routing/LazyRouteCollectionTest.php
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());
}
}
132 changes: 132 additions & 0 deletions Tests/Routing/PagedRouteCollectionTest.php
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());
}
}

0 comments on commit 4f160e2

Please sign in to comment.