How to make Zend automatically switch view and layout with contexts? - php

I have a mobile site that I added detection to for iPhones and other iOS devices. The iOS page needs a different layout and views than the regular pages (which are actually for older mobile devices). So, I have some code that does mobile detection, that part was easy. What I'd like to do is make it so that Zend automagically finds and uses the correct layout and view when an iOS device is detected, but that has turned out to be surprisingly hard...
I needed it to be up and running ASAP, so I did a quick and dirty hack that worked: in each action function, I have a simple If statement that detects if the iOS boolean flag has been set (which happens in the controller's init), and if so, overrides the layout and view explicitly. Existing code (in the actions):
if ($_SESSION['user']['iPhone']) {
$this->_helper->layout->setLayout('osriphone'); // 'osr' is the name of the app
$this->_helper->viewRenderer->setRender('iphone/index');
}
So this works, but it's kinda ugly and hacky and has to be put in each action, and each action's Renderer has to be set, etc. I got to reading about the Zend ContextSwitch, and that seemed like exactly the kind of thing I should use (I'm still kind of new to Zend), so I started messing around with it, but can't quite figure it out.
In the controller's init, I'm initializing the ContextSwitch, adding a context for 'iphone' and setting the suffix to 'iphone', and now what I'd like to do is have a single place where it detects if the user is an iOS device and sets the context to 'iphone', and that should make it automatically use the correct layout and view. New code (in the controller's init):
$this->_helper->contextSwitch()->initContext();
$contextSwitch = $this->_helper->getHelper('contextSwitch');
$contextSwitch->addContext('iphone', array('suffix' => 'iphone'));
$contextSwitch->setAutoDisableLayout(false);
if ($_SESSION['user']['iPhone']) {
//$this->_currentContext = 'iphone'; // Doesn't work.
//$contextSwitch->initContext('iphone'); // Doesn't work.
//$contextSwitch->setContext('iPhone'); // Not the function I'm looking for...
// What to put here, or am I barking up the wrong tree?
}
I did some reading on the contextSwitcher, and it seems like there is a lot of stuff on, e.g. setting it to be specific to each particular action (which I don't need; this needs to happen on every action in my app), and going through and modifying all the links to something like /osr/format/iphone to switch the context (which I also don't really need or want; it's already a mobile site, and I'd like the layout/view switch to be totally transparent to the user and handled only from the backend as it is with my quick and dirty hack). These seem like basically an equal amount of code to my quick and dirty hack. So... Anyone have some suggestions? I'm really hoping for just a single line like "$contextSwitch->setContext('iphone');" that I could use in an If statement in my controller's init, but the Zend documentation is awful, and I can't seem to find any examples of people doing something like this on Google or SO.

Ok I think I figured out how to put this into a plugin:
The Plugin:
//This is my own namespace for ZF 1.x library, use your own
class My_Controller_Plugin_Ios extends Zend_Controller_Plugin_Abstract {
public function preDispatch(Zend_Controller_Request_Abstract $request) {
parent::preDispatch($request);
if ($_SESSION['user']['iPhone']) {
$this->_helper->layout->setLayout('osriphone');
$this->_helper->viewRenderer->setRender('iphone/index');
}
}
}
register the plugin in your application.ini
resources.frontController.plugins.ios = "My_Controller_Plugin_Ios"
I think that's all there is to it. Although you may want to look into the userAgent plugin

ContextSwitch operates off the "format" property in the request object (by default). You need to set it somewhere in your app
$requestObject->setParam('format', 'iphone').
I'd set it in a bootstrap, or more appropriately, a controller plugin, but where it goes really depends on your app.

I don't use Zend ContextSwitch so I can't really help there, but you could use some inheritance in your controllers to set all layouts in just a couple of lines. Even though it might still be classed as a "hack" it is a way better hack
Now whenever you execute a action Zend first fires a number of other functions within the framework first, such as the routing, the preDispatch, Action helpers and so on. It also fires a number of things after the action such as PostDispatch. This can be used to your advantage.
First create a controller called something like "mainController" and let it extend Zend_Controller_action and in this controller create a function called predispatch()
Second. Extend your normal controllers to mainController. Since we now have a function called predispatch() Zend will automatically fire this on every controller, and if you do your iPhone/iOS check there it will automagically be performed on every action on every controller, as long as you don't overwrite the method in your controller (you can make this method final to prevent this). You can offcourse use a multitude of different non-Zend functions and/or helpers within the mainctroller to make the code as compact and reusable as possible Se example code below:
<?php
/**
*Maincontroller
*/
class MainController extends Zend_Controller_Action
{
/**
* Predispatch function is called everytime an action is called
*/
final public function preDispatch(){
//for security reasons, make sure that no one access mainController directly
$this->request = $this->getRequest();
if (strtolower($this->request->controller)=='main')
$this->_redirect('/index/index/');
//Check for iPhone
if ($_SESSION['user']['iPhone']) {
$this->_helper->layout->setLayout('osriphone'); // 'osr' is the name of the app
$this->_helper->viewRenderer->setRender('iphone/index');
}
}
}
<?php
/**
*Othercontroller
*/
class OtherController extends MainController
{
/**
* The correct layout for IndexAction is already set by the inherited preDispatch
*/
public function indexAction(){
/* YOUR CODE HERE */
}
}
For a good overview of the dispatch process check these links (same picture in both):
http://nethands.de/download/zenddispatch_en.pdf
http://img.docstoccdn.com/thumb/orig/22437345.png

Related

Cakephp custom folder structure for admin

Since my app is getting bigger i would like to separate admin prefix actions and views from normal actions and views. The new folder for admin is Controller/admin/UsersController.php.
I would like to change my cakephp controllers and views folder structure to match the prefix I'm using.
Example for admin prefix:
Controller:
app/Controller/UsersController.php (contain view(), index() ...)
app/Controller/admin/UsersController.php (contain admin_view(), admin_index() ...)
View:
app/View/Users/index.ctp (for index() in UsersController.php)
app/View/Users/admin/index.ctp (for admin_index() in admin/UsersController.php)
How can I implement this structure using Cakephp 2.6?
Unlike in 3.x where this is the default behavior for prefixes, this isn't supported in 2.x.
You could try hacking it in using a custom/extended dispatcher (in order to retrieve the desired controller) or even dispatcher filters in case you are adventurous, and in your app controller modify the view template path with respect to the prefix.
That should do it, however I would probably simply go for using plugins instead, this will separate things just fine without any additional fiddling.
If you just want to separate logic you could do something like this. It's untested an just thought to give you just the idea. I'll explain the concept after the code:
public function beforeFilter() {
if ($this->request->prefix === 'foo') {
$name = Inflector::classify($this->request->prefix);
$className = $name . 'ChildController';
App::uses($className, 'Controller/Foo');
$this->ChildController = new $className($this);
}
}
public function __call($method, $args) {
if ($this->request->prefix === 'foo' && method_exists($this->ChildController, $method)) {
call_user_func_array([$this->ChildController, $method], $args);
}
}
Depending on the prefix you can load other classes. How you load that class and how you instantiate it, what params you pass to it is up to you. In my example I'm passing the controller instance directly. I think you could actually init a complete controller here but be aware that components like the Session might cause a problem because they might have been already initiated by the "parent" controller.
When you now call a controller method that doesn't exist it will try to call the same method with the same arguments on the ChildController. Not really a great name for it, but maybe you can come up with something better.
You'll have to implement some logic to load the views from the right place in your classes as well but this shouldn't be hard, just check the controller class.
But actually I don't see your problem, I've worked on an app that hat over 560 tables and not putting the code into sub folders wasn't a problem, it did in fact use a similar solution.
I think you have to much code in your controllers, get more code into your models and the controller shouldn't be a problem.
Another solution might be to think about implementing a service layer in CakePHP which implements the actual business logic while a model is reduced to a data handler. The service would sit between a controller and the model. I've done this a few times as well now and if done right it works very well.

In Magento what is the correct data flow from POST to controller to model to block to template?

When data is sent to a Magento extension via GET or POST, what is the correct way to use the MVC to validate the data, process it based on some business logic then output the results to the screen?
As I understand it, the controller is the correct place to receive and validate the data being submitted (perhaps calling on a model to do the actual validating). Models are the correct place for any business logic, taking the request parameters and processing them. The block is the correct place to prepare data for output, which it can request from a model.
I understand how a controller can receive posted data and forward it to a model
class Company_Project_IndexController extends Mage_Core_Controller_Front_Action
{
public function receivePostedData()
{
$model = Mage::getModel('project/somemodel');
if($model->validateData( $this->getRequest()->getPost('post_vars') )) {
$model->processData( $this->getRequest()->getPost('post_vars') );
}
}
}
which the model will then process
class Company_Project_Model_Somemodel extends Mage_Core_Model_Abstract
{
public function validate( $data )
{
//return true of false if data is valid/invalid
}
public function processData( $data )
{
//Do something with the data
}
public function getData()
{
//return something
}
}
I also understand how a block can instantiate a model and get data from it
class Company_Project_Block_Display extends Mage_Core_Block_Template
{
public function getData()
{
$model = Mage::getModel('project/somemodel');
return $model->getData();
}
}
The problem I have is the disconnect between the controller and the model working together and the Block and the model working together. If the controller is pushing data into the model which is changing the models state, how do I then get data into the block from the model based on that changed state?
As I see it, there are the following possible solutions:
Use Mage::getSingleton to get my model so I'm referring to the same instance in the controller and the block
Refer to the post data directly inside the model or the block
Use the registry to store some of the model state
I've know idea if any of the above are correct or which one I should be using.
I've taken a look at the core Catalogue Search module as its doing exactly what I need, but I got completely lost in the code.
I know that several other very similar questions to this have been asked and answered but I'm still in the dark.
It's a good question, but there isn't one clear answer. If you look at the Magento core itself, you'll see a variety of methods used. All that said, here's some general advice/context for the tradeoffs involved.
Based on usage, Magento's controller action methods are not designed to interact with The View (or as it's known in Magento, The Layout). Instead, a controller action is meant as the main entry point for a specific URL, which should then interact with the request and do stuff with models. When that's done, the controller action then tells the layout to render with a calls to
$this->loadLayout();
$this->renderLayout();
As designed (and this is just an opinion) The Layout is completely decoupled from the rest of the system. CodeIgniter/Kohana style systems feature "dumb views", which take variables from the controller action and do a simple template replacement on them. The Magento Layout, on the other hand, is a collection of nested block objects, and block object methods are meant to query the models directly when they need information.
For example — take a look at the Mage_Adminhtml_Block_Catalog_Product_Attribute_Set_Main_Formset block
#File: app/code/core/Mage/Adminhtml/Block/Catalog/Product/Attribute/Set/Main/Formset.php
protected function _prepareForm()
{
$data = Mage::getModel('eav/entity_attribute_set')
->load($this->getRequest()->getParam('id'));
//...
}
The idea here is, irrespective of what happens in the controller action, the Mage_Adminhtml_Block_Catalog_Product_Attribute_Set_Main_Formset block will always grab the latest eav/entity_attribute_set model information when it renders (accessing the request object to get the request/post data, as you mentioned above). The controller action and the view are decoupled from one another.
As designed, the is The Right way to use the system. Unfortunately, it's also ineffective from a performance standpoint. For developers coming form other systems, the idea that view rendering kicks off another round of SQL queries seems crazy. Magento's famous "start shooting and let the caching layer" sort it out approach met with mixed results.
One remedy for this potential performance problems is where stashing already instantiated models in the registry comes into play. You can see an example of this in the admin console's product editing controller
#File: app/code/core/Mage/Adminhtml/controllers/Catalog/ProductController.php
protected function _initProduct()
{
//...
Mage::register('current_product', $product);
//...
}
and then its use in many of the resulting blocks which render the editing form for products
app/code/core/Mage/Adminhtml/Block/Catalog/Product/Edit/Js.php
public function getProduct()
{
return Mage::registry('current_product');
}
The trade-off here is better SQL performance (no extra queries for the blocks), but you've essentially tied these blocks to a controller action which defines a current_product registry entry. The Magento registry is (in essence) a fancy way of doing global variables, so you have most of the problems inherent with globals. As you noted, you can achieve similar results with the getSingleton method — but that may not be appropriate depending on the task you're trying to accomplish.
For what it's worth, the registry/singleton method appears to be favored by the core developers for both the admin console application (the Mage_Adminhtml module) and the Magento Mobile controllers (the Mage_XmlConnect module), while the more paranoid "load the model explicitly" pattern is used more in the front-end cart application. Whether this is a coincidence, a conscious choice, or just the core team itself learning as it went along is probably one of those unanswerable questions.
Finally, one technique that's not used much by the core code, but that I sort of like when I'm looking for more of a "dumb view" behavior, is this. After you load the layout, but before you render it, you're able to access specific blocks by name. This, combined with the power of magic getters and setters, lets you do something like this
$this->loadLayout();
$content = $this->getLayout()->getBlock('content');
if($content)
{
$content->setSomeValue('Hello World');
}
$this->renderLayout();
and then from your block's template, you can grab the variable with
echo $this->getSomeValue();
You're still tying your block's implementation to your controller action but doing so in a less global-ish way. Also, the decoupled layout may not contain the block you're looking for, so you'll want to be careful with this.

