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

Labels

 
(None)

Front Controller and Action Controllers

How can a discussion of Model-View-Contoller (MVC) patterns be complete without a reference to Fowler's writings, including his summary of front controllers? All user requests received by the application from the web server arrive first in the front controller. The front controller must manage other ZF controller components to find the business logic needed to service the request.

See the file "data/zfdemo.log" for insights into the execution flow and inner workings of the ZFDemo application. Code snippets in the tutorial text represent the real ZFDemo source code, but minor differences sometimes exist to simplify the presentation. Thus code snippets often leave out logging statements and some details from the real code that are not needed to understand how the ZFDemo works.

To get started, copy the 'zfdemo/section4_mvc/index.php' to your web server's document root directory (or the location used for the installation test in the first section). Now, navigate with a browser to the URL of the 'index.php' file just installed to view a menu of possible actions.

Zend Framework Controller System

After choosing an application layout model, the layout must be translated into use of the ZF controller components. These components manage much of the flow of execution in typical ZF applications. Approximately in order of execution, these include:

  • Zend_Controller_Front - Initializes and configures the overall workflow, including managing dispatching
  • Zend_Controller_Request* - Accessors for controllers & actions (and status .. e.g. already dispatched)
  • Zend_Controller_Router - Exactly once per request, calculates correct controllers & actions based on environment in Zend_Controller_Request*.
  • Zend_Controller_Dispatcher - Transfers flow of execution to controllers & actions in request object (Zend_Controller_Request*). Process repeats until no more actions are scheduled.
  • Zend_Controller_Action* - Userland controller classes containing userland actions.
  • Zend_Controller_Response* - contains the output of the executed actions

The front controller in Zend_Controller_Front includes several convenience functions to work with the Dispatcher, Request, Router, and action controllers. The convenience functions also help configure both options and defaults, and manage coordination details among these components of the controller system. In many cases, a developer can directly use the APIs of these components instead. Thus, some parts of this demo avoid supported "shortcuts" so that the inner workings and flow of control become more visible and comprehensible.

If you are reading this tutorial without access to a web server and PHP, browse the latest versions of the demo source code on the ZF server.

At this point in the tutorial, we need to create all the stages found in the city example in order to have a working example of our goal, a forum system. Sessions, authentication, and database abstractions will not be introduced, until after a working demo has been assembled below with results visible using a web browser. After these "skeleton" stages are assembled into the demo-in-progress, future additions can be much smaller with visible results.

Bootstrap

By popular choice, the "main" or starting function for ZF applications receives the name "bootstrap". The bootstrap contains the userland code needed to setup and initialize the ZF controller-related components listed in the section "Zend Framework Controller System" above. This code includes all the stages seen in the "city" example in the previous section. However, the ZFDemo application class contains more code. For clarity, this bootstrap code is divided into functions according to the stages introduced previously. These functions also help highlight what pieces of information travel between the stages.

Recall from the installation instructions, to begin, the demo needs at least one piece of information - where to find the configuration information. This information is passed to the "bootstrap()" method in "bootstrap.php" to get things started by helping the demo find the right directory and configuration file. Conveniently, the path information also contains the name of the section containing this copy of the ZFDemo. The names are also used as the directory names containing each version of this demo. The names were created by abbreviating the title of each section of the tutorial. Leaving out some details for handling errors and configuration, the "bootstrap()" method contains:

Recall that the "missing" stages (3,4,5,6) above actually occur inside the action code and any models and views selected by that action code. The action code contains the key business logic needed to perform whatever action the user requested, such as "show me the list of forum topics". These actions are grouped by controllers into one file per controller. Additionally, logically related controllers are grouped by directory into modules, such as forum related controllers vs. authentication related controllers.

Index.php

So what calls the "bootstrap()" method? The one line of code inside the ZFDemo that can be manually configured exists as the first uncommented, non-empty line in index.php. This line instructs the script on the location of the remaining files needed for this section's copy of the ZFDemo tutorial code. By default, a relative location is assumed, as described in Section 3. However, if your host does not allow organizing the zfdemo directories as "../../zfdemo" relative to the location of index.php, then you will need to hand-edit this line of code in index.php by changing the first string value to the full path name to "section4_mvc". If you find this necessary, do not forget to make similar changes, each time you copy a different tutorial section's index.php to your web server.

