I have pages in my application that make up the navigation tree. I would like to dynamically insert pages into my navigation using the values of the request. I already have the logic to find the page and then call the addPage() method on it. What I'm looking for is how to easily pass the Zend_Controller_Request values to Zend_Navigation_Page::factory() so I can add that page. Maybe even written as a plugin?
Solution
AngelP got the closest, so I'm giving him credit, but here's my solution:
$request = $this->getRequest();
if ($page = $this->view->siteNav->findBy('id', $page_id)) {
$page->addPage(Zend_Navigation_Page::factory($request->getParams())
->setParams($request->getParams())
->setLabel($this->view->title)
->setVisible(false));
}
This code is executed from a controller action. $this->view->siteNav is an instance of Zend_Navigation that I have in the view. getParams() from the Zend_Controller_Request instance is easily passed to Zend_Navigation_Page::factory() and then to the setParams() method of the Zend_Navigation_Page_Mvc instance.
I have limited resources at the moment so I cannot really check my suggestion, but if you're in you controller, why don't you..
$controller = $this->_request->getControllerName();
$action = $this->_request->getActionName();
$page = new Zend_Navigation_Page( array(
'label' => "Sonny's Page",
'controller' => $controller,
'action' => $action
));
Maybe you could use this as a plugin so that you overload your view? And then add to your Navigation Container?
Cheers,
Angel
Since we don't know the code you have written already, I'm only guessing…
You need to:
retrieve the actual Zend_Navigation container used in navigation() view helper
create new Zend_Navigation_Page instance from array of data retrieved from the request
add the page to the container
assign the new container to the navigation helper
This should be easy. The rest you need to know:
how to write controller plugin with preDispatch method, and put the above there,
how to access current view instance in this plugin (from the view renderer or from application resource/bootstrap)
Then in the plugin you operate on navigation view helper as usual in the view.
Hope this clarified some things.
Why not store the instance in Zend_Registry and then in a postDispatch from either a plugin, module bootstrap or action controller add the pages to the original nav?
Related
I have a large application using SF 3.4 and I need to find a better way to do custom rendering based on user on each page load.
Right now we have listeners for a side menu, a footer, side menu favorites, an application menu, and whether or not you are timed out. These listeners fire a theme event via the render function that adds information pertaining to the users access/favorites/if they are timed out. This cannot be done with roles in twig because we have things like menu favorites that change all the time.
We override the Controller render method to dispatch that theme event and array_merge what we get back with the render function's parameter array like this:
protected function render($view, array $parameters = array(), Response $response = null)
{
$themeEvent = new ExampleThemeEvent($this->getUser(), $this->getMyMenuApplication());
$result = $this->getDispatcher()->dispatch(ExampleThemeEvent::NAME, $themeEvent);
$parameters = array_merge($result->getModel(),$parameters);
return parent::render($view, $parameters, $response);
}
Because of this most of our Controllers extend ExampleThemeController instead of just Controller so every page we want to have these features does.
This works fine, but the render method has been marked final as of SF 3.4 and shouldn't be overridden. What would be the best way to approach this without overriding the render method? Is there another place I can dispatch our theme event?
We have tried onKernelController subscribers/listeners to no avail because we need to add the extra view parameters to the container which cannot be done at that point.
Thank you!
For such user-specific changes that go beyond passing globals to twig, I'd check out embedding controllers. You can render a part of your response with a different controller, responsible only for the favourites for example.
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 call an action helper in one of my views using the following code
echo $this->action('foo', 'bar');
The fooAction in the barController does its thing and outputs a list of pages. However, the list has the layout in the output again, which is mightily irritating. If I disable the layout in the fooAction, this causes layout to be completely disabled on the live side, as well.
I'm vexed. I could just create a view helper, and there are many ways around this, but out of curiousity I was wondering if anyone had a solution to this.
From the ZF Reference Guide on Action ViewHelper
The API for the Action view helper follows that of most MVC components that invoke controller actions: action($action, $controller, $module = null, array $params = array()). $action and $controller are required; if no module is specified, the default module is assumed.
Modify your controller to accept a param that controls whether the action should disable the layout. When using the action helper, pass this control flag.
On a sidenote: using the Action ViewHelper is considered bad practise as it will go through the entire dispatch process again and this will slow down your app. If possible, try to access the model directly.
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