[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