Why not copy bootstrap.php to the web server, and avoid using index.php? If you are using the SVN checkout method to obtain the ZFDemo tutorial, and then make changes to bootstrap.php, these changes take effect immediately, without having to copy the modified file to your web server. Also, most system administrators frown heavily on installing mission-critical or sensitive source code directory within the web server's document root directory, in order to reduce the chances for accidental exposure of the source code to the public, and to help reduce various security risks.

To launch to ZFDemo bootstrap, index.php calculate where to find the rest of the source code and configuration information before calling the "bootstrap()" method. Also, E_STRICT "verbose" error reporting is enabled immediately to help detection of problems, such as when users tweak and experimentally alter the demo. This file contains a strange looking function named simply "_()", which exists in this version of the ZFDemo merely as a hook to later expand on for the i18n translation feature. For now, this function emulates "sprintf()" by returning the resulting string.

Stage 1: Initializations / Loading Configuration

Stage 1 in the ZFDemo bootstrap introduces the concept of a global registry for frequently accessed information, keyed by name. For example, the locations of directories will not change during the execution of a single request, so these locations may be cached for the duration of the request in the Zend_Registry. The registry is not persistent between requests, but offers an extremely simple way to save structured information for use elsewhere in the ZFDemo application, without resorting to ugly global variables. Instances of the the registry can even be stored inside the master registry, in order to achieve namespacing of registry data.

Even though we can create normal instances of Zend_Registry, using the default instance of Zend_Registry is more convenient and avoids any extra effort to pass a reference to other functions. Every call to 'getInstance()' returns the exact same instance object, instead of a new one.

This prepends the top-level directory containing this demo's source code. When including ZFDemo files, like models from controllers, the full directory path will not be needed. Thus, "require_once '<module>/models/<Model>.php';" will work in the business logic code, where <module> is the name of a module (i.e. forum in this demo), and <Model> is the name of a model, such as 'Posts' or 'Users' in this demo.

The demo then loads configuration information using an object-oriented wrapper for PHP's parse_ini_file() that supports the '->' object property notation for accessing member elements listed under sections in the ".ini" file. The '$configEnv' variable is one of only two configurable settings hard-coded into the ZFDemo source code (in "index.php"). With the construction of Zend_Config_Ini, '$configEnv' selects a specific named section in the ".ini" file, allowing easy toggling between development and production configuration settings for the application. The trailing 'true' argument in the constructor enables modification of '$config' on the line below where the data directory is prepended to the relative path for the log file name specified in the ".ini" file.

In addition to the '->' property access mechanism, Zend_Config_Ini also supports a form of inheritance. The 'sandbox' section inherits (using a ':' colon) from the 'production' section, yielding a "Los Angeles" timezone and a log filename of "zfdemo.log". The full 'config.ini' file for this section defines several other settings.

Continuing through the code in 'stage1', to avoid various problems with Zend_Date, a default timezone is set.
Change the timezone entry above in 'section4_mvc/config/config.ini' to your timezone.

Conveniently, the database adapter accepts an array of configuration information in name-value pairs. The names chosen in the ".ini" file are exactly the names expected by the MySQL adapter. The relevant part of the Zend_Config_Ini object can be exported directly to an array and then passed to the database adapter constructor. After the DB adapter is configured, a reference is placed into the registry for future reference by model classes.

Thus, we can specify the adapter's options directly in our 'config.ini' file:

Notice how the 'sandbox' setting in 'index.php' results in 'bootstrap.php' using the database.name and db.modelSet settings under the 'sandbox' section of the '{{config.ini}' file.

In order to highlight features with less clutter, this demo requires MySQL. After the demo tutorial is done, we may extend support to others. Patch suggestions are welcome.

STAGE 1: Prepare the front (primary) controller.

