Added by Gavin, last edited by Gavin on Apr 12, 2007  (view change) show comment

Labels

 
(None)

Introduction to Model-View-Controller Architectural Pattern

This section introduces the ZF's approach using an extremely simple introductory example, before beginning the forum demo application in the next section. A basic understanding of the ZF's MVC is required for the next section. In this introductory example, a basic ZF bootstrap is constructed with a minimalistic configuration for the ZF MVC, without complicating the example using a database.

In the beginning, there was code and most developers used purely procedural style programs. As patterns in programs emerged, developers began documenting these patterns and finding ways to more reliably apply common patterns in appropriate situations. The migration from procedural paradigms to encapsulating state and behavior using object-oriented paradigms parallels the migration to an architectural pattern that encapsulates business logic and presentation logic separately.

Separation of Concerns using MVC

Instead of intermingling code to compute results (e.g. statistics from a database) with if-then-else constructs to render the results in a manner suitable for viewing, the model-view-controller pattern uses an intermediate controller component to facilitate decoupling code for data models from code to generate representations or views of these models. Reducing the code connecting creation of views and code creating the data used in views also reduces the effort required to change, update, maintain, and develop this code. For web applications, views typically contain templating systems focused on describing a web page using HTML and CSS using only the data purposely made available to the views with no modification of this data or creation of new data. Similarly, MVC architecture facilitate isolating the construction of the data model to code invoked by controllers upon request from the user's actions.

One Script Per Web Page

Although a single script can both calculate the variable data to show on a web page, and render the final representation of the web page, maintaining the code often becomes more difficult when changes are requested. Look carefully for the "data model" and "business logic" in the code below. The presentation of the representation is scattered throughout the entire script.

The "ini" file provides our data source, but this could have been something vastly more complex involving SQL queries to a database.

The resulting output for http://...../cities/cities-near-london.php?distance=1000:

Cities within 1000 km of London:

City Distance
Geneva 740 km
Paris 343 km
Berlin 929 km

What if the script now needs to show cities within a user-specified distance to Paris?
What if the user can choose which city? Many changes are needed, but the "business logic" and "data model" code is mixed together and with the formatting of the final output view. What if another developer is responsible for beautifying the view and adding styles and graphics to it? Can they find the right places in the code to edit?

Relatively simple changes now require one person to understand most of the script in order to make changes to any one of the data model, business logic, or presentation. When one person is responsible for the database and data model, and another person is responsible for the business logic, and a third person is responsible for the "look and feel" or presentation, the coordination and maintenance begins to accumulate a lot of overhead effort that does not materialy contribute to the application.

Models, Views, and Controllers

In the ZF, representations of data, or views, are separated from the construction of the models and business logic used to produce the dynamic data, such as the list of cities shown on the web page. As with many frameworks, responsibility of separating the data model from the business logic belongs to the developer. In our forum demo, we will also show how to separate the data model.

Separation is usually done by splitting out logically related code into separate files. The value of the separation is enhanced by avoiding referencing presentation code from within the business logic code, and vice-versa. In order to coordinate code organizaed like this, we need "controller" code to tie the right things together in the right ways at the right times.

MVC Workflow

In practice, we can take the "city" example in the prior section and split out the presentation and logic by introducing the ZF Controller component. The controller plays the controlling role in the flow of execution, where the following "STAGES" exist:

  1. controller receives request from user (e.g. web browser page request or Ajax request)
  2. controller determines which action(s) to execute
  3. action determines which models and business logic to execute, possibly updating the model and associated persistent data
  4. action buids presentation model using the data model
  5. action selects view and submits a presentation model (often a subset of the data model) to the view
  6. view computes a representation using the submitted model
  7. controller renders the results back to the user as a response to the user's request

MVC View Workflow

Before examining the entire code for this section's bootstrap and controller, a quick survey and explanation of views and view-related terminology will help clarify other code in this section.

In the bootstrap code section2_intro/index.php, a view object is created. The view object holds our presentation model. The presentation model contains the data used to fill out a view script (i.e. template). Thus, in the ZF the view object is not really a true MVC "view". Instead, the view object is used in combination with a view script, and possibly view helpers, to create or render the view.

This creates a view object, and then instructs the front controller to send this object to the action controller. Although the name of the variable in the bootstrap code holding this object is arbitrary, there must be a convention for submitting it to an action controller. The action controller section2_intro/IndexController.php must pick up the passed object and store it into a protected variable named "_view". In later sections, we expose additional functionality in the ZF that expects and uses this "well known" location for holding the action controller's view object.

