Working with multilanguage routers in Zend - php

I'm developing a multilanguage Zend application with two modules (admin and public), I wanna pass the language code in the url so, in my bootstrap I have:
protected function _initAutoload() {
$this->bootstrap('frontController');
$this->_front = $this->getResource('frontController');
$autoLoader = new Zend_Loader_Autoloader_Resource(array(
'basePath' => APPLICATION_PATH,
'namespace' => '',
'resourceTypes' => array(
'form' => array(
'path' => 'admin/forms/',
'namespace' => 'Admin_Form_',
),
'model' => array(
'path' => 'models/',
'namespace' => 'Model_'
)
)
));
$autoLoader_ = new Zend_Application_Module_Autoloader(array(
'basePath' => APPLICATION_PATH . '/public/',
'namespace' => 'Public_',
'resourceTypes' => array(
'forms' => array(
'path' => 'forms/',
'namespace' => 'Public_Form_'
)
)
));
return $autoLoader;
}
protected function _initConfig() {
$config = new Zend_Config_Xml(BASE_PATH . '/config.xml', APPLICATION_ENV);
$registry = Zend_Registry::getInstance();
$registry->set('config', $config);
return $config;
}
protected function _initDb() {
$this->bootstrap('config');
$config = $this->getResource('config');
$db = Zend_Db::factory($config->database->adapter, $config->database);
$db->setFetchMode(Zend_Db::FETCH_OBJ);
$db->query("SET NAMES 'utf8';");
$db->query("SET CHARACTER SET 'utf8';");
Zend_Db_Table::setDefaultAdapter($db);
return $db;
}
protected function _initRoutes() {
$router = $this->_front->getRouter();
$router->removeDefaultRoutes();
$language = new Zend_Controller_Router_Route(':language', array('language' => 'es'));
$module = new Zend_Controller_Router_Route_Module(
array(
'module' => 'public',
'controller' => 'index',
'action' => 'index'
),
$this->_front->getDispatcher(),
$this->_front->getRequest()
);
$module->isAbstract(true);
$default = new Zend_Controller_Router_Route_Chain();
$default->chain($language);
$default->chain($module);
$router->addRoute('default', $default);
}
http://domain.com/es/admin/ => works
http://domain.com/admin/ => does not work
http://domain.com/es/ => works
http://domain.com/ => does not work
The thing is that I wanna it to work even when I do not specify the language. how can I do it? how can I extract the language code (en) for use it in _initLocale?
Thanks in advance

Here is my bootstrap init for routing. It contains all solutions for working with multilanguage routes and haveing modules and url halper working:
public function _initRoutes() {
$this->bootstrap('FrontController');
$this->_frontController = $this->getResource('FrontController');
$router = $this->_frontController->getRouter();
if (isset($_GET['lang'])) {
$lang = $_GET['lang'];
} else {
// auto recognition of language
$locale = new Zend_Locale();
$lang = $locale->getLanguage();
}
$langRoute = new Zend_Controller_Router_Route(
':lang/',
array(
'lang' => $lang
)
);
$defaultRoute = new Zend_Controller_Router_Route(
':controller/:action/*',
array(
'module' => 'default',
'controller' => 'index',
'action' => 'index'
)
);
$defaultRoute = $langRoute->chain($defaultRoute);
$adminRoute = new Zend_Controller_Router_Route(
'admin/:controller/:action/*',
array(
'module' => 'admin',
'controller' => 'index',
'action' => 'index'
)
);
$router->addRoute('langRoute', $langRoute);
$router->addRoute('defaultRoute', $defaultRoute);
$router->addRoute('adminRoute', $adminRoute);
}

