Share changes with your team using Features and upgrade paths
Meet hook_install() and hook_update_N()
The Features module is great for keeping configuration in code, with just a few clicks you can bundle your settings together and be sure that changes will be tracked and safely dumped to code at the next feature update.
However, often you will want to share other kinds of modifications with your team. Imagine you suddenly need to add a Taxonomy vocabulary or to enable a couple of modules your feature will be dependent on. Since your team's workflow is completely 100% database free, you cannot pass those changes by simply sharing your database. Still, you need to push those changes to other developers, who already enabled the feature you have been modifying in their development environment. How do you do that?
Make your workflow solid: meet hook_update_N()
Features are modules and modules can have their own upgrade path by implementing <a href="http://api.drupal.org/api/function/hook_update_N">hook_update_N()</a>
. That's exactly what you need.
All the changes the Features module is not keeping track of must be stored in sequential implementation of the hook_update_N()
to be sure that other developers will have them replicated in their database by simply visiting update.php.
Have a look at the following example: we want to add a free-tagging taxonomy vocabulary to our feature. First of all we need to enable the Taxonomy module add it as dependency in its .info
file:
dependencies[] = "taxonomy"
Adding a dependency will not enable the new module on the other developers' local copy, since the feature has already been enabled and dependencies will not be rechecked. To be sure the module will be enabled we have to write an update function:
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #FF8000">/**<br> * Enabling Taxonomy module.<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">feature_example_update_6001</span><span style="color: #007700">() {<br> </span><span style="color: #0000BB">$return </span><span style="color: #007700">= array();<br> </span><span style="color: #0000BB">drupal_install_modules</span><span style="color: #007700">(array(</span><span style="color: #DD0000">'taxonomy'</span><span style="color: #007700">));<br> </span><span style="color: #0000BB">$return</span><span style="color: #007700">[] = array(</span><span style="color: #DD0000">'success' </span><span style="color: #007700">=> </span><span style="color: #0000BB">TRUE</span><span style="color: #007700">, </span><span style="color: #DD0000">'query' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'Enabling Taxonomy module.'</span><span style="color: #007700">);<br> return </span><span style="color: #0000BB">$return</span><span style="color: #007700">;<br>}<br></span><span style="color: #0000BB">?></span></span>
Now it's time to create our free-tagging vocabulary. Since taxonomies are still not supported by the Features module we have to find another way to put this change in code. One way to do it is to create the vocabulary in an update function:
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #FF8000">/**<br> * Create "Tags" vocabulary.<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">feature_example_update_6002</span><span style="color: #007700">() {<br> </span><span style="color: #0000BB">$return </span><span style="color: #007700">= array();<br> </span><span style="color: #0000BB">$vocab </span><span style="color: #007700">= array(<br> </span><span style="color: #DD0000">'name' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'Tags'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'multiple' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'required' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'hierarchy' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'relations' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'weight' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'nodes' </span><span style="color: #007700">=> array(</span><span style="color: #DD0000">'story' </span><span style="color: #007700">=> </span><span style="color: #0000BB">1</span><span style="color: #007700">),<br> </span><span style="color: #DD0000">'tags' </span><span style="color: #007700">=> </span><span style="color: #0000BB">TRUE</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'help' </span><span style="color: #007700">=> </span><span style="color: #0000BB">t</span><span style="color: #007700">(</span><span style="color: #DD0000">'Enter tags related to your post.'</span><span style="color: #007700">),<br> );<br> </span><span style="color: #0000BB">taxonomy_save_vocabulary</span><span style="color: #007700">(</span><span style="color: #0000BB">$vocab</span><span style="color: #007700">); <br> </span><span style="color: #0000BB">$return</span><span style="color: #007700">[] = array(</span><span style="color: #DD0000">'success' </span><span style="color: #007700">=> </span><span style="color: #0000BB">TRUE</span><span style="color: #007700">, </span><span style="color: #DD0000">'query' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'Create "Tags" vocabulary.'</span><span style="color: #007700">);<br> return </span><span style="color: #0000BB">$return</span><span style="color: #007700">;<br>}<br></span><span style="color: #0000BB">?></span></span>
We can now commit our changes. Once the other developers pull the latest version of our feature in their local environment all they need to do is to run a Drupal update by visiting update.php.
The comment on top of the update functions above plays an important role in the workflow since both Drush and update.php are actually able to display it when running drush updatedb
or running update.php
, giving valuable information to the other team members:
$ drush updatedb<br>The following updates are pending:<br><br> feature_example module <br> 6001 - Enabling Taxonomy module. <br> 6002 - Create "Tags" vocabulary. <br><br>Do you wish to run all pending updates? (y/n):
Make your features database free: meet hook_install()
Storing your changes in hook_update_N()
will allow your team to be always up-to-date with the latest development of your feature, making your development workflow solid and maintainable. But what happens if a new developer wants to hop in? Our rule of thumb is "No database sharing", hence the new developer needs to install our project from scratch and, still, we need to guarantee that he will get the complete latest status of the project. To do that we need to copy some of the configuration we have been placing in update functions to the hook_install()
:
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #FF8000">/**<br> * Implementation of hook_install()<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">feature_example_install</span><span style="color: #007700">() {<br> </span><span style="color: #0000BB">$vocab </span><span style="color: #007700">= array(<br> </span><span style="color: #DD0000">'name' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'Tags'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'multiple' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'required' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'hierarchy' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'relations' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'weight' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'nodes' </span><span style="color: #007700">=> array(</span><span style="color: #DD0000">'story' </span><span style="color: #007700">=> </span><span style="color: #0000BB">1</span><span style="color: #007700">),<br> </span><span style="color: #DD0000">'tags' </span><span style="color: #007700">=> </span><span style="color: #0000BB">TRUE</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'help' </span><span style="color: #007700">=> </span><span style="color: #0000BB">t</span><span style="color: #007700">(</span><span style="color: #DD0000">'Enter tags related to your post.'</span><span style="color: #007700">),<br> );<br> </span><span style="color: #0000BB">taxonomy_save_vocabulary</span><span style="color: #007700">(</span><span style="color: #0000BB">$vocab</span><span style="color: #007700">); <br>}<br></span><span style="color: #0000BB">?></span></span>
Structural and development updates
As you might have noticed hook_install()
copies code from the feature_example_update_6002()
and not from
feature_example_update_6001()
. This is because the two updates have a different nature: 6002
is a structural update, meaning that it is something we must guarantee even if the feature will be installed from scratch; 6001
is a development update which only aims to upgrade an already working development copy.
When writing your upgrade paths, it's good practice to distinguish between two kinds of updates, and in the case of a structural update, make sure you copy changes to the hook_install()
of your feature.
Real life examples
Upgrade and install hooks are really powerful, you can put virtually anything in there. Below we list some real life example of upgrade paths we use in our projects at Nuvole.
Add a menu and menu items
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #FF8000">/**<br> * Implementation of hook_install()<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">master_site_install</span><span style="color: #007700">() {<br> <br> </span><span style="color: #FF8000">// Create a custom menu called "Manage Content".<br> </span><span style="color: #0000BB">db_query</span><span style="color: #007700">(</span><span style="color: #DD0000">"INSERT INTO {menu_custom} (menu_name, title, description) <br> VALUES ('%s', '%s', '%s')"</span><span style="color: #007700">, <br> </span><span style="color: #DD0000">'menu-content'</span><span style="color: #007700">, <br> </span><span style="color: #DD0000">'Manage Content'</span><span style="color: #007700">, <br> </span><span style="color: #DD0000">'Manage your site content.'</span><span style="color: #007700">);<br><br> </span><span style="color: #FF8000">// Add "Home" menu item to Primary Links menu. <br> </span><span style="color: #0000BB">$item</span><span style="color: #007700">[</span><span style="color: #DD0000">'link_title'</span><span style="color: #007700">] = </span><span style="color: #0000BB">t</span><span style="color: #007700">(</span><span style="color: #DD0000">'Home'</span><span style="color: #007700">);<br> </span><span style="color: #0000BB">$item</span><span style="color: #007700">[</span><span style="color: #DD0000">'link_path'</span><span style="color: #007700">] = </span><span style="color: #DD0000">'<front>'</span><span style="color: #007700">;<br> </span><span style="color: #0000BB">$item</span><span style="color: #007700">[</span><span style="color: #DD0000">'menu_name'</span><span style="color: #007700">] = </span><span style="color: #DD0000">'primary-links'</span><span style="color: #007700">;<br> </span><span style="color: #0000BB">$item</span><span style="color: #007700">[</span><span style="color: #DD0000">'weight'</span><span style="color: #007700">] = -</span><span style="color: #0000BB">10</span><span style="color: #007700">;<br> </span><span style="color: #0000BB">menu_link_save</span><span style="color: #007700">(</span><span style="color: #0000BB">$item</span><span style="color: #007700">);<br>}<br></span><span style="color: #0000BB">?></span></span>
Add OpenId to admin account
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #FF8000">/**<br> * Add Nuvole OpenID to admin account.<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">nuvole_site_update_6004</span><span style="color: #007700">() {<br> </span><span style="color: #0000BB">$return </span><span style="color: #007700">= array();<br> </span><span style="color: #FF8000">// Delete any other association of the OpenId account to avoid conflicts.<br> </span><span style="color: #0000BB">$return</span><span style="color: #007700">[] = </span><span style="color: #0000BB">update_sql</span><span style="color: #007700">(</span><span style="color: #DD0000">"DELETE FROM {authmap} <br> WHERE authname = 'http://nuvole.myopenid.com/'"</span><span style="color: #007700">); <br> </span><span style="color: #FF8000">// Bind OpenId account to admin user.<br> </span><span style="color: #0000BB">$return</span><span style="color: #007700">[] = </span><span style="color: #0000BB">update_sql</span><span style="color: #007700">(</span><span style="color: #DD0000">"INSERT INTO {authmap} (uid, authname, module) <br> VALUES (1, 'http://nuvole.myopenid.com/', 'openid')"</span><span style="color: #007700">); <br> return </span><span style="color: #0000BB">$return</span><span style="color: #007700">; <br>}<br></span><span style="color: #0000BB">?></span></span>
Upgrading data from OpenLayers 1.x to 2.x
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #FF8000">/**<br> * Upgrade OpenLayers 1.x to 2.x: WKT data in "content_type_opera"<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">publicopera_site_update_6007</span><span style="color: #007700">() {<br><br> </span><span style="color: #FF8000">// OpenLayers 2.x stores WKT values as geometry collection.<br> // Update data accordingly.<br> </span><span style="color: #0000BB">db_query</span><span style="color: #007700">(</span><span style="color: #DD0000">"UPDATE {content_type_opera} SET field_opera_map_openlayers_wkt = <br> CONCAT('GEOMETRYCOLLECTION(', field_opera_map_openlayers_wkt, ')')"</span><span style="color: #007700">);<br> return array(array(</span><span style="color: #DD0000">'success' </span><span style="color: #007700">=> </span><span style="color: #0000BB">TRUE</span><span style="color: #007700">, </span><span style="color: #DD0000">'query' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'All WKT content updated.'</span><span style="color: #007700">));<br>}<br></span><span style="color: #0000BB">?></span></span>
Tags: Code Driven DevelopmentDrupal Planet