What solutions,patterns usually used for this?
I want to get rid of if/else statements in my controllers, models and so on.
For example:
if($domain==1) {
// do this
}
elseif($domain==2) {
// do this
}
elseif...
Want to get rid of this madness. Can't imagine what mess will be, when there will be at least 20 websites.
Currently i'm using config and routing files for each domain. But that's not enough.
Can't get rid of this mess inside models and controllers.
I was thinking about some kind of placeholders and separate static class for each domain with method for those placeholders + magic calls.
For example i have action inside controller:
public function postAction(){
$model=new Model();
$this->view->data=$model->get($placeholder_generates_and_return_settings_array); // else default is used
// custom placeholder
// execute custom class method if it's exist
// some model again
// custom placeholder
// execute custom class method if it's exist
// etc
}
Current view is provided inside placeholders Class, types can be assigned. Like data modification, config generation for model etc.
How would you resolve this issue with multiple domains, without cloning controllers, models or creating innumerous if/elseif statements for each domain inside them?
UPDATE
How to describe what i need. I'm trying to create reusable controllers with default logic in it. Just filling/MIXING controller with domain related logic in required places(placeholders), data modification etc. Something like controller-template possible, any patterns exist?
Providing placeholder with all required(CURRENT) data for it's modification if required or further processing AND returning it back.
Guess i'll have to create my own "bicycle". :D
Based on the information you provide I assume that you wish to display your data differently based on the domain. Also assuming that your data remains unchanged you could use a strategy pattern to solve your problem.
Your class structure would then look as follows:
class yourClass
{
protected $_strategy;
public function setStrategy($strategy)
{
$this->_strategy = $strategy;
}
public function showYourData()
{
return $this->_strategy->show($this)
}
}
For each domain you build a separate strategyclass as follows:
class domainStrategy
{
public function show(yourClass $yourClass)
{
// Get your classdata here
$data = $yourClass->whateverFunctionYouNeed();
// Do what you want for this domain
return $output;
}
}
I hope this gets you started, I'm sure you can find more documentation for the strategypattern when you need it
I suggest to create a dispatcher that loads information based on domain criteria.
Something like:
dispatch.php
<?php
...
$domain = get_domain_function(); // here you may automate the domain retrieval
include ('controllers/' . $domain . '.php')
...
?>
controllers/domain1.php ... controllers/domainn.php
<?php
...
do the domain specific business logic here
...
?>
Have a folder for each domain.
When the user accesses the web site (maybe the contactus.php page), this page will check if there is a corrisponding contactus.php file in the domain folder. If there is, it will include that file, otherwise, it will do it's default behaviour.
Want to get rid of this madness. Can't imagine what mess will be, when there will be at least 20 websites.
Why on earth would you put 20 websites in 1 project???
Just use separate vhosts if you are on apache.
http://httpd.apache.org/docs/2.0/vhosts/examples.html
Other webservices also have this functionality (although they might have a different name).
If you want to prevent you need to copy common/shared functionality in you projects.
Just setup some form of a library and with the common functionality which you use in your different projects.
If you look into the MVC pattern http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller you could even have some default views in your library for the projects to use (if needed these can be overridden of course).
if($domain==1) {
// do this
}
elseif($domain==2) {
// do this
}
elseif...
I'm curious what "do this" for important thing is that you need do to it for all sites. It's not possible to specifically answer the question without knowing that.
I can assume you would like to set some variables, for example set up a session, perhaps create some other (database) object with different parameters, or set some variables in a template.
You could do that using some kind of controller, but again, it depends on what you want to achieve.
class Controller
{
public abstract function DoAction1();
// Group shared functionality, call it with different parameters
public function ShowHomePage($view)
{
$template->assign('view', $view);
}
}
class Domain1Controller extends Controller
{
public function DoAction1()
{
// do this
}
}
class Domain2Controller extends Controller
{
public function DoAction1()
{
// do this
}
}
And in your calling code (the router) you simply call Controller->ShowHomePage($view) or Controller->DoAction1() or whatever, but only after you determined on what domain you are and what controller you want to address.
Related
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.
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.
In my current implementation of the MVC design pattern (demonstrated using PHP and CodeIgniter):
Let's say I have a "page" located at "www.mysite.com/blue/page" that is ultimately generated by the following (greatly simplified) files:
/libraries
session_lib.php
/controllers
/red
/white
/blue
page.php
/models
/red
/white
/blue
funky_class.php
page_model.php
/views
/red
/white
/blue
page.php
And here's an easy to understand controller:
// FILE: /controllers/blue/page.php
// Get some paramaters from URL
$params = $this->input->get();
// Use a library to do some stuff with these params with a session
$this->load->library('session_lib');
$this->session_lib->store_in_session($params);
// Use a very page specific class to do some funky stuff with the params
$this->load->model('blue/funky_class');
$funkified_params = $this->funky_class->funkify($params);
// Pass params to a model
$this->load->model('blue/page_model');
$data['output_from_db'] = $this->page_model->get_some_output_from_db($funkified_params);
// Send to view
$this->load->view('blue/page', $data);
And now the question...
What is the best procedure for these "funky" little page specific classes that don't interact with the database? In this example I store the little class with the models, and in some cases might just add additional methods inside the model class to do the funky stuff. Is this good practice? Or should the library and funky class both go inside the model so the controller is even skinnier (however the model might be used in other places without the need for sessions and funkiness)?
I would use a helper.
When a helper is loaded, that function will be available in the global scope so you can call it anywhere.
They're great for small, stand-alone functions that do only one thing that is not necessarily related to code in a model or library
Helpers especially excel for me when converting things between one format or another (slugify, convert_timezone, change_query_string) or doing little tasks that you don't want to think about (force_ssl, is_image, uri_string)
funkify() seems like an appropriate helper function that may prevent a lot of repeated code.
Here's the codeigniter documentation page on them: http://ellislab.com/codeigniter/user-guide/general/helpers.html
If you're in a situation where the helper function is so specific it will be only used in one place, you can still use a helper. Name the helper after your controller, page_helper.php for example.
page_helper.php
function funkify($params) {
// do funky stuff
return $funky_params;
}
then in your controller:
class Page extends CI_Controller {
public function __construct() {
parent::__construct();
$this->load->helper('page');
}
public function page() {
// do stuff
$funky_params = funkify($params);
// do more stuff
$this->load->view('blue/page', $data);
}
I have no excuse for it, but sometimes if I am in a situation where I need a razor specific function that will only be used on one location (say, a controller) ever, I will put it right in the controller's file. You can paste a function outside of the class definition and it will act like a helper and be available globally (as long as that controller is loaded). You can also define functions inside of a view. Yucky, but possible. I don't like to do it often because it's unusual and not expected (by myself or other developers)
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
Here is a quick overview of the controllers functionality in most of the application:
controller loads a specific model, gets data from it, formats the data and passes the formatted data to the view.
Now there is a search page, which needs to do a search query over entire database (all models). It needs to show each type of data in its particular formatted output on a single page as a list.
The problem:
The search controller can do the search, dynamically load model for each record type, and get the data from model. Problem comes when the data needs to be formatted. I am trying to load the specific controller from the search controller, which is causing problems.
What to do?
PS: I tried using the 'Wick' library, but it fails when the controller's format function tries to use its own model and session object, giving errors about call to a member on a non-object.
After much refactoring and trial/error, It appears that the best way to achieve the above is this way:
Keep the format function in the base controller from which all other controllers are derived. The format options are passed to the function along with the data object as arguments.
Make a static function in each derived controller, which returns the formatting options of the data.
Inside the search controller (which is itself derived from the base controller), for each data object, call the static function of its particular controller which returns the data formatting options, then use that to format the object for the view.
I guess I can say I will stick to using the model only for interaction with the database, and let everything else be done by controller. If anyone has a better solution still, I am all ears.
It sounds like you want to use the Factory design pattern
Make this a library:
class MyModelFactory {
static public function Factory($data) {
$type = key($data);
return new $type($data);
}
}
now, in your controller, you can do something like this:
$model = MyModelFactory::Factory(array($_REQUEST['model'] => $_REQUEST));
and now you have an object of whatever model was specified in $_REQUEST['model']. Be sure to take any security precautions you may need for your application to assure the user has permissions to use the model that they request
Now, since you want to be using common methods and stuff, your models should probably be based off an abstract class / interface.. so instead of
class MyModelOne extends Model {
// stuff
}
You probably want something like this, to ensure your required methods will always be available:
abstract class MyAbstractModel extends Model {
protected $search_params;
public function __construct($data = array()) {
$search_params = $data['search_params'];
}
protected function GetSearchParameters() {
return $this->search_params;
}
abstract public function GetData();
abstract public function GetColumns();
abstract public function DefineViewOptions();
}
class MyModelOne extends MyAbstractModel {
public function GetData() {
$params = array();
$params[] = $this->db->escape_str($this->GetSearchParameters());
// return whatever data you want, given the search parameter(s)
}
public function GetColumns() {
// return some columns
}
public function DefineViewOptions() {
// return some configuration options
}
}
In general you can't load another controller from within a controller in CodeIgniter (although there are mods that allow you to do something like this).
I would try creating a class for formatting your data and add it to the application/library folder. Then load, use and re-use this class throughout your various controllers.
Here is a page from the CodeIgniter documentation Creating Your Own Libraries that explains the details and conventions.
Also, if a class is overkill, creating helper functions is an even lighter approach.
The difference between libraries and helpers in CodeIgniter is that libraries are classes, helpers are just a group of php functions.
Once you have formatted your data, you can load any view from any controller, so you should still have all the re-usability you need so you DRY (don't repeat yourself)
There are a few simple approaches based on the principle of what's simpler (versus what's perfectly DRY). Here's one alternative approach I use with CodeIgniter:
Instead of trying to load multiple controllers, reuse the view fragments from your search controller (or search route, depending which you're using). This requires using the same naming conventions for your data elements so the views are interchangeable, but you should be doing this anyway.
Instead of using multiple models for search, add a single Search model that knows about the things that can be searched on. If you want to prevent duplicate SQL, reuse the SQL between models (this can be done using constants, or loading SQL from disk).
Controllers are not great candidates for reuse from your own PHP code: they route actions and requests for resources to the things themselves. They are intended to be called via HTTP, using the URI interface you've come up with. Calling them from code is a coupling you want to avoid. That said, reusing controllers from JavaScript (or via cURL) is a great, decoupled way to reuse things in any web framework.