You need to chain all the routes from the language route. See here:
http://robertbasic.com/blog/chaining-routes-in-zend-framework/
Two modules:
$defaultRoute = new Zend_Controller_Router_Route(
':module/:controller/:action',

This is a plugin that is universal and allow us to create your own routes without remembering about language. It's also setting Zend_Translator. This is a base class, to improve speed I'm suggesting to use Zend_Cache because code below impacts in code efficiency (I would say it's required if you have +100 routes).
<?php
class PsScripts_Controller_Plugin_Lang extends Zend_Controller_Plugin_Abstract {
private function initTranslator($locale){
$translate = new Zend_Translate(array('adapter' => 'tmx',
'content' => APPLICATION_PATH . '/configs/translations.tmx',
'locale' => $locale));
Zend_Registry::set('Zend_Translate', $translate);
}
public function routeStartup(\Zend_Controller_Request_Abstract $request) {
$locale = new Zend_Locale('pl_PL'); //default locale
if (preg_match('/\/([a-z]{2})([\/].*)/', $request->getRequestUri(),$matches)){ //if locale is found in request
$lang = $matches[1]; //obtain locale
/* #var $locale Zend_Locale */
switch ($lang){
case 'en':
$locale->setLocale('en_GB');
break;
}
Zend_Registry::set('Zend_Locale',$locale);
$this->initTranslator($locale);
$router = Zend_Controller_Front::getInstance()->getRouter();
/* #var $router Zend_Controller_Router_Rewrite */
$langRoute = new Zend_Controller_Router_Route(
':lang',
array(
'lang' => $lang
),
array(
'lang' => '[a-z]{0,2}'
)
);
$routes = $router->getRoutes();
foreach ($routes as $name=>$route){
if ($name != 'default'){
/* #var $route Zend_Controller_Router_Route */
$router->removeRoute($name);
/* #var $lang Zend_Controller_Router_Route */
$chain = new Zend_Controller_Router_Route_Chain();
$chain->chain($langRoute)->chain($route);
$router->addRoute($name,$chain);
}
}
$router->route($request);
} else {
$this->initTranslator($locale);
}
parent::routeStartup($request);
}
}

Related

Phalcon subdomain routing

How to connect to the subdomains Phalcon:
city1.site.com
city2.site.com
city3.site.com
...
cityN.site.com
city - in the database
I am trying to do so
$router->add('{subdomain:\w+}.{domain:.+}', array(
'controller' => 'category',
'action' => 'categorySearch'
)
);
but does not work.
Phalcon's router doesn't match subdomains. You have to match $_SERVER['SERVER_NAME'] with a regular expression to create corresponding routers.
<?php
$di = new \Phalcon\Di\FactoryDefault();
$di->setShared('router', function() {
// Match subdomain with regular expression
if(preg_match("/^(\\w+)\\.site\\.com$/", $_SERVER['SERVER_NAME'], $matches) === 1) {
$subdomain = $matches[1];
}
// Create a router without default routes
$router = new \Phalcon\Mvc\Router(false);
if (isset($subdomain)) {
// Create routes for subdomains
$router->add('/category', array(
'controller' => 'category',
'action' => 'categorySearch'
));
} else {
// Create routes for main domain
}
return $router;
});
// Retrieve corresponding router at runtime
$di->getShared('router')->handle();
may be this can help you problem
$di['router'] = function()
{
$router = new Phalcon\Mvc\Router(false);
switch ($_SERVER['HTTP_HOST'])
{
case 'm.domain.com':
$router->add('/m/xxx/yyy', array(
'controller' => 'xxx',
'action' => 'yyy'
));
//...
break;
default:
$router->add('/xxx/yyy', array(
'controller' => 'xxx',
'action' => 'yyy'
));
break;
}
return $router;
};

PHPUnit test settings for AbstractRestfulController in ZF2

I am testing an AbstractRestfulController within a Zend Framework 2 application.
I am failing even the basic assertion
I am thinking that my setup could be broken.. ie not route matching properly. How can I print this out in the controller, so as to assure my settings in ZF2RestServiceTest.setup() are correct?
Documentation is pretty sparse on what goes where here :
ControllerTest.php
protected function setUp()
{
$serviceManager = Bootstrap::getServiceManager();
$this->controller = new IndexController();
$this->request = new Request();
$this->routeMatch = new RouteMatch(array('controller' => 'IndexController'));
$this->event = new MvcEvent();
$config = $serviceManager->get('Config');
$routerConfig = isset($config['router']) ? $config['router'] : array();
$router = HttpRouter::factory($routerConfig);
$this->event->setRouter($router);
$this->event->setRouteMatch($this->routeMatch);
$this->controller->setEvent($this->event);
$this->controller->setServiceLocator($serviceManager);
}
RouteMatch takes in an array, looking for my controller name set in the module.config.php?
public function testIndexActionCanBeAccessed()
{
$this->routeMatch->setParam('action', 'index');
$result = $this->controller->dispatch($this->request);
$response = $this->controller->getResponse();
$this->assertEquals(200, $response->getStatusCode());
}
module.config.php
<?php
return array(
'router' => array(
'routes' => array(
'myservice' => array(
'type' => 'Zend\Mvc\Router\Http\Segment',
'options' => array(
'route' => '/api/myservice[/:optionalParameter]',
'constraints' => array(
'id' => '\w+'
),
'defaults' => array(
'controller' => 'MyService\Controller\Index'
),
),
),
),
),
'controllers' => array(
'invokables' => array(
'MyService\Controller\Index' => 'MyService\Controller\IndexController',
),
),
);
Testing Result
PHPUnit_Framework_ExpectationFailedException : Failed asserting that 404 matches expected 200.
Expected :200
Actual :404
It turns out I need the full controller name under this part, and modify the http parameters since I'm not using an Action Controller.
what was confusing me was a lot of Zend Framework code examples were using Aliases, or different Controller types..
here's the link that helped : https://github.com/RichardKnop/zend-v2-skeleton
protected function setUp()
{
$serviceManager = Bootstrap::getServiceManager();
$this->controller = new IndexController();
$this->request = new Request();
$this->routeMatch = new RouteMatch(array('controller' => 'MyService\Controller\Index'));
$this->event = new MvcEvent();
...
public function testIndexActionCanBeAccessed()
{
$this->routeMatch->setParam('optionalParameter', 'someParam');
$result = $this->controller->dispatch($this->request);
$response = $this->controller->getResponse();
$this->assertEquals(200, $response->getStatusCode());
}

Database Translations Zend Framework 2

I have problem with creating custom translator from database in ZF2. I have a DB like this
and files:
1)Application/module.config.php
'service_manager' => array(
'abstract_factories' => array(),
'factories' => array(
'translator' => function (\Zend\ServiceManager\ServiceManager $serviceManager)
{
$pluginManager = new \Zend\I18n\Translator\LoaderPluginManager();
$pluginManager->setServiceLocator($serviceManager);
$pluginManager->setFactory('DatabaseTranslationLoaderFactory', function($serviceManager)
{
$translator = new \Zend\I18n\Translator\DatabaseTranslationLoaderFactory();
return $translator->createService($serviceManager);
});
$translator = new \Zend\I18n\Translator\Translator(array());
$translator->setFallbackLocale('en_US');
$translator->setPluginManager($pluginManager);
$translator->addRemoteTranslations('DatabaseTranslationLoaderFactory');
return $translator;
},
),
),
'translator' => array(
'locale' => 'en_US',
'translation_file_patterns' => array(
array(
'type' => 'Zend\I18n\Translator\Loader\Database',
'base_dir' => __DIR__ . '/../language',
'pattern' => '%s.mo',
),
),
),
2) Zend/I18n/Translator/Loader/Database.php
<?php
namespace Zend\I18n\Translator\Loader;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Sql\Sql;
use Zend\I18n\Translator\Plural\Rule as PluralRule;
use Zend\I18n\Translator\TextDomain;
class Database implements RemoteLoaderInterface {
protected $dbAdapter;
public $dbAdapter;
public function __construct(Adapter $dbAdapter = null)
{
if ($dbAdapter === null)
{
$configArray = array('driver' => 'Pdo_Mysql',
'database' => 'dbname',
'username' => 'username',
'password' => 'pswd',
'hostname' => 'localhost',
'charset' => 'utf-8',
);
$dbAdapter = new Adapter($configArray);
}
$this->dbAdapter = $dbAdapter;
}
public function load($locale, $textDomain)
{
$sql = new Sql($this->dbAdapter);
$select = $sql->select('ic_var')->columns(array('value'))
->where(array('language' => $locale, 'name' => $textDomain));
$messages = $this->dbAdapter->query(
$sql->getSqlStringForSqlObject($select),
Adapter::QUERY_MODE_EXECUTE
);
$textDomain = new TextDomain();
foreach ($messages as $message) {
if (isset($textDomain[$message['name']])) {
if (!is_array($textDomain[$message['name']])) {
$textDomain[$message['name']] = array(
$message['plural_index'] => $textDomain[$message['name']]
);
}
$textDomain[$message['name']][$message['plural_index']] = $message['value'];
} else {
$textDomain[$message['name']] = $message['value'];
}
}
return $textDomain;
}
}
3) Zend/I18n/Translator/DatabaseTranslationLoaderFactory.php
<?php
namespace Zend\I18n\Translator;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\I18n\Translator\Loader\Database;
class DatabaseTranslationLoaderFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
return new Database($serviceLocator->get('Zend\Db\Adapter\Adapter'));
}
}
4) Application/Module.php
public function onBootstrap(MvcEvent $e)
{
$translator = $e->getApplication()->getServiceManager()->get('translator');
$translator->addTranslationFile(
'DatabaseTranslationLoader',
'text-domain',
'text-domain'
);
}
But translation doesn`t work, because db adapter not find in loader:
Fatal error: Uncaught exception 'Zend\I18n\Exception\RuntimeException' with message 'Specified loader is not a file loader'
Thanks for your answers!
First of all you shouldn't define your custom classes in the Zend namespace as this is reserved a namespace for the ZF2 library and you don't want to touch (or add) files in the vendor directory.
Just put the custom classes in your own namespace outside the vendor folder. i.e. MyI18n
You can register you custom remote loader to the pluginManager in module.config.php.
return array(
'translator' => array(
'loaderpluginmanager' => array(
'factories' => array(
'database' => 'MyI18n\Translator\DatabaseTranslationLoaderFactory',
)
),
'remote_translation' => array(
array(
'type' => 'database' //This sets the database loader for the default textDomain
),
),
)
);
You don't have to overwrite the Translator factory if you want to add a custom loader, so just remove that code in your Module.php.
Als remove the configuration under translation_file_patterns as this is only needed for file loaders.
EDIT
For the above to work you need to overwrite the TranslatorServiceFactory because ZF has no support to register custom loaders on the plugin manager.
namespace MyNamespace\Translator;
use Zend\Mvc\Service\TranslatorServiceFactory as BaseTranslatorFactory;
class TranslatorServiceFactory extends BaseTranslatorFactory
{
/**
* #param ServiceLocatorInterface $serviceLocator
* #return MvcTranslator
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$translator = parent::createService($serviceLocator);
$config = $serviceLocator->get('Config');
$pluginManagerConfig = isset($config['translator']['loaderpluginmanager']) ? $config['translator']['loaderpluginmanager'] : array();
$pluginManager = new LoaderPluginManager(new Config($pluginManagerConfig));
$pluginManager->setServiceLocator($serviceLocator);
$translator->setPluginManager($pluginManager);
return $translator;
}
}
Now register your custom factory in the service configuration:
class Module
{
public function getServiceConfig()
{
return array(
'factories' => array(
'MvcTranslator' => 'MyNamespace\Translator\TranslatorServiceFactory',
)
)
}
}
I register custom remote loader to the pluginManager in module.config.php like this
'translator' => [
'loaderpluginmanager' => [
'factories' => [
'database' => function($lpm){
$sm = $lpm->getServiceLocator();
$loader = new Zf2Translation\Loader\DatabaseTranslationLoader($sm);
return $loader;
},
],
],
'remote_translation' => [
[
'type' => 'database',
],
],
]
Next in Database Loader class
use Zend\I18n\Translator\Loader\RemoteLoaderInterface;
class DatabaseTranslationLoader implements RemoteLoaderInterface
{
protected $dbAdapter;
protected $sm;
public function __construct(ServiceManager $sm)
{
$this->sm = $sm;
$this->dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
}
}
I hope it helps.

ZF2 SessionManager usage

I am new to ZF2 and not quite used to how to do stuff. I want to use session to keep track of an user (remember me). I had this code in one part of my class:
$sessionManager = new \Zend\Session\SessionManager();
$sessionManager->rememberMe($time);
// i want to keep track of my user id too
$populateStorage = array('user_id' => $user->getId());
$storage = new ArrayStorage($populateStorage);
$sessionManager->setStorage($storage);
Ok, so far so good. When i try:
var_dump($sessionManager->getStorage());
I get the expected data.
In another part of my program, i want to retreive my data again (a bit like containers):
$sessionManager = new \Zend\Session\SessionManager();
var_dump($sessionManager->getStorage());
This only returns an empty object.
I guess this is due to the "new" and i think i have to implement SessionManager in a different way, but how? I do not know. This is what i came up with:
In my Module i now have:
public function onBootstrap(\Zend\Mvc\MvcEvent $e)
{
$config = $e->getApplication()
->getServiceManager()
->get('Configuration');
$sessionConfig = new SessionConfig();
$sessionConfig->setOptions($config['session']);
$sessionManager = new SessionManager($sessionConfig);
$sessionManager->start();
In my module.config:
'session' => array(
'remember_me_seconds' => 2419200,
'use_cookies' => true,
'cookie_httponly' => true,
),
But how to procede? How do i get the instance for my sessionManager?
There is not well documented SessionManagerFactory (zf2 api doc) and SessionConfigFactory (zf2 api doc). With those is instantiate SessionManager very easy, just put these factories to your ServiceManager configuration:
'service_manager' => [
'factories' => [
'Zend\Session\SessionManager' => 'Zend\Session\Service\SessionManagerFactory',
'Zend\Session\Config\ConfigInterface' => 'Zend\Session\Service\SessionConfigFactory',
],
],
and to module configuration put your session options, under the session_config key:
'session_config' => [
'remember_me_seconds' => 2419200,
'use_cookies' => true,
'cookie_httponly' => true,
],
and that's it, now you can grab SessionManager from service locator anywhere, for example in controller:
/** #var Zend\Session\SessionManager $sm */
$sessionManager = $this->serviceLocator->get('Zend\Session\SessionManager');
This is available as of 2.2 version of Zend Framework (related pull request).
In my project i use example from other source (book or official documentation, forget).
Main Module.php file:
namespace Application;
class Module
{
public function getServiceConfig()
{
return array(
'factories' => array(
'Zend\Session\SessionManager' => function ($sm){
$config = $sm->get('config');
if (isset($config['session'])){
$session_config = $config['session'];
$sessionConfig = null;
if (isset($session_config['config'])){
$class = isset($session_config['config']['class'])
? $session_config['config']['class']
: 'Zend\Session\Config\SessionConfig';
$options = isset($session_config['config']['options'])
? $session_config['config']['options']
: array();
$sessionConfig = new $class();
$sessionConfig->setOptions($options);
}
$sessionStorage = null;
if (isset($session_config['storage'])){
$class = $session_config['storage'];
$sessionStorage = new $class();
}
$sessionSaveHandler = null;
if (isset($session_config['save_handler'])){
// class should be fetched from service manager
// since it will require constructor arguments
$sessionSaveHandler = $sm->get($session_config['save_handler']);
}
$sessionManager = new SessionManager(
$sessionConfig,
$sessionStorage,
$sessionSaveHandler
);
if (isset($session_config['validator'])){
$chain = $sessionManager->getValidatorChain();
foreach ($session_config['validator'] as $validator){
$validator = new $validator();
$chain->attach('session.validate', array(
$validator,
'isValid'
));
}
}
} else {
$sessionManager = new SessionManager();
}
Container::setDefaultManager($sessionManager);
return $sessionManager;
},
),
);
}
}
My config/autoload/global.php config file:
return array(
'session' => array(
'config' => array(
'class' => 'Zend\Session\Config\SessionConfig',
'options' => array(
'use_cookies' => true,
),
),
'storage' => 'Zend\Session\Storage\SessionArrayStorage',
'validators' => array(
array(
'Zend\Session\Validator\RemoteAddr',
'Zend\Session\Validator\HttpUserAgent',
),
),
),
);
To get user_id from session:
$sessionStorage = $sm->get('Zend\Session\SessionManager')
->getStorage();
$user_id = $sessionStorage->user_id;

How to add resource for module in Zend?

I have a test module.
I have a class in myproject/application/modules/test/lists/Profiles.php
class Test_List_Profiles {
// class members
}
Now when I access this class in myproject/application/modules/test/controllers/ProfileController.php
public function indexAction() {
$profilesList = new Test_List_Profiles();
}
It gives me following error:
Fatal error: Class 'Test_List_Profiles' not found
I have following entry in Bootstrap.php as well:
protected function _initAutoload() {
$autoLoader = Zend_Loader_Autoloader::getInstance();
$testModuleLoader = new Zend_Loader_Autoloader_Resource(array(
'basePath' => APPLICATION_PATH . 'modules/test',
'namespace' => 'Test_',
'resourceTypes' => array( 'form' => array( 'path'=>'forms/', 'namespace'=>'Form_' ),
'list' => array( 'path'=>'lists/', 'namespace'=>'List_' )
)
));
}
How to access Test_List_Profiles class everywhere in project ?
Thanks
You should be able to add your list resource in the test module bootstrap class (myproject/application/modules/test/Bootstrap.php) this way:
class Test_Bootstrap extends Zend_Application_Module_Bootstrap {
protected function _initAutoload(){
$autoloader = $this->getResourceLoader();
$autoloader->addResourceType('list', 'models/lists', 'Model_List');
return $autoloader;
}
}
There was error in basePath path. A slash(/) was missing.
protected function _initAutoload() {
$autoLoader = Zend_Loader_Autoloader::getInstance();
$testModuleLoader = new Zend_Loader_Autoloader_Resource(array(
'basePath' => APPLICATION_PATH . '/modules/test',
'namespace' => 'Test_',
'resourceTypes' => array( 'form' => array( 'path'=>'forms/', 'namespace'=>'Form_' ),
'list' => array( 'path'=>'lists/', 'namespace'=>'List_' )
)
));
}
It is working now.

Categories