Similar to the Zend_Registry, the demo uses a 'getInstance()' method to retrieve the primary front controller for the demo application. Every call to 'getInstance()' returns the exact same instance object, instead of a new one.

The demo has two modules, but one is only a default catch-all. All of the business logic, presentation logic, templates, and models for the forum "application" itself exist in the module and directory named "forum". As with the application layout presented previously, each module contains three subdirectories to hold:

  1. action controllers - business application logic,
  2. models - domain models encapsulating our data store,
  3. views - presentation logic encapsulating templates

The demo creates a view early, only for the purpose of pre-initializing a view object for later use by action controllers. This supports a "copy/modify" form of inheritance, where controllers receive a copy of the initialized view and may then modify it to suit their needs. The sequence of adding paths defines the precedence of use for multiple view scripts having the same name. Like PHP's include path, only the first matching view script in the script path will be used. After concluding the view initialization, STAGE 1 completes by returning the configured front controller to the 'bootstrap()' method.

Why not use "Zend_Registry::set('view', $view);"? If your application has only one view object, then using the registry is ok, but all controllers will write to the same view object, possibly polluting it or causing name clashes. If your application never uses more than a single controller, then pollution and name collision won't occur within this view object accessed via the registry.

However, if your application uses multiple view objects, using the "setParam()" approach works nicely, to keep the different view objects separated. The ZFDemo tutorial uses this approach because later sections introduce the possibility of dispatching multiple controllers within a single request. Sometimes a web page or request involves multiple actions not all related and contained in the same controller file. Any application using multiple modules will likely have situations where more than one module's view must be rendered into the response.

STAGE 2: Find the right action and execute it.

The heart of the controller system's workflow includes only a small number of steps, by default. After determining that an action controller is dispatchable, the controller is instantiated, and then initialized. Next, control of execution is passed to the action controller, which then performs a "preDispatch()" call before finally invoking the selected action method in the controller. Lastly, the action controller's "postDispatch()" method is executed. Even though developers write the action controllers, they inherit base functionality from Zend_Controller_Action. Both "preDispatch()" and "postDispatch()" are provided by this superclass, although neither are needed by the ZFDemo.

Before examining the flow of execution summarized above, first let us examine the context of the statement causing this flow:

Later sections in the tutorial need various hooks into this flow and the objects involved. By explicitly showing these objects in "stage2()", the exchange of information will become more readily apparent. The front controller has several options, including one option to select between displaying the result of "$frontController->dispatch(" to the browser, or merely returning the result for further processing. Returning the results helps those wishing to create site templates with results inserted into an overall template having headers, footers, etc.

Although the Zend_Config_Ini object, "$config", could have been passed explicitly between "stage1()" and "stage2()" via method arguments, the ZFDemo shows another possible approach to publishing the configuration object for general use using the registry. Recall that a reference to the master registry was saved into the ZFDemo object (i.e. "self" below). Retrieving the mater registry via "Zend_Registry::getInstance()" works equally well.

The response and request objects are standard ZF components. The request object holds the request information received by the web server from the web browser, while the response object collects the output generated by the action controllers, including any HTTP headers, if needed. The output is stored in named segments (i.e. an associative array with programmer-friendly keys used to name each segment). Although the default segment used by the response object is named 'default', we avoid any intentional storage of data to the default segment. Thus, any data showing up in the default segment represents an error condition, such as accidental 'echo' debug statements in a controller's application logic code. The ZFDemo uses the 'body' segment for its default/main output segment in the response object. Creating an empty initial 'body' segment enables later sections to append to it under certain circumstances discussed later.

Next we have a "try .. catch()" block surrounding the actual dispatch call to process the request. Since a web application normally should not "die" unexpectedly or send an exception debug message to the web user, all exceptions are caught. In this section (MVC), exception handling remains minimal, with only a generic message shown to the user, appended by the exception's message. Previously, exceptions were enabled via "$frontController->throwExceptions(true);". In the next section, extensive exception handling is added, allowing for dynamic selection of modules, controllers, and actions depending on the nature of any exceptions that might occur. If exceptions are disabled, they are not really disabled, but instead added to an array in the response object, and developers must be more careful when executing various code, since an exception might have been thrown previously (and not directly dealt with).

