views:

1064

answers:

3

I have my own hand-rolled PHP MVC framework for some projects that I'm working on. When I first created the framework, it was in the context of building an admin CMS. Therefore, there was a very nice one-to-one relationship between model, view, and controller. You have a single row in the DB, which maps to a single model. The controller loads the model and passes it to the view to be rendered (such as into an edit form). Nice, clean, and easy.

However, now that I'm working on the front end of the site, things are getting sticky. A page isn't always a view of a single model. It might be a user directory listing with 20 users (each a User model). Furthermore, there might be metadata about the request, such as pagination (current page, total pages, number of results) and/or a search query.

My question is, what is the cleanest way to pass all this data to the view?

Some options I'm considering:

  • Have the controller create an array and pass that to the view as a single parameter:

    class UserController{
    
    
    
    public function renderView(){
    
    
        // assume there's some logic to create models, get pagination, etc.
        $data = array()
        $data['models'] = $models; 
        $data['currentPage'] = $current;
        $data['totalPages'] = $total;
        return $view->render($data);
    }
    
    } class UserView{ public function render($data){ // render the data } }
  • Create properties in the view class and have the controller populate them:

    class UserView{
        public $models;
        public $currentPage;
        public $totalPages;
    }
    
    
    class UserController{
    
    
    
    public function renderView(){
    
    
        // assume there's some logic to create models, get pagination, etc.
        $view = new UserView();
        $view->models = $models; 
        $view->currentPage = $current;
        $view->totalPages = $total;
        return $view->render();
    }
    
    }
  • Give the view some sort of generic HashMap or Collection object as a container which can hold any arbitrary number and name of data.

    class UserView{
        public $collection = new Collection(); // works like a Java collection
    }
    
    
    class UserController{
    
    
    
    public function renderView(){
    
    
        // assume there's some logic to create models, get pagination, etc. 
        $view = new UserView();
        $view->collection->add($models,'models');
        $view->collection->add($currentPage,'currentPage');        
        return $view->render();
    }
    
    }

I know that technically any of the could work, but I'm unsure of the best choice, or if there's a better or more conventional choice that I'm missing.

A: 

I've seen both of the first two methods implemented in popular MVC/templating frameworks.

django uses the first method, passing to the view a dictionary of variables which the view uses to fill the template.

smarty uses the second method, creating a Smarty object and assigning values to each the properties in the container.

Your third method seems to essentially be the same as the second, with minor architecture differences.

Really, I guess I haven't said anything that you haven't thought of already. Basically, these are all sounds ideas, so implement whatever you feel you are most comfortable with.

Marquis Wang
+2  A: 

I'm going to recommend the concept of Fat Models, Skinny Controllers (or, Fat Models Thin Controllers if you prefer...)

In otherwords, your model is too strict - tying your model to represent only something like a RowDataGateway is extremely limiting.

In fact, I think good models hide the fact that you're reading the data from a database at all. Because, in reality, your data could be in text files, or from a web service, or whatever. If you treat your Model like nothing more than a glorified DBAL, you doom yourself to having tightly-coupled code in your controllers that just won't let you break away from the "data only comes from the database" way of thinking.

Peter Bailey
Funny, I just read Jamis Buck's article about Fat Models before I posted this question, but I didn't realize that googling the concept would return such a wealth of resources on the concept. I thought it was just his own little philosophy. Thanks for the tip.
Warren Benedetto
Good article :-) I'm not sure I agree with the Fat Models idea completely. I think the model should be JUST a model, with methods that act on itself (in the singular). In other words, a method like FindPeople() wouldn't fit. I do however agree with the benefit of a skinny controller completely. Enter the argument for the Service Layer. Now I don't know much about this and wouldn't claim to be an expert, but I do think its worth pointing out.Yes it would add the extra complexity of a new layer, but the controller could call e.g PeopleService::FindPeople()
David Archer
The model itself can be multi-layered. Some out-of-the-box frameworks already come with a multi-layer model. Symfony, for example, comes with both a DBAL and an ORM, which it scaffolds up with propel tasks. ZF gives us table and row gateways, and leaves it up to us to apply a Domain Model. I'd recommend reading Bill Karwin's article here http://karwin.blogspot.com/2008/05/activerecord-does-not-suck.html - incidentally he's also and avid SOer.
Peter Bailey
A: 

In the one I use, it has automatically has a view property in the controller that you can access methods and properties on the view. All public properties are then accessible within the view view '$this' since the view is rendered in it's own objects context.

In the controller:

$this->view->myVar = 'test';

And in the view:

$this->myVar; // 'test'

The same goes for the layout since the are both separate instances of the same view object:

$this->layout->myVar = 'test';

And then in the layout:

$this->myVar; // 'test'

The framework used to be proprietary, but is about to be released to the general public. I'd be happy to send you some code from it if you think that'd help. Remember, the simplest answer is usually the best answer.

Tres
That's exactly what I'm contemplating in my second example. I kept the code example simple, so I left out the fact that my controller does indeed have an instance of the view, and would therefore work exactly as you suggest: $this->view->foo = 'bar'What makes me uncomfortable about this option is that I have one View class that might render 5 different views of the same model. Each view could have its own data requirements. That means the View class would have a bunch of properties, only a few of which would be relevant to any one view. That's less clean than I'd like. Any suggestions?
Warren Benedetto
In my example above, i noted that the layout and view are both separate instances. Rather than making your view a singleton, you might want to make it so you can create multiple view objects. That way the view is a different instance than the layout, and the properties you set on each are independent of each other. Since you can have multiple instances, you can also have different helper paths, view paths, etc, so certain helpers are only available to certain views just like the properties. It allows for a greater level of customization, if ever needed as well as logical organization.
Tres
Just released it not too long ago http://europaphp.org/
Tres