Migrating to Page Controllers
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:
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 = 0 in
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
Dispatcher object 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
keeps himself busy with with Unix/Linux, C++, and Java.
Return to the PHP DevCenter