Finally, the base portion of the URL (i.e. how to access 'index.php') is saved to the view object for later use in templates (view scripts) to construct full path URLs, which enable ZFDemo to operate correctly in subdirectories on the host web server, without resorting to using URL rewriting engines in the web server. Also, for local requests over the LAN (not public Internet), additional diagnostic information is placed into the view object, so that web pages can show the computed module, controller, and action that actually received the request. As ZF applications become more complex, knowing this information when viewing web pages aids development and understanding of the flow of execution.

Now, let us examine the sequence of key steps occuring inside "$frontController->dispatch($request, $response);":

  1. front controller initializes plugins, router, and dispatcher
  2. front controller runs routeStartup() for any plugins
  3. front controller runs the router to map the $request object to a module, controller, and action
  4. front controller runs routeShutdown() for any plugins
  5. front controller runs dispatchLoopStartup() for any plugins
  6. front controller enters dispatch loop
    1. front controller marks {{$request}}as dispatched
    2. front controller runs preDispatch() for any plugins
    3. front controller asks dispatcher to dispatch the $request
      1. dispatcher instantiates action controller
      2. dispatcher marks $request as dispatched
      3. dispatcher runs dispatch() on the action controller
        1. action controller initializes via init() called from its constructor
        2. action controller runs notifyPreDispatch() for any action helpers
        3. action controller runs its preDispatch()
        4. action controller executes the requested $action (STAGES 3-6)
        5. action controller runs its postDispatch()
        6. action controller runs notifyPostDispatch() for any action helpers
    4. front controller runs postDispatch() for any plugins
    5. continue dispatch loop, unless the request is marked as "dispatched"
  7. front controller run dispatchLoopShutdown() for any plugins
  8. front controller returns $response, instead of sending HTTP headers and segments in $response (since we asked it not it)

