Implementing Zend Framework’s “Conventional Modular” Directory Structure in Kohana
There is a little conflict in terms here because “modules” are not the same to Kohana as they are to Zend Framework and Yii Framework, which both have support for modular directory structures. In Kohana, modules are like plugins to the framework which can change or add functionality to the core to be made use of in the application. The “Conventional Modular” Directory Structure in Zend Framework is a configuration option, and modules are more like sub-applications; essentially just a neat way to organize various components of an application.
While modules in Kohana can (and often do) have their own controllers and views, they sit below the application layer in terms of cascading priority, whereas the modules in Zend Framework sit above the main application layer. That is, if a module is specified in the route, classes will be searched for first in the module directory, and then in the default directory. I really like this directory structure and want to apply it to Kohana. Because of the naming conflict, I am calling these sub-application directories components instead of modules. Here is an example of what I am shooting for:
/application /cache /config /controllers /template.php (Template_Controller) /welcome.php (Welcome_Controller) /libraries /models /components /admin /controllers /welcome.php (Welcome_Controller) /users.php (Users_Controller) /posts.php (Posts_Controller) /views /template.php (overrides /application/views/template.php) /blog /controllers /posts.php (Posts_Controller) /views /posts.php (from within blog component: new View('posts')) /rest ... /mobile ... ... /modules (Traditional Kohana Modules) /auth /curl ... /system (Kohana Core)
I use the router to determine the current component, implemented inside my branch of the router3 module. The default value can be set for ‘component’ in a route, or the component can be discovered from the uri, or it can fall back to the default component, which by default is the application itself. The default component can also be given a name such as ‘default’ in the config file. To avoid conflicts I have an array in my config file to define the components that are enabled and the router will only attempt to match those components via a regex built into the route.
Hacking the find_file system is not particularly easy, especially when you enable caching because the component is chosen on-the-fly so the results for (‘controller’,’welcome’) will be different from one request to the next. In order to support cascading of all resources in the component directories, the core would have to be hacked. However, I have successfully implemented this as a module which only supports “controllers” and “views” in the component’s directory. Caching of find_file paths is not broken, and components that are not active for the current request are not in the cascading order at all which I believe to be preferable. Any controllers and views that should be shared among all components in the application can go in the application directories, all others can be separated into component directories.
So what does this accomplish? This let’s you easily keep components of your application organized in different directories. I currently do not include the component name in the controller class like Zend does, so your controller class and file names and view paths can be kept short. E.g., for the typical blog example:
/application /controllers /welcome.php (Welcome_Controller for blog frontend) /views /template.php (template for blog frontend) /posts.php (render frontend view of blog posts) /components /admin (URLs starting with admin will use this component) /controllers /welcome.php (Welcome_Controller for admin backend) /views /template.php (template for admin backend) /posts.php (render admin view of blog posts)
Aside from the more compartmentalized directory structure, the template name does not have to be changed for the component’s template to be used, it just works! Also, when instantiating a view, there is no need to specify sub-directories.
// New: $this->template->content = new View('posts'); // Old: public $template = 'admin/template'; $this->template->content = new View('admin/posts');
From a code-management standpoint, this could also have some benefits. For example, do you want to revert the admin component back to an older revision but leave all other components alone? Since the admin components files are all contained under one directory, rather than mixed in with every other component this is a simple task.
The powers that be cannot be convinced that such a directory structure is worthwhile
, or that the Kohana class does not have to be “final” so I’ve unfortunately decided to give up and fork Kohana and track it’s progress.. So much for, as the Kohana homepage puts it, “Loosely coupled architecture” and “Extremely easy to extend”… If anyone is interested in a patch that seamlessly adds this feature, let me know!
Update 2 (4/21/09):
It turns out that the Kohana devs have agreed to make Kohana no longer “final” for 2.4, and in fact have moved the Bootstrap.php file into the application directory as well; Icing on the cake! See r4231