diff --git a/Admin/RedirectRouteAdmin.php b/Admin/RedirectRouteAdmin.php index c41a32bb..a5b37839 100644 --- a/Admin/RedirectRouteAdmin.php +++ b/Admin/RedirectRouteAdmin.php @@ -9,7 +9,6 @@ * file that was distributed with this source code. */ - namespace Symfony\Cmf\Bundle\RoutingBundle\Admin; use Sonata\AdminBundle\Datagrid\DatagridMapper; @@ -31,7 +30,7 @@ class RedirectRouteAdmin extends Admin protected function configureListFields(ListMapper $listMapper) { $listMapper - ->addIdentifier('id', 'text') + ->addIdentifier('path', 'text') ; } @@ -44,14 +43,15 @@ protected function configureFormFields(FormMapper $formMapper) ->add('routeName', 'text', array('required' => false)) ->add('uri', 'text', array('required' => false)) ->add('routeTarget', 'doctrine_phpcr_odm_tree', array('choice_list' => array(), 'required' => false, 'root_node' => $this->routeRoot)) - ->end(); + ->end() + ; } protected function configureDatagridFilters(DatagridMapper $datagridMapper) { $datagridMapper ->add('name', 'doctrine_phpcr_nodename') - ; + ; } public function setRouteRoot($routeRoot) @@ -69,6 +69,6 @@ public function toString($object) return $object instanceof Route && $object->getId() ? $object->getId() : $this->trans('link_add', array(), 'SonataAdminBundle') - ; + ; } } diff --git a/Admin/RouteAdmin.php b/Admin/RouteAdmin.php index e664b59b..6d5dd49e 100644 --- a/Admin/RouteAdmin.php +++ b/Admin/RouteAdmin.php @@ -66,16 +66,18 @@ protected function configureFormFields(FormMapper $formMapper) array('choice_list' => array(), 'select_root_node' => true, 'root_node' => $this->routeRoot) ) ->add('name', 'text') - ->add('addFormatPattern', null, array('required' => false, 'help' => 'form.help_add_format_pattern')) - ->add('addTrailingSlash', null, array('required' => false, 'help' => 'form.help_add_trailing_slash')) ->end(); if (null === $this->getParentFieldDescription()) { $formMapper ->with('form.group_general') - ->add('variablePattern', 'text', array('required' => false)) - ->add('content', 'doctrine_phpcr_odm_tree', array('choice_list' => array(), 'required' => false, 'root_node' => $this->contentRoot)) - ->add('defaults', 'sonata_type_immutable_array', array('keys' => $this->configureFieldsForDefaults())) + ->add('content', 'doctrine_phpcr_odm_tree', array('choice_list' => array(), 'required' => false, 'root_node' => $this->contentRoot)) + ->end() + ->with('form.group_advanced') + ->add('variablePattern', 'text', array('required' => false), array('help' => 'form.help_variable_pattern')) + ->add('defaults', 'sonata_type_immutable_array', array('keys' => $this->configureFieldsForDefaults())) + ->add('options', 'sonata_type_immutable_array', array('keys' => $this->configureFieldsForOptions()), array('help' => 'form.help_options')) + ->end() ->end(); } } @@ -83,7 +85,7 @@ protected function configureFormFields(FormMapper $formMapper) protected function configureDatagridFilters(DatagridMapper $datagridMapper) { $datagridMapper - ->add('name', 'doctrine_phpcr_string'); + ->add('name', 'doctrine_phpcr_nodename'); } public function setRouteRoot($routeRoot) @@ -110,7 +112,7 @@ public function getExportFormats() protected function configureFieldsForDefaults() { - $defaults = array( + $defaults = array( '_controller' => array('_controller', 'text', array('required' => false)), '_template' => array('_template', 'text', array('required' => false)), 'type' => array('type', 'cmf_routing_route_type', array( @@ -125,7 +127,7 @@ protected function configureFieldsForDefaults() $defaults[$name] = array($name, 'text', array('required' => false)); } } - + //parse variable pattern and add defaults for it - taken from routecompiler /** @var $route Route */ $route = $this->subject; @@ -142,6 +144,24 @@ protected function configureFieldsForDefaults() return $defaults; } + protected function configureFieldsForOptions() + { + $options = array( + array('add_locale_pattern', 'checkbox', array('required' => false, 'label' => 'form.label_add_locale_pattern', 'translation_domain' => $this->translationDomain)), + array('add_format_pattern', 'checkbox', array('required' => false, 'label' => 'form.label_add_format_pattern', 'translation_domain' => $this->translationDomain)), + array('add_trailing_slash', 'checkbox', array('required' => false, 'label' => 'form.label_add_trailing_slash', 'translation_domain' => $this->translationDomain)), + ); + + $dynamicOptions = $this->getSubject()->getOptions(); + foreach ($dynamicOptions as $name => $value) { + if (!isset($options[$name])) { + $options[$name] = array($name, 'text', array('required' => false)); + } + } + + return $options; + } + public function prePersist($object) { $defaults = array_filter($object->getDefaults()); diff --git a/CHANGELOG.md b/CHANGELOG.md index 3773a8c0..d942f6ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ Changelog ========= +* **2014-03-29**: [Multilang] Added some options to support locale matching + without separate routes per locale. See the new configuration options + `match_implicit_locale` and `auto_locale_pattern`. + +* **2014-03-29**: [PHPCR] The route provider can now load Routes from more than + path in PHPCR. The configuration option `route_basepath` is renamed to + `route_basepaths` and accepts a list of base paths. See the changelog of + the SimpleCmsBundle as the main impact is on that side. + +* **2014-03-26**: [ORM] Applied the cleanup for the PHPCR provider to the ORM + provider now: If the route matched a pattern with a format extension, the + format extension is no longer set as route a default. + * **2014-03-25**: setParent() and getParent() are now deprecated. Use setParentDocument() and getParentDocument() instead. Moreover, you should now enable the ChildExtension from the CoreBundle. @@ -8,8 +21,8 @@ Changelog * **2014-03-23**: When using PHPCR-ODM, routes can now be generated with their uuid as route name as well, in addition to the repository path. -* **2013-11-28**: [BC BREAK] the alias attribute of the is - renamed to class in the bundle configuration. +* **2013-11-28**: [BC BREAK for xml configuration] the alias attribute of the + is renamed to class in the bundle configuration. 1.1.0 ----- diff --git a/DependencyInjection/CmfRoutingExtension.php b/DependencyInjection/CmfRoutingExtension.php index 65e6ebdd..2390244d 100644 --- a/DependencyInjection/CmfRoutingExtension.php +++ b/DependencyInjection/CmfRoutingExtension.php @@ -95,10 +95,12 @@ private function setupDynamicRouter(array $config, ContainerBuilder $container, $container->setParameter($this->getAlias() . '.route_collection_limit', $config['route_collection_limit']); $locales = false; - if (isset($config['locales'])) { + if (!empty($config['locales'])) { $locales = $config['locales']; $container->setParameter($this->getAlias() . '.dynamic.locales', $locales); + $container->setParameter($this->getAlias() . '.dynamic.auto_locale_pattern', $config['auto_locale_pattern']); } + $container->setParameter($this->getAlias() . '.dynamic.limit_candidates', $config['limit_candidates']); $loader->load('routing-dynamic.xml'); @@ -109,13 +111,13 @@ private function setupDynamicRouter(array $config, ContainerBuilder $container, } if ($config['persistence']['phpcr']['enabled']) { - $this->loadPhpcrProvider($config['persistence']['phpcr'], $loader, $container, $locales); + $this->loadPhpcrProvider($config['persistence']['phpcr'], $loader, $container, $locales, $config['match_implicit_locale']); $hasProvider = true; $hasContentRepository = true; } if ($config['persistence']['orm']['enabled']) { - $this->loadOrmProvider($config['persistence']['orm'], $loader, $container); + $this->loadOrmProvider($config['persistence']['orm'], $loader, $container, $locales, $config['match_implicit_locale']); $hasProvider = true; } @@ -226,22 +228,41 @@ private function setupDynamicRouter(array $config, ContainerBuilder $container, } } - public function loadPhpcrProvider($config, XmlFileLoader $loader, ContainerBuilder $container, $locales) + public function loadPhpcrProvider($config, XmlFileLoader $loader, ContainerBuilder $container, $locales, $matchImplicitLocale) { $loader->load('provider-phpcr.xml'); $container->setParameter($this->getAlias() . '.backend_type_phpcr', true); - $container->setParameter($this->getAlias() . '.dynamic.persistence.phpcr.route_basepath', $config['route_basepath']); - $container->setParameter($this->getAlias() . '.dynamic.persistence.phpcr.content_basepath', $config['content_basepath']); - - $container->setParameter($this->getAlias() . '.dynamic.persistence.phpcr.manager_name', $config['manager_name']); - - $container->setAlias($this->getAlias() . '.route_provider', $this->getAlias() . '.phpcr_route_provider'); - $container->setAlias($this->getAlias() . '.content_repository', $this->getAlias() . '.phpcr_content_repository'); + $container->setParameter( + $this->getAlias() . '.dynamic.persistence.phpcr.route_basepaths', + $config['route_basepaths'] + ); + $container->setParameter( + $this->getAlias() . '.dynamic.persistence.phpcr.content_basepath', + $config['content_basepath'] + ); + + $container->setParameter( + $this->getAlias() . '.dynamic.persistence.phpcr.manager_name', + $config['manager_name'] + ); + + $container->setAlias( + $this->getAlias() . '.route_provider', + $this->getAlias() . '.phpcr_route_provider' + ); + $container->setAlias( + $this->getAlias() . '.content_repository', + $this->getAlias() . '.phpcr_content_repository' + ); if (!$locales) { $container->removeDefinition($this->getAlias() . '.phpcrodm_route_locale_listener'); + } elseif (!$matchImplicitLocale) { + // remove all but the prefixes configuration from the service definition. + $definition = $container->getDefinition($this->getAlias() . '.phpcr_candidates_prefix'); + $definition->setArguments(array($definition->getArgument(0))); } if ($config['use_sonata_admin']) { @@ -256,14 +277,23 @@ public function loadSonataPhpcrAdmin($config, XmlFileLoader $loader, ContainerBu return; } + $basePath = empty($config['admin_basepath']) ? reset($config['route_basepaths']) : $config['admin_basepath']; + $container->setParameter($this->getAlias() . '.dynamic.persistence.phpcr.admin_basepath', $basePath); + $loader->load('admin-phpcr.xml'); } - public function loadOrmProvider($config, XmlFileLoader $loader, ContainerBuilder $container) + public function loadOrmProvider($config, XmlFileLoader $loader, ContainerBuilder $container, $matchImplicitLocale) { $container->setParameter($this->getAlias() . '.dynamic.persistence.orm.manager_name', $config['manager_name']); $container->setParameter($this->getAlias() . '.backend_type_orm', true); $loader->load('provider-orm.xml'); + + if (!$matchImplicitLocale) { + // remove the locales argument from the candidates + $definition = $container->getDefinition($this->getAlias() . '.orm_candidates'); + $definition->setArguments(array()); + } } /** diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 3d1dd75c..b90c5d93 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -77,9 +77,18 @@ public function getConfigTreeBuilder() ->arrayNode('phpcr') ->addDefaultsIfNotSet() ->canBeEnabled() + ->fixXmlConfig('route_basepath') + ->validate() + ->ifTrue(function ($v) { isset($v['route_basepath']) && isset($v['route_basepaths']); }) + ->thenInvalid('Found values for both "route_basepath" and "route_basepaths", use "route_basepaths" instead.') + ->end() ->children() ->scalarNode('manager_name')->defaultNull()->end() - ->scalarNode('route_basepath')->defaultValue('/cms/routes')->end() + ->arrayNode('route_basepaths') + ->prototype('scalar')->end() + ->defaultValue(array('/cms/routes')) + ->end() + ->scalarNode('admin_basepath')->end() ->scalarNode('content_basepath')->defaultValue('/cms/content')->end() ->enumNode('use_sonata_admin') ->beforeNormalization() @@ -122,6 +131,9 @@ public function getConfigTreeBuilder() ->arrayNode('locales') ->prototype('scalar')->end() ->end() + ->scalarNode('limit_candidates')->defaultValue(20)->end() + ->booleanNode('match_implicit_locale')->defaultValue(true)->end() + ->booleanNode('auto_locale_pattern')->defaultValue(false)->end() ->end() ->end() ->end() diff --git a/Doctrine/DoctrineProvider.php b/Doctrine/DoctrineProvider.php index 85c7f1ab..72e058ca 100644 --- a/Doctrine/DoctrineProvider.php +++ b/Doctrine/DoctrineProvider.php @@ -73,8 +73,9 @@ public function setManagerName($managerName) } /** - * Set the limit to apply when calling getRoutesByNames() with null. - * Note that setting the limit to null means no limit applied. + * Set the limit to apply when calling getAllRoutes(). + * + * Setting the limit to null means no limit is applied. * * @param integer|null $routeCollectionLimit */ diff --git a/Doctrine/Orm/RouteProvider.php b/Doctrine/Orm/RouteProvider.php index 912ff707..2b365e4e 100644 --- a/Doctrine/Orm/RouteProvider.php +++ b/Doctrine/Orm/RouteProvider.php @@ -12,6 +12,9 @@ namespace Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Orm; +use Doctrine\Common\Persistence\ManagerRegistry; +use Doctrine\Common\Persistence\ObjectRepository; +use Symfony\Cmf\Component\Routing\Candidates\CandidatesInterface; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Exception\RouteNotFoundException; @@ -32,29 +35,34 @@ class RouteProvider extends DoctrineProvider implements RouteProviderInterface { /** - * @param $url - * - * @return array + * @var CandidatesInterface */ - protected function getCandidates($url) + private $candidatesStrategy; + + public function __construct(ManagerRegistry $managerRegistry, CandidatesInterface $candidatesStrategy, $className) { - $candidates = array(); - if ('/' !== $url) { - if (preg_match('/(.+)\.[a-z]+$/i', $url, $matches)) { - $candidates[] = $url; - $url = $matches[1]; - } + parent::__construct($managerRegistry, $className); + $this->candidatesStrategy = $candidatesStrategy; + } - $part = $url; - while (false !== ($pos = strrpos($part, '/'))) { - $candidates[] = $part; - $part = substr($url, 0, $pos); - } - } + /** + * {@inheritDoc} + */ + public function getRouteCollectionForRequest(Request $request) + { + $collection = new RouteCollection(); - $candidates[] = '/'; + $candidates = $this->candidatesStrategy->getCandidates($request); + if (empty($candidates)) { + return $collection; + } + $routes = $this->getRouteRepository()->findByStaticPrefix($candidates, array('position' => 'ASC')); + /** @var $route Route */ + foreach ($routes as $route) { + $collection->add($route->getName(), $route); + } - return $candidates; + return $collection; } /** @@ -62,7 +70,11 @@ protected function getCandidates($url) */ public function getRouteByName($name) { - $route = $this->getRoutesRepository()->findOneBy(array('name' => $name)); + if (!$this->candidatesStrategy->isCandidate($name)) { + throw new RouteNotFoundException(sprintf('Route "%s" is not handled by this route provider', $name)); + } + + $route = $this->getRouteRepository()->findOneBy(array('name' => $name)); if (!$route) { throw new RouteNotFoundException("No route found for name '$name'"); } @@ -76,21 +88,18 @@ public function getRouteByName($name) public function getRoutesByNames($names = null) { if (null === $names) { - $names = array(); if (0 === $this->routeCollectionLimit) { - return $names; - } - if (null !== $this->routeCollectionLimit) { - return $this->getRoutesRepository()->findBy(array(), null, $this->routeCollectionLimit); + return array(); } - return $this->getRoutesRepository()->findAll(); + return $this->getRouteRepository()->findBy(array(), null, $this->routeCollectionLimit); } $routes = array(); foreach ($names as $name) { + // TODO: if we do findByName with multivalue, we need to filter with isCandidate afterwards try { - $routes[] = $this->getRouteByName($name, $parameters); + $routes[] = $this->getRouteByName($name); } catch (RouteNotFoundException $e) { // not found } @@ -100,39 +109,9 @@ public function getRoutesByNames($names = null) } /** - * {@inheritDoc} - */ - public function getRouteCollectionForRequest(Request $request) - { - $url = $request->getPathInfo(); - - $candidates = $this->getCandidates($url); - - $collection = new RouteCollection(); - - if (empty($candidates)) { - return $collection; - } - - $routes = $this->getRoutesRepository()->findByStaticPrefix($candidates, array('position' => 'ASC')); - foreach ($routes as $key => $route) { - if (preg_match('/.+\.([a-z]+)$/i', $url, $matches)) { - if ($route->getDefault('_format') === $matches[1]) { - continue; - } - - $route->setDefault('_format', $matches[1]); - } - $collection->add($key, $route); - } - - return $collection; - } - - /** - * @return \Doctrine\Common\Persistence\ObjectRepository + * @return ObjectRepository */ - protected function getRoutesRepository() + protected function getRouteRepository() { return $this->getObjectManager()->getRepository($this->className); } diff --git a/Doctrine/Phpcr/IdPrefixListener.php b/Doctrine/Phpcr/IdPrefixListener.php index b2d7dd4a..75fae3aa 100644 --- a/Doctrine/Phpcr/IdPrefixListener.php +++ b/Doctrine/Phpcr/IdPrefixListener.php @@ -15,30 +15,41 @@ use Doctrine\Common\Persistence\Event\LifecycleEventArgs; /** - * Doctrine PHPCR-ODM listener to set the idPrefix on routes + * Doctrine PHPCR-ODM listener to tell routes what part of their id is the URL. + * + * This listener knows about the RouteProvider and uses its prefixes to + * identify routes that could need the prefix. In case prefixes overlap, the + * order matters as the first matching prefix is taken. * * @author David Buchmann */ class IdPrefixListener { /** - * The prefix to add to the url to create the repository path + * Used to ask for the possible prefixes to remove from the repository ID + * to create the URL. * - * @var string + * @var PrefixCandidates */ - protected $idPrefix = ''; + protected $candidates; - public function __construct($prefix) + /** + * This listener only makes sense together with the PrefixCandidates + * strategy. + * + * @param PrefixCandidates $candidates + */ + public function __construct(PrefixCandidates $candidates) { - $this->idPrefix = $prefix; + $this->candidates = $candidates; } /** - * @param $prefix + * @return array */ - public function setPrefix($prefix) + protected function getPrefixes() { - $this->idPrefix = $prefix; + return $this->candidates->getPrefixes(); } public function postLoad(LifecycleEventArgs $args) @@ -51,16 +62,24 @@ public function postPersist(LifecycleEventArgs $args) $this->updateId($args); } + public function postMove(LifecycleEventArgs $args) + { + $this->updateId($args); + } + protected function updateId(LifecycleEventArgs $args) { $doc = $args->getObject(); // only update route objects and only if the prefix can match, to allow // for more than one listener and more than one route root - if (($doc instanceof PrefixInterface) - && ! strncmp($this->idPrefix, $doc->getId(), strlen($this->idPrefix)) - ) { - $doc->setPrefix($this->idPrefix); + if ($doc instanceof PrefixInterface) { + foreach ($this->getPrefixes() as $prefix) { + if (! strncmp($prefix, $doc->getId(), strlen($prefix))) { + $doc->setPrefix($prefix); + break; + } + } } } } diff --git a/Doctrine/Phpcr/LocaleListener.php b/Doctrine/Phpcr/LocaleListener.php index bea164ed..b9907009 100644 --- a/Doctrine/Phpcr/LocaleListener.php +++ b/Doctrine/Phpcr/LocaleListener.php @@ -12,11 +12,11 @@ namespace Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr; +use Doctrine\Common\Persistence\ManagerRegistry; +use Doctrine\ODM\PHPCR\DocumentManager; use Doctrine\ODM\PHPCR\Event\MoveEventArgs; use Doctrine\Common\Persistence\Event\LifecycleEventArgs; -use Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route; - /** * Doctrine PHPCR-ODM listener to update the locale on routes based on the URL. * @@ -29,11 +29,12 @@ class LocaleListener { /** - * The prefix to add to the url to create the repository path + * Used to ask for the possible prefixes to determine the possible locale + * segment of the id. * - * @var string + * @var RouteProvider */ - protected $idPrefix = ''; + protected $candidates; /** * List of possible locales to detect on URL after idPrefix @@ -42,20 +43,47 @@ class LocaleListener */ protected $locales; - public function __construct($prefix, array $locales) - { - $this->idPrefix = $prefix; - $this->locales = $locales; - } + /** + * Whether to enforce the route option add_locale_pattern. + * + * @var bool + */ + protected $addLocalePattern; /** - * The repository path prefix where routes handled by this listener are located. + * Whether to update the _locales requirement based on available + * translations of the document. + * + * This is only done for routes that are translated documents at the same + * time, and only if their path does not start with one of the available + * locales. An example are the CmfSimpleCmsBundle Page documents. * - * @param $prefix + * @var bool */ - public function setPrefix($prefix) - { - $this->idPrefix = $prefix; + protected $updateAvailableTranslations; + + /** + * This listener is built to work with the prefix candidates strategy. + * + * @param PrefixCandidates $candidates To get prefixes from. + * @param array $locales Locales that should be detected. + * @param bool $addLocalePattern Whether to enforce the add_locale_pattern + * option if the route does not have one of + * the allowed locales in its id. + * @param bool $updateAvailableTranslations Whether to update the route document with + * its available translations if it does not + * have one of the allowed locales in its id. + */ + public function __construct( + PrefixCandidates $candidates, + array $locales, + $addLocalePattern = false, + $updateAvailableTranslations = false + ) { + $this->candidates = $candidates; + $this->locales = $locales; + $this->setAddLocalePattern($addLocalePattern); + $this->setUpdateAvailableTranslations($updateAvailableTranslations); } /** @@ -68,6 +96,22 @@ public function setLocales(array $locales) $this->locales = $locales; } + /** + * Whether to make the route prepend the locale pattern if it does not + * have one of the allowed locals in its id. + * + * @param boolean $addLocalePattern + */ + public function setAddLocalePattern($addLocalePattern) + { + $this->addLocalePattern = $addLocalePattern; + } + + public function setUpdateAvailableTranslations($update) + { + $this->updateAvailableTranslations = $update; + } + /** * Update locale after loading a route. * @@ -79,7 +123,7 @@ public function postLoad(LifecycleEventArgs $args) if (! $doc instanceof Route) { return; } - $this->updateLocale($doc, $doc->getId()); + $this->updateLocale($doc, $doc->getId(), $args->getObjectManager()); } /** @@ -93,7 +137,7 @@ public function postPersist(LifecycleEventArgs $args) if (! $doc instanceof Route) { return; } - $this->updateLocale($doc, $doc->getId()); + $this->updateLocale($doc, $doc->getId(), $args->getObjectManager()); } /** @@ -107,35 +151,57 @@ public function postMove(MoveEventArgs $args) if (! $doc instanceof Route) { return; } - $this->updateLocale($doc, $args->getTargetPath(), true); + $this->updateLocale($doc, $args->getTargetPath(), $args->getObjectManager(), true); + } + + /** + * @return array + */ + protected function getPrefixes() + { + return $this->candidates->getPrefixes(); } /** * Update the locale of a route if $id starts with the prefix and has a * valid locale right after. * - * @param Route $doc The route object - * @param string $id The id (in move case, this is not the current id - * of $route) - * @param boolean $force Whether to update the locale even if the route - * already has a locale. + * @param Route $doc The route object + * @param string $id The id (in move case, this is not the current + * id of $route). + * @param DocumentManager $dm The document manager to get locales from if + * the setAvailableTranslations option is + * enabled. + * @param boolean $force Whether to update the locale even if the + * route already has a locale. */ - protected function updateLocale(Route $doc, $id, $force = false) + protected function updateLocale(Route $doc, $id, DocumentManager $dm, $force = false) { $matches = array(); - // only update route objects and only if the prefix can match, to allow - // for more than one listener and more than one route root - if (! preg_match('#' . $this->idPrefix . '/([^/]+)(/|$)#', $id, $matches)) { + // only update if the prefix matches, to allow for more than one + // listener and more than one route root. + if (! preg_match('#(' . implode('|', $this->getPrefixes()) . ')/([^/]+)(/|$)#', $id, $matches)) { return; } - if (in_array($matches[1], $this->locales)) { - if ($force || ! $doc->getDefault('_locale')) { - $doc->setDefault('_locale', $matches[1]); + if (in_array($locale = $matches[2], $this->locales)) { + if ($force || !$doc->getDefault('_locale')) { + $doc->setDefault('_locale', $locale); + } + if ($force || !$doc->getRequirement('_locale')) { + $doc->setRequirement('_locale', $locale); + } + } else { + if ($this->addLocalePattern) { + $doc->setOption('add_locale_pattern', true); } - if ($force || ! $doc->getRequirement('_locale')) { - $doc->setRequirement('_locale', $matches[1]); + if ($this->updateAvailableTranslations + && $dm->isDocumentTranslatable($doc) + && !$doc->getRequirement('_locale') + ) { + $locales = $dm->getLocalesFor($doc, true); + $doc->setRequirement('_locale', implode('|', $locales)); } } } diff --git a/Doctrine/Phpcr/PrefixCandidates.php b/Doctrine/Phpcr/PrefixCandidates.php new file mode 100644 index 00000000..bdf56314 --- /dev/null +++ b/Doctrine/Phpcr/PrefixCandidates.php @@ -0,0 +1,186 @@ + + */ +class PrefixCandidates extends Candidates +{ + /** + * Places in the PHPCR tree where routes are located. + * + * @var array + */ + protected $idPrefixes = array(); + + /** + * @var string + */ + protected $managerName; + + /** + * @var ManagerRegistry + */ + protected $doctrine; + + /** + * @param array $prefixes The prefixes to use. If one of them is + * an empty string, the whole repository + * is used for routing. + * @param array $locales Allowed locales. + * @param ManagerRegistry $doctrine Used when the URL matches one of the + * $locales. This must be the same + * document manager as the RouteProvider + * is using. + * @param int $limit Limit to candidates generated per prefix. + */ + public function __construct(array $prefixes, array $locales = array(), ManagerRegistry $doctrine = null, $limit = 20) + { + parent::__construct($locales, $limit); + $this->setPrefixes($prefixes); + $this->doctrine = $doctrine; + } + + /** + * {@inheritDoc} + * + * A name is a candidate if it starts with one of the prefixes + */ + public function isCandidate($name) + { + foreach ($this->getPrefixes() as $prefix) { + // $name is the route document path + if ($name === $prefix || 0 === strpos($name, $prefix . '/')) { + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + * + * @param QueryBuilder $queryBuilder + */ + public function restrictQuery($queryBuilder) + { + $prefixes = $this->getPrefixes(); + if (array_search('', $prefixes) || !count($prefixes)) { + return; + } + + $where = $queryBuilder->andWhere()->orX(); + foreach ($prefixes as $prefix) { + $where->descendant($prefix, $queryBuilder->getPrimaryAlias()); + } + } + + /** + * {@inheritDoc} + */ + public function getCandidates(Request $request) + { + $candidates = array(); + $url = $request->getPathInfo(); + foreach ($this->getPrefixes() as $prefix) { + $candidates = array_unique(array_merge($candidates, $this->getCandidatesFor($url, $prefix))); + } + + $locale = $this->determineLocale($url); + if ($locale) { + $url = substr($url, strlen($locale) + 1); + foreach ($this->getPrefixes() as $prefix) { + $candidates = array_unique(array_merge($candidates, $this->getCandidatesFor($url, $prefix))); + } + } + + return $candidates; + } + + /** + * Set the prefixes handled by this strategy. + * + * @param array $prefixes List of prefixes, possibly including ''. + */ + public function setPrefixes(array $prefixes) + { + $this->idPrefixes = $prefixes; + } + + /** + * Append a prefix to the allowed prefixes. + * + * @param string $prefix A prefix + */ + public function addPrefix($prefix) + { + $this->idPrefixes[] = $prefix; + } + + /** + * Get all currently configured prefixes where to look for routes. + * + * @return array The prefixes. + */ + public function getPrefixes() + { + return $this->idPrefixes; + } + + /** + * Set the doctrine document manager name. + * + * @param string $manager + */ + public function setManagerName($manager) + { + $this->managerName = $manager; + } + + /** + * {@inheritDoc} + * + * The normal phpcr-odm locale listener "waits" until the routing completes + * as the locale is usually defined inside the route. We need to set it + * already in case the route document itself is translated. + * + * For example the CmfSimpleCmsBundle Page documents. + */ + protected function determineLocale($url) + { + $locale = parent::determineLocale($url); + if ($locale && $this->doctrine) { + $this->getDocumentManager()->getLocaleChooserStrategy()->setLocale($locale); + } + + return $locale; + } + + /** + * @return DocumentManager The document manager + */ + protected function getDocumentManager() + { + return $this->doctrine->getManager($this->managerName); + } + +} diff --git a/Doctrine/Phpcr/RedirectRoute.php b/Doctrine/Phpcr/RedirectRoute.php index 2b1edf52..a6c3ce04 100644 --- a/Doctrine/Phpcr/RedirectRoute.php +++ b/Doctrine/Phpcr/RedirectRoute.php @@ -12,9 +12,10 @@ namespace Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr; -use Symfony\Cmf\Bundle\CoreBundle\Model\ChildInterface; +use Doctrine\Common\Collections\Collection; use Symfony\Cmf\Bundle\RoutingBundle\Model\RedirectRoute as RedirectRouteModel; use Symfony\Cmf\Component\Routing\RouteObjectInterface; +use Symfony\Cmf\Bundle\CoreBundle\Model\ChildInterface; /** * {@inheritDoc} @@ -45,13 +46,6 @@ class RedirectRoute extends RedirectRouteModel implements PrefixInterface, Child */ protected $children; - /** - * if to add "/" to the pattern - * - * @var Boolean - */ - protected $addTrailingSlash; - /** * The part of the PHPCR path that does not belong to the url * @@ -62,18 +56,17 @@ class RedirectRoute extends RedirectRouteModel implements PrefixInterface, Child protected $idPrefix; /** - * Overwrite to be able to create route without pattern + * Overwrite to be able to create route without pattern. + * + * Additional options: * - * @param bool $addFormatPattern if to add ".{_format}" to the route pattern - * also implicitly sets a default/require on "_format" to "html" - * @param bool $addTrailingSlash whether to add a trailing slash to the route, defaults to not add one + * * add_trailing_slash: When set, a trailing slash is appended to the route */ - public function __construct($addFormatPattern = false, $addTrailingSlash = false) + public function __construct(array $options = array()) { - parent::__construct($addFormatPattern); + parent::__construct($options); $this->children = array(); - $this->addTrailingSlash = $addTrailingSlash; } /** @@ -225,7 +218,7 @@ public function generateStaticPrefix($id, $idPrefix) public function getPath() { $pattern = parent::getPath(); - if ($this->addTrailingSlash && '/' !== $pattern[strlen($pattern)-1]) { + if ($this->getOption('add_trailing_slash') && '/' !== $pattern[strlen($pattern)-1]) { $pattern .= '/'; }; diff --git a/Doctrine/Phpcr/Route.php b/Doctrine/Phpcr/Route.php index 44eec064..1c859ece 100644 --- a/Doctrine/Phpcr/Route.php +++ b/Doctrine/Phpcr/Route.php @@ -12,6 +12,7 @@ namespace Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ODM\PHPCR\Document\Generic; use Doctrine\ODM\PHPCR\Exception\InvalidArgumentException; @@ -48,13 +49,6 @@ class Route extends RouteModel implements PrefixInterface, ChildInterface */ protected $children; - /** - * if to add "/" to the pattern - * - * @var Boolean - */ - protected $addTrailingSlash; - /** * The part of the PHPCR path that does not belong to the url * @@ -65,28 +59,34 @@ class Route extends RouteModel implements PrefixInterface, ChildInterface protected $idPrefix; /** - * Overwrite to be able to create route without pattern + * PHPCR id can not end on '/', so we need an additional option for a + * trailing slash. + * + * Additional supported option is: * - * @param Boolean $addFormatPattern if to add ".{_format}" to the route pattern - * also implicitly sets a default/require on "_format" to "html" - * @param Boolean $addTrailingSlash whether to add a trailing slash to the route, defaults to not add one + * * add_trailing_slash: When set, a trailing slash is appended to the route */ - public function __construct($addFormatPattern = false, $addTrailingSlash = false) + public function __construct(array $options= array()) { - parent::__construct($addFormatPattern); + parent::__construct($options); - $this->children = array(); - $this->addTrailingSlash = $addTrailingSlash; + $this->children = new ArrayCollection(); } + /** + * @deprecated use getOption('add_trailing_slash') instead + */ public function getAddTrailingSlash() { - return $this->addTrailingSlash; + return $this->getOption('add_trailing_slash'); } + /** + * @deprecated use setOption('add_trailing_slash', $add) instead + */ public function setAddTrailingSlash($addTrailingSlash) { - $this->addTrailingSlash = $addTrailingSlash; + $this->setOption('add_trailing_slash', $addTrailingSlash); } /** @@ -142,6 +142,8 @@ public function getParentDocument() * Note that this will change the URL this route matches. * * @param string $name the new name + * + * @return self */ public function setName($name) { @@ -217,7 +219,10 @@ public function setPrefix($idPrefix) */ public function getStaticPrefix() { - return $this->generateStaticPrefix($this->getId(), $this->idPrefix); + $path = $this->getId(); + $prefix = $this->getPrefix(); + + return $this->generateStaticPrefix($path, $prefix); } /** @@ -248,11 +253,13 @@ public function generateStaticPrefix($id, $idPrefix) /** * {@inheritDoc} + * + * Handle the trailing slash option. */ public function getPath() { $pattern = parent::getPath(); - if ($this->addTrailingSlash && '/' !== $pattern[strlen($pattern)-1]) { + if ($this->getOption('add_trailing_slash') && '/' !== $pattern[strlen($pattern)-1]) { $pattern .= '/'; }; @@ -297,7 +304,7 @@ public function getRouteChildren() /** * Get all children of this route including non-routes. * - * @return array + * @return Collection */ public function getChildren() { diff --git a/Doctrine/Phpcr/RouteProvider.php b/Doctrine/Phpcr/RouteProvider.php index f06b0403..89358009 100644 --- a/Doctrine/Phpcr/RouteProvider.php +++ b/Doctrine/Phpcr/RouteProvider.php @@ -9,29 +9,27 @@ * file that was distributed with this source code. */ - namespace Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr; -use Doctrine\ODM\PHPCR\DocumentManager; - use PHPCR\Query\QueryInterface; -use PHPCR\RepositoryException; - use PHPCR\Query\RowInterface; - +use PHPCR\RepositoryException; use PHPCR\Util\UUIDHelper; +use Doctrine\Common\Persistence\ManagerRegistry; +use Doctrine\ODM\PHPCR\DocumentManager; + use Symfony\Component\Routing\Route as SymfonyRoute; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Exception\RouteNotFoundException; - use Symfony\Component\HttpFoundation\Request; use Symfony\Cmf\Component\Routing\RouteProviderInterface; +use Symfony\Cmf\Component\Routing\Candidates\CandidatesInterface; use Symfony\Cmf\Bundle\RoutingBundle\Doctrine\DoctrineProvider; /** - * Provide routes loaded from PHPCR-ODM + * Loads routes from Doctrine PHPCR-ODM. * * This is NOT not a doctrine repository but just the route * provider for the NestedMatcher. (you could of course implement this @@ -42,18 +40,14 @@ class RouteProvider extends DoctrineProvider implements RouteProviderInterface { /** - * The prefix to add to the url to create the repository path - * - * @var string + * @var CandidatesInterface */ - protected $idPrefix = ''; + private $candidatesStrategy; - /** - * @param $prefix - */ - public function setPrefix($prefix) + public function __construct(ManagerRegistry $managerRegistry, CandidatesInterface $candidatesStrategy, $className = null) { - $this->idPrefix = $prefix; + parent::__construct($managerRegistry, $className); + $this->candidatesStrategy = $candidatesStrategy; } /** @@ -66,8 +60,7 @@ public function setPrefix($prefix) */ public function getRouteCollectionForRequest(Request $request) { - $url = $request->getPathInfo(); - $candidates = $this->getCandidates($url); + $candidates = $this->candidatesStrategy->getCandidates($request); $collection = new RouteCollection(); @@ -96,32 +89,6 @@ public function getRouteCollectionForRequest(Request $request) return $collection; } - /** - * @param $url - * - * @return array - */ - protected function getCandidates($url) - { - $candidates = array(); - if ('/' !== $url) { - if (preg_match('/(.+)\.[a-z]+$/i', $url, $matches)) { - $candidates[] = $this->idPrefix . $url; - $url = $matches[1]; - } - - $part = $url; - while (false !== ($pos = strrpos($part, '/'))) { - $candidates[] = $this->idPrefix . $part; - $part = substr($url, 0, $pos); - } - } - - $candidates[] = $this->idPrefix; - - return $candidates; - } - /** * {@inheritDoc} * @@ -132,12 +99,19 @@ public function getRouteByName($name) if (UUIDHelper::isUUID($name)) { $route = $this->getObjectManager()->find($this->className, $name); if ($route - && '' !== $this->idPrefix - && 0 !== strpos($this->getObjectManager()->getUnitOfWork()->getDocumentId($route), $this->idPrefix) + && !$this->candidatesStrategy->isCandidate($this->getObjectManager()->getUnitOfWork()->getDocumentId($route)) ) { - $route = null; + throw new RouteNotFoundException( + sprintf( + 'Route with uuid "%s" and id "%s" is not handled by this route provider', + $name, + $this->getObjectManager()->getUnitOfWork()->getDocumentId($route) + ) + ); } - } elseif ('' === $this->idPrefix || 0 === strpos($name, $this->idPrefix)) { + } elseif (!$this->candidatesStrategy->isCandidate($name)) { + throw new RouteNotFoundException(sprintf('Route name "%s" is not handled by this route provider', $name)); + } else { $route = $this->getObjectManager()->find($this->className, $name); } @@ -153,11 +127,12 @@ public function getRouteByName($name) } /** - * Get list of route names + * Get all the routes in the repository that are under one of the + * configured prefixes. This respects the limit. * * @return array */ - private function getRouteNames() + private function getAllRoutes() { if (0 === $this->routeCollectionLimit) { return array(); @@ -165,26 +140,18 @@ private function getRouteNames() /** @var $dm DocumentManager */ $dm = $this->getObjectManager(); - $sql2 = 'SELECT * FROM [nt:unstructured] WHERE [phpcr:classparents] = '.$dm->quote('Symfony\Component\Routing\Route'); + $qb = $dm->createQueryBuilder(); - if ('' !== $this->idPrefix) { - $sql2.= ' AND ISDESCENDANTNODE('.$dm->quote($this->idPrefix).')'; - } - - $query = $dm->createPhpcrQuery($sql2, QueryInterface::JCR_SQL2); - if (null !== $this->routeCollectionLimit) { - $query->setLimit($this->routeCollectionLimit); - } + $qb->from('d')->document('Symfony\Component\Routing\Route', 'd'); - $result = $query->execute(); + $this->candidatesStrategy->restrictQuery($qb); - $names = array(); - foreach ($result as $row) { - /** @var $row RowInterface */ - $names[] = $row->getPath(); + $query = $qb->getQuery(); + if (null !== $this->routeCollectionLimit) { + $query->setMaxResults($this->routeCollectionLimit); } - return $names; + return $query->getResult(); } /** @@ -193,27 +160,36 @@ private function getRouteNames() public function getRoutesByNames($names = null) { if (null === $names) { - $names = $this->getRouteNames(); + return $this->getAllRoutes(); } - if ('' !== $this->idPrefix) { - foreach ($names as $key => $name) { - if (!UUIDHelper::isUUID($name) && 0 !== strpos($name, $this->idPrefix)) { - unset($names[$key]); - } + $candidates = array(); + foreach ($names as $key => $name) { + if (UUIDHelper::isUUID($name) || $this->candidatesStrategy->isCandidate($name)) { + $candidates[$key] = $name; } } + if (!$candidates) { + return array(); + } + /** @var $dm DocumentManager */ $dm = $this->getObjectManager(); - $collection = $dm->findMany($this->className, $names); - foreach ($collection as $key => $document) { + $documents = $dm->findMany($this->className, $candidates); + foreach ($documents as $key => $document) { + if (UUIDHelper::isUUID($key) + && !$this->candidatesStrategy->isCandidate($this->getObjectManager()->getUnitOfWork()->getDocumentId($document)) + ) { + // this uuid pointed out of our path. can only determine after fetching the document + unset($documents[$key]); + } if (!$document instanceof SymfonyRoute) { // we follow the logic of DocumentManager::findMany and do not throw an exception - unset($collection[$key]); + unset($documents[$key]); } } - return $collection; + return $documents; } } diff --git a/Model/Route.php b/Model/Route.php index 777b3afe..73522432 100644 --- a/Model/Route.php +++ b/Model/Route.php @@ -53,13 +53,6 @@ class Route extends SymfonyRoute implements RouteObjectInterface */ protected $variablePattern; - /** - * if to add ".{_format}" to the pattern - * - * @var Boolean - */ - protected $addFormatPattern; - /** * Whether this route was changed since being last compiled. * @@ -72,30 +65,40 @@ class Route extends SymfonyRoute implements RouteObjectInterface /** * Overwrite to be able to create route without pattern * - * @param bool $addFormatPattern if to add ".{_format}" to the route pattern - * also implicitly sets a default/require on "_format" to "html" + * Additional supported options are: + * + * * add_format_pattern: When set, ".{_format}" is appended to the route pattern. + * Also implicitly sets a default/require on "_format" to "html". + * * add_locale_pattern: When set, "/{_locale}" is prepended to the route pattern. + * + * @param array $options */ - public function __construct($addFormatPattern = false) + public function __construct(array $options = array()) { $this->setDefaults(array()); $this->setRequirements(array()); - $this->setOptions(array()); + $this->setOptions($options); - $this->addFormatPattern = $addFormatPattern; - if ($this->addFormatPattern) { + if ($this->getOption('add_format_pattern')) { $this->setDefault('_format', 'html'); $this->setRequirement('_format', 'html'); } } + /** + * @deprecated use getOption('add_format_pattern') instead + */ public function getAddFormatPattern() { - return $this->addFormatPattern; + return $this->getOption('add_format_pattern'); } + /** + * @deprecated use setOption('add_format_pattern', $bool) instead + */ public function setAddFormatPattern($addFormatPattern) { - $this->addFormatPattern = $addFormatPattern; + $this->setOption('add_format_pattern', $addFormatPattern); } /** @@ -213,8 +216,13 @@ public function getPattern() */ public function getPath() { - $pattern = $this->getStaticPrefix() . $this->getVariablePattern(); - if ($this->addFormatPattern && !preg_match('/(.+)\.[a-z]+$/i', $pattern, $matches)) { + $pattern = ''; + if ($this->getOption('add_locale_pattern')) { + $pattern .= '/{_locale}'; + } + $pattern .= $this->getStaticPrefix(); + $pattern .= $this->getVariablePattern(); + if ($this->getOption('add_format_pattern') && !preg_match('/(.+)\.[a-z]+$/i', $pattern, $matches)) { $pattern .= '.{_format}'; }; diff --git a/Resources/config/admin-phpcr.xml b/Resources/config/admin-phpcr.xml index a32c80ef..ac23d55f 100644 --- a/Resources/config/admin-phpcr.xml +++ b/Resources/config/admin-phpcr.xml @@ -29,7 +29,7 @@ - %cmf_routing.dynamic.persistence.phpcr.route_basepath% + %cmf_routing.dynamic.persistence.phpcr.admin_basepath% @@ -47,7 +47,7 @@ - %cmf_routing.dynamic.persistence.phpcr.route_basepath% + %cmf_routing.dynamic.persistence.phpcr.admin_basepath% @@ -55,5 +55,13 @@ + + CmfRoutingBundle + + %cmf_routing.dynamic.persistence.phpcr.admin_basepath% + + + + diff --git a/Resources/config/doctrine-model/Route.orm.xml b/Resources/config/doctrine-model/Route.orm.xml index ef3a76d9..cca534fe 100644 --- a/Resources/config/doctrine-model/Route.orm.xml +++ b/Resources/config/doctrine-model/Route.orm.xml @@ -4,7 +4,6 @@ - diff --git a/Resources/config/doctrine-model/Route.phpcr.xml b/Resources/config/doctrine-model/Route.phpcr.xml index 9280b7f1..d007a8da 100644 --- a/Resources/config/doctrine-model/Route.phpcr.xml +++ b/Resources/config/doctrine-model/Route.phpcr.xml @@ -8,7 +8,6 @@ - diff --git a/Resources/config/doctrine-phpcr/RedirectRoute.phpcr.xml b/Resources/config/doctrine-phpcr/RedirectRoute.phpcr.xml index 6a69c312..07f12b07 100644 --- a/Resources/config/doctrine-phpcr/RedirectRoute.phpcr.xml +++ b/Resources/config/doctrine-phpcr/RedirectRoute.phpcr.xml @@ -9,7 +9,6 @@ - diff --git a/Resources/config/doctrine-phpcr/Route.phpcr.xml b/Resources/config/doctrine-phpcr/Route.phpcr.xml index ba49a4dd..c0f8f000 100644 --- a/Resources/config/doctrine-phpcr/Route.phpcr.xml +++ b/Resources/config/doctrine-phpcr/Route.phpcr.xml @@ -10,7 +10,6 @@ - diff --git a/Resources/config/provider-orm.xml b/Resources/config/provider-orm.xml index 0fcb7c56..e15196b0 100644 --- a/Resources/config/provider-orm.xml +++ b/Resources/config/provider-orm.xml @@ -5,6 +5,7 @@ Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Orm\Route + Symfony\Cmf\Component\Routing\Candidates\Candidates Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Orm\RouteProvider Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Orm\ContentRepository @@ -16,8 +17,14 @@ %cmf_routing.dynamic.persistence.orm.manager_name% + + %cmf_routing.dynamic.locales% + %cmf_routing.dynamic.limit_candidates% + + - + + %cmf_routing.route_entity.class% %cmf_routing.dynamic.persistence.orm.manager_name% %cmf_routing.route_collection_limit% diff --git a/Resources/config/provider-phpcr.xml b/Resources/config/provider-phpcr.xml index f884678e..298c4f06 100644 --- a/Resources/config/provider-phpcr.xml +++ b/Resources/config/provider-phpcr.xml @@ -5,6 +5,7 @@ Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\RouteProvider + Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\PrefixCandidates Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\ContentRepository Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\IdPrefixListener Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\LocaleListener @@ -14,38 +15,40 @@ + %cmf_routing.route_model.class% %cmf_routing.dynamic.persistence.phpcr.manager_name% - %cmf_routing.dynamic.persistence.phpcr.route_basepath% %cmf_routing.route_collection_limit% + + %cmf_routing.dynamic.persistence.phpcr.route_basepaths% + %cmf_routing.dynamic.locales% + + %cmf_routing.dynamic.limit_candidates% + %cmf_routing.dynamic.persistence.phpcr.manager_name% + + %cmf_routing.dynamic.persistence.phpcr.manager_name% - %cmf_routing.dynamic.persistence.phpcr.route_basepath% + + - %cmf_routing.dynamic.persistence.phpcr.route_basepath% + %cmf_routing.dynamic.locales% + %cmf_routing.dynamic.auto_locale_pattern% - - CmfRoutingBundle - - %cmf_routing.dynamic.persistence.phpcr.route_basepath% - - - - diff --git a/Resources/config/schema/routing-1.0.xsd b/Resources/config/schema/routing-1.0.xsd index 1d3a55b4..3c4a1482 100644 --- a/Resources/config/schema/routing-1.0.xsd +++ b/Resources/config/schema/routing-1.0.xsd @@ -39,6 +39,8 @@ + + @@ -73,13 +75,24 @@ + + + + + + + + + + + diff --git a/Resources/translations/CmfRoutingBundle.de.xliff b/Resources/translations/CmfRoutingBundle.de.xliff index 8caf9a03..4cb37376 100644 --- a/Resources/translations/CmfRoutingBundle.de.xliff +++ b/Resources/translations/CmfRoutingBundle.de.xliff @@ -58,6 +58,10 @@ form.group_general Allgemein + + form.group_advanced + Details + form.label_parent Übergeordnet @@ -70,6 +74,10 @@ form.label_variable_pattern Variablenmuster + + form.help_variable_pattern + Ein Muster im Format {variable}/{mehr}... Die Felder werden dem Controller übergeben falls er sie deklariert. + form.label_content Inhalt @@ -78,6 +86,10 @@ form.label_defaults Vorgabewerte + + form.label_options + Optionen + form.label_route_name Name @@ -98,6 +110,10 @@ form.label_routes Routen + + form.label_add_locale_pattern + Sprache hinzufügen + form.label_add_format_pattern Formatmuster hinzufügen @@ -107,12 +123,14 @@ Abschließenden Schrägstrich hinzufügen - form.help_add_format_pattern - Format zu URL hinzufügen: /eine/url.{format}. Wenn nichts angegeben wird ist das Format html. - - - form.help_add_trailing_slash - Die URL hört in diesem Fall mit einem Schrägstrich auf, zum Beispiel /eine/url/. + form.help_options + + + Schrägstrich: Die URL hört in diesem Fall mit einem Schrägstrich auf, zum Beispiel /eine/url/ + ]]> + diff --git a/Resources/translations/CmfRoutingBundle.en.xliff b/Resources/translations/CmfRoutingBundle.en.xliff index a77a30d8..a565391f 100644 --- a/Resources/translations/CmfRoutingBundle.en.xliff +++ b/Resources/translations/CmfRoutingBundle.en.xliff @@ -58,6 +58,10 @@ form.group_general General + + form.group_advanced + Advanced + form.label_parent Parent @@ -70,6 +74,10 @@ form.label_variable_pattern Variable pattern + + form.help_variable_pattern + A pattern in the format {variable}/{more}... The fields are passed to the controller if it declares them as arguments. + form.label_content Content @@ -78,6 +86,10 @@ form.label_defaults Defaults + + form.label_options + Options + form.label_route_name Name @@ -98,6 +110,10 @@ form.label_routes Routes + + form.label_add_locale_pattern + Add locale pattern + form.label_add_format_pattern Add format pattern @@ -107,12 +123,14 @@ Add trailing slash - form.help_add_format_pattern - Append format to route like /your/route.{format}. Default format is html. - - - form.help_add_trailing_slash - Append a trailing slash to route like /your/route/. + form.help_options + + + Format: Append format to route like /your/route.{format}. Default format is 'html'
+ Slash: Append a trailing slash to route like /your/route/ + ]]> +
diff --git a/Resources/translations/CmfRoutingBundle.fr.xliff b/Resources/translations/CmfRoutingBundle.fr.xliff index 273afa34..57ed3fb7 100644 --- a/Resources/translations/CmfRoutingBundle.fr.xliff +++ b/Resources/translations/CmfRoutingBundle.fr.xliff @@ -58,6 +58,10 @@ form.group_general Général + + form.group_advanced + Avancé + form.label_parent Parent @@ -70,6 +74,10 @@ form.label_variable_pattern Motif variable + + form.help_variable_pattern + Un motif en format {variable}/{plus}... Les variables sont passé au controlleur s'il les déclare comme arguments. + form.label_content Contenu @@ -78,6 +86,10 @@ form.label_defaults Defauts + + form.label_options + Options + form.label_route_name Nom @@ -98,6 +110,10 @@ form.label_routes Routes + + form.label_add_locale_pattern + Ajouter le langage + form.label_add_format_pattern Ajouter le motif du format @@ -106,13 +122,14 @@ form.label_add_trailing_slash Ajouter une barre oblique en fin - - form.help_add_format_pattern - Ajoute le format à la route tel que /your/route.{format}. Le format par défaut est html. - - - form.help_add_trailing_slash - Ajoute un slash à la fin de la route tel que /your/route/. + + form.help_options + + Format: Ajoute le format à la route tel que /your/route.{format}. Le format par défaut est 'html'
+ Barre Oblique: Ajoute un slash à la fin de la route tel que /your/route/ + ]]> +
diff --git a/Tests/Functional/Doctrine/Phpcr/RouteProviderTest.php b/Tests/Functional/Doctrine/Phpcr/RouteProviderTest.php index 789d2dbc..b5123f54 100644 --- a/Tests/Functional/Doctrine/Phpcr/RouteProviderTest.php +++ b/Tests/Functional/Doctrine/Phpcr/RouteProviderTest.php @@ -20,7 +20,7 @@ use Symfony\Cmf\Bundle\RoutingBundle\Tests\Functional\BaseTestCase; -class RouteRepositoryTest extends BaseTestCase +class RouteProviderTest extends BaseTestCase { const ROUTE_ROOT = '/test/routing'; @@ -65,7 +65,7 @@ public function testGetRouteCollectionForRequest() $routes = $this->repository->getRouteCollectionForRequest(Request::create('/testroute/noroute/child')); $this->assertCount(3, $routes); - $this->assertContainsOnlyInstancesOf('Symfony\\Cmf\\Component\\Routing\\RouteObjectInterface', $routes); + $this->assertContainsOnlyInstancesOf('Symfony\Cmf\Component\Routing\RouteObjectInterface', $routes); $routes = $routes->all(); list($key, $child) = each($routes); @@ -102,7 +102,7 @@ public function testGetRouteCollectionForRequestFormat() public function testGetRouteCollectionForRequestNophpcrUrl() { $collection = $this->repository->getRouteCollectionForRequest(Request::create(':///')); - $this->assertInstanceOf('Symfony\\Component\\Routing\\RouteCollection', $collection); + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $collection); $this->assertCount(0, $collection); } @@ -118,12 +118,6 @@ public function testGetRoutesByNames() $routes = $this->repository->getRoutesByNames($routeNames); $this->assertCount(2, $routes); - $this->assertContainsOnlyInstancesOf('Symfony\\Cmf\\Component\\Routing\\RouteObjectInterface', $routes); - } - - - public function testSetPrefix() - { - $this->repository->setPrefix(self::ROUTE_ROOT); + $this->assertContainsOnlyInstancesOf('Symfony\Cmf\Component\Routing\RouteObjectInterface', $routes); } } diff --git a/Tests/Functional/Doctrine/Phpcr/RouteTest.php b/Tests/Functional/Doctrine/Phpcr/RouteTest.php index 038a69b3..84c86da9 100644 --- a/Tests/Functional/Doctrine/Phpcr/RouteTest.php +++ b/Tests/Functional/Doctrine/Phpcr/RouteTest.php @@ -29,7 +29,7 @@ public function setUp() public function testPersist() { - $route = new Route; + $route = new Route(); $root = $this->getDm()->find(null, self::ROUTE_ROOT); $route->setContent($root); // this happens to be a referenceable node @@ -68,7 +68,7 @@ public function testPersist() public function testPersistEmptyOptions() { - $route = new Route; + $route = new Route(); $root = $this->getDm()->find(null, self::ROUTE_ROOT); $route->setPosition($root, 'empty'); @@ -129,13 +129,13 @@ public function testInvalidIdPrefix() */ public function testPrefixNonpersisted() { - $route = new Route; + $route = new Route(); $route->getPattern(); } public function testDefaultFormat() { - $route = new Route(true); + $route = new Route(array('add_format_pattern' => true)); $root = $this->getDm()->find(null, self::ROUTE_ROOT); diff --git a/Tests/Functional/Routing/DynamicRouterTest.php b/Tests/Functional/Routing/DynamicRouterTest.php index f2c3dacf..fe333ee6 100644 --- a/Tests/Functional/Routing/DynamicRouterTest.php +++ b/Tests/Functional/Routing/DynamicRouterTest.php @@ -51,35 +51,37 @@ public function setUp() $root = $this->getDm()->find(null, self::ROUTE_ROOT); // do not set a content here, or we need a valid request and so on... - $route = new Route; + $route = new Route(); $route->setPosition($root, 'testroute'); $route->setVariablePattern('/{slug}/{id}'); $route->setDefault('id', '0'); $route->setRequirement('id', '[0-9]+'); $route->setDefault(RouteObjectInterface::CONTROLLER_NAME, 'testController'); - // TODO: what are the options used for? we should test them too if it makes sense + + //TODO options + $this->getDm()->persist($route); - $childroute = new Route; + $childroute = new Route(); $childroute->setPosition($route, 'child'); $childroute->setDefault(RouteObjectInterface::CONTROLLER_NAME, 'testController'); $this->getDm()->persist($childroute); - $formatroute = new Route(true); + $formatroute = new Route(array('add_format_pattern' => true)); $formatroute->setPosition($root, 'format'); $formatroute->setVariablePattern('/{id}'); $formatroute->setRequirement('_format', 'html|json'); $formatroute->setDefault(RouteObjectInterface::CONTROLLER_NAME, 'testController'); $this->getDm()->persist($formatroute); - $format2jsonroute = new Route(true); + $format2jsonroute = new Route(array('add_format_pattern' => true)); $format2jsonroute->setPosition($root, 'format2.json'); $format2jsonroute->setDefault('_format', 'json'); $format2jsonroute->setRequirement('_format', 'json'); $format2jsonroute->setDefault(RouteObjectInterface::CONTROLLER_NAME, 'testJsonController'); $this->getDm()->persist($format2jsonroute); - $format2route = new Route(true); + $format2route = new Route(array('add_format_pattern' => true)); $format2route->setPosition($root, 'format2'); $format2route->setDefault(RouteObjectInterface::CONTROLLER_NAME, 'testController'); $this->getDm()->persist($format2route); @@ -137,7 +139,7 @@ public function testNotAllowed() $root = $this->getDm()->find(null, self::ROUTE_ROOT); // do not set a content here, or we need a valid request and so on... - $route = new Route; + $route = new Route(); $route->setPosition($root, 'notallowed'); $route->setRequirement('_method', 'GET'); $route->setDefault(RouteObjectInterface::CONTROLLER_NAME, 'testController'); @@ -213,15 +215,15 @@ public function testNoMatchingFormat() public function testMatchLocale() { - $route = new Route; + $route = new Route(); $route->setPosition($this->getDm()->find(null, self::ROUTE_ROOT), 'de'); $route->setDefault(RouteObjectInterface::CONTROLLER_NAME, 'testController'); $this->getDm()->persist($route); - $childroute = new Route; + $childroute = new Route(); $childroute->setPosition($route, 'testroute'); $childroute->setDefault(RouteObjectInterface::CONTROLLER_NAME, 'testController'); $this->getDm()->persist($childroute); - $nolocale = new Route; + $nolocale = new Route(); $nolocale->setPosition($this->getDm()->find(null, self::ROUTE_ROOT), 'es'); $nolocale->setDefault(RouteObjectInterface::CONTROLLER_NAME, 'testController'); $this->getDm()->persist($nolocale); @@ -317,7 +319,7 @@ public function testEnhanceTemplateByClass() // put a route for this content $root = $this->getDm()->find(null, self::ROUTE_ROOT); - $route = new Route; + $route = new Route(); $route->setContent($document); $route->setPosition($root, 'templatebyclass'); $this->getDm()->persist($route); diff --git a/Tests/Resources/Fixtures/config/config.php b/Tests/Resources/Fixtures/config/config.php index 1ae9b703..af09e474 100644 --- a/Tests/Resources/Fixtures/config/config.php +++ b/Tests/Resources/Fixtures/config/config.php @@ -21,11 +21,16 @@ ), 'persistence' => array( 'phpcr' => array( - 'route_basepath' => '/cms/routes', + 'route_basepaths' => array( + '/cms/routes', + '/simple', + ), 'content_basepath' => '/cms/content', 'use_sonata_admin' => 'false', ), ), 'locales' => array('en', 'fr'), + 'auto_locale_pattern' => true, + 'match_implicit_locale' => true, ), )); diff --git a/Tests/Resources/Fixtures/config/config.xml b/Tests/Resources/Fixtures/config/config.xml index d4b34449..90213d8d 100644 --- a/Tests/Resources/Fixtures/config/config.xml +++ b/Tests/Resources/Fixtures/config/config.xml @@ -8,7 +8,11 @@ 100 - + acme_main.some_controller:editableAction cmf_content.controller:indexAction @@ -22,7 +26,9 @@ route-basepath="/cms/routes" content-basepath="/cms/content" use-sonata-admin="false" - /> + > + /simple + en diff --git a/Tests/Resources/Fixtures/config/config.yml b/Tests/Resources/Fixtures/config/config.yml index c3c86684..02801b90 100644 --- a/Tests/Resources/Fixtures/config/config.yml +++ b/Tests/Resources/Fixtures/config/config.yml @@ -14,7 +14,11 @@ cmf_routing: Symfony\Cmf\Bundle\ContentBundle\Document\StaticContent: CmfContentBundle:StaticContent:index.html.twig persistence: phpcr: - route_basepath: /cms/routes + route_basepaths: + - /cms/routes + - /simple content_basepath: /cms/content use_sonata_admin: false locales: [en, fr] + auto_locale_pattern: true + match_implicit_locale: true diff --git a/Tests/Resources/Fixtures/config/config4.xml b/Tests/Resources/Fixtures/config/config4.xml index 228e5699..79ee8e2a 100644 --- a/Tests/Resources/Fixtures/config/config4.xml +++ b/Tests/Resources/Fixtures/config/config4.xml @@ -7,6 +7,7 @@ uri-filter-regexp=".*" route-provider-service-id="my-provider" content-repository-service-id="my-repo" + auto-locale-pattern="true" > type-controller class-controller diff --git a/Tests/Unit/DependencyInjection/ConfigurationTest.php b/Tests/Unit/DependencyInjection/ConfigurationTest.php index de801719..988b878b 100644 --- a/Tests/Unit/DependencyInjection/ConfigurationTest.php +++ b/Tests/Unit/DependencyInjection/ConfigurationTest.php @@ -53,7 +53,10 @@ public function testSupportsAllConfigFormats() 'persistence' => array( 'phpcr' => array( 'enabled' => true, - 'route_basepath' => '/cms/routes', + 'route_basepaths' => array( + '/cms/routes', + '/simple', + ), 'content_basepath' => '/cms/content', 'manager_name' => null, 'use_sonata_admin' => false, @@ -68,6 +71,9 @@ public function testSupportsAllConfigFormats() 'uri_filter_regexp' => '', 'route_filters_by_id' => array(), 'locales' => array('en', 'fr'), + 'limit_candidates' => true, + 'auto_locale_pattern' => true, + 'match_implicit_locale' => true, ), ); diff --git a/Tests/Unit/Doctrine/Orm/RouteProviderTest.php b/Tests/Unit/Doctrine/Orm/RouteProviderTest.php index 80f4fdf4..16dc02f4 100644 --- a/Tests/Unit/Doctrine/Orm/RouteProviderTest.php +++ b/Tests/Unit/Doctrine/Orm/RouteProviderTest.php @@ -12,62 +12,275 @@ namespace Symfony\Cmf\Bundle\RoutingBundle\Tests\Doctrine\Orm; +use Doctrine\Common\Persistence\ManagerRegistry; +use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\ORM\EntityRepository; +use Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Orm\Route; use Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Orm\RouteProvider; +use Symfony\Cmf\Component\Routing\Candidates\CandidatesInterface; +use Symfony\Cmf\Component\Routing\Test\CmfUnitTestCase; +use Symfony\Component\HttpFoundation\Request; -class RouteProviderTest extends \PHPUnit_Framework_Testcase +class RouteProviderTest extends CmfUnitTestCase { - private $route; - private $managerRegistry; - private $objectManager; - private $objectRepository; + /** + * @var Route|\PHPUnit_Framework_MockObject_MockObject + */ + private $routeMock; + + /** + * @var Route|\PHPUnit_Framework_MockObject_MockObject + */ + private $route2Mock; + + /** + * @var ManagerRegistry|\PHPUnit_Framework_MockObject_MockObject + */ + private $managerRegistryMock; + + /** + * @var ObjectManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $objectManagerMock; + + /** + * @var EntityRepository|\PHPUnit_Framework_MockObject_MockObject + */ + private $objectRepositoryMock; + + /** + * @var CandidatesInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $candidatesMock; public function setUp() { - $this->route = $this->getMockBuilder('Symfony\Component\Routing\Route') + $this->routeMock = $this->buildMock('Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Orm\Route'); + $this->route2Mock = $this->buildMock('Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Orm\Route'); + $this->objectManagerMock = $this->getMock('Doctrine\Common\Persistence\ObjectManager'); + $this->managerRegistryMock = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry'); + $this->objectRepositoryMock = $this->getMockBuilder('Doctrine\ORM\EntityRepository') + ->setMethods(array('findByStaticPrefix', 'findOneBy', 'findBy')) ->disableOriginalConstructor() ->getMock(); - $this->objectManager = $this->getMock('Doctrine\Common\Persistence\ObjectManager'); - $this->managerRegistry = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry'); - $this->objectRepository = $this->getMock('Doctrine\Common\Persistence\ObjectRepository'); - } - - public function testGetRouteByName() - { - $this->route + $this->candidatesMock = $this->getMock('Symfony\Cmf\Component\Routing\Candidates\CandidatesInterface'); + $this->candidatesMock ->expects($this->any()) - ->method('getPath') - ->will($this->returnValue('/cms/routes/test-route')); + ->method('isCandidate') + ->will($this->returnValue(true)) + ; - $this->objectManager + $this->managerRegistryMock + ->expects($this->any()) + ->method('getManager') + ->will($this->returnValue($this->objectManagerMock)) + ; + $this->objectManagerMock ->expects($this->any()) ->method('getRepository') - ->will($this->returnValue($this->objectRepository)) + ->with('Route') + ->will($this->returnValue($this->objectRepositoryMock)) + ; + } + + public function testGetRouteCollectionForRequest() + { + $request = Request::create('/my/path'); + $candidates = array('/my/path', '/my', '/'); + + $this->candidatesMock + ->expects($this->once()) + ->method('getCandidates') + ->with($request) + ->will($this->returnValue($candidates)) ; - $this->objectRepository + $this->routeMock + ->expects($this->once()) + ->method('getName') + ->will($this->returnValue('/my/path')) + ; + $this->route2Mock + ->expects($this->once()) + ->method('getName') + ->will($this->returnValue('/my')) + ; + $objects = array( + $this->routeMock, + $this->route2Mock, + ); + + $this->objectRepositoryMock + ->expects($this->once()) + ->method('findByStaticPrefix') + ->with($candidates, array('position' => 'ASC')) + ->will($this->returnValue($objects)) + ; + + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock, 'Route'); + $collection = $routeProvider->getRouteCollectionForRequest($request); + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $collection); + $this->assertCount(2, $collection); + } + + public function testGetRouteCollectionForRequestEmpty() + { + $request = Request::create('/my/path'); + + $this->candidatesMock + ->expects($this->once()) + ->method('getCandidates') + ->with($request) + ->will($this->returnValue(array())) + ; + + $this->objectRepositoryMock + ->expects($this->never()) + ->method('findByStaticPrefix') + ; + + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock, 'Route'); + $collection = $routeProvider->getRouteCollectionForRequest($request); + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $collection); + $this->assertCount(0, $collection); + } + + public function testGetRouteByName() + { + $this->objectRepositoryMock ->expects($this->any()) ->method('findOneBy') - ->with(array('name' => '/cms/routes/test-route')) - ->will($this->returnValue($this->route)) + ->with(array('name' => '/test-route')) + ->will($this->returnValue($this->routeMock)) ; - $this->managerRegistry + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock, 'Route'); + $routeProvider->setManagerName('default'); + + $foundRoute = $routeProvider->getRouteByName('/test-route'); + + $this->assertSame($this->routeMock, $foundRoute); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException + */ + public function testGetRouteByNameNotFound() + { + $this->objectRepositoryMock ->expects($this->any()) - ->method('getManager') - ->will($this->returnValue($this->objectManager)) + ->method('findOneBy') + ->with(array('name' => '/test-route')) + ->will($this->returnValue(null)) ; - $routeProvider = new RouteProvider($this->managerRegistry); + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock, 'Route'); $routeProvider->setManagerName('default'); - $foundRoute = $routeProvider->getRouteByName('/cms/routes/test-route'); + $routeProvider->getRouteByName('/test-route'); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException + */ + public function testGetRouteByNameNotCandidate() + { + $this->objectRepositoryMock + ->expects($this->never()) + ->method('findOneBy') + ; + $candidatesMock = $this->getMock('Symfony\Cmf\Component\Routing\Candidates\CandidatesInterface'); + $candidatesMock + ->expects($this->once()) + ->method('isCandidate') + ->with('/test-route') + ->will($this->returnValue(false)) + ; + + $routeProvider = new RouteProvider($this->managerRegistryMock, $candidatesMock, 'Route'); + $routeProvider->setManagerName('default'); - $this->assertInstanceOf('Symfony\Component\Routing\Route', $foundRoute); - $this->assertEquals('/cms/routes/test-route', $foundRoute->getPath()); + $routeProvider->getRouteByName('/test-route'); } public function testGetRoutesByNames() { - $this->markTestIncomplete(); + $paths = array( + '/test-route', + '/other-route', + ); + + $this->objectRepositoryMock + ->expects($this->at(0)) + ->method('findOneBy') + ->with(array('name' => $paths[0])) + ->will($this->returnValue($this->routeMock)) + ; + $this->objectRepositoryMock + ->expects($this->at(1)) + ->method('findOneBy') + ->with(array('name' => $paths[1])) + ->will($this->returnValue($this->routeMock)) + ; + + $paths[] = '/no-candidate'; + + $candidatesMock = $this->getMock('Symfony\Cmf\Component\Routing\Candidates\CandidatesInterface'); + $candidatesMock + ->expects($this->at(0)) + ->method('isCandidate') + ->with($paths[0]) + ->will($this->returnValue(true)) + ; + $candidatesMock + ->expects($this->at(1)) + ->method('isCandidate') + ->with($paths[1]) + ->will($this->returnValue(true)) + ; + $candidatesMock + ->expects($this->at(2)) + ->method('isCandidate') + ->with($paths[2]) + ->will($this->returnValue(false)) + ; + + $routeProvider = new RouteProvider($this->managerRegistryMock, $candidatesMock, 'Route'); + $routeProvider->setManagerName('default'); + + $routes = $routeProvider->getRoutesByNames($paths); + $this->assertCount(2, $routes); + } + + public function testGetAllRoutesDisabled() + { + $this->objectRepositoryMock + ->expects($this->never()) + ->method('findBy') + ; + + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock, 'Route'); + $routeProvider->setRouteCollectionLimit(0); + $routeProvider->setManagerName('default'); + + $routes = $routeProvider->getRoutesByNames(null); + $this->assertCount(0, $routes); + } + + public function testGetAllRoutes() + { + $this->objectRepositoryMock + ->expects($this->once()) + ->method('findBy') + ->with(array(), null, 42) + ->will($this->returnValue(array($this->routeMock))) + ; + + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock, 'Route'); + $routeProvider->setManagerName('default'); + $routeProvider->setRouteCollectionLimit(42); + + $routes = $routeProvider->getRoutesByNames(null); + $this->assertCount(1, $routes); } } diff --git a/Tests/Unit/Doctrine/Phpcr/IdPrefixListenerTest.php b/Tests/Unit/Doctrine/Phpcr/IdPrefixListenerTest.php index f0514b55..187dd945 100644 --- a/Tests/Unit/Doctrine/Phpcr/IdPrefixListenerTest.php +++ b/Tests/Unit/Doctrine/Phpcr/IdPrefixListenerTest.php @@ -12,16 +12,121 @@ namespace Symfony\Cmf\Bundle\RoutingBundle\Tests\Doctrine\Phpcr; +use Doctrine\Common\Persistence\Event\LifecycleEventArgs; +use Doctrine\ODM\PHPCR\DocumentManager; use Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\IdPrefixListener; +use Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route; +use Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\PrefixCandidates; use Symfony\Cmf\Component\Routing\Test\CmfUnitTestCase; class IdPrefixListenerTest extends CmfUnitTestCase { - public function testConstructor() + /** + * @var IdPrefixListener + */ + protected $listener; + + /** + * @var PrefixCandidates|\PHPUnit_Framework_MockObject_MockObject + */ + protected $candidatesMock; + + /** + * @var DocumentManager|\PHPUnit_Framework_MockObject_MockObject + */ + protected $dmMock; + + /** + * @var Route|\PHPUnit_Framework_MockObject_MockObject + */ + protected $routeMock; + + public function setUp() + { + $this->candidatesMock = $this->buildMock('Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\PrefixCandidates'); + $this->candidatesMock + ->expects($this->any()) + ->method('getPrefixes') + ->will($this->returnValue(array('/cms/routes', '/cms/simple'))) + ; + $this->dmMock = $this->buildMock('Doctrine\ODM\PHPCR\DocumentManager'); + $this->routeMock = $this->buildMock('Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route'); + + $this->listener = new IdPrefixListener($this->candidatesMock); + } + + public function testNoRoute() + { + $args = new LifecycleEventArgs($this, $this->dmMock); + + $this->listener->postLoad($args); + } + + private function prepareMatch() + { + $this->routeMock + ->expects($this->once()) + ->method('getId') + ->will($this->returnValue('/cms/routes')) + ; + $this->routeMock + ->expects($this->once()) + ->method('setPrefix') + ->with('/cms/routes') + ; + + return new LifecycleEventArgs($this->routeMock, $this->dmMock); + } + + public function testPostLoad() { - new IdPrefixListener('test'); + $this->listener->postLoad($this->prepareMatch()); } - // the rest is covered by functional test + public function testPostPersist() + { + $this->listener->postPersist($this->prepareMatch()); + } + + public function testPostMove() + { + $this->listener->postMove($this->prepareMatch()); + } + + public function testSecond() + { + + $this->routeMock + ->expects($this->exactly(2)) + ->method('getId') + ->will($this->returnValue('/cms/simple/test')) + ; + $this->routeMock + ->expects($this->once()) + ->method('setPrefix') + ->with('/cms/simple') + ; + + $args = new LifecycleEventArgs($this->routeMock, $this->dmMock); + + $this->listener->postLoad($args); + } + + public function testOutside() + { + $this->routeMock + ->expects($this->exactly(2)) + ->method('getId') + ->will($this->returnValue('/outside')) + ; + $this->routeMock + ->expects($this->never()) + ->method('setPrefix') + ; + + $args = new LifecycleEventArgs($this->routeMock, $this->dmMock); + + $this->listener->postLoad($args); + } } diff --git a/Tests/Unit/Doctrine/Phpcr/LocaleListenerTest.php b/Tests/Unit/Doctrine/Phpcr/LocaleListenerTest.php index b0262be2..aa8a731f 100644 --- a/Tests/Unit/Doctrine/Phpcr/LocaleListenerTest.php +++ b/Tests/Unit/Doctrine/Phpcr/LocaleListenerTest.php @@ -10,56 +10,110 @@ */ -namespace Symfony\Cmf\Bundle\RoutingBundle\Tests\Listener; +namespace Symfony\Cmf\Bundle\RoutingBundle\Tests\Unit\Doctrine\Phpcr; +use Doctrine\ODM\PHPCR\DocumentManager; use Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\LocaleListener; use Doctrine\ODM\PHPCR\Event\MoveEventArgs; use Doctrine\Common\Persistence\Event\LifecycleEventArgs; +use Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route; +use Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\PrefixCandidates; use Symfony\Cmf\Component\Routing\Test\CmfUnitTestCase; class LocaleListenerTest extends CmfUnitTestCase { /** @var LocaleListener */ protected $listener; - protected $routeMock; + + /** + * @var PrefixCandidates|\PHPUnit_Framework_MockObject_MockObject + */ + protected $candidatesMock; + + /** + * @var DocumentManager|\PHPUnit_Framework_MockObject_MockObject + */ protected $dmMock; + /** + * @var Route|\PHPUnit_Framework_MockObject_MockObject + */ + protected $routeMock; + public function setUp() { - $this->listener = new LocaleListener('/prefix/path', array('en', 'de')); + $this->candidatesMock = $this->buildMock('Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\PrefixCandidates'); + + $this->candidatesMock->expects($this->any()) + ->method('getPrefixes') + ->will($this->returnValue(array('/cms/routes', '/cms/simple'))) + ; + $this->listener = new LocaleListener($this->candidatesMock, array('en', 'de')); $this->routeMock = $this->buildMock('Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route'); $this->dmMock = $this->buildMock('Doctrine\ODM\PHPCR\DocumentManager'); } - public function testMoved() + public function testNoRoute() { - $moveArgs = new MoveEventArgs( - $this->routeMock, - $this->dmMock, - '/prefix/path/de/my/route', - '/prefix/path/en/my/route' - ); + $args = new LifecycleEventArgs($this, $this->dmMock); + $this->listener->postLoad($args); + $this->listener->postPersist($args); + } + public function testNoPrefixMatch() + { $this->routeMock->expects($this->once()) + ->method('getId') + ->will($this->returnValue('/cms/outside/de/my/route')) + ; + + $this->routeMock->expects($this->never()) ->method('setDefault') - ->with('_locale', 'en') + ; + $this->routeMock->expects($this->never()) + ->method('setRequirement') + ; + + $args = new LifecycleEventArgs($this->routeMock, $this->dmMock); + + $this->listener->postLoad($args); + } + + private function prepareMatch() + { + $this->routeMock->expects($this->once()) + ->method('getId') + ->will($this->returnValue('/cms/routes/de/my/route')) + ; + + $this->routeMock->expects($this->once()) + ->method('setDefault') + ->with('_locale', 'de') ; $this->routeMock->expects($this->once()) ->method('setRequirement') - ->with('_locale', 'en') + ->with('_locale', 'de') ; - $this->listener->postMove($moveArgs); + return new LifecycleEventArgs($this->routeMock, $this->dmMock); } public function testLoad() { - $args = new LifecycleEventArgs($this->routeMock, $this->dmMock); + $this->listener->postLoad($this->prepareMatch()); + } + + public function testPersist() + { + $this->listener->postPersist($this->prepareMatch()); + } - $this->routeMock->expects($this->any()) + public function testSecond() + { + $this->routeMock->expects($this->once()) ->method('getId') - ->will($this->returnValue('/prefix/path/de/my/route')) + ->will($this->returnValue('/cms/simple/de')) ; $this->routeMock->expects($this->once()) @@ -71,35 +125,56 @@ public function testLoad() ->with('_locale', 'de') ; + $args = new LifecycleEventArgs($this->routeMock, $this->dmMock); $this->listener->postLoad($args); } - public function testNolocaleUrl() + public function testMoveNoRoute() { - $args = new LifecycleEventArgs($this->routeMock, $this->dmMock); + $moveArgs = new MoveEventArgs( + $this, + $this->dmMock, + '/cms/routes/de/my/route', + '/cms/routes/en/my/route' + ); - $this->routeMock->expects($this->any()) - ->method('getId') - ->will($this->returnValue('/prefix/path/my/route')) - ; + $this->listener->postMove($moveArgs); + } - $this->routeMock->expects($this->never()) + public function testMoved() + { + $moveArgs = new MoveEventArgs( + $this->routeMock, + $this->dmMock, + '/cms/routes/de/my/route', + '/cms/routes/en/my/route' + ); + + $this->routeMock->expects($this->once()) ->method('setDefault') + ->with('_locale', 'en') ; - $this->routeMock->expects($this->never()) + $this->routeMock->expects($this->once()) ->method('setRequirement') + ->with('_locale', 'en') ; - $this->listener->postLoad($args); + $this->listener->postMove($moveArgs); + } + + public function testSetLocales() + { + $this->listener->setLocales(array('xx')); + $this->assertAttributeEquals(array('xx'), 'locales', $this->listener); } public function testHaslocale() { $args = new LifecycleEventArgs($this->routeMock, $this->dmMock); - $this->routeMock->expects($this->any()) + $this->routeMock->expects($this->once()) ->method('getId') - ->will($this->returnValue('/prefix/path/de/my/route')) + ->will($this->returnValue('/cms/routes/de/my/route')) ; $this->routeMock->expects($this->once()) @@ -123,4 +198,76 @@ public function testHaslocale() $this->listener->postLoad($args); } + + /** + * URL without locale, addLocalePattern not set. + */ + public function testNolocaleUrl() + { + $args = new LifecycleEventArgs($this->routeMock, $this->dmMock); + + $this->routeMock->expects($this->once()) + ->method('getId') + ->will($this->returnValue('/cms/routes/my/route')) + ; + + $this->routeMock->expects($this->never()) + ->method('setDefault') + ; + $this->routeMock->expects($this->never()) + ->method('setRequirement') + ; + + $this->listener->postLoad($args); + } + + /** + * URL without locale, addLocalePattern set. + */ + public function testLocalePattern() + { + $this->routeMock->expects($this->once()) + ->method('getId') + ->will($this->returnValue('/cms/simple/something')) + ; + + $this->routeMock->expects($this->once()) + ->method('setOption') + ->with('add_locale_pattern', true) + ; + + $this->listener->setAddLocalePattern(true); + $args = new LifecycleEventArgs($this->routeMock, $this->dmMock); + $this->listener->postLoad($args); + } + + /** + * URL without locale, set available translations + */ + public function testAvailableTranslations() + { + $this->routeMock->expects($this->once()) + ->method('getId') + ->will($this->returnValue('/cms/simple/something')) + ; + + $this->dmMock->expects($this->once()) + ->method('isDocumentTranslatable') + ->with($this->routeMock) + ->will($this->returnValue(true)) + ; + $this->dmMock->expects($this->once()) + ->method('getLocalesFor') + ->with($this->routeMock, true) + ->will($this->returnValue(array('en', 'de', 'fr'))) + ; + $this->routeMock->expects($this->once()) + ->method('setRequirement') + ->with('_locale', 'en|de|fr') + ; + + $this->listener->setUpdateAvailableTranslations(true); + $args = new LifecycleEventArgs($this->routeMock, $this->dmMock); + $this->listener->postLoad($args); + } } diff --git a/Tests/Unit/Doctrine/Phpcr/PrefixCandidatesTest.php b/Tests/Unit/Doctrine/Phpcr/PrefixCandidatesTest.php new file mode 100644 index 00000000..6a48a906 --- /dev/null +++ b/Tests/Unit/Doctrine/Phpcr/PrefixCandidatesTest.php @@ -0,0 +1,167 @@ +assertEquals(array('/routes'), $candidates->getPrefixes()); + $candidates->addPrefix('/simple'); + $this->assertEquals(array('/routes', '/simple'), $candidates->getPrefixes()); + $candidates->setPrefixes(array('/other')); + $this->assertEquals(array('/other'), $candidates->getPrefixes()); + } + + public function testGetCandidates() + { + $request = Request::create('/my/path.html'); + + $candidates = new PrefixCandidates(array('/routes', '/simple')); + $paths = $candidates->getCandidates($request); + + $this->assertEquals( + array( + '/routes/my/path.html', + '/routes/my/path', + '/routes/my', + '/routes', + '/simple/my/path.html', + '/simple/my/path', + '/simple/my', + '/simple', + ), + $paths + ); + } + + public function testGetCandidatesLocales() + { + $request = Request::create('/de/path.html'); + + $candidates = new PrefixCandidates(array('/routes', '/simple'), array('de', 'fr')); + $paths = $candidates->getCandidates($request); + + $this->assertEquals( + array( + '/routes/de/path.html', + '/routes/de/path', + '/routes/de', + '/routes', + '/simple/de/path.html', + '/simple/de/path', + '/simple/de', + '/simple', + '/routes/path.html', + '/routes/path', + '/simple/path.html', + '/simple/path', + ), + $paths + ); + } + + public function testGetCandidatesLocalesDm() + { + $request = Request::create('/de/path.html'); + + $dmMock = $this->buildMock('Doctrine\ODM\PHPCR\DocumentManager'); + $managerRegistryMock = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry'); + $managerRegistryMock + ->expects($this->any()) + ->method('getManager') + ->will($this->returnValue($dmMock)) + ; + $localeMock = $this->buildMock('Doctrine\ODM\PHPCR\Translation\LocaleChooser\LocaleChooserInterface'); + $localeMock + ->expects($this->once()) + ->method('setLocale') + ->with('de') + ; + $dmMock + ->expects($this->once()) + ->method('getLocaleChooserStrategy') + ->will($this->returnValue($localeMock)) + ; + + $candidates = new PrefixCandidates(array('/simple'), array('de', 'fr'), $managerRegistryMock); + $candidates->getCandidates($request); + } + + public function testGetCandidatesLocalesDmNoLocale() + { + $request = Request::create('/it/path.html'); + $managerRegistryMock = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry'); + $managerRegistryMock + ->expects($this->never()) + ->method('getManager') + ; + + $candidates = new PrefixCandidates(array('/simple'), array('de', 'fr'), $managerRegistryMock); + $candidates->getCandidates($request); + } + + public function testIsCandidate() + { + $candidates = new PrefixCandidates(array('/routes')); + $this->assertTrue($candidates->isCandidate('/routes')); + $this->assertTrue($candidates->isCandidate('/routes/my/path')); + $this->assertFalse($candidates->isCandidate('/other/my/path')); + $this->assertFalse($candidates->isCandidate('/route')); + $this->assertFalse($candidates->isCandidate('/routesnotsame')); + } + + public function testRestrictQuery() + { + $orX = $this->getMock('Doctrine\ODM\PHPCR\Query\Builder\QueryBuilder', array('descendant')); + $orX->expects($this->once()) + ->method('descendant') + ->with('/routes', 'd') + ; + $andWhere = $this->getMock('Doctrine\ODM\PHPCR\Query\Builder\QueryBuilder', array('orX')); + $andWhere->expects($this->once()) + ->method('orX') + ->will($this->returnValue($orX)) + ; + $qb = $this->getMock('Doctrine\ODM\PHPCR\Query\Builder\QueryBuilder', array('andWhere', 'getPrimaryAlias')); + $qb->expects($this->once()) + ->method('andWhere') + ->will($this->returnValue($andWhere)) + ; + $qb->expects($this->once()) + ->method('getPrimaryAlias') + ->will($this->returnValue('d')) + ; + + $candidates = new PrefixCandidates(array('/routes')); + $candidates->restrictQuery($qb); + } + + public function testRestrictQueryGlobal() + { + $qb = $this->getMock('Doctrine\ODM\PHPCR\Query\Builder\QueryBuilder', array('andWhere')); + $qb->expects($this->never()) + ->method('andWhere') + ; + + $candidates = new PrefixCandidates(array('/routes', '', '/other')); + $candidates->restrictQuery($qb); + } +} diff --git a/Tests/Unit/Doctrine/Phpcr/RouteProviderTest.php b/Tests/Unit/Doctrine/Phpcr/RouteProviderTest.php index 2247fb19..b6a9125c 100644 --- a/Tests/Unit/Doctrine/Phpcr/RouteProviderTest.php +++ b/Tests/Unit/Doctrine/Phpcr/RouteProviderTest.php @@ -9,65 +9,139 @@ * file that was distributed with this source code. */ - -namespace Symfony\Cmf\Bundle\RoutingBundle\Tests\Doctrine\Phpcr; +namespace Symfony\Cmf\Bundle\RoutingBundle\Tests\Unit\Doctrine\Phpcr; use PHPCR\Util\UUIDHelper; +use PHPCR\Query\QueryInterface; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Persistence\ManagerRegistry; +use Doctrine\ODM\PHPCR\DocumentManager; use Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\RouteProvider; +use Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\PrefixCandidates; +use Symfony\Cmf\Component\Routing\Test\CmfUnitTestCase; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; -class RouteProviderTest extends \PHPUnit_Framework_Testcase +class RouteProviderTest extends CmfUnitTestCase { - private $route; - private $route2; - private $objectManager; - private $objectManager2; - private $managerRegistry; + /** + * @var ManagerRegistry|\PHPUnit_Framework_MockObject_MockObject + */ + private $managerRegistryMock; + + /** + * @var PrefixCandidates|\PHPUnit_Framework_MockObject_MockObject + */ + protected $candidatesMock; + + /** + * @var DocumentManager|\PHPUnit_Framework_MockObject_MockObject + */ + protected $dmMock; + /** + * @var DocumentManager|\PHPUnit_Framework_MockObject_MockObject + */ + protected $dm2Mock; + + /** + * @var Route|\PHPUnit_Framework_MockObject_MockObject + */ + protected $routeMock; + /** + * @var Route|\PHPUnit_Framework_MockObject_MockObject + */ + protected $route2Mock; public function setUp() { - $this->route = $this->getMockBuilder('Symfony\Component\Routing\Route') - ->disableOriginalConstructor() - ->getMock() - ; - $this->route2 = $this->getMockBuilder('Symfony\Component\Routing\Route') - ->disableOriginalConstructor() - ->getMock(); - $this->objectManager = $this->getMock('Doctrine\Common\Persistence\ObjectManager'); - $this->objectManager2 = $this->getMock('Doctrine\Common\Persistence\ObjectManager'); - $this->managerRegistry = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry'); + $this->routeMock = $this->buildMock('Symfony\Component\Routing\Route'); + $this->route2Mock = $this->buildMock('Symfony\Component\Routing\Route'); + $this->dmMock = $this->buildMock('Doctrine\ODM\PHPCR\DocumentManager'); + $this->dm2Mock = $this->buildMock('Doctrine\ODM\PHPCR\DocumentManager'); + $this->managerRegistryMock = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry'); + + $this->managerRegistryMock + ->expects($this->any()) + ->method('getManager') + ->will($this->returnValue($this->dmMock)) + ; + + $this->candidatesMock = $this->buildMock('Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\PrefixCandidates'); } public function testGetRouteCollectionForRequest() { - $this->markTestIncomplete(); + $request = Request::create('/my/path'); + $candidates = array('/prefix/my/path', '/prefix/my'); + + $this->candidatesMock + ->expects($this->once()) + ->method('getCandidates') + ->with($request) + ->will($this->returnValue($candidates)) + ; + + $objects = array( + new Route('/my'), + $this, + ); + + $this->dmMock + ->expects($this->once()) + ->method('findMany') + ->with(null, $candidates) + ->will($this->returnValue($objects)) + ; + + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock); + $collection = $routeProvider->getRouteCollectionForRequest($request); + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $collection); + $this->assertCount(1, $collection); } - public function testGetRouteByName() + public function testGetRouteCollectionForRequestEmpty() { + $request = Request::create('/my/path'); + + $this->candidatesMock + ->expects($this->once()) + ->method('getCandidates') + ->with($request) + ->will($this->returnValue(array())) + ; + + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock); + $collection = $routeProvider->getRouteCollectionForRequest($request); + $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $collection); + $this->assertCount(0, $collection); + } - $this->route + public function testGetRouteByName() + { + $this->routeMock ->expects($this->any()) ->method('getPath') ->will($this->returnValue('/cms/routes/test-route')) ; - $this->objectManager + $this->dmMock ->expects($this->any()) ->method('find') ->with(null, '/cms/routes/test-route') - ->will($this->returnValue($this->route)) + ->will($this->returnValue($this->routeMock)) ; - $this->managerRegistry - ->expects($this->any()) - ->method('getManager') - ->will($this->returnValue($this->objectManager)) + $this->candidatesMock + ->expects($this->once()) + ->method('isCandidate') + ->with('/cms/routes/test-route') + ->will($this->returnValue(true)) ; - $routeProvider = new RouteProvider($this->managerRegistry); + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock); $routeProvider->setManagerName('default'); - $routeProvider->setPrefix('/cms/routes/'); $foundRoute = $routeProvider->getRouteByName('/cms/routes/test-route'); $this->assertInstanceOf('Symfony\Component\Routing\Route', $foundRoute); @@ -77,28 +151,20 @@ public function testGetRouteByName() public function testGetRouteByNameUuid() { $uuid = UUIDHelper::generateUUID(); - $this->route + $this->routeMock ->expects($this->any()) ->method('getPath') ->will($this->returnValue('/cms/routes/test-route')) ; - $objectManager = $this - ->getMockBuilder('Doctrine\ODM\PHPCR\DocumentManager') - ->disableOriginalConstructor() - ->getMock() - ; - $uow = $this - ->getMockBuilder('Doctrine\ODM\PHPCR\UnitOfWork') - ->disableOriginalConstructor() - ->getMock() - ; - $objectManager + + $uow = $this->buildMock('Doctrine\ODM\PHPCR\UnitOfWork'); + $this->dmMock ->expects($this->any()) ->method('find') ->with(null, $uuid) - ->will($this->returnValue($this->route)) + ->will($this->returnValue($this->routeMock)) ; - $objectManager + $this->dmMock ->expects($this->any()) ->method('getUnitOfWork') ->will($this->returnValue($uow)) @@ -106,19 +172,20 @@ public function testGetRouteByNameUuid() $uow ->expects($this->any()) ->method('getDocumentId') + ->with($this->routeMock) ->will($this->returnValue('/cms/routes/test-route')) ; - $this->managerRegistry - ->expects($this->any()) - ->method('getManager') - ->will($this->returnValue($objectManager)) + $this->candidatesMock + ->expects($this->once()) + ->method('isCandidate') + ->with('/cms/routes/test-route') + ->will($this->returnValue(true)) ; - $routeProvider = new RouteProvider($this->managerRegistry); + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock); $routeProvider->setManagerName('default'); - $routeProvider->setPrefix('/cms/routes/'); $foundRoute = $routeProvider->getRouteByName($uuid); $this->assertInstanceOf('Symfony\Component\Routing\Route', $foundRoute); @@ -128,160 +195,369 @@ public function testGetRouteByNameUuid() /** * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException */ - public function testGetRouteByNameNotFound() + public function testGetRouteByNameUuidNotFound() { - $this->objectManager + $uuid = UUIDHelper::generateUUID(); + + $this->dmMock ->expects($this->any()) ->method('find') - ->with(null, '/cms/routes/test-route') + ->with(null, $uuid) ->will($this->returnValue(null)) ; - $this->managerRegistry - ->expects($this->any()) - ->method('getManager') - ->will($this->returnValue($this->objectManager)) - ; - - $routeProvider = new RouteProvider($this->managerRegistry); + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock); $routeProvider->setManagerName('default'); - $routeProvider->setPrefix('/cms/routes/'); - $routeProvider->getRouteByName('/cms/routes/test-route'); + + $routeProvider->getRouteByName($uuid); } /** * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException */ - public function testGetRouteByNameNoRoute() + public function testGetRouteByNameUuidNotCandidate() { - $this->objectManager + $uuid = UUIDHelper::generateUUID(); + $this->routeMock ->expects($this->any()) - ->method('find') - ->with(null, '/cms/routes/test-route') - ->will($this->returnValue($this)) + ->method('getPath') + ->will($this->returnValue('/cms/routes/test-route')) ; - $this->managerRegistry + $uow = $this->buildMock('Doctrine\ODM\PHPCR\UnitOfWork'); + $this->dmMock ->expects($this->any()) - ->method('getManager') - ->will($this->returnValue($this->objectManager)) + ->method('find') + ->with(null, $uuid) + ->will($this->returnValue($this->routeMock)) + ; + $this->dmMock + ->expects($this->any()) + ->method('getUnitOfWork') + ->will($this->returnValue($uow)) + ; + $uow + ->expects($this->any()) + ->method('getDocumentId') + ->will($this->returnValue('/cms/routes/test-route')) + ; + + $this->candidatesMock + ->expects($this->once()) + ->method('isCandidate') + ->with('/cms/routes/test-route') + ->will($this->returnValue(false)) ; - $routeProvider = new RouteProvider($this->managerRegistry); + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock); $routeProvider->setManagerName('default'); - $routeProvider->setPrefix('/cms/routes/'); - $routeProvider->getRouteByName('/cms/routes/test-route'); + + $routeProvider->getRouteByName($uuid); } /** - * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException + * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException */ - public function testGetRouteByNameInvalidRoute() + public function testGetRouteByNameNotCandidate() { - $this->objectManager + $this->dmMock ->expects($this->never()) ->method('find') ; - $this->managerRegistry - ->expects($this->any()) - ->method('getManager') - ->will($this->returnValue($this->objectManager)) + $this->candidatesMock + ->expects($this->once()) + ->method('isCandidate') + ->with('/cms/routes/test-route') + ->will($this->returnValue(false)) ; - $routeProvider = new RouteProvider($this->managerRegistry); + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock); $routeProvider->setManagerName('default'); - - $routeProvider->setPrefix('/cms/routes'); - - $routeProvider->getRouteByName('invalid_route'); + $routeProvider->getRouteByName('/cms/routes/test-route'); } - public function testGetRouteByNameIdPrefixEmptyString() + /** + * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException + */ + public function testGetRouteByNameNotFound() { - - $this->route - ->expects($this->any()) - ->method('getPath') - ->will($this->returnValue('/cms/routes/test-route')) - ; - - $this->objectManager + $this->dmMock ->expects($this->any()) ->method('find') ->with(null, '/cms/routes/test-route') - ->will($this->returnValue($this->route)) + ->will($this->returnValue(null)) ; - $this->managerRegistry - ->expects($this->any()) - ->method('getManager') - ->will($this->returnValue($this->objectManager)) + $this->candidatesMock + ->expects($this->once()) + ->method('isCandidate') + ->with('/cms/routes/test-route') + ->will($this->returnValue(true)) ; - $routeProvider = new RouteProvider($this->managerRegistry); + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock); $routeProvider->setManagerName('default'); - - $routeProvider->setPrefix(''); - $foundRoute = $routeProvider->getRouteByName('/cms/routes/test-route'); - - $this->assertInstanceOf('Symfony\Component\Routing\Route', $foundRoute); - $this->assertEquals('/cms/routes/test-route', $foundRoute->getPath()); + $routeProvider->getRouteByName('/cms/routes/test-route'); } - /** * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException */ - public function testGetRouteByNameNotFoundIdPrefixEmptyString() + public function testGetRouteByNameNoRoute() { - $this->objectManager + $this->dmMock ->expects($this->any()) ->method('find') ->with(null, '/cms/routes/test-route') - ->will($this->returnValue(null)) + ->will($this->returnValue($this)) ; - - $this->managerRegistry - ->expects($this->any()) - ->method('getManager') - ->will($this->returnValue($this->objectManager)) + $this->candidatesMock + ->expects($this->once()) + ->method('isCandidate') + ->with('/cms/routes/test-route') + ->will($this->returnValue(true)) ; - $routeProvider = new RouteProvider($this->managerRegistry); + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock); $routeProvider->setManagerName('default'); - $routeProvider->setPrefix(''); $routeProvider->getRouteByName('/cms/routes/test-route'); } - /** - * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException - */ - public function testGetRouteByNameNoRoutePrefixEmptyString() + public function testGetRoutesByNames() { - $this->objectManager - ->expects($this->any()) - ->method('find') - ->with(null, '/cms/routes/test-route') - ->will($this->returnValue($this)) + $paths = array( + '/cms/routes/test-route', + '/cms/simple/other-route', + '/cms/routes/not-a-route', + ); + + $routes = new ArrayCollection(); + $routes->set('/cms/routes/test-route', new Route('/test-route')); + $routes->set('/cms/simple/other-route', new Route('/other-route')); + $routes->set('/cms/routes/not-a-route', $this); + + $this->dmMock + ->expects($this->once()) + ->method('findMany') + ->with(null, $paths) + ->will($this->returnValue($routes)) + ; + + $this->candidatesMock + ->expects($this->at(0)) + ->method('isCandidate') + ->with('/cms/routes/test-route') + ->will($this->returnValue(true)) + ; + $this->candidatesMock + ->expects($this->at(1)) + ->method('isCandidate') + ->with('/cms/simple/other-route') + ->will($this->returnValue(true)) + ; + $this->candidatesMock + ->expects($this->at(2)) + ->method('isCandidate') + ->with('/cms/routes/not-a-route') + ->will($this->returnValue(true)) ; + $this->candidatesMock + ->expects($this->at(3)) + ->method('isCandidate') + ->with('/outside/prefix') + ->will($this->returnValue(false)) + ; + + $paths[] = '/outside/prefix'; + + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock); + $routeProvider->setManagerName('default'); + + $routes = $routeProvider->getRoutesByNames($paths); + $this->assertCount(2, $routes); + } + + public function testGetRoutesByNamesNotCandidates() + { + $paths = array( + '/cms/routes/test-route', + '/cms/simple/other-route', + '/cms/routes/not-a-route', + ); + + $this->dmMock + ->expects($this->never()) + ->method('findMany') + ; + + $this->candidatesMock + ->expects($this->at(0)) + ->method('isCandidate') + ->with('/cms/routes/test-route') + ->will($this->returnValue(false)) + ; + $this->candidatesMock + ->expects($this->at(1)) + ->method('isCandidate') + ->with('/cms/simple/other-route') + ->will($this->returnValue(false)) + ; + $this->candidatesMock + ->expects($this->at(2)) + ->method('isCandidate') + ->with('/cms/routes/not-a-route') + ->will($this->returnValue(false)) + ; + + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock); + $routeProvider->setManagerName('default'); + + $routes = $routeProvider->getRoutesByNames($paths); + $this->assertCount(0, $routes); + } + + public function testGetRoutesByNamesUuid() + { + $uuid1 = UUIDHelper::generateUUID(); + $uuid2 = UUIDHelper::generateUUID(); + $paths = array( + $uuid1, + $uuid2, + ); + + $route1 = new Route('/test-route'); + $route2 = new Route('/other-route'); - $this->managerRegistry + $routes = new ArrayCollection(); + $routes->set($uuid1, $route1); + $routes->set($uuid2, $route2); + + $this->dmMock + ->expects($this->once()) + ->method('findMany') + ->with(null, $paths) + ->will($this->returnValue($routes)) + ; + + $uow = $this->buildMock('Doctrine\ODM\PHPCR\UnitOfWork'); + + $this->dmMock ->expects($this->any()) - ->method('getManager') - ->will($this->returnValue($this->objectManager)) + ->method('getUnitOfWork') + ->will($this->returnValue($uow)) + ; + $uow + ->expects($this->at(0)) + ->method('getDocumentId') + ->with($route1) + ->will($this->returnValue('/cms/routes/test-route')) + ; + $uow + ->expects($this->at(1)) + ->method('getDocumentId') + ->with($route2) + ->will($this->returnValue('/cms/routes/other-route')) + ; + + $this->candidatesMock + ->expects($this->at(0)) + ->method('isCandidate') + ->with('/cms/routes/test-route') + ->will($this->returnValue(true)) + ; + $this->candidatesMock + ->expects($this->at(1)) + ->method('isCandidate') + ->with('/cms/routes/other-route') + ->will($this->returnValue(false)) ; - $routeProvider = new RouteProvider($this->managerRegistry); + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock); $routeProvider->setManagerName('default'); - $routeProvider->setPrefix(''); - $routeProvider->getRouteByName('/cms/routes/test-route'); + $routes = $routeProvider->getRoutesByNames($paths); + $this->assertCount(1, $routes); } - public function testGetRoutesByNames() + private function doRouteDump($limit) + { + $from = $this->getMock('Doctrine\ODM\PHPCR\Query\Builder\QueryBuilder', array('document')); + $from->expects($this->once()) + ->method('document') + ->with('Symfony\Component\Routing\Route', 'd') + ; + + $query = $this->buildMock('Doctrine\ODM\PHPCR\Query\Query'); + $query->expects($this->once())->method('getResult'); + if ($limit) { + $query + ->expects($this->once()) + ->method('setMaxResults') + ->with($limit) + ; + } else { + $query + ->expects($this->never()) + ->method('setMaxResults') + ; + } + + $queryBuilder = $this->getMock('Doctrine\ODM\PHPCR\Query\Builder\QueryBuilder', array('from', 'getQuery')); + $queryBuilder->expects($this->once()) + ->method('from') + ->with('d') + ->will($this->returnValue($from)) + ; + $queryBuilder->expects($this->once()) + ->method('getQuery') + ->will($this->returnValue($query)) + ; + + $this->dmMock + ->expects($this->once()) + ->method('createQueryBuilder') + ->will($this->returnValue($queryBuilder)) + ; + + $this->candidatesMock + ->expects($this->once()) + ->method('restrictQuery') + ->with($queryBuilder) + ; + + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock); + $routeProvider->setManagerName('default'); + $routeProvider->setRouteCollectionLimit($limit); + + $routeProvider->getRoutesByNames(); + } + + public function testDumpRoutesNoLimit() + { + $this->doRouteDump(null); + } + + public function testDumpRoutesLimit() { - $this->markTestIncomplete(); + $this->doRouteDump(1); + } + + public function testDumpRoutesDisabled() + { + $this->dmMock + ->expects($this->never()) + ->method('createPhpcrQuery') + ; + $this->dmMock + ->expects($this->never()) + ->method('getDocumentsByPhpcrQuery') + ; + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock); + $routeProvider->setManagerName('default'); + $routeProvider->setRouteCollectionLimit(0); + + $this->assertEquals(array(), $routeProvider->getRoutesByNames()); } /** @@ -290,35 +566,36 @@ public function testGetRoutesByNames() */ public function testChangingDocumentManager() { - $this->route + $this->routeMock ->expects($this->any()) ->method('getPath') ->will($this->returnValue('/cms/routes/test-route')); - $this->route2 + $this->route2Mock ->expects($this->any()) ->method('getPath') ->will($this->returnValue('/cms/routes/new-route')); - $this->objectManager + $this->dmMock ->expects($this->any()) ->method('find') ->with(null, '/cms/routes/test-route') - ->will($this->returnValue($this->route)) + ->will($this->returnValue($this->routeMock)) ; - $this->objectManager2 + $this->dm2Mock ->expects($this->any()) ->method('find') ->with(null, '/cms/routes/test-route') - ->will($this->returnValue($this->route2)) + ->will($this->returnValue($this->route2Mock)) ; $objectManagers = array( - 'default' => $this->objectManager, - 'new_manager' => $this->objectManager2 + 'default' => $this->dmMock, + 'new_manager' => $this->dm2Mock ); - $this->managerRegistry + $this->managerRegistryMock = $this->getMock('Doctrine\Common\Persistence\ManagerRegistry'); + $this->managerRegistryMock ->expects($this->any()) ->method('getManager') ->will( @@ -327,12 +604,17 @@ function ($name) use ($objectManagers) { return $objectManagers[$name]; } ) - ); + ) + ; - $routeProvider = new RouteProvider($this->managerRegistry); + $this->candidatesMock + ->expects($this->any()) + ->method('isCandidate') + ->will($this->returnValue(true)) + ; + $routeProvider = new RouteProvider($this->managerRegistryMock, $this->candidatesMock); $routeProvider->setManagerName('default'); - $routeProvider->setPrefix('/cms/routes/'); $foundRoute = $routeProvider->getRouteByName('/cms/routes/test-route'); $this->assertInstanceOf('Symfony\Component\Routing\Route', $foundRoute); diff --git a/Tests/Unit/Doctrine/Phpcr/RouteTest.php b/Tests/Unit/Doctrine/Phpcr/RouteTest.php index 2022203b..97d53c15 100644 --- a/Tests/Unit/Doctrine/Phpcr/RouteTest.php +++ b/Tests/Unit/Doctrine/Phpcr/RouteTest.php @@ -10,7 +10,7 @@ */ -namespace Symfony\Cmf\Bundle\RoutingBundle\Tests\Doctrine\Phpcr; +namespace Symfony\Cmf\Bundle\RoutingBundle\Tests\Unit\Doctrine\Phpcr; use Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route; diff --git a/UPGRADE-1.2.md b/UPGRADE-1.2.md new file mode 100644 index 00000000..3b0425d5 --- /dev/null +++ b/UPGRADE-1.2.md @@ -0,0 +1,31 @@ +UPGRADE FROM 1.1 TO 1.2 +======================= + +Renamed the configuration option `dynamic.persistence.phpcr.route_basepath` to +`dynamic.persistence.phpcr.route_basepaths` and made it a list instead of a +single value. `route_basepath` is supported for BC but deprecated. + +Refactored explicit properties for `addTrailingSlash` and `addFormatPattern` +into Route options. Use setOption/getOption with `add_trailing_slash` resp +`add_format_pattern` to interact with the options. The getters and setters are +kept for BC. If you have stored PHPCR Routes with these options activated, you +need to move the data into the options: + +```php +/** @var $dm \Doctrine\ODM\PHPCR\DocumentManager */ +$query = $dm->createPhpcrQuery("SELECT * FROM nt:base WHERE addFormatPattern = 'true'", QueryInterface::JCR_SQL2) +$routes = $dm->getDocumentsByPhpcrQuery($query); +/** @var $route \Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route */ +foreach($routes as $route) { + $route->setOption('add_format_pattern', true); +} + +$query = $dm->createPhpcrQuery("SELECT * FROM nt:base WHERE addTrailingSlash = 'true'", QueryInterface::JCR_SQL2) +$routes = $dm->getDocumentsByPhpcrQuery($query); +/** @var $route \Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route */ +foreach($routes as $route) { + $route->setOption('add_trailing_slash', true); +} + +$dm->flush(); +```