diff --git a/Command/DumpCommand.php b/Command/DumpCommand.php index 40fdfd42..d6d9c514 100644 --- a/Command/DumpCommand.php +++ b/Command/DumpCommand.php @@ -101,6 +101,13 @@ protected function configure() InputOption::VALUE_NONE, 'Pretty print the JSON.' ) + ->addOption( + 'domain', + null, + InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, + 'Specify expose domain', + array() + ) ; } @@ -132,12 +139,15 @@ protected function execute(InputInterface $input, OutputInterface $output) */ private function doDump(InputInterface $input, OutputInterface $output) { + $domain = $input->getOption('domain'); + $extractor = $this->extractor; $serializer = $this->serializer; $targetPath = $input->getOption('target') ?: sprintf( - '%s/../web/js/fos_js_routes.%s', + '%s/../web/js/fos_js_routes%s.%s', $this->rootDir, + empty($domain) ? '' : ('_' . implode('_', $domain)), $input->getOption('format') ); @@ -168,7 +178,8 @@ private function doDump(InputInterface $input, OutputInterface $output) $extractor->getPrefix($input->getOption('locale')), $extractor->getHost(), $extractor->getPort(), - $extractor->getScheme() + $extractor->getScheme(), + $domain ), 'json', $params diff --git a/Command/RouterDebugExposedCommand.php b/Command/RouterDebugExposedCommand.php index 42fbd1bc..caac0fc2 100644 --- a/Command/RouterDebugExposedCommand.php +++ b/Command/RouterDebugExposedCommand.php @@ -20,6 +20,7 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouterInterface; +use Symfony\Component\Routing\RouteCollection; /** * A console command for retrieving information about exposed routes. @@ -54,6 +55,7 @@ protected function configure() new InputOption('show-controllers', null, InputOption::VALUE_NONE, 'Show assigned controllers in overview'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw route(s)'), + new InputOption('domain', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify expose domain', array()) )) ->setName('fos:js-routing:debug') ->setDescription('Displays currently exposed routes for an application') @@ -96,11 +98,35 @@ protected function execute(InputInterface $input, OutputInterface $output) )); } else { $helper = new DescriptorHelper(); - $helper->describe($output, $this->extractor->getRoutes(), array( + $helper->describe($output, $this->getRoutes($input->getOption('domain')), array( 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), 'show_controllers' => $input->getOption('show-controllers'), )); } } + + protected function getRoutes($domain = array()) + { + $routes = $this->extractor->getRoutes(); + + if (empty($domain)) { + return $routes; + } + + $targetRoutes = new RouteCollection(); + + foreach ($routes as $name => $route) { + + $expose = $route->getOption('expose'); + $expose = is_string($expose) ? ($expose === 'true' ? 'default' : $expose) : 'default'; + + if (in_array($expose, $domain, true)) { + $targetRoutes->add($name, $route); + } + + } + + return $targetRoutes; + } } diff --git a/Controller/Controller.php b/Controller/Controller.php index 7e4124be..c5c7c154 100644 --- a/Controller/Controller.php +++ b/Controller/Controller.php @@ -98,7 +98,8 @@ public function indexAction(Request $request, $_format) $this->exposedRoutesExtractor->getHost(), $this->exposedRoutesExtractor->getPort(), $this->exposedRoutesExtractor->getScheme(), - $request->getLocale() + $request->getLocale(), + $request->query->has('domain') ? explode(',', $request->query->get('domain')) : array() ); $content = $this->serializer->serialize($routesResponse, 'json'); diff --git a/Extractor/ExposedRoutesExtractor.php b/Extractor/ExposedRoutesExtractor.php index 7e26924f..48f62730 100644 --- a/Extractor/ExposedRoutesExtractor.php +++ b/Extractor/ExposedRoutesExtractor.php @@ -38,25 +38,37 @@ class ExposedRoutesExtractor implements ExposedRoutesExtractorInterface */ protected $bundles; + /** + * @var string + */ + protected $pattern; + /** * @var array */ - protected $routesToExpose; + protected $availableDomains; /** * Default constructor. * - * @param RouterInterface $router The router. - * @param array $routesToExpose Some route names to expose. - * @param string $cacheDir - * @param array $bundles list of loaded bundles to check when generating the prefix + * @param RouterInterface $router The router. + * @param array $routesToExpose Some route names to expose. + * @param string $cacheDir + * @param array $bundles list of loaded bundles to check when generating the prefix + * + * @throws \Exception */ public function __construct(RouterInterface $router, array $routesToExpose = array(), $cacheDir, $bundles = array()) { $this->router = $router; - $this->routesToExpose = $routesToExpose; $this->cacheDir = $cacheDir; $this->bundles = $bundles; + + $domainPatterns = $this->extractDomainPatterns($routesToExpose); + + $this->availableDomains = array_keys($domainPatterns); + + $this->pattern = $this->buildPattern($domainPatterns); } /** @@ -69,9 +81,27 @@ public function getRoutes() /** @var Route $route */ foreach ($collection->all() as $name => $route) { - if ($this->isRouteExposed($route, $name)) { + + if ($route->hasOption('expose')) { $routes->add($name, $route); + continue; } + + preg_match('#' . $this->pattern . '#', $name, $matches); + + if (count($matches) === 0) { + continue; + } + + $domain = $this->getDomainByRouteMatches($matches, $name); + + if (is_null($domain)) { + continue; + } + + $route = clone $route; + $route->setOption('expose', $domain); + $routes->add($name, $route); } return $routes; @@ -166,23 +196,64 @@ public function getResources() */ public function isRouteExposed(Route $route, $name) { - $pattern = $this->buildPattern(); + return true === $route->hasOption('expose') || + ('' !== $this->pattern && preg_match('#' . $this->pattern . '#', $name)); + } - return true === $route->getOption('expose') - || 'true' === $route->getOption('expose') - || ('' !== $pattern && preg_match('#' . $pattern . '#', $name)); + protected function getDomainByRouteMatches($matches, $name) + { + $matches = array_filter($matches, function($match) { + return !empty($match); + }); + + $matches = array_flip(array_intersect_key($matches, array_flip($this->availableDomains))); + + return isset($matches[$name]) ? $matches[$name] : null; + } + + protected function extractDomainPatterns($routesToExpose) + { + $domainPatterns = array(); + + foreach ($routesToExpose as $item) { + + if (is_string($item)) { + $domainPatterns['default'][] = $item; + continue; + } + + if (is_array($item) && is_string($item['pattern'])) { + + if (!isset($item['domain'])) { + $domainPatterns['default'][] = $item['pattern']; + continue; + } elseif (is_string($item['domain'])) { + $domainPatterns[$item['domain']][] = $item['pattern']; + continue; + } + + } + + throw new \Exception('routes_to_expose definition is invalid'); + } + + return $domainPatterns; } /** * Convert the routesToExpose array in a regular expression pattern * + * @param $domainPatterns * @return string + * @throws \Exception */ - protected function buildPattern() + protected function buildPattern($domainPatterns) { $patterns = array(); - foreach ($this->routesToExpose as $toExpose) { - $patterns[] = '(' . $toExpose . ')'; + + foreach ($domainPatterns as $domain => $items) { + + $patterns[] = '(?P<' . $domain . '>' . implode($items, '|') . ')'; } return implode($patterns, '|'); diff --git a/Response/RoutesResponse.php b/Response/RoutesResponse.php index 538be722..1f9e7564 100644 --- a/Response/RoutesResponse.php +++ b/Response/RoutesResponse.php @@ -22,8 +22,10 @@ class RoutesResponse private $port; private $scheme; private $locale; + private $domains; - public function __construct($baseUrl, RouteCollection $routes = null, $prefix = null, $host = null, $port = null, $scheme = null, $locale = null) + public function __construct($baseUrl, RouteCollection $routes = null, $prefix = null, $host = null, $port = null, + $scheme = null, $locale = null, $domains = array()) { $this->baseUrl = $baseUrl; $this->routes = $routes ?: new RouteCollection(); @@ -32,6 +34,7 @@ public function __construct($baseUrl, RouteCollection $routes = null, $prefix = $this->port = $port; $this->scheme = $scheme; $this->locale = $locale; + $this->domains = $domains; } public function getBaseUrl() @@ -43,6 +46,23 @@ public function getRoutes() { $exposedRoutes = array(); foreach ($this->routes->all() as $name => $route) { + + if (!$route->hasOption('expose')) { + $domain = 'default'; + } else { + $domain = $route->getOption('expose'); + $domain = is_string($domain) ? ($domain === 'true' ? 'default' : $domain) : 'default'; + } + + + if (count($this->domains) === 0) { + if ($domain !== 'default') { + continue; + } + } elseif (!in_array($domain, $this->domains, true)) { + continue; + } + $compiledRoute = $route->compile(); $defaults = array_intersect_key( $route->getDefaults(), diff --git a/Tests/Controller/ControllerTest.php b/Tests/Controller/ControllerTest.php index 10181c4e..50ebd495 100644 --- a/Tests/Controller/ControllerTest.php +++ b/Tests/Controller/ControllerTest.php @@ -157,6 +157,49 @@ public function testCacheControl() $this->assertEquals(456, $response->headers->getCacheControlDirective('s-maxage')); } + public function testExposeDomain() + { + $routes = new RouteCollection(); + $routes->add('homepage', new Route('/')); + $routes->add('admin_index', new Route('/admin', array(), array(), + array('expose' => 'admin'))); + $routes->add('admin_pages', new Route('/admin/path', array(), array(), + array('expose' => 'admin'))); + $routes->add('blog_index', new Route('/blog', array(), array(), + array('expose' => 'blog'), 'localhost')); + $routes->add('blog_post', new Route('/blog/{slug}', array(), array(), + array('expose' => 'blog'), 'localhost')); + + $controller = new Controller( + $this->getSerializer(), + $this->getExtractor($routes) + ); + + $response = $controller->indexAction($this->getRequest('/'), 'json'); + + $this->assertEquals('{"base_url":"","routes":{"homepage":{"tokens":[["text","\/"]],"defaults":[],"requirements":[],"hosttokens":[],"methods":[],"schemes":[]}},"prefix":"","host":"","port":null,"scheme":""}', $response->getContent()); + + $response = $controller->indexAction($this->getRequest('/', + 'GET', array('domain' => 'admin')), 'json'); + + $this->assertEquals('{"base_url":"","routes":{"admin_index":{"tokens":[["text","\/admin"]],"defaults":[],"requirements":[],"hosttokens":[],"methods":[],"schemes":[]},"admin_pages":{"tokens":[["text","\/admin\/path"]],"defaults":[],"requirements":[],"hosttokens":[],"methods":[],"schemes":[]}},"prefix":"","host":"","port":null,"scheme":""}', $response->getContent()); + + $response = $controller->indexAction($this->getRequest('/', + 'GET', array('domain' => 'blog')), 'json'); + + $this->assertEquals('{"base_url":"","routes":{"blog_index":{"tokens":[["text","\/blog"]],"defaults":[],"requirements":[],"hosttokens":[["text","localhost"]],"methods":[],"schemes":[]},"blog_post":{"tokens":[["variable","\/","[^\/]++","slug"],["text","\/blog"]],"defaults":[],"requirements":[],"hosttokens":[["text","localhost"]],"methods":[],"schemes":[]}},"prefix":"","host":"","port":null,"scheme":""}', $response->getContent()); + + $response = $controller->indexAction($this->getRequest('/', + 'GET', array('domain' => 'admin,blog')), 'json'); + + $this->assertEquals('{"base_url":"","routes":{"admin_index":{"tokens":[["text","\/admin"]],"defaults":[],"requirements":[],"hosttokens":[],"methods":[],"schemes":[]},"admin_pages":{"tokens":[["text","\/admin\/path"]],"defaults":[],"requirements":[],"hosttokens":[],"methods":[],"schemes":[]},"blog_index":{"tokens":[["text","\/blog"]],"defaults":[],"requirements":[],"hosttokens":[["text","localhost"]],"methods":[],"schemes":[]},"blog_post":{"tokens":[["variable","\/","[^\/]++","slug"],["text","\/blog"]],"defaults":[],"requirements":[],"hosttokens":[["text","localhost"]],"methods":[],"schemes":[]}},"prefix":"","host":"","port":null,"scheme":""}', $response->getContent()); + + $response = $controller->indexAction($this->getRequest('/', + 'GET', array('domain' => 'default,admin,blog')), 'json'); + + $this->assertEquals('{"base_url":"","routes":{"homepage":{"tokens":[["text","\/"]],"defaults":[],"requirements":[],"hosttokens":[],"methods":[],"schemes":[]},"admin_index":{"tokens":[["text","\/admin"]],"defaults":[],"requirements":[],"hosttokens":[],"methods":[],"schemes":[]},"admin_pages":{"tokens":[["text","\/admin\/path"]],"defaults":[],"requirements":[],"hosttokens":[],"methods":[],"schemes":[]},"blog_index":{"tokens":[["text","\/blog"]],"defaults":[],"requirements":[],"hosttokens":[["text","localhost"]],"methods":[],"schemes":[]},"blog_post":{"tokens":[["variable","\/","[^\/]++","slug"],["text","\/blog"]],"defaults":[],"requirements":[],"hosttokens":[["text","localhost"]],"methods":[],"schemes":[]}},"prefix":"","host":"","port":null,"scheme":""}', $response->getContent()); + } + private function getExtractor(RouteCollection $exposedRoutes = null, $baseUrl = '') { if (null === $exposedRoutes) { diff --git a/Tests/Extractor/ExposedRoutesExtractorTest.php b/Tests/Extractor/ExposedRoutesExtractorTest.php index 0fa0a9d0..31d7aea5 100644 --- a/Tests/Extractor/ExposedRoutesExtractorTest.php +++ b/Tests/Extractor/ExposedRoutesExtractorTest.php @@ -44,6 +44,9 @@ public function testGetRoutes() $router = $this->getRouter($expected); $extractor = new ExposedRoutesExtractor($router, array('.*'), $this->cacheDir, array()); + + $expected->addOptions(array('expose' => 'default')); + $this->assertEquals($expected, $extractor->getRoutes()); }