Migrating to Page Controllers
Pages: 1, 2
Migrating to a Page Controller Setup
Converting mixed logic/view pages to their Page Controller equivalents is straightforward. The migration is transparent to users because the original page's URI doesn't need to change unless you want it to.
This section uses a sample page to demonstrate the migration process. Both the "before" and "after" versions (old.zip and new.zip, respectively) are available for download. Feel free to review the files and follow along:
|
Related Reading
|
Draw a map. Draft a flowchart that maps the page's execution into logic and presentation content. Make note of which logic triggers a given set of content, and what dynamic data that content uses.
The sample code uses the flowchart from Figure 1. The controller (new.php) passes objects generated from database query results (Lines 56-75, then 82) and error messages (Lines 28 and 38) to the views.
Create the views. Extract presentation content into separate pages. The sample code's views are the files new-DBConnectError.php, new-NoResults.php, new-SQLError.php, and new-Success.php. (The view pages may have duplicate content, such as menu bars and footers. Later in the article I offer one solution to this.)
The remaining code will become the controller. Leaving the controller at the old page's URI makes for a seamless transition, as old links to that URL will continue to work.
Notice that the sample pages have no logic for error handling. That should take place within the controller itself, which can dispatch to the appropriate error page. (If you haven't done so already, set
display_errors = 0in php.ini to avoid interleaving PHP's code-level error messages with your view pages' content.) View pages should have just enough logic to format the data that the controller provides.Formalize the controller. Rework the remaining code (the controller) to dispatch to the view pages based on the results of each decision. Place data in a variable that both the controller and views can access.
The controller in the sample app puts all of the data in the array
$viewData. The view pages will call this same variable.Update the views. Rework the view pages to pull data from variables assigned in the controller.
For example, note how the new-Success.php page iterates through the array
$viewData['CustomerList'](Lines 33-45). The error pages look for a message under$viewData['ErrorMessage'].
Follow a similar process when designing a new app with Page Controllers in mind.
Growth and Scalability
Converting a large, dynamic web application may leave you with tens or hundreds of Page Controllers. These can be pockets of stability in a larger mess unless you take a long-range view of your design. Consider the following ideas to help scale your application accordingly:
Design controllers to fetch URIs from an app-wide map instead of using direct file paths. That makes the app more adaptable to changes in file structure and makes it easier to debug dispatches to nonexistent pages. The "map" could even be a custom object that returns a predefined error page when someone requests a nonexistent alias.
Identify and eliminate duplicate pages. While the "success" page is closely tied to a specific controller, chances are the error pages are very similar. (The sample code's error pages are near clones of one another.) You can reduce your overall page count by sharing a generic error page that looks for a message in a predefined variable.
Don't stop refactoring now. Converting an existing Model 1 app to use Page Controllers will uncover code duplication. The sample app, for example, could encapsulate its data access in a Data Access Object (DAO) or DataObject.
You can also extract your business processes into objects or function libraries, whittling down your controllers to the bare minimum of code:
<?php
// ... any include() or require() calls ...
$busObj = new BusObj( ... ) ;
$result = ${busObj}->exec( ... ) ;
if( defined( $result ) )
{
$viewData[ 'Result' ] = $result ;
include( 'success.php' ) ;
}
else
{
$viewData[ 'ErrorMessage' ] = "No result" ;
include( 'error.php' ) ;
}
?>
This level of separation lets you put other faces on your business processing. (Think fat-client GUI or web services.)
Mixing Page Controllers and Front Controller
Those of you who have read my previous article on the Front Controller pattern may wonder when to use that instead of Page Controller. You can (and probably should) use both.
The two patterns coexist peacefully within the same application because they hold different, yet complementary, responsibilities. The Front Controller specifies where to go (the page to fetch for the requested URI), while a Page Controller decides what to do (the action to perform).
Furthermore, they work without the knowledge of one another. The Front Controller doesn't realize it's dispatching to a Page Controller, and the Page Controller doesn't know what called it. To integrate the two, map a Page Controller to one of the Front Controller's target URIs.
One reason to use a Front Controller with a Page Controller is to minimize the duplicated content in the extracted view pages. The Front Controller can set up common menu bars, footers, and so on, while the Page Controller will handle the specific request.
That's a Wrap
Migrating all-in-one pages to Page Controllers is the first step in a top-down refactoring of your web application. Your end users will notice that the app is more stable and that it takes you less time to implement new features. The clear separation between logic and display will make it easier for you to fix bugs and make changes.
Page Controller is a small-scale pattern that can apply to several places within an application. Combine it with large-scale patterns such as the Front Controller to keep your design clean both from far away and up close.
Ethan would like to thank "Mr .NET the SB" for reviewing and improving this article.
Resources
- Download the sample code. It will likely require changes to work on your system, as it uses PostgreSQL-specific database calls. (I opted against PEAR or other abstraction layers in order to keep the code focused on the matter at hand.)
- Patterns of Enterprise Application Architecture (Fowler) includes a section on the Page Controller pattern.
- Core J2EE
Patterns (Alur, Crupi, Malks) describes the Service to Worker
pattern. This twist on the traditional Page Controller uses a separate
Dispatcherobject that determines the view and passes control to it. - Refactoring (Fowler) covers the reasoning behind the practice in addition to several pattern-style refactorings.
- Java's RequestDispatcher class is the key ingredient to a servlet+JSP Page Controller, which was the model for my PHP implementation thereof.
- I described the Front Controller pattern in a previous article. That article includes a sample PHP Front Controller implementation.
Ethan McCallum grew from curious child to curious adult, turning his passion for technology into a career.
Return to the PHP DevCenter
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 4 of 4.
-
Front controller
2004-11-06 06:25:45 SilvioMolinari [Reply | View]
-
Front controller
2004-11-07 05:58:20 Ethan McCallum |
[Reply | View]
It's true, Java/J2EE webapps have a portable, standard means to limit access to resources (among other useful features); that said, in a non-J2EE webapp you can still prevent people from directly accessing view pages:
1/ use .htaccess to disable bare directory listings. It's tougher for people to access a resource if they don't know what it is.
2/ put a different extension on your view pages (e.g. ".phpv") and use .htaccess to deny access to them. The controller can still access those files because PHP's include() bypasses the Apache request cycle.
-
Front controller
2004-12-13 16:13:36 alexSchmid [Reply | View]
3.) hold your views in a Directory not accesible through the browser but by php.
I often use a setup like:
+documentRoot
+html //holds you php-scripts
+elements //holds images, css and .js
+templates //holds template files e.g. 'views'
+Language1
+Language2
With a setup like this you can employ a template engine like smarty or sigma to populate the views with content and deliver through the page controler. If necessary also in different languages/screen-layouts or with content created by loops for result-sets with not too much effort.
Due to the physical location of the template-files the webserver will never deliver those files to the user. Only trap is to make sure that the directories can be accessed by php and the path is working (use a global value like: /var/www/myDomain/templates/ and complete by the pagecontroller depending on language/view)
Happy programming,
Alex Schmid
P.S.: Excuse my bad english & typos



This design (as described in some J2EE best practice pattern design books) can be achieved with some Apache configuration directives using mod_rewrite. For example, mod_rewrite could force all requests to be handled by the same PHP file (the controller), with the exception of requests starting with '/images/' or '/css/' just to name a few.
More design patters could be applied at this stage, for instance, the Controller, could rely on a Factory class to produce Command and/or View objects to handle the request as specified, let's say, in a configuration file.
This sounds like a lot of work (and it is!) but once the framework is in place, it makes life a lot easier.