Phalcon php multilingual routing - php

Hi I'm new to the php world.
I'm wondering What is the best way to handle multilingual routing ?
I'm starting to create a website with Phalcon php.
I have the following routing structure.
$router->add('/{language:[a-z]{2}}/:controller/:action/:params', array(
'controller' => 2,
'action' => 3,
'params' => 4,
));
$router->add('/{language:[a-z]{2}}/:controller/:action', array(
'controller' => 2,
'action' => 3,
));
$router->add('/{language:[a-z]{2}}/:controller', array(
'controller' => 2,
'action' => 'index',
));
$router->add('/{language:[a-z]{2}}', array(
'controller' => 'index',
'action' => 'index',
));
My problem is for instance when I go on mywebsite.com/ I want to change my url in the dispatcher like mywebsite.com/en/ or other language. Is it a good practise to handle it in the beforeDispatchLoop ? I seek the best solutions.
/**Triggered before entering in the dispatch loop.
* At this point the dispatcher don't know if the controller or the actions to be executed exist.
* The Dispatcher only knows the information passed by the Router.
* #param Event $event
* #param Dispatcher $dispatcher
*/
public function beforeDispatchLoop(Event $event, Dispatcher $dispatcher)
{
//$params = $dispatcher->getParams();
$params = array('language' => 'en');
$dispatcher->setParams($params);
return $dispatcher;
}
This code doesn't work at all, my url is not change. The url stay mywebsite.com/ and not mywebsite.com/en/
Thanks in advance.
I try one solution above.
The redirect doesn't seems to work. I even try to hard-coded it for test.
use Phalcon\Http\Response;
//Obtain the standard eventsManager from the DI
$eventsManager = $di->getShared('eventsManager');
$dispatcher = new Phalcon\Mvc\Dispatcher();
$eventsManager->attach("dispatch:beforeDispatchLoop",function($event, $dispatcher)
{
$dispatcher->getDI->get('response')->redirect('/name/en/index/index/');
}

I think you are confusing something. If I understand correctly: you want to redirect users to a valid url if they open a page without specifying a language.
If that's the case you should verify in your event handler whether the language parameter is specified and redirect user to the same url + the default language if its missing. I am also assuming that your beforeDispatchLoop is located in your controller, which is also part of the problem, because your route without a language never matches and you never get into that controller. Instead you need to use it as an event handler with the event manager as per the documentation. Here is how you do the whole thing.
$di->get('eventsManager')->attach("dispatch:beforeDispatchLoop", function(Event $event, Dispatcher $dispatcher)
{
$params = $dispatcher->getParams();
// Be careful here, if it matches a valid route without a language it may go into a massive
// recursion. Really, you probably want to use `beforeExecuteRoute` event or explicitly
// check if `$_SERVER['REQUEST_URI']` contains the language part before redirecting here.
if (!isset($params['language']) {
$dispatcher->getDI->get('response')->redirect('/en' . $_SERVER['REQUEST_URI']);
return false;
}
return $dispatcher;
});

Related

How to make a helper function which manipulates an instance?

I am busy learning as much as I can about php frameworks.
At the moment I have a file full of routes. The routes take this general form.
$bootstrap->router->add('/home', ['controller' => 'home', 'action' => 'index']);
So, the 'routes' file is not a class, it is just a file which I include into the index.php page just underneath $bootstrap = new Bootstrap($router);.
As you can see, to add a route I add it to the instantiated $bootstrap object, which itself has an instantiated ->router object inside which I am calling the 'add()' method.
Now I want to make it easier to add routes by writing something like this...
route('/home', ['controller' => 'home', 'action' => 'index']);
question
Exactly how to go about this is unclear... I am not even sure if this is a done thing. I have tried making a 'helpers/RoutesHelper file' but that takes my request out of the object scope...
...so I am a bit confused, how do I go about implementing a plane old helper function inside object oriented code?
Many thanks in advance.
anonymous function
If you are willing to add a $ to the beginning of that route(... line:
$route = function($path, $options) use ($bootstrap) {
$bootstrap->router->add($path, $options);
}
would work.
global $bootstrap
Otherwise it would be dirty to bring the bootstrap object into the route function:
function route($path, $options) {
global $bootstrap; // <-- i don't like this
$bootstrap->router->add($path, $options);
}
definitions in array
you could also just make an array in your file like:
$routes = array()
$routes['/home'] = ['controller' => 'home', 'action' => 'index'];
// ... more routes ...
and in your main program just loop over that array:
foreach($routes as $path => $options) {
$bootstrap->router->add($path, $options);
}
those are the three solutions that pop into my mind ...
Like this
if(!function_exists( 'route' ) ){
function route( $path, $params ){
$bootstrap = ''; //?
$bootstrap->router->add($path, $params);
}
}
Of course I have no idea where $bootstrap came from, so you'll want that in there, obviously. You could pass that in as a parameter, but it would be better to initialize it inside the function if possible.

Add a language parameter in url in zend framework

I have a site in zend framework . Now I am making site in multiple language. For it I need to modify the url.
For example if sitename is www.example.com then i want to make it like
www.example.com/ch
www.example.com/fr
There can be some work around it that you can ask me to create a folder name ch and put a copy of code inside it. But for it I have to manage multiple folder when updating files on server.
What is the best or correct way to do it ?
My routs code is
public function _initRouter() {
$frontController = Zend_Controller_Front::getInstance();
$router = $frontController->getRouter();
$routes = array(
'page' => new Zend_Controller_Router_Route('page/:slug', array('controller' => 'staticpage', 'action' => 'page', 'slug' => ''))
);
$router->addRoutes($routes);
}
Thanks
You have to add the language as parameter in your route(s). Here is an example: http://devzone.zend.com/1765/chaining-language-with-default-route/
You need a function to get a user choice of a language and a default language used if a user just starts with example.com.
You may want to get the current browser and Language Header from the users HTTP request.
Take a look at Zend_Locale and Zend_Translate.
You can use something like $locale = new Zend_Locale('browser'); to detect the users browser language.
Then look if Zend_Translate or your translation engine has the language available and set it to a cookie or session to store the date.
If the user then navigate to some language change site like example.com/?language=en you may want to set the locale based on the user choice and recheck if available in your translations.
If not, get back to original default language and present an error page or something like that.
If you want to get your Zend_Router urls to be language dependent, which might be a bad choice because of copy paste, backlinks or forum posts of your links, you need to add something before each route.
In my Applications i use something like the following in my main bootstrap.php file. I've cut some parts of to keep it simple.
protected function _initTranslate() {
$session = new Zend_Session_Namespace("globals");
// Get current registry
$registry = Zend_Registry::getInstance();
/**
* Set application wide source string language
* i.e. $this->translate('Hallo ich bin ein deutscher String!');
*/
if(!$session->current_language) {
try {
$locale = new Zend_Locale('browser'); //detect browser language
}
catch (Zend_Locale_Exception $e) {
//FIXME: get from db or application.ini
$locale = new Zend_Locale('en_GB'); //use the default language
}
}
else {
$locale = new Zend_Locale($session->current_language);
}
$translate = new Zend_Translate(
array(
'adapter' => 'array',
'content' => realpath(APPLICATION_PATH . '/../data/i18n/'),
'locale' => $locale,
'scan' => Zend_Translate::LOCALE_DIRECTORY,
'reload' => false,
'disableNotices' => true, //dont get exception if language is not available
'logUntranslated' => false //log untranslated values
)
);
if(!$translate->isAvailable($locale->getLanguage())) {
$locale = new Zend_Locale('en_GB'); //default
}
$translate->setLocale($locale);
$session->current_language = $locale->getLanguage();
//Set validation messages
Zend_Validate_Abstract::setDefaultTranslator($translate);
//Max lenght of Zend_Form Error Messages (truncated with ... ending)
Zend_Validate::setMessageLength(100);
//Zend_Form Validation messages get translated into the user language
Zend_Form::setDefaultTranslator($translate);
/**
* Both of these registry keys are magical and makes do automagical things.
*/
$registry->set('Zend_Locale', $locale);
$registry->set('Zend_Translate', $translate);
return $translate;
}
This is for the default translation setup of each visitor.
To set a user language depending on some HTTP parameters, I decided to create a Plugin, which will run on each request, see if the global language parameter is set (key=_language) and try setting the new language.
I then redirect the user to the new route, depending on his choice.
So, if the user click on the link for english language (example.com/de/test/123?_language=en) he will get redirected to example.com/en/test/123.
class Application_Plugin_Translate extends Core_Controller_Plugin_Abstract {
public function preDispatch(Zend_Controller_Request_Abstract $request) {
$frontController = Zend_Controller_Front::getInstance();
// Get the registry object (global vars)
$registry = Zend_Registry::getInstance();
// Get our translate object from registry (set in bootstrap)
$translate = $registry->get('Zend_Translate');
// Get our locale object from registry (set in bootstrap)
$locale = $registry->get('Zend_Locale');
// Create Session block and save the current_language
$session = new Zend_Session_Namespace('globals');
//get the current language param from request object ($_REQUEST)
$language = $request->getParam('_language',$session->current_language);
// see if a language file is available for translate (scan auto)
if($translate->isAvailable($language)) {
//update global locale
$locale = $registry->get('Zend_Locale');
$locale->setLocale($language);
$registry->set('Zend_Locale', $locale);
//update global translate
$translate = $registry->get('Zend_Translate');
$translate->setLocale($locale);
$registry->set('Zend_Translate', $translate);
//language changed
if($language!=$session->current_language) {
//update session
$session->current_language = $language;
$redirector = new Zend_Controller_Action_Helper_Redirector;
$redirector->gotoRouteAndExit(array());
}
}
else {
$request->setParam('_language', '');
unset($session->current_language);
$redirector = new Zend_Controller_Action_Helper_Redirector;
$redirector->gotoRouteAndExit(array());
}
}
}
And finally, to prepare your router with the new language routes, you need to setup a base language route and chain your other language depending routes.
public function _initRouter() {
$frontController = Zend_Controller_Front::getInstance();
$router = $frontController->getRouter();
$languageRoute = new Zend_Controller_Router_Route(
':language',
array(
'language' => "de"
),
array('language' => '[a-z]{2}')
);
$defaultRoute = new Zend_Controller_Router_Route(
':#controller/:#action/*',
array(
'module' => 'default',
'controller' => 'index',
'action' => 'index'
)
);
$router->addRoute(
'default',
$defaultRoute
);
$languageDefaultRoute = $languageRoute->chain($defaultRoute);
$router->addRoute(
'language',
$languageDefaultRoute
);
}
Good luck with your project, hope it will help you and others!

Phalcon ActionFilters

I'm learning Phalcon (trying REST API in multi-module application template), and I did simple checking for each request, "does this request contain specific header" for example x-api-key (something like ActionFilters in ASP.NET MVC).
I tried doing it with annotations, plugins, beforeExecuteRoute, and beforeException. But when I write in one of them throw new \Exception("Some exception", 500); then Phalcon returns a blank page without an exception message and code. AFAIK this is a known bug.
I tried to do it with the dispatcher in beforeException:
public function beforeException($event, $dispatcher, $exception)
{
if ($exception instanceof \Phalcon\Http\Request\Exception)
{
$dispatcher->forward(
array(
'controller' => 'error',
'action' => 'showInternalServerError'
)
);
return false;
}
//...
}
and it seems that's working, but this is not an elegant solution and I'm too lazy for this :)
QUESTION: Do you have any better ideas how to do ActionFilters in PhalconPHP?
Take a look at the solution on cmoore4/phalcon-rest/HTTPException
When the application throws an HTTPError this one modifies the response object to reflect the error details and headers and send it to the output.
I like the cmoore4 way of doing many things on the REST implementation.
You can use the Match Callbacks in order to check for your api key:
Assume you have the following route:
$router->add('/api/v1', array(
'module' => 'api',
'controller' => 'index'
))
You can prepend a check to it like this:
$router->add('/api/v1', array(
'module' => 'api',
'controller' => 'index'
))
->beforeMatch(array(new AuthenticationFilter(), 'check'));
And in your custom created AuthenticationFilter, you are able to check for a valid api key:
<?php
class AuthenticationFilter
{
public function check($uri, $route)
{
$response = new \Phalcon\Http\Response();
if ($response->getHeaders()->get('X-Api-Key') != 'XYZ')
{
throw new CustomAuthenticationErrorExteption('Api Key Invalid');
// you can also just return false here and redirect to a default non-authenticated 404 response
}
else return true;
}
}
Reference
https://docs.phalconphp.com/en/latest/reference/routing.html#match-callbacks

CakePHP - admin routing for /admin/, but not all the rest (way to set default admin false?)

I read the tutorial, and found that to use the "admin" prefix, you can just uncomment the:
Configure::write('Routing.prefixes', array('admin'));
config/core.php file.
I did that, and my admin routing works great - /admin/users/add hits the admin_add() function in my users_controller.
The problem is - it's also changing my normal links - ie. my "LOGOUT" button now tries to go to /admin/users/logout instead of just /users/logout. I realize I can add 'admin'=>false, but I'd rather not have to do that for every link in my site.
Is there a way to make it so ONLY urls with either 'admin'=>true or /admin/... to go to the admin, and NOT all the rest of the links?
Expanding on user Abba Bryant's edit, have a look at how to create a helper in the cook book : http://book.cakephp.org/view/1097/Creating-Helpers
If having to disable routing manually on all your links is an annoyance (it would be to me!), you could create a new helper MyCustomUrlHelper (name doesn't have to be that long of course), and have it use the core UrlHelper to generate the URLs for you.
class MyCustomUrlHelper extends AppHelper {
public $helpers = array('Html');
function url($controller, $action, $params ,$routing = false, $plugin = false) {
//Example only, the params you send could be anything
$opts = array(
'controller' => $controller,
'action' => $action
//....
);
}
//another option
function url($params) {
//Example only, the params you send could be anything
$opts = array(
'controller' => $params['controller'],
'action' => $params['action']
//....
)
}
//just fill up $opts array with the parameters that core URL helper
//expects. This allows you to specify your own app specific defaults
return $this->Html->url($opts); //finally just use the normal url helper
}
Basically you can make it as verbose or terse as you want. It's just a wrapper class for the the actual URL helper which will do the work from inside. This allows you to give defaults that work for your specific application. This would also allow you to make a change in one place and have the routing for the whole application be updated.
EDIT
You could also check whether the passed $opts array is a string. This way you can have the best of both worlds.
Make sure if you use the prefix routing that you handle it in the HtmlHelper::link calls like so
<?php
...
echo $html->link( array(
'controller' => 'users',
'action' => 'logout',
'plugin' => false,
'admin' => false,
));
...
?>
** EDIT **
You could extend the url function in your AppHelper to inspect the passed array and set the Routing.prefixes keys to false if they aren't already set in the url call.
You would then need to specify the prefix in your admin links every time.
The HtmlHelper accepts two ways of giving the URL: it can be a Cake-relative URL or an array of URL parameters.
If you use the URL parameters, by default if you don't specify the 'admin' => false parameter the HtmlHelper automatically prefixes the action by 'admin' if you are on an admin action.
IMHO, the easiest way to get rid off this parameter is to use the Cake-relative URL as a string.
<?php
//instead of using
//echo $this->Html->link(__('logout', true), array('controller' => 'users', 'action' => 'logout'));
//use
echo $this->Html->link(__('logout', true), '/users/logout');
Kind regards,
nIcO
I encountered this problem this week and this code seemed to fix it. Let me know if it doesn't work for you and I can try to find out what else I did to get it to work.
$this->Auth->autoRedirect = false;
$this->Auth->loginAction = array(Configure::read('Routing.admin') => false, 'controller' => 'users', 'action' => 'login');
$this->Auth->logoutRedirect = array(Configure::read('Routing.admin') => false, 'controller' => 'users', 'action' => 'logout');
$this->Auth->loginRedirect = array(Configure::read('Routing.admin') => false, 'controller' => 'users', 'action' => 'welcome');
This was really frustrating, so I'm glad to help out.
I'm late to the party, but I have a very good answer:
You can override the default behavior by creating an AppHelper class. Create app/app_helper.php and paste the following:
<?php
class AppHelper extends Helper{
function url($url = null, $full = false) {
if(is_array($url) && !isset($url['admin'])){
$url['admin'] = false;
}
return parent::url($url, $full);
}
}
?>
Unless it is specified when you call link() or url(), admin will be set to false.

Zend_Controller_Router_Route Chaining Problem with more than 3 url parameters

I can't seem to figure out what's going wrong, but I'm attempting to setup module routing based on sub domain. Otherwise the routing is standard. The following works until I add more than 3 parameters in the URL:
This is within a controller plugin
...
public function routeStartup() {
$router = Zend_Controller_Front::getInstance()->getRouter();
$pathRoute = new Zend_Controller_Router_Route (
':controller/:action/*',
array(
'controller' => 'index',
'action' => 'index'
)
);
$hostRoute = new Zend_Controller_Router_Route_Hostname(':module.domain.com');
$chainedRoute = $hostRoute->chain($pathRoute);
$router->addRoute('host', $chainedRoute);
...
}
http://module.domain.com/controllerName/actionName/param1 works
http://module.domain.com/controllerName/actionName/param1/param2 does not work
Has anyone else run into this?
Looks like a bug in the framework routing code.
See http://framework.zend.com/issues/browse/ZF-6654 for a fix.

Categories