Skip to content

Instantly share code, notes, and snippets.

@Itach1Uchixa
Last active March 18, 2017 18:39
Show Gist options
  • Save Itach1Uchixa/4840b004be2f2dcded19ec516e8aa6f1 to your computer and use it in GitHub Desktop.
Save Itach1Uchixa/4840b004be2f2dcded19ec516e8aa6f1 to your computer and use it in GitHub Desktop.
Zend Framework 2 Localized route concept
<?php
/**
* @author Kakhramonov Javlonbek <kakjavlon@gmail.com>
* @version 1.0.0
*/
namespace Application\Factory;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Mvc\Service\RouterConfigTrait;
use Zend\Mvc\Router\RouteStackInterface;
use Zend\ServiceManager\FactoryInterface;
class LocalizedTreeRouteFactory implements FactoryInterface
{
use RouterConfigTrait;
/**
* Create and return the HTTP router
*
* Retrieves the "router" key of the Config service, and uses it
* to instantiate the router. Uses the TreeRouteStack implementation by
* default.
*
* @param ServiceLocatorInterface $serviceManager
* @param string $name
* @param null|array $options
* @return RouteStackInterface
*/
public function createService(ServiceLocatorInterface $serviceManager)
{
$config = $serviceManager->has('config') ? $serviceManager->get('config') : [];
$translatorConfig = $config['translator'];
// Defaults
$class = 'Application\Router\LocalizedTreeRouteStack';
$config = isset($config['router']) ? $config['router'] : [];
$localizedRouteParams = [
'locale' => $translatorConfig['locale'],
/**
* You can change it to fit to your logic
* in this case application config has
* $config['translator']['locales'] key that has structure:
*
* [
* 'locale_name' => 'Language name',
* 'locale_name2' => 'Language name 2',
* ]
*
* Notice that 'locale_name' key will be used by router to assemble
* and match routes e.g route will be like:
* locale_name/your/routes or
* /your/routes for default locale
*/
'locales' => $translatorConfig['locales'],
];
if (is_array($config['default_params'])) {
$config['default_params'] = array_merge(
$localizedRouteParams,
$config['default_params']
);
} else {
$config['default_params'] = $localizedRouteParams;
}
return $this->createRouter($class, $config, $serviceManager);
}
}
<?php
/**
* @author Kakhramonov Javlonbek <kakjavlon@gmail.com>
* @version 1.0.2
*/
namespace Application\Router;
use Zend\Mvc\Router\Http\RouteMatch;
use Zend\Mvc\Router\Http\TreeRouteStack;
use Zend\Stdlib\RequestInterface as Request;
use Zend\Uri\Uri;
use Zend\Uri\UriFactory;
use Application\Exception;
class LocalizedTreeRouteStack extends TreeRouteStack
{
/**
* @var string
*/
protected $locale;
/**
* $options['default_params']['locales'] structure must be like:
* [
* 'locale_name' => 'language name or whatever you want',
* 'en-us' => 'English',
* 'ru_RU' => 'Russian',
* 'en_GB' => 'British',
* 'de_de' => 'German'
* ]
*
* Notice that 'locale_name' key will be used by router to assemble
* and match routes e.g route will be like:
* locale_name/your/routes or
* /your/routes for default locale that is in $options['default_params']['locale']
*
* @inheritDoc
*/
public static function factory($options = [])
{
if (!isset($options['default_params']['locale']) || !isset($options['default_params']['locales'])) {
throw new Exception\RuntimeException(
sprintf(
"%s::factory expects default 'locales' and 'locale' option",
get_class(self)
)
);
}
return parent::factory($options);
}
/**
* Simple modification for localization
* Adds locale to the uri
*
* @inheritdoc
*/
public function assemble(array $params = [], array $options = [])
{
$assembled = parent::assemble($params, $options);
$locale = $this->locale;
if (isset($options['locale']) && $options['locale']) {
$locale = trim($options['locale']);
}
$isDefaultNeeded = isset($options['force_canonical']) && $options['force_canonical'];
$isDefaultNeeded = $isDefaultNeeded
|| isset($options['keep_default_locale'])
&& $options['keep_default_locale'];
// if it is not required remove default locale
if (!$isDefaultNeeded && $locale === $this->defaultParams['locale']) {
unset($locale);
}
if (isset($locale) && $locale) {
if (!isset($this->defaultParams['locales'][$locale])) {
throw new Exception\RuntimeException(
sprintf("Invalid locale '%s'", $locale)
);
}
/**
* @var Uri $uri
*/
$uri = UriFactory::factory($assembled);
$uri->setPath(sprintf('/%s%s', $locale, $uri->getPath()));
return $uri;
}
return $assembled;
}
/**
* Checks request for locale
* Does little modification of request uri (removes locale from it)
* and sets back after match if needed
*
* @inheritdoc
*/
public function match(Request $request, $pathOffset = null, array $options = [])
{
$locale = $this->localeFromUri($request->getUri());
if ($locale) {
/**
* replace uri
*
* @var Uri $originalUri
* @var Uri $modifiedUri
*/
$originalUri = $request->getUri();
$this->locale = $locale;
$modifiedUri = clone $originalUri;
$parts = explode('/', $modifiedUri->getPath());
// temporary unset locale
unset($parts[array_search($locale, $parts)]);
$modifiedUri->setPath(join('/', $parts));
$request->setUri($modifiedUri);
}
$routeMatch = parent::match($request, $pathOffset, $options);
// if original uri is not null then reset it to request
if (isset($originalUri) && $originalUri) {
$request->setUri($originalUri);
$this->setRequestUri($originalUri);
}
// add locale param to route match
if ($routeMatch instanceof RouteMatch && $this->locale) {
$routeMatch->setParam('locale', $this->locale);
}
return $routeMatch;
}
/**
* Returns matched locale from
*
* @param Uri $uri
*
* @return string|null
*/
private function localeFromUri(Uri $uri)
{
$path = $uri->getPath();
$parts = explode('/', $path);
$locales = $this->defaultParams['locales'];
if (is_array($locales) && !empty($locales)) {
foreach ($parts as $key => $value) {
if (isset($locales[$value])) {
$match = $key;
}
}
}
if (isset($match)) {
// first match will be app locale
$locale = $parts[$match];
return $locale;
} else {
return null;
}
}
}
<?php
/**
* @author Kakhramonov Javlonbek <kakjavlon@gmail.com>
* @version 1.0.1
*/
namespace Application\Listener;
use Zend\ServiceManager\ServiceManager;
use Zend\EventManager\AbstractListenerAggregate;
use Zend\EventManager\EventManagerInterface;
use Zend\Mvc\MvcEvent;
use Zend\Mvc\Router\Http\RouteMatch;
/**
* Example route listener to prepare locale settings
* Change it the way you want don't forget to attach it to your EventManager
*/
class RouteListener extends AbstractListenerAggregate
{
/**
* @param EventManagerInterface $events
* @param int $priority
*/
public function attach(EventManagerInterface $events, $priority = 1)
{
$events->attach(MvcEvent::EVENT_ROUTE, [$this, 'afterRoute'], -1001);
}
public function afterRoute(MvcEvent $e)
{
$routeMatch = $e->getRouteMatch();
if (!$routeMatch instanceof RouteMatch) {
return;
}
$this->setLocale(
$routeMatch->getParam('locale'),
$e->getApplication()->getServiceManager()
);
}
/**
* Sets locale where needed
*
* @param string $locale
* @param ContainerInterface $services
*
* @return void
*/
private function setLocale($locale, ServiceManager $services)
{
$translator = $services->get('translator');
$translator->setLocale($locale);
// there you can set locale to services you need
}
}
@Itach1Uchixa
Copy link
Author

1 step. Add line below to your service_manager config

'HttpRouter' => 'Application\Factory\LocalizedTreeRouteFactory'

2 step. Register listener to your event manager

Application\Listener\RouteListener

3 step. Your translator config must have locales key that must be like

'translator' => array(
    // this will be default locale of route
    'locale'  => 'en_US',
    // key must be locale that you want
    'locales' => array(
        'en_US' => 'English',
        'fr'    => 'French', 
        'ru_RU' => 'Russian'
     ),
),

Step 3 is optional you can change route factory to use another config
The config above will make you route:

/your/route or en_US/your/route for English
/fr/your/route for French
/ru_RU/your/route for Russian

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment