diff --git a/CHANGELOG.md b/CHANGELOG.md index b006b765..e28c8495 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ -## 2.0.0 (2013-XX-XX) +## 2.0.0 beta 1 (2013-XX-XX) +* Introduced extension points in the MenuFactory through `Knp\Menu\Factory\ExtensionInterface` +* [BC break compared to 2.0 alpha 1] The inheritance extension points introduced in alpha1 are + deprecated in favor of extensions and will be removed before the stable release. +* `Knp\Menu\Silex\RouterAwareFactory` is deprecated in favor of `Knp\Menu\Silex\RoutingExtension`. * [BC break] Deprecated the methods `createFromArray` and `createFromNode` in the MenuFactory and removed them from `Knp\Menu\FactoryInterface`. Use `Knp\Menu\Loader\ArrayLoader` and `Knp\Menu\Loader\NodeLoader` instead. @@ -9,7 +13,7 @@ instead. * Made the RouterVoter comaptible with SensioFrameworkExtraBundle param converters * Added the possibility to match routes using a regex on their name in the RouterVoter -* [BC break] Refactored the RouterVoter to make it more flexible +* [BC break compared to 2.0 alpha 1] Refactored the RouterVoter to make it more flexible The way to pass routes in the item extras has changed. Before: @@ -36,10 +40,12 @@ ## 2.0.0 alpha 1 (2013-06-23) +* Added protected methods `buildOptions` and `configureItem` in the MenuFactory as extension point by inheritance * [BC break] Refactored the way to mark items as current ``setCurrentUri``, ``getCurrentUri`` and ``getCurrentItem`` have been removed from the ItemInterface. Determining the current items is now delegated to a matcher, and the default implementation uses voters to apply the matching. Getting the current items can be done thanks to the CurrentItemFilterIterator. +* [BC break] The signature of the CurrentItemFilterIterator constructor changed to accept the item matcher * [BC break] Changed the format of the breadcrumb array Instead of storing the elements with the label as key and the uri as value the array now stores an array of array elements with 3 keys: `label`, `uri` and `item`. diff --git a/src/Knp/Menu/Factory/CoreExtension.php b/src/Knp/Menu/Factory/CoreExtension.php new file mode 100644 index 00000000..9ea1b454 --- /dev/null +++ b/src/Knp/Menu/Factory/CoreExtension.php @@ -0,0 +1,59 @@ + null, + 'label' => null, + 'attributes' => array(), + 'linkAttributes' => array(), + 'childrenAttributes' => array(), + 'labelAttributes' => array(), + 'extras' => array(), + 'current' => null, + 'display' => true, + 'displayChildren' => true, + ), + $options + ); + } + + /** + * Configures the newly created item with the passed options + * + * @param ItemInterface $item + * @param array $options + */ + public function buildItem(ItemInterface $item, array $options) + { + $item + ->setUri($options['uri']) + ->setLabel($options['label']) + ->setAttributes($options['attributes']) + ->setLinkAttributes($options['linkAttributes']) + ->setChildrenAttributes($options['childrenAttributes']) + ->setLabelAttributes($options['labelAttributes']) + ->setExtras($options['extras']) + ->setCurrent($options['current']) + ->setDisplay($options['display']) + ->setDisplayChildren($options['displayChildren']) + ; + } +} diff --git a/src/Knp/Menu/Factory/ExtensionInterface.php b/src/Knp/Menu/Factory/ExtensionInterface.php new file mode 100644 index 00000000..394c0ec4 --- /dev/null +++ b/src/Knp/Menu/Factory/ExtensionInterface.php @@ -0,0 +1,25 @@ +extensions = new \SplPriorityQueue(); + + $this->addExtension(new CoreExtension(), -10); + } + public function createItem($name, array $options = array()) { + // TODO remove this BC layer before releasing 2.0 + $processedOptions = $this->buildOptions($options); + if ($processedOptions !== $options) { + trigger_error(sprintf('Overwriting Knp\Menu\MenuFactory::buildOptions is deprecated. Use a factory extension instead of %s.', get_class($this)), E_USER_DEPRECATED); + + $options = $processedOptions; + } + + foreach (clone $this->extensions as $extension) { + $options = $extension->buildOptions($options); + } + $item = new MenuItem($name, $this); - $options = $this->buildOptions($options); - $this->configureItem($item, $options); + foreach (clone $this->extensions as $extension) { + $extension->buildItem($item, $options); + } + + // TODO remove this BC layer before releasing 2.0 + if (method_exists($this, 'configureItem')) { + trigger_error(sprintf('Overwriting Knp\Menu\MenuFactory::configureItem is deprecated. Use a factory extension instead of %s.', get_class($this)), E_USER_DEPRECATED); + + $this->configureItem($item, $options); + } return $item; } /** - * Builds the full option array used to configure the item. - * - * @param array $options + * Adds a factory extension * - * @return array + * @param ExtensionInterface $extension + * @param integer $priority */ - protected function buildOptions(array $options) + public function addExtension(ExtensionInterface $extension, $priority = 0) { - return array_merge( - array( - 'uri' => null, - 'label' => null, - 'attributes' => array(), - 'linkAttributes' => array(), - 'childrenAttributes' => array(), - 'labelAttributes' => array(), - 'extras' => array(), - 'display' => true, - 'displayChildren' => true, - ), - $options - ); + $this->extensions->insert($extension, $priority); } /** - * Configures the newly created item with the passed options + * Builds the full option array used to configure the item. * - * @param ItemInterface $item - * @param array $options + * @deprecated Use a Knp\Menu\Factory\ExtensionInterface instead + * + * @param array $options + * + * @return array */ - protected function configureItem(ItemInterface $item, array $options) + protected function buildOptions(array $options) { - $item - ->setUri($options['uri']) - ->setLabel($options['label']) - ->setAttributes($options['attributes']) - ->setLinkAttributes($options['linkAttributes']) - ->setChildrenAttributes($options['childrenAttributes']) - ->setLabelAttributes($options['labelAttributes']) - ->setExtras($options['extras']) - ->setDisplay($options['display']) - ->setDisplayChildren($options['displayChildren']) - ; + return $options; } /** diff --git a/src/Knp/Menu/Silex/KnpMenuServiceProvider.php b/src/Knp/Menu/Silex/KnpMenuServiceProvider.php index e12aa55b..e4763f72 100644 --- a/src/Knp/Menu/Silex/KnpMenuServiceProvider.php +++ b/src/Knp/Menu/Silex/KnpMenuServiceProvider.php @@ -18,11 +18,13 @@ class KnpMenuServiceProvider implements ServiceProviderInterface public function register(Application $app) { $app['knp_menu.factory'] = $app->share(function () use ($app) { + $factory = new MenuFactory(); + if (isset($app['url_generator'])) { - return new RouterAwareFactory($app['url_generator']); + $factory->addExtension(new RoutingExtension($app['url_generator'])); } - return new MenuFactory(); + return $factory; }); $app['knp_menu.matcher'] = $app->share(function () use ($app) { diff --git a/src/Knp/Menu/Silex/RouterAwareFactory.php b/src/Knp/Menu/Silex/RouterAwareFactory.php index 2663fba2..e94d244e 100644 --- a/src/Knp/Menu/Silex/RouterAwareFactory.php +++ b/src/Knp/Menu/Silex/RouterAwareFactory.php @@ -7,30 +7,16 @@ /** * Factory able to use the Symfony2 Routing component to build the url + * + * @deprecated Use Knp\Menu\Silex\RoutingExtension instead */ class RouterAwareFactory extends MenuFactory { - protected $generator; - public function __construct(UrlGeneratorInterface $generator) { - $this->generator = $generator; - } - - protected function buildOptions(array $options = array()) - { - if (!empty($options['route'])) { - $params = isset($options['routeParameters']) ? $options['routeParameters'] : array(); - $absolute = isset($options['routeAbsolute']) ? $options['routeAbsolute'] : false; - $options['uri'] = $this->generator->generate($options['route'], $params, $absolute); - - // adding the item route to the extras under the 'routes' key (for the Silex RouteVoter) - $options['extras']['routes'][] = array( - 'route' => $options['route'], - 'parameters' => $params, - ); - } + trigger_error(__CLASS__ . ' is deprecated. Use Knp\Menu\Silex\RoutingExtension instead.', E_USER_DEPRECATED); - return parent::buildOptions($options); + parent::__construct(); + $this->addExtension(new RoutingExtension($generator)); } } diff --git a/src/Knp/Menu/Silex/RoutingExtension.php b/src/Knp/Menu/Silex/RoutingExtension.php new file mode 100644 index 00000000..27eacf3c --- /dev/null +++ b/src/Knp/Menu/Silex/RoutingExtension.php @@ -0,0 +1,41 @@ +generator = $generator; + } + + public function buildOptions(array $options = array()) + { + if (!empty($options['route'])) { + $params = isset($options['routeParameters']) ? $options['routeParameters'] : array(); + $absolute = isset($options['routeAbsolute']) ? $options['routeAbsolute'] : false; + $options['uri'] = $this->generator->generate($options['route'], $params, $absolute); + + // adding the item route to the extras under the 'routes' key (for the Silex RouteVoter) + $options['extras']['routes'][] = array( + 'route' => $options['route'], + 'parameters' => $params, + ); + } + + return $options; + } + + public function buildItem(ItemInterface $item, array $options) + { + } +} diff --git a/tests/Knp/Menu/Tests/MenuFactoryTest.php b/tests/Knp/Menu/Tests/MenuFactoryTest.php index 6fd4be74..3676f6ab 100644 --- a/tests/Knp/Menu/Tests/MenuFactoryTest.php +++ b/tests/Knp/Menu/Tests/MenuFactoryTest.php @@ -6,6 +6,36 @@ class MenuFactoryTest extends \PHPUnit_Framework_TestCase { + public function testExtensions() + { + $factory = new MenuFactory(); + + $extension1 = $this->getMock('Knp\Menu\Factory\ExtensionInterface'); + $extension1->expects($this->once()) + ->method('buildOptions') + ->with(array('foo' => 'bar')) + ->will($this->returnValue(array('uri' => 'foobar'))); + $extension1->expects($this->once()) + ->method('buildItem') + ->with($this->isInstanceOf('Knp\Menu\ItemInterface'), $this->contains('foobar')); + + $factory->addExtension($extension1); + + $extension2 = $this->getMock('Knp\Menu\Factory\ExtensionInterface'); + $extension2->expects($this->once()) + ->method('buildOptions') + ->with(array('foo' => 'baz')) + ->will($this->returnValue(array('foo' => 'bar'))); + $extension1->expects($this->once()) + ->method('buildItem') + ->with($this->isInstanceOf('Knp\Menu\ItemInterface'), $this->contains('foobar')); + + $factory->addExtension($extension2, 10); + + $item = $factory->createItem('test', array('foo' => 'baz')); + $this->assertEquals('foobar', $item->getUri()); + } + public function testCreateItem() { $factory = new MenuFactory(); diff --git a/tests/Knp/Menu/Tests/Silex/KnpMenuServiceProviderTest.php b/tests/Knp/Menu/Tests/Silex/KnpMenuServiceProviderTest.php index 92e9658d..4b106db0 100644 --- a/tests/Knp/Menu/Tests/Silex/KnpMenuServiceProviderTest.php +++ b/tests/Knp/Menu/Tests/Silex/KnpMenuServiceProviderTest.php @@ -27,15 +27,6 @@ public function testFactoryWithoutRouter() $this->assertEquals('Knp\Menu\MenuFactory', get_class($app['knp_menu.factory'])); } - public function testFactoryWithRouter() - { - $app = new Application(); - $app->register(new KnpMenuServiceProvider()); - $app->register(new UrlGeneratorServiceProvider()); - - $this->assertInstanceOf('Knp\Menu\Silex\RouterAwareFactory', $app['knp_menu.factory']); - } - public function testTwigRendererNotRegistered() { $app = new Application(); diff --git a/tests/Knp/Menu/Tests/Silex/RouterAwareFactoryTest.php b/tests/Knp/Menu/Tests/Silex/RouterAwareFactoryTest.php index 1d176023..afd7cf99 100644 --- a/tests/Knp/Menu/Tests/Silex/RouterAwareFactoryTest.php +++ b/tests/Knp/Menu/Tests/Silex/RouterAwareFactoryTest.php @@ -21,46 +21,29 @@ public function testCreateItemWithRoute() ->with('homepage', array(), false) ->will($this->returnValue('/foobar')) ; - $factory = new RouterAwareFactory($generator); - $item = $factory->createItem('test_item', array('uri' => '/hello', 'route' => 'homepage')); - $this->assertEquals('/foobar', $item->getUri()); - } - - public function testCreateItemWithRouteAndParameters() - { - $generator = $this->getMock('Symfony\Component\Routing\Generator\UrlGeneratorInterface'); - $generator->expects($this->once()) - ->method('generate') - ->with('homepage', array('id' => 12), false) - ->will($this->returnValue('/foobar')) - ; - $factory = new RouterAwareFactory($generator); - $item = $factory->createItem('test_item', array('route' => 'homepage', 'routeParameters' => array('id' => 12))); - $this->assertEquals('/foobar', $item->getUri()); - } - public function testCreateItemWithAbsoluteRoute() - { - $generator = $this->getMock('Symfony\Component\Routing\Generator\UrlGeneratorInterface'); - $generator->expects($this->once()) - ->method('generate') - ->with('homepage', array(), true) - ->will($this->returnValue('http://php.net')) - ; - $factory = new RouterAwareFactory($generator); - $item = $factory->createItem('test_item', array('route' => 'homepage', 'routeAbsolute' => true)); - $this->assertEquals('http://php.net', $item->getUri()); - } + $deprecatedErrorCatched = false; + set_error_handler(function ($errorNumber, $message, $file, $line) use (&$deprecatedErrorCatched) { + if ($errorNumber & E_USER_DEPRECATED) { + $deprecatedErrorCatched = true; + return true; + } + + return \PHPUnit_Util_ErrorHandler::handleError($errorNumber, $message, $file, $line); + }); + + try { + $factory = new RouterAwareFactory($generator); + } catch (\Exception $e) { + restore_error_handler(); + throw $e; + } - public function testCreateItemAppendsRouteUnderExtras() - { - $generator = $this->getMock('Symfony\Component\Routing\Generator\UrlGeneratorInterface'); - $factory = new RouterAwareFactory($generator); + restore_error_handler(); - $item = $factory->createItem('test_item', array('route' => 'homepage')); - $this->assertEquals(array(array('route' => 'homepage', 'parameters' => array())), $item->getExtra('routes')); + $this->assertTrue($deprecatedErrorCatched, 'The RouterAwareFactory throws a E_USER_DEPRECATED when instantiating it.'); - $item = $factory->createItem('test_item', array('route' => 'homepage', 'extras' => array('routes' => array('other_page')))); - $this->assertContains(array('route' => 'homepage', 'parameters' => array()), $item->getExtra('routes')); + $item = $factory->createItem('test_item', array('uri' => '/hello', 'route' => 'homepage')); + $this->assertEquals('/foobar', $item->getUri()); } } diff --git a/tests/Knp/Menu/Tests/Silex/RoutingExtensionTest.php b/tests/Knp/Menu/Tests/Silex/RoutingExtensionTest.php new file mode 100644 index 00000000..a0ade16a --- /dev/null +++ b/tests/Knp/Menu/Tests/Silex/RoutingExtensionTest.php @@ -0,0 +1,82 @@ +markTestSkipped('The Symfony2 Routing component is not available'); + } + } + + public function testCreateItemWithRoute() + { + $generator = $this->getMock('Symfony\Component\Routing\Generator\UrlGeneratorInterface'); + $generator->expects($this->once()) + ->method('generate') + ->with('homepage', array(), false) + ->will($this->returnValue('/foobar')) + ; + + $extension = new RoutingExtension($generator); + + $processedOptions = $extension->buildOptions(array('uri' => '/hello', 'route' => 'homepage', 'label' => 'foo')); + + $this->assertEquals('/foobar', $processedOptions['uri']); + $this->assertEquals('foo', $processedOptions['label']); + } + + public function testCreateItemWithRouteAndParameters() + { + $generator = $this->getMock('Symfony\Component\Routing\Generator\UrlGeneratorInterface'); + $generator->expects($this->once()) + ->method('generate') + ->with('homepage', array('id' => 12), false) + ->will($this->returnValue('/foobar')) + ; + + $extension = new RoutingExtension($generator); + + $processedOptions = $extension->buildOptions(array('route' => 'homepage', 'routeParameters' => array('id' => 12))); + + $this->assertEquals('/foobar', $processedOptions['uri']); + } + + public function testCreateItemWithAbsoluteRoute() + { + $generator = $this->getMock('Symfony\Component\Routing\Generator\UrlGeneratorInterface'); + $generator->expects($this->once()) + ->method('generate') + ->with('homepage', array(), true) + ->will($this->returnValue('http://php.net')) + ; + + $extension = new RoutingExtension($generator); + + $processedOptions = $extension->buildOptions(array('route' => 'homepage', 'routeAbsolute' => true)); + + $this->assertEquals('http://php.net', $processedOptions['uri']); + } + + public function testCreateItemAppendsRouteUnderExtras() + { + $generator = $this->getMock('Symfony\Component\Routing\Generator\UrlGeneratorInterface'); + + $extension = new RoutingExtension($generator); + + $processedOptions = $extension->buildOptions( array('route' => 'homepage')); + $this->assertEquals(array(array('route' => 'homepage', 'parameters' => array())), $processedOptions['extras']['routes']); + + $processedOptions = $extension->buildOptions( array('route' => 'homepage', 'routeParameters' => array('bar' => 'baz'))); + $this->assertEquals(array(array('route' => 'homepage', 'parameters' => array('bar' => 'baz'))), $processedOptions['extras']['routes']); + + $processedOptions = $extension->buildOptions( array('route' => 'homepage', 'extras' => array('routes' => array('other_page')))); + $this->assertContains(array('route' => 'homepage', 'parameters' => array()), $processedOptions['extras']['routes']); + $this->assertContains('other_page', $processedOptions['extras']['routes']); + } + +}