diff --git a/BrauneDigitalTranslationBaseBundle.php b/BrauneDigitalTranslationBaseBundle.php index adec3c4..b25b39c 100644 --- a/BrauneDigitalTranslationBaseBundle.php +++ b/BrauneDigitalTranslationBaseBundle.php @@ -2,8 +2,15 @@ namespace BrauneDigital\TranslationBaseBundle; +use BrauneDigital\TranslationBaseBundle\DependencyInjection\Compiler\RouterInjectionPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; class BrauneDigitalTranslationBaseBundle extends Bundle { + public function build(ContainerBuilder $container) + { + parent::build($container); + $container->addCompilerPass(new RouterInjectionPass()); + } } diff --git a/Command/ImportLanguagesCommand.php b/Command/ImportLanguagesCommand.php index 3453c81..bd80935 100644 --- a/Command/ImportLanguagesCommand.php +++ b/Command/ImportLanguagesCommand.php @@ -2,7 +2,6 @@ namespace BrauneDigital\TranslationBaseBundle\Command; -use Likez\BaseBundle\Entity\OperatorMail; use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputArgument; @@ -10,8 +9,6 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Likez\BaseBundle\Entity\Mail; - class ImportLanguagesCommand extends ContainerAwareCommand { protected function configure() diff --git a/DependencyInjection/BrauneDigitalTranslationBaseExtension.php b/DependencyInjection/BrauneDigitalTranslationBaseExtension.php index d41286f..c1c41bf 100644 --- a/DependencyInjection/BrauneDigitalTranslationBaseExtension.php +++ b/DependencyInjection/BrauneDigitalTranslationBaseExtension.php @@ -20,8 +20,20 @@ class BrauneDigitalTranslationBaseExtension extends Extension public function load(array $configs, ContainerBuilder $container) { $configuration = new Configuration(); - $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); - $loader->load('services.yml'); + + $config = $this->processConfiguration($configuration, $configs); + + $bundles = $container->getParameter('kernel.bundles'); + + + if($config['routing']) { + $loader->load('routing_services.yml'); + $container->setParameter('braune_digital.translation_base.use_routing', true); + } + + if($config['admin'] && isset($bundles['SonataAdminBundle'])) { + $loader->load('admin.yml'); + } } -} +} \ No newline at end of file diff --git a/DependencyInjection/Compiler/RouterInjectionPass.php b/DependencyInjection/Compiler/RouterInjectionPass.php new file mode 100644 index 0000000..ee1120f --- /dev/null +++ b/DependencyInjection/Compiler/RouterInjectionPass.php @@ -0,0 +1,24 @@ +hasParameter('braune_digital.translation_base.use_routing') && $container->getParameter('braune_digital.translation_base.use_routing')) { + $container->setAlias('router', 'braune_digital.translation_base.routing.service_router'); + } + } +} \ No newline at end of file diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index c7cd038..6dd511f 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -2,6 +2,7 @@ namespace BrauneDigital\TranslationBaseBundle\DependencyInjection; +use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -18,9 +19,25 @@ class Configuration implements ConfigurationInterface public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('braunedigital_mail'); + $rootNode = $treeBuilder->root('braune_digital_translation_base'); + $this->addAdminSection($rootNode); + $this->addRoutingSection($rootNode); return $treeBuilder; } + + /** + * @param $rootNode + */ + protected function addAdminSection(ArrayNodeDefinition $rootNode) { + $rootNode->children()->booleanNode('admin')->defaultTrue(); + } + + /** + * @param $rootNode + */ + protected function addRoutingSection(ArrayNodeDefinition $rootNode) { + $rootNode->children()->booleanNode('routing')->defaultFalse(); + } } diff --git a/Form/TranslationType.php b/Form/TranslationType.php index 5514048..614be42 100644 --- a/Form/TranslationType.php +++ b/Form/TranslationType.php @@ -2,7 +2,6 @@ namespace BrauneDigital\TranslationBaseBundle\Form; -use Application\AppBundle\Services\LocaleOptions; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormInterface; @@ -44,11 +43,7 @@ public function __construct(RequestStack $requestStack, Container $container) { $this->container = $container; // Get current locale - if ($this->request->get('object_locale')) { - $this->currentLocale = array($this->request->get('object_locale')); - } else { - $this->currentLocale = array('en'); - } + $this->currentLocale = $this->request->get('object_locale', $this->container->getParameter('locale')); } /** @@ -72,10 +67,15 @@ public function buildTranslations(FormBuilderInterface $builder) { } public function getDisabled(FormBuilderInterface $builder) { - if (!$builder->getData()->getDefaultLanguage()) { - return false; + if(method_exists($builder->getData(), 'getDefaultLanguage')) { + + if (!$builder->getData()->getDefaultLanguage()) { + return false; + } + return ($this->currentLocale == $builder->getData()->getDefaultLanguage()->getCode()) ? false : true; + } else { + return $this->currentLocale != $this->container->getParameter('locale'); } - return ($this->currentLocale[0] == $builder->getData()->getDefaultLanguage()->getCode()) ? false : true; } /** @@ -90,6 +90,4 @@ public function buildView(FormView $view, FormInterface $form, array $options) $view->vars['currentTranslation'] = $this->container->get('doctrine')->getRepository('BrauneDigitalTranslationBaseBundle:Language')->findOneBy(array('code' => $this->request->get('object_locale'))); } - - } diff --git a/Model/Translatable/TranslatableMethods.php b/Model/Translatable/TranslatableMethods.php index 6ef62a9..c574101 100644 --- a/Model/Translatable/TranslatableMethods.php +++ b/Model/Translatable/TranslatableMethods.php @@ -144,4 +144,39 @@ public function setNewTranslations($newTranslations) { $this->newTranslations = $newTranslations; } + + /** + * @param $name + * generic translation + * @return mixed + */ + public function translateProperty($name) { + $functionName = 'get' . ucfirst($name); + $value = $this->translate()->$functionName(); + + if ($value) { + return $value; + } else { + return $this->translate($this->getDefaultLocale())->$functionName(); + } + } + + /** + * @param $name + * proxy getter for translation values + * @return mixed + */ + public function __get($name) { + return $this->translateProperty($name); + } + + /** + * @param $name + * proxy setter for translation values + * @return mixed + */ + public function __set($name, $value) { + $functionName = 'set' . ucfirst($name); + $value = $this->translate()->$functionName($value); + } } diff --git a/README.md b/README.md index 68c804d..7ddeb0e 100644 --- a/README.md +++ b/README.md @@ -213,3 +213,4 @@ class EntityAdmin extends TranslationAdmin This Bundle also provides a basic Language Entity, which can be managed through SonataAdmin as well. ## Todo Add requirements with versions in `composer.json`. +A caching mechanism is needed for the service Router. The Symfony2 router creates own instances and caches them. The service Router however is not able to do that, but maybe a RouteCollection cache would speed things up ;) diff --git a/Resources/config/services.yml b/Resources/config/admin.yml similarity index 100% rename from Resources/config/services.yml rename to Resources/config/admin.yml diff --git a/Resources/config/routing_services.yml b/Resources/config/routing_services.yml new file mode 100644 index 0000000..9015224 --- /dev/null +++ b/Resources/config/routing_services.yml @@ -0,0 +1,34 @@ +parameters: + braune_digital.translation_base.routing.locales: "%locales%" + braune_digital.translation_base.routing.default_locale: "%locale%" + +services: + braune_digital.translation_base.routing.generator: + class: "BrauneDigital\\TranslationBaseBundle\\Routing\\LocalizedUrlGenerator" + arguments: + - "@logger" + - "%braune_digital.translation_base.routing.locales%" + - "%braune_digital.translation_base.routing.default_locale%" + + braune_digital.translation_base.routing.matcher: + class: "BrauneDigital\\TranslationBaseBundle\\Routing\\LocalizedUrlMatcher" + arguments: + - "%braune_digital.translation_base.routing.locales%" + - "%braune_digital.translation_base.routing.default_locale%" + + braune_digital.translation_base.routing.service_router: + class: "BrauneDigital\\TranslationBaseBundle\\Routing\\ServiceRouter" + tags: + - {name: "monolog.logger", channel: "router"} + arguments: + - "@service_container" + - "%router.resource%" + - "@router.request_context" + - "braune_digital.translation_base.routing.generator" + - "braune_digital.translation_base.routing.matcher" + + braune_digital.translation_base.routing.loader.yaml: + class: "BrauneDigital\\TranslationBaseBundle\\Routing\\YamlLocalizedLoader" + arguments: ["@file_locator", "%braune_digital.translation_base.routing.locales%"] + tags: + - { name: routing.loader } \ No newline at end of file diff --git a/Resources/views/admin/translation_layout.html.twig b/Resources/views/admin/translation_layout.html.twig index df10a27..1c11ea2 100644 --- a/Resources/views/admin/translation_layout.html.twig +++ b/Resources/views/admin/translation_layout.html.twig @@ -1,4 +1,4 @@ -{% extends "SonataAdminBundle:standard_layout.html.twig" %} +{% extends "SonataAdminBundle::standard_layout.html.twig" %} {% block sonata_page_content_nav %} {% if _tab_menu is not empty or _actions is not empty %} diff --git a/Routing/LocalizedRouteCompiler.php b/Routing/LocalizedRouteCompiler.php new file mode 100644 index 0000000..8e6b2b0 --- /dev/null +++ b/Routing/LocalizedRouteCompiler.php @@ -0,0 +1,228 @@ +.+)". + */ + public static function compile(Route $route) + { + $hostVariables = array(); + $variables = array(); + $hostRegex = null; + $hostTokens = array(); + + if ('' !== $host = $route->getHost()) { + $result = self::compilePattern($route, $host, true); + + $hostVariables = $result['variables']; + $variables = $hostVariables; + + $hostTokens = $result['tokens']; + $hostRegex = $result['regex']; + } + + $path = $route->getPath(); + + $result = self::compilePattern($route, $path, false); + + $staticPrefix = $result['staticPrefix']; + + $pathVariables = $result['variables']; + $variables = array_merge($variables, $pathVariables); + + $tokens = $result['tokens']; + $regex = $result['regex']; + + return new CompiledRoute( + $staticPrefix, + $regex, + $tokens, + $pathVariables, + $hostRegex, + $hostTokens, + $hostVariables, + array_unique($variables) + ); + } + + private static function compilePattern(Route $route, $pattern, $isHost) + { + $tokens = array(); + $variables = array(); + $matches = array(); + $pos = 0; + $defaultSeparator = $isHost ? '.' : '/'; + + // Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable + // in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself. + preg_match_all('#\{\w+\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + foreach ($matches as $match) { + $varName = substr($match[0][0], 1, -1); + // get all static text preceding the current variable + $precedingText = substr($pattern, $pos, $match[0][1] - $pos); + $pos = $match[0][1] + strlen($match[0][0]); + $precedingChar = strlen($precedingText) > 0 ? substr($precedingText, -1) : ''; + $isSeparator = '' !== $precedingChar && false !== strpos(static::SEPARATORS, $precedingChar); + + if (is_numeric($varName)) { + throw new \DomainException(sprintf('Variable name "%s" cannot be numeric in route pattern "%s". Please use a different name.', $varName, $pattern)); + } + if (in_array($varName, $variables)) { + throw new \LogicException(sprintf('Route pattern "%s" cannot reference variable name "%s" more than once.', $pattern, $varName)); + } + + if ($isSeparator && strlen($precedingText) > 1) { + $tokens[] = array('text', substr($precedingText, 0, -1)); + } elseif (!$isSeparator && strlen($precedingText) > 0) { + $tokens[] = array('text', $precedingText); + } + + $regexp = $route->getRequirement($varName); + if (null === $regexp) { + $followingPattern = (string) substr($pattern, $pos); + // Find the next static character after the variable that functions as a separator. By default, this separator and '/' + // are disallowed for the variable. This default requirement makes sure that optional variables can be matched at all + // and that the generating-matching-combination of URLs unambiguous, i.e. the params used for generating the URL are + // the same that will be matched. Example: new Route('/{page}.{_format}', array('_format' => 'html')) + // If {page} would also match the separating dot, {_format} would never match as {page} will eagerly consume everything. + // Also even if {_format} was not optional the requirement prevents that {page} matches something that was originally + // part of {_format} when generating the URL, e.g. _format = 'mobile.html'. + $nextSeparator = self::findNextSeparator($followingPattern); + $regexp = sprintf( + '[^%s%s]+', + preg_quote($defaultSeparator, self::REGEX_DELIMITER), + $defaultSeparator !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator, self::REGEX_DELIMITER) : '' + ); + if (('' !== $nextSeparator && !preg_match('#^\{\w+\}#', $followingPattern)) || '' === $followingPattern) { + // When we have a separator, which is disallowed for the variable, we can optimize the regex with a possessive + // quantifier. This prevents useless backtracking of PCRE and improves performance by 20% for matching those patterns. + // Given the above example, there is no point in backtracking into {page} (that forbids the dot) when a dot must follow + // after it. This optimization cannot be applied when the next char is no real separator or when the next variable is + // directly adjacent, e.g. '/{x}{y}'. + $regexp .= '+'; + } + } + + $tokens[] = array('variable', $isSeparator ? $precedingChar : '', $regexp, $varName); + $variables[] = $varName; + } + + if ($pos < strlen($pattern)) { + $tokens[] = array('text', substr($pattern, $pos)); + } + + // find the first optional token + $firstOptional = PHP_INT_MAX; + if (!$isHost) { + for ($i = count($tokens) - 1; $i >= 0; --$i) { + $token = $tokens[$i]; + if ('variable' === $token[0] && $route->hasDefault($token[3])) { + $firstOptional = $i; + } else { + break; + } + } + } + + // compute the matching regexp + $regexp = ''; + for ($i = 0, $nbToken = count($tokens); $i < $nbToken; ++$i) { + $regexp .= self::computeRegexp($tokens, $i, $firstOptional); + } + + + return array( + 'staticPrefix' => 'text' === $tokens[0][0] ? $tokens[0][1] : '', + 'regex' => self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'s'.($isHost ? 'i' : ''), + 'tokens' => array_reverse($tokens), + 'variables' => $variables, + ); + } + + /** + * Returns the next static character in the Route pattern that will serve as a separator. + * + * @param string $pattern The route pattern + * + * @return string The next static character that functions as separator (or empty string when none available) + */ + private static function findNextSeparator($pattern) + { + if ('' == $pattern) { + // return empty string if pattern is empty or false (false which can be returned by substr) + return ''; + } + // first remove all placeholders from the pattern so we can find the next real static character + $pattern = preg_replace('#\{\w+\}#', '', $pattern); + + return isset($pattern[0]) && false !== strpos(static::SEPARATORS, $pattern[0]) ? $pattern[0] : ''; + } + + /** + * Computes the regexp used to match a specific token. It can be static text or a subpattern. + * + * @param array $tokens The route tokens + * @param int $index The index of the current token + * @param int $firstOptional The index of the first optional token + * + * @return string The regexp pattern for a single token + */ + private static function computeRegexp(array $tokens, $index, $firstOptional) + { + $token = $tokens[$index]; + if ('text' === $token[0]) { + // Text tokens + return preg_quote($token[1], self::REGEX_DELIMITER); + } else { + // Variable tokens + if (0 === $index && 0 === $firstOptional) { + // When the only token is an optional variable token, the separator is required + return sprintf('%s(?P<%s>%s)?', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]); + } else { + + $basePattern = '%s(?P<%s>%s)'; + + if($token[3] == '_locale') { + $basePattern = '(%s(?P<%s>%s))?'; + } + + $regexp = sprintf($basePattern, preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]); + + if ($index >= $firstOptional) { + // Enclose each optional token in a subpattern to make it optional. + // "?:" means it is non-capturing, i.e. the portion of the subject string that + // matched the optional subpattern is not passed back. + $regexp = "(?:$regexp"; + $nbTokens = count($tokens); + if ($nbTokens - 1 == $index) { + // Close the optional subpatterns + $regexp .= str_repeat(')?', $nbTokens - $firstOptional - (0 === $firstOptional ? 1 : 0)); + } + } + + + return $regexp; + } + } + } +} \ No newline at end of file diff --git a/Routing/LocalizedUrlGenerator.php b/Routing/LocalizedUrlGenerator.php new file mode 100644 index 0000000..10f66f7 --- /dev/null +++ b/Routing/LocalizedUrlGenerator.php @@ -0,0 +1,198 @@ +logger = $logger; + $this->locales = $locales; + $this->defaultLocale = $defaultLocale; + } + + /** + * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route + * @throws InvalidParameterException When a parameter value for a placeholder is not correct because + * it does not match the requirement + */ + protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, array $requiredSchemes = array()) + { + if (is_bool($referenceType) || is_string($referenceType)) { + @trigger_error('The hardcoded value you are using for the $referenceType argument of the '.__CLASS__.'::generate method is deprecated since version 2.8 and will not be supported anymore in 3.0. Use the constants defined in the UrlGeneratorInterface instead.', E_USER_DEPRECATED); + + if (true === $referenceType) { + $referenceType = self::ABSOLUTE_URL; + } elseif (false === $referenceType) { + $referenceType = self::ABSOLUTE_PATH; + } elseif ('relative' === $referenceType) { + $referenceType = self::RELATIVE_PATH; + } elseif ('network' === $referenceType) { + $referenceType = self::NETWORK_PATH; + } + } + + $variables = array_flip($variables); + $mergedParams = array_replace($defaults, $this->context->getParameters(), $parameters); + + // all params must be given + if ($diff = array_diff_key($variables, $mergedParams)) { + throw new MissingMandatoryParametersException(sprintf('Some mandatory parameters are missing ("%s") to generate a URL for route "%s".', implode('", "', array_keys($diff)), $name)); + } + + $url = ''; + $optional = true; + foreach ($tokens as $token) { + if ('variable' === $token[0]) { + if (!$optional || !array_key_exists($token[3], $defaults) || null !== $mergedParams[$token[3]] && (string) $mergedParams[$token[3]] !== (string) $defaults[$token[3]]) { + // check requirement + if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#', $mergedParams[$token[3]])) { + $message = sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given) to generate a corresponding URL.', $token[3], $name, $token[2], $mergedParams[$token[3]]); + if ($this->strictRequirements) { + throw new InvalidParameterException($message); + } + + if ($this->logger) { + $this->logger->error($message); + } + + return; + } + + if($token[3] !== $this->localeKey || (string) $mergedParams[$token[3]] != $this->defaultLocale) { + $url = $token[1].$mergedParams[$token[3]].$url; + $optional = false; + } + } + } else { + // static text + $url = $token[1].$url; + $optional = false; + } + } + + if ('' === $url) { + $url = '/'; + } + + // the contexts base URL is already encoded (see Symfony\Component\HttpFoundation\Request) + $url = strtr(rawurlencode($url), $this->decodedChars); + + // the path segments "." and ".." are interpreted as relative reference when resolving a URI; see http://tools.ietf.org/html/rfc3986#section-3.3 + // so we need to encode them as they are not used for this purpose here + // otherwise we would generate a URI that, when followed by a user agent (e.g. browser), does not match this route + $url = strtr($url, array('/../' => '/%2E%2E/', '/./' => '/%2E/')); + if ('/..' === substr($url, -3)) { + $url = substr($url, 0, -2).'%2E%2E'; + } elseif ('/.' === substr($url, -2)) { + $url = substr($url, 0, -1).'%2E'; + } + + $schemeAuthority = ''; + if ($host = $this->context->getHost()) { + $scheme = $this->context->getScheme(); + + if ($requiredSchemes) { + $schemeMatched = false; + foreach ($requiredSchemes as $requiredScheme) { + if ($scheme === $requiredScheme) { + $schemeMatched = true; + + break; + } + } + + if (!$schemeMatched) { + $referenceType = self::ABSOLUTE_URL; + $scheme = current($requiredSchemes); + } + } elseif (isset($requirements['_scheme']) && ($req = strtolower($requirements['_scheme'])) && $scheme !== $req) { + // We do this for BC; to be removed if _scheme is not supported anymore + $referenceType = self::ABSOLUTE_URL; + $scheme = $req; + } + + if ($hostTokens) { + $routeHost = ''; + foreach ($hostTokens as $token) { + if ('variable' === $token[0]) { + if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#i', $mergedParams[$token[3]])) { + $message = sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given) to generate a corresponding URL.', $token[3], $name, $token[2], $mergedParams[$token[3]]); + + if ($this->strictRequirements) { + throw new InvalidParameterException($message); + } + + if ($this->logger) { + $this->logger->error($message); + } + + return; + } + + $routeHost = $token[1].$mergedParams[$token[3]].$routeHost; + } else { + $routeHost = $token[1].$routeHost; + } + } + + if ($routeHost !== $host) { + $host = $routeHost; + if (self::ABSOLUTE_URL !== $referenceType) { + $referenceType = self::NETWORK_PATH; + } + } + } + + if (self::ABSOLUTE_URL === $referenceType || self::NETWORK_PATH === $referenceType) { + $port = ''; + if ('http' === $scheme && 80 != $this->context->getHttpPort()) { + $port = ':'.$this->context->getHttpPort(); + } elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) { + $port = ':'.$this->context->getHttpsPort(); + } + + $schemeAuthority = self::NETWORK_PATH === $referenceType ? '//' : "$scheme://"; + $schemeAuthority .= $host.$port; + } + } + + if (self::RELATIVE_PATH === $referenceType) { + $url = self::getRelativePath($this->context->getPathInfo(), $url); + } else { + $url = $schemeAuthority.$this->context->getBaseUrl().$url; + } + + // add a query string if needed + $extra = array_diff_key($parameters, $variables, $defaults); + if ($extra && $query = http_build_query($extra, '', '&')) { + // "/" and "?" can be left decoded for better user experience, see + // http://tools.ietf.org/html/rfc3986#section-3.4 + $url .= '?'.strtr($query, array('%2F' => '/')); + } + + return $url; + } + + /** + * @return RouteCollection + */ + public function getRoutes() { + return $this->routes; + } + + /** + * @param RouteCollection $routes + */ + public function setRoutes($routes) { + $this->routes = $routes; + } +} \ No newline at end of file diff --git a/Routing/LocalizedUrlMatcher.php b/Routing/LocalizedUrlMatcher.php new file mode 100644 index 0000000..033d001 --- /dev/null +++ b/Routing/LocalizedUrlMatcher.php @@ -0,0 +1,31 @@ +locales = $locales; + $this->defaultLocale = $default; + } + + /** + * @return RouteCollection + */ + public function getRoutes() { + return $this->routes; + } + + /** + * @param RouteCollection $routes + */ + public function setRoutes($routes) { + $this->routes = $routes; + } +} \ No newline at end of file diff --git a/Routing/ServiceRouter.php b/Routing/ServiceRouter.php new file mode 100644 index 0000000..3d487a8 --- /dev/null +++ b/Routing/ServiceRouter.php @@ -0,0 +1,69 @@ +container = $container; + $this->generatorId = $generatorId; + $this->matcherId = $matcherId; + } + + /** + * Gets the UrlGenerator instance associated with this Router. + * + * @return UrlGeneratorInterface A UrlGeneratorInterface instance + */ + public function getGenerator() { + + if($this->generator == null) { + $this->generator = $this->container->get($this->generatorId); + $this->generator->setRoutes($this->getRouteCollection()); + $this->generator->setContext($this->getContext()); + } + + return $this->generator; + } + + /** + * Gets the UrlMatcher instance associated with this Router. + * + * @return UrlMatcherInterface A UrlMatcherInterface instance + */ + public function getMatcher() { + + if($this->matcher == null) { + $this->matcher = $this->container->get($this->matcherId); + $this->matcher->setRoutes($this->getRouteCollection()); + $this->matcher->setContext($this->getContext()); + } + + return $this->matcher; + } +} \ No newline at end of file diff --git a/Routing/YamlLocalizedLoader.php b/Routing/YamlLocalizedLoader.php new file mode 100644 index 0000000..3e7c143 --- /dev/null +++ b/Routing/YamlLocalizedLoader.php @@ -0,0 +1,84 @@ +locales = $locales; + } + + /** + * Parses a route and adds it to the RouteCollection. + * + * @param RouteCollection $collection A RouteCollection instance + * @param string $name Route name + * @param array $config Route definition + * @param string $path Full path of the YAML file being processed + */ + protected function parseRoute(RouteCollection $collection, $name, array $config, $path) + { + parent::parseRoute($collection, $name, $this->modifyConfig($config), $path); + } + + /** + * Parses an import and adds the routes in the resource to the RouteCollection. + * + * @param RouteCollection $collection A RouteCollection instance + * @param array $config Route definition + * @param string $path Full path of the YAML file being processed + * @param string $file Loaded file name + */ + protected function parseImport(RouteCollection $collection, array $config, $path, $file) + { + parent::parseImport($collection, $this->modifyConfig($config), $path, $file); + } + + /** + * + */ + protected function modifyConfig($config) { + + $requirements = isset($config['requirements']) ? $config['requirements'] : array(); + + if(!array_key_exists('_locale', $requirements)) { + $requirements['_locale'] = implode('|', $this->locales); + } + + $config['requirements'] = $requirements; + + $options = isset($config['options']) ? $config['options'] : array(); + + if(!array_key_exists('compiler_class', $requirements)) { + $options['compiler_class'] = "BrauneDigital\\TranslationBaseBundle\\Routing\\LocalizedRouteCompiler"; + } + + $config['options'] = $options; + + return $config; + } + + /** + * @param mixed $resource + * @param null $type + * + * @return bool + */ + public function supports($resource, $type = null) + { + return parent::supports($resource, null) && $type === 'localized'; + } +} \ No newline at end of file diff --git a/composer.json b/composer.json index bc81463..e7ed9e1 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "MIT" ], "require" : { - "knplabs/doctrine-behaviors": "1.2.0", + "knplabs/doctrine-behaviors": "1.3.2", "a2lix/translation-form-bundle": "2.0.4", "egeloen/ckeditor-bundle": "2.5.2" },