How to implement a composite view?

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

blueyon
Forum Commoner
Posts: 76
Joined: Tue Oct 30, 2007 9:53 am

Re: How to implement a composite view?

Post by blueyon »

This is what I have come up with.

I choose not to go down the action chain to the render. The reason I did this is because I have a project on the go and needed a quick solution.

Code: Select all

 
Default Page:
 
<?php
class ActionDefault extends Action {
    var $id     = 'content';
    var $parent = 'layout/layout';
    
    function execute() {
        $this->document->setTitle('Default Controller');
        
        $this->data['links'] = array();
        
        $this->data['links'][] = array(
            'text' => 'Google',
            'href' => 'http://google.co.uk'
        );
        
        $this->render('common/default.tpl');
    }
}
?>
 
Layout Page:
 
<?php
class ActionLayout extends Action { 
    var $id       = 'layout';
    var $children = array(
        'module/header',
        'module/column',
        'module/footer'
    );
    
    function execute() {
        $this->data['title']       = $this->document->getTitle();
        $this->data['description'] = $this->document->getDescription();
        $this->data['base']        = $this->document->getBase();
        $this->data['charset']     = $this->document->getCharset();
        $this->data['direction']   = $this->document->getDirection();
        $this->data['language']    = $this->document->getLanguage();
        $this->data['links']       = $this->document->getLink();
        $this->data['styles']      = $this->document->getStyle();
        $this->data['scripts']     = $this->document->getScript();
        
        $this->render('layout/layout.tpl');
    }
}
?>
 
Front Controller:
 
<?php
class Front {
    var $default;
    var $error;
    var $pre_action = array();
    var $output;
    
    function __construct(&$registry) {
        $this->registry =& $registry;
    }
    
    function setDefault($default) {
        $this->default = $default;
    }
    
    function setError($error) {
        $this->error = $error;
    }
    
    function addPreAction($pre_action) {
        $this->pre_action[] = $pre_action;
    }
    
    function dispatch(&$request, &$response) {
        $action = $this->requestHandler($request);
        
        while ($action) {
            foreach ($this->pre_action as $pre_action) {
                $result = $this->execute($pre_action);
 
                if ($result) {
                    $action = $result;
                    
                    break;
                }
            }
            
            $action = $this->execute($action);
        }
        
        $response->set($this->output);
    }
    
    function execute($action) {
        $action = $this->build($action);
        
        $result = $action->process();
        
        if ($result) {
            return $result;
        } else {
            $this->output = $action->getOutput();
        }
    }
    
    function build($action, $child = NULL) {
        $file  = DIR_CONTROLLER . $action . '.php';
        $class = 'Action' . basename($action);
 
        if (file_exists($file)) {
            include($file);
                
            $action = new $class($this->registry);      
        
            foreach ($action->children as $key => $value) {
                $action->children[$key] = $this->build($value);
            }
        
            if ($child) {
                $action->children[] = $child;
            }
        
            if ($action->parent) {
                $action = $this->build($action->parent, $action);
            }
        
            return $action;
        } else {
            exit('Error: Could not create ' . $action . '!');
        }
    }
    
    function requestHandler(&$request) {
        if (!is_null($request->get('action'))) {
            return $request->get('action');
        } else {
            return $this->default;
        }
    }   
}
?>
 
Page Controller:
 
<?php
class Action {  
    var $id;
    var $parent;
    var $children = array();
    var $data     = array();
    var $output;
    var $registry;
    
    function __construct(&$registry) {
        $this->registry =& $registry;
    }
    
    function __set($key, $value) {
        $this->registry->set($key, $value);
    }
    
    function __get($key) {
        return $this->registry->get($key);
    }
            
    function getId() {
        return $this->id;
    }
            
    function process() {
        foreach ($this->children as &$child) {
            $result = $child->process();
            
            if ($result) {
                return $result;
            }
        }
        
        $result = $this->execute();
        
        if ($result) {
            return $result;
        }
    }
        
    function forward($action) {
        return $action;
    }
    
    function redirect($url) {
        header('Location: ' . $url);
        exit(0);
    }
    
    function render($filename) {
        foreach ($this->children as &$child) {
            $this->data[$child->getId()] = $child->getOutput();
        }
        
        $this->output = $this->fetch($filename);
    }
    
    function fetch($filename) {
        $file = DIR_VIEW . $filename;
    
        if (file_exists($file)) {
            extract($this->data);
 
            ob_start();
      
            include($file);
      
            $content = ob_get_contents();
 
            ob_end_clean();
 
            return $content;
        } else {
            exit('Error: View ' . $file . ' not found!');
        }       
    }
    
    function getOutput() {
        return $this->output;
    }
}
?>
 
It works, but forwarding is a bit tricky.
 
