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();
}
}
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.
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 searched a lot around the web but I couldn't find any specific sollution to this.
In CakePHP 1.3, different from 1.2, if you had a controller inside a plugin, and both had the same name, you could access through "<plugin>/<action>", and it would call the 'default' controller. But in 1.3, according to this:
http://cakeqs.org/eng/questions/view/setting_up_magic_routes_for_plugins_in_cakephp_1_3
It was removed, and only the 'index' action in the default plugin controller can be accessed this way.
I thought about adding extra code in my routes.php file, and loop through all the plugins in my app, making such routes for every action in the controllers named after the plugin, but it doesn't seem like it's the right thing to do...
any other suggestions to make this work in 1.3? or at least some very specific code documentation of this particular change? I've already read something in the 1.3.0-RC4 annoucement, but it was not clear enough..
thanks
Assuming a plugin named "test", you could do something like this in app/plugins/test/controller/test_controller.php:
<?php
class TestController
extends AppController
{
public function index()
{
// Is there any additional args passed to us?
if(count($this->passedArgs) > 0)
{
// Is this a request for one of our actions?
$actualAction = $this->passedArgs[0];
if(is_callable(array($this, $actualAction)))
{
// Yup. Do it.
return call_user_func_array(array($this, $actualAction), array_slice($this->passedArgs, 1));
}
}
// Default functionality here.
die("Index of plugin requested.");
}
public function another($param1, $param2)
{
die("{$param1}, {$param2}");
}
}
You'll also have to add the following to app/config/routes.php:
Router::connect("/test/*", array("plugin" => "test", "controller" => "test"));
With this done, a request to /test/another/one/two will correctly render "one, two" in the browser, and a request to /test will display "Index of plugin requested."
I think this isn't a bad way to go, minimal fuss on the plugin consumer side, only a little bit of fluff in the plugin code.
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 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