Skip to end of metadata
Go to start of metadata

<ac:macro ac:name="note"><ac:parameter ac:name="title">Please go to the new MVC Changes page</ac:parameter><ac:rich-text-body>
<p>This proposal is no longer being worked on. Instead direct your attention to the official proposal from Zend:</p>
<h3><a class="external-link" href="http://framework.zend.com/wiki/x/kxM">http://framework.zend.com/wiki/x/kxM</a></h3></ac:rich-text-body></ac:macro>

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[

Zend Framework: Changes to Zend MVC Component Proposal

Proposed Component Name Changes to Zend MVC
Developer Notes http://framework.zend.com/wiki/display/ZFDEV/Changes to Zend MVC
Proposers Michael Sheakoski
Christopher Thompson
Revision 1.1 - 1 August 2006: Updated from community comments. (wiki revision: 41)

Table of Contents

1. Overview

This proposal is a combination of several changes to the MVC architecture in ZF:

  • introduction of a Request object which acts a common layer of communication between the bootstrap, Router, FrontController, and ActionController. It is designed in a way to pull data from any source and provide it via a common interface to the MVC components. Currently in this proposal the Zend_Http_Request object provides input from PATH_INFO, GET, and POST data.
  • removal of the DispatchToken. Its functionality is now contained in the Request object.
  • removal of Router-dependant code from the FrontController::dispatch() method. The router is now an optional component that is handled in the bootstrap or in a convenience method such as ::run()
  • removal of URL-related functions from the Router. This is now handled by the Request object.
  • added support for ActionControllers in subdirectories

2. References

3. Component Requirements, Constraints, and Acceptance Criteria

Zend_Http_Request

4. Dependencies on Other Framework Components

5. Theory of Operation

6. Milestones / Tasks

7. Class Index

  • Zend_Request_Interface is a common generic interface that the Router, FrontController, Dispatcher, and ActionController use to to talk to each other. It is designed to interface with any type of input source.
  • Zend_Http_Request builds on top of the Zend_Request_Interface. It contains functions to pull any data from the browser and provide it to the MVC components.
    Some features include:
    • determines important variables on any platform with or without mod_rewrite such as REQUEST_URI and PATH_INFO
    • getParam() automatically falls back to GET/POST variables if a Router is not used or user-defined vars are not set
    • aliases allow you to call your controllers and actions something other than 'controller' and 'action'. This allows for a central location to configure these settings.
    • aliases can be used for more flexible URLs. For example:
      $request->setAlias('controller' => 'c'); $request->setAlias('action' => 'a'); would work with the URL: http://mysite.com/index.php?c=news&a=view
  • Zend_Cli_Request (not created yet) is meant to pull any input from the command-line (args) or keyboard (stdin) and provide it to the MVC components through the Zend_Controller_Request_Interface. This is an example of how the proposed MVC changes allow for much more flexibility by not limiting them to HTTP-specific functionality.
  • Zend_Router_Rewrite (evolution of the RewriteRouter, for mapping PATH_INFO to a controller/action)
  • Zend_Router_RewriteLite (evolution of the original ZF Router, handles simple routes such as http://mysite.com/ and http://mysite.com/ctrl/act/p1/one/p2/two/pN/N) sites in need of a fast, lean and mean solution would use this as opposed to the more full-featured PathRouter
  • Zend_Router_CliParam (not created yet) would allow you to map command-line parameters or keyboard input to an action/controller. For example, automatic processing of parameters in the GNU "--paramname=value" format, "-f filename", "/C zend", etc...

8. Use Cases

This is perhaps how an intermediate / advanced user would use it:

And this would be the beginner approach:

9. Class Skeletons

Download the working prototype here: [Zend_Db_Adapter_Odbtp_Mssql^mvc_working_prototype.zip]


]]></ac:plain-text-body></ac:macro>

]]></ac:plain-text-body></ac:macro>