Then the front controller dispatches the request object to the correct action controller, which populates the view object with data:

Creating the view object outside of the action controller enables us later to initialize and prepare the view before use by an action controller. Managing views outside of action controllers allows for the possibility of using multiple action controllers without having their view object polluted from other controllers. For example, imagine a web page containing multiple frames/modules, such as a buddies list and recent posts list, each produced by different actions in different controllers.

Lastly, the action controller renders a "view" using a view script and the view object, placing the rendered view (e.g. HTML code) into the response object:

Then, the bootstrap echo's the final response back to the browser:

Bootstrap

In the ZF, the first code to execute loads and configures the ZF components needed for the ZF MVC to work. Many call this "boot" code the application's "bootstrap". Different applications have different needs and necessarily have variances in their bootstrap code. The example in this tutorial purport to highlight the typical things needed by most ZF applications in their bootstrap.

In stage 1, a classic front controller pattern acts as a gateway for all requests in order to simplify applying common actions, initializations and security to an entire application. This pattern takes the form of a singleton, because each request has only one instance of these front controller objects.

After creating the front controller, the working directory for MVC actions is set to the same location containing the file above. This causes the front controller to look for actions in the same directory as the file above. Although not best practice, this example remains simple with all relevant files stored in the same location.

As a convenience, an empty view is published and made available to all MVC actions using the setParam() mechanism provided by the front controller.