As I said I think the correct way of doing it would be to use action chains to store data and pass it on from one action to the next. Having one controller do the redirecting, forwarding and rendering seems like its putting a lot of responsibility on a single object.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: How to implement a composite view?

Post by Christopher »

Ok, you've made a single Presentation class (Action) that combines the Controller and View. That certainly streamlines things and can be a good choice. Looks nice.

Do you have a downloadable archive with all the files and directories (that runs) to try out?
(#10850)
blueyon
Forum Commoner
Posts: 76
Joined: Tue Oct 30, 2007 9:53 am

Re: How to implement a composite view?

Post by blueyon »

Heres a little test script.

If you can improve on it please let me know.
Attachments
MVC Composite Example.zip
(10.42 KiB) Downloaded 62 times
blueyon
Forum Commoner
Posts: 76
Joined: Tue Oct 30, 2007 9:53 am

Re: How to implement a composite view?

Post by blueyon »

There are still some problems with my implmentation.

Such as how to handle the errors if no page is found.

One way I was thinking of is have a application controller that would build the structure from the first controller its given. The structure could be paced into an array and then each output put could be used to make up the full page which would be then passed back to the front controller.
User avatar
Kieran Huggins
DevNet Master
Posts: 3635
Joined: Wed Dec 06, 2006 4:14 pm
Location: Toronto, Canada
Contact:

Re: How to implement a composite view?

Post by Kieran Huggins »

merb uses something called "exception controllers" which I'm really digging. There's a general one, and possibly one for each specific http error. That might be what you're looking for. If you raise an exception of a certain type the framework redirects to the appropriate exception controller/view.
blueyon
Forum Commoner
Posts: 76
Joined: Tue Oct 30, 2007 9:53 am

Re: How to implement a composite view?

Post by blueyon »

Sorry, but I have to say this incase any one else trying to learn about MVC getting the wrong idea!
There is nothing in the MVC pattern that says that the View cannot access the database. MVC define two separations: Primary - between the Model and the Presentation (V+C) and Secondary - between the View and Controller. Either the View or Controller may have a dependency on the Model
MVC is the speration on concerns.

That means that controller (application flow) / model (business logic (or data access)) / view (template ) is split up.

It does not mean that one object just passes another object into another object.

The view should not have direct DB access it means the controller passes the results of the DB query to the view or any other calcualtion the controller tells the model to make.

Smalltalk has been around since the 1970's and pretty much defined MVC. Its up to been around for 38+ years and I consider it more evolved than any of the recent web reincarnations have to offer.

The 1 = 1 (view = controller) controller should still apply and makes better sense.

Checkout all the differnt version of MVC here:

http://ctrl-shift-b.blogspot.com/2007/0 ... cture.html
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: How to implement a composite view?

Post by Christopher »

blueyon wrote:MVC is the speration on concerns.
That is one thing. It is also about specific dependencies. And it also supports things like DRY via modularization.
blueyon wrote:That means that controller (application flow) / model (business logic (or data access)) / view (template ) is split up.
The controller is also about user input. The model is the domain code, but Data Access it typically put in a layer below the Model. The View is not just templates -- it is all presentation logic. Templates are essentially data for the View to process.
blueyon wrote:It does not mean that one object just passes another object into another object.

The view should not have direct DB access it means the controller passes the results of the DB query to the view or any other calcualtion the controller tells the model to make.
The Controller is not a go-between for the Model and View. It can change public setting in the Model based on use input and program state. The View should get its data directly from the Model.
blueyon wrote:Smalltalk has been around since the 1970's and pretty much defined MVC. Its up to been around for 38+ years and I consider it more evolved than any of the recent web reincarnations have to offer.
Which explains why Smalltalk is dominant language today and Java, C# and PHP are only small players in the MVC story. ;) I think you have it backwards. The early implementations of MVC were in Smalltalk, but the evolution happend in other languages -- and especially the web where MVC makes more sense after being generally abandoned in desktop apps.
blueyon wrote:The 1 = 1 (view = controller) controller should still apply and makes better sense.
1:1 defeats the DRY benefits of having the separations in the first place. This is especially true in modern Front/Action Controller architectures and for things like Ajax.
blueyon wrote:Checkout all the differnt version of MVC here:
I read it and I don't see when it supports your views of MVC.
(#10850)
blueyon
Forum Commoner
Posts: 76
Joined: Tue Oct 30, 2007 9:53 am

Re: How to implement a composite view?

Post by blueyon »

ok i'm not going to keep arguing about the differnt approaches of MVC as I would be here putting my points accross for the next few months.

I would like to get back to the composite view.

If you check this thead on sitepoint:

http://www.sitepoint.com/forums/showthr ... 808&page=8

You said:
You are correct about the heirarchy being controllers, not views. What I am looking for is something that easily handles doing the most common case where you pass a connection object to the model and the model to the view, then get the rendered output of the view.
What made you change your mind?
Post Reply