Simple way to pass object properties to next

This might be a bit hard to comprehend so I apologize in advance if this is not clear enough.
I'm writing my own MVC framework and am once again stuck.
I am in the process of writing the controller classes for the framework. Basically this is how it works:
Instantiate class coreController which extends abstract class
coreController sets controller to be loaded by interpreting query string
query string values stored in variables
other variables assigned values
new controller is loaded
new controller checks if an action object needs to be instantiated.
new actioncontroller is loaded
action controller checks if it is the final object required.
action controller is returned as an object to be referenced during the rest of the script.
generic $controller->method() can be called and references final controller loaded.
Another overview:
coreController
pageController
pageControllerActionAdd
return as object to start
$controller->something(); //References pageControllerActionAdd
Esentially what I want to be able to do is be able enter a url like:
http://www.mywebsite.com/page/modify/
and have the script pull up the PageModifyController as a variable so I can execute it's methods.
If you can tell me a better method for what I am doing please go ahead. You don't have to write any code, just the idea would be great. It is just that the way I am currently doing is very confusing and hard to debug. I will end up with multiple nested objects and I don't like the concept of that.
I've been reading a lot of other source code and found that it too can be quite sophisticated.
I actually created a framework that works along the lines you are trying to implement. I think what you are missing is a RoutingHandler class. Routing is the physical manipulation of the URL, which tells your application which Controller to load, and which Action to run.
In my world I also have Modules, so the basic routing scheme is
Module -> Controller -> Action
These three items map to my URI scheme in that fashion. Variables can be appended also like so...
http://www.domain.com/module/controller/action/var1/val1/var2/val2
So, what happens after the URI is parsed, and control is passed over to the appropriate controller and action? Let's make some code up to demonstrate a simple example...
<?php
class indexController extends Controller {
protected function Initialize() {
$this->objHomeModel = new HomeModel;
$this->objHeader = new Header();
$this->objFooter = new Footer();
$this->objHeader
->SetPageId('home');
}
public function indexAction() {
$this->objHeader->SetPageTitle('This is my page title.');
}
// other actions and/or helper methods...
}
?>
In the Initialize method, I'm setting some controller-wide stuff, and grabbing an instance of my Model to use later. The real meat is in the indexAction method. This is where you would set up stuff to use in your View. For example...
public function randomAction() {
$this->_CONTROL->Append($intSomeVar, 42);
}
_CONTROL is an array of values that I manipulate and pass onto the View. The Controller class knows how to find the right template for the View because it is named after the Action (and in a sibling directory).
The Controller parent class takes the name of the action method and parses it like so...
indexAction -> index.tpl.php
You can also do some other fun stuff here, for example...
Application::SetNoRender();
...would tell the Controller not to render inside a template, but just complete the method. This is useful for those situations where you don't actually want to output anything.
Lastly, all of the controllers, models, and views live inside their own Module directory like so...
my_module
controllers
indexController.class.php
someotherController.class.php
:
:
models
HomeModel.class.php
:
:
templates
index.tpl.php
someother.tpl.php
:
:
I can have as many Modules as I need, which means I can separate functionality out by Module and/or Controller.
I could go on, but I'm writing this from memory, and there are some wrinkles here and there, but hopefully this gives you food for thought.

