A Simple OO Controller
However, sometimes it's useful to see much simpler samples to understand how a simple MVC controller might work (without necessarily supporting implicit invocation). Ben had some problems with his OO controller, so I thought I'd post a very simple sample of the kind of approach I've been playing with . . .
I have a LightBase.cfc bean that does all the heavy lifting, and the main method is processPage():
<cfargument name="PageStruct" required="true" type="struct">
<cfscript>
// Create and populate a page request object to hold and pass around all page state information required var PageRequest = createandPopulatePageRequestObject(PageStruct);
// Then process the main content area (before displaying the site template) and load the results into the page request PageRequest.setMainContentArea( mainContentController(PageRequest) );
// Call the Render component to render the page template (which in turn will call the renderer for the main areas) Render.page(PageRequest);
</cfscript>
</cffunction>
All it does is call methods to load up a page request object with all of the information required, call the main controller method to create the data and get the screen name for the primary content area, and then calls another bean to render the page.
createandPopulatePageRequestObject() does all of the bitty work required to load up the PageRequest object with input variables, create a SiteUser object as a meaningful facade to the session scope with real behaviors (like authentication, etc) and the like:
<cfargument name="PageStruct" required="true" type="struct">
<cfscript>
var PageRequest = BeanFactory.getTransient("PageRequest");
// Create a page object for providing information about the "page" being viewed var Page = PageService.new();
// Create an "input" object to handle getting and setting of all inputs (URL and form scope) var Input = BeanFactory.getTransient("Input");
// Create a "SiteUser" object to represent the user making the request var SiteUser = SiteUserService.new();
// Load the input object with URL and form variables Input.loadStruct(URL);
Input.loadStruct(form);
// Populate the page object if ( StructKeyExists( PageStruct , "FilePath" ) ) {
// For page controller, load all the page properties Page.loadStruct( PageStruct );
// Overload the method with the input method if appropriate if ( Len( Input.getAction() ) ) {
Page.setMethod( Input.getAction() ); };
}
else
{
// For front controller, set the controller and method Page.setController( ListFirst( Input.getAction() , "." ) );
if ( Listlen( Input.getAction() , "." ) GT 1 )
{ Page.setMethod( ListGetAt( Input.getAction() , 2, "." ) ); };
};
Page.setURL( CGI.script_name);
// Load the PageRequest object with all the necessary beans PageRequest.setApplicationConfig( ApplicationConfig );
PageRequest.setRequestProperties( createandPopulateRequestPropertiesObject() );
PageRequest.setInput( Input );
PageRequest.setSiteUser( SiteUser );
PageRequest.setPage( Page );
return PageRequest;
</cfscript>
</cffunction>
The mainContentController() method calls the appropriate controller bean that's been DI'd into this object:
<cfargument name="PageRequest" type="any" required="true">
<cfscript>
var MainContentArea = structnew();
var ControllerName = PageRequest.getPage().getController();
// Check the controller bean exists
if ( NOT StructKeyExists( variables , "#ControllerName#Controller" ) )
{ throw ("Invalid controller (#PageRequest.getPage().getController()#)."); }
else
{
MainContentArea = evaluate( "#ControllerName#Controller.call(PageRequest)" );
};
return MainContentArea;
</cfscript>
</cffunction>
And then there is a base call() method in the BaseController that all of my controllers extend for calling the appropriate method in that controller:
<cfargument name="PageRequest" type="any" required="true">
<cfscript>
var Response = StructNew();
var ControllerName = PageRequest.getPage().getController();
var MethodName = PageRequest.getPage().getMethod();
if ( NOT len(MethodName) ) {
MethodName = DefaultMethodName; };
if ( StructkeyExists( variables , MethodName ) ) {
Response = evaluate("#MethodName#( PageRequest=PageRequest )"); }
else {
throw( "Invalid method name (#MethodName#) in controller #ControllerName#." ); };
return Response;
</cfscript>
</cffunction>
As for the rendering, it is as simple as the page() method within the Render component. You'd extend this to customize the logic for using different page templates depending on the rules for a project - time of day, user, section of the site, etc:
<cfargument name="PageRequest" type="any" required="true">
<cfscript>
var TemplateName = "default-page-template";
variables.PageRequest = PageRequest;
</cfscript>
<cfinclude template="#SubdirectoryPath#/lb/template/page/#TemplateName#.cfm">
</cffunction>
Your page template might look something like:
<head></head>
<body>
Page template with header and the like goes here<br />
#displayMainContentArea()#
Footer goes here<br />
</body>
</html>
And the displayMainContentArea() calls that method on the render() object which simple does the following:
<cfscript>
var TemplateName = PageRequest.getMainContentArea().Template;
var Data = PageRequest.getMainContentArea().Data;
var Page = PageRequest.getPage();
var Input = PageRequest.getInput();
var Message = "";
if ( StructKeyExists( PageRequest.getMainContentArea() , "Message" ) ) {
Message = PageRequest.getMainContentArea().Message; };
</cfscript>
<cfinclude template="#SubdirectoryPath#/lb/template/screen/#TemplateName#.cfm">
</cffunction>
A ColdBox it isn't, but it works quite well as an example of how simple the basic problem of loading up some kind of event/pagerequest bean and then calling a controller method and passing it to an appropriate renderer can be.
Thoughts?



I'm interested in your take on what the community MVC frameworks give you that this little sketch does not. The kind of approach you outline here avoids the two big drawbacks the community frameworks tend to have:
1. Massive quantities of what are effectively global variables
2. A tendency towards spaghetti XML
So, without starting a religious war (I know some frameworks do avoid one or both of these) - what are the benefits that make those drawbacks worthwhile?
Jaime Metcher
You do realize that there is no possible way to answer your question without starting the Nth iteration of the same frameworks discussion! j/k