From d07b40b38903cc1bc17cb20cc6e3abe4ac483184 Mon Sep 17 00:00:00 2001 From: dantleech Date: Sat, 26 Oct 2013 09:03:40 +0200 Subject: [PATCH] Adding support for multilang --- AutoRoute/AutoRouteManager.php | 40 +++++++++---- AutoRoute/BuilderContext.php | 23 ++++++++ AutoRoute/Driver/DriverInterface.php | 21 +++++++ AutoRoute/Driver/PhpcrOdmDriver.php | 38 +++++++++++++ AutoRoute/PathProvider/LocaleProvider.php | 39 +++++++++++++ Command/RefreshCommand.php | 36 ++++++------ EventListener/AutoRouteListener.php | 39 +++++++++---- Resources/config/auto_route.xml | 5 ++ Resources/config/path_provider.xml | 9 +++ .../EventListener/AutoRouteListenerTest.php | 57 ++++++++++++++++++- Tests/Resources/Document/Article.php | 41 +++++++++++++ Tests/Resources/Document/Post.php | 2 +- .../Resources/app/config/routingautoroute.yml | 17 ++++++ Tests/Unit/AutoRoute/AutoRouteManagerTest.php | 6 +- .../PathProvider/LocaleProviderTest.php | 53 +++++++++++++++++ composer.json | 3 +- 16 files changed, 386 insertions(+), 43 deletions(-) create mode 100644 AutoRoute/Driver/DriverInterface.php create mode 100644 AutoRoute/Driver/PhpcrOdmDriver.php create mode 100644 AutoRoute/PathProvider/LocaleProvider.php create mode 100644 Tests/Resources/Document/Article.php create mode 100644 Tests/Unit/AutoRoute/PathProvider/LocaleProviderTest.php diff --git a/AutoRoute/AutoRouteManager.php b/AutoRoute/AutoRouteManager.php index 6452527..65cd671 100644 --- a/AutoRoute/AutoRouteManager.php +++ b/AutoRoute/AutoRouteManager.php @@ -6,6 +6,7 @@ use Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\AutoRouteStack; use Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\RouteStack\Builder; use Doctrine\Common\Util\ClassUtils; +use Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\Driver\DriverInterface; /** * This class is concerned with the automatic creation of route objects. @@ -15,11 +16,13 @@ class AutoRouteManager { protected $factory; + protected $driver; - public function __construct(Factory $factory, Builder $builder) + public function __construct(DriverInterface $driver, Factory $factory, Builder $builder) { $this->factory = $factory; $this->builder = $builder; + $this->driver = $driver; } /** @@ -30,25 +33,38 @@ public function __construct(Factory $factory, Builder $builder) * * @param object Mapped document for which to generate the AutoRoute * - * @return BuilderContext + * @return BuilderContext[] */ public function updateAutoRouteForDocument($document) { $classFqn = ClassUtils::getClass($document); + $locales = $this->driver->getLocales($document) ? : array(null); - $context = new BuilderContext; - $context->setContent($document); + $contexts = array(); - // build chain - $builderUnitChain = $this->factory->getRouteStackBuilderUnitChain($classFqn); - $builderUnitChain->executeChain($context); + foreach ($locales as $locale) { + if (null !== $locale) { + $document = $this->driver->translateObject($document, $locale); + } - // persist the auto route - $autoRouteStack = new AutoRouteStack($context); - $builderUnit = $this->factory->getContentNameBuilderUnit($classFqn); - $this->builder->build($autoRouteStack, $builderUnit); + $context = new BuilderContext; - return $context; + $context->setContent($document); + $context->setLocale($locale); + + // build path elements + $builderUnitChain = $this->factory->getRouteStackBuilderUnitChain($classFqn); + $builderUnitChain->executeChain($context); + + // persist the content name element (the autoroute) + $autoRouteStack = new AutoRouteStack($context); + $builderUnit = $this->factory->getContentNameBuilderUnit($classFqn); + $this->builder->build($autoRouteStack, $builderUnit); + + $contexts[] = $context; + } + + return $contexts; } /** diff --git a/AutoRoute/BuilderContext.php b/AutoRoute/BuilderContext.php index d93e8e7..0887488 100644 --- a/AutoRoute/BuilderContext.php +++ b/AutoRoute/BuilderContext.php @@ -16,10 +16,14 @@ class BuilderContext { /** @var RouteStack[] */ protected $routeStacks = array(); + /** @var RouteStack */ protected $stagedRouteStack; + protected $content; + protected $locale; + /** * Return an ordered array of all routes from * all stacks. @@ -131,4 +135,23 @@ public function getContent() { return $this->content; } + + /** + * Return the locale for this context + * + * @return string + */ + public function getLocale() + { + return $this->locale; + } + /** + * Set the locale for this context + * + * @param string + */ + public function setLocale($locale) + { + $this->locale = $locale; + } } diff --git a/AutoRoute/Driver/DriverInterface.php b/AutoRoute/Driver/DriverInterface.php new file mode 100644 index 0000000..95c490f --- /dev/null +++ b/AutoRoute/Driver/DriverInterface.php @@ -0,0 +1,21 @@ + + */ +interface DriverInterface +{ + /** + * Return locales for object + * + * @return array + */ + public function getLocales($object); + + public function translateObject($object, $locale); +} diff --git a/AutoRoute/Driver/PhpcrOdmDriver.php b/AutoRoute/Driver/PhpcrOdmDriver.php new file mode 100644 index 0000000..9aa5378 --- /dev/null +++ b/AutoRoute/Driver/PhpcrOdmDriver.php @@ -0,0 +1,38 @@ +dm = $dm; + } + + public function getLocales($document) + { + if ($this->dm->isDocumentTranslatable($document)) { + return $this->dm->getLocalesFor($document); + } + + return array(); + } + + public function translateObject($document, $locale) + { + $meta = $this->dm->getMetadataFactory()->getMetadataFor(get_class($document)); + $this->dm->findTranslation(get_class($document), $meta->getIdentifierValue($document), $locale); + return $document; + } +} diff --git a/AutoRoute/PathProvider/LocaleProvider.php b/AutoRoute/PathProvider/LocaleProvider.php new file mode 100644 index 0000000..463a4da --- /dev/null +++ b/AutoRoute/PathProvider/LocaleProvider.php @@ -0,0 +1,39 @@ + + */ +class LocaleProvider implements PathProviderInterface +{ + public function init(array $options) + { + } + + public function providePath(RouteStack $routeStack) + { + $context = $routeStack->getContext(); + $locale = $context->getLocale(); + + if (!$locale) { + throw new \RuntimeException( + 'LocaleProvider requires that a locale is set on the BuilderContext' + ); + } + + $routeStack->addPathElements(array($locale)); + } +} diff --git a/Command/RefreshCommand.php b/Command/RefreshCommand.php index bc7a07d..2286471 100644 --- a/Command/RefreshCommand.php +++ b/Command/RefreshCommand.php @@ -78,23 +78,25 @@ public function execute(InputInterface $input, OutputInterface $output) foreach ($result as $autoRouteableDocument) { $id = $uow->getDocumentId($autoRouteableDocument); $output->writeln(' Refreshing: '.$id); - $context = $arm->updateAutoRouteForDocument($autoRouteableDocument); - - foreach ($context->getRoutes() as $route) { - $dm->persist($route); - $routeId = $uow->getDocumentId($route); - - if ($verbose) { - $output->writeln(sprintf( - ' - %sPersisting: %s %s', - $dryRun ? '(dry run) ' : '', - $routeId, - '[...]'.substr(get_class($route), -10) - )); - } - - if (true !== $dryRun) { - $dm->flush(); + $contexts = $arm->updateAutoRouteForDocument($autoRouteableDocument); + + foreach ($contexts as $context) { + foreach ($context->getRoutes() as $route) { + $dm->persist($route); + $routeId = $uow->getDocumentId($route); + + if ($verbose) { + $output->writeln(sprintf( + ' - %sPersisting: %s %s', + $dryRun ? '(dry run) ' : '', + $routeId, + '[...]'.substr(get_class($route), -10) + )); + } + + if (true !== $dryRun) { + $dm->flush(); + } } } } diff --git a/EventListener/AutoRouteListener.php b/EventListener/AutoRouteListener.php index 7497cf5..81b38ad 100644 --- a/EventListener/AutoRouteListener.php +++ b/EventListener/AutoRouteListener.php @@ -41,16 +41,35 @@ public function onFlush(ManagerEventArgs $args) foreach ($updates as $document) { if ($this->getArm()->isAutoRouteable($document)) { - $context = $this->getArm()->updateAutoRouteForDocument($document); - foreach ($context->getRoutes() as $route) { - $dm->persist($route); - - // this was originally computeSingleDocumentChangeset - // however this caused problems in a real usecase - // (functional tests were fine) - // - // this is probably not very efficient, but it works - $uow->computeChangeSets(); + $contexts = $this->getArm()->updateAutoRouteForDocument($document); + + $persistedRoutes = array(); + + foreach ($contexts as $context) { + foreach ($context->getRoutes() as $route) { + + if ($route instanceof AutoRoute) { + $routeParent = $route->getParent(); + $id = spl_object_hash($routeParent).$route->getName(); + } else { + $metadata = $dm->getClassMetadata(get_class($route)); + $id = $metadata->getIdentifierValue($route); + } + + if (isset($persistedRoutes[$id])) { + continue; + } + + $dm->persist($route); + $persistedRoutes[$id] = true; + + // this was originally computeSingleDocumentChangeset + // however this caused problems in a real usecase + // (functional tests were fine) + // + // this is probably not very efficient, but it works + $uow->computeChangeSets(); + } } } } diff --git a/Resources/config/auto_route.xml b/Resources/config/auto_route.xml index 63e649f..bee5292 100644 --- a/Resources/config/auto_route.xml +++ b/Resources/config/auto_route.xml @@ -10,6 +10,7 @@ Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\Factory Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\RouteStack\Builder Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\RoutePatcher\GenericPatcher + Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\Driver\PhpcrOdmDriver @@ -25,6 +26,7 @@ id="cmf_routing_auto.auto_route_manager" class="Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\AutoRouteManager" > + @@ -48,5 +50,8 @@ + + + diff --git a/Resources/config/path_provider.xml b/Resources/config/path_provider.xml index 7eeedd1..88a1f01 100644 --- a/Resources/config/path_provider.xml +++ b/Resources/config/path_provider.xml @@ -9,6 +9,7 @@ Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\PathProvider\ContentMethodProvider Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\PathProvider\ContentObjectProvider Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\PathProvider\ContentDateTimeProvider + Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\PathProvider\LocaleProvider @@ -48,5 +49,13 @@ + + + + diff --git a/Tests/Functional/EventListener/AutoRouteListenerTest.php b/Tests/Functional/EventListener/AutoRouteListenerTest.php index bbda0b3..860b1e7 100644 --- a/Tests/Functional/EventListener/AutoRouteListenerTest.php +++ b/Tests/Functional/EventListener/AutoRouteListenerTest.php @@ -5,6 +5,8 @@ use Symfony\Cmf\Bundle\RoutingAutoBundle\Tests\Functional\BaseTestCase; use Symfony\Cmf\Bundle\RoutingAutoBundle\Tests\Resources\Document\Blog; use Symfony\Cmf\Bundle\RoutingAutoBundle\Tests\Resources\Document\Post; +use Symfony\Cmf\Bundle\RoutingAutoBundle\Tests\Resources\Document\Article; +use Symfony\Cmf\Bundle\RoutingAutoBundle\Document\AutoRoute; class AutoRouteListenerTest extends BaseTestCase { @@ -19,7 +21,6 @@ protected function createBlog($withPosts = false) if ($withPosts) { $post = new Post; $post->title = 'This is a post title'; - $post->body = 'Test Body'; $post->blog = $blog; $this->getDm()->persist($post); } @@ -157,4 +158,58 @@ public function testUpdatePost() $this->assertInstanceOf('Symfony\Cmf\Bundle\RoutingAutoBundle\Document\AutoRoute', $routes[0]); $this->assertEquals('this-is-different', $routes[0]->getName()); } + + public function provideMultilangArticle() + { + return array( + array( + array( + 'en' => 'Hello everybody!', + 'fr' => 'Bonjour le monde!', + 'de' => 'Gutentag', + 'es' => 'Hola todo el mundo', + ), + array( + 'test/auto-route/articles/en/hello-everybody', + 'test/auto-route/articles/fr/bonjour-le-monde', + 'test/auto-route/articles/de/gutentag', + 'test/auto-route/articles/es/hola-todo-el-mundo', + ), + ), + ); + } + + /** + * @dataProvider provideMultilangArticle + */ + public function testMultilangArticle($data, $expectedPaths) + { + $article = new Article; + $article->path = '/test/article-1'; + $this->getDm()->persist($article); + + foreach ($data as $lang => $title) { + $article->title = $title; + $this->getDm()->bindTranslation($article, $lang); + } + + $this->getDm()->flush(); + $this->getDm()->clear(); + + $articleTitles = array_values($data); + foreach ($expectedPaths as $i => $expectedPath) { + $route = $this->getDm()->find(null, $expectedPath); + + $this->assertNotNull($route); + $this->assertInstanceOf('Symfony\Cmf\Bundle\RoutingAutoBundle\Document\AutoRoute', $route); + + $content = $route->getContent(); + + $this->assertNotNull($content); + $this->assertInstanceOf('Symfony\Cmf\Bundle\RoutingAutoBundle\Tests\Resources\Document\Article', $content); + + // We havn't loaded the translation for the document, so it is always in the default language + $this->assertEquals('Hello everybody!', $content->title); + } + } } diff --git a/Tests/Resources/Document/Article.php b/Tests/Resources/Document/Article.php new file mode 100644 index 0000000..a44ac7e --- /dev/null +++ b/Tests/Resources/Document/Article.php @@ -0,0 +1,41 @@ +title; + } +} + diff --git a/Tests/Resources/Document/Post.php b/Tests/Resources/Document/Post.php index 30be9e1..94744f7 100644 --- a/Tests/Resources/Document/Post.php +++ b/Tests/Resources/Document/Post.php @@ -36,7 +36,7 @@ class Post public $title; /** - * @PHPCR\String + * @PHPCR\String(nullable=true) */ public $body; diff --git a/Tests/Resources/app/config/routingautoroute.yml b/Tests/Resources/app/config/routingautoroute.yml index bb391ec..50813da 100644 --- a/Tests/Resources/app/config/routingautoroute.yml +++ b/Tests/Resources/app/config/routingautoroute.yml @@ -63,3 +63,20 @@ cmf_routing_auto: provider: [ content_method, { method: getTitle } ] exists_action: [ auto_increment, { pattern: -%d } ] not_exists_action: [ create ] + + Symfony\Cmf\Bundle\RoutingAutoBundle\Tests\Resources\Document\Article: + + content_path: + path_units: + base: + provider: [ specified, { path: test/auto-route/articles } ] + exists_action: use + not_exists_action: create + locale: + provider: [ locale ] + exists_action: use + not_exists_action: create + content_name: + provider: [ content_method, { method: getTitle } ] + exists_action: [ auto_increment, { pattern: -%d } ] + not_exists_action: create diff --git a/Tests/Unit/AutoRoute/AutoRouteManagerTest.php b/Tests/Unit/AutoRoute/AutoRouteManagerTest.php index c377885..e8f66bb 100644 --- a/Tests/Unit/AutoRoute/AutoRouteManagerTest.php +++ b/Tests/Unit/AutoRoute/AutoRouteManagerTest.php @@ -16,7 +16,11 @@ public function setUp() 'Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\RouteStack\Builder' )->disableOriginalConstructor()->getMock(); - $this->arm = new AutoRouteManager($this->factory, $this->builder); + $this->driver = $this->getMock( + 'Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\Driver\DriverInterface' + ); + + $this->arm = new AutoRouteManager($this->driver, $this->factory, $this->builder); $this->builderUnitChain = $this->getMockBuilder( 'Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\RouteStack\BuilderUnitChain' diff --git a/Tests/Unit/AutoRoute/PathProvider/LocaleProviderTest.php b/Tests/Unit/AutoRoute/PathProvider/LocaleProviderTest.php new file mode 100644 index 0000000..7cec641 --- /dev/null +++ b/Tests/Unit/AutoRoute/PathProvider/LocaleProviderTest.php @@ -0,0 +1,53 @@ +builderContext = $this->getMock( + 'Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\BuilderContext' + ); + $this->routeStack = $this->getMockBuilder( + 'Symfony\Cmf\Bundle\RoutingAutoBundle\AutoRoute\RouteStack' + )->disableOriginalConstructor()->getMock(); + + + $this->provider = new LocaleProvider(); + } + + /** + * @expectedException RuntimeException + */ + public function testProvidePathNoLocale() + { + $this->routeStack->expects($this->once()) + ->method('getContext') + ->will($this->returnValue($this->builderContext)); + $this->provider->providePath($this->routeStack); + } + + public function testProvidePath() + { + $this->routeStack->expects($this->once()) + ->method('getContext') + ->will($this->returnValue($this->builderContext)); + $this->builderContext->expects($this->once()) + ->method('getLocale') + ->will($this->returnValue('fr')); + + $this->routeStack->expects($this->once()) + ->method('addPathElements') + ->with(array('fr')); + + $res = $this->provider->providePath($this->routeStack); + } +} + diff --git a/composer.json b/composer.json index d121e6f..2eca4f2 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "require": { "symfony-cmf/routing-bundle": "1.1.*", "symfony-cmf/core-bundle": "1.0.*", - "aferrandini/urlizer": "1.0.*" + "aferrandini/urlizer": "1.0.*", + "doctrine/phpcr-odm": "dev-master as 1.0.x-dev" }, "require-dev": { "symfony-cmf/testing": "1.0.*",