PHP - MVC - Better way to prevent calling unwanted method in Controller?

I don't know if my question should be asked here or not. Please let me know or move it/delete it if that is the case.
Question:
For the sake of learning, I'm making my own little MVC "library" for a very small personal website. Here is the process being used (please correct me if I'm not even close to doing it the right way):
All requests (except images, etc.) get sent through index.php (boostrap file).
The bootstrap file parses the request and extracts the controller and action, ex:
http://www.Domain.com/Controller/Action/Argument1/Argument2/...
Where Controller is the controller, action is the method called on the controller. In this case, it would end up being: Controller->Action ( Argument1, Argument2 );
Now, what if a user visits:
http://www.Domain.com/Controller/__destruct
or
http://www.Domain.com/Controller/__get/password
Current solution(s):
Run the request through a $config->getURIFilter () method or something
Do a check for method_exists () and is_callable (), etc.
Don't have any methods in the controller that aren't for handling a request
It just seems like this shouldn't be an issue to begin with and my design is wrong.
P.S. I've already looked at plenty of good MVC PHP frameworks (CodeIgniter, Yii, CakePHP, Zend, Swiftlet, etc.)
Either make your Controllers only handle specific actions, e.g.
/controllers
/user
loginController.php
logoutController.php
and then have classes that only do that one thing
class LoginController implements RequestHandler
{
public function handleRequest(Request $request, Response $response)
{
…
}
private function someAuxiliaryMethod() {
…
so that example.com/user/login will create a new LoginController and call the interface method handleRequest. This is a Strategy Pattern.
Or - if you dont want to split your controller actions like this - suffix your actions with a word (Zend Framework does this):
class UserController
{
public function loginAction() {
…
and in your bootstrap you add the suffix and invoke those methods then.
Yet another option would be to introduce a Router that can map URLs to Controller Actions and which can optionally do sanitizing to filter out malformed URLs, e.g. strip underscores.
I suggest that you treat any method preceded with an underscore as a private method (just show a not found page when you do your method_exists check if it starts with an underscore).
Just mark methods that you don't want to expose to your controller as private or protected.
Keep it simple!
I rolled my own MVC structure once for the same reason you're doing it, and the way I solved this was simply making an architectural decision to prefix all routable controller actions with the word "action", ie:
public function actionGetData(){
}
public function actionUpdateSomething() {
}
etc.
The reasons I did this:
You maintain full control over public/protected/private scope in your class without worrying about whether a method is inadvertently exposed via some URL.
It makes the routable actions obvious to the programmer. public function actionDoSomething() is clearly accessible via a public URL (/controllerName/doSomething), whereas public function getErDone() is not.
You're not forced to jump through hoops to identify routable actions. Simply prefix your incoming parameter with "action" and see if the method exists. Fast and simple.
I found this worked really well.

MVC - Can I call more than one (or multiple) controller in class controller?

For projects written in php, can I call more than one (or multiple) controller in class controller? Example in http://img192.imageshack.us/img192/7538/mvc03.gif
ASK: I need to call an action from another controller... And if I do like the picture above, I'm being out-ethics?
Thanks,
Vinicius.
I'm sure that you can do what you want with whichever framework you're using. If you can't do it natively for whatever reason, then you can extend your framework as required.
Having said that, I personally don't like the idea of a controller calling another controller. It seems to somewhat break the MVC paradigm if only from a theoretical standpoint. What I might do instead is build a library class that contains the functionality required and then have both controllers instantiate that class as a member and call the functions required.
For example, using CodeIgniter:
libraries/MyLib.php:
class MyLib
{
public function MyFunc()
{ /* do whatever */ }
}
controllers/ControllerA.php:
class ControllerA extends Controller
{
public function index()
{
$this->load->library('MyLib');
$this->mylib->MyFunc();
}
}
controllers/ControllerB:
class ControllerB extends Controller
{
public function index()
{
$this->load->library('MyLib');
$this->mylib->MyFunc();
}
}
out-ethics? Anywhose... back to reality.
Yes, a controller can call another controller's action. For instance, in cakePHP, this functionality is afforded via requestAction
// pass uri to request action and receive vars back
$ot3 = $this->requestAction('/stories/xenu');
If you're rolling your own, the details of how to implement it will be very specific to your framework.
then you need to modify framework, find place where controller is lounched and add there your second controller.
what framework you are using?
You can do it any way that you want. You don't have to use MVC if you don't want to. However, in MVC you really should only have one controller active at a time. You probably want multiple Views or Models, not another Controller. There is nothing at all wrong in loading, say, a header and footer view for the menu and footer of the site.
If you are building another Controller, then feel that you need to access the functionality of a previous Controller to access its functionality (because it works with a specific / desired Model), then the Model you developed for the latter probably needs to be refactored. IN plain speak, your target Model may be doing too much. Break it up.
You are trying to avoid repeating yourself (DRY) by using calling the methods of a Controller that has already been developed, but in doing so your are creating TIGHT coupling between both controllers! If something changes in the borrowed controller, it will have an effect on the borrowing controller. Not good, Dr. Jones.

Categories