For a new CMS i've developed a Pages module that allows me to manage the site's tree structure. Each page is reachable from the url http://www.example.com/pageslug/ where pageslug identifies the page being called.
What I want to achieve now is a route that allows me to route all incoming requests to a single PagesController unless it's a request to an existing controller (like images for example).
It's easy enough to catch all requests to the Pages Controller but how to exclude existing controllers?
This is my module bootstrap. How can i achieve this in the most preferrable way
<?php
class Default_Bootstrap extends Zend_Application_Module_Bootstrap
{
protected function _initRoute()
{
$this->bootstrap('frontController');
/* #var $frontcontroller Zend_Controller_Front */
$frontcontroller = $this->getResource('frontController');
$router = $frontcontroller->getRouter();
$router->addRoute(
'all',
new Zend_Controller_Router_Route('*',
array('controller' => 'pages',
'action' => 'view')
)
);
}
}
Zend routes work in order - if you add a second route after your first, it will take precedence if it matches. In my own Zend project I've got a bunch of routes, the first of which is much like yours, a catch all route. However, anything below it that matches the url overrides it - so just try adding slightly more specific routes (if all your /user/ requests go to your user_controller, add a /user/* route)
Making your pages controller default, and adding a route for every existing controller might get very messy very quickly, and you have to change it every time you add a controller.
An alternative might be to customize the ErrorController. Since in the event of a missing controller, the framework will throw a Zend_Controller_Dispatcher_Exception which will propagate through to the error handler as an EXCEPTION_NO_CONTROLLER, you can just check for that type and forward to your pages controller.
If you're feeling masochistic, you could also write a custom route class that returns false if the controller exists and handles all routes if not. This is probably the best option in terms of roles and responsibilities, but also the most complex to implement.
Related
while i'm well awared this topic might have come a number of time, i still think that the perspective from which i'm looking at it is different.
I have a ZF 1.10.8 project whith essentially ContentController to manage what i call static pages (not so static anyway) like about us, contact us, products and NewsController for articles, tutorials, all writeups.
i've found that having a dynamic menu will solve a lot of complains from the client and gives more freedom changing the content of the site.
currently i only a main menu bar which is a partial (under layouts/scripts/partials/_mainmenu.phtml folder) which i call in every layout that exists in the system.
Now if i go dynamic, and a new link is created let's say category, how to map the category page to a route (routes are in /application/configs/routes.ini) since the url would be the value of the link in the menu table in the database?
the first thought is to change everything to resource handled by the NewsController to even about us will be an article in that case.Since those that i referenced as static pages require different view i wouldn't know how to handle them.
I'm kind of uncomfortable with my way of thinking it.
Can anyone point me to the right direction please? How would you do if? how joomla guys do it?
thanks for reading.....
i am using named routes to build links in menu
new Zend_Navigation_Page_Mvc(array(
'label' => $category->getTitle(),
'route' => 'catalog-category',
'params' => array('id' => $category->getId()),
));
exact module/controller/action mapping handled by routes
module specific routes defined in module bootstrap.
i cant give you more on this as my current routes implementation are too tricky and fragile. must heavily refactor it first.
My approach is to have a plugin that in routeStartup() checks for the existence of the current URI in a database, if the URI is found then the correct route is added using this function:
protected function _createRoute($route, $name, $action = "index", $controller = "index", $module = "default", $params)
{
$router = Zend_Controller_Front::getInstance()->getRouter();
$defaults = array('controller' => $controller,
'action' => $action,
"module" => $module);
$route = new Zend_Controller_Router_Route($route, array_merge($defaults, $params));
$router->addRoute("region", $route);
}
I don't have a limitless number of controllers and modules so the parameters for this function are hard coded into the plugin, but they could easily for stored against the row in the DB to be more dynamic.
Adding the plugin do this means that after routeStartup() is complete the new correct route is available for the dispatch operation.
I'm quite new in Zend framework, but quickly learning. I've encountered the following problem, but I don't really know if my solution is good :)
I've created an application which uses widgets. Widget is a class which implements Widget_Interface and is executed by Widget_Manager.
Widgets can be loaded via WidgetController (which calls Widget_Manager, etc). Now the problem I encountered is: widgets can also be configured, and to make the code more transparent, I'd like a widget to have its own controller (currently, it is only a class). But the problem is, I'd like all widget configurations to be addressed via WidgetController, and then passed to specific widget controller.
An example: let's say I've got a widget named 'scrobbler'. Now when configuring it in the UI, I'd like to make Ajax request with updated settings. I could make a request like http://myapp.com/scrobbler/update-info/, so the framework would run ScrobblerController and I'd process the information from here on.
My idea is to make a request on http://myapp.com/widget/update/scrobbler/, so the framework runs WidgetController. WidgetController would then call ScrobblerController and pass other parameters.
I'm aware of _forward() function in Zend_Controller, but I'd like to have widget controllers and my application's controllers separated (let's say application controllers in /application/controllers and widget controllers in /application/controllers/widgets).
Is it possible to make this and what do I have to add to the Zend framework configuration? Hope I didn't complicate too much :)
Nice day
Edit:
Solved this using modular structure, and moved common classes into root directory.
You coud probably utilize Controller helpers instead of controllers in this case. So let's say that WidgetController is responsible for updating all types of widgets. The updateAction would need to find information on which widget type you wish to configure, this is the scrobbler parameter. You would need to name this parameter so it can be accessed easily. This can be done by either adding a route or adding the name before scrobbler in the uri.
Solution 1: Add a route:
In Bootstrap:
public function __initRoutes () {
$route = new Zend_Controller_Router_Route(
'widget/update/:type',
array (
'controller' => 'widget',
'action' => 'update'
),
array (
'type' => '[a-z_-]*'
)
);
/* #var $fc Zend_Controller_Front */
$fc = $this->bootstrap('FrontController')->getResource('FrontController');
/* #var $router Zend_Controller_Router_Rewrite */
$router = $fc->getRouter();
$router->addRoute('update-widget', $route);
}
Solution 2: Add the parameter name in the uri:
Make requests to /widget/update/type/widgetName instead.
Now, in the WidgetController::updateAction, you can fetch the widget to update using $this->_getParam('type').
So the code could look something like:
class WidgetController extends Zend_Controller_Action
{
public function updateAction ()
{
$widgetName = $this->_getParam('type');
$this->view->result = $this->_helper->Widgets->update($widgetName);
}
}
class App_Controller_Helper_Widgets extends Zend_Controller_Action_Helper
{
public function update($widgetName)
{
$widgetManager = new App_Model_WidgetManager();
$widget = $widgetManager->load($widgetName);
$widget->setOptions($this->getRequest()->getParams());
return $widget->save();
}
}
I have a controller and action which I'm accessing through a custom URL. The original route is still accessible though at the default location
zend.com/controller/action
How can I change this to simulate a "Page not found" when the user tries to access this URL? Is it possible?
If the action handler is used to respond to both URLs, you would first have to detect which URL is being requested (using $this->_request->getRequestUri()). If the default URL is detected I think the easiest way to create a "page not found" would be to use
$this->_redirect("/path/to/simulated/404/page")
and set up a controller and action to respond.
This won't actually send an HTTP 404, though. To do that, I think you would have to raise an exception within your action handler. I don't know what the official "zendy" way of doing this is, but this seems to work:
throw new Zend_Controller_Action_Exception('Not Found', 404);
You could change the main controller script to redirect a certain controller name and action name to a new page. But it's probably easier to add a new rule to the .htaccess file, indicating that this specific URL should be redirected to an error page. Example:
RewriteRule ^controller/action/?$ / [R=404,L]
Or redirect the page to an error page within your site:
RewriteRule ^controller/action/?$ /error/page-not-found/ [L]
You need to use:
$this->getResponse()->setHttpResponseCode(404);
And build your own 404 view
$this->view->message = 'Page not found';
Or you could forward to an error controller for example
$this->_forward('page-not-found', 'error');
Finally, if you have in your error controller
//...
switch ($errors->type) {
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
// 404 error -- controller or action not found
$this->getResponse()->setHttpResponseCode(404);
$this->view->message = 'Page not found';
break;
//...
You can just do as #bogeymin said:
throw new Zend_Controller_Action_Exception('Not Found', 404);
If you are looking other solutions than mod_rewrite based, you may create a Regex Route to match the actions you need to hide.
The other solution is to restrict access to Actions using Zend_Acl, treating each action as an ACL resource.
But the simplest and most lightweight solution is still mod_rewrite in .htaccess.
Edit:
As you can see, this may be done in numerous ways. But probably, you will need some kind of the switch, to still allow somehow to access the "hidden" action. In this case, use:
mod_rewrite for quick implementation (switching requires the person to know the .htaccess rules)
Zend_Router - the person who knows the right route can still access the feature
Zend_Acl + Zend_Auth for scalable and secure solution.
If you don't need to have authenticated users, Zend_Acl combined with Zend_Router might be the solution.
For smart handling the exceptions and building ACL's, see this (and other posts on this blog):
Handling errors in Zend Framework | CodeUtopia - The blog of Jani Hartikainen
By default the router includes default routes for :module/:controller/:action/ and :controller/:action/. You can disable these with:
$router->removeDefaultRoutes();
then only routes you setup will work. If you still want to use the default routes for some other things, you'll either have to go with one of the other answers posted or add your own 'default' routes which will match all but the modules/controllers you have custom routes for.
If you don't want to remove the default route as #Tim Fountain suggests, you should do something like this in your controller (either preDispatch or whateverAction methods)
$router = $this->getFrontController()->getRouter();
$route = $router->getCurrentRouteName();
// if we reached this controller/action from the default route, 404
if ($route == 'default')
{
throw new Zend_Controller_Action_Exception('Not Found', 404);
}
I think, all answers above are incorrect. Those show ways to achieve the same thing, but present logic at the wrong place in your application, which eventually can cause trouble later on.
The correct part of your route logic is, how extremely simple, in the routes. What is missing is that the default route Zend_Controller_Router_Route_Module does not allow you to add exceptions to specific routes. So what you need to do, is remove the default route from your routes, and add a new custom route (which should function exactly as the default route, but allows excludes) at it's place.
You can write the new route by extending the class of the default route.
class My_Custom_Route extends Zend_Controller_Router_Route_Module
{
protected $_excludes = array();
public function exclude($abc)
{
//add to $_excludes here the controller/action you want to exclude
}
public function match($abc)
{
//add functionality here that denies if the mod/contr/action is in $_excludes
//you can also add this in a separate method
//re-use parent code
}
}
You can now add add the excludes for example in a config file, and load + add the excludes at the place you initiate the new Route (and add it to the router). Off you go.
I would like to keep the old default Zend Router, and just add a router for administration subpages since the controllers are growing in size and I would like to logically separate them a little as well as have cleaner URLs.
The documentation seems to explain how to do other things but not this...
This will work out of the box with the default routes. You just need to add an administration module, and then /administration/users will map to the users controller in the administration module.
I don't know if its possibile to do it with an Underscore and the upcase user, sorry, but without you had to add the following to your bootstrap.php
$ctrl = Zend_Controller_Front::getInstance();
$router = $ctrl->getRouter();
$route['admin_users'] = new Zend_Controller_Router_Route_Regex(
'administration/users',
array(
'controller' => 'administrationusers',
)
);
$router->addRoute('admin_users_route', $route['admin_users']);
note: in this scenario your controller is:
class AdministrationusersController extends Zend_Controller_Action
{
// stuff
}
I am working on a project that needs to use a database driven MVC scheme where the route to the controllers and views are controlled through a single database table. However, I haven't been able to find any tutorials that demonstrate this with a current version of the framework (they all appear to have been written several versions ago) and I was wondering if anyone has done something like this with a more recent version of the framework or if anyone knows of blogs or tutorials that discuss how to accomplish this in a simple manner.
The basic idea is that there will be a sitePage table that will contain pageName, controller, module and view fields. When the request is processed I need to query the database for the given pageName and determine the appropriate controller, module and view and then pass this into the necessary Zend class to continue with the normal routing and processing of the request.
Thanks in advance.
You can also use the routeStartup() method in your plugin.
eg:
class My_Plugin_PageRoute extends Zend_Controller_Plugin_Abstract {
public function routeStartup () {
$front = Zend_Controller_Front::getInstance();
$pages = new Model_Pages();
$page_data = $pages ->getPageInfo();
$router = $front->getRouter();
foreach($page_data as $page) {
$r = new Zend_Controller_Router_Route(
'' . $page -> page_name,
array('controller' => 'pages',
'action' => 'index',
'page_id' => $page -> page_id)
);
$router->addRoute('pages_' . $page -> page_id, $r);
}
}
}
I realized that a more elegant approach is indeed to use a router, but for that you would need to create a custom one by extending the Zend_Controller_Router_Abstract class and implementing the "route" method.
You get a Zend_Controller_Request_Abstract object as the parameter of the "route" method. There you can talk to the database and then you can use:
Zend_Controller_Request_Abstract::setModuleName(),
Zend_Controller_Request_Abstract::setControllerName(),
Zend_Controller_Request_Abstract::setActionName()
to define your route.
I hope it helps!
Maybe the best aproach is not by using routers but by using plugins or a common controller. Without a deeper analysis I would suggest you to create a Front Controller Plugin, and then inside the preDispatch() method you can talk to the database and reset the request so it is dispatched to the right controller.
You can also get the same effect by using a common controller, all requests are routed to it then it can forwards to the right controller after talking to the database, although I prefer to use a plugin.
From the Manual:
preDispatch() is called before an action is dispatched by the dispatcher. This callback allows for proxy or filter behavior. By altering the request and resetting its dispatched flag (via Zend_Controller_Request_Abstract::setDispatched(false)), the current action may be skipped and/or replaced.
http://framework.zend.com/manual/en/zend.controller.plugins.html