Zend Framework 1 add single custom rule for routing - zend-framework

I am having problem with adding custom routing into my application. Zend Framework 1:
I have already working application with default Zend routing. I have a lot of controllers and for some of them I want to use friendly urls, for example:
domain.com/cities/edit/id/3
to
domain.com/cities/edit/abu-dhabi
There are some controllers to switch into that url (in that format).
I tried to configure it by ini file:
resources.router.routes.slugurl.route = /:controller/:action/:slug
And also by Bootstrap method:
protected function _initSlugRouter()
{
$this->bootstrap('FrontController');
$router = $this->frontController->getRouter();
$route = new Zend_Controller_Router_Route('/:controller/:action/:slug/',
array(
'controller' => 'slug',
'action' => 'forward',
'slug' => ':slug'
)
);
$router->addRoute('slug',$route);
}
The main problem - with ini configuration - request goes directly to controller city (not slug). Second one (bootstrap) - also executes the city controller, but the default routing not working as it was, I am not able to execute:
domain.com/cities/ but domain.com/cities/index/ works. The error without action defined:
Action "forward" does not exist and was not trapped in __call()
I can monitor the "slug" in controllers/move check into some library, but I would like to do it only with routing - that way looks much better for me.. Please, how to solve that issue?
edit with summary of solution
Thank you Max P. for interesting. I solved it at last - in last 2 minutes:) I had wrong rules at application.ini with routing. Right now it is defined that:
resources.router.routes.slugurl.route = /:controller/:action/:slug
resources.router.routes.slugurl.defaults.controller = :controller
resources.router.routes.slugurl.defaults.action = :action
resources.router.routes.slugurl.defaults.slug = :slug
The controller is defined like that:
class CitiesController extends Crm_Abstract
The Crm_Abstract has code:
<?php
class Crm_Abstract extends Zend_Controller_Action {
public function __construct(\Zend_Controller_Request_Abstract $request, \Zend_Controller_Response_Abstract $response, array $invokeArgs = array()) {
$params = $request->getParams();
if (!empty($params['slug'])) {
$modelSlug = Crm_Helper_Slug::getInstance();
$parameters = $modelSlug->redirectRequest($request);
if ($parameters !== false) {
foreach ($parameters as $key => $value) {
$request->setParam($key, $value);
}
}
}
parent::__construct($request, $response, $invokeArgs);
}
public function __call($methodName, $args) {
parent::__call($methodName, $args);
}
}
The slug helper get parameters for slug at defined controller. The url to access the slug is defined/calculated in place of $this->url - to $this->slug and there rest of code is done in slug helper. I am not sure if my solution is 100% sure, because that is my first time with routing.
The major thing is that - I just needed to change Zend_Controller_Action to Crm_Abstract which extends the Zend_Controller_Action. Single line - no additional custom code in each controller.

I created two route rules:
resources.router.routes.slugurl.type = "Zend_Controller_Router_Route"
resources.router.routes.slugurl.route = /:controller/:action/:slug/
resources.router.routes.slugurl.defaults.module = "default"
resources.router.routes.slugurl.defaults.controller = :controller
resources.router.routes.slugurl.defaults.action = :action
resources.router.routes.slugurl.defaults.slug = :slug
resources.router.routes.plain1.route = "/:controller/:action/"
resources.router.routes.plain1.defaults.module = "default"
resources.router.routes.plain1.defaults.controller = "index"
resources.router.routes.plain1.defaults.action = "index"
And for now that works fine. Bad thing - I don't understand routing (htaccess rules), but the time spent on that problem and solve it is better than modify all controllers...

Related

Zend Framework Router Hostname and Multi-Language support

