[Solar-talk] Sub-controllers re-visited [long]

Antti Holvikari anttih at gmail.com
Sun Nov 18 14:48:57 CST 2007


On Nov 18, 2007 8:22 PM, Clay Loveless <clay at killersoft.com> wrote:
> Hey folks,
>
> Long-time lurker, long-time-since-I-posted ... :)
>
> I'm in the midst of a fairly heavy coding binge, and wanted to throw
> this idea out for litmus testing.
>
> The topic of Solar_Controller_Page routing has come up many times on
> the list, as the topic of breaking up large applications into sub-
> controllers somehow. (Links-a-plenty to those topics if you browse
> the list archives from October 2006 and early 2007.)
>
> The problem that I'm having that seems like it would benefit from sub-
> controller organization is this:
>
> - I have a giant mess of an application in one area of my
> Vendor_App_* tree. Let's call this mess Vendor_App_Disaster.
>
> - I want to split that beast into more manageable chunks. Many of the
> methods within the app are complex enough that they should really be
> their own top-level Vendor_App_* controllers.
>
> - However, they're all part of what seems (or should seem) to a
> visitor to the website like one cohesive application/section of the
> site.
>
> Presently, portions of that app are accessed like this:
>
> example.com/disaster/foo
> example.com/disaster/bar
>
> ... which trigger Vendor_App_Disaster::actionFoo() and
> Vendor_App_Disaster::actionBar() respectively.
>
> While I like this from a URL readability perspective, it's really a
> horrific mess within the Disaster.
>
> Existing Solar convention would dictate that the Disaster be split up
> into separate controllers, at Vendor_App_DisasterFoo and
> Vendor_App_DisasterBar. The parts about that I don't like are:
>
> - It becomes more difficult for DisasterFoo to share view partials
> with DisasterBar.
>
> - URLs become the less-intuitive
>         example.com/disaster-foo
>         example.com/disaster-bar
>
> - If I were ever going to distribute this Disaster, I'd be left with
> two Apps to distribute instead of one.
>
>
> So.
>
>
> I've come up with a solution, but I'm not convinced it's all that
> smart. One main objective of the solution was not having to override
> anything within Solar_Controller_Page, which most of the previously
> proposed routing solutions have suggested. The other plus is that
> URLs won't change when the Disaster is less disastrous -- example.com/
> disaster/foo will still be the URL, which is a very-nice-to-have in
> this kind of refactoring.
>
> Here's what I've done:
>
> - Foo and Bar become helper classes Vendor_App_Disaster_Helper_Foo
> and Vendor_App_Disaster_Helper_Bar.
>
> - Vendor_App_Disaster's _setup() method looks like this:
>
> /**
>   *
>   * Post-constructor setup.
>   *
>   * @return void
>   *
>   */
> protected function _setup()
> {
>      parent::_setup();
>
>      $helper_classes = array();
>      $helpers = new DirectoryIterator(
>          dirname(__FILE__)
>          . DIRECTORY_SEPARATOR
>          . 'Disaster'
>          . DIRECTORY_SEPARATOR
>          . 'Helper');
>
>      foreach ($helpers as $file) {
>          if (! $file->isDot()) {
>              $helper_classes[] = basename($file->getFilename(), '.php');
>          }
>      }
>      $this->_app_helpers = $helper_classes;
>
>      // routing for dummies
>      $this->_route();
> }
>
> Key points:
>         * Crawl the Helper directory for .php files
>         * Call the protected _route() method.
>
>
> - Vendor_App_Disaster's _route() method borrows from
> Solar_Page_Controller::_load(), and looks like this:
>
> /**
>   *
>   * Executes before the first action, and handles unknown methods by
>   * pseudo-routing to controller helpers.
>   *
>   * @return void
>   *
>   */
> protected function _route()
> {
>      // hate to do this, since Solar_Controller_Page::_load() will do it
>      // again, but there's no other clean way to "pre-emptively route"
>      // that I could think of. -- Clay
>      $uri = Solar::factory('Solar_Uri_Action');
>      $this->_info = $uri->path;
>      $this->_query = $uri->query;
>      $this->_format = $uri->format;
>
>      // if the first param is the page name, drop it.
>      // needed when no spec is passed and we're using the default URI.
>      if (! empty($this->_info[0]) && $this->_info[0] == $this->_name) {
>          array_shift($this->_info);
>      }
>
>      // check first element of info
>      if (! empty($this->_info)) {
>          $helper = ucfirst($this->_info[0]);
>          if (in_array($helper, $this->_app_helpers)) {
>              $app_helper = 'Vendor_App_Disaster_Helper_' . $helper;
>              $this->_app_helper = Solar::factory($app_helper, array(
>                  'app' => &$this
>              ));
>          }
>      }
> }
>
> Key points:
>         * Get the path to determine if any helper/sub-controller exists.
>         * Instantiate the helper class and give it a reference to the
>           "actual" application.

One thing that might confuse people with this implementation is that
the action could map to a method **or** to a helper class. I'd like to
know everytime I look at the url where the code is that runs the
action. Not a big problem though.

We've all tried to solve this problem at some point. So I must
represent the solution that I came up with:

I've created a custom front controller that **always** maps to a
sub-page controller. Your Disaster -app would map to controllers
Vendor_App_Disaster_Foo and Vendor_App_Disaster_Bar. Both extend
Vendor_App_Disaster and share all of it's assets.

This is probably the obvious solution and not that flexible but it
solved my problem really well. As I said, _all_ that is needed is a
custom front controller :-).

-- 
Antti Holvikari


More information about the Solar-talk mailing list