For stage 2, some magic happens with the request string (http://...../zfdemo/cities/index.php?distance=1000), and then the right action is executed inside the file IndexController.php. The magic secrets are reserved for a later section In this example, default rules are used to map the URL (.../index.php) to the MVC action named "indexAction" in the controller "IndexController". In the ZF, all such actions have the suffix "Controller" added to their name.

Stages 3, 4, 5, and 6 occur in other files, shown and explained below.

The last stage, stage 7, completes the flow of execution by echoing the final results for display in the web browser.

MVC Action Controller

Although fundamentally different from the front controller, the action code selected and executed in Stage 2 "controls" the flow of execution for a while and is responsible for coordinating between data stores, data models, presentation models, and views (e.g. templates). Thus, the ZF names these "action controllers" in the code.

When in doubt, sometimes an adventurous peek into the source code found in "library/Zend/Controller" of the ZF answers questions. Many will be pleasantly surprised by the clarity of the code and docblocks.

If we try http://zf/zfdemo/cities/index.php/index/hello, the browser should show Hello World!. Breaking up the pieces in the URL, "hello" maps to the controller action hello(), while "index.php" simply specifies which PHP script the web server should run. The long "/index/" part of the URL is used to calculate the name of the controller file (class) by appending "Controller" to it. Thus, the shortened URL above (http://...../zfdemo/cities/index.php?distance=1000) can also be written as http://zf/zfdemo/cities/index.php/index/index?distance=900, where the second "index" word corresponds to the prefix used to name the file above, and the third "index" word corresponds to the function to execute inside this file. Before the index() method executes, the action controller above is instantiated and initialized automatically using its init() method, which retrieves the view object created in the boostrap file before stage 2 and adds the view object as a local property.

Next, in stage 3, the index() method executes, creating a simple data model from our data store ('cities.ini').

Stage 4 begins with some simple initializations of the presentation data model stored in the view, including filtering some user input and saving the result for use within the view. The "heavy" business logic of this tiny example executes next by calculating the subset of the data model satisfying the distance criteria requested by the user, with the result once again stored into the view's presentation model for later use. Commonly, the last step in action controllers, including this one, involves choosing a which view template to use (stage 5).

Views / Templates

Approaching completion, the script then computes the result of filling out the template using the view's data, in stage 6 below:

The separation of business logic from presentation exists, since no data is modified, and all conditionals use only data stored in the view and computed prior to the execution of the view's template code. The results of the execution of this template are automatically captured by the "magic glue" code in the ZF MVC and then returned back to the bootstrap script, as requested by "$frontController->returnResponse(true)." Looking back to the boostrap script, stage 7 executes and generates the final response sent back to the web browser:

Alternatively, "echo $response;" accomplishes the same effect by using the magic "__toString()" method, which invokes "sendResponse()".

Now that we have seen the complete cycle of the ZF MVC workflow, let us examine a more complex example in the following sections, based on the popular forum or bulletin board concept.


Next Section: 3. Application Topography and Configuration

Why is the bootstrap file calling a own function ???

bootstrap();
function bootstrap()

Unknown macro: { ... }

Is it not much more logic to have the bootstrap file without the function useage ?

This simplifies reading of the code and has the same logic.

Why is the bootstrap file calling a own function ???

I am not sure I have ever seen anyone object to having code inside a function before Many developers consider code outside of a function very bad practice. I doubt many people will have difficulty understanding the logic in this index.php file:

Posted by Gavin at Mar 20, 2007 17:38

Well, I can't speak for Thomas, but I think I may understand what he's getting at:

Those of use who started programming years ago with languages like C and C++ learned to declare things before we used them - and to put include statements outside the scope of functions and objects and other declarations.

So, instead of first calling the function, then opening the function declaration, and then including Zend/Controller.php", we old-timers would expect first the require_once, then the function declartion, and finally the call to the function.

No big deal - in my opinion at least - but it does beg the question whether there's a good reason - in your opinion - to prefer one order over the other ( ? ).

I have a question about the names used in the sample code - in particular, about "view" and "_view".

Apparently they are meant to be examples - not requirements.  I wasn't clear on that point, so I tried changing them, and it didn't break the code, so... am I right in believing that they could be named differently?

Are there any conventions you're following here? Particularly with regard to "_view"? I'm accustomed to seeing names that start with underscores being reserved for some kind of "internal" use.  Do names starting with underscores play a special role in the Zend Framework - even if only by convention?

Finally, could you explain the object instances behind the names a little more? Why was the "view" instance instantiated outside the controller, and passed through to the controller? Why couldn't it simply be initiated there? And what role does it play? All the values used in the view seem - at first glance - to be either constants, or the return values of standard functions e.g. date(), or a value taken from the standard PHP $_REQUEST...

I trust there's more going on here than meets the eye (or my eyes, at any rate)... Could you please give me (and your other readers) a little help figuring out why things are done the way they are?  It's sometimes hard to see when things are "merely examples" and when they are "best practices" and when they more or less obligatory (I was frankly surprised that using other names than "view" and "_view" worked - somehow they looked to me like predefined names!).

Thanks in advance!

Thanks again, your feedback helps me see areas to improve

I added a new section "MVC View Workflow" as a prelude to introducing the entire code for the bootstrap and controller. It should address most of your questions. If not, please describe a little more what is meant by "using other names than 'view' and '_view'".

Regarding the data stored in the view, two of the variables, cities and distances are dynamically selected by the application logic. Data like the current date could be inserted directly by a view script using a view helper or similar, but it is too early to introduce view helpers. Also, purists sometimes object to view scripts / templates doing things that "compute" values. Thus, the approach above is less provocative.

Posted by Gavin at Apr 12, 2007 18:42

I think you must add this line to index.php:
$frontController->setParam('useDefaultControllerAlways', true);

I have just done a text search of the zfdemo folder that I Checked Out onto my PC and, if WinXP search is being honest with me, there was no mention of the phrase "ViewRenderer" within the source code.

Was there no mention of this because:
a) The tutorial was written before ViewRenderer was added to Zend?
or
b) ViewRenderer is not really relevant to this tutorial?
or
c) My Windows XP search is rubbish and there actually is stuff about the ViewRenderer but XP thought it would be best not to tell me about it in case I get too confused by the syntax?

If (a) is the case, could anyone comment on how ViewRenderer fits into the whole MVC pattern.

Thanks - I'm a great fan of your work.

I'm a newbie with Zend framework but I did enough tutorials and personal tries to agree that the ViewRenderer is a "most to know". Assuming that this tutorial don't want to go to fast with the helpers because it want to show a little bit the under hood, I presume the answer is b) ViewRenderer is not really relevant to this tutorial?

cheers

This is one of the best pieces I've read that explains the MVC concept to me - so thanks.

I'm still a bit confused, however, by the following sentence:

"Managing views outside of action controllers allows for the possibility of using multiple action controllers without having their view object polluted from other controllers."

I'm wondering - if the view is managed outside of the action controllers, doesn't that increase the chances that a view will be polluted by another action controller?

Or are you suggesting that each controller makes up an 'internal view' which it then passes to an 'external view' object?