Labels:
None
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Sep 09, 2006

    <p>This is shaping up nicely. Two quick Qs:-</p>

    <ul>
    <li>Did you decide to remove the __get / __set methods for the request properties intentionally? It's not a real deal-breaker if they don't exist, however would be a convenience to the component</li>
    <li>The proposed Front Controller still - IMO - suffers from a request object being pushed into it, rather than having it passed via the constructor. It's splitting hairs, but I may wish to set up my own 'request' property within a subclassed Zend_Controller_Action and any value I populated the public $request property with would be overwritten by the Front Controller. Could you change the code to read simply
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    $controller = new $className($request);
    ]]></ac:plain-text-body></ac:macro>
    and then the default constructor for Zend_Controller_Action could then be
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    public function __construct(Zend_Controller_Request_Http $request)
    {
    $this->request = $request;
    }
    ]]></ac:plain-text-body></ac:macro></li>
    </ul>

    1. Sep 09, 2006

      <p>Hello Simon,</p>

      <p>For _<em>get/</em>_set do you propose they be wrappers for getParam/setParam? This would seem to make the most sense.</p>

      <p>As for passing $request to the Action constructor that does seem to make sense. I wasn't sure if the constructor was reserved for any other uses so I left it alone initially.</p>

      1. Sep 11, 2006

        <p>Indeed, those _<em>get/</em>_set would map to those methods in an ideal world.</p>

      2. Sep 11, 2006

        <p>Simon, I was thinking about it and passing the $request to the constructor is not necessarely a good idea. It might be better off going through a method such as $controller->setRequest($request) because the developer may want to use the constructor to initialize the controller for their own needs such as setting up global views/templates, db connections specific to the controller, etc... What are your thoughts?</p>

        1. Sep 11, 2006

          <p>I didn't see that as a huge problem as the Zend_Controller_Action is an abstract class and in most cases the developer will be customising the Action as they see fit anyway.</p>

          <p>So I may simply leave the constructor along, but someone else may choose to customise AND retain existing functionality:-</p>

          <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
          class MyAction extends Zend_Controller_Action
          {
          function __construct(Zend_Controller_Request_Http $request)

          Unknown macro: { parent}

          }
          ]]></ac:plain-text-body></ac:macro>

          <p>I'd enforce the interface for the constructor, though.</p>

          1. Sep 11, 2006

            <p>Good call. I'll change it accordingly <ac:emoticon ac:name="smile" /></p>

          2. Sep 11, 2006

            <p>My one caveat here is that the programmer may want to pass a Zend_Registry object instead (that would contain a Request object). If we enforce the interface then that is not possible. So the decision on what can be passed needs to be decided first before defining the interface. </p>

            1. Sep 11, 2006

              <p>You mean in the FrontController where you have the following?</p>
              <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
              public function run($arg=null)
              {
              if ($arg) {
              // get request object if passed
              if ($arg instanceof Zend_Http_Request)

              Unknown macro: { $this->_request = $arg; // if registry passed, attempt to get request from it }

              elseif ($arg instanceof Zend_Registry && $arg->has('Zend_Http_Request'))

              Unknown macro: { $this->_request = $arg->get('Zend_Http_Request'); }

              }

              // if no registry, create it
              if (! $this->_request)

              Unknown macro: { $this->_request = new Zend_Http_Request(); }

              ]]></ac:plain-text-body></ac:macro>

              <p>To me this seems a bit unnecessary because once you pass the Registry object the only thing the FrontController does with it is extract the Request. FrontController is only run once and the one line of code to deal with the Registry if needed could be done in the bootstrap rather than giving the FrontController an additional unrelated object to support.</p>

              <p>This whole idea of having the Request object is to have a common layer of communication for all of the MVC components to deal with. The components should only be as "smart" as they have to which means they don't have to know anything else besides how to interface with a Request.</p>

              1. Sep 11, 2006

                <p>For me, there are two "ideas" for having a Request object. One is a common communication object in what I might call the "Intercepting Filter Pipeline" meaning pre-filters, the Front Controller, the dispatched action controllers, and post-filters. That is one need. The other is to provide access to the request vars within the Action.</p>

                <p>But the programmer might like access to several objects within the action and that is the need for a Registry to be injected into the controllers. By doing this, the programmer can inject any depenencies they want into their actions. </p>

                <p>Passing a Registry containing a Http_Request object meets some needs. Passing just a Request object makes the Request one of several useful objects potentially available. Once you eliminate the ability to pass in a Registry/Service Locator you can never really get that capability back. If I could only have one interface, then I would choose to passa Registry object because it would be the most powerful solution in the long term. </p>

                <p>However, I don't think these details should be apparent to the average scripter. Zend_Controller_Front::run() should just do it all internally. But programmers who want to do the process manually should get the more powerful injection capabilities of a Registry/Service Locator. </p>

                <p>I don't think ZF should limit what is passed because there are two or three different possiblities (e.g Context object) that people use in different situations. But the Registry style is the most flexible. </p>

                1. Sep 11, 2006

                  <p>Basically you want less code in the bootstrap to make it easier for "average scripters" but there are many issues to contend with once you start making assumptions on what they want to do.</p>

                  <p>Let's say the bootstrap looks like this as you propose:</p>
                  <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
                  $controller = new Zend_Controller_Front();
                  $controller->setControllerDirectory(dirname(_FILE_) . '/controllers');
                  $controller->run();
                  ]]></ac:plain-text-body></ac:macro>

                  <p>Would the average scripter get confused by adding one additional line of code?<br />
                  This is the only basic difference between auto (above) and manual (below).</p>
                  <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
                  $request = new Zend_Controller_Request_Http();
                  $controller = new Zend_Controller_Front();
                  $controller->setControllerDirectory(dirname(_FILE_) . '/controllers');
                  $controller->dispatch($request);
                  ]]></ac:plain-text-body></ac:macro>

                  <p>My goal was to keep the routing proces fast, simple, extensible, and easy to maintain. My fear is with the FrontController assuming what you are trying to do instead of just doing what you tell it. How will it know if you are trying to route GET/POST variables or trying to route PATH_INFO? If you are trying to route PATH_INFO then you need a router, now there is more unrelated code in the controller to deal w/ the router. What if the input is coming from CLI? Now you have to check if it is HTTP or CLI. Etc... Sure you can do all of these checks and get a mostly reliable result but at what cost in complexity and decreased performance? All just to eliminate one line of code in the most simple case?</p>

                  <p>I think the hand-holding will make it easier for the beginners at the expense of those who want more power and speed. The bootstrap in this proposal is no more complex than the current bootstrap in 0.1.5 I'm sure developers of all skill levels would be able to figure it out once the docs are published.</p>

                  <p>I'm not saying that "auto pilot mode" is bad in general but I don't think that this is the place to put it. I think it would make more sense in something like ZFApp or "My_Controller_Front extends Zend_Controller_Front"</p>

                  1. Sep 11, 2006

                    <p>I guess I was thinking that the short way for scripters would be just:</p>
                    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
                    Zend_Controller_Front::run()
                    ]]></ac:plain-text-body></ac:macro>
                    <p>And the long way would be:</p>
                    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
                    $registry = new Zend_Registry();$registry->set('Request', new Zend_Http_Request());$registry->set('Config', new Zend_Config_Xml('config.xml'));$registry->set('Session', new Zend_Session());$registry->set('View', new Zend_View());$registry->set('Db', Zend_Db::factory('PDO_MYSQL'));$controller = new Zend_Controller_Front();
                    $controller->setControllerDirectory('/my/path/to/controllers');
                    $controller->execute($registry);
                    ]]></ac:plain-text-body></ac:macro>
                    <p>The code inside the Zend_Controller_Front::run() method would create a Registry, Request, Router and Front – and then run them using the default directories and names. </p>

                    1. Sep 12, 2006

                      <p>I'm just trying to think how would you auto-detect the default controller directory without setting it in the bootstrap or adding code to the FrontController that reads a config file somewhere? It's late and my brain is only half awake <ac:emoticon ac:name="smile" /> but the only way I can think of is doing a debug_backtrace(), figuring out the location of the script that called the FrontController and then do a few guesses relative to that location. That way probably wouldn't sit well with the community though since it is hackish.</p>

                      1. Sep 12, 2006

                        <p>I don't think you auto-detect anything. Just have the default controller directory "controllers/" and the default route "index/index". That way the majority of PHP applications will be very consistent in their layout – which is a good thing. Only more complex applications need to do anything different. </p>

                        1. Sep 12, 2006

                          <p>Ahh okay. Now that I got some sleep I see what you mean. The ::run() method can handle all of the automatic stuff while leaving ->dispatch() still available for the power users that prefer a manual approach. Sounds like a good idea to me!</p>

                          1. Sep 12, 2006

                            <p>Yes. But I was thinking that if the Router and Front Controllers are both potential polymorphic filters for an Intercepting Filter loop then I think execute() would be more generic than despatch(). Plus we might want to use dispatch() for acutally that in the FC.</p>

                  2. Sep 11, 2006

                    <p>Sorry, the linebreaks were stripped. The long way would be:</p>
                    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
                    $registry = new Zend_Registry();
                    $registry->set('Request', new Zend_Http_Request());
                    $registry->set('Config', new Zend_Config_Xml('config.xml'));
                    $registry->set('Session', new Zend_Session());
                    $registry->set('View', new Zend_View());
                    $registry->set('Db', Zend_Db::factory('PDO_MYSQL'));

                    $controller = new Zend_Controller_Front();
                    $controller->setControllerDirectory('/my/path/to/controllers');
                    $controller->execute($registry);
                    ]]></ac:plain-text-body></ac:macro>

  2. Sep 16, 2006

    <p>It might be useful to add an optional $default parameter to the getQuery(), getPost() etc methods rather than having null all the time.</p>

    <p>Also, I might be wrong here but since 'null' is being returned as default, would it be better to use isset() instead of array_key_exists()? I was under the impression that language constructs are more efficient than functions. I suppose it's only marginal and not worth worrying about? </p>

    1. Sep 16, 2006

      <ac:macro ac:name="unmigrated-wiki-markup"><ac:plain-text-body><Unable to render embedded object: File ( if () not found.$requst->getQuery('loggedIn'))

      Unknown macro: { do this; }

      http://www.php.net/manual/en/language.types.boolean.php

      I understand where you are coming from and would be interested in hearing what others have to say about this as well. I am trying to keep things as lightweight as possible and these little things add up. As long as the interface is consistent I personally don't think it is a problem that NULL is returned for values not found.]]></ac:plain-text-body></ac:macro>

    2. Sep 16, 2006

      <p>Oh I think I read your post wrong. You want a default parameter such as <code>$request->getQuery('loggedIn', false)</code> ?</p>

      <p>In the above post I was wrongly thinking you wanted something like <code>$request->setDefaultNotFoundValue(false)</code></p>

      1. Sep 17, 2006

        <p>Yeah, the optional parameter, $request->getQuery('loggedIn', false).<br />
        I'd have to make wrappers for it if you didn't, and I would presume a lot of developers would find it useful too <ac:emoticon ac:name="smile" /></p>

  3. Sep 20, 2006

    <p>I'm not sure why "added support for ActionControllers in subdirectories" is part of this proposal when the rest of it is to do with a Request object and the ramifications on the Router.</p>

    <p>Also what are the proposed changes to Zend_Controller_RewriteRouter to create Zend_Router_Rewrite? Removal of detectRewriteBase() and fix up route() to not use $_SERVER but use a Zend_Request_Interface instead?</p>