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
Related
I have started to learn Zend Framework. I have done the quickstart tutorial and the akrabat tutorial.
Now I am trying to work on some of my own project with Zend Framework and it is a frustrating experience to say the least.
I made a layout that creates my standard header that shows up on every page. Now I want to display the user's name in the header which is retrieved from a mysql database. I don't know how to go about it. Do I interact with a controller and model from the layout??
Also, are there any other good guides for this kind of information? The zend documentation seems to be very detailed for each component but not very good at explaining how things work together.
Within your layout you will need this
<?= $this->layout()->content ?>
This will output anything from your view script.
In your project structure you need a controller and a view script for that controller. You should know how to access this if you have done the tutorial.
Your controller should get the username from the DB and assign it to the view like so
$this->view->username = "John";
Then in your view
echo $this->username;
EDIT
In your Bootstrap class register a new plugin
$plugin = new Default_Controller_Plugin_Username();
Zend_Controller_Front::getInstance()->registerPlugin($plugin);
This plugin might look like this
class Default_Controller_Plugin_Username
extends Zend_Controller_Plugin_Abstract
implements Lib_Observer_Observable
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
//Do things in here to get username
//Then you can set it in the registry
Zend_Registry::set("username", $username);
}
}
If you have the $user from some kind of auth/login sequence, then you might be using Zend_Auth. The default container for the identity data is session-based, so depending upon your session-handling settings, you might not need to hit the db each time to get that user info.
The bigger question to me is whether you need a front controller plugin to place this $user info from Zend_Auth into the view (to be rendered in your layout) or whether the layout can pull from Zend_Auth directly.
I've seen (and used) both.
Your problem is the problem of the block composition on the page. As you may know Zend Framework is only a Framework, so there's really more than one way to deal with this problem.
To fill the layout part you could for example use :
some variable containing the block and given to the view for each controller (with a controller plugin? a main Controller that all controllers inherits?
the Action Helper in the view to chain an internal call to another MVC call in this part of the layout
the Action stack
certainly others things
I think the nicest one is the Action stack. The way it work is:
You make a main query on the MVC-thing (where you get yout fooAction called in a controller)
You call the Action stack in this Action to tell ZF that some others internal calls on some other Action should be done. Here you can list all blocks that should be filled in the Layout
Others bloacks are called by the internal MVC loop. Some other barAction userAction entry points are called, in these actions you tell the Layout which key of the layout is filled by the resulting view rendering with setResponseSegment.
Here's how it looks in code:
For the block action (here a /modulefoo/titi/pilili internal request), that should fill the 'foobar' block of the Layout :
public function pilipiliAction() {
(...) // do things
$this->_helper->viewRenderer->setResponseSegment('foobar');
}
On the layout you echo this block with:
<?= $this->foobar ?>
Now this action must be called by your main action (with the Action Stack helper). But the nicest way will be to use a new custom helper that will stack all the blocks actions.
Here 's a fooAction on a controller, your classical entry point. At the end of the action an helper is called that should build the blocks of the layouts
public function fooAction() {
(...) // do things
// Build Main layout
$this->_helper->LayoutBuilder();
}
Here's an example of what this helper would do (should work, it's a simplified version of an existing one which makes a lot of others things, like managing cache for blocks):
class My_Action_Helper_LayoutBuilder extends Zend_Controller_Action_Helper_Abstract
{
/**
* #var $actionStack
* local ActionStack builtin helper used to Stack several MVC actions,
* for action for each Layout block
*/
protected static $actionStack;
/**
* #var _layout
* reference to the general layout collecting view
*/
protected $_layout;
public function __construct() {
self::$actionStack = Zend_Controller_Action_HelperBroker::getStaticHelper('actionStack');
$this->_layout = Zend_Layout::getMvcInstance();
}
public function direct($blocklist = null) {
$layoutblocks = array(
array('module' => 'default',
'controller' => 'index',
'action' => 'nav')
,
array('module' => 'modulefoo',
'controller' => 'titi',
'action' => 'pilipili') // action given upper
);
$auth = Zend_Auth::getInstance();
if ($auth->hasIdentity()) {
$request = Zend_Registry::get('request');
$module = $request->getModuleName();
$layoutblocks[] = array('module' => $module,
'controller' => 'index',
'action' => 'modulenav');
}
if (isset($blocklist))
{ //user had his own blocklist to add
$layoutblocks = array_merge($layoutblocks,$blocklist);
}
$this->buildCompositeLayout($layoutblocks);
}
public function buildCompositeLayout($blocklist = null) {
foreach($blocklist as $MVCBlock) {
$block = $MVCBlock['action'];
if (!isset($MVCBlock['module'])) {
$module = $this->getRequest()->getModuleName();
$front = $this->getFrontController();
$module = $front->getDispatcher()->getDefaultModule();
} else {
$module = $MVCBlock['module'];
}
if (!isset($MVCBlock['controller'])) {
if (!isset($front)) {
$front = $this->getFrontController();
}
$controller = $front->getDispatcher()->getDefaultController();
} else {
$controller = $MVCBlock['controller'];
}
// Here we stack actions
self::$actionStack->actionToStack($block,
$controller,
$module,
);
}
}
But as I said before, there's more than one way to do it. I like this way as I can interactively add new blocks for some requests, add some caching in the block rendering, and I can as well 'forget' to call the LayoutBuilder helper for some actions, like the ajax ones. The secret is that the MVC loop of Zend Framework is really a loop; it can run several actions for one request.
Zend_Auth rocks.. you need to use it.
There's a good tutorial on Zend_Auth here:
http://akrabat.com/zend-auth-tutorial/
This includes the process of creating a View Helper to do the repetitive task of displaying the Username, role and a logout link.
Take the time to come to grips with View Helpers.. they really explode your productivity and code reuse when coding views.
Duncan.
#Mike, this answer from #jakenoble is exactly correct for your scenario.
If you want to perform some global actions in your controllers then i suggest to create your own base controller class inherited from Zend_Controller say MyBaseController.
Then you can inherit all your controllers from MyBaseController, this way all the actions performed in init() method of MyBaseController will global to your complete project.
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();
}
}
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.
I've just put together a very basic site using the Zend Framework and its MVC. (Actually, I'm not even using models at the moment, it's all just Controllers/Views for static info so far).
When I started toying with the Forms I realized that in the examples for Zend_Form they use something like this this set the form's action:
$form->setAction('/user/login')
Which contains the URL. I understand that Zend Framework has Routes and that they can be named, but I can't seem to grasp from the manual how to create a simple route for certain Controller/Actions so that I could do something like this:
$form->setAction($named_route)
// or
$form->setAction('named_route')
I hope my question is clear. I wasn't able to locate any duplicate questions, but if you spot one and point it out I won't mind.
Links to resources are as good as examples, so don't waste your time if you know of a decent blog post somewhere. Thanks!
References:
http://framework.zend.com/manual/en/zend.controller.router.html#zend.controller.router.routes.standard - Look for "12.5.7.1. Zend_Controller_Router_Route" for a clearer explanation.
This is not the best way to do it, but I have a working example. I welcome corrections.
After seeing Shorten Zend Framework Route Definitions, I agree that named Routes should go in their own config (I use Django, and named Views/URLs are generally separated) - but here I'm just going to define Routes in my Bootstrap.
So, in Bootstrap.php, inside the Bootstrap Class of course, I've created an function that will be automatically run, like so:
public function _initRoutes()
{
$frontController = Zend_Controller_Front::getInstance();
$route = new Zend_Controller_Router_Route(
'login/', // The URL, after the baseUrl, with no params.
array(
'controller' => 'login', // The controller to point to.
'action' => 'index' // The action to point to, in said Controller.
)
);
$frontController->getRouter()->addRoute('loginpage', $route);
}
In the above example, "loginpage" will be the Name of the "Named Route".
So, inside my LoginController, (in a function that builds the form) instead of doing
$form->setAction('/blah/login')
I retrieve the URL of the named Route and pass that in, like so:
$form_action_url = $this->view->Url(array(), 'loginpage', true);
// -- SNIP --
$form->setAction($form_action_url) // ...
This may be pointless and wrong, but it seems to work at the moment.
My reason for wanting a named URL, when Zend Framework handles URLs as /Controller/View(Action)/ automatically is because I'm anal about that kind of thing. I've been using Django for awhile, where the URLs are predefined, and I like it that way.
The Zend Framework MVC urls working out of the box is nice, tho.
Feel free to add notes and corrections to how this should work!