Last two days I was fighting with Zend Framework Router and still didn't find solution.
Existing project has 2 different modules which work with the same domain e.g. www.domain1.com:
default - Contains public information, can be accessed via www.domain1.com
admin - Administrator interface, can be accessed via www.domain1.com/admin
Project is multi-langual and to keep language code it is transmitted as first parameter of every URL, e.g. www.domain1.com/en/ www.domain1.com/en/admin
Part of code which takes care is next plugin:
class Foo_Plugin_Language extends Zend_Controller_Plugin_Abstract
{
const LANGUAGE_KEY = 'lang';
public function routeStartup(Zend_Controller_Request_Abstract $request)
{
$languagesRegexp = implode('|', array_map('preg_quote', $this->_bootstrap->getLanguages()));
$routeLang = new Zend_Controller_Router_Route(
':' . self::LANGUAGE_KEY,
array(self::LANGUAGE_KEY => $this->_bootstrap->getLanguage()->toString()),
array(self::LANGUAGE_KEY => $languagesRegexp)
);
$router = $this->getFrontController()->getRouter();
$router->addDefaultRoutes();
$chainSeparator = '/';
foreach ($router->getRoutes() as $name => $route) {
$chain = new Zend_Controller_Router_Route_Chain();
$chain
->chain($routeLang, $chainSeparator)
->chain($route, $chainSeparator);
$new_name = $this->_formatLanguageRoute($name);
$router->addRoute($new_name, $chain);
}
protected function _formatLanguageRoute($name)
{
$suffix = '_' . self::LANGUAGE_KEY;
if (substr($name, -strlen($suffix)) == $suffix) return $name;
return $name . '_' . self::LANGUAGE_KEY;
}
public function routeShutdown(Zend_Controller_Request_Abstract $request)
{
$lang = $request->getParam(self::LANGUAGE_KEY, null);
$this->_bootstrap->setLanguage($lang);
$actual_lang = $this->_bootstrap->getLanguage()->toString();
$router = $this->getFrontController()->getRouter();
$router->setGlobalParam(self::LANGUAGE_KEY, $lang);
// Do not redirect image resize requests OR get js, css files
if (preg_match('/.*\.(jpg|jpeg|gif|png|bmp|js|css)$/i', $request->getPathInfo())) {
return true;
}
// redirect to appropriate language
if ($lang != $actual_lang) {
$redirector = Zend_Controller_Action_HelperBroker::getStaticHelper('redirector');
$params = array(self::LANGUAGE_KEY => $actual_lang);
$route = $this->_formatLanguageRoute($router->getCurrentRouteName());
return $redirector->gotoRouteAndExit($params, $route, false);
}
}
}
One of the first question is what do you think about such way to provide multi-lang support. What I've noticed is that all this chains dramatically decrease operation speed of the server, response time from the server is about 4 seconds...
Main question is: Currently I have to implement such feature: I have domain www.domain2.com that should work just with single module e.g. "foobar"... and it should be available via second url... or, of course, it should work like www.domain1.com/en/foobar by default...
To provide this functionality in Bootstrap class I'be implemented such part of code
// Instantiate default module route
$routeDefault = new Zend_Controller_Router_Route_Module(
array(),
$front->getDispatcher(),
$front->getRequest()
);
$foobarHostname = new Zend_Controller_Router_Route_Hostname(
'www.domain2.com',
array(
'module' => 'foobar'
)
);
$router->addRoute("foobarHostname", $foobarHostname->chain($routeDefault));
And that is not working and as I've found routeDefault always rewrite found correct model name "foobar" with value "default"
Then I've implemented default router like this:
new Zend_Controller_Router_Route(
':controller/:action/*',
array(
'controller' => 'index',
'action' => 'index'
);
);
But that still didn't work, and started work without language only when I comment "routeStartup" method in Foo_Plugin_Language BUT I need language support, I've played a lot with all possible combinations of code and in the end made this to provide language support by default:
class Project_Controller_Router_Route extends Zend_Controller_Router_Route_Module
{
/**
* #param string $path
* #param bool $partial
* #return array
*/
public function match($path, $partial = false)
{
$result = array();
$languageRegexp = '%^\/([a-z]{2})(/.*)%i';
if (preg_match($languageRegexp, $path, $matches)) {
$result['lang'] = $matches[1];
$path = $matches[2];
}
$parentMatch = parent::match($path);
if (is_array($parentMatch)) {
$result = array_merge($result, $parentMatch);
}
return $result;
}
}
So language parameter was carefully extracted from path and regular processed left part as usual...
But when I did next code I was not able to get access to the foobar module via www.domain2.com url, because of module name in request was always "default"
$front = Zend_Controller_Front::getInstance();
/** #var Zend_Controller_Router_Rewrite $router */
$router = $front->getRouter();
$dispatcher = $front->getDispatcher();
$request = $front->getRequest();
$routerDefault = new Project_Controller_Router_Route(array(), $dispatcher, $request);
$router->removeDefaultRoutes();
$router->addRoute('default', $routerDefault);
$foobarHostname = new Zend_Controller_Router_Route_Hostname(
'www.domain2.com',
array(
'module' => 'foobar'
)
);
$router->addRoute("foobar", $foobarHostname->chain($routerDefault));
Instead of summary:
Problem is that I should implement feature that will provide access for the secondary domain to the specific module of ZendFramework, and I should save multi-language support. And I cannot find a way, how to manage all of this...
Secondary question is about performance of chain router, it makes site work very-very slow...
The way I have solved problem with multilanguage page is in this thread:
Working with multilanguage routers in Zend (last post).
Ofcourse my sollution need some caching to do, but I think it will solve your problem.
Cheers.

Have a default controller in Zend

I have a module-based arhitecture in Zend Framework 1.11 (site.com/module/controller/action).
I've setup my site to have a default module, so that if I have the site module as default, and you go to site.com/something1/something2, it will actually take you to site.com/site/something1/something2.
I want to achieve the same thing 1 level further: say if you go to site.com/something, it should take you to site.com/site/index/something. I'm not talking about a redirect, just a re-routing.
Would something like this be possible?
If I understand correctly, it is possible and here is an example you can put in your Bootstrap:
protected function _initControllerDefaults()
{
$this->bootstrap('frontcontroller');
$front = Zend_Controller_Front::getInstance();
// set default action in controllers to "something" instead of index
$front->setDefaultAction('something');
// You can also override the default controller from "index" to something else
$front->setDefaultControllerName('default');
}
If you need the default action name to be dynamic based on the URL accessed, then I think you are looking for a custom route. In that case try:
protected function _initRoutes()
{
$router = Zend_Controller_Front::getInstance()->getRouter();
// Custom route:
// - Matches : site.com/foo or site.com/foo/
// - Routes to: site.com/site/index/foo
$route = new Zend_Controller_Router_Route_Regex(
'^(\w+)\/?$',
array(
'module' => 'site',
'controller' => 'index',
),
array(1 => 'action')
);
$router->addRoute('actions', $route);
}

Full dynamic router in Zend Framework

By default you have the following URL-syntax in ZF: /module/controller/action. What i want, is to build an menu-system where i can use any URL I want.
Lets say I make an menu-item called 'news'. When i call http://www.site.com/news i want to have the folowing loaded:
module: news
controller: frontpage
action: display
These config-values must be configured in the database-record for the menu-item.
How can I do this in zend? I spend a lot of time searching for it, but I still can't figure out how to. Does anybody?
I'd suggest using a front controller plugin to scan your database for all the entries, create routing rules based on those entries and add them to the router (see this).
Of course caching strategy is recommended so that you don't do a lot of processing on every request.
You can create a plugin and in routeStartup define something that intercept your request and route /module/controller/action to /action, but for this all your action names must be unique :
class My_CustomRouterPlugin extends Zend_Controller_Plugin_Abstract
{
public function routeStartup(Zend_Controller_Request_Abstract $request)
{
$fc = Zend_Controller_Front::getInstance();
$action =$fc->getRequest()->getActionName();
$router = $fc->getRouter();
$model= new myModel();
$myPage = $model->getPageByAction($action);
$route = new Zend_Controller_Router_Route('/action', array(
'module' => $myPage->getModule();
'controller' => $myPage->getController();
'action' => $action;
));
$router->addRoute($action, $route);
return $router;
}
}
In myModel define a method can get you an object(or an array) that contains module, controller names (from you DB ).
and register this plugin in your bootstrap:
$front->registerPlugin(new My_CustomRouterPlugin());

is it different 'default ' and 'Default' in zend default session namespace?

I am using sessions in zend framework.
the question is i need to know is there a difference between
new Zend_Session_Namespace("default");
and
new Zend_Session_Namespace("Default");
in my application, I have used both, it seems the code is not working correctly,
if there is a difference, what is the correct one to use.
here is my code
<?php
class Admin_DashboardController extends Zend_Controller_Action
{
function init()
{
//
}
/**
* Add hotelId to default session
* redirect to admin/hotels if hotelId is not avialble
*/
public function indexAction()
{
$params = $this->getRequest()->getParams();
$hotelid = NULL;
$config_session = new Zend_Session_Namespace("default");
$config_session->hotelid = $params['id'];
if(isset($params['id']) && !empty($params['id'])){
}else{
//redirect user to select hotels page
$redirector = new Zend_Controller_Action_Helper_Redirector();
$url = array(
'action' => 'admin/hotels/index'
);
$redirector->gotoRouteAndExit($url);
}
}
}
All Zend_Session_Namespace does internally is create a named array inside the $_SESSION superglobal. As array keys in PHP are case sensitive, "Default" and "default" will be treated as separate namespaces.
You can use whichever one you want, just be consistent if you expect to use the same data.

Zend framework. how to pass options to custom front controller plugin?

I have custom front controller plugin that takes some options.
At this time I load it (plugin) in application.ini file like this:
resources.frontController.plugins.DynamicLayout = "My_Controller_Plugin_DynamicLayout"
At this time I just have option.ini file and then use zend_config to import it.
Is there a way to specify plugin options from ZEND's primary application.ini file?
Maybe something like this?:
resources.frontController.plugins.DynamicLayout.test = "test_value"
I use something like this to pass info to my layouts using bootstrap.
This example is for an application that runs on different domains, thus different layouts. (and has a separate version for MSIE). Each domain as a separate application.ini
<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap {
protected function _initAutoload() {
return new Zend_Application_Module_Autoloader(array(
'namespace' => '',
'basePath' => APPLICATION_PATH
));
}
// <snip> more _initMethods: Loggers, ACLs, ViewHelpers, etc. </snip>
/**
* Setup dynamic layout plugin
*
* #return Zend_Controller_Plugin_Broker
*/
protected function _initFrontControllerLayoutPlugin() {
// Ensure the front controller is initialized
$this->bootstrap('FrontController');
$front = $this->getResource('FrontController');
$this->bootstrap('layout');
$layout = $this->getResource('layout');
// Set our Front Controller Plugin
// !!!! right here I pass values to the layout
// !!!! example layoutName, but you could pass anything you want...
$plugin = new Plugin_DynamicLayout($layout, $this->getOption('layoutName'));
return $front->registerPlugin($plugin);
}
}
The layout Handler:
<?php
class Plugin_DynamicLayout extends Zend_Controller_Plugin_Abstract {
private $layoutName;
public function __construct(Zend_Layout $layout, $layoutName) {
$this->layout = $layout;
$this->layoutName = $layoutName;
}
public function preDispatch(Zend_Controller_Request_Abstract $request) {
$layoutName = $this->layoutName;
if (false !== strpos($request->getHeader('User-Agent'), 'MSIE')) {
$layoutName = $layoutName . '-ie';
}
$this->layout->setLayout($layoutName);
}
}
The application.ini:
[production]
layoutName = "Some_File_Name"