SitePoint Sponsor |
|
User Tag List
Results 1 to 25 of 72
Thread: [MVC] Implementation issues
Hybrid View
-
May 16, 2005, 16:05 #1
- Join Date
- Mar 2004
- Location
- Sweden
- Posts
- 180
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
[MVC] Implementation issues
Hi!
I have a few fundamental questions regarding...pretty much everything that has to do with developing a successfull MVC application. Ok, lets start with probably the easiest one concerning Front Controller and Page Controller.
If you implement the Front Controller, you let your index.php be your single entrypoint to the application, and conversely, with the Page Controller you implement a Controller for every page, thus you'll get multiple entrypoints.
However, with the Page Controller you don't have to be concered with the Action Mapping since it will be hardcoded(?) in the controllers. This is not the case with the Front Controller, and my question is, how should I implement the Action Mapping for the Front Controller? (I may have misunderstood the whole concept of Action Mapping, but isn't its purpose to restrict the FrontController from executing an arbitrary action by altering the appropriate $_GET variable manually? For example, if the client is at your index page, he shouldn't be able to execute the...say ListMostActiveUsersOnTheForum Action just by manipulating the URL from index.php to index.php?action=ListMostActiveUsersOnTheForum), right?)
How can a ActionChain / Composite View pattern help me when I want to generate a complex page? I'm mainly interested in code examples regarding this issue.
What is the best way to transfer data from the Model to the View? Or should I let the View fetch the data it needs?
Hmm, that's about it...for now
-
May 16, 2005, 18:11 #2
- Join Date
- Sep 2003
- Location
- Glasgow
- Posts
- 1,690
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by Overunner
How can a ActionChain / Composite View pattern help me when I want to generate a complex page? I'm mainly interested in code examples regarding this issue.
I'm not so sure about composite views: I don't like lots of template fragments, preferring a single template file per page.
What is the best way to transfer data from the Model to the View? Or should I let the View fetch the data it needs?
-
May 16, 2005, 20:46 #3
For the framework I've been developing, I went with what seemed to me to be the simplest system. I have a front controller that calls a second, domain specific controller; sort of like a command object, except that all the commands for a given domain are part of the same class. Therefore, a request such as index.php?section=news&action=view calls the exec_view function of the news_controller. The authentication system takes care of security issues i.e. a user can't arbitrarily execute a command he doesn't have the right to execute.
For transfering data to the view, I simply have the controller pass the model to the view object for the latter to do it's thing. Getting the controller more involved means you risk having to modify the controller when you change views, which I wanted to avoid.
As for the ActionChain / Composite View issue, that's something I'm still working out for myself; most of the work I've done to date on my framework has been in the orm department
-
May 17, 2005, 00:37 #4
- Join Date
- Aug 2004
- Location
- California
- Posts
- 1,672
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
how should I implement the Action Mapping for the Front Controller?
What is the best way to transfer data from the Model to the View? Or should I let the View fetch the data it needs?Christopher
-
May 17, 2005, 03:51 #5
- Join Date
- Mar 2004
- Location
- Sweden
- Posts
- 180
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
I have a front controller that calls a second, domain specific controller; sort of like a command object, except that all the commands for a given domain are part of the same class. Therefore, a request such as index.php?section=news&action=view calls the exec_view function of the news_controller.
Is your authentication system implemented as Intercepting filters or...something else?I remember I had some difficulties implementing it as filters, so I put it in the Page Controllers. However, it would be interesting to hear your solution.
Either translate a parameter value into file/class names, or lookup the file/class names in a map using the parameter value.
Now regarding chaining actions, I think the main problem is that there is a 1 -> many relation between a user request and the number of actions which get executed. For example, say the user has requests your Top10 page, index.php?action=top10 (1 request), where the most active users, most popular downloads etc. (many actions) will be displayed. The problem is, how do I know when several actions gonna be executed (and how do I store the result of each action, since each action will return a template fragment)? I've thought about two ways to tackle this.
You could for instance have 1 large Action e.g ListMostActiveUsersAndMostPopularDownloadsAndKitchenSinks which executes several actions. But as you can see, it will probably result in an explosion of classes.
The other approach requires to have a Front Controller + Page Controllers / Application Controllers. E.g the user requests the Top10 page, index.php?section=top10&action=. The Front Controller creates a Top10 PageController (or App. Controller, not sure about the difference) which in turn examines the action the user has request and uses conditional logic to build the action chain. E.g
PHP Code:class Top10Controller extends PageController
{
function execute()
{
$this->actionChain = new ActionChain();
switch (Request :: getAction())
{
default:
case 'ListTop10s':
$this->actionChain->register('ListMostActiveUsers');
$this->actionChain->register('ListMostPopularDownloads');
// ...
break;
case 'blabla':
}
return $this->actionChain->execute();
}
}
This is where the Composite View patterns comes in I guess? This method involves building a tree structure of components (actions) and then let a Visitor visit each node and execute it and display its content...somehow. Anyhow I'm very interested in how others have solved this problem.
Cheers.
-
May 17, 2005, 18:46 #6
Originally Posted by Overunner
Originally Posted by Overunner
- Front controller receives request
- Front controller looks up an entry in a data source of some kind to find the actions for that request, along with a template to use for the final layout.
- The controller executes each action by calling the sub controllers in turn, and the output for each view is placed in the template.
- The completed template is then returned to the browser.
I plan on adding a default mapping system featuring wildcards to obviate the need for creating entries in the mapping for each possible request that is recieved. As for the mapping data, I'm currently planning on storing it in a database and then caching it for quick access (maybe as a serialized array?).
Ultimately, I see my framework as having two layers; a lower layer of MVC triads for each type of data, and an upper layer consisting of the Front Controller, the mapping data, and the Composite Views. I realise that this may be exactly how a lot of existing MVC frameworks work, I haven't done a whole lot of research into existing solutions...
Originally Posted by arborint
-
May 18, 2005, 04:09 #7
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by 33degrees
I'm not really sure if that was what you meant, but nevertheless.
Originally Posted by aborint
-
May 17, 2005, 05:19 #8
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by McGruff
Originally Posted by arborint
If the Controller pass the Model to the View, the Controller becomes dependent on the View. On the other hand, if the View access the Model directly, the seperation between Controller and View is blurred out - in the extreme you have DispatcherView, witch is really a combination of Controller and View. Still - there is no problem in combining FrontController with DispatcherView, witch I have found a good compromise in the world of PHP.
I think a source of confusion surrounding MVC is that FrontController and PageController doesn't really have anything directly to do with the MVC pattern - they are related, but they are dealing with different problemdomains. PageController and FrontController is about mapping request to controller, while MVC is about seperating Model, View and Controller. Keep that in mind.
-
May 17, 2005, 13:39 #9
- Join Date
- Aug 2004
- Location
- California
- Posts
- 1,672
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
I'm a little confused about what you "respectfully disagree" about.
I'm also unclear about what "fetch the data" means in this context. I think of the Model or some layer below as fetching the data. I'm not sure I've ever heard "the View fetched the data from the Model" used before, but maybe that is what was meant.Christopher
-
May 17, 2005, 14:14 #10
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by arborint
If a DispatcherView uses a ViewHelper to access the Model, you have the View as the active part of the action, in contrast to having a Controller assign values to the View (template-style).
-
May 17, 2005, 14:51 #11
- Join Date
- Aug 2004
- Location
- California
- Posts
- 1,672
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
OK, sorry. I meant that if you break View/Model separation it's not MVC. I think the Model should fetch the data and the View shoud access the data through the Model.
I agree that View/Controller separation is secondary. I think the general goal of MVC is that the View is not dependent on the Controller, but often it makes sense to have some dependecy the other way.Christopher
-
May 18, 2005, 06:10 #12
- Join Date
- May 2003
- Location
- Berlin, Germany
- Posts
- 1,829
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
I go the way of storing all the actions/commands my front controller need to handle in a CommanDicitionary object, which then gets passed to the FrontController. Passing the Request to the FrontController it can then execute the appropriate action/command. If the requested command is not registered with the FC, I display an error page.
Now there are a few implementation issues with this: Doing it the dictionary way one can build other dictionaries at run time, which can be send via a network. Often, with our websites this is not needed, but I have a Java background.
Also whether you simply register the name of the action/command with the FC and then let it load the required class or whether you store the Action object in the dictionary is pretty much down to taste isn't it? The latter of course may result in performance issues once the dictionary gets really big or whether some of the loaded classes are really big. In most projects however, this should not play any role.
Well, what are the advantages of this? It's closer to the problem...i.e. the FC reading from a book and translating the Request into a Response/Execution of a Command. Secondly, everything is well encapsulated from each other. The FC need not know whether an Action is a simple one or a Composite one with many sub actions.
About the MVC implementation: Actually I firstly did it the way what McGruff stands for: The model needs to pull the data from the sources it needs, i.e. data fetching is done by the model. The Views simply access the model. That means, that the dispatcher/controller selects a view and passes the model to it. The View tells the model to fetch some data it will need and then access that data via the model.
But then again if you have a PDFView and a HtmlView of some text that is stored in the database, both Views would include the same code for telling the model to fetch the data. Isn't that Code Repition?
My 2 bits.
-
May 18, 2005, 06:51 #13
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by DarkAngelBGE
-
May 18, 2005, 09:53 #14
Originally Posted by DarkAngelBGE
-
May 18, 2005, 10:17 #15
- Join Date
- Oct 2004
- Posts
- 88
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Why not let the view figure out which model(s) it needs?
The controller loads the demanded view.
|
The view fetchs data from the model(s).
|
The view assign template variables.
|
The controller loads the related template and output its result content
-
May 18, 2005, 11:42 #16
Originally Posted by atu
-
May 18, 2005, 12:30 #17
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by atu
Allow me to recap on TemplateView vs. DispatcherView:
If you use DispatherView, the View pulls from the Model, whereas in the TemplateView the Model is pushed to the View (~Template) by the Controller (~Action). You may then argue that the DispatcherView isn't "true" MVC, since Controller and View are intertwined. This can be dealt with by introducing the ViewHelper witch is an object that pulls data from the Model. This gives you back your VC seperation, since the controller-logic is now in the ViewHelper.
So in the DispatcherView you have the Controller represented by the ViewHelper, while in the TemplateView you have the Controller represented by the Action.
The benefit of using DispatherView over TemplateView is that if you have little to nothing going on in the Controller, you don't need to create an object. In the TemplateView, you will allways have an Action - even if it does nothing but echo out the View. The dark side of the DispatcherView is that it requires discipline since you could easily be tempted to stuff controller-logic into your View.
-
May 18, 2005, 11:23 #18
- Join Date
- May 2003
- Location
- Berlin, Germany
- Posts
- 1,829
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by 33degrees
As McGruff said, we had a discussion about this a few days or weeks ago and didn't come to a result. It probably doesn't matter much, since both way have disadvantages. The code repetition problem can easily be adressed with a ViewHelper.
Then again I come to believe that MVC really means only separation of concerns. How everybody implements this separation is not part of MVC.
Just for the purpose of receiving comments, here is my current set up:
-FC is created and populated with a CommandDictionary that holds the Command objects and a string to identify them.
-The Request is passed to the FC...the $_GET['command'] variable is matched to the CmdDictionary. If there is a command for it, execute it (or if it is a Composite Command, execute all childs as well), else execute a default Command.
-Each Command holds a controller, which is executed when the command is executed. Based on the Request, the Controller loads a View and populates it with a model, which belongs to the controller.
- The View accesses the model and tells it to fetch some data. The View accesses this data and loads a template based on criteria that is met by the data (i.e. there me a default error template and then some).
-The FC fetches the output from the command, which fetches it from its controller, which fetches it from the view it loaded.
So the last line of all my applications is always:
PHP Code:$fc->getCommand()->getController()->getView()->display();
-
May 18, 2005, 11:48 #19
Originally Posted by DarkAngelBGE
-
May 18, 2005, 14:45 #20
- Join Date
- Sep 2003
- Location
- Glasgow
- Posts
- 1,690
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
I'm getting a bit behind in this topic but...
Originally Posted by DarkAngelBGE
-
May 18, 2005, 06:58 #21
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
As for the FC, I have a ChainOfResposibility with one or more RequestMappers. Each RequestMapper may have different strategies for selecting a proper Controller, or it may fall thru to the next in the chain, if it doesn't find a match.
PHP Code:$ac =& InputController::getInstance();
$ac->addMapper(new RequestMapper_ActionRequestMapper()); // maps to a command (action)
$ac->addMapper(new RequestMapper_ServerPageRequestMapper()); // maps directly to a serverpage (dispatcherview)
$ac->addMapper(new RequestMapper_DefaultRequestMapper('404.php')); // default sink
$ac->execute();
-
May 18, 2005, 11:34 #22
- Join Date
- May 2003
- Location
- Berlin, Germany
- Posts
- 1,829
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Here is some code example of mine..the way I deal with controller and views.
Here the controller:
PHP Code:<?php
/**
* Author: Tim Koschützki
* Date Started: April 7th, 2005
*/
require_once('IssueController.php');
class DeleteIssueController extends IssueController {
public function DeleteIssueController (&$dao,$input=null) {
parent::__construct($dao, $input);
}
public function execute() {
if(!isset($this->input['submit'])) {
require_once(ISSUE_VIEW_PATH.'DeleteIssueItemView.php');
$this->setView(new DeleteIssueItemView($this->model, $this->lang, $this->input['id']));
} else {
$success=0;
if($this->model->deleteIssue($this->input['id'])) {
$success=1;
}
require_once(ISSUE_VIEW_PATH.'IssueItemHasBeenDeletedView.php');
$this->setView(new IssueItemHasBeenDeletedView($this->model, $this->lang, $success));
}
}
}
?>
PHP Code:<?php
/**
* Author: Tim Koschützki
* Date Started: April 7th, 2005
*/
require_once(LIB_PATH.'Template.php');
require_once('IssueView.php');
class IssueHasBeenDeletedView extends IssueView {
/**
* Private
* $success Did the deleting work alright?
*/
private $success;
//! A constructor.
/**
* Constucts a new IssueItemView object
* @param $model an instance of the IssueModel class
*/
public function __construct(&$model,&$lang,$success) {
parent::__construct(&$model,&$lang);
$this->success=$success;
}
//! A manipulator
/**
* Renders a single issue
* @return void
*/
private function deleteIssueItem() {
if($this->success)) {
$tmp =& new Template('issue_deleted');
$tmp->setValue('{TITLE}',$this->lang->$deleteSuccessHeading);
$tmp->setValue('{CONTENT}',$this->lang->$deleteSuccessText);
} else {
$tmp =& new Template('error_page');
$tmp->setValue('{TITLE}',$this->lang->$deleteFailureHeading);
$tmp->setValue('{CONTENT}',$this->lang->$deleteFailureText);
}
$tmp->parse();
return $tmp->fetch();
}
public function display () {
$tmp =& new Template('standard');
$tmp->setValue('{HEADER}',$this->header());
$tmp->setValue('{FOOTER}',$this->footer());
$tmp->setValue('{NAVBAR}',$this->navbar());
$tmp->setValue('{CONTENT}',$this->deleteIssueItem());
$tmp->parse();
return $tmp->fetch();
}
}
?>PHP Code:<?php
/**
* Author: Tim Koschützki
* Date Started: April 7th, 2005
*/
require_once(LIB_PATH.'Template.php');
require_once('IssueView.php');
class DeleteIssueView extends IssueView {
/**
* Private
* $issueID ID of issue to delete
*/
private $issueID;
//! A constructor.
/**
* Constucts a new IssueItemView object
* @param $model an instance of the IssueModel class
*/
public function __construct(&$model,&$lang,$issueID) {
parent::__construct(&$model,&$lang);
$this->issueID=$issueID;
}
//! A manipulator
/**
* Renders a single issue
* @return void
*/
pruvate function deleteIssueItem() {
$this->model->listIssue($this->issueID);
$issue=$this->model->getIssue();
$tmp =& new Template('delete_issue_confirm');
$tmp->setValue('{ISSUE_ID}',$this->issueID);
$tmp->setValue('{ISSUE_TITLE}' => $issue['title']);
$tmp->parse();
return $issueTemplate->fetch();
}
public function display () {
$tmp =& new Template('standard');
$tmp->setValue('{HEADER}',$this->header());
$tmp->setValue('{FOOTER}',$this->footer());
$tmp->setValue('{NAVBAR}',$this->navbar());
$tmp->setValue('{CONTENT}',$this->deleteIssueItem());
$tmp->parse();
return $tmp->fetch();
}
}
?>
-
May 18, 2005, 11:34 #23
- Join Date
- Jan 2003
- Posts
- 5,748
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Umm...
Not being negative, but I can remember in one thread, that you posted your Dictionary, and one or more members referred to it as being bloated?
I don't use a Dictionary, so I didn't comment at the time as I wasn't in the position to do so, but I'd be interested in knowing if you've refactored it since then?
-
May 18, 2005, 11:41 #24
- Join Date
- May 2003
- Location
- Berlin, Germany
- Posts
- 1,829
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Well, didn't follow that thread anymore for some unknown reason. However, I didn't refactor it yet. Probably the comments were due to me storing the actual objects in it already, instead of storing only some sort of identifying strings and leave it to the FC to require the necessary class and instantiate it.
Well I might refactor it.Here it is again, for others to comment as well:
PHP Code:<?php
/**
* Author: Tim Koschützki
* Date Started: April 14th, 2005
*/
class CommandDictionary {
private $commands;
public function isListed($name) {
foreach($this->commands as $command) {
if($name == $command->getName()) {
return true;
}
}
return false;
}
public function addCommand(&$command) {
$this->commands[] = &$command;
}
public function getCommand($name) {
foreach($this->commands as $command) {
if($name == $command->getName()) {
return $command;
}
}
return null;
}
}
?>
PHP Code:// parse input data
require_once(LIB_PATH.'class.Request.php');
$request =& new Request();
$INPUT = $request->parse();
// get a Command Dictionary
require_once(LIB_PATH.'class.CommandDictionary.php');
$cmdDic =& new CommandDictionary;
// declare our commands and insert them into our command directory
require_once(LIB_PATH.'class.Command.php');
// start a front controller
require_once(LIB_PATH.'class.FrontController.php');
$fc=& new FrontController($INPUT['action']);
// Populate our Command Dictionary based on whether we are viewing the admin panel or not
if( isset($INPUT['admin']) && $INPUT['admin'] == 1 ) {
$cmd =& new Command('insert_issue');
require_once(ISSUE_CONTROLLER_PATH.'class.InsertIssueController.php');
$cmd->addController(new InsertIssueController($dao, $INPUT));
$cmdDic->addCommand($cmd);
$cmd =& new Command('edit_issue');
require_once(ISSUE_CONTROLLER_PATH.'class.EditIssueController.php');
$cmd->addController(new EditIssueController($dao, $INPUT));
$cmdDic->addCommand($cmd);
$cmd =& new Command('delete_issue');
require_once(ISSUE_CONTROLLER_PATH.'class.DeleteIssueController.php');
$cmd->addController(new DeleteIssueController($dao, $INPUT));
$cmdDic->addCommand($cmd);
// default page is insert issue page
if(!isset($INPUT['action'])) {
$INPUT['action'] = 'insert_issue';
}
// add the user authentication filter to the FC
require_once(FILTER_PATH.'class.UserAuthenticationFilter.php');
$fc->addFilter(new UserAuthenticationFilter());
} else { // Public Interface
$cmd =& new Command('view_issue');
require_once(ISSUE_CONTROLLER_PATH.'class.ViewIssueController.php');
$cmd->addController(new ViewIssueController($dao, $INPUT));
$cmdDic->addCommand($cmd);
$cmd =& new Command('view_archive');
require_once(ISSUE_CONTROLLER_PATH.'class.ViewArchiveController.php');
$cmd->addController(new ViewArchiveController($dao, $INPUT));
$cmdDic->addCommand($cmd);
// default page is view archive page
if(!isset($INPUT['action'])) {
$INPUT['action'] = 'view_archive';
}
}
// pass our command directory to the fc
$fc->addCmdDic($cmdDic);
$fc->run();
$output=$fc->fetchOutput();
-
May 18, 2005, 20:18 #25
Originally Posted by DarkAngelBGE
In the controller
PHP Code:<?php
/**
* Author: Tim Koschützki
* Date Started: April 7th, 2005
*/
require_once('IssueController.php');
class DeleteIssueController extends IssueController {
public function DeleteIssueController (&$dao,$input=null) {
parent::__construct($dao, $input);
}
public function execute() {
if(!isset($this->input['submit'])) {
require_once(ISSUE_VIEW_PATH.'DeleteIssueItemView.php');
$this->model->listIssue($this->input['id']);
$this->setView(new DeleteIssueItemView($this->model, $this->lang));
} else {
$success=0;
if($this->model->deleteIssue($this->input['id'])) {
$success=1;
}
require_once(ISSUE_VIEW_PATH.'IssueItemHasBeenDeletedView.php');
$this->setView(new IssueItemHasBeenDeletedView($this->model, $this->lang, $success));
}
}
}
?>
PHP Code:<?php
/**
* Author: Tim Koschützki
* Date Started: April 7th, 2005
*/
require_once(LIB_PATH.'Template.php');
require_once('IssueView.php');
class DeleteIssueView extends IssueView {
//! A constructor.
/**
* Constucts a new IssueItemView object
* @param $model an instance of the IssueModel class
*/
public function __construct(&$model,&$lang) {
parent::__construct(&$model,&$lang);
}
//! A manipulator
/**
* Renders a single issue
* @return void
*/
private function deleteIssueItem() {
$tmp =& new Template('delete_issue_confirm');
$tmp->setValue('{ISSUE_ID}',$this->model->ID);
$tmp->setValue('{ISSUE_TITLE}' => $issue['title']);
$tmp->parse();
return $issueTemplate->fetch();
}
public function display () {
$tmp =& new Template('standard');
$tmp->setValue('{HEADER}',$this->header());
$tmp->setValue('{FOOTER}',$this->footer());
$tmp->setValue('{NAVBAR}',$this->navbar());
$tmp->setValue('{CONTENT}',$this->deleteIssueItem());
$tmp->parse();
return $tmp->fetch();
}
}
?>
Also, since the model already contains the data the ID of the item to be deleted, there's no longer any need for the controller to pass it seperately and for the view to store it.
Other than that, it looks good to me!
Bookmarks