SitePoint Sponsor |
|
User Tag List
Results 26 to 50 of 68
Thread: My First crack at MVC
-
Oct 5, 2006, 01:01 #26
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by rockit
Originally Posted by rockit
There are pros and cons to doing both things.
-
Oct 13, 2006, 06:26 #27
what i'm working on now is a basic admin area of website that handles adding product categories into a db.
i have it set up in such a way that a front controller handles the initial request and creates the appropriate page controller.
PHP Code:$dao= new DataAccess ($localhost, $user, $pass, $dbname);
switch ($_GET['page']) {
case "add":
$controller = new AddCategoryController($dao, $_GET);
break;
case "edit":
$controller = new EditCategoryController($dao, $_GET);
break;
case "delete":
$controller = new DeleteCategoryController($dao, $_GET);
break;
case "view":
$controller = new ViewCategoryController($dao, $_GET); //handles a single record view
default:
$controller = new ListCategoryController($dao, $_GET); //handles a listing of all categories
break;
}
here's an example of the AddCategoryController.
inPHP Code:$this->model= new AddCategoryModel($CategoryDao);
PHP Code:class AddCategoryController {
public function __construct ($dao,$null) {
$CategoryDao= new CategoryDao($dao);
$this->model= new AddCategoryModel($CategoryDao);
$this->view= new AddCategoryView($this->model,$getvars);
}
public function getView () {
return $this->view;
}
}
-
Oct 13, 2006, 07:01 #28
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Your front controller is fine, although a bit low-tech. You might want to generalize it a bit as your application grows. A simple solution is to use an associative array with page => classname.
You class named DataAccess looks more like a database connection than a dao ? Either way, it is clearly a model layer component. I'd create it from within the controller, rather than in the front controller.
I wouldn't pass the model to the view, as you do it in AddCategoryController - let the view create it's own model(s), if it needs any.
Originally Posted by rockit
-
Oct 13, 2006, 08:25 #29
Originally Posted by kyberfabrikken
Originally Posted by kyberfabrikken
No, there is no 1:1 relationship between model and controller (or model and view ... or controller and view)
ie.
PHP Code:class CategoryModel {
...
public function add() {
...
}
public function edit() {
CategoryDao->editCategory(...);
}
public function view() {
...
}
public function delete() {
...
}
}
i think this is it. btw, cheers on the assistance. you're a good person to have around here. thanks.
-
Oct 14, 2006, 02:58 #30
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by rockit
PHP Code:$map = Array(
'add' => 'AddCategoryController',
'edit' => 'EditCategoryController',
'delete' => 'DeleteCategoryController',
'view' => 'ViewCategoryController',
'' => 'ListCategoryController',
);
$name = $map[$_GET['page']];
$controller = new $name($_GET);
Originally Posted by rockit
Originally Posted by rockit
Originally Posted by rockit
-
Oct 14, 2006, 05:41 #31
Originally Posted by kyberfabrikken
But I'm sure some people will want to argue that, so from a practical perspective, the View knows exactly which model it needs and is perfectly capable of fetching it, so why not let it.
-
Oct 14, 2006, 10:22 #32
- Join Date
- Jan 2003
- Posts
- 5,748
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
> the View knows exactly which model it needs and is perfectly capable of fetching it, so why not let it.
But of course
There are a number of ways you can do this though, and still maintain a certain level of separation.
-
Oct 14, 2006, 16:44 #33
Originally Posted by kyberfabrikken
also, from a view perspective... when rendering a template, is it best off to have a template object go along with it to render a template based view?
-
Oct 14, 2006, 20:54 #34
- Join Date
- Jun 2003
- Location
- Melbourne, Australia
- Posts
- 440
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by kyberfabrikken
Zealotry is contingent upon 100 posts and addiction 200?
-
Oct 15, 2006, 03:49 #35
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by auricle
If the controller has the task of assigning model to the view it would have to either A) pass all information to both views, even though it's only one of them that needs it all or B) treat the views differently. Taking A to the extreme, the controller would have to pass every conceivable model component to every view. B means that your controller and view are very hard coupled - You might as well merge them into one component. This isn't nescesarily a bad idea - for simple applications a 2-tier may be sufficient. It's not MVC though.
Originally Posted by auricle
-
Oct 17, 2006, 20:26 #36
- Join Date
- Jun 2003
- Location
- Melbourne, Australia
- Posts
- 440
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by kyberfabrikken
Taking A to the extreme, the controller would have to pass every conceivable model component to every view. B means that your controller and view are very hard coupled - You might as well merge them into one component.
There's a lot of talk about coupling and dependencies in this forum which veers between orthodoxy and practicalities. We're all making specific controllers, rather than generic ones, but we forget (or ignore) that, in a sense, the controller is 'coupled' to the request and that the extra information (as you put it) is probably going to specific to that request. (Hat tip to Dr Livingston: that may not be the case in a composite structure but then different parts of that will probably have different controllers handling them.) So having a controller which isn't going to be used for anything else know choose the model isn't bad ...it may be considered practical.
Six of one, half a dozen of the other between:
1. Controller chooses View
2. View chooses Model
and
1. Controller chooses View
2. Controller chooses Model
Originally Posted by kyberfabrikken
Zealotry is contingent upon 100 posts and addiction 200?
-
Oct 18, 2006, 00:50 #37
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by auricle
Code:Client1 request text/html => Server respond text/html (view1) ... Client2 request text/xml => Server respond text/xml (view2)
Originally Posted by auricle
Originally Posted by auricle
-
Oct 18, 2006, 07:05 #38
- Join Date
- Jan 2003
- Posts
- 5,748
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
> Well, I suppose they might have been, but surely some sort of lazy loading mechanism could take
> care of that.
That is beside the point, Kyber was talking about something else, all together, which I see he has cleared it up now?
-
Oct 18, 2006, 08:49 #39
- Join Date
- Sep 2003
- Location
- Glasgow
- Posts
- 1,690
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by auricle
The technical ability of the end user often influences what you want to display. A simple success/failure type of view with a note to contact a sysadmin when something fails might be best for relatively unskilled users. Apart from a simple boolean, the view does not need to gather any data from the domain at all. At the other extreme a longer, detailed report might be better for admin-level users. In contrast to the simple report, the detailed version will probably touch most of the domain objects involved in whatever is being done. Maybe you would have to provide both for different users: multiple views for the same underlying domain manipulation.
I usually find myself facing this kind of problem if I'm writing a new app ie exactly what data do I want to pull out for display? Often I don't really know and it won't become clear until later. Suppose you were writing the first ever unit testing framework. There is a kind of standard where test runs produce a red or green bar - a simple boolean result with a brief note of the number of test cases and individual assertions. If you were exploring the territory for the first time it might take a few tries before you hit on this simple view type but none of the different views you might try out would affect the underlying domain action: "run a list of tests". Different views simply ask different objects for different kinds of data, and maybe add some presentation logic to interpret the data into a meaningful display. Separation between controller and view means that a controller - which orchestrates the domain manipulation - does not need to keep pace with changes to the view. It can be written, tested and put to one side while you play around with different display ideas.
Another example: at the moment I'm writing a refactoring tool: what should I display for a "rename class" refactoring? This could be a simple "name changed successfully", a list of all edited files (the file containing the class and all files which include or instantiate it) or at the extreme end of the scale a bunch of diffs showing everything that happened in minute detail. Or something else - perhaps just a red/green bar from an "all tests" run to verify that the refactoring worked. I won't really know until I've played around with it myself and also get some feedback from others.
Underlying these view options is the same "rename class" action. The controller will issue commands to domain objects to kick this off and, since they're ready at hand, it might be tempting simply to pass these on to the view (many "MVC" apps do just that). That's not necessarily bad design (although I wouldn't call it MVC). It could be that a view reads from the same list of objects manipulated by the controller. But not always. In the last example - show the results of an "all tests" run - the view doesn't read from any of them. The display would be created with a whole other app - SimpleTest - which has nothing to do with the rename class refactoring.
OOP is all about choices and consequences. I think the key point about MVC is to allow controllers and views to vary independently. If that's not an issue, you don't have to follow an MVC architecture.Last edited by McGruff; Oct 19, 2006 at 09:37. Reason: clarifying some points
working on: Aperiplus, Rephactor, Phemto
useful links: xUnit test patterns, martinfowler.com, c2 wiki
-
Oct 23, 2006, 16:28 #40
Originally Posted by kyberfabrikken
-
Oct 24, 2006, 01:36 #41
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by rockit
The controllers task is to a) update the model and b) select the view. Nothing more. The controller is not a glue layer between model and view. The view on the other hand, is more than just a template. It's the logic that renders the output. The view can include complex logic (but often doesn't) and it can access the model. It just can't write to the model. Only the controller can do that.
-
Oct 24, 2006, 03:07 #42
- Join Date
- Apr 2006
- Location
- Pennsylvania
- Posts
- 1,736
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
I have a model factory that is passed to the view that allows the view to select a model.
The following is in PHP4 since I'm currently programming a site in PHP4.
PHP Code:$model =& $this->model_factory->get('model_name');
PHP Code:$this->handler->models['model_name']->subscribe('on_data_selected', array($this, 'dataSelected'));
PHP Code:function dataSelected(&$model, $arguments)
{
$this->data = $arguments['data'];
}
-
Oct 24, 2006, 03:34 #43
Originally Posted by kyberfabrikken
Code:$model = new $model($db, $get); //... model functions if need be, $view = new $view($collection); //pass any data to the view in a collection?
-
Oct 24, 2006, 04:16 #44
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by rockit
Originally Posted by rockit
Model layer :
PHP Code:class MyModel
{
protected $name = "World";
static protected $instance;
static function getInstance() {
if (!isset(self::$instance)) {
self::$instance = new MyModel();
}
return self::$instance;
}
function getName() {
return $this->name;
}
function setName($name) {
$this->name = $name;
}
}
PHP Code:class MyView
{
function execute() {
$m = MyModel::getInstance();
echo "Hello ".$m->getName();
}
}
PHP Code:if ($_SERVER['HTTP_METHOD'] == "POST") {
$m = MyModel::getInstance();
$m->setName("PostWorld");
}
$view = new MyView();
$view->execute();
Last edited by kyberfabrikken; Oct 24, 2006 at 06:41. Reason: fixed minor bugs, that prevented the example from working
-
Oct 24, 2006, 05:41 #45
Originally Posted by kyberfabrikken
ok, but you create a model from the view in your execute command?
-
Oct 24, 2006, 05:49 #46
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by rockit
This raises another problem - namely about shared objects. You'd normally want to avoid creating the same model twice, so you'd have to deal with this somehow. That problem is unrelated to the MVC strategy though. You'd eventually get the problem anyway.
Off Topic:
I can see that I missed an echo somewhere in the example above. You can replace return in the view class with echo to make the example actually work.
-
Oct 24, 2006, 06:07 #47
Actually kyberfabrikken, your "by the book" example does not work. Well it does work on GET and would print "Hello World", but it does not work on POST and would print "Hello World" again whereas it should print "Hello PostWorld".
-
Oct 24, 2006, 06:31 #48
- Join Date
- Jun 2004
- Location
- Copenhagen, Denmark
- Posts
- 6,157
- Mentioned
- 0 Post(s)
- Tagged
- 0 Thread(s)
Originally Posted by dreamscape
Off Topic:
well .. i edited the post now, so it'll actually work .. sorry for not testing before posting
-
Oct 24, 2006, 06:55 #49
ok... so as a rule of thumb...???
Model - Domain Logic. doesn't concern itself with anyone else besides itself processing data?
View - Creates a model & Responsible for read only access to the model to output data?
Controller - Updates the Model? and of course many other things ie. Intercepting filters, session handling...
is this a correct understanding of MVC?
-
Oct 24, 2006, 07:20 #50
what confused me is tutorials i found online that contradict some discussions around here.
http://www.phppatterns.com/docs/desi...roller_pattern
Code:<?php /** * Fetches "products" from the database */ class ProductModel { /** * Private * $dao an instance of the DataAccess class */ var $dao; //! A constructor. /** * Constucts a new ProductModel object * @param $dbobject an instance of the DataAccess class */ function ProductModel (&$dao) { $this->dao=& $dao; } //! A manipulator /** * Tells the $dboject to store this query as a resource * @param $start the row to start from * @param $rows the number of rows to fetch * @return void */ function listProducts($start=1,$rows=50) { $this->dao->fetch("SELECT * FROM products LIMIT ".$start.", ".$rows); } //! A manipulator /** * Tells the $dboject to store this query as a resource * @param $id a primary key for a row * @return void */ function listProduct($id) { $this->dao->fetch("SELECT * FROM products WHERE PRODUCTID='".$id."'"); } //! A manipulator /** * Fetches a product as an associative array from the $dbobject * @return mixed */ function getProduct() { if ( $product=$this->dao->getRow() ) return $product; else return false; } } ?>
Code:<?php /** * Binds product data to HTML rendering */ class ProductView { /** * Private * $model an instance of the ProductModel class */ var $model; /** * Private * $output rendered HTML is stored here for display */ var $output; //! A constructor. /** * Constucts a new ProductView object * @param $model an instance of the ProductModel class */ function ProductView (&$model) { $this->model=& $model; } //! A manipulator /** * Builds the top of an HTML page * @return void */ function header () { $this->output=<<<EOD <!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title> Our Products </title> <style> body { font-size: 13.75px; font-family: verdana } td { font-size: 13.75px; font-family: verdana } .title { font-size: 15.75px; font-weight: bold; font-family: verdana } .heading { font-size: 13.75px; font-weight: bold; font-family: verdana; background-color: #f7f8f9 } .nav { background-color: #f7f8f9 } </style> </head> <body> <div align="center" class="title">Our Products</div> EOD; $this->output.="\n<div align=\"right\"><a href=\"". $_SERVER['PHP_SELF']."\">Start Over</a></div>\n"; } //! A manipulator /** * Builds the bottom of an HTML page * @return void */ function footer () { $this->output.="</body>\n</html>"; } } class ProductItemView extends ProductView { /** * Private * $productID ID of product to render */ var $productID; //! A constructor. /** * Constucts a new ProductView object * @param $model an instance of the ProductModel class */ function ProductItemView (&$model,$productID) { ProductView::ProductView($model); $this->productID=$productID; } //! A manipulator /** * Renders a single product * @return void */ function productItem() { $this->model->listProduct($this->productID); while ( $product=$this->model->getProduct() ) { $this->output.="<p><b>Name</b>:".$product['PRODUCTNAME']."</p>". "<p><b>Price</b>:".$product['UNITPRICE']."</p>". "<p><b># In Stock</b>:".$product['UNITSINSTOCK']."</p>"; if ( $product['DISCONTINUED']==1 ) { $this->output.="<p>This product has been discontinued.</p>"; } } } //! An accessor /** * Returns the rendered HTML * @return string */ function display () { $this->header(); $this->productItem(); $this->footer(); return $this->output; } } class ProductTableView extends ProductView { /** * Private * $rowsperpage number of results per page */ var $rowsPerPage; /** * Private * $rownum begin display of rows at this ID */ var $rowNum; //! A constructor. /** * Constucts a new ProductView object * @param $model an instance of the ProductModel class */ function ProductTableView (&$model,$rowsPerPage=20,$rowNum=1) { ProductView::ProductView($model); $this->rowsPerPage=$rowsPerPage; $this->rowNum=$rowNum; } //! A manipulator /** * Renders a product table * @return void */ function productTable() { $this->model->listProducts($this->rowNum,$this->rowsPerPage); $this->output.="<table width=\"600\" align=\"center\">\n<tr>\n". "<td class=\"heading\">Name</td>\n". "<td class=\"heading\">Price</td>\n</tr>\n"; while ( $product=$this->model->getProduct() ) { $lastID=$product['PRODUCTID']; $this->output.="<tr>\n<td><a href=\"".$_SERVER['PHP_SELF']. "?view=product&id=".$product['PRODUCTID']."\">". $product['PRODUCTNAME']."</a></td>". "<td>".$product['UNITPRICE']."</td>\n</tr>\n"; } $this->output.="<tr class=\"nav\">\n"; if ( $this->rowNum > 0 && $this->rowNum > $this->rowsPerPage ) { $this->output.="<td><a href=\"".$_SERVER['PHP_SELF']. "?view=table&rownum=".($this->rowNum-$this->rowsPerPage). "\"><< Prev</a></td>"; } else { $this->output.="<td> </td>"; } if ( $this->rowsPerPage <= ( $lastID - $this->rowNum ) ) { $this->output.="<td><a href=\"".$_SERVER['PHP_SELF']. "?view=table&rownum=".($this->rowNum+$this->rowsPerPage). "\">Next >></a></td>"; } else { $this->output.="<td> </td>\n"; } $this->output.="</tr>\n</table>\n"; } //! An accessor /** * Returns the rendered HTML * @return string */ function display () { $this->header(); $this->productTable(); $this->footer(); return $this->output; } } ?>
Code:<?php /** * Controls the application */ class ProductController { var $model; var $view; //! A constructor. /** * Constucts a new ProductController object * @param $model an instance of the ProductModel class * @param $getvars the incoming HTTP GET method variables */ function ProductController (& $dao) { $this->model=& new ProductModel($dao); } } class ProductItemController extends ProductController { //! A constructor. /** * Constucts a new ProductItemController object * @param $model an instance of the ProductModel class * @param $getvars the incoming HTTP GET method variables */ function ProductItemController (& $dao,$getvars=null) { ProductController::ProductController($dao); $this->view=& new ProductItemView($this->model,$getvars['id']); } function & getView () { return $this->view; } } class ProductTableController extends ProductController { //! A constructor. /** * Constucts a new ProductTableController object * @param $model an instance of the ProductModel class * @param $getvars the incoming HTTP GET method variables */ function ProductTableController (& $dao,$getvars=null) { ProductController::ProductController($dao); if ( !isset ($getvars['rowsperpage']) ) $rowsperpage=20; if ( !isset ($getvars['rownum']) ) $getvars['rownum']=1; $this->view=& new ProductTableView($this->model, $rowsperpage, $getvars['rownum']); } function & getView () { return $this->view; } } ?>
Code:<?php require_once('lib/DataAccess.php'); require_once('lib/ProductModel.php'); require_once('lib/ProductView.php'); require_once('lib/ProductController.php'); $dao=& new DataAccess ('localhost','user','pass','dbname'); switch ( $_GET['view'] ) { case "product": $controller=& new ProductItemController($dao,$_GET); break; default: $controller=& new ProductTableController($dao,$_GET); break; } $view=$controller->getView(); echo ("<pre>" ); // print_r($controller); echo ("</pre>" ); echo ($view->display()); ?>
it just doesn't seem to be a true MVC example. who knows, maybe i'm wrong.
Bookmarks