Stages 3 to 6 occur after control of execution is transferred to the action controller (e.g. section4_mvc/forum/controllers/*Controller.php).

To begin using the demo application, a user must start somewhere. Before viewing a topic or post, the user must first ask, "show me the list of forum topics." Since no information is needed from the user to service this request, navigate to the 'index.php' file just installed, and click on the first link, "Forums". The page should now show something like the following:

Mar 9, 2007 5:48:16 PM - Welcome anonymous
Forum Topics

Topic User Posted On Last Modified
Suggestions gavin Mar 6, 2007 1:33:08 PM  
Introduce Yourself gavin Feb 23, 2007 6:50:08 PM  

STAGE 7: Render results in response to request.

For now, we skip the meat of the application, STAGES 3-6, in the action controller, model, and view. The bootstrap resumes executing after STAGES 3 through 6 finish. Responses to non-AJAX requests need a doctype, header, title, body, and footer. The ZFDemo must support internationalization, so UTF8 is used throughout the demo.

Some controllers provide their own HTML '<title>' tag, so 'stage7()' below conditionally adds a title, only if it is missing in the response. Fortunately, the named output segment array in $response is ordered, so ZFDemo is able to prepend the doctype/header information, and append a standard footer.

Since a demo should be easy to tweak by users, displaying exceptions to the browser seems appropriate for request received over the local network. For production systems, more robust handling of caught exceptions will be needed.

Alternatively, the named segments in the response could be submitted as properties of a new view object to a view script. Such a view script would be equivalent to traditional "site templates".

STAGES 3, 4, and 5

Now let us return to the skipped stages implemented by code in other files.

ZFDemo_Controller_Action extends Zend_Controller_Action

All action controllers in the ZFDemo extend ZFDemo_Controller_Action class. Above, we saw that the dispatcher instantiates the action controller. Thus, the constructor in Zend_Controller_Action will initialize properties in every action controller to hold references to the request object, the response object, and the parameters set from the front controller. Finally, the constructor invokes 'init()' on the action controller causing 'init()' in lib/Controller/Action.php to execute:

Initializations made here are shared by all action controllers. The last two-thirds of 'init()' above locates, includes and executes a method named 'moduleInit' in a class named 'ZFModule_<module name>' in a file <module name>/<module name>.php. This initialization method allows the module's controllers to easily share a common startup routine. It also provides a place to enforce authentication and access control for controllers, as shown in later sections.

The next two methods are a little more cryptic, and fit in with existing methods in Zend_Controller_Action. Normally, ZFDemo action controllers call 'renderToSegment()' when done computing a view object's presentation model. This method then supplies defaults to Zend_Controller_Action's 'render()' method, based on the name of the action controller and the name of the action selected by the router and dispatched by the dispatcher. The 'render()' method runs a view script and captures the output into a named segment in the response object.

To reduce repetitive coding inside every controller and every action, several steps are automated with Zend_Controller_Action and ZFDemo_Controller_Action. The 'initView()' above contains some code not used by the ZFDemo that preserves backwards-compatibility, allowing other ZF applications to use the features added by ZFDemo_Controller_Action. In the ZFDemo, view objects are always passed to the action controllers and stored in the instance variable 'view', allowing the module's script, filter, and helper paths to be prepended to the search list, thus eliminating the need to configure these in normal "userland" code. For example, when rendering a view in an action controller, if a view script exists in 'forum/views/scripts' it will be used, but if the view script only exists in the default module's script directory, then that script will be used. This approach allows default scripts, helpers, and filters to be shared amongst modules.

ZFModule_Forum

The ZFDemo forum system is based on some fundamental assumptions related to traditional behavior of web-based forum/bulletin board systems. This class helps share functionality based on those assumptions with all forum controllers in the ZFDemo. Later sections will add to this class. For now, it mostly serves to select and load the PDO versions of the forum model classes needed by the forum controllers to interact with the database.

Forum Controller

Code for the forum module's "home" page is presented. The "home" page produces a list of all topics, to give the website visitor a place to start. The default controller and action of the forum module produce this list of topics.

For the default forum controller, the default action is "indexAction". This default action checks if a user requested the posts for a specific topic. If a topic was specified, then the topic's posts are shown. However, if no topic was given, then the "indexAction" controller redirect to the topics controller, which will then show a list of forum topics.

In order to show the posts, first the appropriate models must be loaded in STAGE 3. The business logic asks the model for all posts belonging to a topic. Then the business logic sifts through the results, adding information needed for rendering to the view object. Display action is broken up into individual stages in order to support extension points for adding an administrative controller that inherits from this controller.

Lastly, STAGE 6 is initiated by requesting the view object to render itself into the "body" segment of the response object.

Helper functions abstract common tasks to avoid code duplication within action controllers like "indexAction".

STAGE 6: Render results in response to the request.

STAGE 6 occurs in a view script "template": "section4_mvc/forum/views/scripts/indexPosts.phtml"

The view template accesses the presentation model data stored previously in the $view object by the action controller above. All "logic" in this script exists only to choose which information to render. Data is not created or processed within the view script.

Note that "<?= $this->someVariable ?>" is a PHP shortcut for "<?php echo $this->someVariable; ?>". This shortcut is not used to maximize compatibility with ZFDemo user's installed PHP configuration, since these short tags can be disabled in 'php.ini'.

SQL Schema

As with many applications, the key to understanding how they work often requires an understanding of their data models, and the ZFDemo is no different.

If anyone would like to submit a pretty diagram of the schema below, I am sure the tutorial readers would be most appreciative

For our demo, one table holds the forum posts, another table contains information on the users making the posts, and a third contains the list of topics. Forum posts are grouped under topics, where each post has exactly one topic and exactly one author (user who made the post). In a later section, retrieved web content attached to posts is supported by the attachments table.

Since this is a demo, the schema enhances the ability to understand changes to the database by adding two columns to every table. The "modification_time" column contains a timestamp reflecting the last time the row was set or updated, while the "creation_time" column records the timestamp for the initial insertion of the row into the database. When querying the database, these columns simplify the task of locating and examining rows updated by the demo application, especially when using a tool like phpMyAdmin to browse the table contents. When viewing posts, these columns provide the "last edited" time and the "posted on" time. All primary and foreign keys end in a classic "_id" suffix and use a prefix corresponding to the name of the associated table.

The UTF-8 character set is used exclusively throughout this demo, in order to preserve the wonderfully internationalized flavor found in the ZF, and the ZF's support for i18n.

In the ZFDemo's actual 'config/zfdemo.mysql.sql' file, the starting schema below is augmented with a small number of "starter" rows to help show how the columns and tables are intended to work, and to enable immediate non-empty output when accessing the demo to browse forum topics and forum posts. Clicking the "reset" link in the ZFDemo will drop these tables and data and recreate them.

All code may also be viewed using FishEye.

The tables 'u2', 't2', and 'p2' are not shown above. These three tables are duplicates of 'users', 'topics', and 'posts', respectively. They are not used by the demo application, but exist for comparison purposes, so that you can examine the effects of various actions performed using the demo's web interface. If you have phpMyAdmin installed, these "backup" copies of the tables make comparing changes more convenient.

Action Helpers

Action helpers provide a convenient mechanism to share code between controllers. They are not yet supported in the core ZF library yet, but we expect this soon.

Key advantages:

  • loading an action helper automatically connects the helper's pre/post dispatch methods to the action controller
  • convenience: loading the helper provides easy access to its public methods from within the action controller
  • helpers can be as easy as "drop in place and use" for many ZF applications

Why not keep the helpers as standalone classes? Action controller would then have to load the standalone helper, register the helper's pre/post dispatch hooks (defined/added to the action controller abstract), then initialize the helper. After these steps, the action controllre would need to register a copy of itself with the standalone class, in order to make various things possible from with the standalone class, such as found currently in _redirect (which needs to examine information in the request object). Thus, natively supported action helpers will prove quite convenient.


Next Section: 5. Dispatch Error Handling and 404 Error Pages

When and how should the database get initialized?  In particular, when are the four tables (posts, users, topics and attachments) supposed to get added to the database, and how - i.e. automatically or manually?

 If manually - it might be good to tell the poor reader that at some point - and to point him to the data and/or SQL to be used. 

If automatically - then it doesn't seem to be working for me.  I've got the code running far enough that it dies in testDb, but not any further.  By the way, logging implode( "  ", $expected ) in testDb doesn't work, not at least with my setup - it outputs the values, not the keys, i.e. nothing.  Just to get output, I changed that to log print_r( $expected, TRUE ), and voila - all 4 tables are missing...

 ...which frankly did not come as a terribly big surprise; where were they supposed to come from anyway?   (see above).

OK - I found the SQL in the config directory (filename zfdemo.sql) - and have gotten a bit further. 

So just consider the comment above to be a sample of how readers react to things like this...   (and not a matter that needs immediate attention).

Another good suggestion from Ronald

The code to automate loading has been added to section1_install/index.php.
Also, all copies of the ZFDemo source now include the link "Resest/Load/Restore DB" in the top right corner, allowing users to delete topics and posts, add posts, etc. without fear of making a mess.

Posted by Gavin at Apr 13, 2007 15:38

After a fresh install ... and completing the "section1_install/index.php" ok, I went to "section4_mvc/index.php" ... and get the following error:

Fatal error: Cannot redeclare _() in C:\xampp\htdocs\zfdemo\section4_mvc\index.php on line 88

I'm not sure if I even could have done anything to declare a protected function ... _()

same happen to me- after checking- found out by just adding an extra "- or any word or letter" to the _($msg) bellow things work.
don't know why.

/**

  • Hook for SECTION: i18n (translation)
    */

orig:

function _($msg)
{
/////////////////////////////
// ==> SECTION: mvc <==
if (func_num_args() === 1)

Unknown macro: { return $msg; }

else

Unknown macro: { $args = func_get_args(); array_shift($args); return vsprintf($msg, $args); }

}
-------------------------------------
fix:

function log_($msg)
{
/////////////////////////////
// ==> SECTION: mvc <==
if (func_num_args() === 1)

} else
Unknown macro: { $args = func_get_args(); array_shift($args); return vsprintf($msg, $args); }

}

