An Object-Oriented Approach for Drupal Module Development
Now that I have been here for more than a month, I have had a solid opportunity to really dive into the inner workings of Drupal. Coming from a background where I worked with object-oriented programming and the MVC design pattern (model, view, controller), it has been a little overwhelming to get a grasp on exactly how Drupal is organized.
I am used to a very clear separation of concerns that the MVC design pattern affords when using popular frameworks such as CodeIgniter and CakePHP. While there is a tool named Movico, which is essentially an MVC approach for structuring a Drupal module, I have yet to fully wrap my brain around how it interacts with Drupal.
MVC is a design pattern where the data is handled by models (mysql), the presentation layer is handled by the views (css/xhtml/jquery), and the controller (classes) monitors traffic and provides some business logic to the overall application. The way the popular frameworks have adopted this pattern for web-based applications is by using a router class which maps url segments to class names and methods. For example, the url "http://www.mydomain.com/about/history" would map to a controller class named "about" and to a method named "history":
class About extends Controller{
public
function __construct()
{
parent::__construct();
}
public function history()
{
do
something here....
}
}
Drupal has a semblance of mapping url segments to functions with its hook_menu api; however, it does not have the clear separation of concerns I am used to. As a result, I have to honestly say that more than once I have wanted to pull my hair out because of all the spaghetti code and repetition that is involved for many tasks within modules. I have been able to reconcile its apparent lack of clear MVC-style code organization by realizing I am very new to the platform (I have a lot to learn)—and the fact that much of Drupal's power comes from its hook architecture.
In an effort to not go bald, I have also tried to find ways to introduce some object orientation to my code and modules to help keep myself dry (avoiding repetition) as much as possible. It also has given me the ability to reuse code across projects and modules. This is being accomplished by creating special helper classes to address common functionality that I was finding as I worked on various solutions. This approach also allows for a level of abstraction that makes it very easy to hide the inner workings of the module or template functions while exposing only those parts that are necessary for the api to make sense.
For example, in a recent module project I created the standard my_module.module file with the necessary my_module_menu that maps urls and segments to module functions. Each menu has its own function file that provides the callbacks necessary to present the content for that item. I then created a special class for my module, which I included in the my_module.module file, making all of the methods available to me in any part of the module.
For each module function where I found repetitive code, I tried to create a static method wrapper that would allow me to isolate its core functionality and abstract it enough so I could reuse it along the way. Since this only works for custom code, I have to still use procedural functions to call core Drupal hooks, but within those hook functions, I can call any static methods in my own class to achieve the application's goals. Below is an example of a static class method:
/**
* Description of static
method
*
* @param int $int
* @return string $result
*/
public static function
method_name($int)
{
$result = 'The common code you need to run.';
return
$result;
}
By wrapping my common functions in a static class, it allows me to call the method in one line and, if necessary, gives me the ability to extend each function as my module or needs change. For example, I could add a new argument to make the method more useful:
/**
* Description of static method
*
* @param int $int
* @param
array $array
* @return string $result
*/
public static function method_name($int,
$array=array())
{
Add array for loop code here or other functionality;
$result =
'The common code you need to run.';
return $result;
}
The full class would look something like this:
/**
* My Module Class
*
* A set of common functions for
my module.
*
* @package module
* @author Digett
*
@copyright (c) 2010 Digett
*/
class my_module_class
{
/**
*
Description of static method
*
* @param int $int
* @param
array $array
* @return string $result
*/
public
static function method_name($int, $array=array())
{
Add
array for loop code here or other functionality;
$result = 'The common code you need to run.';
return $result;
}
}
To call a method in my class inside a procedural function, I type:
my_module_class::method_name()
Using static classes is beneficial in this context because it does not require us to instantiate the class and create a new object each time. Having all our common methods wrapped in a class affords us the portability, reusability, and dryness of code that is essential in larger modules. While Drupal does use some object-oriented type approaches within its architecture (i.e. node and user objects), a full object-oriented approach is not available. Using custom helper classes for your modules or template functions can help alleviate the headaches of spaghetti code that can quickly result without a well-planned-out approach.
Some other benefits of organizing your code like this include instant documentation, if you follow the PHPDoc convention. Using online tools like the PHP Documentor, you could create a fully formatted document that explains your class, methods, and parameters, thereby allowing you to provide very good information about your API; in my case, it helps me remember if I need to pass a string or an array to a method. Also, if you are using IDE's that provide code insight, having a fully documented API using the PHPDoc convention will give you instant access to the methods or arguments for a class method as you call them. IDE's like NetBeans and PHPEd provide this built-in functionality out of the box.
Drupal is a powerful framework which, while not fully object oriented, provides the flexibility to introduce coding patterns that make for a better organization and rapid development.
MONTHLY MARKETING INSIGHTS.
Get thought-provoking and actionable insights to improve how your firm makes a connection with your customers.
LEAVE A COMMENT
I won't argue that there is spaghetti code, that is would be great to use PHP Documentor, that OOP would be nice to use in more drupal places, or that Drupal may slowly drive you mad I will say that understanding some of the patterns in Drupal may help you get it a little better.
For example, Drupal is architected not to be MVC and it should not go there anytime soon. Drupal uses the PAC pattern for its top level architecture. This is further development on MVC and enables more complex UIs than MVC can easily accommodate.
Drupal hooks (most of them) are the observer pattern. Since PHP is new and fresh on every page load having them use a naming pattern rather than registering them on each page load is great for performance.
Projects like Movico try to bolt on a different architecture to Drupal rather than working within the one that already exists. If you want to see how you can do OOP well within the Drupal architecture I'd suggest taking a little time to look at the Views module.
This is very interesting thanks for sharing. The PAC (presentation, abstraction, control) pattern helps explain a lot for me. That will certainly help in further understanding how the code is structured. I think tools like Movico can help developers like me coming from working in an MVC pattern understand what parts of Drupal correspond to what aspects of it's architecture. However I have to admit I have not been able to build a fully functional module in Movico maybe because as you alluded to it is forcing an MVC approach on a PAC architecture, or it could just be I need to learn a lot more.
Alex
You could just put the functions in your static class into the .module file itself...
True. However on larger module projects where you can easily have my hundreds if not thousands of lines of code it can get a bit dicey scrolling such a large file. Also having a static class in it's own file is better practice since you can port that class file over to other modules as needed.
Thanks for sharing.
Alex
Don't feel too discouraged.. I beat my head against the desk for over a year before I really felt like I knew what I was doing with drupal module development. And now that I'm getting back to it after doing MVC stuff for a couple months it feels foreign again. I get the feeling you're expecting the learning process to be easier than it is -- it's probably not going to be.... the learning curve is steep.
Having a static class buys you nothing over just a bunch of functions in PHP. You don't get the free extra layer of indirection of having an object. You can't pass it to another function or method as a parameter. Before 5.3 you can't even extend a static class in anything resembling a useful way.
What you're talking about providing is a utility library for yourself. That's all well and good and makes total sense to do, but just wrapping functions in a class does not a library make. And it doesn't save you from declaring the code twice in two different modules that need it and causing PHP to crash, same as any other code library.
What you want is a utility module with functions. Those are easy. Create a module with no UI components, just an info file and a .module file with your utility library in it. Then make it a dependency for your real site-specific modules. Problem solved, in a way that other Drupal developers will understand, gets you the same benefit as coping and pasting a static class (and then some, since you don't need to re-copy-and-paste each time you update it), and doesn't lead you or anyone else down the wrong path about what OO is or what it actually gets you. Just using the word "class" does not make something OO.
As for MVC, well, that's already been extensively covered elsewhere:
http://www.garfieldtech.com/blog/mvc-vs-pac
http://engineeredweb.com/blog/10/4/its-time-nomvc
Short version: Ruby on Rails is not the definition of MVC.
Documentation also has nothing to do with MVC or OO. Drupal is very well documented overall, although it certainly has room for improvement. Every function or method in Drupal *should* have a full docblock on it; Drupal nominally uses a variant of Doxygen rather than phpDocumentor, but the differences are small and we're moving in the direction of being more like phpDoc. All of core gets parsed and the API documentation made available here: http:;//api.drupal.org/
If you find code in Drupal that doesn't have a docblock on it yet, that is a bug. Please file a patch. :-)
I beg to differ that Static Classes are not OOP. While this is true on a technical level because we are not dealing with an object that we pass around per se it is part of an understanding of Object Oriented Programming. This is why the article is titled an OOP Approach, it is more a way of thinking about organizing your code in a way that makes more sense and is easier to reference and reuse. Pick up any book on OOP and you will find a chapter on Static Classes and their place in o o programming. At the very least the Static Class is definitely better than a bunch of functions just sitting in the code without any real rhyme or reason to how they are organized.
The idea of a utility module is very good and one that makes a lot of sense if we were to want to share code among modules.
True ,Ruby on Rails is not MVC this is why I qualified my statement about MVC stating "The way the popular frameworks have adopted this pattern for web-based applications..." MVC is just one pattern, which has gotten a lot of attention due to Rails, CodeIgniter, Cake and others.
While it is true that documentation has nothing to with either MVC or OO it has everything to do with best practices. If a developer follows certain conventions when writing static classes or any code for that matter they can take advantage of tools that are available to help them work faster. Unfortunately some of these tools do not work on stray functions that sit in a file or are badly commented - they expect a certain structure to work correctly.
I appreciate your feedback and ideas, thanks for sharing.
Just letting everyone know, that there is a new Drupal MVC framework called Drupal Prometheus. We have been using it extensively on a past project and it's awesome. It has advanced URL to namespace/class/function mapping support, input data filtering and validation and also comes with presentation layer that's either region or full page based. It's loadable as Drupal CMS module. Check it out at http://www.drupalprometheus.org
Thanks.
The MVC vs PAC article linked to is in fact splitting hairs--the most popular PHP MVC frameworks out there discourage calling the model from within the view, so that should not be the defining factor in the differences between PAC and MVC, or even anywhere near the top of the list.
The pattern heavily utilized by MANY MVC frameworks out there is that the Model provides access to the data, the controller processes requests, pulls in and formulates model data for the view, and then renders the view. The view is "dumb" much like how it should ideally be in Drupal anyway. The only real PHP code in the view should be things like outputting variables, formatting dates, and looping over arrays to generate lists, tables, etc.
CakePHP and CodeIgniter, for example, are not PAC frameworks; they are MVC frameworks, and they don't work the way the PAC vs MVC article says they should. It's not about how strictly you adhere to the specifications of a certain methodology, but how clearly you define your own use of the chosen methodology and portray it in such a way that users can grasp it and get the most out of it.
Drupal does not clearly define how to develop with it "properly" using the PAC methodology. It exposes a few places where users can insert their own functionality without most people going any deeper than that and figuring out how they can relate best practices in PAC development to their Drupal work.
ANother of my biggest problems with Drupal is its lack of encapsulation. Not a day goes by when I don't have to create long, hard to read names for my functions because they're sharing the same space with all other functions in all other (foreign) modules--and we simply have to rely on the assumption that every other module and theme developer has adhered properly to drupal's naming standards and won't ever use a name that any other plugin's methods might want to use.
My other problem is with separation of concerns. A 'module' is most often not a single entity that performs a single task, but a bundle of functionality whose hooks are usually all just mashed into one .module file. Unless you're creating a new module for every small task you want to accomplish, the code gets quickly unruly and turns into speghetti code.
It's not that it's impossible to create nice, structured code with Drupal, it's that there is no clear standard way of doing it in Drupal. Now that PHP5 is a clear standard, enforcing proper OO patterns would be a great way of ensuring an organized, easy-to-maintain codebase and far greater modularity. With Drupal, it's hit or miss because it's so darn simple to create bad, unmaintainable code when writing themes and modules.
I'm fine with a PAC architecture, I just want Drupal to implement a more object-oriented approach so that modules become easier to standardize, easier to maintain, easier to read and understand, and safer for the global namespace. I'm tired of hearing about how certain parts of drupal 'mimic' objects and encapsulation, because that simply makes it muddier and harder to grasp. There's been a standard object model for years, and there's no reason anymore to work outside of that while still trying to achieve a similar result.
But that's just my two cents, and it comes from an on-and-off relationship with Drupal and other frameworks. Every time I return to Drupal, I'm amazed how many times I find myself doing things I wouldn't even consider in another framework because they just feel like bad practices.
I have updated and moved the movico project to www.drupal.se/movico_7/ and there is a Github repo also at https://github.com/carlmcdade/Movico-D7.
If you surf to the site you'll come straight into the demo. The source code is shown there. I am going to be blogging on how things work and I usually work with the code live so you can see changes immediately.
If anyone has any ideas they would like to see implemented drop me a line at groups.drupal.org or comment in the blog.
[...] An Object-Oriented Approach for Drupal Module Development (or how you will go slowly crazy if you try to treat Drupal like an MVC framework) [...]