We all know we should be writing portable code. Functionality that’s reusable is sure to pay off in efficiency gains, and a portable code is also naturally clearer, better organized and easier to maintain.
In agency or in-house development of a Magento site, the “full build” cycle makes it easy to fall into a pattern where function and presentation are treated as one entity. Feature code gets peppered throughout one particular theme, ad-hoc modules become unwieldy as unrelated bits of functionality are bolted on over time, and the application becomes over-reliant on assumptions about the site’s specific use cases. And sure enough, when a particularly slick feature becomes relevant to another site down the road, it’s a chore to hunt down all the components that made that feature tick.
A scenario like this comes about because of a “hurry and get it done” mindset, but keeping code more separated, reusable and maintainable doesn’t have to mean a ton of extra time. The following tips can make a big difference with very little extra effort once you’ve practiced them. They’re hardly revelatory, either. In fact, you probably see them all the time in third-party extensions – otherwise known as the scenario where portability is mandatory. The most important effort required is simply a little forethought.
The “all one piece” approach
As a very quick example of the familiar pattern that often tangles theme and functionality, let’s envision a hypothetical site build. Incorporated into the prescribed theme are two features of note: Support for showing a YouTube or Wistia embedded video in the media gallery on the product detail page (using information, like thumbnails, pulled directly from the video service), and custom badges shown on the product listing page.
As we work on the theme and these features together as one, our code base starts to grow, and our end result more often than not is going to end up looking something like this:
app/ code/ local/ MyCompany/ MyStore/ design/ frontend/ mystore/ default/ layout/ local.xml template/ ... catalog/ product/ list.phtml view/ media.phtml
I’ve imagined that the various blocks, helpers and observer methods we need for our videos and product badges have been lumped together in a single module (MyCompany_MyStore). This would be a natural outgrowth of having added a method here and there as we fleshed out the site. Also natural is the fact that the additions to the theme structure itself ended up in direct overrides of the key templates, sitting alongside no doubt numerous other templates in the theme. All necessary layout changes occur within the theme’s local.xml file, and of course, while it’s not shown, any necessary styles have been incorporated into the theme’s main styles.css.
Both pieces of functionality likely rely on product attributes. For the videos, this might be a text attribute to hold one or more embed codes. For the badges, a multi-select attribute with pre-defined badge text. So for an extra wrench in the works, we’ll assume we created these attributes through the easy admin interface.
Looking at the list of files above, there are certainly no clues about where the key logic for the video feature resides. If we want to track that down, we’ll have to start looking in the code itself.
To arrive at a clearer and more portable structure, we need to go back to before development started and ask the important question: What constitutes merely part of our theme, and what constitutes functionality?
The age-old maxim: Separation of concerns
That question may seem obvious, but it takes practice to make it a part of your mindset each and every time you sit down to scope a piece of development. If we ask what parts of our hypothetical site design are “theme” components only, we would quickly identify that our two noted features – videos and product badges – don’t fit the bill.
With this mindset from the beginning, instead of lumping our engineering code together, we create two separate modules specifically for these pieces of functionality: MyCompany_Videos and MyCompany_Badges. The former likely contains a model for fetching info from YouTube or Wistia and a product save observer to do so at the right time, and the latter probably has some helper methods to cleanly process product attributes into the appropriate output. Both certainly contain blocks.
With this dead simple first step, we’ve actually already worked wonders for our code organization. Even if we went no further, we at least have a starting point for tracking down our feature code at a glance.
The caveat here is that you can certainly take this approach of a separate module for every feature too far. We don’t want to end up with 30 modules containing a single helper method each! Grouping small bits of functionality into a catch-all module is just fine. (The simple features in our example, in fact, ride that line in their present form, but we’re running on the assumption that their reach would be broader in the real world.) It’s the kind of judgment call that becomes easier and more intuitive the more you ask the questions of separation of function and presentation.
It’s in our theme files where the rubber meets the road when it comes to separating features, and there are more challenges to be found here than in the first step.
For starters, I’ll suggest that truly feature-focused code shouldn’t go in a site-specific theme at all. If at all possible, it should be placed in a universal location in the fallback scheme: base/default, or rwd/default if the files extend or rely on Magento’s responsive theme. If we’re building a feature like embedded video on the product detail page, why should this be dependent on any particular theme? By locating our files somewhere more global, we ensure our functionality is available on any store we add to our Magento install, without the need for cherry-picking and copying components from theme to theme. More sneakily, it horses us to start asking other questions. Without a specific theme to work in, where do our changes go?
The simplest answer is for layout files. We already have different modules defined for our distinct features; those modules can define their own layout files as well. So now, rather than functionality-related layout additions sharing space with general theme changes in local.xml, we have videos.xml and badges.xml.
For template files, things get trickier. We should start by moving our logic into separate, brand new templates wherever possible. The core theme provides a decent number of text list blocks (defined in layout with type “core/text_list”) that will assist with this; children of these block types will be output automatically. In our case, though, we’re almost certainly going to have to override the markup of two core templates: the product list template (for outputting our badges), and the media template (for outputting our video).
One strategy is to use layout XML to change the template path of the right block. (<action method=“setTemplate”><path>file.phtml</path></action>) This is a fine solution for smaller, little-touched templates. It’s problematic for many others, though. If we change the path of the product list template, we’ve certainly accomplished the goal of making it clear where our feature code resides, but at the cost of maintainability of the theme. If another developer inherits our code and goes looking for the right place to tweak the product list template, he or she might be in for some frustration.
If all else fails, go ahead and use template overrides in your theme to contain the appropriate feature logic. Or try this approach on for size: Splash some more text list blocks right where you need them in your theme, and then make use of them to add content using module-specific template files. Yes, this strategy still depends on a particular theme in order for the functionality to work. But the footprint is as small (and generic) as possible, while the main event is still contained in theme files specific to the feature.
It’s worth noting, though, that Magento’s responsive theme and its adoption of CSS compilation with Sass/Compass make code organization a lot easier without necessitating separate final CSS files. In this new paradigm, styles.css is compiled from the contents of several “partial” files grouped by concern. Thanks to this structure, keeping the styles related to a particular feature cleanly separated is as simple as adding a new partial (e.g., skin/frontend/mystore/default/scss/module/_badges.scss) and importing it within styles.scss. Learn more about making Sass part of your toolset in Magento’s knowledgebase.
As a final note, a truly separate CSS file for a feature does carry the benefit of being able to reside in a fallback theme as prescribed above, and thus free of dependence on a specific theme. This is obviously more relevant for structural CSS than cosmetic, and it again comes down to a judgment call about the complexity of the feature.
Keep it in the code
Our example features rely on a couple of different product attributes, which I made a point to mention might easily be added in the admin. Admin attribute management is a powerful tool for merchants to add to their catalog content without needing to touch code. Creating attributes for layered navigation, product comparison, or the specs list on the product detail page are great uses of this interface.
When a piece of site functionality relies on a particular attribute, however (as in the case of our video embed code and badge text attributes), resorting to admin-entered content is a poor solution. What if one particular attribute is missed when migrating a feature from a staging environment to production? What if its definition isn’t consistent between the two? The clarity and reusability of our feature code also suffer dramatically, as the necessary attributes can only be discerned by finding where they are used. (And even then, their exact definitions can only be guessed at.)
This kind of attribute belongs in the code with the rest of the feature, and the proper place is in a module install/upgrade script. You can find a plethora of examples in the Magento core code. Upgrade scripts are located in the sql directory of a module, and to work the setup resource that matches their directory name must be defined in a node like “global/resources/*/setup” in etc/config.xml. The class Mage_Catalog_Model_Resource_Setup is necessary for creating product attributes, accomplished within the upgrade scripts with the addAttribute method.
This technique is likely to be intimidating to you if you are unfamiliar with it, but it’s a vital step in writing reliable and maintainable feature code. With a little practice and some examination of the core codebase’s use of addAttribute, it will become old hat quickly. (Examine the _prepareValues methods of Mage_Catalog_Model_Resource_Setup and its ancestor Mage_Eav_Model_Entity_Setup for an instant digest of the configuration values you can set on any product attribute.)
The same principle can apply to any piece of content your functionality expects to be present, including CMS static blocks or custom variables. (And don’t dare hard code a product or category ID. Create a System Configuration value to capture such information. More on System Config below.) Any kind of content can be created in an upgrade script, and if the code relies on it, the code is where it belongs.
NOTE: If you want to create CMS content that your feature relies on, the appropriate kind of upgrade script is actually a data upgrade script, which shares the same setup resource definition in config.xml but reside in the data directory instead of sql. The main difference is that the Magento application is fully bootstrapped by the time data upgrade scripts are run, and therefore you can use standard model logic to create and save your content.
Can you turn it off?
For this last section, think about the following question: What if we ever need to remove a site feature? Can it be done easily, or will an attempt to delete a few lines of code lead to a hunt for all the little references and dependencies that are now making everything blow up?
I suppose the answer to the question could itself be a testament to how portable the code was written. Regardless, why not make it possible to enable/disable functionality on our site without touching the code at all? We’ve gone this far in making our feature code as self-contained as possible; why not go the extra mile and add an on/off switch? It’s almost absurdly easy to do so.
You’re no doubt familiar with the System Configuration area of the Magento admin, where a host of site settings live. What our video and product badge modules need is a couple of Yes/No values in this admin section to control whether these features are enabled or not. If you’re unfamiliar with creating such values, there’s nothing to fear at all. A simple XML structure in etc/system.xml of any module defines them, and opening a few such files in the core codebase will demystify the process swiftly. Slightly more effort is required if you want to create a System Configuration section of your own rather than adding to an existing one since these sections carry specific admin user permissions. But even this only requires examining one more type of module config file – etc/adminhtml.xml – to get a handle on.
Once these values are defined, it’s a simple matter of calling Mage::getStoreConfig in the appropriate entry points of our modules to check whether the features are enabled. (Any calls to block methods in layout XML, such as setTemplate, can also use the “ifconfig” property to make their execution conditional on such values.)
The hidden benefits of portable code
A final snapshot of our hypothetical codebase might look something like this:
app/ code/ local/ MyCompany/ Badges/ Videos/ design/ frontend/ rwd/ default/ layout/ badges.xml videos.xml template/ badges/ catalog/ product/ badges.phtml videos/ catalog/ product/ view/ media.phtml skin/ frontend/ mystore/ default/ scss/ ... module/ _videos.scss _badges.scss styles.scss
In this example, we’ve opted to use setTemplate in our layout file to change the location of our product media template, accounting for the new one under the “videos” namespace. We’ve managed to avoid any template overrides with product badges, though, with this feature making use of an entirely new template. And naturally, our new modules contain appropriate install/upgrade scripts to set up required product attributes, as well as defining System Config values for enabling these features.
The immensely improved new structure makes it crystal clear where to find the code related to certain features, and pulling out those features for use elsewhere will be so much easier. Even if we never have occasion to port our code to other sites, the greater maintainability is sure to pay dividends.
Those are the benefits at the development level, and yet they’re not even the only benefits. Throughout this article, I’ve repeatedly mentioned the required mindset of separating theme from function, and this is a cyclical process. Thinking about your project in this way will result in better-organized code, and consistently writing better code will come right back around and improve your way of thinking about requirements. In the terms of Agile methodology, the codebase structure I outlined at the beginning likely came out of a project backlog with simple “product list page” and “product detail page” tasks. A developer practiced in separating form from function would quickly recognize that the video and product badge features deserve to be elevated; they should be talked about, prioritized, and developed independently from the theme. With a routine mentality like this, you’ll deliver working code faster, you’ll drive better conversations with your client or project owner, and you’ll realize a much better foundation for expanding your features in an organic way.