Posted by taka at Apr 24, 2007 01:57

Further to the unhappy comments I made on the Introduction page, I really hit problems in the MVC section. Not just the illegal (as far as I know) attempt to overload _() mentioned by others, but once commented out, any attempt to click on the forums link got me silently redirected back to the test page. Wish I'd traced the headers earlier instead of rummaging through all the controller code. I could have saved hours.

The show stopper appeared to be setRedirectCode() calls (tons of them in the demo) but no declaration that I could find. After commenting that out it got worse still with a call in
section4_mvc/forum/controllers/TopicsController:displayStage3_4() to ZFDemoModel_Topics::getPresentationModel(). How is it supposed to have loaded this class file? I think I found it in section4_mvc/forum/models/pdo/Topics.php of all places. Even by adding require_once to get around the class naming/path disparity yet more errors came up.

I'm giving up for now, but as this appears to be the "official" demo tutorial I do really hope someone finds time to look into all these (apparent) bugs.

I got a little problem concerning the postDispatch step.

As far as I understand, the action controller iterates through all the action helpers and postDispatches them.

The problem is, that there is a Action_Helper_ViewRenderer which seems to automatically render my view. However i've done that bevore in the renderToSegment-Method. So I'd like to somehow disable this helper.

Can anyone tell me how this is best done? 

Using ZF 1.0.3 I needed to do the following corrections in order to get section4_mvc to work:

LOC: index.php [from section4_mvc]
ERR: function name '_' not allowed by (my) php
OLD: function _($msg)
NEW: function _x($msg)

LOC: index.php
ERR: paths doesn't work
OLD: //ZFDemoGrub('section4_mvc', 'sandbox'); [comment out]
NEW: ZFDemoGrub('/Users/marcgrue/Sites/zfdemo/section4_mvc/', 'sandbox'); [uncomment this line - use an absolute path]

LOC: index (from section4_mvc)
ERR: php errors not showing for debugging (with my general php_ini settings)
NEW: ini_set('display_errors', true); [add this line before/after the line 'error_reporting(E_ALL|E_STRICT);']

LOC: section4_mvc/bootstap.php::stage1() ca line 170
ERR: fatal error: Zend_View class declared twice
OLD: require 'Zend/View.php';
NEW: require_once 'Zend/View.php';

LOC: section4_mvc/bootstap.php::stage1() ca line 191
ERR: Going to 'Forums': wrong (empty) view object is rendered, so no topics are shown
NEW: $frontController->setParam('noViewRenderer', true); [add this line just before returning the $frontController]

LOC: section4_mvc/default/controllers/IndexController.php::indexAction() ca line 29
ERR: '/index.php' added unnecessarily to baseurl
OLD: $this->view->baseUrl .= '/index.php';
NEW: //$this->view->baseUrl .= '/index.php'; [comment out]

LOC: section4_mvc/forum/controllers/Index::redirectToTopics() ca line 121
ERR: link to forums is silently redirected back to 'home'
OLD: $this->setRedirectCode(303);
NEW: //$this->setRedirectCode(303); [uncomment]
ALTERNATIVE: add "/topics" to Forums link in zfdemo/index.php

LOC: section4_mvc/forum/models/pdo/Topics.php::getPostsByTopicId() ca line 33
ERR: Zend_Date not loaded
NEW: require_once 'Zend/Date.php'; [add line]

Hope this can save others some debugging hours, although you learn a lot from it :-|

Dear Gavin Vess - I found a lot of recursions in the frontcontroller, request and response objects. Is it supposed to be so? There's a lot to learn from your tutorial, but I wonder if some parts have got out of tune with recent zf versions?! Are you planning to update the tutorial?

got the function name wrong:
LOC: section4_mvc/forum/models/pdo/Topics.php *getPresentationModel()* ca line 33
ERR: Zend_Date not loaded
NEW: require_once 'Zend/Date.php'; [add line]

forgot this one:
LOC: section4_mvc/forum/models/pdo/Posts.php::getPostsByTopicId() ca line 58
ERR: Zend_Date not loaded
NEW: require_once 'Zend/Date.php'; [add line]